Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/index.css

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion lib/index.css.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion lib/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion lib/index.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion lib/tyk-ui.css

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion lib/tyk-ui.css.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion lib/tyk-ui.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion lib/tyk-ui.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@tyk-technologies/tyk-ui",
"version": "4.4.22",
"version": "4.4.23",
"description": "Tyk UI - ui reusable components",
"main": "src/index.js",
"scripts": {
Expand Down
49 changes: 49 additions & 0 deletions src/components/CollapsibleList/CollapsibleList.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
.tyk-collapsible-list {
width: 100%;
}

.tyk-collapsible-list__list {
list-style: none;
margin: 0;
padding: 0;
}

.tyk-collapsible-list__item {
padding: 0;
margin: 0;
}

.tyk-collapsible-list__item-wrapper {
overflow: hidden;
transition: max-height 0.3s ease-in-out, opacity 0.3s ease-in-out, margin 0.3s ease-in-out;
}

.tyk-collapsible-list__item-wrapper--visible {
max-height: 1000px;
opacity: 1;
margin-bottom: 0;
}

.tyk-collapsible-list__item-wrapper--hidden {
max-height: 0;
opacity: 0;
margin-bottom: 0;
pointer-events: none;
}

.tyk-collapsible-list__item-wrapper--animated.tyk-collapsible-list__item-wrapper--visible {
animation: tykCollapsibleListSlideDown 0.3s ease-in-out;
}

@keyframes tykCollapsibleListSlideDown {
from {
max-height: 0;
opacity: 0;
transform: translateY(-10px);
}
to {
max-height: 1000px;
opacity: 1;
transform: translateY(0);
}
}
111 changes: 111 additions & 0 deletions src/components/CollapsibleList/CollapsibleList.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import React from 'react';
import CollapsibleList from './index';

describe('CollapsibleList', () => {
it('renders the component with items', () => {
cy.mount(
<CollapsibleList open={false} visibleCount={2}>
<CollapsibleList.Item>Item 1</CollapsibleList.Item>
<CollapsibleList.Item>Item 2</CollapsibleList.Item>
<CollapsibleList.Item>Item 3</CollapsibleList.Item>
</CollapsibleList>,
);

cy.get('.tyk-collapsible-list').should('exist');
cy.get('.tyk-collapsible-list__list').should('exist');
cy.contains('Item 1').should('exist');
cy.contains('Item 2').should('exist');
cy.contains('Item 3').should('exist');
});

it('shows only visible items when collapsed', () => {
cy.mount(
<CollapsibleList open={false} visibleCount={2}>
<CollapsibleList.Item>Visible 1</CollapsibleList.Item>
<CollapsibleList.Item>Visible 2</CollapsibleList.Item>
<CollapsibleList.Item>Hidden 3</CollapsibleList.Item>
<CollapsibleList.Item>Hidden 4</CollapsibleList.Item>
</CollapsibleList>,
);

cy.get('.tyk-collapsible-list__item-wrapper--visible').should('have.length', 2);
cy.get('.tyk-collapsible-list__item-wrapper--hidden').should('have.length', 2);
});

it('shows all items when open is true', () => {
cy.mount(
<CollapsibleList open={true} visibleCount={1}>
<CollapsibleList.Item>Item 1</CollapsibleList.Item>
<CollapsibleList.Item>Item 2</CollapsibleList.Item>
<CollapsibleList.Item>Item 3</CollapsibleList.Item>
</CollapsibleList>,
);

cy.get('.tyk-collapsible-list__item-wrapper--visible').should('have.length', 3);
cy.get('.tyk-collapsible-list__item-wrapper--hidden').should('not.exist');
});

it('hides all items when visibleCount is 0 and open is false', () => {
cy.mount(
<CollapsibleList open={false} visibleCount={0}>
<CollapsibleList.Item>Item 1</CollapsibleList.Item>
<CollapsibleList.Item>Item 2</CollapsibleList.Item>
</CollapsibleList>,
);

cy.get('.tyk-collapsible-list__item-wrapper--hidden').should('have.length', 2);
cy.get('.tyk-collapsible-list__item-wrapper--visible').should('not.exist');
});

it('applies custom className', () => {
cy.mount(
<CollapsibleList className="custom-class" open={false} visibleCount={1}>
<CollapsibleList.Item>Item 1</CollapsibleList.Item>
</CollapsibleList>,
);

cy.get('.tyk-collapsible-list')
.should('have.class', 'custom-class');
});

it('applies animation classes to items beyond visibleCount', () => {
cy.mount(
<CollapsibleList open={true} visibleCount={1}>
<CollapsibleList.Item>Always visible</CollapsibleList.Item>
<CollapsibleList.Item>Animated 1</CollapsibleList.Item>
<CollapsibleList.Item>Animated 2</CollapsibleList.Item>
</CollapsibleList>,
);

cy.get('.tyk-collapsible-list__item-wrapper--animated').should('have.length', 2);
});

it('renders items with custom content', () => {
cy.mount(
<CollapsibleList open={true} visibleCount={2}>
<CollapsibleList.Item>
<div className="custom-content">
<span>Label:</span>
<strong>Value</strong>
</div>
</CollapsibleList.Item>
</CollapsibleList>,
);

cy.get('.custom-content').should('exist');
cy.contains('Label:').should('exist');
cy.contains('Value').should('exist');
});

it('renders list items with proper structure', () => {
cy.mount(
<CollapsibleList open={true} visibleCount={1}>
<CollapsibleList.Item>Test Item</CollapsibleList.Item>
</CollapsibleList>,
);

cy.get('ul.tyk-collapsible-list__list').should('exist');
cy.get('.tyk-collapsible-list__item').should('exist');
cy.get('.tyk-collapsible-list__item').should('match', 'li');
});
});
65 changes: 65 additions & 0 deletions src/components/CollapsibleList/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
**Basic Example - Show 2 items by default**
```js
import Button from '../Button';
const [isOpen, setIsOpen] = React.useState(false);

<>
<Button onClick={() => setIsOpen(!isOpen)} theme="primary">
{isOpen ? 'Collapse' : 'Expand'}
</Button>
<CollapsibleList open={isOpen} visibleCount={2}>
<CollapsibleList.Item>Item 1 (always visible)</CollapsibleList.Item>
<CollapsibleList.Item>Item 2 (always visible)</CollapsibleList.Item>
<CollapsibleList.Item>Item 3 (hidden by default)</CollapsibleList.Item>
<CollapsibleList.Item>Item 4 (hidden by default)</CollapsibleList.Item>
</CollapsibleList>
</>
```

**Show All Items Initially**
```js
<CollapsibleList open={true} visibleCount={0}>
<CollapsibleList.Item>All items visible</CollapsibleList.Item>
<CollapsibleList.Item>From the start</CollapsibleList.Item>
</CollapsibleList>
```

**Hide All Items by Default**
```js
<CollapsibleList open={false} visibleCount={0}>
<CollapsibleList.Item>Hidden until expanded</CollapsibleList.Item>
<CollapsibleList.Item>Hidden until expanded</CollapsibleList.Item>
</CollapsibleList>
```

## Props

### CollapsibleList

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `children` | `node` | required | The list items (should be `CollapsibleList.Item` components) |
| `open` | `bool` | `false` | Controls whether the list is expanded or collapsed |
| `visibleCount` | `number` | `0` | Number of items visible by default (0 shows none) |
| `className` | `string` | `''` | Additional CSS class for the container |

### CollapsibleList.Item

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `children` | `node` | - | Content of the list item |
| `className` | `string` | `''` | Additional CSS class for the item |

## Animation

- Items fade in with opacity transition (0.3s)
- Items slide down with transform animation
- Each item has a staggered delay (50ms) for cascading effect
- Only items beyond `visibleCount` are animated

## Notes

- The component uses CSS transitions for smooth animations
- Items are rendered as `<ul>` with `<li>` elements
- The `open` prop fully controls visibility - when true, all items show regardless of `visibleCount`
- When `open` is false, only the first `visibleCount` items are visible
63 changes: 63 additions & 0 deletions src/components/CollapsibleList/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import React from 'react';
import PropTypes from 'prop-types';
import CollapsibleListItem from './js/CollapsibleListItem';
import './CollapsibleList.css';

function CollapsibleList(props) {
const {
children,
open = false,
visibleCount = 0,
className = '',
} = props;

const items = React.Children.toArray(children);

const getCssClasses = () => {
let cssClasses = ['tyk-collapsible-list'];

if (className) {
cssClasses = cssClasses.concat(className.split(' '));
}

return cssClasses.join(' ');
};

return (
<div className={getCssClasses()}>
<ul className="tyk-collapsible-list__list">
{items.map((item, index) => {
const shouldShow = open || index < visibleCount;
const isAnimated = index >= visibleCount;

return (
<div
key={index}
className={`tyk-collapsible-list__item-wrapper ${
shouldShow ? 'tyk-collapsible-list__item-wrapper--visible' : 'tyk-collapsible-list__item-wrapper--hidden'
} ${
isAnimated ? 'tyk-collapsible-list__item-wrapper--animated' : ''
}`}
style={{
transitionDelay: isAnimated && open ? `${(index - visibleCount) * 50}ms` : '0ms',
}}
>
{item}
</div>
);
})}
</ul>
</div>
);
}

CollapsibleList.propTypes = {
children: PropTypes.node.isRequired,
open: PropTypes.bool,
visibleCount: PropTypes.number,
className: PropTypes.string,
};

CollapsibleList.Item = CollapsibleListItem;

export default CollapsibleList;
27 changes: 27 additions & 0 deletions src/components/CollapsibleList/js/CollapsibleListItem.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react';
import PropTypes from 'prop-types';

function CollapsibleListItem(props) {
const {
className = '',
children,
} = props;

return (
<li className={`tyk-collapsible-list__item ${className}`}>
{children}
</li>
);
}

CollapsibleListItem.propTypes = {
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
PropTypes.element,
PropTypes.string,
]),
className: PropTypes.string,
};

export default CollapsibleListItem;
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export { default as ButtonGroup } from './components/ButtonGroup';
export { default as Card } from './components/Card';
export { default as Chart } from './components/Chart';
export { default as Collapsible } from './components/Collapsible';
export { default as CollapsibleList } from './components/CollapsibleList';
export { default as CopyToClipboard } from './components/CopyToClipboard';
export { default as Confirm } from './components/Confirm';
export { default as FixedWrapper } from './components/FixedWrapper';
Expand Down
Loading