@astryxdesign/core 0.1.7 → 0.1.8-canary.0c4d8d7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +65 -0
- package/README.md +30 -19
- package/dist/AppShell/AppShell.d.ts.map +1 -1
- package/dist/AppShell/AppShell.js +6 -2
- package/dist/AppShell/AppShellMobileContext.d.ts +8 -0
- package/dist/AppShell/AppShellMobileContext.d.ts.map +1 -1
- package/dist/Avatar/Avatar.d.ts +10 -5
- package/dist/Avatar/Avatar.d.ts.map +1 -1
- package/dist/Avatar/Avatar.js +12 -8
- package/dist/Avatar/AvatarSizeContext.d.ts +1 -1
- package/dist/Avatar/AvatarSizeContext.js +1 -1
- package/dist/Avatar/AvatarStatusDot.d.ts +2 -2
- package/dist/Avatar/AvatarStatusDot.js +2 -2
- package/dist/AvatarGroup/AvatarGroup.d.ts +2 -2
- package/dist/AvatarGroup/AvatarGroup.d.ts.map +1 -1
- package/dist/AvatarGroup/AvatarGroup.js +2 -2
- package/dist/AvatarGroup/AvatarGroupOverflow.d.ts +1 -1
- package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
- package/dist/AvatarGroup/AvatarGroupOverflow.js +16 -14
- package/dist/Banner/Banner.d.ts +9 -1
- package/dist/Banner/Banner.d.ts.map +1 -1
- package/dist/Banner/Banner.js +31 -2
- package/dist/Button/Button.d.ts +8 -2
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +31 -2
- package/dist/ButtonGroup/ButtonGroup.d.ts +9 -1
- package/dist/ButtonGroup/ButtonGroup.d.ts.map +1 -1
- package/dist/ButtonGroup/ButtonGroup.js +30 -1
- package/dist/Card/Card.d.ts +8 -2
- package/dist/Card/Card.d.ts.map +1 -1
- package/dist/Card/Card.js +28 -2
- package/dist/Carousel/Carousel.d.ts +6 -2
- package/dist/Carousel/Carousel.d.ts.map +1 -1
- package/dist/Carousel/Carousel.js +55 -5
- package/dist/Chat/ChatComposer.d.ts +8 -0
- package/dist/Chat/ChatComposer.d.ts.map +1 -1
- package/dist/Chat/ChatComposer.js +31 -6
- package/dist/Chat/ChatComposerDrawer.d.ts +2 -0
- package/dist/Chat/ChatComposerDrawer.d.ts.map +1 -1
- package/dist/Chat/ChatComposerDrawer.js +11 -1
- package/dist/Chat/ChatComposerInput.d.ts +20 -3
- package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
- package/dist/Chat/ChatComposerInput.js +19 -3
- package/dist/Chat/ChatLayout.d.ts.map +1 -1
- package/dist/Chat/ChatLayoutScrollButton.d.ts.map +1 -1
- package/dist/Chat/ChatMessage.d.ts +1 -1
- package/dist/Chat/ChatMessage.d.ts.map +1 -1
- package/dist/Chat/ChatMessage.js +1 -1
- package/dist/Chat/ChatMessageMetadata.d.ts.map +1 -1
- package/dist/Chat/ChatSendButton.d.ts.map +1 -1
- package/dist/Chat/ChatToolCalls.d.ts +5 -1
- package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
- package/dist/Chat/ChatToolCalls.js +12 -3
- package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
- package/dist/Citation/Citation.d.ts.map +1 -1
- package/dist/Citation/Citation.js +10 -4
- package/dist/ClickableCard/ClickableCard.d.ts +9 -2
- package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
- package/dist/ClickableCard/ClickableCard.js +3 -0
- package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
- package/dist/CodeBlock/CodeBlock.js +8 -1
- package/dist/Collapsible/Collapsible.d.ts +11 -1
- package/dist/Collapsible/Collapsible.d.ts.map +1 -1
- package/dist/Collapsible/Collapsible.js +36 -4
- package/dist/CommandPalette/CommandPaletteInput.d.ts +7 -1
- package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteInput.js +6 -1
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +4 -3
- package/dist/ContextMenu/index.d.ts +3 -0
- package/dist/ContextMenu/index.d.ts.map +1 -1
- package/dist/ContextMenu/index.js +7 -1
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +10 -1
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +2 -1
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +8 -4
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts +88 -0
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -0
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +169 -0
- package/dist/DropdownMenu/DropdownMenuContext.d.ts +17 -1
- package/dist/DropdownMenu/DropdownMenuContext.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuContext.js +18 -0
- package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts +68 -0
- package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts.map +1 -0
- package/dist/DropdownMenu/DropdownMenuRadioGroup.js +69 -0
- package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts +69 -0
- package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts.map +1 -0
- package/dist/DropdownMenu/DropdownMenuRadioItem.js +178 -0
- package/dist/DropdownMenu/index.d.ts +5 -1
- package/dist/DropdownMenu/index.d.ts.map +1 -1
- package/dist/DropdownMenu/index.js +10 -1
- package/dist/DropdownMenu/menuItemRoles.d.ts +13 -0
- package/dist/DropdownMenu/menuItemRoles.d.ts.map +1 -0
- package/dist/DropdownMenu/menuItemRoles.js +15 -0
- package/dist/FieldStatus/FieldStatus.d.ts +8 -0
- package/dist/FieldStatus/FieldStatus.d.ts.map +1 -1
- package/dist/FieldStatus/FieldStatus.js +22 -4
- package/dist/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +10 -2
- package/dist/HoverCard/HoverCard.d.ts +9 -1
- package/dist/HoverCard/HoverCard.d.ts.map +1 -1
- package/dist/HoverCard/HoverCard.js +2 -0
- package/dist/HoverCard/useHoverCard.d.ts +8 -0
- package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
- package/dist/HoverCard/useHoverCard.js +7 -2
- package/dist/Icon/Icon.d.ts +43 -13
- package/dist/Icon/Icon.d.ts.map +1 -1
- package/dist/Icon/Icon.js +62 -27
- package/dist/Layer/useLayer.d.ts +6 -0
- package/dist/Layer/useLayer.d.ts.map +1 -1
- package/dist/Layer/useLayer.js +2 -0
- package/dist/MobileNav/MobileNav.d.ts.map +1 -1
- package/dist/MobileNav/MobileNav.js +6 -1
- package/dist/MobileNav/MobileNavToggle.d.ts.map +1 -1
- package/dist/MobileNav/MobileNavToggle.js +4 -0
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +39 -12
- package/dist/MultiSelector/hooks.d.ts.map +1 -1
- package/dist/MultiSelector/hooks.js +16 -0
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/Outline/Outline.d.ts +68 -3
- package/dist/Outline/Outline.d.ts.map +1 -1
- package/dist/Outline/Outline.js +128 -26
- package/dist/Outline/useScrollSpy.d.ts +27 -7
- package/dist/Outline/useScrollSpy.d.ts.map +1 -1
- package/dist/Outline/useScrollSpy.js +202 -70
- package/dist/OverflowList/OverflowList.d.ts +20 -3
- package/dist/OverflowList/OverflowList.d.ts.map +1 -1
- package/dist/OverflowList/OverflowList.js +29 -4
- package/dist/PowerSearch/PowerSearchEditPopover.js +1 -1
- package/dist/PowerSearch/usePowerSearchSource.d.ts.map +1 -1
- package/dist/SelectableCard/SelectableCard.d.ts +9 -2
- package/dist/SelectableCard/SelectableCard.d.ts.map +1 -1
- package/dist/SelectableCard/SelectableCard.js +14 -11
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +4 -2
- package/dist/Selector/hooks.d.ts.map +1 -1
- package/dist/Selector/hooks.js +16 -0
- package/dist/SideNav/SideNavCollapseButton.d.ts.map +1 -1
- package/dist/SideNav/SideNavHeading.d.ts.map +1 -1
- package/dist/SideNav/SideNavHeading.js +21 -8
- package/dist/SideNav/SideNavItem.d.ts.map +1 -1
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +15 -1
- package/dist/TabList/Tab.d.ts.map +1 -1
- package/dist/TabList/Tab.js +2 -2
- package/dist/TabList/TabList.d.ts.map +1 -1
- package/dist/TabList/TabList.js +2 -0
- package/dist/TabList/TabMenu.d.ts.map +1 -1
- package/dist/TabList/TabMenu.js +1 -1
- package/dist/Table/Table.d.ts +1 -1
- package/dist/Table/Table.js +1 -1
- package/dist/Table/index.d.ts +4 -0
- package/dist/Table/index.d.ts.map +1 -1
- package/dist/Table/index.js +2 -0
- package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +24 -2
- package/dist/Table/plugins/rowStatus/index.d.ts +3 -0
- package/dist/Table/plugins/rowStatus/index.d.ts.map +1 -0
- package/dist/Table/plugins/rowStatus/index.js +3 -0
- package/dist/Table/plugins/rowStatus/useTableRowStatus.d.ts +60 -0
- package/dist/Table/plugins/rowStatus/useTableRowStatus.d.ts.map +1 -0
- package/dist/Table/plugins/rowStatus/useTableRowStatus.js +145 -0
- package/dist/Table/plugins/selection/useTableSelection.d.ts +12 -0
- package/dist/Table/plugins/selection/useTableSelection.d.ts.map +1 -1
- package/dist/Table/plugins/selection/useTableSelection.js +4 -1
- package/dist/Table/plugins/sortable/useTableSortable.d.ts.map +1 -1
- package/dist/Table/plugins/sortable/useTableSortable.js +18 -7
- package/dist/Table/plugins/tree/index.d.ts +5 -0
- package/dist/Table/plugins/tree/index.d.ts.map +1 -0
- package/dist/Table/plugins/tree/index.js +4 -0
- package/dist/Table/plugins/tree/useTableTreeData.d.ts +38 -0
- package/dist/Table/plugins/tree/useTableTreeData.d.ts.map +1 -0
- package/dist/Table/plugins/tree/useTableTreeData.js +358 -0
- package/dist/Table/plugins/tree/useTableTreeState.d.ts +51 -0
- package/dist/Table/plugins/tree/useTableTreeState.d.ts.map +1 -0
- package/dist/Table/plugins/tree/useTableTreeState.js +175 -0
- package/dist/Table/useBaseTablePlugins.d.ts.map +1 -1
- package/dist/Table/useBaseTablePlugins.js +6 -3
- package/dist/Thumbnail/Thumbnail.d.ts +8 -1
- package/dist/Thumbnail/Thumbnail.d.ts.map +1 -1
- package/dist/Thumbnail/Thumbnail.js +46 -8
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +15 -4
- package/dist/Tokenizer/Tokenizer.d.ts +1 -1
- package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.js +16 -4
- package/dist/Toolbar/Toolbar.d.ts.map +1 -1
- package/dist/Toolbar/Toolbar.js +8 -1
- package/dist/TopNav/TopNavHeading.d.ts.map +1 -1
- package/dist/TopNav/TopNavHeading.js +18 -8
- package/dist/TopNav/TopNavMegaMenu.js +10 -4
- package/dist/TopNav/TopNavMenu.d.ts +2 -1
- package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
- package/dist/TopNav/TopNavMenu.js +68 -6
- package/dist/TreeList/TreeListItem.d.ts.map +1 -1
- package/dist/TreeList/TreeListItem.js +5 -14
- package/dist/astryx.css +51 -16
- package/dist/astryx.umd.js +56 -56
- package/dist/astryx.umd.js.map +4 -4
- package/dist/authoring/doc.d.ts.map +1 -1
- package/dist/hooks/computeOverflow.d.ts +47 -0
- package/dist/hooks/computeOverflow.d.ts.map +1 -0
- package/dist/hooks/computeOverflow.js +213 -0
- package/dist/hooks/useOverflow.d.ts +19 -0
- package/dist/hooks/useOverflow.d.ts.map +1 -1
- package/dist/hooks/useOverflow.js +37 -25
- package/dist/theme/Theme.js +1 -1
- package/dist/theme/defineTheme.d.ts +1 -1
- package/dist/theme/defineTheme.js +1 -1
- package/dist/theme/generateThemeRules.d.ts.map +1 -1
- package/dist/theme/generateThemeRules.js +51 -0
- package/dist/theme/hct.d.ts +0 -1
- package/dist/theme/hct.d.ts.map +1 -1
- package/dist/theme/hct.js +0 -3
- package/dist/utils/types.d.ts +13 -0
- package/dist/utils/types.d.ts.map +1 -1
- package/docs.mjs +32 -461
- package/locales/en.json +212 -172
- package/locales/fr-FR.json +10 -0
- package/locales/pseudo.json +30 -0
- package/package.json +3 -1
- package/scripts/postinstall.mjs +93 -0
- package/src/AppShell/AppShell.tsx +6 -0
- package/src/AppShell/AppShellMobileContext.tsx +8 -0
- package/src/AppShell/useAppShellMobile.doc.mjs +8 -0
- package/src/Avatar/Avatar.doc.mjs +6 -6
- package/src/Avatar/Avatar.tsx +16 -11
- package/src/Avatar/AvatarSizeContext.ts +1 -1
- package/src/Avatar/AvatarStatusDot.tsx +2 -2
- package/src/AvatarGroup/AvatarGroup.doc.mjs +1 -1
- package/src/AvatarGroup/AvatarGroup.test.tsx +17 -3
- package/src/AvatarGroup/AvatarGroup.tsx +3 -3
- package/src/AvatarGroup/AvatarGroupOverflow.doc.mjs +4 -4
- package/src/AvatarGroup/AvatarGroupOverflow.tsx +21 -5
- package/src/Banner/Banner.doc.mjs +9 -0
- package/src/Banner/Banner.test.tsx +30 -0
- package/src/Banner/Banner.tsx +47 -3
- package/src/Button/Button.doc.mjs +9 -0
- package/src/Button/Button.test.tsx +49 -0
- package/src/Button/Button.tsx +25 -1
- package/src/ButtonGroup/ButtonGroup.doc.mjs +3 -0
- package/src/ButtonGroup/ButtonGroup.test.tsx +37 -0
- package/src/ButtonGroup/ButtonGroup.tsx +32 -0
- package/src/Card/Card.doc.mjs +9 -0
- package/src/Card/Card.test.tsx +21 -3
- package/src/Card/Card.tsx +35 -2
- package/src/Carousel/Carousel.doc.mjs +1 -0
- package/src/Carousel/Carousel.test.tsx +149 -1
- package/src/Carousel/Carousel.tsx +62 -5
- package/src/Chat/Chat.doc.mjs +1 -1
- package/src/Chat/ChatComposer.doc.mjs +9 -0
- package/src/Chat/ChatComposer.test.tsx +35 -0
- package/src/Chat/ChatComposer.tsx +58 -9
- package/src/Chat/ChatComposerDrawer.test.tsx +61 -0
- package/src/Chat/ChatComposerDrawer.tsx +11 -1
- package/src/Chat/ChatComposerInput.doc.mjs +7 -0
- package/src/Chat/ChatComposerInput.test.tsx +87 -0
- package/src/Chat/ChatComposerInput.tsx +35 -3
- package/src/Chat/ChatLayout.tsx +3 -1
- package/src/Chat/ChatLayoutScrollButton.tsx +3 -1
- package/src/Chat/ChatMessage.tsx +4 -2
- package/src/Chat/ChatMessageMetadata.tsx +1 -2
- package/src/Chat/ChatSendButton.tsx +5 -1
- package/src/Chat/ChatToolCalls.test.tsx +64 -0
- package/src/Chat/ChatToolCalls.tsx +13 -1
- package/src/Chat/useTriggerMenu.tsx +3 -1
- package/src/CheckboxInput/CheckboxInput.doc.mjs +1 -1
- package/src/CheckboxInput/CheckboxInput.test.tsx +32 -1
- package/src/CheckboxList/CheckboxList.test.tsx +9 -1
- package/src/Citation/Citation.test.tsx +51 -0
- package/src/Citation/Citation.tsx +8 -2
- package/src/ClickableCard/ClickableCard.doc.mjs +2 -0
- package/src/ClickableCard/ClickableCard.test.tsx +34 -0
- package/src/ClickableCard/ClickableCard.tsx +11 -1
- package/src/CodeBlock/CodeBlock.test.tsx +56 -0
- package/src/CodeBlock/CodeBlock.tsx +9 -1
- package/src/Collapsible/Collapsible.doc.mjs +7 -0
- package/src/Collapsible/Collapsible.test.tsx +80 -0
- package/src/Collapsible/Collapsible.tsx +58 -5
- package/src/CommandPalette/CommandPaletteInput.doc.mjs +26 -9
- package/src/CommandPalette/CommandPaletteInput.test.tsx +34 -0
- package/src/CommandPalette/CommandPaletteInput.tsx +11 -0
- package/src/ContextMenu/ContextMenu.test.tsx +75 -1
- package/src/ContextMenu/ContextMenu.tsx +10 -5
- package/src/ContextMenu/index.ts +15 -0
- package/src/DateTimeInput/DateTimeInput.test.tsx +35 -0
- package/src/DateTimeInput/DateTimeInput.tsx +14 -2
- package/src/Dialog/Dialog.tsx +12 -2
- package/src/DropdownMenu/DropdownMenu.doc.mjs +23 -0
- package/src/DropdownMenu/DropdownMenu.tsx +11 -6
- package/src/DropdownMenu/DropdownMenuCheckboxItem.doc.mjs +95 -0
- package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +236 -0
- package/src/DropdownMenu/DropdownMenuContext.tsx +29 -1
- package/src/DropdownMenu/DropdownMenuItem.doc.mjs +7 -1
- package/src/DropdownMenu/DropdownMenuRadioGroup.doc.mjs +80 -0
- package/src/DropdownMenu/DropdownMenuRadioGroup.tsx +118 -0
- package/src/DropdownMenu/DropdownMenuRadioItem.doc.mjs +80 -0
- package/src/DropdownMenu/DropdownMenuRadioItem.tsx +232 -0
- package/src/DropdownMenu/DropdownMenuSelectable.test.tsx +163 -0
- package/src/DropdownMenu/index.ts +23 -3
- package/src/DropdownMenu/menuItemRoles.ts +22 -0
- package/src/Field/Field.doc.mjs +1 -1
- package/src/Field/Field.test.tsx +57 -12
- package/src/Field/FieldLabel.doc.mjs +1 -1
- package/src/FieldStatus/FieldStatus.doc.mjs +17 -5
- package/src/FieldStatus/FieldStatus.test.tsx +78 -40
- package/src/FieldStatus/FieldStatus.tsx +22 -4
- package/src/FileInput/FileInput.test.tsx +52 -3
- package/src/FileInput/FileInput.tsx +13 -2
- package/src/HoverCard/HoverCard.doc.mjs +13 -0
- package/src/HoverCard/HoverCard.test.tsx +21 -2
- package/src/HoverCard/HoverCard.tsx +11 -0
- package/src/HoverCard/useHoverCard.doc.mjs +2 -0
- package/src/HoverCard/useHoverCard.tsx +24 -2
- package/src/Icon/Icon.doc.mjs +12 -0
- package/src/Icon/Icon.test.tsx +142 -0
- package/src/Icon/Icon.tsx +98 -30
- package/src/IconButton/IconButton.doc.mjs +8 -0
- package/src/IconButton/IconButton.test.tsx +15 -18
- package/src/Item/Item.doc.mjs +1 -1
- package/src/Layer/useLayer.tsx +8 -0
- package/src/MobileNav/MobileNav.tsx +12 -1
- package/src/MobileNav/MobileNavToggle.test.tsx +116 -0
- package/src/MobileNav/MobileNavToggle.tsx +9 -1
- package/src/MultiSelector/MultiSelector.doc.mjs +4 -2
- package/src/MultiSelector/MultiSelector.test.tsx +162 -1
- package/src/MultiSelector/MultiSelector.tsx +55 -12
- package/src/MultiSelector/hooks.ts +17 -0
- package/src/NumberInput/NumberInput.doc.mjs +2 -2
- package/src/NumberInput/NumberInput.test.tsx +11 -7
- package/src/NumberInput/NumberInput.tsx +1 -2
- package/src/Outline/Outline.doc.mjs +123 -3
- package/src/Outline/Outline.test.tsx +735 -0
- package/src/Outline/Outline.tsx +183 -33
- package/src/Outline/useScrollSpy.ts +248 -72
- package/src/OverflowList/OverflowList.doc.mjs +39 -6
- package/src/OverflowList/OverflowList.test.tsx +111 -0
- package/src/OverflowList/OverflowList.tsx +48 -8
- package/src/PowerSearch/PowerSearchEditPopover.test.tsx +67 -0
- package/src/PowerSearch/PowerSearchEditPopover.tsx +1 -1
- package/src/PowerSearch/usePowerSearchSource.ts +1 -2
- package/src/SelectableCard/SelectableCard.doc.mjs +2 -0
- package/src/SelectableCard/SelectableCard.test.tsx +46 -4
- package/src/SelectableCard/SelectableCard.tsx +27 -14
- package/src/Selector/Selector.test.tsx +69 -0
- package/src/Selector/Selector.tsx +5 -1
- package/src/Selector/SelectorOption.doc.mjs +1 -1
- package/src/Selector/hooks.ts +17 -0
- package/src/SideNav/SideNav.test.tsx +119 -3
- package/src/SideNav/SideNavCollapseButton.tsx +6 -1
- package/src/SideNav/SideNavHeading.tsx +24 -8
- package/src/SideNav/SideNavItem.doc.mjs +2 -2
- package/src/SideNav/SideNavItem.tsx +5 -1
- package/src/Slider/Slider.test.tsx +62 -2
- package/src/Slider/Slider.tsx +15 -1
- package/src/Switch/Switch.doc.mjs +1 -1
- package/src/Switch/Switch.test.tsx +32 -1
- package/src/TabList/Tab.tsx +5 -1
- package/src/TabList/TabList.doc.mjs +3 -3
- package/src/TabList/TabList.test.tsx +66 -0
- package/src/TabList/TabList.tsx +8 -0
- package/src/TabList/TabMenu.tsx +4 -1
- package/src/Table/Table.doc.mjs +2 -0
- package/src/Table/Table.tsx +1 -1
- package/src/Table/index.ts +12 -0
- package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +6 -2
- package/src/Table/plugins/rowExpansion/useTableRowExpansion.test.tsx +90 -1
- package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +37 -5
- package/src/Table/plugins/rowStatus/index.ts +7 -0
- package/src/Table/plugins/rowStatus/useTableRowStatus.test.tsx +138 -0
- package/src/Table/plugins/rowStatus/useTableRowStatus.tsx +189 -0
- package/src/Table/plugins/selection/useTableSelection.test.tsx +16 -0
- package/src/Table/plugins/selection/useTableSelection.tsx +18 -1
- package/src/Table/plugins/sortable/useTableSortable.test.tsx +95 -9
- package/src/Table/plugins/sortable/useTableSortable.tsx +23 -7
- package/src/Table/plugins/tree/index.ts +12 -0
- package/src/Table/plugins/tree/useTableTreeData-perf.test.tsx +163 -0
- package/src/Table/plugins/tree/useTableTreeData.test.tsx +594 -0
- package/src/Table/plugins/tree/useTableTreeData.tsx +495 -0
- package/src/Table/plugins/tree/useTableTreeState.test.tsx +636 -0
- package/src/Table/plugins/tree/useTableTreeState.tsx +253 -0
- package/src/Table/useBaseTablePlugins.ts +6 -2
- package/src/Table/useTableRowStatus.doc.mjs +30 -0
- package/src/Table/useTableSelection.doc.mjs +65 -14
- package/src/Table/useTableTreeData.doc.mjs +75 -0
- package/src/Table/useTableTreeState.doc.mjs +98 -0
- package/src/TextArea/TextArea.doc.mjs +1 -1
- package/src/TextArea/TextArea.test.tsx +9 -1
- package/src/TextInput/TextInput.doc.mjs +1 -1
- package/src/Thumbnail/Thumbnail.doc.mjs +9 -0
- package/src/Thumbnail/Thumbnail.test.tsx +38 -8
- package/src/Thumbnail/Thumbnail.tsx +28 -1
- package/src/TimeInput/TimeInput.test.tsx +76 -1
- package/src/TimeInput/TimeInput.tsx +19 -6
- package/src/Tokenizer/Tokenizer.doc.mjs +1 -1
- package/src/Tokenizer/Tokenizer.test.tsx +137 -2
- package/src/Tokenizer/Tokenizer.tsx +16 -4
- package/src/Toolbar/Toolbar.tsx +10 -3
- package/src/TopNav/TopNav.test.tsx +80 -1
- package/src/TopNav/TopNavHeading.tsx +24 -8
- package/src/TopNav/TopNavMegaMenu.test.tsx +149 -1
- package/src/TopNav/TopNavMegaMenu.tsx +10 -4
- package/src/TopNav/TopNavMenu.test.tsx +118 -4
- package/src/TopNav/TopNavMenu.tsx +83 -5
- package/src/TreeList/TreeList.test.tsx +32 -1
- package/src/TreeList/TreeListItem.tsx +19 -10
- package/src/authoring/doc.ts +1 -3
- package/src/docs-script.test.mjs +18 -13
- package/src/hooks/computeOverflow.test.ts +422 -0
- package/src/hooks/computeOverflow.ts +290 -0
- package/src/hooks/useOverflow.test.ts +96 -4
- package/src/hooks/useOverflow.ts +77 -33
- package/src/postinstall.test.mjs +71 -0
- package/src/theme/Theme.doc.mjs +4 -4
- package/src/theme/Theme.tsx +1 -1
- package/src/theme/defineTheme.ts +1 -1
- package/src/theme/generateThemeRules.test.ts +65 -0
- package/src/theme/generateThemeRules.ts +56 -0
- package/src/theme/hct.ts +0 -4
- package/src/utils/types.ts +14 -0
- package/dist/TreeList/treeListItem.markers.stylex.d.ts +0 -16
- package/dist/TreeList/treeListItem.markers.stylex.d.ts.map +0 -1
- package/dist/TreeList/treeListItem.markers.stylex.js +0 -22
- package/src/TreeList/treeListItem.markers.stylex.ts +0 -20
package/src/Table/index.ts
CHANGED
|
@@ -28,10 +28,12 @@ export {useTableColumnResize} from './plugins/columnResize';
|
|
|
28
28
|
export {useTableStickyColumns} from './plugins/stickyColumns';
|
|
29
29
|
export {useTableGroupedRows} from './plugins/groupedRows';
|
|
30
30
|
export {useTableRowIndex} from './plugins/rowIndex';
|
|
31
|
+
export {useTableRowStatus} from './plugins/rowStatus';
|
|
31
32
|
export {
|
|
32
33
|
useTableRowExpansion,
|
|
33
34
|
useTableRowExpansionState,
|
|
34
35
|
} from './plugins/rowExpansion';
|
|
36
|
+
export {useTableTreeData, useTableTreeState} from './plugins/tree';
|
|
35
37
|
export {resolveContextActions} from './tableContextMenu';
|
|
36
38
|
export {
|
|
37
39
|
useTableFiltering,
|
|
@@ -109,6 +111,16 @@ export type {
|
|
|
109
111
|
UseTableGroupedRowsConfig,
|
|
110
112
|
UseTableGroupedRowsResult,
|
|
111
113
|
} from './plugins/groupedRows';
|
|
114
|
+
export type {
|
|
115
|
+
UseTableRowStatusConfig,
|
|
116
|
+
TableRowStatus,
|
|
117
|
+
} from './plugins/rowStatus';
|
|
118
|
+
export type {
|
|
119
|
+
TableTreeRowMeta,
|
|
120
|
+
UseTableTreeDataConfig,
|
|
121
|
+
UseTableTreeStateConfig,
|
|
122
|
+
UseTableTreeStateResult,
|
|
123
|
+
} from './plugins/tree';
|
|
112
124
|
export type {
|
|
113
125
|
UseTableFilteringConfig,
|
|
114
126
|
TableFilterState,
|
|
@@ -325,8 +325,12 @@ export function useTableGroupedRows<T extends Record<string, unknown>>(
|
|
|
325
325
|
}}
|
|
326
326
|
aria-label={
|
|
327
327
|
collapsed
|
|
328
|
-
? t('@astryx.tableGroupedRows.expandGroup', {
|
|
329
|
-
|
|
328
|
+
? t('@astryx.tableGroupedRows.expandGroup', {
|
|
329
|
+
groupKey: header.groupKey,
|
|
330
|
+
})
|
|
331
|
+
: t('@astryx.tableGroupedRows.collapseGroup', {
|
|
332
|
+
groupKey: header.groupKey,
|
|
333
|
+
})
|
|
330
334
|
}
|
|
331
335
|
aria-expanded={!collapsed}>
|
|
332
336
|
<span
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
2
|
|
|
3
3
|
import {describe, it, expect, vi, beforeEach} from 'vitest';
|
|
4
|
-
import {render, screen, fireEvent} from '@testing-library/react';
|
|
4
|
+
import {render, renderHook, screen, fireEvent} from '@testing-library/react';
|
|
5
5
|
import {useState} from 'react';
|
|
6
6
|
import {Table} from '../../Table';
|
|
7
7
|
import type {TableColumn} from '../../types';
|
|
@@ -153,3 +153,92 @@ describe('useTableRowExpansion', () => {
|
|
|
153
153
|
expect(screen.queryByText('File B1')).not.toBeInTheDocument();
|
|
154
154
|
});
|
|
155
155
|
});
|
|
156
|
+
|
|
157
|
+
describe('useTableRowExpansionState cycle guard', () => {
|
|
158
|
+
/** A row whose children array contains the row itself (plus a real child). */
|
|
159
|
+
function makeSelfReferential(): TreeItem[] {
|
|
160
|
+
const x: TreeItem = {id: 'x', name: 'Self', children: []};
|
|
161
|
+
const y: TreeItem = {id: 'y', name: 'Leaf Y', children: []};
|
|
162
|
+
x.children.push(x, y);
|
|
163
|
+
return [x];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** root -> child -> grand, where grand's children point back at root. */
|
|
167
|
+
function makeDeepCycle(): TreeItem[] {
|
|
168
|
+
const root: TreeItem = {id: 'root', name: 'Root', children: []};
|
|
169
|
+
const child: TreeItem = {id: 'child', name: 'Child', children: []};
|
|
170
|
+
const grand: TreeItem = {id: 'grand', name: 'Grand', children: []};
|
|
171
|
+
root.children.push(child);
|
|
172
|
+
child.children.push(grand);
|
|
173
|
+
grand.children.push(root);
|
|
174
|
+
return [root];
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function renderState(baseData: TreeItem[], expandedKeys: Set<string>) {
|
|
178
|
+
const setExpandedKeys = vi.fn();
|
|
179
|
+
const hook = renderHook(() =>
|
|
180
|
+
useTableRowExpansionState<TreeItem>({
|
|
181
|
+
baseData,
|
|
182
|
+
getChildren: item => item.children,
|
|
183
|
+
getRowKey: item => item.id,
|
|
184
|
+
expandedKeys,
|
|
185
|
+
setExpandedKeys,
|
|
186
|
+
}),
|
|
187
|
+
);
|
|
188
|
+
return {hook, setExpandedKeys};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
it('terminates on a self-referential expanded row and flattens each key once', () => {
|
|
192
|
+
const {hook} = renderState(makeSelfReferential(), new Set(['x']));
|
|
193
|
+
const {data, expansionConfig} = hook.result.current;
|
|
194
|
+
expect(data.map(item => item.id)).toEqual(['x', 'y']);
|
|
195
|
+
expect(expansionConfig.getDepth?.(data[0])).toBe(0);
|
|
196
|
+
expect(expansionConfig.getDepth?.(data[1])).toBe(1);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('terminates on a deeper cycle back to the root and flattens each key once', () => {
|
|
200
|
+
const {hook} = renderState(
|
|
201
|
+
makeDeepCycle(),
|
|
202
|
+
new Set(['root', 'child', 'grand']),
|
|
203
|
+
);
|
|
204
|
+
const {data, expansionConfig} = hook.result.current;
|
|
205
|
+
expect(data.map(item => item.id)).toEqual(['root', 'child', 'grand']);
|
|
206
|
+
expect(expansionConfig.getDepth?.(data[0])).toBe(0);
|
|
207
|
+
expect(expansionConfig.getDepth?.(data[1])).toBe(1);
|
|
208
|
+
expect(expansionConfig.getDepth?.(data[2])).toBe(2);
|
|
209
|
+
// The cycle guard keeps isAllExpanded computable — true, not a crash.
|
|
210
|
+
expect(expansionConfig.isAllExpanded).toBe(true);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('terminates when collecting allExpandableKeys on cyclic data with nothing expanded', () => {
|
|
214
|
+
const {hook, setExpandedKeys} = renderState(
|
|
215
|
+
makeDeepCycle(),
|
|
216
|
+
new Set<string>(),
|
|
217
|
+
);
|
|
218
|
+
const {data, expansionConfig} = hook.result.current;
|
|
219
|
+
expect(data.map(item => item.id)).toEqual(['root']);
|
|
220
|
+
expansionConfig.onToggleExpandAll?.(true);
|
|
221
|
+
const nextKeys = setExpandedKeys.mock.calls[0][0] as Set<string>;
|
|
222
|
+
expect(Array.from(nextKeys)).toEqual(['root', 'child', 'grand']);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('re-walks a shared child under each expanded parent (ancestor-path, not visited-set, semantics)', () => {
|
|
226
|
+
const leaf: TreeItem = {id: 'leaf', name: 'Leaf', children: []};
|
|
227
|
+
const shared: TreeItem = {id: 's', name: 'Shared', children: [leaf]};
|
|
228
|
+
const p1: TreeItem = {id: 'p1', name: 'Parent 1', children: [shared]};
|
|
229
|
+
const p2: TreeItem = {id: 'p2', name: 'Parent 2', children: [shared]};
|
|
230
|
+
const {hook} = renderState([p1, p2], new Set(['p1', 'p2', 's']));
|
|
231
|
+
// 's' is on neither parent's ancestor path, so it must flatten under
|
|
232
|
+
// both — the guard only skips true cycles, matching pre-guard behavior
|
|
233
|
+
// for acyclic (DAG-shaped) data.
|
|
234
|
+
const {data} = hook.result.current;
|
|
235
|
+
expect(data.map(item => item.id)).toEqual([
|
|
236
|
+
'p1',
|
|
237
|
+
's',
|
|
238
|
+
'leaf',
|
|
239
|
+
'p2',
|
|
240
|
+
's',
|
|
241
|
+
'leaf',
|
|
242
|
+
]);
|
|
243
|
+
});
|
|
244
|
+
});
|
|
@@ -141,12 +141,19 @@ export function useTableRowExpansionState<T extends Record<string, unknown>>({
|
|
|
141
141
|
|
|
142
142
|
const depthMap = useMemo(() => {
|
|
143
143
|
const map = new Map<string, number>();
|
|
144
|
+
// Ancestor keys on the current walk path — guards against cyclic data.
|
|
145
|
+
const path = new Set<string>();
|
|
144
146
|
function walk(items: T[], depth: number) {
|
|
145
147
|
for (const item of items) {
|
|
146
148
|
const key = getRowKey(item);
|
|
149
|
+
if (path.has(key)) {
|
|
150
|
+
continue; // cyclic edge — skip
|
|
151
|
+
}
|
|
147
152
|
map.set(key, depth);
|
|
148
153
|
if (expandedKeys.has(key)) {
|
|
154
|
+
path.add(key);
|
|
149
155
|
walk(getChildren(item), depth + 1);
|
|
156
|
+
path.delete(key);
|
|
150
157
|
}
|
|
151
158
|
}
|
|
152
159
|
}
|
|
@@ -156,12 +163,19 @@ export function useTableRowExpansionState<T extends Record<string, unknown>>({
|
|
|
156
163
|
|
|
157
164
|
const data = useMemo(() => {
|
|
158
165
|
const result: T[] = [];
|
|
166
|
+
// Ancestor keys on the current walk path — guards against cyclic data.
|
|
167
|
+
const path = new Set<string>();
|
|
159
168
|
function walk(items: T[]) {
|
|
160
169
|
for (const item of items) {
|
|
161
|
-
result.push(item);
|
|
162
170
|
const key = getRowKey(item);
|
|
171
|
+
if (path.has(key)) {
|
|
172
|
+
continue; // cyclic edge — skip
|
|
173
|
+
}
|
|
174
|
+
result.push(item);
|
|
163
175
|
if (expandedKeys.has(key)) {
|
|
176
|
+
path.add(key);
|
|
164
177
|
walk(getChildren(item));
|
|
178
|
+
path.delete(key);
|
|
165
179
|
}
|
|
166
180
|
}
|
|
167
181
|
}
|
|
@@ -172,11 +186,19 @@ export function useTableRowExpansionState<T extends Record<string, unknown>>({
|
|
|
172
186
|
// Every expandable key across the whole tree (drives expand-all).
|
|
173
187
|
const allExpandableKeys = useMemo(() => {
|
|
174
188
|
const keys: string[] = [];
|
|
189
|
+
// Ancestor keys on the current walk path — guards against cyclic data.
|
|
190
|
+
const path = new Set<string>();
|
|
175
191
|
function walk(items: T[]) {
|
|
176
192
|
for (const item of items) {
|
|
193
|
+
const key = getRowKey(item);
|
|
194
|
+
if (path.has(key)) {
|
|
195
|
+
continue; // cyclic edge — skip
|
|
196
|
+
}
|
|
177
197
|
if (isExpandable(item)) {
|
|
178
|
-
keys.push(
|
|
198
|
+
keys.push(key);
|
|
199
|
+
path.add(key);
|
|
179
200
|
walk(getChildren(item));
|
|
201
|
+
path.delete(key);
|
|
180
202
|
}
|
|
181
203
|
}
|
|
182
204
|
}
|
|
@@ -399,7 +421,11 @@ export function useTableRowExpansion<T extends Record<string, unknown>>(
|
|
|
399
421
|
<ExpansionChevron
|
|
400
422
|
isExpanded={isExpanded}
|
|
401
423
|
onToggle={() => onToggle(key)}
|
|
402
|
-
ariaLabel={
|
|
424
|
+
ariaLabel={
|
|
425
|
+
isExpanded
|
|
426
|
+
? t('@astryx.tableRowExpansion.collapseRow')
|
|
427
|
+
: t('@astryx.tableRowExpansion.expandRow')
|
|
428
|
+
}
|
|
403
429
|
/>
|
|
404
430
|
);
|
|
405
431
|
},
|
|
@@ -457,7 +483,11 @@ export function useTableRowExpansion<T extends Record<string, unknown>>(
|
|
|
457
483
|
<ExpansionChevron
|
|
458
484
|
isExpanded={isExpanded}
|
|
459
485
|
onToggle={() => onToggle(key)}
|
|
460
|
-
ariaLabel={
|
|
486
|
+
ariaLabel={
|
|
487
|
+
isExpanded
|
|
488
|
+
? t('@astryx.tableRowExpansion.collapseRow')
|
|
489
|
+
: t('@astryx.tableRowExpansion.expandRow')
|
|
490
|
+
}
|
|
461
491
|
/>
|
|
462
492
|
) : (
|
|
463
493
|
<span {...stylex.props(expansionStyles.placeholder)} />
|
|
@@ -499,7 +529,9 @@ export function useTableRowExpansion<T extends Record<string, unknown>>(
|
|
|
499
529
|
{...stylex.props(expansionStyles.chevronButton)}
|
|
500
530
|
onClick={() => onToggleExpandAll(!allExpanded)}
|
|
501
531
|
aria-label={
|
|
502
|
-
allExpanded
|
|
532
|
+
allExpanded
|
|
533
|
+
? t('@astryx.tableRowExpansion.collapseAllRows')
|
|
534
|
+
: t('@astryx.tableRowExpansion.expandAllRows')
|
|
503
535
|
}>
|
|
504
536
|
<span
|
|
505
537
|
{...stylex.props(
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
import {describe, it, expect} from 'vitest';
|
|
4
|
+
import {render, screen, within} from '@testing-library/react';
|
|
5
|
+
import {Table} from '../../Table';
|
|
6
|
+
import type {TableColumn} from '../../types';
|
|
7
|
+
import {useTableRowStatus, type TableRowStatus} from './useTableRowStatus';
|
|
8
|
+
|
|
9
|
+
interface Row extends Record<string, unknown> {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
state: 'error' | 'warning' | 'ok' | 'done';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const data: Row[] = [
|
|
16
|
+
{id: 'a', name: 'Alice', state: 'error'},
|
|
17
|
+
{id: 'b', name: 'Bob', state: 'ok'},
|
|
18
|
+
{id: 'c', name: 'Carol', state: 'warning'},
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const columns: TableColumn<Row>[] = [{key: 'name', header: 'Name'}];
|
|
22
|
+
|
|
23
|
+
function getStatus(item: Row): TableRowStatus | null {
|
|
24
|
+
if (item.state === 'error') {
|
|
25
|
+
return {color: 'red', label: 'Error'};
|
|
26
|
+
}
|
|
27
|
+
if (item.state === 'warning') {
|
|
28
|
+
return {color: 'orange', label: 'Warning'};
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function Harness({
|
|
34
|
+
rows = data,
|
|
35
|
+
statusFn = getStatus,
|
|
36
|
+
}: {
|
|
37
|
+
rows?: Row[];
|
|
38
|
+
statusFn?: (item: Row) => TableRowStatus | null;
|
|
39
|
+
}) {
|
|
40
|
+
const rowStatus = useTableRowStatus<Row>({getStatus: statusFn});
|
|
41
|
+
return (
|
|
42
|
+
<Table data={rows} columns={columns} idKey="id" plugins={{rowStatus}} />
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
describe('useTableRowStatus', () => {
|
|
47
|
+
it('prepends a narrow status column with an empty header', () => {
|
|
48
|
+
render(<Harness />);
|
|
49
|
+
const headers = screen.getAllByRole('columnheader');
|
|
50
|
+
// Status column is first; its header is empty.
|
|
51
|
+
expect(headers[0]).toHaveAttribute('data-column-key', '__rowStatus');
|
|
52
|
+
expect(headers[0].textContent).toBe('');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('renders a labeled dot for rows with a status', () => {
|
|
56
|
+
render(<Harness />);
|
|
57
|
+
expect(screen.getByRole('img', {name: 'Error'})).toBeInTheDocument();
|
|
58
|
+
expect(screen.getByRole('img', {name: 'Warning'})).toBeInTheDocument();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('renders a dot (not an icon) by default', () => {
|
|
62
|
+
render(<Harness />);
|
|
63
|
+
// Default (no icon) renders a plain colored dot: no svg in the indicator.
|
|
64
|
+
const dot = screen.getByRole('img', {name: 'Error'});
|
|
65
|
+
expect(dot.querySelector('svg')).toBeNull();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('renders no indicator for rows returning null', () => {
|
|
69
|
+
render(<Harness />);
|
|
70
|
+
const rows = screen.getAllByRole('row');
|
|
71
|
+
// rows[2] is Bob (state ok): no status indicator in his status cell.
|
|
72
|
+
const bob = rows[2];
|
|
73
|
+
expect(within(bob).getByText('Bob')).toBeInTheDocument();
|
|
74
|
+
expect(within(bob).queryByRole('img')).not.toBeInTheDocument();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('maps a semantic color name to its icon color token', () => {
|
|
78
|
+
render(<Harness />);
|
|
79
|
+
const errorDot = screen.getByRole('img', {name: 'Error'});
|
|
80
|
+
// 'red' resolves to var(--color-icon-red); StyleX emits it on the inline
|
|
81
|
+
// style of the inner dot element.
|
|
82
|
+
const dot = errorDot.querySelector('span');
|
|
83
|
+
expect(dot?.getAttribute('style')).toContain('--color-icon-red');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('passes through a raw CSS color as an escape hatch', () => {
|
|
87
|
+
render(
|
|
88
|
+
<Harness
|
|
89
|
+
statusFn={item =>
|
|
90
|
+
item.state === 'error' ? {color: 'rgb(1, 2, 3)', label: 'Raw'} : null
|
|
91
|
+
}
|
|
92
|
+
/>,
|
|
93
|
+
);
|
|
94
|
+
const indicator = screen.getByRole('img', {name: 'Raw'});
|
|
95
|
+
const dot = indicator.querySelector('span');
|
|
96
|
+
expect(dot?.getAttribute('style')).toContain('rgb(1, 2, 3)');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('renders an icon as the status signifier when icon is provided', () => {
|
|
100
|
+
render(
|
|
101
|
+
<Harness
|
|
102
|
+
statusFn={item =>
|
|
103
|
+
item.state === 'error'
|
|
104
|
+
? {color: 'red', icon: 'error', label: 'Error'}
|
|
105
|
+
: null
|
|
106
|
+
}
|
|
107
|
+
/>,
|
|
108
|
+
);
|
|
109
|
+
// Icon-mode still exposes the accessible label via role=img.
|
|
110
|
+
const indicator = screen.getByRole('img', {name: 'Error'});
|
|
111
|
+
expect(indicator).toBeInTheDocument();
|
|
112
|
+
// An SVG icon is rendered inside the indicator (dot mode has no svg).
|
|
113
|
+
expect(indicator.querySelector('svg')).not.toBeNull();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('exposes the required label as the accessible name in dot mode', () => {
|
|
117
|
+
render(
|
|
118
|
+
<Harness
|
|
119
|
+
statusFn={item =>
|
|
120
|
+
item.state === 'error' ? {color: 'red', label: 'Error'} : null
|
|
121
|
+
}
|
|
122
|
+
/>,
|
|
123
|
+
);
|
|
124
|
+
// Label is required, so every dot is announced via role=img with its name.
|
|
125
|
+
const indicator = screen.getByRole('img', {name: 'Error'});
|
|
126
|
+
expect(indicator).toBeInTheDocument();
|
|
127
|
+
// Dot mode renders no svg (that is icon mode).
|
|
128
|
+
expect(indicator.querySelector('svg')).toBeNull();
|
|
129
|
+
expect(screen.getByText('Alice')).toBeInTheDocument();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('renders the status header with empty data and no indicators', () => {
|
|
133
|
+
render(<Harness rows={[]} />);
|
|
134
|
+
const headers = screen.getAllByRole('columnheader');
|
|
135
|
+
expect(headers[0]).toHaveAttribute('data-column-key', '__rowStatus');
|
|
136
|
+
expect(screen.queryByRole('img')).not.toBeInTheDocument();
|
|
137
|
+
});
|
|
138
|
+
});
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @file useTableRowStatus.tsx
|
|
7
|
+
* @input React, StyleX, Icon, Table types
|
|
8
|
+
* @output Exports useTableRowStatus hook + config type
|
|
9
|
+
* @position Row-status plugin; consumed by Table via plugins prop
|
|
10
|
+
*
|
|
11
|
+
* SYNC: When modified, update these files to stay in sync:
|
|
12
|
+
* - /packages/core/src/Table/index.ts (exports)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import {useMemo} from 'react';
|
|
16
|
+
import * as stylex from '@stylexjs/stylex';
|
|
17
|
+
import {Icon, type IconColor, type IconName} from '../../../Icon';
|
|
18
|
+
import {Tooltip} from '../../../Tooltip';
|
|
19
|
+
import type {TableColumn, TablePlugin} from '../../types';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Semantic status colors, resolved to the design system's icon color tokens.
|
|
23
|
+
* Prefer these over raw CSS so status colors stay consistent with the theme.
|
|
24
|
+
*/
|
|
25
|
+
export type TableRowStatusColor =
|
|
26
|
+
| 'accent'
|
|
27
|
+
| 'success'
|
|
28
|
+
| 'error'
|
|
29
|
+
| 'warning'
|
|
30
|
+
| 'red'
|
|
31
|
+
| 'orange'
|
|
32
|
+
| 'green'
|
|
33
|
+
| 'yellow'
|
|
34
|
+
| 'blue'
|
|
35
|
+
| 'gray';
|
|
36
|
+
|
|
37
|
+
const SEMANTIC_COLORS: Record<TableRowStatusColor, string> = {
|
|
38
|
+
accent: 'var(--color-icon-accent)',
|
|
39
|
+
success: 'var(--color-icon-green)',
|
|
40
|
+
error: 'var(--color-icon-red)',
|
|
41
|
+
warning: 'var(--color-icon-orange)',
|
|
42
|
+
red: 'var(--color-icon-red)',
|
|
43
|
+
orange: 'var(--color-icon-orange)',
|
|
44
|
+
green: 'var(--color-icon-green)',
|
|
45
|
+
yellow: 'var(--color-icon-yellow)',
|
|
46
|
+
blue: 'var(--color-icon-blue)',
|
|
47
|
+
gray: 'var(--color-icon-gray)',
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/** Icon colors that map cleanly from a semantic status color. */
|
|
51
|
+
const ICON_COLOR_BY_STATUS: Record<TableRowStatusColor, IconColor> = {
|
|
52
|
+
accent: 'accent',
|
|
53
|
+
success: 'success',
|
|
54
|
+
error: 'error',
|
|
55
|
+
warning: 'warning',
|
|
56
|
+
red: 'red',
|
|
57
|
+
orange: 'warning',
|
|
58
|
+
green: 'green',
|
|
59
|
+
yellow: 'warning',
|
|
60
|
+
blue: 'blue',
|
|
61
|
+
gray: 'gray',
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* A row's status indicator. `color` accepts a semantic status color
|
|
66
|
+
* (mapped to a theme token) or any raw CSS color string as an escape hatch.
|
|
67
|
+
* By default the plugin renders a colored status dot. Provide `icon` to signal
|
|
68
|
+
* status by shape as well as color, which is more accessible when several
|
|
69
|
+
* statuses coexist. `label` is required so the status is never conveyed by
|
|
70
|
+
* color alone — it names the indicator for assistive technology and shows on
|
|
71
|
+
* hover. Return `null` for rows with no status.
|
|
72
|
+
*/
|
|
73
|
+
export interface TableRowStatus {
|
|
74
|
+
/** Semantic status color (preferred) or a raw CSS color string. */
|
|
75
|
+
color: TableRowStatusColor | (string & {});
|
|
76
|
+
/** Optional icon rendered as the signifier instead of the dot (shape as an a11y differentiator). */
|
|
77
|
+
icon?: IconName;
|
|
78
|
+
/**
|
|
79
|
+
* Accessible name for the status, announced to assistive technology and
|
|
80
|
+
* shown in a tooltip on hover. Required: a status must never be conveyed by
|
|
81
|
+
* color alone.
|
|
82
|
+
*/
|
|
83
|
+
label: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Configuration for {@link useTableRowStatus}. */
|
|
87
|
+
export interface UseTableRowStatusConfig<T extends Record<string, unknown>> {
|
|
88
|
+
/**
|
|
89
|
+
* Derive the status indicator for a row. Return `null` for no indicator.
|
|
90
|
+
* Memoize with `useCallback` for a stable plugin identity across renders.
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```
|
|
94
|
+
* getStatus: row =>
|
|
95
|
+
* row.hasError ? {color: 'error', icon: 'error', label: 'Error'} : null
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
getStatus: (item: T) => TableRowStatus | null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// The status column holds a small centered dot (or an icon when provided).
|
|
102
|
+
// A fixed narrow width keeps every row's indicator aligned in one gutter.
|
|
103
|
+
const STATUS_COLUMN_WIDTH = {type: 'pixel' as const, value: 28};
|
|
104
|
+
|
|
105
|
+
/** Resolve a semantic color name to a token, or pass a raw CSS color through. */
|
|
106
|
+
function resolveColor(color: string): string {
|
|
107
|
+
return (SEMANTIC_COLORS as Record<string, string>)[color] ?? color;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const styles = stylex.create({
|
|
111
|
+
// Centers the dot or icon within the narrow status column.
|
|
112
|
+
wrap: {
|
|
113
|
+
display: 'flex',
|
|
114
|
+
alignItems: 'center',
|
|
115
|
+
justifyContent: 'center',
|
|
116
|
+
},
|
|
117
|
+
dot: (color: string) => ({
|
|
118
|
+
width: '8px',
|
|
119
|
+
height: '8px',
|
|
120
|
+
borderRadius: '50%',
|
|
121
|
+
backgroundColor: color,
|
|
122
|
+
flexShrink: 0,
|
|
123
|
+
}),
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Returns a {@link TablePlugin} that prepends a narrow column signaling per-row
|
|
128
|
+
* status: a colored status dot by default, or an icon when `icon` is provided
|
|
129
|
+
* (shape + color is more accessible than color alone).
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* ```
|
|
133
|
+
* const rowStatus = useTableRowStatus<Row>({
|
|
134
|
+
* getStatus: row =>
|
|
135
|
+
* row.state === 'error'
|
|
136
|
+
* ? {color: 'error', icon: 'error', label: 'Error'}
|
|
137
|
+
* : null,
|
|
138
|
+
* });
|
|
139
|
+
* <Table data={data} columns={columns} idKey="id" plugins={{rowStatus}} />;
|
|
140
|
+
* ```
|
|
141
|
+
*/
|
|
142
|
+
export function useTableRowStatus<T extends Record<string, unknown>>(
|
|
143
|
+
config: UseTableRowStatusConfig<T>,
|
|
144
|
+
): TablePlugin<T> {
|
|
145
|
+
const {getStatus} = config;
|
|
146
|
+
|
|
147
|
+
return useMemo(
|
|
148
|
+
(): TablePlugin<T> => ({
|
|
149
|
+
transformColumns(columns) {
|
|
150
|
+
const statusColumn: TableColumn<T> = {
|
|
151
|
+
key: '__rowStatus',
|
|
152
|
+
header: '',
|
|
153
|
+
width: STATUS_COLUMN_WIDTH,
|
|
154
|
+
resizable: false,
|
|
155
|
+
renderCell: (item: T) => {
|
|
156
|
+
const status = getStatus(item);
|
|
157
|
+
if (!status) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
const signifier = status.icon ? (
|
|
161
|
+
<Icon
|
|
162
|
+
icon={status.icon}
|
|
163
|
+
size="xsm"
|
|
164
|
+
color={
|
|
165
|
+
ICON_COLOR_BY_STATUS[status.color as TableRowStatusColor] ??
|
|
166
|
+
'primary'
|
|
167
|
+
}
|
|
168
|
+
/>
|
|
169
|
+
) : (
|
|
170
|
+
<span {...stylex.props(styles.dot(resolveColor(status.color)))} />
|
|
171
|
+
);
|
|
172
|
+
return (
|
|
173
|
+
<Tooltip content={status.label}>
|
|
174
|
+
<span
|
|
175
|
+
{...stylex.props(styles.wrap)}
|
|
176
|
+
role="img"
|
|
177
|
+
aria-label={status.label}>
|
|
178
|
+
{signifier}
|
|
179
|
+
</span>
|
|
180
|
+
</Tooltip>
|
|
181
|
+
);
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
return [statusColumn, ...columns];
|
|
185
|
+
},
|
|
186
|
+
}),
|
|
187
|
+
[getStatus],
|
|
188
|
+
);
|
|
189
|
+
}
|
|
@@ -40,9 +40,11 @@ const selectableColumns: TableColumn<SelectableUser>[] = [
|
|
|
40
40
|
function SelectionTable({
|
|
41
41
|
getIsItemSelectable,
|
|
42
42
|
getIsItemEnabled,
|
|
43
|
+
getRowLabel,
|
|
43
44
|
}: {
|
|
44
45
|
getIsItemSelectable?: (item: SelectableUser) => boolean;
|
|
45
46
|
getIsItemEnabled?: (item: SelectableUser) => boolean;
|
|
47
|
+
getRowLabel?: (item: SelectableUser) => string;
|
|
46
48
|
}) {
|
|
47
49
|
const [selectedKeys, setSelectedKeys] = useState<Set<string>>(new Set());
|
|
48
50
|
|
|
@@ -75,6 +77,7 @@ function SelectionTable({
|
|
|
75
77
|
},
|
|
76
78
|
getIsItemSelectable,
|
|
77
79
|
getIsItemEnabled,
|
|
80
|
+
getRowLabel,
|
|
78
81
|
});
|
|
79
82
|
|
|
80
83
|
return (
|
|
@@ -109,6 +112,19 @@ describe('useTableSelection', () => {
|
|
|
109
112
|
expect(rowCheckboxes).toHaveLength(3);
|
|
110
113
|
});
|
|
111
114
|
|
|
115
|
+
it('derives per-row accessible names from getRowLabel', () => {
|
|
116
|
+
render(<SelectionTable getRowLabel={item => item.name} />);
|
|
117
|
+
expect(screen.getByLabelText('Select Alice')).toBeInTheDocument();
|
|
118
|
+
expect(screen.getByLabelText('Select Bob')).toBeInTheDocument();
|
|
119
|
+
expect(screen.getByLabelText('Select Charlie')).toBeInTheDocument();
|
|
120
|
+
expect(screen.queryByLabelText('Select row')).not.toBeInTheDocument();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('keeps the "Select all rows" header label when getRowLabel is provided', () => {
|
|
124
|
+
render(<SelectionTable getRowLabel={item => item.name} />);
|
|
125
|
+
expect(screen.getByLabelText('Select all rows')).toBeInTheDocument();
|
|
126
|
+
});
|
|
127
|
+
|
|
112
128
|
it('toggles individual row selection on click', async () => {
|
|
113
129
|
const user = userEvent.setup();
|
|
114
130
|
render(<SelectionTable />);
|
|
@@ -68,6 +68,18 @@ export interface UseTableSelectionConfig<T extends Record<string, unknown>> {
|
|
|
68
68
|
getIsItemSelectable?: (item: T) => boolean;
|
|
69
69
|
/** Is this row's checkbox interactive? Disabled rows show disabled checkbox. @default () => true */
|
|
70
70
|
getIsItemEnabled?: (item: T) => boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Derive a human-readable identity for a row, used in the row checkbox's
|
|
73
|
+
* hidden label as `Select ${getRowLabel(item)}`. Without it, every row
|
|
74
|
+
* checkbox announces an undifferentiated "Select row" to screen readers.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```
|
|
78
|
+
* getRowLabel: item => item.name
|
|
79
|
+
* // Checkbox accessible names: "Select Alice", "Select Bob", ...
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
getRowLabel?: (item: T) => string;
|
|
71
83
|
}
|
|
72
84
|
|
|
73
85
|
// =============================================================================
|
|
@@ -235,6 +247,7 @@ function SelectionCellContentInner<T extends Record<string, unknown>>({
|
|
|
235
247
|
const isSelected = useIsItemSelected(store, item);
|
|
236
248
|
const selectable = config.getIsItemSelectable?.(item) ?? true;
|
|
237
249
|
const enabled = config.getIsItemEnabled?.(item) ?? true;
|
|
250
|
+
const rowLabel = config.getRowLabel?.(item);
|
|
238
251
|
|
|
239
252
|
if (!selectable) {
|
|
240
253
|
return null;
|
|
@@ -242,7 +255,11 @@ function SelectionCellContentInner<T extends Record<string, unknown>>({
|
|
|
242
255
|
|
|
243
256
|
return (
|
|
244
257
|
<CheckboxInput
|
|
245
|
-
label={
|
|
258
|
+
label={
|
|
259
|
+
rowLabel != null
|
|
260
|
+
? t('@astryx.table.selection.selectRowNamed', {label: rowLabel})
|
|
261
|
+
: t('@astryx.table.selection.selectRow')
|
|
262
|
+
}
|
|
246
263
|
isLabelHidden
|
|
247
264
|
value={isSelected}
|
|
248
265
|
onChange={() =>
|