UNPKG

@mantine/core

Version:

React components library focused on usability, accessibility and developer experience

1 lines 13.6 kB
{"version":3,"file":"CascaderColumns.mjs","names":[],"sources":["../../../src/components/Cascader/CascaderColumns.tsx"],"sourcesContent":["import { useRef, useState } from 'react';\nimport type { GetStylesApi } from '../../core';\nimport { AccordionChevron } from '../Accordion';\nimport { CheckIcon } from '../Checkbox/CheckIcon';\nimport { ScrollArea, ScrollAreaProps } from '../ScrollArea';\nimport { UnstyledButton } from '../UnstyledButton';\nimport type {\n CascaderOption,\n CascaderSafeAreaPolygonOptions,\n CascaderStylesNames,\n} from './Cascader';\nimport { cascaderOptionHasChildren, getCascaderColumns } from './get-cascader-columns';\nimport { useCascaderSafeArea } from './use-cascader-safe-area';\n\ntype CascaderColumnsGetStyles = GetStylesApi<{ props: any; stylesNames: CascaderStylesNames }>;\n\nexport interface CascaderColumnsProps {\n data: CascaderOption[];\n activePath: string[];\n value: string[] | null;\n keyboardNav: boolean;\n withCheckIcon: boolean | undefined;\n checkIconPosition: 'left' | 'right' | undefined;\n renderOption: ((option: CascaderOption, level: number) => React.ReactNode) | undefined;\n columnWidth: number | string | undefined;\n maxDisplayedLevels: number | undefined;\n previousLevelsControlLabel: string | undefined;\n nextLevelsControlLabel: string | undefined;\n maxDropdownHeight: number | string | undefined;\n nothingFoundMessage: React.ReactNode;\n getStyles: CascaderColumnsGetStyles;\n unstyled: boolean | undefined;\n scrollAreaProps: ScrollAreaProps | undefined;\n onOptionClick: (level: number, option: CascaderOption) => void;\n onOptionMouseEnter: (level: number, option: CascaderOption) => void;\n onColumnsMouseLeave: (() => void) | undefined;\n onPointerActivity: (() => void) | undefined;\n listId: string | undefined;\n safeAreaPolygon: boolean | CascaderSafeAreaPolygonOptions | undefined;\n}\n\nexport function getCascaderOptionId(listId: string | undefined, level: number, value: string) {\n return listId ? `${listId}-${level}-${value}` : undefined;\n}\n\nexport function CascaderColumns({\n data,\n activePath,\n value,\n keyboardNav,\n withCheckIcon,\n checkIconPosition,\n renderOption,\n columnWidth,\n maxDisplayedLevels,\n previousLevelsControlLabel,\n nextLevelsControlLabel,\n maxDropdownHeight,\n nothingFoundMessage,\n getStyles,\n unstyled,\n scrollAreaProps,\n onOptionClick,\n onOptionMouseEnter,\n onColumnsMouseLeave,\n onPointerActivity,\n listId,\n safeAreaPolygon,\n}: CascaderColumnsProps) {\n const allColumns = getCascaderColumns(data, activePath);\n const totalColumns = allColumns.length;\n const maxLevels =\n maxDisplayedLevels && maxDisplayedLevels > 0 ? maxDisplayedLevels : totalColumns;\n const maxOffset = Math.max(0, totalColumns - maxLevels);\n const focusedLevel = activePath.length - 1;\n const showCheckIcon = withCheckIcon !== false;\n\n // Manual left/right scrolling of the visible window. It is reset synchronously whenever the\n // active path changes so that the focused column always stays visible during navigation.\n const activePathKey = JSON.stringify(activePath);\n const [windowState, setWindowState] = useState({ key: activePathKey, offset: 0 });\n const windowOffset = windowState.key === activePathKey ? windowState.offset : 0;\n if (windowState.key !== activePathKey) {\n setWindowState({ key: activePathKey, offset: 0 });\n }\n const setWindowOffset = (updater: (current: number) => number) =>\n setWindowState((current) => ({ key: activePathKey, offset: updater(current.offset) }));\n\n const clampedOffset = Math.min(windowOffset, maxOffset);\n const windowStart = Math.max(0, maxOffset - clampedOffset);\n const windowEnd = Math.min(totalColumns, windowStart + maxLevels);\n const visibleColumns = allColumns.slice(windowStart, windowEnd);\n const hiddenBefore = windowStart;\n const hiddenAfter = totalColumns - windowEnd;\n\n const check = showCheckIcon ? <CheckIcon {...getStyles('columnOptionCheck')} /> : null;\n\n const columnRefs = useRef<Record<number, HTMLDivElement | null>>({});\n const safeArea = useCascaderSafeArea({ safeAreaPolygon, onFlush: onOptionMouseEnter });\n\n return (\n <div\n {...getStyles('columnsList')}\n role=\"presentation\"\n onMouseLeave={() => {\n safeArea.disarm();\n onColumnsMouseLeave?.();\n }}\n onMouseMove={onPointerActivity}\n >\n {hiddenBefore > 0 && (\n <UnstyledButton\n {...getStyles('columnsOverflow')}\n data-position=\"start\"\n tabIndex={-1}\n unstyled={unstyled}\n aria-label={previousLevelsControlLabel}\n title={previousLevelsControlLabel}\n onMouseDown={(event) => event.preventDefault()}\n onClick={() => setWindowOffset((current) => Math.min(maxOffset, current + 1))}\n >\n <AccordionChevron />\n </UnstyledButton>\n )}\n\n {visibleColumns.map((options, index) => {\n const level = windowStart + index;\n const isLastColumn = index === visibleColumns.length - 1 && hiddenAfter === 0;\n\n return (\n <div\n key={level}\n ref={(node) => {\n columnRefs.current[level] = node;\n }}\n data-last={isLastColumn || undefined}\n {...getStyles('column', {\n style: columnWidth ? { width: columnWidth, minWidth: columnWidth } : undefined,\n })}\n >\n <ScrollArea.Autosize\n mah={maxDropdownHeight ?? 260}\n type=\"scroll\"\n scrollbarSize=\"var(--combobox-padding)\"\n role=\"listbox\"\n {...scrollAreaProps}\n {...getStyles('columnScroll')}\n >\n {options.length === 0 ? (\n <div {...getStyles('columnEmpty')}>{nothingFoundMessage}</div>\n ) : (\n options.map((option) => {\n const isActive = activePath[level] === option.value;\n const isCurrent = isActive && level === focusedLevel && keyboardNav;\n // The trail only covers ancestor columns; the deepest column is not highlighted\n // during mouse navigation (only via the keyboard \"current\" state)\n const isInPath = isActive && level < focusedLevel;\n const hasChildren = cascaderOptionHasChildren(option);\n const isSelected =\n !!value && value.length === level + 1 && value[level] === option.value;\n\n return (\n <UnstyledButton\n key={option.value}\n id={getCascaderOptionId(listId, level, option.value)}\n role=\"option\"\n aria-selected={isCurrent || undefined}\n aria-disabled={option.disabled || undefined}\n data-active={isCurrent || undefined}\n data-in-path={isInPath || undefined}\n data-selected={isSelected || undefined}\n data-disabled={option.disabled || undefined}\n tabIndex={-1}\n unstyled={unstyled}\n onMouseDown={(event) => event.preventDefault()}\n onClick={() => {\n if (!option.disabled) {\n onOptionClick(level, option);\n }\n }}\n onMouseEnter={() => {\n if (option.disabled) {\n return;\n }\n\n if (safeArea.isBlocked(level)) {\n safeArea.setPending(level, option);\n return;\n }\n\n onOptionMouseEnter(level, option);\n }}\n onMouseLeave={(event) => {\n safeArea.clearPending(level, option);\n\n if (!safeArea.enabled || option.disabled) {\n return;\n }\n\n if (activePath[level] !== option.value) {\n return;\n }\n\n const floating = columnRefs.current[level + 1];\n\n if (!floating) {\n return;\n }\n\n safeArea.arm({\n level,\n reference: event.currentTarget,\n floating,\n clientX: event.clientX,\n clientY: event.clientY,\n });\n }}\n {...getStyles('columnOption')}\n >\n {isSelected && checkIconPosition === 'left' && check}\n\n <span {...getStyles('columnOptionLabel')}>\n {renderOption\n ? renderOption(option, level)\n : (option.label ?? option.value)}\n </span>\n\n {isSelected && checkIconPosition !== 'left' && check}\n\n {hasChildren && (\n <span {...getStyles('columnOptionIcon')}>\n <AccordionChevron />\n </span>\n )}\n </UnstyledButton>\n );\n })\n )}\n </ScrollArea.Autosize>\n </div>\n );\n })}\n\n {hiddenAfter > 0 && (\n <UnstyledButton\n {...getStyles('columnsOverflow')}\n data-position=\"end\"\n tabIndex={-1}\n unstyled={unstyled}\n aria-label={nextLevelsControlLabel}\n title={nextLevelsControlLabel}\n onMouseDown={(event) => event.preventDefault()}\n onClick={() => setWindowOffset((current) => Math.max(0, current - 1))}\n >\n <AccordionChevron />\n </UnstyledButton>\n )}\n </div>\n );\n}\n\nCascaderColumns.displayName = '@mantine/core/CascaderColumns';\n"],"mappings":"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"}