@astryxdesign/core 0.3.0 → 0.4.0-canary.058f476
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 +156 -0
- package/dist/AlertDialog/AlertDialog.d.ts +10 -2
- package/dist/AlertDialog/AlertDialog.d.ts.map +1 -1
- package/dist/AlertDialog/AlertDialog.js +22 -3
- package/dist/AppShell/AppShell.d.ts +2 -2
- package/dist/AppShell/AppShell.d.ts.map +1 -1
- package/dist/AppShell/AppShell.js +40 -9
- package/dist/AspectRatio/AspectRatio.d.ts +13 -0
- package/dist/AspectRatio/AspectRatio.d.ts.map +1 -1
- package/dist/AspectRatio/AspectRatio.js +10 -2
- package/dist/AspectRatio/index.d.ts.map +1 -1
- package/dist/AspectRatio/index.js +1 -2
- package/dist/Avatar/Avatar.d.ts.map +1 -1
- package/dist/Avatar/Avatar.js +33 -20
- package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
- package/dist/AvatarGroup/AvatarGroupOverflow.js +2 -3
- package/dist/Badge/Badge.d.ts.map +1 -1
- package/dist/Badge/Badge.js +1 -2
- package/dist/Badge/index.d.ts.map +1 -1
- package/dist/Badge/index.js +1 -2
- package/dist/Banner/Banner.d.ts +3 -0
- package/dist/Banner/Banner.d.ts.map +1 -1
- package/dist/Banner/Banner.js +32 -17
- package/dist/Blockquote/Blockquote.d.ts.map +1 -1
- package/dist/Blockquote/Blockquote.js +1 -2
- package/dist/Blockquote/index.d.ts.map +1 -1
- package/dist/Blockquote/index.js +1 -2
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
- package/dist/Breadcrumbs/BreadcrumbItem.js +21 -8
- package/dist/Breadcrumbs/index.d.ts +2 -1
- package/dist/Breadcrumbs/index.d.ts.map +1 -1
- package/dist/Breadcrumbs/index.js +1 -0
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +12 -16
- package/dist/Calendar/Calendar.d.ts.map +1 -1
- package/dist/Calendar/Calendar.js +2 -1
- package/dist/Calendar/styles.d.ts +0 -2
- package/dist/Calendar/styles.d.ts.map +1 -1
- package/dist/Calendar/styles.js +0 -2
- package/dist/Card/Card.d.ts.map +1 -1
- package/dist/Card/Card.js +1 -2
- package/dist/Card/index.d.ts.map +1 -1
- package/dist/Card/index.js +1 -2
- package/dist/Carousel/Carousel.d.ts.map +1 -1
- package/dist/Carousel/Carousel.js +8 -12
- package/dist/Center/Center.d.ts.map +1 -1
- package/dist/Center/Center.js +1 -2
- package/dist/Center/index.d.ts.map +1 -1
- package/dist/Center/index.js +1 -2
- package/dist/Chat/ChatDictationButton.d.ts.map +1 -1
- package/dist/Chat/ChatDictationButton.js +2 -1
- package/dist/Chat/ChatLayoutScrollButton.d.ts.map +1 -1
- package/dist/Chat/ChatLayoutScrollButton.js +3 -1
- package/dist/Chat/ChatSendButton.d.ts.map +1 -1
- package/dist/Chat/ChatSendButton.js +4 -3
- package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
- package/dist/Chat/ChatToolCalls.js +17 -17
- package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
- package/dist/Chat/useTriggerMenu.js +2 -6
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +36 -125
- package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
- package/dist/ClickableCard/ClickableCard.js +3 -11
- package/dist/Code/Code.d.ts.map +1 -1
- package/dist/Code/Code.js +1 -2
- package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
- package/dist/CodeBlock/CodeBlock.js +84 -67
- package/dist/Collapsible/Collapsible.d.ts.map +1 -1
- package/dist/Collapsible/Collapsible.js +35 -15
- package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPalette.js +5 -0
- package/dist/CommandPalette/CommandPaletteItem.d.ts +1 -1
- package/dist/CommandPalette/CommandPaletteItem.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteItem.js +6 -3
- package/dist/CommandPalette/CommandPaletteList.js +1 -1
- package/dist/ComplexSelector/ComplexSelector.d.ts +1 -1
- package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
- package/dist/ComplexSelector/ComplexSelector.js +32 -31
- package/dist/ContextMenu/ContextMenu.d.ts +2 -2
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +1 -1
- package/dist/ContextMenu/index.d.ts +2 -1
- package/dist/ContextMenu/index.d.ts.map +1 -1
- package/dist/ContextMenu/index.js +1 -0
- package/dist/DateInput/DateInput.d.ts +9 -2
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +47 -33
- package/dist/DateRangeInput/DateRangeInput.d.ts +9 -2
- package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
- package/dist/DateRangeInput/DateRangeInput.js +55 -32
- package/dist/DateTimeInput/DateTimeInput.d.ts +9 -1
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +54 -30
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +4 -4
- package/dist/DropdownMenu/DropdownMenu.d.ts +33 -8
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +62 -20
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts +8 -11
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +32 -31
- package/dist/DropdownMenu/DropdownMenuDivider.d.ts +25 -0
- package/dist/DropdownMenu/DropdownMenuDivider.d.ts.map +1 -0
- package/dist/DropdownMenu/DropdownMenuDivider.js +66 -0
- package/dist/DropdownMenu/DropdownMenuItem.d.ts +15 -2
- package/dist/DropdownMenu/DropdownMenuItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuItem.js +20 -8
- package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts +1 -1
- package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuRadioGroup.js +10 -3
- package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts +5 -5
- package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuRadioItem.js +24 -67
- package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuSubMenu.js +2 -8
- package/dist/DropdownMenu/index.d.ts +2 -1
- package/dist/DropdownMenu/index.d.ts.map +1 -1
- package/dist/DropdownMenu/index.js +4 -0
- package/dist/DropdownMenu/renderDropdownItems.d.ts.map +1 -1
- package/dist/DropdownMenu/renderDropdownItems.js +35 -27
- package/dist/EmptyState/EmptyState.d.ts.map +1 -1
- package/dist/EmptyState/EmptyState.js +9 -5
- package/dist/Field/InputClearButton.d.ts +16 -3
- package/dist/Field/InputClearButton.d.ts.map +1 -1
- package/dist/Field/InputClearButton.js +15 -4
- package/dist/Field/PanelSearchInput.d.ts +81 -0
- package/dist/Field/PanelSearchInput.d.ts.map +1 -0
- package/dist/Field/PanelSearchInput.js +151 -0
- package/dist/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +21 -10
- package/dist/Grid/Grid.d.ts.map +1 -1
- package/dist/Grid/Grid.js +1 -2
- package/dist/Grid/index.d.ts.map +1 -1
- package/dist/Grid/index.js +1 -2
- package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
- package/dist/HoverCard/useHoverCard.js +3 -19
- package/dist/Icon/globalIconRegistry.d.ts +5 -4
- package/dist/Icon/globalIconRegistry.d.ts.map +1 -1
- package/dist/Icon/globalIconRegistry.js +5 -4
- package/dist/Indicator/CheckIndicator.d.ts +25 -0
- package/dist/Indicator/CheckIndicator.d.ts.map +1 -0
- package/dist/Indicator/CheckIndicator.js +155 -0
- package/dist/Indicator/CheckboxIndicator.d.ts +22 -0
- package/dist/Indicator/CheckboxIndicator.d.ts.map +1 -0
- package/dist/Indicator/CheckboxIndicator.js +173 -0
- package/dist/Indicator/RadioIndicator.d.ts +23 -0
- package/dist/Indicator/RadioIndicator.d.ts.map +1 -0
- package/dist/Indicator/RadioIndicator.js +137 -0
- package/dist/Indicator/index.d.ts +24 -0
- package/dist/Indicator/index.d.ts.map +1 -0
- package/dist/Indicator/index.js +26 -0
- package/dist/Indicator/indicator.markers.stylex.d.ts +16 -0
- package/dist/Indicator/indicator.markers.stylex.d.ts.map +1 -0
- package/dist/Indicator/indicator.markers.stylex.js +21 -0
- package/dist/Indicator/indicatorRegistry.d.ts +71 -0
- package/dist/Indicator/indicatorRegistry.d.ts.map +1 -0
- package/dist/Indicator/indicatorRegistry.js +93 -0
- package/dist/Indicator/types.d.ts +177 -0
- package/dist/Indicator/types.d.ts.map +1 -0
- package/dist/Indicator/types.js +1 -0
- package/dist/Indicator/useIndicator.d.ts +18 -0
- package/dist/Indicator/useIndicator.d.ts.map +1 -0
- package/dist/Indicator/useIndicator.js +30 -0
- package/dist/Item/Item.d.ts.map +1 -1
- package/dist/Item/Item.js +4 -8
- package/dist/Layer/useLayer.d.ts +14 -0
- package/dist/Layer/useLayer.d.ts.map +1 -1
- package/dist/Layer/useLayer.js +25 -2
- package/dist/Lightbox/Lightbox.d.ts.map +1 -1
- package/dist/Lightbox/Lightbox.js +80 -103
- package/dist/Link/Link.d.ts.map +1 -1
- package/dist/Link/Link.js +4 -5
- package/dist/MobileNav/MobileNav.js +8 -8
- package/dist/MoreMenu/MoreMenu.d.ts +14 -1
- package/dist/MoreMenu/MoreMenu.d.ts.map +1 -1
- package/dist/MoreMenu/MoreMenu.js +4 -0
- package/dist/MultiSelector/MultiSelector.d.ts +8 -1
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +205 -170
- package/dist/NumberInput/NumberInput.d.ts +27 -1
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/NumberInput/NumberInput.js +230 -42
- package/dist/Outline/Outline.d.ts.map +1 -1
- package/dist/Outline/Outline.js +2 -3
- package/dist/Pagination/Pagination.d.ts.map +1 -1
- package/dist/Pagination/Pagination.js +61 -63
- package/dist/Popover/Popover.d.ts.map +1 -1
- package/dist/Popover/Popover.js +6 -8
- package/dist/Popover/usePopover.d.ts +15 -0
- package/dist/Popover/usePopover.d.ts.map +1 -1
- package/dist/Popover/usePopover.js +14 -2
- package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearch.js +1 -1
- package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearchEditPopover.js +7 -0
- package/dist/ProgressBar/ProgressBar.d.ts +11 -4
- package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
- package/dist/ProgressBar/ProgressBar.js +95 -6
- package/dist/RadioList/RadioListItem.d.ts.map +1 -1
- package/dist/RadioList/RadioListItem.js +29 -84
- package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
- package/dist/Resizable/ResizeHandle.js +2 -3
- package/dist/Section/Section.d.ts.map +1 -1
- package/dist/Section/Section.js +1 -2
- package/dist/Section/index.d.ts.map +1 -1
- package/dist/Section/index.js +1 -2
- package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControlItem.js +2 -3
- package/dist/SelectableCard/SelectableCard.d.ts.map +1 -1
- package/dist/SelectableCard/SelectableCard.js +3 -11
- package/dist/Selector/Selector.d.ts +12 -1
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +293 -183
- package/dist/Selector/hooks.d.ts +18 -6
- package/dist/Selector/hooks.d.ts.map +1 -1
- package/dist/Selector/hooks.js +57 -40
- package/dist/SideNav/SideNav.d.ts.map +1 -1
- package/dist/SideNav/SideNav.js +1 -1
- package/dist/SideNav/SideNavCollapseButton.d.ts +1 -1
- package/dist/SideNav/SideNavCollapseButton.d.ts.map +1 -1
- package/dist/SideNav/SideNavCollapseButton.js +29 -14
- package/dist/SideNav/SideNavHeading.d.ts.map +1 -1
- package/dist/SideNav/SideNavHeading.js +56 -20
- package/dist/SideNav/SideNavItem.d.ts.map +1 -1
- package/dist/SideNav/SideNavItem.js +28 -22
- package/dist/Skeleton/Skeleton.d.ts.map +1 -1
- package/dist/Skeleton/Skeleton.js +1 -2
- package/dist/Skeleton/index.d.ts.map +1 -1
- package/dist/Skeleton/index.js +1 -2
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +2 -6
- package/dist/Stack/stack.stylex.d.ts +3 -3
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +14 -13
- package/dist/TabList/Tab.d.ts.map +1 -1
- package/dist/TabList/Tab.js +2 -3
- package/dist/TabList/TabMenu.d.ts.map +1 -1
- package/dist/TabList/TabMenu.js +41 -19
- package/dist/Table/BaseTable.d.ts.map +1 -1
- package/dist/Table/BaseTable.js +8 -0
- package/dist/Table/TableRow.d.ts +1 -1
- package/dist/Table/TableRow.d.ts.map +1 -1
- package/dist/Table/TableRow.js +4 -2
- package/dist/Table/index.d.ts +1 -1
- package/dist/Table/index.d.ts.map +1 -1
- package/dist/Table/index.js +1 -1
- package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts +1 -1
- package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
- package/dist/Table/plugins/groupedRows/useTableGroupedRows.js +22 -18
- package/dist/Table/plugins/rowExpansion/index.d.ts +1 -1
- package/dist/Table/plugins/rowExpansion/index.d.ts.map +1 -1
- package/dist/Table/plugins/rowExpansion/index.js +1 -1
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts +41 -76
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +94 -345
- package/dist/Table/plugins/sortable/useTableSortable.d.ts.map +1 -1
- package/dist/Table/plugins/sortable/useTableSortable.js +23 -4
- package/dist/Table/plugins/tree/useTableTreeData.d.ts.map +1 -1
- package/dist/Table/plugins/tree/useTableTreeData.js +29 -33
- package/dist/Table/tableContextMenu.d.ts.map +1 -1
- package/dist/Table/tableContextMenu.js +2 -1
- package/dist/Table/types.d.ts +11 -0
- package/dist/Table/types.d.ts.map +1 -1
- package/dist/TextArea/TextArea.d.ts +10 -1
- package/dist/TextArea/TextArea.d.ts.map +1 -1
- package/dist/TextArea/TextArea.js +16 -6
- package/dist/TextInput/TextInput.d.ts +10 -1
- package/dist/TextInput/TextInput.d.ts.map +1 -1
- package/dist/TextInput/TextInput.js +13 -19
- package/dist/Thumbnail/Thumbnail.d.ts.map +1 -1
- package/dist/Thumbnail/Thumbnail.js +32 -26
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +14 -17
- package/dist/Timestamp/Timestamp.d.ts.map +1 -1
- package/dist/Timestamp/Timestamp.js +14 -3
- package/dist/Timestamp/TimestampHoverCard.d.ts.map +1 -1
- package/dist/Timestamp/TimestampHoverCard.js +16 -34
- package/dist/Timestamp/formatInstant.d.ts +11 -1
- package/dist/Timestamp/formatInstant.d.ts.map +1 -1
- package/dist/Timestamp/formatInstant.js +3 -1
- package/dist/Token/Token.d.ts.map +1 -1
- package/dist/Token/Token.js +20 -11
- package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.js +4 -2
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +3 -19
- package/dist/TopNav/TopNav.d.ts.map +1 -1
- package/dist/TopNav/TopNav.js +6 -5
- package/dist/TopNav/TopNavHeading.d.ts.map +1 -1
- package/dist/TopNav/TopNavHeading.js +54 -16
- package/dist/TopNav/TopNavItem.d.ts.map +1 -1
- package/dist/TopNav/TopNavItem.js +3 -9
- package/dist/TopNav/TopNavMegaMenu.d.ts +7 -0
- package/dist/TopNav/TopNavMegaMenu.d.ts.map +1 -1
- package/dist/TopNav/TopNavMegaMenu.js +146 -60
- package/dist/TopNav/TopNavMegaMenuItem.d.ts.map +1 -1
- package/dist/TopNav/TopNavMegaMenuItem.js +22 -3
- package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
- package/dist/TopNav/TopNavMenu.js +83 -38
- package/dist/TreeList/TreeList.d.ts.map +1 -1
- package/dist/TreeList/TreeList.js +13 -0
- package/dist/TreeList/TreeListBranches.d.ts +1 -1
- package/dist/TreeList/TreeListBranches.d.ts.map +1 -1
- package/dist/TreeList/TreeListBranches.js +14 -4
- package/dist/TreeList/TreeListItem.d.ts +11 -1
- package/dist/TreeList/TreeListItem.d.ts.map +1 -1
- package/dist/TreeList/TreeListItem.js +57 -30
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +16 -8
- package/dist/VisuallyHidden/VisuallyHidden.d.ts.map +1 -1
- package/dist/VisuallyHidden/VisuallyHidden.js +1 -2
- package/dist/VisuallyHidden/index.d.ts.map +1 -1
- package/dist/VisuallyHidden/index.js +1 -2
- package/dist/astryx.css +86 -96
- package/dist/astryx.umd.js +56 -56
- package/dist/astryx.umd.js.map +4 -4
- package/dist/hooks/containerReveal.stylex.d.ts +61 -0
- package/dist/hooks/containerReveal.stylex.d.ts.map +1 -0
- package/dist/hooks/containerReveal.stylex.js +63 -0
- package/dist/hooks/index.d.ts +3 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +2 -0
- package/dist/hooks/useClipboard.d.ts +59 -0
- package/dist/hooks/useClipboard.d.ts.map +1 -0
- package/dist/hooks/useClipboard.js +106 -0
- package/dist/hooks/useContainerReveal.d.ts +14 -8
- package/dist/hooks/useContainerReveal.d.ts.map +1 -1
- package/dist/hooks/useContainerReveal.js +28 -69
- package/dist/hooks/useIndicatorFocusRing.d.ts +28 -0
- package/dist/hooks/useIndicatorFocusRing.d.ts.map +1 -0
- package/dist/hooks/useIndicatorFocusRing.js +96 -0
- package/dist/hooks/useInputStatusIcon.d.ts.map +1 -1
- package/dist/hooks/useInputStatusIcon.js +17 -4
- package/dist/hooks/useLongPress.d.ts.map +1 -1
- package/dist/hooks/useLongPress.js +12 -2
- package/dist/hooks/useScrollLock.d.ts.map +1 -1
- package/dist/hooks/useScrollLock.js +33 -18
- package/dist/hooks/useStreamingText.d.ts +18 -3
- package/dist/hooks/useStreamingText.d.ts.map +1 -1
- package/dist/hooks/useStreamingText.js +47 -7
- package/dist/hooks/useTreeFocus.d.ts.map +1 -1
- package/dist/hooks/useTreeFocus.js +9 -3
- package/dist/hooks/useTypeahead.d.ts +3 -2
- package/dist/hooks/useTypeahead.d.ts.map +1 -1
- package/dist/hooks/useTypeahead.js +19 -7
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/theme/defineTheme.d.ts +18 -2
- package/dist/theme/defineTheme.d.ts.map +1 -1
- package/dist/theme/defineTheme.js +10 -1
- package/dist/theme/derivedVarRegistry.d.ts +8 -0
- package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
- package/dist/theme/derivedVarRegistry.js +14 -13
- package/dist/theme/expandColorScale.d.ts +3 -1
- package/dist/theme/expandColorScale.d.ts.map +1 -1
- package/dist/theme/expandColorScale.js +17 -7
- package/dist/theme/expandTypeScale.d.ts.map +1 -1
- package/dist/theme/expandTypeScale.js +17 -0
- package/dist/theme/generateThemeRules.d.ts.map +1 -1
- package/dist/theme/generateThemeRules.js +11 -0
- package/dist/theme/index.d.ts +2 -1
- package/dist/theme/index.d.ts.map +1 -1
- package/dist/theme/index.js +1 -1
- package/dist/theme/tokens.stylex.d.ts +12 -0
- package/dist/theme/tokens.stylex.d.ts.map +1 -1
- package/dist/theme/tokens.stylex.js +23 -0
- package/dist/utils/focusOutline.stylex.d.ts +122 -0
- package/dist/utils/focusOutline.stylex.d.ts.map +1 -0
- package/dist/utils/focusOutline.stylex.js +143 -0
- package/dist/utils/focusReturn.d.ts +24 -0
- package/dist/utils/focusReturn.d.ts.map +1 -0
- package/dist/utils/focusReturn.js +29 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +6 -1
- package/dist/utils/interactionModality.d.ts +34 -0
- package/dist/utils/interactionModality.d.ts.map +1 -0
- package/dist/utils/interactionModality.js +70 -0
- package/dist/utils/mergeRefs.d.ts.map +1 -1
- package/dist/utils/mergeRefs.js +7 -5
- package/dist/utils/rtlStyles.d.ts +2 -2
- package/dist/utils/rtlStyles.js +2 -2
- package/dist/utils/themeProps.d.ts +20 -1
- package/dist/utils/themeProps.d.ts.map +1 -1
- package/dist/utils/themeProps.js +8 -2
- package/locales/en.json +52 -0
- package/locales/pseudo.json +39 -0
- package/package.json +8 -3
- package/src/AlertDialog/AlertDialog.doc.mjs +16 -3
- package/src/AlertDialog/AlertDialog.test.tsx +208 -1
- package/src/AlertDialog/AlertDialog.tsx +21 -3
- package/src/AppShell/AppShell.doc.mjs +14 -0
- package/src/AppShell/AppShell.test.tsx +139 -0
- package/src/AppShell/AppShell.tsx +32 -22
- package/src/AspectRatio/AspectRatio.doc.mjs +27 -3
- package/src/AspectRatio/AspectRatio.test.tsx +30 -0
- package/src/AspectRatio/AspectRatio.tsx +13 -2
- package/src/AspectRatio/index.ts +0 -2
- package/src/Avatar/Avatar.doc.mjs +2 -13
- package/src/Avatar/Avatar.test.tsx +33 -8
- package/src/Avatar/Avatar.tsx +52 -50
- package/src/AvatarGroup/AvatarGroupOverflow.tsx +2 -9
- package/src/Badge/Badge.tsx +0 -2
- package/src/Badge/index.ts +0 -2
- package/src/Banner/Banner.test.tsx +58 -1
- package/src/Banner/Banner.tsx +33 -11
- package/src/Blockquote/Blockquote.tsx +0 -2
- package/src/Blockquote/index.ts +0 -2
- package/src/Breadcrumbs/BreadcrumbItem.tsx +16 -6
- package/src/Breadcrumbs/index.ts +5 -1
- package/src/Button/Button.doc.mjs +2 -2
- package/src/Button/Button.tsx +22 -41
- package/src/Calendar/Calendar.tsx +2 -1
- package/src/Calendar/styles.ts +0 -8
- package/src/Card/Card.tsx +0 -2
- package/src/Card/index.ts +0 -2
- package/src/Carousel/Carousel.doc.mjs +3 -0
- package/src/Carousel/Carousel.tsx +10 -6
- package/src/Center/Center.tsx +0 -2
- package/src/Center/index.ts +0 -2
- package/src/Chat/Chat.doc.mjs +2 -0
- package/src/Chat/ChatDictationButton.tsx +7 -1
- package/src/Chat/ChatLayoutScrollButton.test.tsx +22 -0
- package/src/Chat/ChatLayoutScrollButton.tsx +8 -1
- package/src/Chat/ChatSendButton.tsx +2 -3
- package/src/Chat/ChatToolCalls.test.tsx +35 -0
- package/src/Chat/ChatToolCalls.tsx +36 -16
- package/src/Chat/useTriggerMenu.tsx +2 -5
- package/src/CheckboxInput/CheckboxInput.doc.mjs +4 -2
- package/src/CheckboxInput/CheckboxInput.test.tsx +120 -0
- package/src/CheckboxInput/CheckboxInput.tsx +45 -202
- package/src/ClickableCard/ClickableCard.tsx +6 -11
- package/src/Code/Code.tsx +0 -2
- package/src/CodeBlock/CodeBlock.doc.mjs +3 -0
- package/src/CodeBlock/CodeBlock.test.tsx +91 -0
- package/src/CodeBlock/CodeBlock.tsx +57 -80
- package/src/Collapsible/Collapsible.tsx +22 -18
- package/src/CommandPalette/CommandPalette.test.tsx +83 -0
- package/src/CommandPalette/CommandPalette.tsx +5 -0
- package/src/CommandPalette/CommandPaletteItem.tsx +7 -4
- package/src/CommandPalette/CommandPaletteList.tsx +2 -2
- package/src/ComplexSelector/ComplexSelector.doc.mjs +42 -0
- package/src/ComplexSelector/ComplexSelector.test.tsx +73 -0
- package/src/ComplexSelector/ComplexSelector.tsx +41 -28
- package/src/ContextMenu/ContextMenu.doc.mjs +1 -1
- package/src/ContextMenu/ContextMenu.test.tsx +41 -0
- package/src/ContextMenu/ContextMenu.tsx +3 -3
- package/src/ContextMenu/index.ts +5 -1
- package/src/DateInput/DateInput.doc.mjs +18 -3
- package/src/DateInput/DateInput.test.tsx +112 -11
- package/src/DateInput/DateInput.tsx +44 -25
- package/src/DateRangeInput/DateRangeInput.doc.mjs +10 -2
- package/src/DateRangeInput/DateRangeInput.test.tsx +110 -14
- package/src/DateRangeInput/DateRangeInput.tsx +27 -21
- package/src/DateTimeInput/DateTimeInput.doc.mjs +16 -2
- package/src/DateTimeInput/DateTimeInput.test.tsx +154 -1
- package/src/DateTimeInput/DateTimeInput.tsx +58 -17
- package/src/Dialog/Dialog.tsx +2 -9
- package/src/DropdownMenu/DropdownMenu.doc.mjs +5 -9
- package/src/DropdownMenu/DropdownMenu.test.tsx +534 -4
- package/src/DropdownMenu/DropdownMenu.tsx +113 -52
- package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +27 -30
- package/src/DropdownMenu/DropdownMenuDivider.doc.mjs +34 -0
- package/src/DropdownMenu/DropdownMenuDivider.tsx +81 -0
- package/src/DropdownMenu/DropdownMenuItem.doc.mjs +39 -4
- package/src/DropdownMenu/DropdownMenuItem.tsx +48 -10
- package/src/DropdownMenu/DropdownMenuRadioGroup.tsx +2 -1
- package/src/DropdownMenu/DropdownMenuRadioItem.tsx +30 -76
- package/src/DropdownMenu/DropdownMenuSelectable.test.tsx +25 -19
- package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +44 -14
- package/src/DropdownMenu/DropdownMenuSubMenu.tsx +1 -5
- package/src/DropdownMenu/index.ts +8 -1
- package/src/DropdownMenu/renderDropdownItems.tsx +33 -34
- package/src/EmptyState/EmptyState.doc.mjs +4 -0
- package/src/EmptyState/EmptyState.test.tsx +62 -0
- package/src/EmptyState/EmptyState.tsx +16 -6
- package/src/Field/Field.doc.mjs +1 -0
- package/src/Field/InputClearButton.test.tsx +117 -0
- package/src/Field/InputClearButton.tsx +30 -3
- package/src/Field/PanelSearchInput.tsx +271 -0
- package/src/FileInput/FileInput.tsx +21 -8
- package/src/Grid/Grid.tsx +0 -2
- package/src/Grid/index.ts +0 -2
- package/src/HoverCard/useHoverCard.tsx +2 -23
- package/src/Icon/Icon.doc.mjs +2 -1
- package/src/Icon/globalIconRegistry.tsx +5 -4
- package/src/Indicator/CheckIndicator.tsx +174 -0
- package/src/Indicator/CheckboxIndicator.tsx +257 -0
- package/src/Indicator/Indicator.doc.mjs +344 -0
- package/src/Indicator/Indicator.test.tsx +393 -0
- package/src/Indicator/RadioIndicator.tsx +206 -0
- package/src/Indicator/index.ts +46 -0
- package/src/Indicator/indicator.markers.stylex.ts +19 -0
- package/src/Indicator/indicatorRegistry.test.tsx +106 -0
- package/src/Indicator/indicatorRegistry.ts +120 -0
- package/src/Indicator/types.ts +197 -0
- package/src/Indicator/useIndicator.ts +39 -0
- package/src/Item/Item.doc.mjs +1 -1
- package/src/Item/Item.tsx +7 -14
- package/src/Layer/useLayer.doc.mjs +2 -2
- package/src/Layer/useLayer.test.tsx +85 -0
- package/src/Layer/useLayer.tsx +39 -1
- package/src/Lightbox/Lightbox.doc.mjs +1 -1
- package/src/Lightbox/Lightbox.tsx +45 -49
- package/src/Link/Link.tsx +4 -11
- package/src/MobileNav/MobileNav.tsx +3 -3
- package/src/MoreMenu/MoreMenu.doc.mjs +31 -0
- package/src/MoreMenu/MoreMenu.test.tsx +68 -1
- package/src/MoreMenu/MoreMenu.tsx +19 -0
- package/src/MultiSelector/MultiSelector.doc.mjs +18 -1
- package/src/MultiSelector/MultiSelector.test.tsx +387 -18
- package/src/MultiSelector/MultiSelector.tsx +267 -220
- package/src/NumberInput/NumberInput.doc.mjs +265 -50
- package/src/NumberInput/NumberInput.test.tsx +726 -21
- package/src/NumberInput/NumberInput.tsx +353 -59
- package/src/Outline/Outline.tsx +2 -5
- package/src/Pagination/Pagination.doc.mjs +1 -1
- package/src/Pagination/Pagination.test.tsx +22 -7
- package/src/Pagination/Pagination.tsx +38 -35
- package/src/Popover/Popover.doc.mjs +2 -0
- package/src/Popover/Popover.tsx +6 -7
- package/src/Popover/usePopover.tsx +37 -4
- package/src/PowerSearch/PowerSearch.tsx +1 -1
- package/src/PowerSearch/PowerSearchEditPopover.test.tsx +42 -0
- package/src/PowerSearch/PowerSearchEditPopover.tsx +7 -0
- package/src/ProgressBar/ProgressBar.doc.mjs +27 -5
- package/src/ProgressBar/ProgressBar.test.tsx +224 -1
- package/src/ProgressBar/ProgressBar.tsx +126 -30
- package/src/RadioList/RadioList.doc.mjs +4 -2
- package/src/RadioList/RadioListItem.tsx +31 -130
- package/src/Resizable/ResizeHandle.tsx +2 -9
- package/src/Section/Section.tsx +0 -2
- package/src/Section/index.ts +0 -2
- package/src/SegmentedControl/SegmentedControlItem.tsx +2 -9
- package/src/SelectableCard/SelectableCard.tsx +4 -9
- package/src/Selector/Selector.doc.mjs +17 -4
- package/src/Selector/Selector.test.tsx +1394 -100
- package/src/Selector/Selector.tsx +355 -212
- package/src/Selector/hooks.ts +77 -50
- package/src/SideNav/SideNav.test.tsx +46 -0
- package/src/SideNav/SideNav.tsx +1 -0
- package/src/SideNav/SideNavCollapseButton.tsx +15 -10
- package/src/SideNav/SideNavHeading.tsx +48 -10
- package/src/SideNav/SideNavItem.tsx +20 -12
- package/src/Skeleton/Skeleton.tsx +0 -2
- package/src/Skeleton/index.ts +0 -2
- package/src/Slider/Slider.tsx +2 -11
- package/src/Stack/Stack.doc.mjs +110 -0
- package/src/StatusDot/StatusDot.doc.mjs +12 -3
- package/src/Switch/Switch.test.tsx +32 -0
- package/src/Switch/Switch.tsx +18 -10
- package/src/TabList/Tab.tsx +2 -9
- package/src/TabList/TabMenu.tsx +14 -21
- package/src/Table/BaseTable.tsx +11 -0
- package/src/Table/Table.test.tsx +31 -0
- package/src/Table/TableRow.tsx +6 -0
- package/src/Table/index.ts +1 -4
- package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +30 -14
- package/src/Table/plugins/rowExpansion/index.ts +1 -4
- package/src/Table/plugins/rowExpansion/useTableRowExpansion.test.tsx +125 -175
- package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +115 -458
- package/src/Table/plugins/sortable/useTableSortable.tsx +2 -6
- package/src/Table/plugins/tree/useTableTreeData.tsx +43 -24
- package/src/Table/tableContextMenu.test.tsx +27 -0
- package/src/Table/tableContextMenu.tsx +1 -0
- package/src/Table/types.ts +11 -0
- package/src/Table/useTableGroupedRows.doc.mjs +1 -1
- package/src/Table/useTableRowExpansion.doc.mjs +43 -66
- package/src/TextArea/TextArea.doc.mjs +42 -2
- package/src/TextArea/TextArea.test.tsx +219 -0
- package/src/TextArea/TextArea.tsx +34 -7
- package/src/TextInput/TextInput.doc.mjs +18 -2
- package/src/TextInput/TextInput.test.tsx +189 -0
- package/src/TextInput/TextInput.tsx +25 -31
- package/src/Thumbnail/Thumbnail.tsx +2 -9
- package/src/TimeInput/TimeInput.doc.mjs +2 -2
- package/src/TimeInput/TimeInput.test.tsx +84 -3
- package/src/TimeInput/TimeInput.tsx +26 -28
- package/src/Timestamp/Timestamp.doc.mjs +4 -3
- package/src/Timestamp/Timestamp.test.tsx +91 -14
- package/src/Timestamp/Timestamp.tsx +14 -2
- package/src/Timestamp/TimestampHoverCard.tsx +13 -35
- package/src/Timestamp/formatInstant.ts +16 -1
- package/src/Token/Token.tsx +5 -28
- package/src/Tokenizer/Tokenizer.doc.mjs +2 -2
- package/src/Tokenizer/Tokenizer.test.tsx +45 -3
- package/src/Tokenizer/Tokenizer.tsx +10 -2
- package/src/Tooltip/useTooltip.tsx +2 -23
- package/src/TopNav/TopNav.tsx +1 -3
- package/src/TopNav/TopNavHeading.tsx +42 -13
- package/src/TopNav/TopNavItem.tsx +3 -21
- package/src/TopNav/TopNavMegaMenu.doc.mjs +5 -0
- package/src/TopNav/TopNavMegaMenu.test.tsx +433 -33
- package/src/TopNav/TopNavMegaMenu.tsx +118 -53
- package/src/TopNav/TopNavMegaMenuItem.tsx +2 -9
- package/src/TopNav/TopNavMenu.tsx +34 -37
- package/src/TreeList/TreeList.doc.mjs +5 -0
- package/src/TreeList/TreeList.test.tsx +208 -8
- package/src/TreeList/TreeList.tsx +22 -0
- package/src/TreeList/TreeListBranches.tsx +25 -2
- package/src/TreeList/TreeListItem.tsx +89 -52
- package/src/Typeahead/BaseTypeahead.tsx +19 -6
- package/src/Typeahead/Typeahead.doc.mjs +1 -0
- package/src/Typeahead/Typeahead.test.tsx +76 -3
- package/src/VisuallyHidden/VisuallyHidden.tsx +0 -2
- package/src/VisuallyHidden/index.ts +0 -2
- package/src/hooks/containerReveal.stylex.ts +102 -0
- package/src/hooks/index.ts +4 -0
- package/src/hooks/useClipboard.doc.mjs +87 -0
- package/src/hooks/useClipboard.test.tsx +151 -0
- package/src/hooks/useClipboard.ts +135 -0
- package/src/hooks/useContainerReveal.doc.mjs +7 -7
- package/src/hooks/useContainerReveal.test.tsx +42 -62
- package/src/hooks/useContainerReveal.ts +36 -91
- package/src/hooks/useImageMode.test.ts +1 -1
- package/src/hooks/useIndicatorFocusRing.tsx +120 -0
- package/src/hooks/useInputStatusIcon.tsx +8 -8
- package/src/hooks/useLongPress.test.tsx +18 -0
- package/src/hooks/useLongPress.ts +12 -2
- package/src/hooks/useScrollLock.test.ts +72 -0
- package/src/hooks/useScrollLock.ts +51 -19
- package/src/hooks/useStreamingText.test.ts +46 -1
- package/src/hooks/useStreamingText.ts +49 -7
- package/src/hooks/useTreeFocus.test.tsx +50 -0
- package/src/hooks/useTreeFocus.ts +14 -3
- package/src/hooks/useTypeahead.test.tsx +150 -0
- package/src/hooks/useTypeahead.ts +24 -9
- package/src/i18n/InternationalizationProvider.doc.mjs +7 -2
- package/src/index.ts +1 -0
- package/src/serverSafeComponents.test.ts +665 -0
- package/src/theme/defineTheme.ts +27 -0
- package/src/theme/derivedVarRegistry.test.ts +21 -1
- package/src/theme/derivedVarRegistry.ts +19 -6
- package/src/theme/expandColorScale.test.ts +14 -0
- package/src/theme/expandColorScale.ts +24 -9
- package/src/theme/expandTypeScale.test.ts +43 -0
- package/src/theme/expandTypeScale.ts +17 -0
- package/src/theme/generateThemeRules.test.ts +78 -20
- package/src/theme/generateThemeRules.ts +11 -0
- package/src/theme/index.ts +15 -0
- package/src/theme/tokens.stylex.ts +18 -0
- package/src/theme/tokens.test.ts +37 -0
- package/src/utils/focusOutline.stylex.ts +177 -0
- package/src/utils/focusReturn.test.ts +42 -0
- package/src/utils/focusReturn.ts +31 -0
- package/src/utils/index.ts +5 -0
- package/src/utils/interactionModality.ts +74 -0
- package/src/utils/mergeRefs.test.ts +48 -0
- package/src/utils/mergeRefs.ts +12 -6
- package/src/utils/rtlStyles.ts +2 -2
- package/src/utils/themeProps.ts +26 -4
- package/dist/CheckboxInput/checkbox.markers.stylex.d.ts +0 -9
- package/dist/CheckboxInput/checkbox.markers.stylex.d.ts.map +0 -1
- package/dist/CheckboxInput/checkbox.markers.stylex.js +0 -14
- package/dist/RadioList/radio.markers.stylex.d.ts +0 -7
- package/dist/RadioList/radio.markers.stylex.d.ts.map +0 -1
- package/dist/RadioList/radio.markers.stylex.js +0 -12
- package/dist/hooks/containerReveal.pool.stylex.d.ts +0 -303
- package/dist/hooks/containerReveal.pool.stylex.d.ts.map +0 -1
- package/dist/hooks/containerReveal.pool.stylex.js +0 -292
- package/src/CheckboxInput/checkbox.markers.stylex.ts +0 -12
- package/src/RadioList/radio.markers.stylex.ts +0 -10
- package/src/hooks/containerReveal.pool.stylex.ts +0 -571
|
@@ -10,8 +10,10 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import {describe, it, expect, vi, beforeEach} from 'vitest';
|
|
13
|
-
import {render, screen, fireEvent} from '@testing-library/react';
|
|
13
|
+
import {render, screen, fireEvent, waitFor} from '@testing-library/react';
|
|
14
|
+
import userEvent from '@testing-library/user-event';
|
|
14
15
|
import {AlertDialog} from './AlertDialog';
|
|
16
|
+
import {useImperativeAlertDialog} from './useImperativeAlertDialog';
|
|
15
17
|
|
|
16
18
|
beforeEach(() => {
|
|
17
19
|
HTMLDialogElement.prototype.showModal = vi.fn(function (
|
|
@@ -111,4 +113,209 @@ describe('AlertDialog', () => {
|
|
|
111
113
|
render(<AlertDialog {...defaultProps} />);
|
|
112
114
|
expect(screen.getByText('Cancel')).toBeInTheDocument();
|
|
113
115
|
});
|
|
116
|
+
|
|
117
|
+
describe('keyboard', () => {
|
|
118
|
+
it('calls onOpenChange(false) on Escape', async () => {
|
|
119
|
+
const onOpenChange = vi.fn();
|
|
120
|
+
render(<AlertDialog {...defaultProps} onOpenChange={onOpenChange} />);
|
|
121
|
+
fireEvent.keyDown(screen.getByRole('alertdialog'), {key: 'Escape'});
|
|
122
|
+
await waitFor(() => expect(onOpenChange).toHaveBeenCalledWith(false));
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('does not call onAction on Escape', () => {
|
|
126
|
+
const onAction = vi.fn();
|
|
127
|
+
render(<AlertDialog {...defaultProps} onAction={onAction} />);
|
|
128
|
+
fireEvent.keyDown(screen.getByRole('alertdialog'), {key: 'Escape'});
|
|
129
|
+
expect(onAction).not.toHaveBeenCalled();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('activates cancel with Enter', async () => {
|
|
133
|
+
const user = userEvent.setup();
|
|
134
|
+
const onOpenChange = vi.fn();
|
|
135
|
+
render(<AlertDialog {...defaultProps} onOpenChange={onOpenChange} />);
|
|
136
|
+
screen.getByRole('button', {name: 'Cancel'}).focus();
|
|
137
|
+
await user.keyboard('{Enter}');
|
|
138
|
+
expect(onOpenChange).toHaveBeenCalledWith(false);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('activates the action button with Space', async () => {
|
|
142
|
+
const user = userEvent.setup();
|
|
143
|
+
const onAction = vi.fn();
|
|
144
|
+
render(<AlertDialog {...defaultProps} onAction={onAction} />);
|
|
145
|
+
screen.getByRole('button', {name: 'Delete'}).focus();
|
|
146
|
+
await user.keyboard(' ');
|
|
147
|
+
expect(onAction).toHaveBeenCalled();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('reaches both buttons by Tab, cancel first', async () => {
|
|
151
|
+
const user = userEvent.setup();
|
|
152
|
+
render(<AlertDialog {...defaultProps} />);
|
|
153
|
+
const cancel = screen.getByRole('button', {name: 'Cancel'});
|
|
154
|
+
const action = screen.getByRole('button', {name: 'Delete'});
|
|
155
|
+
cancel.focus();
|
|
156
|
+
expect(cancel).toHaveFocus();
|
|
157
|
+
await user.tab();
|
|
158
|
+
expect(action).toHaveFocus();
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
describe('focus', () => {
|
|
163
|
+
it('marks cancel as the initial focus target', () => {
|
|
164
|
+
render(<AlertDialog {...defaultProps} />);
|
|
165
|
+
// Dialog focuses [data-autofocus] itself after showModal(), so the
|
|
166
|
+
// contract this component owns is which element carries the attribute.
|
|
167
|
+
expect(screen.getByRole('button', {name: 'Cancel'})).toHaveAttribute(
|
|
168
|
+
'data-autofocus',
|
|
169
|
+
);
|
|
170
|
+
expect(screen.getByRole('button', {name: 'Delete'})).not.toHaveAttribute(
|
|
171
|
+
'data-autofocus',
|
|
172
|
+
);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('keeps the initial focus target on the least destructive action', () => {
|
|
176
|
+
render(<AlertDialog {...defaultProps} cancelLabel="Never mind" />);
|
|
177
|
+
expect(screen.getByRole('button', {name: 'Never mind'})).toHaveAttribute(
|
|
178
|
+
'data-autofocus',
|
|
179
|
+
);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('returns focus to the trigger when it closes', async () => {
|
|
183
|
+
const trigger = document.createElement('button');
|
|
184
|
+
trigger.textContent = 'Open';
|
|
185
|
+
document.body.appendChild(trigger);
|
|
186
|
+
trigger.focus();
|
|
187
|
+
|
|
188
|
+
const {rerender} = render(<AlertDialog {...defaultProps} />);
|
|
189
|
+
rerender(<AlertDialog {...defaultProps} isOpen={false} />);
|
|
190
|
+
|
|
191
|
+
await waitFor(() => expect(trigger).toHaveFocus());
|
|
192
|
+
trigger.remove();
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('does not disable the cancel button while the action is loading', () => {
|
|
196
|
+
render(<AlertDialog {...defaultProps} isActionLoading />);
|
|
197
|
+
expect(screen.getByRole('button', {name: 'Cancel'})).toBeEnabled();
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
describe('aria', () => {
|
|
202
|
+
it('marks the action button busy while loading', () => {
|
|
203
|
+
render(<AlertDialog {...defaultProps} isActionLoading />);
|
|
204
|
+
expect(screen.getByRole('button', {name: /Delete/})).toHaveAttribute(
|
|
205
|
+
'aria-busy',
|
|
206
|
+
'true',
|
|
207
|
+
);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('is a modal alertdialog', () => {
|
|
211
|
+
render(<AlertDialog {...defaultProps} />);
|
|
212
|
+
expect(screen.getByRole('alertdialog')).toHaveAttribute(
|
|
213
|
+
'aria-modal',
|
|
214
|
+
'true',
|
|
215
|
+
);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
describe('the inline preview path', () => {
|
|
219
|
+
it('does not claim the alertdialog role', () => {
|
|
220
|
+
render(<AlertDialog {...defaultProps} isInline />);
|
|
221
|
+
expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument();
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('exposes a named group instead, with no aria-modal', () => {
|
|
225
|
+
render(<AlertDialog {...defaultProps} isInline />);
|
|
226
|
+
const group = screen.getByRole('group', {name: 'Delete item?'});
|
|
227
|
+
expect(group).not.toHaveAttribute('aria-modal');
|
|
228
|
+
const describedBy = group.getAttribute('aria-describedby');
|
|
229
|
+
expect(describedBy).toBeTruthy();
|
|
230
|
+
expect(document.getElementById(describedBy!)).toHaveTextContent(
|
|
231
|
+
'This action cannot be undone.',
|
|
232
|
+
);
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
describe('useImperativeAlertDialog', () => {
|
|
239
|
+
const noop = () => {};
|
|
240
|
+
|
|
241
|
+
function Harness({onAction = noop}: {onAction?: () => unknown}) {
|
|
242
|
+
const alert = useImperativeAlertDialog();
|
|
243
|
+
return (
|
|
244
|
+
<>
|
|
245
|
+
<button
|
|
246
|
+
type="button"
|
|
247
|
+
onClick={() =>
|
|
248
|
+
alert.show({
|
|
249
|
+
title: 'Delete item?',
|
|
250
|
+
description: 'This action cannot be undone.',
|
|
251
|
+
actionLabel: 'Delete',
|
|
252
|
+
onAction,
|
|
253
|
+
})
|
|
254
|
+
}>
|
|
255
|
+
Open
|
|
256
|
+
</button>
|
|
257
|
+
<button type="button" onClick={alert.hide}>
|
|
258
|
+
Close it
|
|
259
|
+
</button>
|
|
260
|
+
<span data-testid="is-open">{String(alert.isOpen)}</span>
|
|
261
|
+
{alert.element}
|
|
262
|
+
</>
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
it('renders nothing until show is called', () => {
|
|
267
|
+
render(<Harness />);
|
|
268
|
+
expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument();
|
|
269
|
+
expect(screen.getByTestId('is-open')).toHaveTextContent('false');
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('shows the dialog with the given options', async () => {
|
|
273
|
+
const user = userEvent.setup();
|
|
274
|
+
render(<Harness />);
|
|
275
|
+
await user.click(screen.getByRole('button', {name: 'Open'}));
|
|
276
|
+
expect(screen.getByRole('alertdialog')).toBeInTheDocument();
|
|
277
|
+
expect(screen.getByText('Delete item?')).toBeInTheDocument();
|
|
278
|
+
expect(
|
|
279
|
+
screen.getByText('This action cannot be undone.'),
|
|
280
|
+
).toBeInTheDocument();
|
|
281
|
+
expect(screen.getByTestId('is-open')).toHaveTextContent('true');
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('hides on hide()', async () => {
|
|
285
|
+
const user = userEvent.setup();
|
|
286
|
+
render(<Harness />);
|
|
287
|
+
await user.click(screen.getByRole('button', {name: 'Open'}));
|
|
288
|
+
await user.click(screen.getByRole('button', {name: 'Close it'}));
|
|
289
|
+
expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument();
|
|
290
|
+
expect(screen.getByTestId('is-open')).toHaveTextContent('false');
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('closes when the dialog cancels', async () => {
|
|
294
|
+
const user = userEvent.setup();
|
|
295
|
+
render(<Harness />);
|
|
296
|
+
await user.click(screen.getByRole('button', {name: 'Open'}));
|
|
297
|
+
await user.click(screen.getByRole('button', {name: 'Cancel'}));
|
|
298
|
+
expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument();
|
|
299
|
+
expect(screen.getByTestId('is-open')).toHaveTextContent('false');
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it('forwards onAction and leaves closing to the caller', async () => {
|
|
303
|
+
const user = userEvent.setup();
|
|
304
|
+
const onAction = vi.fn();
|
|
305
|
+
render(<Harness onAction={onAction} />);
|
|
306
|
+
await user.click(screen.getByRole('button', {name: 'Open'}));
|
|
307
|
+
await user.click(screen.getByRole('button', {name: 'Delete'}));
|
|
308
|
+
expect(onAction).toHaveBeenCalledTimes(1);
|
|
309
|
+
expect(screen.getByRole('alertdialog')).toBeInTheDocument();
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it('can be shown again after being hidden', async () => {
|
|
313
|
+
const user = userEvent.setup();
|
|
314
|
+
render(<Harness />);
|
|
315
|
+
await user.click(screen.getByRole('button', {name: 'Open'}));
|
|
316
|
+
await user.click(screen.getByRole('button', {name: 'Close it'}));
|
|
317
|
+
await user.click(screen.getByRole('button', {name: 'Open'}));
|
|
318
|
+
expect(screen.getByRole('alertdialog')).toBeInTheDocument();
|
|
319
|
+
expect(screen.getByTestId('is-open')).toHaveTextContent('true');
|
|
320
|
+
});
|
|
114
321
|
});
|
|
@@ -39,7 +39,8 @@ export interface AlertDialogProps extends BaseProps<HTMLDialogElement> {
|
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
41
|
* Renders alert dialog content inline without modal behavior.
|
|
42
|
-
* For documentation previews and showcases only.
|
|
42
|
+
* For documentation previews and showcases only. The inline path is not a
|
|
43
|
+
* modal, so it renders `role="group"` rather than `role="alertdialog"`.
|
|
43
44
|
* @default false
|
|
44
45
|
*/
|
|
45
46
|
isInline?: boolean;
|
|
@@ -100,9 +101,16 @@ export interface AlertDialogProps extends BaseProps<HTMLDialogElement> {
|
|
|
100
101
|
/**
|
|
101
102
|
* A confirmation dialog for destructive or irreversible actions.
|
|
102
103
|
*
|
|
104
|
+
* Implements the WAI-ARIA APG Alert Dialog pattern:
|
|
105
|
+
* https://www.w3.org/WAI/ARIA/apg/patterns/alertdialog/
|
|
106
|
+
*
|
|
103
107
|
* Uses `role="alertdialog"` and requires explicit user action to dismiss.
|
|
104
108
|
* Cannot be dismissed by clicking outside. Escape key triggers cancel.
|
|
105
|
-
* Initial focus goes to the cancel button (least destructive action)
|
|
109
|
+
* Initial focus goes to the cancel button (least destructive action), pinned
|
|
110
|
+
* with `data-autofocus` so it survives any change to the footer's order.
|
|
111
|
+
*
|
|
112
|
+
* The `isInline` preview path is not modal, so it renders `role="group"`
|
|
113
|
+
* instead — the alertdialog role would promise modality it does not have.
|
|
106
114
|
*
|
|
107
115
|
* @example
|
|
108
116
|
* ```
|
|
@@ -153,7 +161,12 @@ export function AlertDialog({
|
|
|
153
161
|
onOpenChange={onOpenChange}
|
|
154
162
|
width={width}
|
|
155
163
|
purpose="form"
|
|
156
|
-
role
|
|
164
|
+
// `alertdialog` is a modal role: it promises an interruption the user
|
|
165
|
+
// has to deal with, a focus trap, and an inert page behind it. The
|
|
166
|
+
// inline path renders a plain always-present div with none of that, so
|
|
167
|
+
// the role would misdescribe it. `group` keeps the title and
|
|
168
|
+
// description associated with a container without claiming a dialog.
|
|
169
|
+
role={isInline ? 'group' : 'alertdialog'}
|
|
157
170
|
aria-labelledby={titleId}
|
|
158
171
|
aria-describedby={descriptionId}
|
|
159
172
|
{...mergeProps(themeProps('alert-dialog'), {className, style})}
|
|
@@ -177,6 +190,11 @@ export function AlertDialog({
|
|
|
177
190
|
variant="ghost"
|
|
178
191
|
label={cancelLabel}
|
|
179
192
|
onClick={handleCancel}
|
|
193
|
+
// Dialog focuses [data-autofocus] itself after showModal(),
|
|
194
|
+
// because React's autoFocus runs during commit while the
|
|
195
|
+
// dialog is still invisible. Cancel is the least destructive
|
|
196
|
+
// choice, so it is the one that should be preselected.
|
|
197
|
+
data-autofocus
|
|
180
198
|
/>
|
|
181
199
|
<Button
|
|
182
200
|
variant={actionVariant}
|
|
@@ -14,8 +14,19 @@ export const docs = {
|
|
|
14
14
|
bestPractices: [
|
|
15
15
|
{guidance: true, description: 'Choose the right height: use "fill" for dashboards with internal scrolling and "auto" for pages that grow with content.'},
|
|
16
16
|
{guidance: true, description: 'Set `contentPadding` based on content type: 4 for forms and settings, 0 for tables and dashboards.'},
|
|
17
|
+
{guidance: true, description: 'Give every nav slot an accessible name. AppShell renders TopNav and SideNav as separate navigation landmarks, and a screen reader lists them by name, so pass `label` to each one.'},
|
|
18
|
+
{guidance: true, description: 'Start the page heading inside `children`. AppShell owns the skip link, the banner landmark and the main landmark, but it renders no heading, so the first heading in the content area is the page h1.'},
|
|
17
19
|
{guidance: false, description: "Nest one AppShell inside another; it's the outermost layout frame."},
|
|
18
20
|
{guidance: false, description: 'Use for sub-page layouts; use Layout for content areas within AppShell.'},
|
|
21
|
+
{guidance: false, description: 'Add your own skip link or <main> element. AppShell already renders both, and a second main landmark makes the first ambiguous.'},
|
|
22
|
+
],
|
|
23
|
+
anatomy: [
|
|
24
|
+
{name: 'Skip link', required: true, description: 'First focusable element on the page. Visually hidden until focused, then moves focus to the main content area.'},
|
|
25
|
+
{name: 'Banner', required: false, description: 'The banner slot, for system-wide announcements. Renders above the top nav, inside the banner landmark.'},
|
|
26
|
+
{name: 'Top navigation', required: false, description: 'The topNav slot, typically TopNav. Below the mobile breakpoint it becomes a compact bar carrying the nav toggle.'},
|
|
27
|
+
{name: 'Side navigation', required: false, description: 'The sideNav slot, typically SideNav. Inline above the breakpoint, moved into the mobile drawer below it.'},
|
|
28
|
+
{name: 'Main content', required: true, description: 'children, rendered in the main landmark. Scrolls internally when height is fill, and with the page when it is auto.'},
|
|
29
|
+
{name: 'Mobile nav drawer', required: false, description: 'Generated below the breakpoint from the nav slots unless mobileNav disables or replaces it. A modal dialog: it traps focus and returns focus to the toggle on close.'},
|
|
19
30
|
],
|
|
20
31
|
},
|
|
21
32
|
props: [
|
|
@@ -192,8 +203,11 @@ export const docsDense = {
|
|
|
192
203
|
bestPractices: [
|
|
193
204
|
{guidance: true, description: 'Choose the right height: use "fill" for dashboards with internal scrolling and "auto" for pages that grow with content.'},
|
|
194
205
|
{guidance: true, description: 'Set `contentPadding` based on content type: 4 for forms and settings, 0 for tables and dashboards.'},
|
|
206
|
+
{guidance: true, description: 'Give every nav slot an accessible name: TopNav and SideNav are separate navigation landmarks, so pass `label` to each.'},
|
|
207
|
+
{guidance: true, description: 'Start the page heading inside `children`: AppShell owns the skip link, banner, and main landmarks but renders no heading, so the first content heading is the page h1.'},
|
|
195
208
|
{guidance: false, description: "Nest one AppShell inside another; it's the outermost layout frame."},
|
|
196
209
|
{guidance: false, description: 'Use for sub-page layouts; use Layout for content areas within AppShell.'},
|
|
210
|
+
{guidance: false, description: 'Add your own skip link or <main>; AppShell renders both, and a second main landmark makes the first ambiguous.'},
|
|
197
211
|
],
|
|
198
212
|
},
|
|
199
213
|
propDescriptions: {
|
|
@@ -19,11 +19,13 @@ import {
|
|
|
19
19
|
afterEach,
|
|
20
20
|
} from 'vitest';
|
|
21
21
|
import {render, screen, fireEvent} from '@testing-library/react';
|
|
22
|
+
import userEvent from '@testing-library/user-event';
|
|
22
23
|
import {AppShell} from './AppShell';
|
|
23
24
|
import {InternationalizationProvider} from '../i18n';
|
|
24
25
|
import {MobileNav} from '../MobileNav';
|
|
25
26
|
import {SideNav, SideNavItem, SideNavSection} from '../SideNav';
|
|
26
27
|
import {TopNav, TopNavHeading, TopNavItem} from '../TopNav';
|
|
28
|
+
import {useAppShellMobile} from './AppShellMobileContext';
|
|
27
29
|
|
|
28
30
|
// jsdom doesn't implement showModal/close on <dialog>, so we mock them
|
|
29
31
|
beforeAll(() => {
|
|
@@ -778,4 +780,141 @@ describe('AppShell', () => {
|
|
|
778
780
|
expect(ref).toHaveBeenCalled();
|
|
779
781
|
expect(ref.mock.calls[0][0]).toBe(screen.getByTestId('shell'));
|
|
780
782
|
});
|
|
783
|
+
// ===========================================================================
|
|
784
|
+
// Keyboard operation
|
|
785
|
+
//
|
|
786
|
+
// The focus TRAP and focus RESTORE contracts of the drawer are native
|
|
787
|
+
// <dialog> behaviour and cannot be asserted here: jsdom has no dialog
|
|
788
|
+
// implementation, so this file shims showModal()/close() with an `open`
|
|
789
|
+
// attribute. Those two were verified in Chromium instead. What is asserted
|
|
790
|
+
// here is everything the shim can still prove: the keyboard path to each
|
|
791
|
+
// control, and the ARIA wiring between the toggle and the drawer.
|
|
792
|
+
// ===========================================================================
|
|
793
|
+
|
|
794
|
+
it('reaches the skip link with Tab and moves focus to main with Enter', async () => {
|
|
795
|
+
const user = userEvent.setup();
|
|
796
|
+
render(
|
|
797
|
+
<AppShell topNav={<TopNav label="Main navigation" />}>
|
|
798
|
+
<div>Content</div>
|
|
799
|
+
</AppShell>,
|
|
800
|
+
);
|
|
801
|
+
|
|
802
|
+
await user.tab();
|
|
803
|
+
const skipLink = screen.getByTestId('skip-to-content');
|
|
804
|
+
expect(skipLink).toHaveFocus();
|
|
805
|
+
|
|
806
|
+
await user.keyboard('{Enter}');
|
|
807
|
+
expect(screen.getByRole('main')).toHaveFocus();
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
it('opens the mobile drawer from the keyboard and points the toggle at it', async () => {
|
|
811
|
+
mockMql = createMockMatchMedia(true);
|
|
812
|
+
vi.stubGlobal('matchMedia', vi.fn().mockReturnValue(mockMql));
|
|
813
|
+
const user = userEvent.setup();
|
|
814
|
+
|
|
815
|
+
render(
|
|
816
|
+
<AppShell sideNav={<TestSideNav />}>
|
|
817
|
+
<div>Content</div>
|
|
818
|
+
</AppShell>,
|
|
819
|
+
);
|
|
820
|
+
|
|
821
|
+
const toggle = screen.getByRole('button', {name: 'Open navigation'});
|
|
822
|
+
expect(toggle).toHaveAttribute('aria-expanded', 'false');
|
|
823
|
+
|
|
824
|
+
toggle.focus();
|
|
825
|
+
await user.keyboard('{Enter}');
|
|
826
|
+
|
|
827
|
+
expect(toggle).toHaveAttribute('aria-expanded', 'true');
|
|
828
|
+
const drawer = screen.getByRole('dialog', {hidden: true});
|
|
829
|
+
// aria-controls has to name the drawer that actually opened, or a screen
|
|
830
|
+
// reader cannot follow the toggle to it.
|
|
831
|
+
expect(toggle.getAttribute('aria-controls')).toBe(drawer.id);
|
|
832
|
+
expect(drawer.id).toBeTruthy();
|
|
833
|
+
});
|
|
834
|
+
|
|
835
|
+
// ===========================================================================
|
|
836
|
+
// Banner landmark on the mobile top bar
|
|
837
|
+
// ===========================================================================
|
|
838
|
+
|
|
839
|
+
it('exposes the mobile top bar as a banner landmark in a sidenav-only layout', () => {
|
|
840
|
+
mockMql = createMockMatchMedia(true);
|
|
841
|
+
vi.stubGlobal('matchMedia', vi.fn().mockReturnValue(mockMql));
|
|
842
|
+
|
|
843
|
+
render(
|
|
844
|
+
<AppShell sideNav={<TestSideNav />}>
|
|
845
|
+
<div>Content</div>
|
|
846
|
+
</AppShell>,
|
|
847
|
+
);
|
|
848
|
+
|
|
849
|
+
// Same landmark structure as the topNav layout: one banner region holding
|
|
850
|
+
// the top bar, whichever nav slots the page happens to fill.
|
|
851
|
+
expect(screen.getByRole('banner')).toBeInTheDocument();
|
|
852
|
+
});
|
|
853
|
+
|
|
854
|
+
it('renders exactly one banner landmark when a banner slot joins the mobile top bar', () => {
|
|
855
|
+
mockMql = createMockMatchMedia(true);
|
|
856
|
+
vi.stubGlobal('matchMedia', vi.fn().mockReturnValue(mockMql));
|
|
857
|
+
|
|
858
|
+
render(
|
|
859
|
+
<AppShell banner={<div>Announcement</div>} sideNav={<TestSideNav />}>
|
|
860
|
+
<div>Content</div>
|
|
861
|
+
</AppShell>,
|
|
862
|
+
);
|
|
863
|
+
|
|
864
|
+
expect(screen.getAllByRole('banner')).toHaveLength(1);
|
|
865
|
+
});
|
|
866
|
+
|
|
867
|
+
// ===========================================================================
|
|
868
|
+
// useAppShellMobile
|
|
869
|
+
// ===========================================================================
|
|
870
|
+
|
|
871
|
+
function MobileProbe() {
|
|
872
|
+
const {isMobile, isMobileNavOpen, isMobileNavEnabled, mobileNavId} =
|
|
873
|
+
useAppShellMobile();
|
|
874
|
+
return (
|
|
875
|
+
<span data-testid="probe">
|
|
876
|
+
{JSON.stringify({
|
|
877
|
+
isMobile,
|
|
878
|
+
isMobileNavOpen,
|
|
879
|
+
isMobileNavEnabled,
|
|
880
|
+
hasId: mobileNavId != null,
|
|
881
|
+
})}
|
|
882
|
+
</span>
|
|
883
|
+
);
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
it('useAppShellMobile is inert outside an AppShell', () => {
|
|
887
|
+
render(<MobileProbe />);
|
|
888
|
+
expect(JSON.parse(screen.getByTestId('probe').textContent)).toEqual({
|
|
889
|
+
isMobile: false,
|
|
890
|
+
isMobileNavOpen: false,
|
|
891
|
+
isMobileNavEnabled: false,
|
|
892
|
+
hasId: false,
|
|
893
|
+
});
|
|
894
|
+
});
|
|
895
|
+
|
|
896
|
+
it('useAppShellMobile reports the shell mobile state and drawer id', async () => {
|
|
897
|
+
mockMql = createMockMatchMedia(true);
|
|
898
|
+
vi.stubGlobal('matchMedia', vi.fn().mockReturnValue(mockMql));
|
|
899
|
+
const user = userEvent.setup();
|
|
900
|
+
|
|
901
|
+
render(
|
|
902
|
+
<AppShell sideNav={<TestSideNav />}>
|
|
903
|
+
<MobileProbe />
|
|
904
|
+
</AppShell>,
|
|
905
|
+
);
|
|
906
|
+
|
|
907
|
+
expect(JSON.parse(screen.getByTestId('probe').textContent)).toEqual({
|
|
908
|
+
isMobile: true,
|
|
909
|
+
isMobileNavOpen: false,
|
|
910
|
+
isMobileNavEnabled: true,
|
|
911
|
+
hasId: true,
|
|
912
|
+
});
|
|
913
|
+
|
|
914
|
+
await user.click(screen.getByRole('button', {name: 'Open navigation'}));
|
|
915
|
+
|
|
916
|
+
expect(
|
|
917
|
+
JSON.parse(screen.getByTestId('probe').textContent).isMobileNavOpen,
|
|
918
|
+
).toBe(true);
|
|
919
|
+
});
|
|
781
920
|
});
|
|
@@ -51,6 +51,7 @@ import type {AppShellMobileContextValue} from './AppShellMobileContext';
|
|
|
51
51
|
import type {SpacingStep} from '../utils/types';
|
|
52
52
|
import type {BaseProps} from '../BaseProps';
|
|
53
53
|
import {mergeProps, mergeRefs, isRenderable} from '../utils';
|
|
54
|
+
import {focusOutlineProps} from '../utils/focusOutline.stylex';
|
|
54
55
|
import {useMediaQuery} from '../hooks/useMediaQuery';
|
|
55
56
|
import {observeResize, unobserveResize} from '../utils/sharedResizeObserver';
|
|
56
57
|
import {themeProps} from '../utils/themeProps';
|
|
@@ -217,7 +218,7 @@ export interface AppShellProps extends BaseProps<HTMLDivElement> {
|
|
|
217
218
|
* <AppShell mobileNav={{ hasToggle: false }}>
|
|
218
219
|
* <MobileNavToggle />
|
|
219
220
|
* </AppShell>
|
|
220
|
-
* <AppShell mobileNav={<MobileNav
|
|
221
|
+
* <AppShell mobileNav={<MobileNav header="Menu">...</MobileNav>} />
|
|
221
222
|
* <AppShell mobileNav={false} />
|
|
222
223
|
* ```
|
|
223
224
|
*/
|
|
@@ -409,8 +410,8 @@ const styles = stylex.create({
|
|
|
409
410
|
flexShrink: 0,
|
|
410
411
|
overflow: 'clip',
|
|
411
412
|
position: 'sticky',
|
|
412
|
-
top: 'var(--
|
|
413
|
-
height: 'calc(100dvh - var(--
|
|
413
|
+
top: 'var(--_app-shell-header-height, 0px)',
|
|
414
|
+
height: 'calc(100dvh - var(--_app-shell-header-height, 0px))',
|
|
414
415
|
// Ensure children (LayoutPanel → SideNav) fill the sticky container
|
|
415
416
|
display: 'flex',
|
|
416
417
|
flexDirection: 'column',
|
|
@@ -441,7 +442,7 @@ const styles = stylex.create({
|
|
|
441
442
|
* topNav={<TopNav label="Navigation" heading={<TopNavHeading heading="My App" />} />}
|
|
442
443
|
* sideNav={<SideNav>{navSections}</SideNav>}
|
|
443
444
|
* mobileNav={
|
|
444
|
-
* <MobileNav isOpen={mobileOpen} onOpenChange={(open) => setMobileOpen(open)}
|
|
445
|
+
* <MobileNav isOpen={mobileOpen} onOpenChange={(open) => setMobileOpen(open)} header="My App">
|
|
445
446
|
* {navSections}
|
|
446
447
|
* </MobileNav>
|
|
447
448
|
* }>
|
|
@@ -512,14 +513,19 @@ export function AppShell({
|
|
|
512
513
|
const [uncontrolledMobileOpen, setUncontrolledMobileOpen] = useState(false);
|
|
513
514
|
const isMobileNavOpen = mobileNavConfig?.isOpen ?? uncontrolledMobileOpen;
|
|
514
515
|
|
|
516
|
+
const mobileNavOnOpenChange = mobileNavConfig?.onOpenChange;
|
|
515
517
|
const setMobileNavOpen = useCallback(
|
|
516
518
|
(open: boolean) => {
|
|
517
519
|
if (!mobileNavIsControlled) {
|
|
518
520
|
setUncontrolledMobileOpen(open);
|
|
519
521
|
}
|
|
520
|
-
|
|
522
|
+
mobileNavOnOpenChange?.(open);
|
|
521
523
|
},
|
|
522
|
-
|
|
524
|
+
// Depend on the callback itself, not on the config object: the documented
|
|
525
|
+
// usage is an inline `mobileNav={{isOpen, onOpenChange}}` literal, which is
|
|
526
|
+
// a new object every render and would otherwise churn this callback and the
|
|
527
|
+
// context value memo built from it on every render of the shell.
|
|
528
|
+
[mobileNavIsControlled, mobileNavOnOpenChange],
|
|
523
529
|
);
|
|
524
530
|
|
|
525
531
|
// Move focus to the main content container when the skip link is activated.
|
|
@@ -577,7 +583,7 @@ export function AppShell({
|
|
|
577
583
|
|
|
578
584
|
const updateHeight = () => {
|
|
579
585
|
const height = headerEl.getBoundingClientRect().height;
|
|
580
|
-
shellEl.style.setProperty('--
|
|
586
|
+
shellEl.style.setProperty('--_app-shell-header-height', `${height}px`);
|
|
581
587
|
};
|
|
582
588
|
|
|
583
589
|
observeResize(headerEl, () => updateHeight());
|
|
@@ -669,20 +675,19 @@ export function AppShell({
|
|
|
669
675
|
</LayoutHeader>
|
|
670
676
|
) : undefined;
|
|
671
677
|
|
|
672
|
-
const headerContent =
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
{
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
) : undefined;
|
|
678
|
+
const headerContent = isRenderable(headerInner) ? (
|
|
679
|
+
<div
|
|
680
|
+
ref={headerRef}
|
|
681
|
+
// Top-level banner landmark for the header region (topNav + banner).
|
|
682
|
+
// Safe here: the wrapper is never nested inside main/nav/other landmarks.
|
|
683
|
+
role="banner"
|
|
684
|
+
{...mergeProps(
|
|
685
|
+
themeProps('app-shell-header', {variant}),
|
|
686
|
+
stylex.props(navAreaStyle, isAuto && styles.headerSticky),
|
|
687
|
+
)}>
|
|
688
|
+
{headerInner}
|
|
689
|
+
</div>
|
|
690
|
+
) : undefined;
|
|
686
691
|
|
|
687
692
|
// =========================================================================
|
|
688
693
|
// Build sideNav content
|
|
@@ -760,6 +765,11 @@ export function AppShell({
|
|
|
760
765
|
const autoMobileTopBar =
|
|
761
766
|
shouldShowAutoToggle && !hasTopNav && hasSideNav ? (
|
|
762
767
|
<div
|
|
768
|
+
// Banner landmark for the mobile top bar, so the sidenav-only layout
|
|
769
|
+
// exposes the same landmark structure as the topNav one. Only when
|
|
770
|
+
// there is no headerContent: a banner slot with no topNav renders both,
|
|
771
|
+
// and two sibling banner regions is worse than the one this restores.
|
|
772
|
+
role={headerContent == null ? 'banner' : undefined}
|
|
763
773
|
{...mergeProps(
|
|
764
774
|
themeProps('app-shell-header', {variant}),
|
|
765
775
|
stylex.props(navAreaStyle, isAuto && styles.headerSticky),
|
|
@@ -805,7 +815,7 @@ export function AppShell({
|
|
|
805
815
|
<a
|
|
806
816
|
href={`#${MAIN_CONTENT_ID}`}
|
|
807
817
|
onClick={handleSkipLinkClick}
|
|
808
|
-
{...
|
|
818
|
+
{...focusOutlineProps.focusVisible(styles.skipLink)}
|
|
809
819
|
data-testid="skip-to-content">
|
|
810
820
|
{t('@astryx.appShell.skipToContent')}
|
|
811
821
|
</a>
|
|
@@ -9,13 +9,21 @@ export const docs = {
|
|
|
9
9
|
keywords: ["aspect-ratio","ratio","proportion","responsive","embed","container","widescreen","thumbnail","letterbox","crop"],
|
|
10
10
|
usage: {
|
|
11
11
|
description:
|
|
12
|
-
'Maintains a fixed width-to-height ratio for its children
|
|
12
|
+
'Maintains a fixed width-to-height ratio for its children as its container resizes. Use it for media containers like videos, images, thumbnails, or any content that needs consistent proportions. It takes its width from the container and derives its height from the ratio, so it needs an ancestor with a definite width.',
|
|
13
13
|
bestPractices: [
|
|
14
14
|
{guidance: true, description: 'Express the ratio as a fraction like `16/9` or `4/3` for readability.'},
|
|
15
15
|
{guidance: true, description: 'Use for media that needs consistent proportions across screen sizes.'},
|
|
16
16
|
{guidance: true, description: 'Use `fit="cover"` for images and video so the component sizes the child; the child should not repeat `width`/`height`/`objectFit` styles.'},
|
|
17
|
+
{guidance: true, description: 'Pass one child. With `fit` set, every direct child is stretched to fill the box, so put an overlay or caption inside a single wrapper child rather than passing it as a second child.'},
|
|
18
|
+
{guidance: true, description: 'Describe media children with `alt`, or `alt=""` when the image is decorative. AspectRatio adds no role and no accessible name of its own, so the child carries the whole accessible description.'},
|
|
17
19
|
{guidance: false, description: 'Use for general layout containers; use standard layout components instead.'},
|
|
18
20
|
{guidance: false, description: 'Nest AspectRatio containers; one level is sufficient.'},
|
|
21
|
+
{guidance: false, description: 'Constrain the height on its own. The width comes from the container, so a `height` or `maxHeight` by itself clamps the box off ratio; pair it with `width: "auto"` to size from the height instead.'},
|
|
22
|
+
{guidance: false, description: 'Place it in a shrink-to-fit parent such as `inline-flex`, `width: fit-content` or a floated box. It contributes no intrinsic width there and collapses to zero.'},
|
|
23
|
+
],
|
|
24
|
+
anatomy: [
|
|
25
|
+
{name: 'Ratio box', required: true, description: 'The outer element that holds the aspect ratio and clips overflow. Carries the `astryx-aspect-ratio` theme target, and the elliptical clip when `shape` is `ellipse`.'},
|
|
26
|
+
{name: 'Content slot', required: true, description: 'A wrapper that fills the ratio box and positions the child. With `fit` set it also sizes the child; without it the child styles itself.'},
|
|
19
27
|
],
|
|
20
28
|
},
|
|
21
29
|
props: [
|
|
@@ -68,13 +76,21 @@ export const docsZh = {
|
|
|
68
76
|
displayName: 'Aspect Ratio',
|
|
69
77
|
usage: {
|
|
70
78
|
description:
|
|
71
|
-
'Maintains a fixed width-to-height ratio for its children
|
|
79
|
+
'Maintains a fixed width-to-height ratio for its children as its container resizes. Use it for media containers like videos, images, thumbnails, or any content that needs consistent proportions. It takes its width from the container and derives its height from the ratio, so it needs an ancestor with a definite width.',
|
|
72
80
|
bestPractices: [
|
|
73
81
|
{guidance: true, description: 'Express the ratio as a fraction like `16/9` or `4/3` for readability.'},
|
|
74
82
|
{guidance: true, description: 'Use for media that needs consistent proportions across screen sizes.'},
|
|
75
83
|
{guidance: true, description: 'Use `fit="cover"` for images and video so the component sizes the child; the child should not repeat `width`/`height`/`objectFit` styles.'},
|
|
84
|
+
{guidance: true, description: 'Pass one child. With `fit` set, every direct child is stretched to fill the box, so put an overlay or caption inside a single wrapper child rather than passing it as a second child.'},
|
|
85
|
+
{guidance: true, description: 'Describe media children with `alt`, or `alt=""` when the image is decorative. AspectRatio adds no role and no accessible name of its own, so the child carries the whole accessible description.'},
|
|
76
86
|
{guidance: false, description: 'Use for general layout containers; use standard layout components instead.'},
|
|
77
87
|
{guidance: false, description: 'Nest AspectRatio containers; one level is sufficient.'},
|
|
88
|
+
{guidance: false, description: 'Constrain the height on its own. The width comes from the container, so a `height` or `maxHeight` by itself clamps the box off ratio; pair it with `width: "auto"` to size from the height instead.'},
|
|
89
|
+
{guidance: false, description: 'Place it in a shrink-to-fit parent such as `inline-flex`, `width: fit-content` or a floated box. It contributes no intrinsic width there and collapses to zero.'},
|
|
90
|
+
],
|
|
91
|
+
anatomy: [
|
|
92
|
+
{name: 'Ratio box', required: true, description: 'The outer element that holds the aspect ratio and clips overflow. Carries the `astryx-aspect-ratio` theme target, and the elliptical clip when `shape` is `ellipse`.'},
|
|
93
|
+
{name: 'Content slot', required: true, description: 'A wrapper that fills the ratio box and positions the child. With `fit` set it also sizes the child; without it the child styles itself.'},
|
|
78
94
|
],
|
|
79
95
|
},
|
|
80
96
|
props: [
|
|
@@ -110,13 +126,21 @@ export const docsDense = {
|
|
|
110
126
|
description: 'maintains specific aspect ratio for children',
|
|
111
127
|
usage: {
|
|
112
128
|
description:
|
|
113
|
-
'Maintains a fixed width-to-height ratio for its children
|
|
129
|
+
'Maintains a fixed width-to-height ratio for its children as its container resizes. Use it for media containers like videos, images, thumbnails, or any content that needs consistent proportions. It takes its width from the container and derives its height from the ratio, so it needs an ancestor with a definite width.',
|
|
114
130
|
bestPractices: [
|
|
115
131
|
{guidance: true, description: 'Express the ratio as a fraction like `16/9` or `4/3` for readability.'},
|
|
116
132
|
{guidance: true, description: 'Use for media that needs consistent proportions across screen sizes.'},
|
|
117
133
|
{guidance: true, description: 'Use `fit="cover"` for images and video so the component sizes the child; the child should not repeat `width`/`height`/`objectFit` styles.'},
|
|
134
|
+
{guidance: true, description: 'Pass one child. With `fit` set, every direct child is stretched to fill the box, so put an overlay or caption inside a single wrapper child rather than passing it as a second child.'},
|
|
135
|
+
{guidance: true, description: 'Describe media children with `alt`, or `alt=""` when the image is decorative. AspectRatio adds no role and no accessible name of its own, so the child carries the whole accessible description.'},
|
|
118
136
|
{guidance: false, description: 'Use for general layout containers; use standard layout components instead.'},
|
|
119
137
|
{guidance: false, description: 'Nest AspectRatio containers; one level is sufficient.'},
|
|
138
|
+
{guidance: false, description: 'Constrain the height on its own. The width comes from the container, so a `height` or `maxHeight` by itself clamps the box off ratio; pair it with `width: "auto"` to size from the height instead.'},
|
|
139
|
+
{guidance: false, description: 'Place it in a shrink-to-fit parent such as `inline-flex`, `width: fit-content` or a floated box. It contributes no intrinsic width there and collapses to zero.'},
|
|
140
|
+
],
|
|
141
|
+
anatomy: [
|
|
142
|
+
{name: 'Ratio box', required: true, description: 'The outer element that holds the aspect ratio and clips overflow. Carries the `astryx-aspect-ratio` theme target, and the elliptical clip when `shape` is `ellipse`.'},
|
|
143
|
+
{name: 'Content slot', required: true, description: 'A wrapper that fills the ratio box and positions the child. With `fit` set it also sizes the child; without it the child styles itself.'},
|
|
120
144
|
],
|
|
121
145
|
},
|
|
122
146
|
propDescriptions: {
|