@astryxdesign/core 0.1.9-canary.d2d84f8 → 0.1.9-canary.e00a241
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/dist/Avatar/Avatar.d.ts +6 -1
- package/dist/Avatar/Avatar.d.ts.map +1 -1
- package/dist/Avatar/Avatar.js +46 -12
- package/dist/Avatar/AvatarStatusDot.d.ts +6 -4
- package/dist/Avatar/AvatarStatusDot.d.ts.map +1 -1
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
- package/dist/Breadcrumbs/BreadcrumbItem.js +11 -4
- package/dist/Breadcrumbs/index.d.ts +1 -0
- package/dist/Breadcrumbs/index.d.ts.map +1 -1
- package/dist/Breadcrumbs/index.js +2 -1
- package/dist/Calendar/Calendar.d.ts.map +1 -1
- package/dist/Calendar/Calendar.js +50 -9
- package/dist/Calendar/dayCellUtils.d.ts +4 -4
- package/dist/Calendar/dayCellUtils.js +2 -2
- package/dist/Calendar/styles.d.ts +30 -31
- package/dist/Calendar/styles.d.ts.map +1 -1
- package/dist/Calendar/styles.js +30 -31
- package/dist/Card/Card.js +1 -1
- package/dist/Carousel/Carousel.d.ts.map +1 -1
- package/dist/Carousel/Carousel.js +22 -12
- package/dist/Chat/ChatDictationButton.d.ts.map +1 -1
- package/dist/Chat/ChatDictationButton.js +3 -1
- package/dist/Chat/ChatMessageBubble.d.ts.map +1 -1
- package/dist/Chat/ChatMessageBubble.js +8 -8
- package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
- package/dist/Chat/ChatToolCalls.js +10 -3
- package/dist/CheckboxInput/CheckboxInput.js +4 -4
- package/dist/Citation/Citation.d.ts +22 -1
- package/dist/Citation/Citation.d.ts.map +1 -1
- package/dist/Citation/Citation.js +20 -6
- package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
- package/dist/CodeBlock/CodeBlock.js +13 -7
- package/dist/Collapsible/Collapsible.d.ts.map +1 -1
- package/dist/Collapsible/Collapsible.js +3 -1
- package/dist/CommandPalette/CommandPalette.d.ts +1 -1
- package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPalette.js +42 -3
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +14 -5
- package/dist/ContextMenu/index.d.ts +1 -0
- package/dist/ContextMenu/index.d.ts.map +1 -1
- package/dist/ContextMenu/index.js +3 -1
- package/dist/DateInput/DateInput.d.ts +1 -0
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +31 -19
- package/dist/DateRangeInput/DateRangeInput.d.ts +1 -0
- package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
- package/dist/DateRangeInput/DateRangeInput.js +20 -19
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +23 -18
- package/dist/Divider/Divider.d.ts +2 -2
- package/dist/Divider/Divider.d.ts.map +1 -1
- package/dist/Divider/Divider.js +13 -0
- package/dist/DropdownMenu/DropdownMenu.d.ts +6 -0
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +22 -5
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +4 -1
- package/dist/DropdownMenu/DropdownMenuItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuItem.js +4 -1
- package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuRadioItem.js +4 -1
- package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts +104 -0
- package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -0
- package/dist/DropdownMenu/DropdownMenuSubMenu.js +427 -0
- package/dist/DropdownMenu/index.d.ts +10 -0
- package/dist/DropdownMenu/index.d.ts.map +1 -1
- package/dist/DropdownMenu/index.js +13 -0
- package/dist/DropdownMenu/menuItemHover.d.ts +23 -0
- package/dist/DropdownMenu/menuItemHover.d.ts.map +1 -0
- package/dist/DropdownMenu/menuItemHover.js +33 -0
- package/dist/DropdownMenu/menuItemRoles.d.ts +8 -0
- package/dist/DropdownMenu/menuItemRoles.d.ts.map +1 -1
- package/dist/DropdownMenu/menuItemRoles.js +10 -1
- package/dist/DropdownMenu/renderDropdownItems.d.ts.map +1 -1
- package/dist/DropdownMenu/renderDropdownItems.js +21 -6
- package/dist/Field/Field.d.ts +3 -1
- package/dist/Field/Field.d.ts.map +1 -1
- package/dist/Field/Field.js +4 -1
- package/dist/Field/inputStyles.stylex.d.ts +1 -0
- package/dist/Field/inputStyles.stylex.d.ts.map +1 -1
- package/dist/Field/inputStyles.stylex.js +1 -0
- package/dist/FieldStatus/FieldStatus.d.ts +4 -1
- package/dist/FieldStatus/FieldStatus.d.ts.map +1 -1
- package/dist/FieldStatus/FieldStatus.js +4 -2
- package/dist/FileInput/FileInput.d.ts +1 -0
- package/dist/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +91 -65
- package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
- package/dist/HoverCard/useHoverCard.js +10 -4
- package/dist/Icon/Icon.d.ts +15 -1
- package/dist/Icon/Icon.d.ts.map +1 -1
- package/dist/Icon/Icon.js +19 -3
- package/dist/Lightbox/Lightbox.d.ts.map +1 -1
- package/dist/Lightbox/Lightbox.js +15 -9
- package/dist/List/List.d.ts.map +1 -1
- package/dist/List/List.js +6 -3
- package/dist/Markdown/Markdown.d.ts.map +1 -1
- package/dist/Markdown/Markdown.js +7 -0
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +65 -40
- package/dist/NumberInput/NumberInput.d.ts +1 -0
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/NumberInput/NumberInput.js +20 -17
- package/dist/Outline/Outline.js +2 -2
- package/dist/RadioList/RadioList.d.ts +2 -2
- package/dist/RadioList/RadioListItem.js +4 -4
- package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
- package/dist/Resizable/ResizeHandle.js +49 -19
- package/dist/SegmentedControl/SegmentedControlItem.d.ts +1 -1
- package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControlItem.js +2 -1
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +58 -16
- package/dist/SideNav/SideNav.d.ts.map +1 -1
- package/dist/SideNav/SideNav.js +5 -5
- package/dist/SideNav/SideNavCollapseButton.d.ts.map +1 -1
- package/dist/SideNav/SideNavCollapseButton.js +13 -10
- package/dist/SideNav/SideNavItem.d.ts +1 -1
- package/dist/SideNav/SideNavItem.d.ts.map +1 -1
- package/dist/SideNav/SideNavItem.js +12 -10
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +87 -34
- package/dist/Spinner/Spinner.d.ts +3 -2
- package/dist/Spinner/Spinner.d.ts.map +1 -1
- package/dist/Spinner/Spinner.js +11 -2
- package/dist/Switch/Switch.d.ts +8 -1
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +160 -91
- package/dist/TabList/TabList.d.ts +2 -11
- package/dist/TabList/TabList.d.ts.map +1 -1
- package/dist/TabList/TabList.js +5 -9
- package/dist/TabList/TabListContext.d.ts +0 -5
- package/dist/TabList/TabListContext.d.ts.map +1 -1
- package/dist/TabList/TabListContext.js +0 -5
- package/dist/Table/BaseTable.d.ts.map +1 -1
- package/dist/Table/BaseTable.js +30 -4
- package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
- package/dist/Table/plugins/groupedRows/useTableGroupedRows.js +15 -11
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +36 -26
- package/dist/Table/plugins/selection/useTableSelection.d.ts.map +1 -1
- package/dist/Table/plugins/selection/useTableSelection.js +3 -0
- package/dist/Table/plugins/stickyColumns/useTableStickyColumns.d.ts.map +1 -1
- package/dist/Table/plugins/stickyColumns/useTableStickyColumns.js +14 -6
- package/dist/Table/plugins/tree/useTableTreeData.d.ts.map +1 -1
- package/dist/Table/plugins/tree/useTableTreeData.js +29 -23
- package/dist/Table/types.d.ts +32 -0
- package/dist/Table/types.d.ts.map +1 -1
- package/dist/TextArea/TextArea.d.ts +1 -0
- package/dist/TextArea/TextArea.d.ts.map +1 -1
- package/dist/TextArea/TextArea.js +16 -19
- package/dist/TextInput/TextInput.d.ts +1 -0
- package/dist/TextInput/TextInput.d.ts.map +1 -1
- package/dist/TextInput/TextInput.js +18 -17
- package/dist/TimeInput/TimeInput.d.ts +1 -0
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +15 -17
- package/dist/Timestamp/Timestamp.d.ts +31 -3
- package/dist/Timestamp/Timestamp.d.ts.map +1 -1
- package/dist/Timestamp/Timestamp.js +47 -76
- package/dist/Timestamp/formatInstant.d.ts +41 -0
- package/dist/Timestamp/formatInstant.d.ts.map +1 -0
- package/dist/Timestamp/formatInstant.js +177 -0
- package/dist/Timestamp/index.d.ts +1 -0
- package/dist/Timestamp/index.d.ts.map +1 -1
- package/dist/Timestamp/tooltipEntries.d.ts +56 -0
- package/dist/Timestamp/tooltipEntries.d.ts.map +1 -0
- package/dist/Timestamp/tooltipEntries.js +163 -0
- package/dist/Token/Token.d.ts +30 -3
- package/dist/Token/Token.d.ts.map +1 -1
- package/dist/Token/Token.js +17 -2
- package/dist/Token/index.d.ts +1 -1
- package/dist/Token/index.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.js +2 -2
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +17 -1
- package/dist/TopNav/TopNavItem.d.ts +3 -1
- package/dist/TopNav/TopNavItem.d.ts.map +1 -1
- package/dist/TopNav/TopNavItem.js +30 -3
- package/dist/TreeList/TreeList.d.ts.map +1 -1
- package/dist/TreeList/TreeList.js +1 -0
- package/dist/TreeList/TreeListBranches.d.ts.map +1 -1
- package/dist/TreeList/TreeListBranches.js +8 -6
- package/dist/TreeList/TreeListItem.d.ts.map +1 -1
- package/dist/TreeList/TreeListItem.js +32 -14
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +19 -10
- package/dist/Typeahead/Typeahead.js +1 -1
- package/dist/Typeahead/TypeaheadItem.d.ts +1 -1
- package/dist/Typeahead/TypeaheadItem.d.ts.map +1 -1
- package/dist/Typeahead/TypeaheadItem.js +20 -10
- package/dist/astryx.css +38 -14
- package/dist/astryx.umd.js +52 -52
- package/dist/astryx.umd.js.map +4 -4
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/isRtlElement.d.ts +18 -0
- package/dist/hooks/isRtlElement.d.ts.map +1 -0
- package/dist/hooks/isRtlElement.js +27 -0
- package/dist/hooks/useAnnounce.d.ts +3 -0
- package/dist/hooks/useAnnounce.d.ts.map +1 -1
- package/dist/hooks/useAnnounce.js +39 -1
- package/dist/hooks/useFocusTrap.d.ts.map +1 -1
- package/dist/hooks/useFocusTrap.js +52 -11
- package/dist/hooks/useGridFocus.d.ts +6 -1
- package/dist/hooks/useGridFocus.d.ts.map +1 -1
- package/dist/hooks/useGridFocus.js +12 -9
- package/dist/hooks/useHotkeys.d.ts +9 -0
- package/dist/hooks/useHotkeys.d.ts.map +1 -1
- package/dist/hooks/useHotkeys.js +9 -0
- package/dist/hooks/useInputStatusIcon.d.ts +38 -0
- package/dist/hooks/useInputStatusIcon.d.ts.map +1 -0
- package/dist/hooks/useInputStatusIcon.js +158 -0
- package/dist/hooks/useListFocus.d.ts +39 -1
- package/dist/hooks/useListFocus.d.ts.map +1 -1
- package/dist/hooks/useListFocus.js +54 -9
- package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
- package/dist/theme/derivedVarRegistry.js +13 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -1
- package/dist/utils/plainDate.d.ts +17 -0
- package/dist/utils/plainDate.d.ts.map +1 -1
- package/dist/utils/plainDate.js +23 -4
- package/dist/utils/rtlStyles.d.ts +24 -0
- package/dist/utils/rtlStyles.d.ts.map +1 -0
- package/dist/utils/rtlStyles.js +27 -0
- package/locales/en.json +76 -0
- package/locales/pseudo.json +57 -0
- package/package.json +6 -1
- package/src/Avatar/Avatar.doc.mjs +18 -4
- package/src/Avatar/Avatar.test.tsx +106 -0
- package/src/Avatar/Avatar.tsx +62 -10
- package/src/Avatar/AvatarStatusDot.doc.mjs +4 -4
- package/src/Avatar/AvatarStatusDot.tsx +6 -4
- package/src/AvatarGroup/AvatarGroup.test.tsx +16 -1
- package/src/Breadcrumbs/BreadcrumbItem.tsx +10 -10
- package/src/Breadcrumbs/Breadcrumbs.test.tsx +51 -0
- package/src/Breadcrumbs/index.ts +4 -0
- package/src/Button/Button.doc.mjs +0 -4
- package/src/Calendar/Calendar.test.tsx +170 -20
- package/src/Calendar/Calendar.tsx +73 -16
- package/src/Calendar/dayCellUtils.test.ts +18 -18
- package/src/Calendar/dayCellUtils.ts +2 -2
- package/src/Calendar/styles.ts +33 -39
- package/src/Card/Card.tsx +1 -1
- package/src/Carousel/Carousel.tsx +25 -6
- package/src/Chat/ChatComposer.doc.mjs +2 -2
- package/src/Chat/ChatComposerInput.doc.mjs +1 -1
- package/src/Chat/ChatDictationButton.tsx +7 -1
- package/src/Chat/ChatMessageBubble.tsx +15 -10
- package/src/Chat/ChatSystemMessage.test.tsx +5 -0
- package/src/Chat/ChatToolCalls.tsx +9 -2
- package/src/CheckboxInput/CheckboxInput.doc.mjs +6 -0
- package/src/CheckboxInput/CheckboxInput.tsx +4 -4
- package/src/CheckboxList/CheckboxList.doc.mjs +6 -0
- package/src/Citation/Citation.doc.mjs +4 -3
- package/src/Citation/Citation.test.tsx +97 -0
- package/src/Citation/Citation.tsx +43 -11
- package/src/CodeBlock/CodeBlock.tsx +35 -8
- package/src/Collapsible/Collapsible.doc.mjs +1 -0
- package/src/Collapsible/Collapsible.test.tsx +7 -0
- package/src/Collapsible/Collapsible.tsx +16 -6
- package/src/CommandPalette/CommandPalette.test.tsx +95 -1
- package/src/CommandPalette/CommandPalette.tsx +41 -2
- package/src/ContextMenu/ContextMenu.test.tsx +42 -0
- package/src/ContextMenu/ContextMenu.tsx +13 -11
- package/src/ContextMenu/index.ts +5 -0
- package/src/DateInput/DateInput.doc.mjs +15 -5
- package/src/DateInput/DateInput.test.tsx +210 -3
- package/src/DateInput/DateInput.tsx +41 -29
- package/src/DateRangeInput/DateRangeInput.doc.mjs +11 -3
- package/src/DateRangeInput/DateRangeInput.test.tsx +120 -3
- package/src/DateRangeInput/DateRangeInput.tsx +29 -27
- package/src/DateTimeInput/DateTimeInput.doc.mjs +22 -1
- package/src/DateTimeInput/DateTimeInput.test.tsx +112 -0
- package/src/DateTimeInput/DateTimeInput.tsx +44 -39
- package/src/Divider/Divider.test.tsx +32 -8
- package/src/Divider/Divider.tsx +14 -1
- package/src/DropdownMenu/DropdownMenu.test.tsx +60 -0
- package/src/DropdownMenu/DropdownMenu.tsx +31 -12
- package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +8 -2
- package/src/DropdownMenu/DropdownMenuItem.tsx +8 -2
- package/src/DropdownMenu/DropdownMenuRadioItem.tsx +8 -2
- package/src/DropdownMenu/DropdownMenuSubMenu.doc.mjs +88 -0
- package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +635 -0
- package/src/DropdownMenu/DropdownMenuSubMenu.tsx +553 -0
- package/src/DropdownMenu/index.ts +16 -0
- package/src/DropdownMenu/menuItemHover.ts +39 -0
- package/src/DropdownMenu/menuItemRoles.ts +9 -0
- package/src/DropdownMenu/renderDropdownItems.tsx +29 -15
- package/src/Field/Field.tsx +14 -9
- package/src/Field/inputStyles.stylex.ts +2 -0
- package/src/FieldStatus/FieldStatus.tsx +10 -4
- package/src/FileInput/FileInput.doc.mjs +13 -7
- package/src/FileInput/FileInput.test.tsx +84 -16
- package/src/FileInput/FileInput.tsx +96 -79
- package/src/HoverCard/useHoverCard.tsx +15 -3
- package/src/Icon/Icon.doc.mjs +13 -0
- package/src/Icon/Icon.test.tsx +89 -0
- package/src/Icon/Icon.tsx +51 -3
- package/src/Lightbox/Lightbox.tsx +11 -3
- package/src/List/List.test.tsx +17 -29
- package/src/List/List.tsx +6 -1
- package/src/Markdown/Markdown.test.tsx +21 -0
- package/src/Markdown/Markdown.tsx +4 -0
- package/src/MultiSelector/MultiSelector.doc.mjs +11 -0
- package/src/MultiSelector/MultiSelector.test.tsx +306 -3
- package/src/MultiSelector/MultiSelector.tsx +88 -57
- package/src/NumberInput/NumberInput.doc.mjs +11 -5
- package/src/NumberInput/NumberInput.test.tsx +48 -0
- package/src/NumberInput/NumberInput.tsx +25 -26
- package/src/Outline/Outline.test.tsx +12 -12
- package/src/Outline/Outline.tsx +2 -2
- package/src/RadioList/RadioList.doc.mjs +6 -0
- package/src/RadioList/RadioList.tsx +2 -2
- package/src/RadioList/RadioListItem.tsx +4 -4
- package/src/Resizable/ResizeHandle.test.tsx +94 -0
- package/src/Resizable/ResizeHandle.tsx +70 -20
- package/src/SegmentedControl/SegmentedControlItem.tsx +2 -0
- package/src/Selector/Selector.doc.mjs +8 -0
- package/src/Selector/Selector.test.tsx +328 -3
- package/src/Selector/Selector.tsx +70 -19
- package/src/SideNav/SideNav.tsx +1 -8
- package/src/SideNav/SideNavCollapseButton.tsx +9 -7
- package/src/SideNav/SideNavItem.tsx +5 -3
- package/src/Slider/Slider.doc.mjs +6 -0
- package/src/Slider/Slider.test.tsx +240 -12
- package/src/Slider/Slider.tsx +96 -23
- package/src/Spinner/Spinner.test.tsx +10 -8
- package/src/Spinner/Spinner.tsx +15 -5
- package/src/Switch/Switch.doc.mjs +142 -42
- package/src/Switch/Switch.test.tsx +22 -0
- package/src/Switch/Switch.tsx +90 -33
- package/src/TabList/TabList.doc.mjs +0 -6
- package/src/TabList/TabList.test.tsx +3 -15
- package/src/TabList/TabList.tsx +6 -17
- package/src/TabList/TabListContext.ts +0 -6
- package/src/Table/BaseTable.tsx +34 -2
- package/src/Table/Table.doc.mjs +11 -0
- package/src/Table/Table.test.tsx +85 -0
- package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +9 -6
- package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +24 -17
- package/src/Table/plugins/selection/useTableSelection.test.tsx +61 -3
- package/src/Table/plugins/selection/useTableSelection.tsx +3 -0
- package/src/Table/plugins/stickyColumns/useTableStickyColumns.tsx +37 -8
- package/src/Table/plugins/tree/useTableTreeData.tsx +17 -13
- package/src/Table/types.ts +33 -0
- package/src/Table/useTableRowStatus.doc.mjs +1 -1
- package/src/TextArea/TextArea.doc.mjs +11 -5
- package/src/TextArea/TextArea.tsx +17 -26
- package/src/TextInput/TextInput.doc.mjs +11 -5
- package/src/TextInput/TextInput.test.tsx +130 -3
- package/src/TextInput/TextInput.tsx +18 -25
- package/src/Thumbnail/Thumbnail.doc.mjs +1 -1
- package/src/TimeInput/TimeInput.doc.mjs +11 -5
- package/src/TimeInput/TimeInput.tsx +14 -25
- package/src/Timestamp/Timestamp.doc.mjs +32 -6
- package/src/Timestamp/Timestamp.test.tsx +371 -0
- package/src/Timestamp/Timestamp.tsx +96 -93
- package/src/Timestamp/formatInstant.ts +218 -0
- package/src/Timestamp/index.ts +4 -0
- package/src/Timestamp/tooltipEntries.test.ts +552 -0
- package/src/Timestamp/tooltipEntries.ts +210 -0
- package/src/Token/Token.test.tsx +21 -15
- package/src/Token/Token.tsx +31 -14
- package/src/Token/index.ts +1 -1
- package/src/Tokenizer/Tokenizer.doc.mjs +8 -1
- package/src/Tokenizer/Tokenizer.tsx +4 -2
- package/src/Tooltip/Tooltip.test.tsx +46 -0
- package/src/Tooltip/useTooltip.tsx +20 -0
- package/src/TopNav/TopNav.test.tsx +93 -2
- package/src/TopNav/TopNavItem.doc.mjs +3 -3
- package/src/TopNav/TopNavItem.tsx +35 -6
- package/src/TopNav/TopNavMegaMenuFeaturedCard.doc.mjs +1 -1
- package/src/TreeList/TreeList.doc.mjs +6 -0
- package/src/TreeList/TreeList.test.tsx +50 -10
- package/src/TreeList/TreeList.tsx +5 -0
- package/src/TreeList/TreeListBranches.tsx +8 -6
- package/src/TreeList/TreeListItem.tsx +33 -11
- package/src/Typeahead/BaseTypeahead.tsx +29 -14
- package/src/Typeahead/Typeahead.doc.mjs +6 -0
- package/src/Typeahead/Typeahead.test.tsx +27 -0
- package/src/Typeahead/Typeahead.tsx +1 -1
- package/src/Typeahead/TypeaheadItem.tsx +2 -1
- package/src/__tests__/inputWidthContract.test.tsx +62 -0
- package/src/doc-mjs.d.ts +19 -0
- package/src/hooks/index.ts +6 -0
- package/src/hooks/isRtlElement.ts +27 -0
- package/src/hooks/useAnnounce.test.tsx +75 -2
- package/src/hooks/useAnnounce.ts +47 -1
- package/src/hooks/useFocusTrap.test.tsx +104 -0
- package/src/hooks/useFocusTrap.ts +61 -12
- package/src/hooks/useGridFocus.doc.mjs +3 -3
- package/src/hooks/useGridFocus.test.tsx +41 -0
- package/src/hooks/useGridFocus.ts +21 -10
- package/src/hooks/useHotkeys.ts +9 -0
- package/src/hooks/useInputStatusIcon.test.tsx +162 -0
- package/src/hooks/useInputStatusIcon.tsx +224 -0
- package/src/hooks/useListFocus.doc.mjs +25 -3
- package/src/hooks/useListFocus.test.tsx +153 -1
- package/src/hooks/useListFocus.ts +102 -9
- package/src/reset.css +0 -26
- package/src/reset.test.ts +60 -0
- package/src/theme/derivedVarRegistry.test.ts +1 -0
- package/src/theme/derivedVarRegistry.ts +6 -0
- package/src/theme/generateThemeRules.test.ts +42 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/plainDate.test.ts +24 -6
- package/src/utils/plainDate.ts +18 -4
- package/src/utils/rtlStyles.ts +27 -0
|
@@ -4,6 +4,7 @@ import {describe, it, expect, vi, beforeEach, afterEach} from 'vitest';
|
|
|
4
4
|
import {render, screen, act, waitFor} from '@testing-library/react';
|
|
5
5
|
import userEvent from '@testing-library/user-event';
|
|
6
6
|
import {Timestamp} from './Timestamp';
|
|
7
|
+
import {formatTooltipLines} from './tooltipEntries';
|
|
7
8
|
|
|
8
9
|
describe('Timestamp', () => {
|
|
9
10
|
beforeEach(() => {
|
|
@@ -487,4 +488,374 @@ describe('Timestamp', () => {
|
|
|
487
488
|
expect(label).not.toContain('ago');
|
|
488
489
|
});
|
|
489
490
|
});
|
|
491
|
+
|
|
492
|
+
// --- Multi-zone / multi-format tooltip entries ---
|
|
493
|
+
|
|
494
|
+
describe('tooltipEntries', () => {
|
|
495
|
+
// A fixed instant that lands on a different calendar day in Tokyo, so a
|
|
496
|
+
// zone-blind implementation is visible in the output.
|
|
497
|
+
const VALUE = '2026-02-19T17:00:00Z';
|
|
498
|
+
|
|
499
|
+
const originalShowPopover = HTMLElement.prototype.showPopover;
|
|
500
|
+
const originalHidePopover = HTMLElement.prototype.hidePopover;
|
|
501
|
+
|
|
502
|
+
beforeEach(() => {
|
|
503
|
+
// The tooltip is lazy-loaded; real timers let the dynamic import and
|
|
504
|
+
// RTL's findBy* resolve (the outer beforeEach installs fake timers).
|
|
505
|
+
vi.useRealTimers();
|
|
506
|
+
HTMLElement.prototype.showPopover = vi.fn();
|
|
507
|
+
HTMLElement.prototype.hidePopover = vi.fn();
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
afterEach(() => {
|
|
511
|
+
HTMLElement.prototype.showPopover = originalShowPopover;
|
|
512
|
+
HTMLElement.prototype.hidePopover = originalHidePopover;
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
it('leaves the default tooltip as a single unwrapped line', async () => {
|
|
516
|
+
render(
|
|
517
|
+
<Timestamp
|
|
518
|
+
value={Date.now() / 1000 - 3600}
|
|
519
|
+
format="relative"
|
|
520
|
+
data-testid="ts"
|
|
521
|
+
/>,
|
|
522
|
+
);
|
|
523
|
+
const layer = await screen.findByRole('tooltip', {hidden: true});
|
|
524
|
+
// No entries configured — the tooltip content stays the bare string it
|
|
525
|
+
// has always been, with no list wrapper introduced around it.
|
|
526
|
+
expect(layer.querySelector('dl')).toBeNull();
|
|
527
|
+
|
|
528
|
+
const normalize = (s: string) => s.replace(/\s+/g, ' ');
|
|
529
|
+
expect(normalize(layer.textContent ?? '')).toContain(
|
|
530
|
+
normalize(screen.getByTestId('ts').getAttribute('aria-label') ?? '\0'),
|
|
531
|
+
);
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
it('renders one tooltip line per configured entry', async () => {
|
|
535
|
+
render(
|
|
536
|
+
<Timestamp
|
|
537
|
+
value={VALUE}
|
|
538
|
+
format="relative"
|
|
539
|
+
tooltipEntries={[
|
|
540
|
+
{label: 'Local'},
|
|
541
|
+
{timezoneID: 'UTC', label: 'UTC'},
|
|
542
|
+
{timezoneID: 'Asia/Tokyo', label: 'Tokyo'},
|
|
543
|
+
]}
|
|
544
|
+
/>,
|
|
545
|
+
);
|
|
546
|
+
const layer = await screen.findByRole('tooltip', {hidden: true});
|
|
547
|
+
expect(layer.querySelectorAll('dd')).toHaveLength(3);
|
|
548
|
+
expect(layer.textContent).toContain('Local');
|
|
549
|
+
expect(layer.textContent).toContain('UTC');
|
|
550
|
+
expect(layer.textContent).toContain('Tokyo');
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
it('renders each entry in the time zone it names', async () => {
|
|
554
|
+
render(
|
|
555
|
+
<Timestamp
|
|
556
|
+
value={VALUE}
|
|
557
|
+
format="relative"
|
|
558
|
+
tooltipEntries={[
|
|
559
|
+
{timezoneID: 'UTC', format: 'system_date_time', label: 'UTC'},
|
|
560
|
+
{
|
|
561
|
+
timezoneID: 'Asia/Tokyo',
|
|
562
|
+
format: 'system_date_time',
|
|
563
|
+
label: 'Tokyo',
|
|
564
|
+
},
|
|
565
|
+
]}
|
|
566
|
+
/>,
|
|
567
|
+
);
|
|
568
|
+
const layer = await screen.findByRole('tooltip', {hidden: true});
|
|
569
|
+
const values = Array.from(layer.querySelectorAll('dd')).map(
|
|
570
|
+
el => el.textContent,
|
|
571
|
+
);
|
|
572
|
+
// Machine formats are locale- and host-timezone-independent, so these
|
|
573
|
+
// hold on any developer machine and on CI.
|
|
574
|
+
expect(values).toEqual(['2026-02-19 17:00:00', '2026-02-20 02:00:00']);
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
it('shows a tooltip for absolute formats once entries are configured', async () => {
|
|
578
|
+
render(
|
|
579
|
+
<Timestamp
|
|
580
|
+
value={VALUE}
|
|
581
|
+
format="date_time"
|
|
582
|
+
tooltipEntries={[{timezoneID: 'UTC', label: 'UTC'}]}
|
|
583
|
+
data-testid="ts"
|
|
584
|
+
/>,
|
|
585
|
+
);
|
|
586
|
+
// Without entries an absolute format has no tooltip at all; configuring
|
|
587
|
+
// entries must not be silently ignored.
|
|
588
|
+
const layer = await screen.findByRole('tooltip', {hidden: true});
|
|
589
|
+
expect(layer.textContent).toContain('UTC');
|
|
590
|
+
// ...and the anchor becomes keyboard-reachable, as it is for relative.
|
|
591
|
+
expect(screen.getByTestId('ts')).toHaveAttribute('tabindex', '0');
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
it('treats an empty entry list exactly like no configuration', () => {
|
|
595
|
+
render(
|
|
596
|
+
<Timestamp
|
|
597
|
+
value={VALUE}
|
|
598
|
+
format="date_time"
|
|
599
|
+
tooltipEntries={[]}
|
|
600
|
+
data-testid="ts"
|
|
601
|
+
/>,
|
|
602
|
+
);
|
|
603
|
+
// `hasTooltip` stays the only on/off switch — an empty array is not a
|
|
604
|
+
// second way to spell "off", and it must not widen the gate either.
|
|
605
|
+
expect(screen.getByTestId('ts')).not.toHaveAttribute('tabindex');
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
it('still honors hasTooltip={false} when entries are configured', () => {
|
|
609
|
+
render(
|
|
610
|
+
<Timestamp
|
|
611
|
+
value={VALUE}
|
|
612
|
+
format="date_time"
|
|
613
|
+
hasTooltip={false}
|
|
614
|
+
tooltipEntries={[{timezoneID: 'UTC'}]}
|
|
615
|
+
data-testid="ts"
|
|
616
|
+
/>,
|
|
617
|
+
);
|
|
618
|
+
expect(screen.getByTestId('ts')).not.toHaveAttribute('tabindex');
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
it('leaves the accessible name unchanged when entries are configured', async () => {
|
|
622
|
+
const value = Date.now() / 1000 - 3600;
|
|
623
|
+
const {unmount} = render(
|
|
624
|
+
<Timestamp value={value} format="relative" data-testid="a" />,
|
|
625
|
+
);
|
|
626
|
+
const before = screen.getByTestId('a').getAttribute('aria-label');
|
|
627
|
+
unmount();
|
|
628
|
+
|
|
629
|
+
render(
|
|
630
|
+
<Timestamp
|
|
631
|
+
value={value}
|
|
632
|
+
format="relative"
|
|
633
|
+
tooltipEntries={[{timezoneID: 'UTC', label: 'UTC'}]}
|
|
634
|
+
data-testid="b"
|
|
635
|
+
/>,
|
|
636
|
+
);
|
|
637
|
+
// The accessible name stays the canonical absolute time in the viewer's
|
|
638
|
+
// own zone; the extra zones reach assistive tech through the tooltip's
|
|
639
|
+
// aria-describedby, not by being stuffed into the name.
|
|
640
|
+
expect(screen.getByTestId('b').getAttribute('aria-label')).toBe(before);
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
it('does not add an accessible name to absolute formats with entries', () => {
|
|
644
|
+
render(
|
|
645
|
+
<Timestamp
|
|
646
|
+
value={VALUE}
|
|
647
|
+
format="date_time"
|
|
648
|
+
tooltipEntries={[{timezoneID: 'UTC'}]}
|
|
649
|
+
data-testid="ts"
|
|
650
|
+
/>,
|
|
651
|
+
);
|
|
652
|
+
expect(screen.getByTestId('ts').getAttribute('aria-label')).toBeNull();
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
it('renders nothing for an unparseable value even with entries', () => {
|
|
656
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
657
|
+
try {
|
|
658
|
+
const {container} = render(
|
|
659
|
+
<Timestamp
|
|
660
|
+
value="not-a-date"
|
|
661
|
+
tooltipEntries={[{timezoneID: 'UTC', label: 'UTC'}]}
|
|
662
|
+
/>,
|
|
663
|
+
);
|
|
664
|
+
// The invalid-value bail-out runs before any tooltip work, so there is
|
|
665
|
+
// no half-rendered tooltip anchored to nothing.
|
|
666
|
+
expect(container).toBeEmptyDOMElement();
|
|
667
|
+
} finally {
|
|
668
|
+
warn.mockRestore();
|
|
669
|
+
}
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
it('shows configured entries when auto resolves to an absolute format', async () => {
|
|
673
|
+
render(
|
|
674
|
+
<Timestamp
|
|
675
|
+
value={VALUE}
|
|
676
|
+
format="auto"
|
|
677
|
+
autoThreshold={0}
|
|
678
|
+
tooltipEntries={[
|
|
679
|
+
{timezoneID: 'UTC', format: 'system_date_time', label: 'UTC'},
|
|
680
|
+
]}
|
|
681
|
+
data-testid="ts"
|
|
682
|
+
/>,
|
|
683
|
+
);
|
|
684
|
+
// autoThreshold={0} forces the absolute branch regardless of the clock.
|
|
685
|
+
const layer = await screen.findByRole('tooltip', {hidden: true});
|
|
686
|
+
expect(layer.textContent).toContain('2026-02-19 17:00:00');
|
|
687
|
+
expect(screen.getByTestId('ts')).toHaveAttribute('tabindex', '0');
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
it('accepts a unix-seconds value alongside entries', async () => {
|
|
691
|
+
render(
|
|
692
|
+
<Timestamp
|
|
693
|
+
value={Date.parse(VALUE) / 1000}
|
|
694
|
+
format="relative"
|
|
695
|
+
tooltipEntries={[
|
|
696
|
+
{timezoneID: 'UTC', format: 'system_date_time', label: 'UTC'},
|
|
697
|
+
]}
|
|
698
|
+
/>,
|
|
699
|
+
);
|
|
700
|
+
const layer = await screen.findByRole('tooltip', {hidden: true});
|
|
701
|
+
expect(layer.textContent).toContain('2026-02-19 17:00:00');
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
it('pairs exactly one label cell with one value cell per entry', async () => {
|
|
705
|
+
render(
|
|
706
|
+
<Timestamp
|
|
707
|
+
value={VALUE}
|
|
708
|
+
format="relative"
|
|
709
|
+
tooltipEntries={[
|
|
710
|
+
{timezoneID: 'UTC', label: 'UTC'},
|
|
711
|
+
{timezoneID: 'Asia/Tokyo'},
|
|
712
|
+
{timezoneID: 'Europe/London', label: 'London'},
|
|
713
|
+
]}
|
|
714
|
+
/>,
|
|
715
|
+
);
|
|
716
|
+
const layer = await screen.findByRole('tooltip', {hidden: true});
|
|
717
|
+
// An unlabeled entry still emits its label cell, so the two-column grid
|
|
718
|
+
// stays aligned and the <dl> stays valid markup.
|
|
719
|
+
expect(layer.querySelectorAll('dt')).toHaveLength(3);
|
|
720
|
+
expect(layer.querySelectorAll('dd')).toHaveLength(3);
|
|
721
|
+
expect(layer.querySelectorAll('dt')[1].textContent).toBe('');
|
|
722
|
+
});
|
|
723
|
+
|
|
724
|
+
it('gives the tab stop back when entries are removed', () => {
|
|
725
|
+
const {rerender} = render(
|
|
726
|
+
<Timestamp
|
|
727
|
+
value={VALUE}
|
|
728
|
+
format="date_time"
|
|
729
|
+
tooltipEntries={[{timezoneID: 'UTC'}]}
|
|
730
|
+
data-testid="ts"
|
|
731
|
+
/>,
|
|
732
|
+
);
|
|
733
|
+
expect(screen.getByTestId('ts')).toHaveAttribute('tabindex', '0');
|
|
734
|
+
|
|
735
|
+
rerender(<Timestamp value={VALUE} format="date_time" data-testid="ts" />);
|
|
736
|
+
// The widened gate is driven purely by entry presence, so dropping the
|
|
737
|
+
// prop must also drop the tab stop rather than stranding one.
|
|
738
|
+
expect(screen.getByTestId('ts')).not.toHaveAttribute('tabindex');
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
it('describes an absolute-format timestamp with its tooltip', async () => {
|
|
742
|
+
render(
|
|
743
|
+
<Timestamp
|
|
744
|
+
value={VALUE}
|
|
745
|
+
format="date_time"
|
|
746
|
+
tooltipEntries={[{timezoneID: 'UTC', label: 'UTC'}]}
|
|
747
|
+
data-testid="ts"
|
|
748
|
+
/>,
|
|
749
|
+
);
|
|
750
|
+
const layer = await screen.findByRole('tooltip', {hidden: true});
|
|
751
|
+
const el = screen.getByTestId('ts');
|
|
752
|
+
// An absolute format carries no aria-label, so aria-describedby is the
|
|
753
|
+
// only route the configured zones have to assistive tech.
|
|
754
|
+
expect(el.getAttribute('aria-label')).toBeNull();
|
|
755
|
+
await waitFor(() => {
|
|
756
|
+
expect(el.getAttribute('aria-describedby')).toBe(layer.id);
|
|
757
|
+
});
|
|
758
|
+
expect(layer.id).toBeTruthy();
|
|
759
|
+
});
|
|
760
|
+
|
|
761
|
+
it('leaves no tooltip and no description when hasTooltip is false', () => {
|
|
762
|
+
render(
|
|
763
|
+
<Timestamp
|
|
764
|
+
value={VALUE}
|
|
765
|
+
format="relative"
|
|
766
|
+
hasTooltip={false}
|
|
767
|
+
tooltipEntries={[{timezoneID: 'UTC', label: 'UTC'}]}
|
|
768
|
+
data-testid="ts"
|
|
769
|
+
/>,
|
|
770
|
+
);
|
|
771
|
+
expect(
|
|
772
|
+
screen.queryByRole('tooltip', {hidden: true}),
|
|
773
|
+
).not.toBeInTheDocument();
|
|
774
|
+
// No orphaned reference pointing at a tooltip that was never rendered.
|
|
775
|
+
expect(
|
|
776
|
+
screen.getByTestId('ts').getAttribute('aria-describedby'),
|
|
777
|
+
).toBeNull();
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
it('does not crash on an unknown time zone', async () => {
|
|
781
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
782
|
+
try {
|
|
783
|
+
render(
|
|
784
|
+
<Timestamp
|
|
785
|
+
value={VALUE}
|
|
786
|
+
format="relative"
|
|
787
|
+
tooltipEntries={[
|
|
788
|
+
{timezoneID: 'Not/AZone', format: 'system_date_time'},
|
|
789
|
+
{timezoneID: 'UTC', format: 'system_date_time'},
|
|
790
|
+
]}
|
|
791
|
+
/>,
|
|
792
|
+
);
|
|
793
|
+
const layer = await screen.findByRole('tooltip', {hidden: true});
|
|
794
|
+
const values = Array.from(layer.querySelectorAll('dd')).map(
|
|
795
|
+
el => el.textContent,
|
|
796
|
+
);
|
|
797
|
+
expect(values).toHaveLength(2);
|
|
798
|
+
expect(values[1]).toBe('2026-02-19 17:00:00');
|
|
799
|
+
expect(warn).toHaveBeenCalledWith(expect.stringContaining('Not/AZone'));
|
|
800
|
+
} finally {
|
|
801
|
+
warn.mockRestore();
|
|
802
|
+
}
|
|
803
|
+
});
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
describe('one formatter behind both surfaces', () => {
|
|
807
|
+
// The rendered text and a tooltip line are two views of the same instant.
|
|
808
|
+
// They are meant to come from one formatter parameterized by zone, not two
|
|
809
|
+
// that happen to agree: a format added or reshaped on one surface and not
|
|
810
|
+
// the other is a silent drift, invisible until someone compares them.
|
|
811
|
+
// These assertions compare the two surfaces directly, so re-forking either
|
|
812
|
+
// one fails here. Locale- and timezone-agnostic: nothing is pinned to a
|
|
813
|
+
// literal, only the two paths to each other.
|
|
814
|
+
const VALUE = '2026-02-19T17:00:00Z';
|
|
815
|
+
|
|
816
|
+
const SHARED_FORMATS = [
|
|
817
|
+
'date',
|
|
818
|
+
'date_long',
|
|
819
|
+
'date_weekday',
|
|
820
|
+
'date_time',
|
|
821
|
+
'time',
|
|
822
|
+
'system_date',
|
|
823
|
+
'system_date_time',
|
|
824
|
+
'system_time',
|
|
825
|
+
] as const;
|
|
826
|
+
|
|
827
|
+
it.each(SHARED_FORMATS)(
|
|
828
|
+
'renders %s identically as text and as a zone-less tooltip line',
|
|
829
|
+
format => {
|
|
830
|
+
render(
|
|
831
|
+
<Timestamp
|
|
832
|
+
value={VALUE}
|
|
833
|
+
format={format}
|
|
834
|
+
hasTooltip={false}
|
|
835
|
+
data-testid="ts"
|
|
836
|
+
/>,
|
|
837
|
+
);
|
|
838
|
+
const [line] = formatTooltipLines(new Date(VALUE), [{format}]);
|
|
839
|
+
expect(line.value).toBe(screen.getByTestId('ts').textContent);
|
|
840
|
+
},
|
|
841
|
+
);
|
|
842
|
+
|
|
843
|
+
it('renders the full style identically as the aria-label and as a line', () => {
|
|
844
|
+
// 'full' is the one member with no visible-text counterpart — it backs
|
|
845
|
+
// the accessible name of a relative timestamp and the tooltip's default
|
|
846
|
+
// line. Those two must not drift apart either.
|
|
847
|
+
render(
|
|
848
|
+
<Timestamp
|
|
849
|
+
value={VALUE}
|
|
850
|
+
format="relative"
|
|
851
|
+
hasTooltip={false}
|
|
852
|
+
data-testid="ts"
|
|
853
|
+
/>,
|
|
854
|
+
);
|
|
855
|
+
const [line] = formatTooltipLines(new Date(VALUE), [{format: 'full'}]);
|
|
856
|
+
expect(line.value).toBe(
|
|
857
|
+
screen.getByTestId('ts').getAttribute('aria-label'),
|
|
858
|
+
);
|
|
859
|
+
});
|
|
860
|
+
});
|
|
490
861
|
});
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* - /packages/cli/templates/blocks/components/Timestamp/ (showcase blocks)
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import {useEffect, useRef, useState, lazy, Suspense} from 'react';
|
|
19
|
+
import {useEffect, useRef, useState, lazy, Suspense, Fragment} from 'react';
|
|
20
20
|
import * as stylex from '@stylexjs/stylex';
|
|
21
21
|
import {Text} from '../Text';
|
|
22
22
|
import type {TextType, TextSize, TextColor, TextWeight} from '../theme/types';
|
|
@@ -24,8 +24,10 @@ import {mergeProps, mergeRefs} from '../utils';
|
|
|
24
24
|
import {useDevWarning} from '../hooks/useDevWarning';
|
|
25
25
|
import type {BaseProps} from '../BaseProps';
|
|
26
26
|
import {themeProps} from '../utils/themeProps';
|
|
27
|
-
import {colorVars} from '../theme/tokens.stylex';
|
|
28
|
-
import {
|
|
27
|
+
import {colorVars, spacingVars} from '../theme/tokens.stylex';
|
|
28
|
+
import {formatInstant} from './formatInstant';
|
|
29
|
+
import {formatTooltipLines} from './tooltipEntries';
|
|
30
|
+
import type {TimestampTooltipEntry} from './tooltipEntries';
|
|
29
31
|
|
|
30
32
|
const LazyXDSTooltip = lazy(async () =>
|
|
31
33
|
import('../Tooltip/Tooltip').then(mod => ({default: mod.Tooltip})),
|
|
@@ -78,8 +80,35 @@ export interface TimestampProps extends BaseProps<HTMLTimeElement> {
|
|
|
78
80
|
*/
|
|
79
81
|
hasTooltip?: boolean;
|
|
80
82
|
/**
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
+
* Lines to show in the hover tooltip, so one instant can be read in several
|
|
84
|
+
* time zones and/or formats at once. Each entry is one line, rendered in the
|
|
85
|
+
* order given, with an optional label.
|
|
86
|
+
*
|
|
87
|
+
* Configuring entries also attaches the tooltip to absolute formats, which
|
|
88
|
+
* otherwise have no tooltip at all. `hasTooltip={false}` still suppresses it,
|
|
89
|
+
* and an empty array is treated as no configuration.
|
|
90
|
+
*
|
|
91
|
+
* @default undefined — a single line with the full absolute time in the
|
|
92
|
+
* viewer's own time zone
|
|
93
|
+
* @example
|
|
94
|
+
* ```
|
|
95
|
+
* <Timestamp
|
|
96
|
+
* value={savedAt}
|
|
97
|
+
* tooltipEntries={[
|
|
98
|
+
* {label: 'Your time'},
|
|
99
|
+
* {timezoneID: 'UTC', label: 'UTC'},
|
|
100
|
+
* ]}
|
|
101
|
+
* />
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
tooltipEntries?: ReadonlyArray<TimestampTooltipEntry>;
|
|
105
|
+
/**
|
|
106
|
+
* Whether to append the timezone abbreviation after the timestamp text.
|
|
107
|
+
* Applies to the date_time and time formats. The system_* formats stay
|
|
108
|
+
* machine-readable and never carry a timezone abbreviation.
|
|
109
|
+
*
|
|
110
|
+
* Affects the visible text only — use `tooltipEntries` to control the
|
|
111
|
+
* tooltip's time zones.
|
|
83
112
|
* @default false
|
|
84
113
|
*/
|
|
85
114
|
isTimezoneShown?: boolean;
|
|
@@ -137,6 +166,31 @@ const styles = stylex.create({
|
|
|
137
166
|
':focus-visible': '2px',
|
|
138
167
|
},
|
|
139
168
|
},
|
|
169
|
+
// Label/value pairs for a multi-entry tooltip. The label column is sized to
|
|
170
|
+
// its content, so when no entry carries a label it collapses to zero width
|
|
171
|
+
// and the values sit exactly where a plain list of lines would.
|
|
172
|
+
tooltipLines: {
|
|
173
|
+
display: 'grid',
|
|
174
|
+
gridTemplateColumns: 'auto 1fr',
|
|
175
|
+
rowGap: spacingVars['--spacing-0-5'],
|
|
176
|
+
marginBlock: 0,
|
|
177
|
+
marginInline: 0,
|
|
178
|
+
},
|
|
179
|
+
tooltipLabel: {
|
|
180
|
+
marginBlock: 0,
|
|
181
|
+
marginInline: 0,
|
|
182
|
+
// Only a label that actually has text earns the gutter, keeping the
|
|
183
|
+
// unlabeled case flush.
|
|
184
|
+
paddingInlineEnd: {
|
|
185
|
+
default: 0,
|
|
186
|
+
':not(:empty)': spacingVars['--spacing-2'],
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
tooltipValue: {
|
|
190
|
+
marginBlock: 0,
|
|
191
|
+
// <dd> carries a 40px inline start margin from the UA stylesheet.
|
|
192
|
+
marginInline: 0,
|
|
193
|
+
},
|
|
140
194
|
});
|
|
141
195
|
|
|
142
196
|
// =============================================================================
|
|
@@ -244,89 +298,6 @@ function getRelativeTimeString(date: Date, now: Date): string {
|
|
|
244
298
|
return `${years} ${years === 1 ? 'year' : 'years'} ago`;
|
|
245
299
|
}
|
|
246
300
|
|
|
247
|
-
function pad(n: number): string {
|
|
248
|
-
return String(n).padStart(2, '0');
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
function formatTimestamp(
|
|
252
|
-
date: Date,
|
|
253
|
-
format: Exclude<TimestampFormat, 'relative' | 'auto'>,
|
|
254
|
-
isTimezoneShown: boolean,
|
|
255
|
-
): string {
|
|
256
|
-
switch (format) {
|
|
257
|
-
case 'date':
|
|
258
|
-
return new Intl.DateTimeFormat(
|
|
259
|
-
undefined,
|
|
260
|
-
SHARED_DATE_FORMAT_OPTIONS.date,
|
|
261
|
-
).format(date);
|
|
262
|
-
|
|
263
|
-
case 'date_long':
|
|
264
|
-
return new Intl.DateTimeFormat(
|
|
265
|
-
undefined,
|
|
266
|
-
SHARED_DATE_FORMAT_OPTIONS.date_long,
|
|
267
|
-
).format(date);
|
|
268
|
-
|
|
269
|
-
case 'date_weekday':
|
|
270
|
-
return new Intl.DateTimeFormat(
|
|
271
|
-
undefined,
|
|
272
|
-
SHARED_DATE_FORMAT_OPTIONS.date_weekday,
|
|
273
|
-
).format(date);
|
|
274
|
-
|
|
275
|
-
case 'date_time':
|
|
276
|
-
return new Intl.DateTimeFormat(undefined, {
|
|
277
|
-
year: 'numeric',
|
|
278
|
-
month: 'short',
|
|
279
|
-
day: 'numeric',
|
|
280
|
-
hour: 'numeric',
|
|
281
|
-
minute: '2-digit',
|
|
282
|
-
...(isTimezoneShown ? {timeZoneName: 'short'} : {}),
|
|
283
|
-
}).format(date);
|
|
284
|
-
|
|
285
|
-
case 'time':
|
|
286
|
-
return new Intl.DateTimeFormat(undefined, {
|
|
287
|
-
hour: 'numeric',
|
|
288
|
-
minute: '2-digit',
|
|
289
|
-
...(isTimezoneShown ? {timeZoneName: 'short'} : {}),
|
|
290
|
-
}).format(date);
|
|
291
|
-
|
|
292
|
-
case 'system_date': {
|
|
293
|
-
const y = date.getFullYear();
|
|
294
|
-
const m = pad(date.getMonth() + 1);
|
|
295
|
-
const d = pad(date.getDate());
|
|
296
|
-
return `${y}-${m}-${d}`;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
case 'system_date_time': {
|
|
300
|
-
const y = date.getFullYear();
|
|
301
|
-
const m = pad(date.getMonth() + 1);
|
|
302
|
-
const d = pad(date.getDate());
|
|
303
|
-
const h = pad(date.getHours());
|
|
304
|
-
const min = pad(date.getMinutes());
|
|
305
|
-
const s = pad(date.getSeconds());
|
|
306
|
-
return `${y}-${m}-${d} ${h}:${min}:${s}`;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
case 'system_time': {
|
|
310
|
-
const h = pad(date.getHours());
|
|
311
|
-
const min = pad(date.getMinutes());
|
|
312
|
-
const s = pad(date.getSeconds());
|
|
313
|
-
return `${h}:${min}:${s}`;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
function getFullAbsoluteString(date: Date): string {
|
|
319
|
-
return new Intl.DateTimeFormat(undefined, {
|
|
320
|
-
year: 'numeric',
|
|
321
|
-
month: 'long',
|
|
322
|
-
day: 'numeric',
|
|
323
|
-
hour: 'numeric',
|
|
324
|
-
minute: '2-digit',
|
|
325
|
-
second: '2-digit',
|
|
326
|
-
timeZoneName: 'short',
|
|
327
|
-
}).format(date);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
301
|
/** Returns the interval (in ms) at which a relative timestamp should update. */
|
|
331
302
|
function getLiveInterval(diffSeconds: number): number {
|
|
332
303
|
const absDiff = Math.abs(diffSeconds);
|
|
@@ -374,6 +345,7 @@ export function Timestamp({
|
|
|
374
345
|
format = 'auto',
|
|
375
346
|
autoThreshold = DEFAULT_AUTO_THRESHOLD,
|
|
376
347
|
hasTooltip = true,
|
|
348
|
+
tooltipEntries,
|
|
377
349
|
isTimezoneShown = false,
|
|
378
350
|
isLive = false,
|
|
379
351
|
type = 'supporting',
|
|
@@ -406,17 +378,18 @@ export function Timestamp({
|
|
|
406
378
|
: 'date_time'
|
|
407
379
|
: format;
|
|
408
380
|
|
|
409
|
-
// Format the display text
|
|
381
|
+
// Format the display text. No time zone is passed: the visible text always
|
|
382
|
+
// reads in the viewer's own zone, and only the tooltip names others.
|
|
410
383
|
const displayText = !isValidDate
|
|
411
384
|
? ''
|
|
412
385
|
: effectiveFormat === 'relative'
|
|
413
386
|
? getRelativeTimeString(date, now)
|
|
414
387
|
: isAbsoluteFormat(effectiveFormat)
|
|
415
|
-
?
|
|
388
|
+
? formatInstant(date, effectiveFormat, {isTimezoneShown})
|
|
416
389
|
: '';
|
|
417
390
|
|
|
418
391
|
// Full absolute text for tooltip and aria-label
|
|
419
|
-
const fullAbsoluteText = isValidDate ?
|
|
392
|
+
const fullAbsoluteText = isValidDate ? formatInstant(date, 'full') : '';
|
|
420
393
|
|
|
421
394
|
// Live updates
|
|
422
395
|
useEffect(() => {
|
|
@@ -443,7 +416,19 @@ export function Timestamp({
|
|
|
443
416
|
return null;
|
|
444
417
|
}
|
|
445
418
|
|
|
446
|
-
|
|
419
|
+
// An empty array is not a second way to spell "off" — `hasTooltip` stays the
|
|
420
|
+
// only on/off axis — so normalize it away before anything reads it.
|
|
421
|
+
const entries =
|
|
422
|
+
tooltipEntries !== undefined && tooltipEntries.length > 0
|
|
423
|
+
? tooltipEntries
|
|
424
|
+
: undefined;
|
|
425
|
+
|
|
426
|
+
// Absolute formats have never carried a tooltip. Leaving that gate closed
|
|
427
|
+
// when a consumer has explicitly configured tooltip lines would let `format`
|
|
428
|
+
// silently suppress another prop's output, so entry presence opens it too.
|
|
429
|
+
// With no entries this reduces to the original condition exactly.
|
|
430
|
+
const showTooltip =
|
|
431
|
+
hasTooltip && (effectiveFormat === 'relative' || entries !== undefined);
|
|
447
432
|
|
|
448
433
|
const timestampProps = mergeProps(
|
|
449
434
|
themeProps('timestamp', {format: effectiveFormat}),
|
|
@@ -479,13 +464,31 @@ export function Timestamp({
|
|
|
479
464
|
);
|
|
480
465
|
|
|
481
466
|
if (showTooltip) {
|
|
467
|
+
// Built inside the branch so a timestamp without a tooltip allocates none
|
|
468
|
+
// of it. With no entries the content stays the bare string it has always
|
|
469
|
+
// been — no wrapper element is introduced around the default line.
|
|
470
|
+
const tooltipContent =
|
|
471
|
+
entries === undefined ? (
|
|
472
|
+
fullAbsoluteText
|
|
473
|
+
) : (
|
|
474
|
+
<dl {...stylex.props(styles.tooltipLines)}>
|
|
475
|
+
{formatTooltipLines(date, entries).map((line, index) => (
|
|
476
|
+
// eslint-disable-next-line @eslint-react/no-array-index-key -- tooltip lines are fixed positional slots and two entries may legitimately be identical
|
|
477
|
+
<Fragment key={index}>
|
|
478
|
+
<dt {...stylex.props(styles.tooltipLabel)}>{line.label ?? ''}</dt>
|
|
479
|
+
<dd {...stylex.props(styles.tooltipValue)}>{line.value}</dd>
|
|
480
|
+
</Fragment>
|
|
481
|
+
))}
|
|
482
|
+
</dl>
|
|
483
|
+
);
|
|
484
|
+
|
|
482
485
|
return (
|
|
483
486
|
<>
|
|
484
487
|
{timeElement}
|
|
485
488
|
<Suspense fallback={null}>
|
|
486
489
|
<LazyXDSTooltip
|
|
487
490
|
anchorRef={timeRef}
|
|
488
|
-
content={
|
|
491
|
+
content={tooltipContent}
|
|
489
492
|
placement="above"
|
|
490
493
|
/>
|
|
491
494
|
</Suspense>
|