UNPKG

@mantine/core

Version:

React components library focused on usability, accessibility and developer experience

1 lines 15.2 kB
{"version":3,"file":"OverflowList.cjs","names":["createVarsResolver","getSpacing","genericFactory","useProps","useStyles","useDimensions","getRowPositionsData","Box","Fragment","classes"],"sources":["../../../src/components/OverflowList/OverflowList.tsx"],"sourcesContent":["// Originally based on https://github.com/Eliav2/react-responsive-overflow-list (MIT License)\n// Contains the modified version adapted for Mantine\nimport { cloneElement, useMemo, useRef, useState } from 'react';\nimport { Fragment } from 'react/jsx-runtime';\nimport { useIsomorphicEffect, useMergedRef } from '@mantine/hooks';\nimport {\n Box,\n BoxProps,\n createVarsResolver,\n ElementProps,\n Factory,\n genericFactory,\n getSpacing,\n MantineSpacing,\n StylesApiProps,\n useProps,\n useStyles,\n} from '../../core';\nimport { getRowPositionsData } from './get-row-position-data';\nimport { useDimensions } from './use-dimensions';\nimport classes from './OverflowList.module.css';\n\nexport type OverflowListStylesNames = 'root';\nexport type OverflowListCssVariables = {\n root: '--ol-gap';\n};\n\nexport interface OverflowListProps<T = any>\n extends BoxProps, StylesApiProps<OverflowListFactory>, ElementProps<'div', 'children'> {\n /** Array of items to display */\n data: T[];\n\n /** Function to render item */\n renderItem: (item: T, index: number) => React.ReactNode;\n\n /** Function to render hidden items */\n renderOverflow: (items: T[]) => React.ReactNode;\n\n /** Number of rows to display @default 1 */\n maxRows?: number;\n\n /** Maximum number of visible items @default Infinity */\n maxVisibleItems?: number;\n\n /** Key of `theme.spacing` or any valid CSS value for `gap`, numbers are converted to rem @default 'xs' */\n gap?: MantineSpacing;\n\n /** Direction from which items are collapsed when they overflow, `'end'` collapses last items, `'start'` collapses first items @default 'end' */\n collapseFrom?: 'start' | 'end';\n\n /** A function to resolve a unique key for each item. Used to detect when the contents of `data`\n * change (for example when items are reordered while the length stays the same) so the\n * visible/overflow split can be recomputed. Required to detect reordering when `data` contains\n * objects; for primitive items (strings, numbers) the item value is used by default. */\n getItemKey?: (item: T, index: number) => React.Key;\n}\n\nexport type OverflowListFactory = Factory<{\n props: OverflowListProps<any>;\n ref: HTMLDivElement;\n stylesNames: OverflowListStylesNames;\n vars: OverflowListCssVariables;\n signature: <T = any>(props: OverflowListProps<T>) => React.JSX.Element;\n}>;\n\nconst defaultProps = {\n maxRows: 1,\n maxVisibleItems: Infinity,\n} satisfies Partial<OverflowListProps<any>>;\n\nconst varsResolver = createVarsResolver<OverflowListFactory>((_, { gap }) => ({\n root: {\n '--ol-gap': getSpacing(gap),\n },\n}));\n\nfunction getDataSignature<T>(\n data: T[],\n getItemKey: ((item: T, index: number) => React.Key) | undefined\n): string {\n return data\n .map((item, index) => {\n if (getItemKey) {\n return getItemKey(item, index);\n }\n return item !== null && (typeof item === 'object' || typeof item === 'function')\n ? index\n : String(item);\n })\n .join('\\u0000');\n}\n\nexport const OverflowList = genericFactory<OverflowListFactory>((_props) => {\n const props = useProps('OverflowList', defaultProps, _props);\n const {\n classNames,\n className,\n style,\n styles,\n unstyled,\n vars,\n attributes,\n data,\n renderOverflow,\n renderItem,\n maxRows,\n maxVisibleItems,\n collapseFrom,\n getItemKey,\n ref,\n ...others\n } = props;\n\n const getStyles = useStyles<OverflowListFactory>({\n name: 'OverflowList',\n classes,\n props,\n className,\n style,\n classNames,\n styles,\n unstyled,\n attributes,\n vars,\n varsResolver,\n });\n\n const [visibleCount, setVisibleCount] = useState(data.length);\n const [subtractCount, setSubtractCount] = useState(0);\n const [phase, setPhase] = useState<'normal' | 'measuring' | 'measuring-overflow-indicator'>(\n 'normal'\n );\n\n const containerRef = useRef<HTMLElement>(null);\n const rootRef = useMergedRef(containerRef, ref);\n const finalVisibleCount = visibleCount - subtractCount;\n const overflowCount = data.length - finalVisibleCount;\n const showOverflow = overflowCount > 0 && phase !== 'measuring';\n const isCollapseStart = collapseFrom === 'start';\n const overflowItems = isCollapseStart\n ? data.slice(0, data.length - finalVisibleCount)\n : data.slice(finalVisibleCount);\n const overflowElement = showOverflow ? renderOverflow?.(overflowItems) : null;\n\n const _overflowRef = useRef<HTMLElement>(null);\n const overflowRef = useMergedRef(_overflowRef, (overflowElement as any)?.ref);\n const dimensions = useDimensions(containerRef);\n const dataKey = useMemo(() => getDataSignature(data, getItemKey), [data, getItemKey]);\n\n useIsomorphicEffect(() => {\n setPhase('measuring');\n setVisibleCount(data.length);\n setSubtractCount(0);\n }, [dataKey, maxRows, collapseFrom]);\n\n useIsomorphicEffect(() => {\n if (phase === 'measuring') {\n countVisibleItems();\n setPhase('measuring-overflow-indicator');\n }\n }, [phase]);\n\n useIsomorphicEffect(() => {\n if (phase === 'measuring-overflow-indicator') {\n const updateWasNeeded = updateOverflowIndicator();\n if (!updateWasNeeded) {\n setPhase('normal');\n }\n }\n }, [phase, subtractCount]);\n\n useIsomorphicEffect(() => {\n if (phase === 'normal') {\n setPhase('measuring');\n setSubtractCount(0);\n }\n }, [dimensions]);\n\n const fitsInRows = (\n itemWidths: number[],\n containerWidth: number,\n columnGap: number,\n startIndex = 0\n ) => {\n let rows = 1;\n let rowWidth = 0;\n\n for (let i = startIndex; i < itemWidths.length; i += 1) {\n const width = itemWidths[i];\n const needed = rowWidth > 0 ? width + columnGap : width;\n\n if (rowWidth + needed > containerWidth && rowWidth > 0) {\n rows++;\n if (rows > maxRows!) {\n return false;\n }\n rowWidth = width;\n } else {\n rowWidth += needed;\n }\n }\n\n return true;\n };\n\n const countVisibleItems = () => {\n const rowData = getRowPositionsData(containerRef, _overflowRef);\n if (!rowData) {\n return;\n }\n\n const container = containerRef.current;\n if (!container) {\n return;\n }\n\n if (isCollapseStart) {\n const containerWidth = container.getBoundingClientRect().width;\n const columnGap = parseFloat(getComputedStyle(container).columnGap) || 0;\n const children = rowData.children;\n const widths = children.map((child) => child.getBoundingClientRect().width);\n\n let count = 0;\n for (let i = widths.length - 1; i >= 0; i--) {\n if (!fitsInRows(widths, containerWidth, columnGap, i)) {\n break;\n }\n count = widths.length - i;\n }\n\n count = Math.min(count, maxVisibleItems!);\n setVisibleCount(count);\n return;\n }\n\n if (data.length === 1) {\n const itemRef = rowData.itemsSizesMap[rowData.rowPositions[0]].elements.values().next().value;\n const containerWidth = container.getBoundingClientRect().width;\n const itemWidth = itemRef?.getBoundingClientRect().width ?? 0;\n\n if (itemWidth > containerWidth) {\n setVisibleCount(0);\n } else {\n setVisibleCount(1);\n }\n\n return;\n }\n\n const visibleRowPositions = rowData.rowPositions.slice(0, maxRows);\n\n let fittingCount = visibleRowPositions.reduce((acc, position) => {\n return acc + rowData.itemsSizesMap[position].elements.size;\n }, 0);\n\n fittingCount = Math.min(fittingCount, maxVisibleItems);\n setVisibleCount(fittingCount);\n };\n\n const updateOverflowIndicator = () => {\n if (!_overflowRef.current) {\n return false;\n }\n const rowData = getRowPositionsData(containerRef, _overflowRef);\n if (!rowData) {\n return false;\n }\n\n const { rowPositions, itemsSizesMap } = rowData;\n\n if (isCollapseStart) {\n const container = containerRef.current;\n if (!container) {\n return false;\n }\n const containerWidth = container.getBoundingClientRect().width;\n const columnGap = parseFloat(getComputedStyle(container).columnGap) || 0;\n const overflowWidth = _overflowRef.current.getBoundingClientRect().width;\n const children = rowData.children;\n const itemWidths = [\n overflowWidth,\n ...children.map((child) => child.getBoundingClientRect().width),\n ];\n\n if (!fitsInRows(itemWidths, containerWidth, columnGap)) {\n setSubtractCount((c) => c + 1);\n return true;\n }\n\n return false;\n }\n\n const overflowRect = _overflowRef.current.getBoundingClientRect();\n const overflowMiddleY = overflowRect.top + overflowRect.height / 2;\n const lastRowTop = rowPositions[rowPositions.length - 1];\n const lastRow = itemsSizesMap[lastRowTop];\n\n if (overflowMiddleY > lastRow.bottom) {\n setSubtractCount((c) => c + 1);\n return true;\n }\n\n return false;\n };\n\n const clonedOverflowElement = overflowElement\n ? cloneElement(overflowElement as React.ReactElement<any>, { ref: overflowRef })\n : null;\n\n let finalItems = data;\n if (maxVisibleItems) {\n finalItems = isCollapseStart\n ? finalItems.slice(-maxVisibleItems!)\n : finalItems.slice(0, maxVisibleItems);\n }\n\n const indexOffset = isCollapseStart ? data.length - finalItems.length : 0;\n\n return (\n <Box ref={rootRef} {...getStyles('root')} {...others}>\n {isCollapseStart && clonedOverflowElement}\n\n {finalItems.map((item, index) => {\n const isVisible =\n phase === 'measuring' ||\n (isCollapseStart\n ? index >= finalItems.length - finalVisibleCount\n : index < finalVisibleCount);\n if (!isVisible) {\n return null;\n }\n const dataIndex = indexOffset + index;\n const itemComponent = renderItem(item, dataIndex);\n\n return <Fragment key={dataIndex}>{itemComponent}</Fragment>;\n })}\n\n {!isCollapseStart && clonedOverflowElement}\n </Box>\n );\n});\n\nOverflowList.displayName = '@mantine/core/OverflowList';\nOverflowList.classes = classes;\nOverflowList.varsResolver = varsResolver;\n\nexport namespace OverflowList {\n export type Props = OverflowListProps;\n export type Factory = OverflowListFactory;\n export type StylesNames = OverflowListStylesNames;\n export type CssVariables = OverflowListCssVariables;\n}\n"],"mappings":"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"}