diff options
Diffstat (limited to 'src/components/atoms/lists/list/list.tsx')
| -rw-r--r-- | src/components/atoms/lists/list/list.tsx | 150 |
1 files changed, 150 insertions, 0 deletions
diff --git a/src/components/atoms/lists/list/list.tsx b/src/components/atoms/lists/list/list.tsx new file mode 100644 index 0000000..6e58433 --- /dev/null +++ b/src/components/atoms/lists/list/list.tsx @@ -0,0 +1,150 @@ +import { + forwardRef, + type CSSProperties, + type HTMLAttributes, + type OlHTMLAttributes, + type ReactNode, + type ForwardedRef, +} from 'react'; +import type { Spacing } from '../../../../types'; +import styles from './list.module.scss'; + +type OrderedListProps = Omit<OlHTMLAttributes<HTMLOListElement>, 'children'>; + +type UnorderedListProps = Omit<HTMLAttributes<HTMLUListElement>, 'children'>; + +type BaseListProps<O extends boolean, H extends boolean> = O extends true + ? OrderedListProps + : H extends true + ? OrderedListProps + : UnorderedListProps; + +type AdditionalProps<O extends boolean, H extends boolean> = { + /** + * An array of list items. + */ + children: ReactNode; + /** + * Should the items marker be hidden? + * + * @default false + */ + hideMarker?: boolean; + /** + * Should the list be ordered and hierarchical? + * + * @default false + */ + isHierarchical?: H; + /** + * Should the list be inlined? + * + * @default false + */ + isInline?: boolean; + /** + * Should the list be ordered? + * + * @default false + */ + isOrdered?: O; + /** + * Define the spacing between list items. + * + * @default null + */ + spacing?: Spacing | null; +}; + +type BuildClassNameConfig<O extends boolean, H extends boolean> = Pick< + BaseListProps<O, H>, + 'className' +> & + Pick< + AdditionalProps<O, H>, + 'hideMarker' | 'isHierarchical' | 'isInline' | 'isOrdered' + >; + +const buildClassName = <O extends boolean, H extends boolean>({ + className = '', + hideMarker, + isHierarchical, + isInline, + isOrdered, +}: BuildClassNameConfig<O, H>) => { + const orderedClassName = isHierarchical + ? 'list--hierarchical' + : 'list--ordered'; + const classNames: string[] = [ + isHierarchical || isOrdered ? orderedClassName : 'list--unordered', + isInline ? 'list--inline' : 'list--stack', + hideMarker ? 'list--no-marker' : 'list--has-marker', + className, + ].map((key) => styles[key]); + + if (className) classNames.push(className); + + return classNames.join(' '); +}; + +export type ListProps<O extends boolean, H extends boolean> = BaseListProps< + O, + H +> & + AdditionalProps<O, H>; + +const ListWithRef = <O extends boolean, H extends boolean>( + { + className, + children, + hideMarker = false, + isHierarchical, + isInline = false, + isOrdered, + spacing = null, + style, + ...props + }: ListProps<O, H>, + ref: ForwardedRef< + O extends true + ? HTMLOListElement + : H extends true + ? HTMLOListElement + : HTMLUListElement + > +) => { + const itemSpacing = spacing === null ? 0 : `var(--spacing-${spacing})`; + const listClass = buildClassName({ + className, + hideMarker, + isHierarchical, + isInline, + isOrdered, + }); + const listStyles = { + ...style, + '--itemSpacing': itemSpacing, + } as CSSProperties; + + return isHierarchical || isOrdered ? ( + <ol + {...props} + className={listClass} + ref={ref as ForwardedRef<HTMLOListElement>} + style={listStyles} + > + {children} + </ol> + ) : ( + <ul {...props} className={listClass} ref={ref} style={listStyles}> + {children} + </ul> + ); +}; + +/** + * List component + * + * Render either an ordered or an unordered list. + */ +export const List = forwardRef(ListWithRef); |
