Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 | 8x 11x 11x 8x 6x 2x 8x 11x 6x 8x 8x 8x 8x 2x 6x 6x 31x 31x 31x 31x 6x 6x 5x 6x | import {
type FC,
Children,
useState,
type ReactNode,
type HTMLAttributes,
} from 'react';
import cn from 'classnames';
import Button from './button';
import '../styles/components/expandable-list.scss';
type ExpandableMessageProps = {
expanded?: boolean;
setExpanded: (expanded: boolean) => unknown;
descriptionString?: string;
showHideWording?: boolean;
nHiddenItems?: number;
};
export const ExpandableMessage: FC<ExpandableMessageProps> = ({
descriptionString = 'items',
expanded,
setExpanded,
showHideWording,
nHiddenItems,
}) => {
let message = `${showHideWording ? 'Hide' : 'Fewer'} ${descriptionString}`;
if (!expanded) {
if (nHiddenItems === undefined) {
message = showHideWording ? 'Show' : 'More';
} else {
message = showHideWording
? `Show ${nHiddenItems}`
: `${nHiddenItems} more`;
}
message += ` ${descriptionString}`;
}
return (
<Button
variant="tertiary"
className="expandable-list__action"
onClick={() => setExpanded(!expanded)}
data-testid="expandable-message"
>
{message}
</Button>
);
};
type ExpandableListProps = {
/**
* Children as an array of react elements, items of the list
*/
children?: ReactNode;
/**
* Threshold from which to start hiding items of the list
*/
numberCollapsedItems?: number;
/**
* Description of the items to put in text of the open/close button
*/
descriptionString?: string;
/**
* Wether to show or hide the visual bullet points
*/
showBullets?: boolean;
/**
* Extra element to place alongside the open/close button
*/
extraActions?: ReactNode;
/**
* Wether to display or not the number of hidden elements
*/
displayNumberOfHiddenItems?: boolean;
/**
* Classnames to be added to the list container
*/
className?: string;
};
export const ExpandableList = ({
children: c,
numberCollapsedItems = 5,
descriptionString = 'items',
showBullets,
extraActions,
displayNumberOfHiddenItems,
className,
...props
}: ExpandableListProps & HTMLAttributes<HTMLUListElement>) => {
const [expanded, setExpanded] = useState(false);
// get an array of children, filter out null or undefined children to avoid
// counting them towards the threshold limit
const children = Children.toArray(c).filter(Boolean);
if (!children.length) {
return null;
}
const enoughChildren = children.length > numberCollapsedItems + 1;
const itemNodes = Children.map(
children.slice(
0,
expanded || !enoughChildren ? children.length : numberCollapsedItems
),
(child, index) => {
let key: string | number = index;
Eif (typeof child === 'object' && 'key' in child && child.key) {
key = child.key;
}
return <li key={key}>{child}</li>;
}
);
let actions = null;
if (enoughChildren || extraActions) {
actions = (
<li>
{enoughChildren && (
<ExpandableMessage
expanded={expanded}
setExpanded={setExpanded}
descriptionString={descriptionString || 'items'}
showHideWording={numberCollapsedItems === 0}
nHiddenItems={
displayNumberOfHiddenItems
? children.length - numberCollapsedItems
: undefined
}
/>
)}
{extraActions}
</li>
);
}
return (
<ul
className={cn(className, 'expandable-list', {
'no-bullet': !showBullets,
})}
{...props}
>
{itemNodes}
{actions}
</ul>
);
};
export default ExpandableList;
|