@astryxdesign/core 0.7.1-canary.ff40b78 → 0.7.1
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 +116 -0
- package/dist/Button/Button.d.ts +1 -1
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +5 -2
- package/dist/Calendar/Calendar.d.ts.map +1 -1
- package/dist/Calendar/Calendar.js +191 -32
- package/dist/Chat/ChatComposerInput.d.ts +9 -0
- package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
- package/dist/Chat/ChatComposerInput.js +5 -0
- package/dist/Chat/ChatMessage.d.ts +11 -0
- package/dist/Chat/ChatMessage.d.ts.map +1 -1
- package/dist/Chat/ChatMessage.js +18 -9
- package/dist/Chat/useTriggerMenu.d.ts +9 -2
- package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
- package/dist/Chat/useTriggerMenu.js +222 -50
- package/dist/CodeBlock/CodeBlock.d.ts +10 -1
- package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
- package/dist/CodeBlock/CodeBlock.js +158 -38
- package/dist/CodeBlock/tokenizer.d.ts +39 -0
- package/dist/CodeBlock/tokenizer.d.ts.map +1 -1
- package/dist/CodeBlock/tokenizer.js +108 -0
- package/dist/CommandPalette/CommandPalette.d.ts +9 -5
- package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPalette.js +38 -20
- package/dist/CommandPalette/CommandPaletteEmpty.d.ts +4 -3
- package/dist/CommandPalette/CommandPaletteEmpty.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteEmpty.js +4 -3
- package/dist/CommandPalette/CommandPaletteInput.d.ts +5 -4
- package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteInput.js +35 -18
- package/dist/CommandPalette/CommandPaletteItem.d.ts +11 -6
- package/dist/CommandPalette/CommandPaletteItem.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteItem.js +8 -9
- package/dist/CommandPalette/CommandPaletteList.d.ts +4 -2
- package/dist/CommandPalette/CommandPaletteList.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteList.js +10 -5
- package/dist/ComplexSelector/ComplexSelector.d.ts +8 -5
- package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
- package/dist/ComplexSelector/ComplexSelector.js +12 -3
- package/dist/ContextMenu/ContextMenu.d.ts +3 -3
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +62 -48
- package/dist/DateInput/DateInput.js +1 -1
- package/dist/DateInput/MonthScroller.d.ts.map +1 -1
- package/dist/DateInput/MonthScroller.js +12 -4
- package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
- package/dist/DateRangeInput/DateRangeInput.js +12 -2
- package/dist/Dialog/Dialog.d.ts +1 -1
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +16 -5
- package/dist/Dialog/DialogHeader.d.ts +1 -1
- package/dist/Dialog/DialogHeader.d.ts.map +1 -1
- package/dist/Dialog/DialogHeader.js +7 -7
- package/dist/Divider/Divider.d.ts +1 -0
- package/dist/Divider/Divider.d.ts.map +1 -1
- package/dist/Divider/Divider.js +5 -4
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +161 -65
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts +7 -5
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +12 -5
- package/dist/Field/InputClearButton.d.ts +8 -4
- package/dist/Field/InputClearButton.d.ts.map +1 -1
- package/dist/Field/InputClearButton.js +23 -7
- package/dist/FormLayout/FormLayout.d.ts.map +1 -1
- package/dist/FormLayout/FormLayout.js +1 -0
- package/dist/Grid/Grid.d.ts +13 -4
- package/dist/Grid/Grid.d.ts.map +1 -1
- package/dist/Grid/Grid.js +7 -1
- package/dist/HoverCard/useHoverCard.d.ts +1 -1
- package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
- package/dist/HoverCard/useHoverCard.js +67 -49
- package/dist/InputGroup/InputGroup.d.ts +2 -4
- package/dist/InputGroup/InputGroup.d.ts.map +1 -1
- package/dist/InputGroup/InputGroup.js +2 -4
- package/dist/InputGroup/groupStyles.d.ts +10 -2
- package/dist/InputGroup/groupStyles.d.ts.map +1 -1
- package/dist/InputGroup/groupStyles.js +19 -2
- package/dist/Layer/useLayerDismissal.d.ts +9 -9
- package/dist/Layer/useLayerDismissal.js +9 -9
- package/dist/Markdown/Markdown.d.ts.map +1 -1
- package/dist/Markdown/Markdown.js +25 -4
- package/dist/MobileNav/MobileNav.d.ts +7 -0
- package/dist/MobileNav/MobileNav.d.ts.map +1 -1
- package/dist/MobileNav/MobileNav.js +45 -2
- package/dist/MultiSelector/MultiSelector.d.ts +7 -4
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +24 -3
- package/dist/NumberInput/NumberInput.js +1 -1
- package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearchEditPopover.js +6 -0
- package/dist/Resizable/ResizeHandle.d.ts +1 -1
- package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
- package/dist/Resizable/ResizeHandle.js +83 -15
- package/dist/Resizable/useResizable.d.ts +23 -0
- package/dist/Resizable/useResizable.d.ts.map +1 -1
- package/dist/Resizable/useResizable.js +78 -0
- package/dist/Selector/Selector.d.ts +6 -3
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +28 -6
- package/dist/SideNav/SideNav.d.ts.map +1 -1
- package/dist/SideNav/SideNav.js +10 -2
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +18 -7
- package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.js +16 -4
- package/dist/Toolbar/Toolbar.d.ts +2 -1
- package/dist/Toolbar/Toolbar.d.ts.map +1 -1
- package/dist/Toolbar/Toolbar.js +3 -1
- package/dist/TreeList/TreeList.d.ts.map +1 -1
- package/dist/TreeList/TreeList.js +6 -3
- package/dist/TreeList/TreeListItem.d.ts.map +1 -1
- package/dist/TreeList/TreeListItem.js +7 -1
- package/dist/astryx.css +22 -0
- package/dist/hooks/useScrollOverflow.d.ts.map +1 -1
- package/dist/hooks/useScrollOverflow.js +55 -12
- package/dist/hooks/useTreeFocus.d.ts +21 -0
- package/dist/hooks/useTreeFocus.d.ts.map +1 -1
- package/dist/hooks/useTreeFocus.js +40 -2
- package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/en.generated.js +3 -0
- package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/pseudo.generated.js +3 -0
- package/dist/reset.a11y.chromium.spec.js +6 -1
- package/dist/reset.until-found.a11y.browser.spec.d.ts +2 -0
- package/dist/reset.until-found.a11y.browser.spec.d.ts.map +1 -0
- package/dist/reset.until-found.a11y.browser.spec.js +71 -0
- package/dist/theme/syntax/SyntaxTheme.d.ts.map +1 -1
- package/dist/theme/syntax/SyntaxTheme.js +43 -2
- package/dist/theme/syntax/defineSyntaxTheme.d.ts +18 -5
- package/dist/theme/syntax/defineSyntaxTheme.d.ts.map +1 -1
- package/dist/theme/syntax/defineSyntaxTheme.js +38 -9
- package/dist/theme/syntax/index.d.ts +1 -1
- package/dist/theme/syntax/index.d.ts.map +1 -1
- package/dist/theme/syntax/tokens.d.ts +12 -2
- package/dist/theme/syntax/tokens.d.ts.map +1 -1
- package/dist/theme/syntax/tokens.js +25 -4
- package/dist/utils/plainDate.d.ts +12 -0
- package/dist/utils/plainDate.d.ts.map +1 -1
- package/dist/utils/plainDate.js +63 -10
- package/locales/en.json +13 -1
- package/locales/pseudo.json +9 -0
- package/package.json +4 -4
- package/src/AlertDialog/AlertDialog.doc.mjs +6 -0
- package/src/AlertDialog/AlertDialog.spec.md +205 -0
- package/src/AlertDialog/AlertDialog.test.tsx +3 -14
- package/src/AlertDialog/__tests__/AlertDialog.a11y.chromium.spec.ts +115 -0
- package/src/AlertDialog/__tests__/AlertDialog.a11y.renders.tsx +52 -0
- package/src/AlertDialog/__tests__/AlertDialog.a11y.states.ts +68 -0
- package/src/AlertDialog/__tests__/AlertDialog.a11y.test.tsx +144 -0
- package/src/Button/Button.test.tsx +37 -0
- package/src/Button/Button.tsx +5 -1
- package/src/Calendar/Calendar.doc.mjs +234 -60
- package/src/Calendar/Calendar.spec.md +209 -0
- package/src/Calendar/Calendar.test.tsx +559 -20
- package/src/Calendar/Calendar.tsx +258 -41
- package/src/Calendar/__tests__/Calendar.a11y.chromium.spec.ts +126 -0
- package/src/Calendar/__tests__/Calendar.a11y.known-failures.ts +12 -0
- package/src/Calendar/__tests__/Calendar.a11y.renders.tsx +57 -0
- package/src/Calendar/__tests__/Calendar.a11y.states.ts +139 -0
- package/src/Calendar/__tests__/Calendar.a11y.test.tsx +137 -0
- package/src/Carousel/Carousel.test.tsx +54 -71
- package/src/Carousel/__tests__/Carousel.a11y.chromium.spec.ts +192 -0
- package/src/Carousel/__tests__/Carousel.a11y.known-failures.ts +27 -0
- package/src/Carousel/__tests__/Carousel.a11y.renders.tsx +39 -0
- package/src/Carousel/__tests__/Carousel.a11y.states.ts +139 -0
- package/src/Carousel/__tests__/Carousel.a11y.test.tsx +127 -0
- package/src/Chat/ChatComposerInput.doc.mjs +1 -1
- package/src/Chat/ChatComposerInput.spec.md +94 -55
- package/src/Chat/ChatComposerInput.test.tsx +202 -1
- package/src/Chat/ChatComposerInput.tsx +13 -0
- package/src/Chat/ChatMessage.doc.mjs +31 -16
- package/src/Chat/ChatMessage.spec.md +265 -0
- package/src/Chat/ChatMessage.test.tsx +246 -0
- package/src/Chat/ChatMessage.tsx +30 -7
- package/src/Chat/ChatSystemMessage.spec.md +36 -37
- package/src/Chat/ChatSystemMessage.test.tsx +4 -4
- package/src/Chat/useTriggerMenu.test.tsx +994 -0
- package/src/Chat/useTriggerMenu.tsx +278 -46
- package/src/CodeBlock/CodeBlock.doc.mjs +7 -0
- package/src/CodeBlock/CodeBlock.spec.md +40 -41
- package/src/CodeBlock/CodeBlock.test.tsx +89 -9
- package/src/CodeBlock/CodeBlock.tsx +205 -25
- package/src/CodeBlock/__tests__/CodeBlockOverflowStop.a11y.chromium.spec.ts +125 -0
- package/src/CodeBlock/__tests__/DiffRowPaint.a11y.chromium.spec.ts +93 -0
- package/src/CodeBlock/tokenizer.test.ts +112 -0
- package/src/CodeBlock/tokenizer.ts +106 -0
- package/src/Collapsible/CollapsibleGroup.spec.md +17 -8
- package/src/Collapsible/__tests__/CollapsibleGroup.a11y.renders.tsx +45 -0
- package/src/Collapsible/__tests__/CollapsibleGroup.a11y.states.ts +139 -0
- package/src/Collapsible/__tests__/CollapsibleGroup.a11y.test.tsx +135 -0
- package/src/Collapsible/__tests__/CollapsibleGroupAccordion.a11y.chromium.spec.ts +128 -0
- package/src/CommandPalette/CommandPalette.doc.mjs +126 -23
- package/src/CommandPalette/CommandPalette.spec.md +100 -49
- package/src/CommandPalette/CommandPalette.test.tsx +241 -2
- package/src/CommandPalette/CommandPalette.tsx +47 -21
- package/src/CommandPalette/CommandPaletteEmpty.tsx +4 -3
- package/src/CommandPalette/CommandPaletteInput.doc.mjs +61 -4
- package/src/CommandPalette/CommandPaletteInput.spec.md +215 -0
- package/src/CommandPalette/CommandPaletteInput.test.tsx +375 -51
- package/src/CommandPalette/CommandPaletteInput.tsx +74 -21
- package/src/CommandPalette/CommandPaletteItem.doc.mjs +73 -22
- package/src/CommandPalette/CommandPaletteItem.spec.md +223 -0
- package/src/CommandPalette/CommandPaletteItem.test.tsx +200 -5
- package/src/CommandPalette/CommandPaletteItem.tsx +16 -11
- package/src/CommandPalette/CommandPaletteList.doc.mjs +3 -3
- package/src/CommandPalette/CommandPaletteList.spec.md +41 -36
- package/src/CommandPalette/CommandPaletteList.test.tsx +80 -0
- package/src/CommandPalette/CommandPaletteList.tsx +10 -5
- package/src/CommandPalette/__tests__/CommandPalette.a11y.chromium.spec.ts +599 -0
- package/src/ComplexSelector/ComplexSelector.doc.mjs +10 -8
- package/src/ComplexSelector/ComplexSelector.spec.md +14 -9
- package/src/ComplexSelector/ComplexSelector.test.tsx +64 -16
- package/src/ComplexSelector/ComplexSelector.tsx +37 -8
- package/src/ContextMenu/ContextMenu.doc.mjs +6 -0
- package/src/ContextMenu/ContextMenu.spec.md +51 -53
- package/src/ContextMenu/ContextMenu.test.tsx +132 -1
- package/src/ContextMenu/ContextMenu.tsx +78 -48
- package/src/DateInput/DateInput.doc.mjs +52 -0
- package/src/DateInput/DateInput.spec.md +27 -15
- package/src/DateInput/DateInput.test.tsx +38 -0
- package/src/DateInput/DateInput.tsx +1 -1
- package/src/DateInput/DateInputTouch.test.tsx +22 -0
- package/src/DateInput/MonthScroller.tsx +12 -4
- package/src/DateRangeInput/DateRangeInput.test.tsx +49 -0
- package/src/DateRangeInput/DateRangeInput.tsx +20 -2
- package/src/Dialog/Dialog.test.tsx +27 -1
- package/src/Dialog/Dialog.tsx +16 -5
- package/src/Dialog/DialogHeader.test.tsx +27 -2
- package/src/Dialog/DialogHeader.tsx +7 -8
- package/src/Dialog/__tests__/Dialog.a11y.chromium.spec.ts +21 -14
- package/src/Divider/Divider.doc.mjs +5 -3
- package/src/Divider/Divider.spec.md +140 -54
- package/src/Divider/Divider.test.tsx +15 -0
- package/src/Divider/Divider.tsx +6 -4
- package/src/DropdownMenu/DropdownMenu.doc.mjs +2 -2
- package/src/DropdownMenu/DropdownMenu.spec.md +121 -45
- package/src/DropdownMenu/DropdownMenu.test.tsx +288 -23
- package/src/DropdownMenu/DropdownMenu.tsx +212 -81
- package/src/DropdownMenu/DropdownMenuCheckboxItem.doc.mjs +38 -2
- package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +32 -18
- package/src/DropdownMenu/DropdownMenuSelectable.test.tsx +146 -1
- package/src/DropdownMenu/__tests__/MenuButton.a11y.chromium.spec.ts +136 -0
- package/src/DropdownMenu/__tests__/MenuButton.a11y.known-failures.ts +12 -0
- package/src/DropdownMenu/__tests__/MenuButton.a11y.renders.tsx +67 -0
- package/src/DropdownMenu/__tests__/MenuButton.a11y.states.ts +141 -0
- package/src/DropdownMenu/__tests__/MenuButton.a11y.test.tsx +138 -0
- package/src/DropdownMenu/__tests__/MenuHeaderFooter.a11y.chromium.spec.ts +40 -5
- package/src/Field/InputClearButton.test.tsx +29 -4
- package/src/Field/InputClearButton.tsx +26 -8
- package/src/FieldStatus/__tests__/StatusMessage.a11y.chromium.spec.ts +21 -14
- package/src/FormLayout/FormLayout.tsx +1 -0
- package/src/FormLayout/__snapshots__/FormLayout.test.tsx.snap +1 -1
- package/src/Grid/Grid.doc.mjs +14 -11
- package/src/Grid/Grid.spec.md +50 -32
- package/src/Grid/Grid.tsx +13 -4
- package/src/HoverCard/HoverCard.test.tsx +46 -1
- package/src/HoverCard/useHoverCard.tsx +68 -48
- package/src/InputGroup/InputGroup.doc.mjs +11 -1
- package/src/InputGroup/InputGroup.tsx +2 -4
- package/src/InputGroup/groupStyles.ts +40 -2
- package/src/Layer/useLayerDismissal.ts +9 -9
- package/src/Link/__tests__/Link.a11y.chromium.spec.ts +142 -0
- package/src/Link/__tests__/Link.a11y.renders.tsx +97 -0
- package/src/Link/__tests__/Link.a11y.states.ts +104 -0
- package/src/Link/__tests__/Link.a11y.test.tsx +127 -0
- package/src/Markdown/Markdown.doc.mjs +1 -1
- package/src/Markdown/Markdown.fr23.perf.test.ts +161 -40
- package/src/Markdown/Markdown.spec.md +45 -23
- package/src/Markdown/Markdown.test.tsx +38 -0
- package/src/Markdown/Markdown.tsx +24 -2
- package/src/Markdown/plugins/semanticFence.test.tsx +3 -2
- package/src/MobileNav/MobileNav.tsx +50 -2
- package/src/MobileNav/MobileNavFocus.test.tsx +553 -0
- package/src/MoreMenu/MoreMenu.spec.md +18 -7
- package/src/MoreMenu/MoreMenu.test.tsx +38 -0
- package/src/MultiSelector/MultiSelector.doc.mjs +8 -8
- package/src/MultiSelector/MultiSelector.spec.md +1 -1
- package/src/MultiSelector/MultiSelector.test.tsx +188 -21
- package/src/MultiSelector/MultiSelector.tsx +50 -11
- package/src/NumberInput/NumberInput.test.tsx +24 -0
- package/src/NumberInput/NumberInput.tsx +1 -1
- package/src/NumberInput/__tests__/NumberInput.a11y.chromium.spec.ts +22 -15
- package/src/PowerSearch/PowerSearchEditPopover.test.tsx +78 -0
- package/src/PowerSearch/PowerSearchEditPopover.tsx +6 -0
- package/src/Resizable/Resizable.spec.md +41 -9
- package/src/Resizable/ResizeHandle.test.tsx +565 -2
- package/src/Resizable/ResizeHandle.tsx +126 -21
- package/src/Resizable/__tests__/ResizeHandle.a11y.chromium.spec.ts +119 -0
- package/src/Resizable/__tests__/ResizeHandle.a11y.known-failures.ts +39 -0
- package/src/Resizable/__tests__/ResizeHandle.a11y.renders.tsx +80 -0
- package/src/Resizable/__tests__/ResizeHandle.a11y.states.ts +197 -0
- package/src/Resizable/__tests__/ResizeHandle.a11y.test.tsx +123 -0
- package/src/Resizable/useResizable.ts +95 -0
- package/src/Selector/Selector.doc.mjs +7 -6
- package/src/Selector/Selector.spec.md +37 -24
- package/src/Selector/Selector.test.tsx +176 -10
- package/src/Selector/Selector.tsx +59 -14
- package/src/Selector/__tests__/Combobox.a11y.known-failures.ts +27 -0
- package/src/Selector/__tests__/Combobox.a11y.states.ts +145 -0
- package/src/Selector/__tests__/Selector.combobox.a11y.chromium.spec.ts +108 -0
- package/src/Selector/__tests__/Selector.combobox.a11y.test.tsx +126 -0
- package/src/SideNav/SideNav.test.tsx +35 -0
- package/src/SideNav/SideNav.tsx +16 -2
- package/src/Slider/Slider.spec.md +43 -17
- package/src/Slider/Slider.test.tsx +134 -0
- package/src/Slider/Slider.tsx +29 -6
- package/src/Slider/__tests__/Slider.a11y.chromium.spec.ts +112 -0
- package/src/Slider/__tests__/Slider.a11y.known-failures.ts +12 -0
- package/src/Slider/__tests__/Slider.a11y.renders.tsx +42 -0
- package/src/Slider/__tests__/Slider.a11y.states.ts +106 -0
- package/src/Slider/__tests__/Slider.a11y.test.tsx +105 -0
- package/src/Switch/__tests__/Switch.a11y.chromium.spec.ts +25 -18
- package/src/TabList/__tests__/Tabs.a11y.chromium.spec.ts +19 -12
- package/src/TextInput/__tests__/TextInput.a11y.chromium.spec.ts +21 -14
- package/src/ToggleButton/__tests__/ToggleButton.a11y.chromium.spec.ts +18 -11
- package/src/Tokenizer/Tokenizer.test.tsx +87 -0
- package/src/Tokenizer/Tokenizer.tsx +31 -3
- package/src/Toolbar/Toolbar.test.tsx +131 -4
- package/src/Toolbar/Toolbar.tsx +3 -1
- package/src/TreeList/TreeList.spec.md +33 -8
- package/src/TreeList/TreeList.test.tsx +72 -0
- package/src/TreeList/TreeList.tsx +10 -6
- package/src/TreeList/TreeListItem.tsx +5 -0
- package/src/TreeList/__tests__/TreeList.a11y.chromium.spec.ts +125 -0
- package/src/TreeList/__tests__/TreeList.a11y.renders.tsx +44 -0
- package/src/TreeList/__tests__/TreeList.a11y.states.ts +164 -0
- package/src/TreeList/__tests__/TreeList.a11y.test.tsx +138 -0
- package/src/TreeList/__tests__/TreeListRowNames.a11y.chromium.spec.ts +82 -0
- package/src/hooks/useScrollOverflow.test.tsx +222 -0
- package/src/hooks/useScrollOverflow.ts +55 -13
- package/src/hooks/useTreeFocus.test.tsx +37 -0
- package/src/hooks/useTreeFocus.ts +55 -2
- package/src/i18n/generated-locales/en.generated.ts +3 -0
- package/src/i18n/generated-locales/pseudo.generated.ts +3 -0
- package/src/reset.a11y.chromium.spec.ts +9 -1
- package/src/reset.css +8 -3
- package/src/reset.until-found.a11y.browser.spec.ts +75 -0
- package/src/reset.until-found.test.ts +32 -0
- package/src/stylexValidation.source-build.test.mjs +42 -0
- package/src/theme/syntax/SyntaxTheme.tsx +46 -5
- package/src/theme/syntax/defineSyntaxTheme.diff.test.ts +91 -0
- package/src/theme/syntax/defineSyntaxTheme.ts +75 -15
- package/src/theme/syntax/index.ts +5 -1
- package/src/theme/syntax/tokens.ts +32 -2
- package/src/theme/syntax/useSyntaxTheme.diff.test.tsx +40 -0
- package/src/utils/plainDate.test.ts +147 -0
- package/src/utils/plainDate.ts +75 -10
|
@@ -31,8 +31,8 @@ test.afterAll(async () => {
|
|
|
31
31
|
|
|
32
32
|
const STORY = 'core-dropdownmenu--header-and-footer';
|
|
33
33
|
// The trigger button only: the sub-menu row inside the menu also carries
|
|
34
|
-
// aria-haspopup
|
|
35
|
-
const TRIGGER = '#storybook-root button[aria-haspopup="
|
|
34
|
+
// aria-haspopup. With a header and footer the trigger opens a dialog.
|
|
35
|
+
const TRIGGER = '#storybook-root button[aria-haspopup="dialog"]';
|
|
36
36
|
|
|
37
37
|
async function openMenu(page: Page) {
|
|
38
38
|
await page.goto(`${storybook.origin}/iframe.html?id=${STORY}&viewMode=story`);
|
|
@@ -76,7 +76,7 @@ test('typing into the header field filters without moving to a row', async ({
|
|
|
76
76
|
await expect(page.getByRole('menuitem')).toHaveText(['Design', 'Move to']);
|
|
77
77
|
});
|
|
78
78
|
|
|
79
|
-
test('Tab walks header, menu, footer,
|
|
79
|
+
test('Tab walks header, menu, footer, and wraps at both ends', async ({
|
|
80
80
|
page,
|
|
81
81
|
}) => {
|
|
82
82
|
const menu = await openMenu(page);
|
|
@@ -97,9 +97,44 @@ test('Tab walks header, menu, footer, then closes to the trigger', async ({
|
|
|
97
97
|
await expect(page.getByTestId('created')).toHaveText('1');
|
|
98
98
|
await expect(menu).toBeVisible();
|
|
99
99
|
|
|
100
|
+
// Past the footer's last control, Tab wraps to the header; before the
|
|
101
|
+
// header's first, Shift+Tab wraps to the footer. The dialog stays open.
|
|
100
102
|
await page.keyboard.press('Tab');
|
|
101
|
-
await expect(
|
|
102
|
-
await expect(
|
|
103
|
+
await expect(field).toBeFocused();
|
|
104
|
+
await expect(menu).toBeVisible();
|
|
105
|
+
await page.keyboard.press('Shift+Tab');
|
|
106
|
+
await expect(create).toBeFocused();
|
|
107
|
+
await expect(menu).toBeVisible();
|
|
108
|
+
await expect(page.locator(TRIGGER)).toHaveAttribute('aria-expanded', 'true');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('the popover is a modal dialog around the menu, and the trigger points at it', async ({
|
|
112
|
+
page,
|
|
113
|
+
}) => {
|
|
114
|
+
const menu = await openMenu(page);
|
|
115
|
+
const dialog = page.getByRole('dialog', {name: 'Labels'});
|
|
116
|
+
await expect(dialog).toBeVisible();
|
|
117
|
+
await expect(dialog).toHaveAttribute('aria-modal', 'true');
|
|
118
|
+
await expect(
|
|
119
|
+
dialog.getByRole('menu', {name: 'Labels', exact: true}),
|
|
120
|
+
).toBeVisible();
|
|
121
|
+
await expect(
|
|
122
|
+
dialog.getByRole('textbox', {name: 'Filter labels'}),
|
|
123
|
+
).toBeVisible();
|
|
124
|
+
await expect(
|
|
125
|
+
dialog.getByRole('button', {name: 'Create label'}),
|
|
126
|
+
).toBeVisible();
|
|
127
|
+
const trigger = page.locator(TRIGGER);
|
|
128
|
+
await expect(trigger).toHaveAttribute('aria-haspopup', 'dialog');
|
|
129
|
+
const controlsDialog = await trigger.evaluate(el => {
|
|
130
|
+
const target = document.getElementById(
|
|
131
|
+
el.getAttribute('aria-controls') ?? '',
|
|
132
|
+
);
|
|
133
|
+
const dialogEl = document.querySelector('[role="dialog"]');
|
|
134
|
+
return target != null && dialogEl != null && target.contains(dialogEl);
|
|
135
|
+
});
|
|
136
|
+
expect(controlsDialog).toBe(true);
|
|
137
|
+
await expect(menu).toBeFocused();
|
|
103
138
|
});
|
|
104
139
|
|
|
105
140
|
test('Shift+Tab from a row goes straight to the header', async ({page}) => {
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
type InputClearButtonProps,
|
|
26
26
|
} from './InputClearButton';
|
|
27
27
|
import {Icon, registerIcons, resetIcons} from '../Icon';
|
|
28
|
+
import {InternationalizationProvider} from '../i18n';
|
|
28
29
|
import {defineTheme} from '../theme/defineTheme';
|
|
29
30
|
import {generateThemeCSS} from '../theme/generateThemeRules';
|
|
30
31
|
import {declaredValue} from '../__tests__/stylexDeclarations';
|
|
@@ -95,13 +96,23 @@ describe('InputClearButton public props', () => {
|
|
|
95
96
|
});
|
|
96
97
|
|
|
97
98
|
describe('InputClearButton', () => {
|
|
98
|
-
it('
|
|
99
|
+
it('shows a generic "Clear" tooltip while the accessible name keeps the field label', async () => {
|
|
100
|
+
// A long field label repeated in the tooltip reads as awkward copy
|
|
101
|
+
// ("Clear Assigned engineering partner"); the field's own label already
|
|
102
|
+
// sits beside the glyph. The contextual name stays for assistive tech.
|
|
99
103
|
const showPopover = vi.spyOn(HTMLElement.prototype, 'showPopover');
|
|
100
|
-
render(
|
|
104
|
+
render(
|
|
105
|
+
<InputClearButton
|
|
106
|
+
label="Clear Assigned engineering partner"
|
|
107
|
+
onClick={() => {}}
|
|
108
|
+
/>,
|
|
109
|
+
);
|
|
101
110
|
|
|
102
|
-
const button = screen.getByRole('button', {
|
|
111
|
+
const button = screen.getByRole('button', {
|
|
112
|
+
name: 'Clear Assigned engineering partner',
|
|
113
|
+
});
|
|
103
114
|
const tooltip = screen.getByRole('tooltip', {hidden: true});
|
|
104
|
-
expect(tooltip).toHaveTextContent(/^Clear
|
|
115
|
+
expect(tooltip).toHaveTextContent(/^Clear$/);
|
|
105
116
|
expect(button.getAttribute('aria-describedby')).toBe(tooltip.id);
|
|
106
117
|
|
|
107
118
|
fireEvent.mouseEnter(button);
|
|
@@ -109,6 +120,20 @@ describe('InputClearButton', () => {
|
|
|
109
120
|
showPopover.mockRestore();
|
|
110
121
|
});
|
|
111
122
|
|
|
123
|
+
it('localizes the tooltip through the catalog', () => {
|
|
124
|
+
render(
|
|
125
|
+
<InternationalizationProvider
|
|
126
|
+
locale="en"
|
|
127
|
+
overrides={{en: {'@astryx.inputClearButton.tooltip': 'Leeren'}}}>
|
|
128
|
+
<InputClearButton label="Suche leeren" onClick={() => {}} />
|
|
129
|
+
</InternationalizationProvider>,
|
|
130
|
+
);
|
|
131
|
+
expect(screen.getByRole('button', {name: 'Suche leeren'})).toBeTruthy();
|
|
132
|
+
expect(screen.getByRole('tooltip', {hidden: true})).toHaveTextContent(
|
|
133
|
+
/^Leeren$/,
|
|
134
|
+
);
|
|
135
|
+
});
|
|
136
|
+
|
|
112
137
|
it('renders a real button with the given accessible label', () => {
|
|
113
138
|
render(<InputClearButton label="Clear" onClick={() => {}} />);
|
|
114
139
|
const button = screen.getByRole('button', {name: 'Clear'});
|
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file InputClearButton.tsx
|
|
7
|
-
* @input Uses React, Button, and
|
|
8
|
-
* @output Exports the public InputClearButton and an internal popup-aware variant
|
|
9
|
-
*
|
|
7
|
+
* @input Uses React, Button, Icon, and the i18n translator
|
|
8
|
+
* @output Exports the public InputClearButton and an internal popup-aware variant.
|
|
9
|
+
* Both show the localized generic "Clear" tooltip and keep the caller's
|
|
10
|
+
* contextual label as the accessible name.
|
|
10
11
|
* @position Shared primitive. Every input that renders a clear affordance —
|
|
11
12
|
* TextInput, NumberInput, TimeInput, DateInput, DateTimeInput,
|
|
12
13
|
* DateRangeInput, Selector, MultiSelector, Typeahead, Tokenizer, FileInput —
|
|
@@ -19,6 +20,7 @@ import type {ReactNode} from 'react';
|
|
|
19
20
|
import * as stylex from '@stylexjs/stylex';
|
|
20
21
|
import {Button} from '../Button';
|
|
21
22
|
import {Icon} from '../Icon';
|
|
23
|
+
import {useTranslator} from '../i18n';
|
|
22
24
|
import {themeProps} from '../utils/themeProps';
|
|
23
25
|
|
|
24
26
|
const styles = stylex.create({
|
|
@@ -67,7 +69,10 @@ const styles = stylex.create({
|
|
|
67
69
|
});
|
|
68
70
|
|
|
69
71
|
export interface InputClearButtonProps {
|
|
70
|
-
/**
|
|
72
|
+
/**
|
|
73
|
+
* Contextual accessible name for the button, such as "Clear Search". The
|
|
74
|
+
* visible tooltip is always the localized generic "Clear".
|
|
75
|
+
*/
|
|
71
76
|
label: string;
|
|
72
77
|
onClick: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
|
73
78
|
xstyle?: stylex.StyleXStyles;
|
|
@@ -83,10 +88,11 @@ interface InternalInputClearButtonProps extends InputClearButtonProps {
|
|
|
83
88
|
type InputClearButtonRenderProps = InputClearButtonProps &
|
|
84
89
|
Partial<
|
|
85
90
|
Pick<InternalInputClearButtonProps, 'onPointerDown' | 'onClickCapture'>
|
|
86
|
-
|
|
91
|
+
> & {tooltip: string};
|
|
87
92
|
|
|
88
93
|
function renderInputClearButton({
|
|
89
94
|
label,
|
|
95
|
+
tooltip,
|
|
90
96
|
onClick,
|
|
91
97
|
onPointerDown,
|
|
92
98
|
onClickCapture,
|
|
@@ -100,7 +106,11 @@ function renderInputClearButton({
|
|
|
100
106
|
variant="ghost"
|
|
101
107
|
size="sm"
|
|
102
108
|
label={label}
|
|
103
|
-
tooltip
|
|
109
|
+
// The field's label already sits beside the glyph, so the tooltip names
|
|
110
|
+
// only the action; repeating a long label reads as awkward copy. The
|
|
111
|
+
// contextual label stays the accessible name, which still contains
|
|
112
|
+
// the visible word (WCAG 2.5.3, label in name).
|
|
113
|
+
tooltip={tooltip}
|
|
104
114
|
className={buttonTargetClassName}
|
|
105
115
|
icon={
|
|
106
116
|
<Icon
|
|
@@ -128,14 +138,22 @@ function renderInputClearButton({
|
|
|
128
138
|
}
|
|
129
139
|
|
|
130
140
|
export function InputClearButton(props: InputClearButtonProps): ReactNode {
|
|
131
|
-
|
|
141
|
+
const t = useTranslator();
|
|
142
|
+
return renderInputClearButton({
|
|
143
|
+
...props,
|
|
144
|
+
tooltip: t('@astryx.inputClearButton.tooltip'),
|
|
145
|
+
});
|
|
132
146
|
}
|
|
133
147
|
|
|
134
148
|
/** Internal variant used while popup-aware clear behavior is rolled out. */
|
|
135
149
|
export function InternalInputClearButton(
|
|
136
150
|
props: InternalInputClearButtonProps,
|
|
137
151
|
): ReactNode {
|
|
138
|
-
|
|
152
|
+
const t = useTranslator();
|
|
153
|
+
return renderInputClearButton({
|
|
154
|
+
...props,
|
|
155
|
+
tooltip: t('@astryx.inputClearButton.tooltip'),
|
|
156
|
+
});
|
|
139
157
|
}
|
|
140
158
|
|
|
141
159
|
InputClearButton.displayName = 'InputClearButton';
|
|
@@ -168,26 +168,18 @@ async function runState(
|
|
|
168
168
|
});
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const results: BindingResult[] = [];
|
|
177
|
-
for (const state of CORE_STATUS_MESSAGE_BINDING_STATES) {
|
|
178
|
-
results.push(await runState(page, cdp, state));
|
|
179
|
-
}
|
|
180
|
-
expect(neverExercised(results)).toEqual([]);
|
|
181
|
-
expect(
|
|
182
|
-
unmatchedKnownFailures(CORE_STATUS_MESSAGE_KNOWN_FAILURES, results),
|
|
183
|
-
).toEqual([]);
|
|
184
|
-
});
|
|
171
|
+
// Each state's result, recorded by its own test below so the whole-suite check
|
|
172
|
+
// at the end reads it instead of measuring every state a second time. A state
|
|
173
|
+
// whose test did not run in this worker (a filtered run, or a worker restarted
|
|
174
|
+
// after a failure) is measured by the whole-suite check itself.
|
|
175
|
+
const recorded = new Map<string, BindingResult>();
|
|
185
176
|
|
|
186
177
|
for (const state of CORE_STATUS_MESSAGE_BINDING_STATES) {
|
|
187
178
|
test(`${state.binding} [${state.id}] — ${state.summary}`, async ({page}) => {
|
|
188
179
|
test.setTimeout(2 * 60 * 1000);
|
|
189
180
|
const cdp = await page.context().newCDPSession(page);
|
|
190
181
|
const result = await runState(page, cdp, state);
|
|
182
|
+
recorded.set(state.id, result);
|
|
191
183
|
const report = summarize(STATUS_MESSAGE_PATTERN, [result]);
|
|
192
184
|
// eslint-disable-next-line no-console -- the report is this run's artifact
|
|
193
185
|
console.log(formatReport(report));
|
|
@@ -196,3 +188,18 @@ for (const state of CORE_STATUS_MESSAGE_BINDING_STATES) {
|
|
|
196
188
|
expect(report.counts.unexpectedPass).toBe(0);
|
|
197
189
|
});
|
|
198
190
|
}
|
|
191
|
+
|
|
192
|
+
test('every expectation is exercised by at least one Core binding state', async ({
|
|
193
|
+
page,
|
|
194
|
+
}) => {
|
|
195
|
+
test.setTimeout(4 * 60 * 1000);
|
|
196
|
+
const cdp = await page.context().newCDPSession(page);
|
|
197
|
+
const results: BindingResult[] = [];
|
|
198
|
+
for (const state of CORE_STATUS_MESSAGE_BINDING_STATES) {
|
|
199
|
+
results.push(recorded.get(state.id) ?? (await runState(page, cdp, state)));
|
|
200
|
+
}
|
|
201
|
+
expect(neverExercised(results)).toEqual([]);
|
|
202
|
+
expect(
|
|
203
|
+
unmatchedKnownFailures(CORE_STATUS_MESSAGE_KNOWN_FAILURES, results),
|
|
204
|
+
).toEqual([]);
|
|
205
|
+
});
|
|
@@ -17,7 +17,7 @@ exports[`FormLayout > matches snapshot for horizontal direction 1`] = `
|
|
|
17
17
|
|
|
18
18
|
exports[`FormLayout > matches snapshot for horizontal-labels direction 1`] = `
|
|
19
19
|
<div
|
|
20
|
-
class="astryx-form-layout FormLayout__styles.base xdt5ytf FormLayout__styles.horizontalLabels xrvj5dj x1pmbctz xlaq8a2 x7a106z xedohl4 x1rpgqan x1a1jff"
|
|
20
|
+
class="astryx-form-layout FormLayout__styles.base xdt5ytf FormLayout__styles.horizontalLabels xrvj5dj x1pmbctz xlaq8a2 x7a106z xedohl4 x1rpgqan x1a1jff x1q32nj3"
|
|
21
21
|
data-direction="horizontal-labels"
|
|
22
22
|
data-testid="layout"
|
|
23
23
|
>
|
package/src/Grid/Grid.doc.mjs
CHANGED
|
@@ -27,10 +27,11 @@ export const docs = {
|
|
|
27
27
|
usage: {
|
|
28
28
|
anatomy,
|
|
29
29
|
description:
|
|
30
|
-
'A CSS grid layout container for arranging children in rows and columns. Use Grid for card galleries, dashboards, and any multi-column layout.
|
|
30
|
+
'A CSS grid layout container for arranging children in rows and columns. Use Grid for card galleries, dashboards, and any multi-column layout. For columns that reflow on small screens, use `columns={{minWidth, max}}`; a numeric `columns` is exactly N columns at every width and never reflows.',
|
|
31
31
|
bestPractices: [
|
|
32
|
-
{ guidance: true, description: '
|
|
33
|
-
{ guidance:
|
|
32
|
+
{ guidance: true, description: 'Default to responsive columns for anything that must survive a phone: `columns={{minWidth: 240, max: 4}}` renders at most 4 columns, each at least 240px, and drops to one full-width column when narrow.' },
|
|
33
|
+
{ guidance: false, description: 'Use `columns={4}` for a row that must work on small screens; a number never reflows and renders four narrow tracks on a phone. Reserve numbers for rows that must stay fixed, such as a 7-day week.' },
|
|
34
|
+
{ guidance: true, description: 'Always pair `minWidth` with `max`: `max` caps the count on wide screens and lets the last column shrink to a narrow container; `{minWidth}` alone overflows a phone narrower than `minWidth`.' },
|
|
34
35
|
{ guidance: true, description: 'Use `repeat: \'fill\'` (the default) for consistent item widths. Use `\'fit\'` when items should stretch to fill leftover space.' },
|
|
35
36
|
{ guidance: false, description: 'Write manual CSS grid; Grid handles spacing and responsive behavior for you.' },
|
|
36
37
|
{ guidance: false, description: 'Use `HStack` with wrapping for grids; use Grid instead.' },
|
|
@@ -54,12 +55,12 @@ export const docs = {
|
|
|
54
55
|
],
|
|
55
56
|
},
|
|
56
57
|
},
|
|
57
|
-
description: 'Grid container with fixed or responsive columns.',
|
|
58
|
+
description: 'Grid container with fixed or responsive columns; `{minWidth, max}` reflows, a number never does.',
|
|
58
59
|
props: [
|
|
59
60
|
{
|
|
60
61
|
name: 'columns',
|
|
61
62
|
type: "number | {minWidth: number, max?: number, repeat?: 'fill' | 'fit'}",
|
|
62
|
-
description: 'Column configuration.
|
|
63
|
+
description: 'Column configuration. A number is exactly N equal columns at every width and never reflows (e.g. `columns={3}`); for columns that reflow on small screens, use `{minWidth, max}`. Use an object for responsive columns: `minWidth` sets the minimum column width in px, `repeat` controls track behavior (`"fill"` preserves empty tracks for consistent widths, `"fit"` collapses empty tracks so items stretch; defaults to `"fill"`), and `max` caps the maximum number of columns.',
|
|
63
64
|
},
|
|
64
65
|
{
|
|
65
66
|
name: 'width',
|
|
@@ -129,10 +130,11 @@ export const docsZh = {
|
|
|
129
130
|
usage: {
|
|
130
131
|
anatomy,
|
|
131
132
|
description:
|
|
132
|
-
'A CSS grid layout container for arranging children in rows and columns. Use Grid for card galleries, dashboards, and any multi-column layout.
|
|
133
|
+
'A CSS grid layout container for arranging children in rows and columns. Use Grid for card galleries, dashboards, and any multi-column layout. For columns that reflow on small screens, use `columns={{minWidth, max}}`; a numeric `columns` is exactly N columns at every width and never reflows.',
|
|
133
134
|
bestPractices: [
|
|
134
|
-
{ guidance: true, description: '
|
|
135
|
-
{ guidance:
|
|
135
|
+
{ guidance: true, description: 'Default to responsive columns for anything that must survive a phone: `columns={{minWidth: 240, max: 4}}` renders at most 4 columns, each at least 240px, and drops to one full-width column when narrow.' },
|
|
136
|
+
{ guidance: false, description: 'Use `columns={4}` for a row that must work on small screens; a number never reflows and renders four narrow tracks on a phone. Reserve numbers for rows that must stay fixed, such as a 7-day week.' },
|
|
137
|
+
{ guidance: true, description: 'Always pair `minWidth` with `max`: `max` caps the count on wide screens and lets the last column shrink to a narrow container; `{minWidth}` alone overflows a phone narrower than `minWidth`.' },
|
|
136
138
|
{ guidance: true, description: 'Use `repeat: \'fill\'` (the default) for consistent item widths. Use `\'fit\'` when items should stretch to fill leftover space.' },
|
|
137
139
|
{ guidance: false, description: 'Write manual CSS grid; Grid handles spacing and responsive behavior for you.' },
|
|
138
140
|
{ guidance: false, description: 'Use `HStack` with wrapping for grids; use Grid instead.' },
|
|
@@ -146,10 +148,11 @@ export const docsDense = {
|
|
|
146
148
|
description: 'CSS Grid-based layout w/ responsive column support.',
|
|
147
149
|
usage: {
|
|
148
150
|
anatomy,
|
|
149
|
-
description: 'A CSS grid layout container for arranging children in rows and columns. Use Grid for card galleries, dashboards, and any multi-column layout.
|
|
151
|
+
description: 'A CSS grid layout container for arranging children in rows and columns. Use Grid for card galleries, dashboards, and any multi-column layout. For columns that reflow on small screens, use `columns={{minWidth, max}}`; a numeric `columns` is exactly N columns at every width and never reflows.',
|
|
150
152
|
bestPractices: [
|
|
151
|
-
{ guidance: true, description: '
|
|
152
|
-
{ guidance:
|
|
153
|
+
{ guidance: true, description: 'Default to responsive columns for anything that must survive a phone: columns={{minWidth: 240, max: 4}} = at most 4 columns, each >= 240px, one full-width column when narrow.' },
|
|
154
|
+
{ guidance: false, description: 'Use columns={4} for rows that must work on small screens; a number never reflows (4 narrow tracks on a phone). Numbers only for rows that must stay fixed, e.g. a 7-day week.' },
|
|
155
|
+
{ guidance: true, description: 'Always pair minWidth with max: max caps the count on wide screens and lets the last column shrink to a narrow container; {minWidth} alone overflows a phone narrower than minWidth.' },
|
|
153
156
|
{ guidance: true, description: 'Use repeat: \'fill\' (the default) for consistent item widths. Use \'fit\' when items should stretch to fill leftover space.' },
|
|
154
157
|
{ guidance: false, description: 'Write manual CSS grid; Grid handles spacing and responsive behavior for you.' },
|
|
155
158
|
{ guidance: false, description: 'Use HStack with wrapping for grids; use Grid instead.' },
|
package/src/Grid/Grid.spec.md
CHANGED
|
@@ -44,36 +44,6 @@ layout, targets, or public API.
|
|
|
44
44
|
|
|
45
45
|
Consumer migration instructions belong in consumer docs and release notes.
|
|
46
46
|
|
|
47
|
-
### DEP-0010 — Deprecate the fixed meaning of numeric `Grid` `columns`
|
|
48
|
-
|
|
49
|
-
| Field | Value |
|
|
50
|
-
| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
51
|
-
| id | `DEP-0010` |
|
|
52
|
-
| cleanup | `CLN-0019` |
|
|
53
|
-
| package | `@astryxdesign/core` |
|
|
54
|
-
| surface | `Grid` `columns={N}`, which renders exactly N equal tracks (`repeat(N, 1fr)`) |
|
|
55
|
-
| old contract | A numeric `columns` value is an explicit equal-width track count at every width |
|
|
56
|
-
| replacement | `columns={{count: N, isFixed: true}}` keeps exactly N tracks |
|
|
57
|
-
| direct authority | `component:Grid`, with `family:layout-primitives` representative matrix row "Grid / fixed columns". Owners: `cixzhang` (`component:Grid`); `cixzhang`, `imdreamrunner` (`family:layout-primitives`) |
|
|
58
|
-
| warning | Consumer docs and the release notes name `DEP-0010` and the fixed shape. No runtime warning (`spec:AST-017` FR29) |
|
|
59
|
-
| migration | Mechanical where the fixed count is wanted: rewrite `columns={N}` to `columns={{count: N, isFixed: true}}`. Keeping the number accepts the new reflow |
|
|
60
|
-
| codemod | None yet. A preserving codemod is mechanical, but it would opt every caller out of the change it exists to deliver |
|
|
61
|
-
| downstream | In-repo fixed rows such as week rows use the fixed shape |
|
|
62
|
-
| transition | no prior transition |
|
|
63
|
-
| state | `proposed` |
|
|
64
|
-
| open decision | This record is draft and lists new responsive behavior as a non-goal; the family matrix pins numeric columns as fixed. Both need an owner ruling |
|
|
65
|
-
| target plan | `CLN-0019` in the next minor release |
|
|
66
|
-
|
|
67
|
-
### CLN-0019 — Numeric `columns` means at most N columns
|
|
68
|
-
|
|
69
|
-
| Field | Value |
|
|
70
|
-
| --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
71
|
-
| id | `CLN-0019` |
|
|
72
|
-
| lifecycle | `DEP-0010` |
|
|
73
|
-
| delta | A numeric `columns` value renders up to N columns of at least 12rem and fewer when the grid is narrower; a `GridSpan` in such a grid takes the full row once the grid has fewer columns than it spans |
|
|
74
|
-
| rollback | Restore `repeat(N, 1fr)` for numeric `columns` from the final-patch baseline |
|
|
75
|
-
| state | `pending` — lands in a declared minor with its `[breaking]` Changeset |
|
|
76
|
-
|
|
77
47
|
## Ownership boundary
|
|
78
48
|
|
|
79
49
|
**Owns**
|
|
@@ -110,6 +80,43 @@ in the canonical `Grid.doc.mjs` and the subordinate `GridSpan.doc.mjs`;
|
|
|
110
80
|
- A Spanning item may span columns, rows, both, or neither while retaining the
|
|
111
81
|
same target.
|
|
112
82
|
|
|
83
|
+
### Responsive contract
|
|
84
|
+
|
|
85
|
+
The `columns` prop decides how many column tracks Grid lays out and whether
|
|
86
|
+
that count changes with the container's width. Other inputs change the result
|
|
87
|
+
only by changing what `columns` has to fit: `width` and `maxWidth` set the
|
|
88
|
+
container width a reflowing shape measures against; an `xstyle`, `className`,
|
|
89
|
+
or `style` that sets the track template (including under a media query)
|
|
90
|
+
replaces the computed one; and a numeric GridSpan wider than the grid's tracks
|
|
91
|
+
makes the browser add implicit tracks to the whole grid, which later items flow
|
|
92
|
+
into. No other prop changes the count.
|
|
93
|
+
|
|
94
|
+
| `columns` | Behavior | Reflows |
|
|
95
|
+
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
|
|
96
|
+
| `N` (number) | Exactly N tracks that share the row equally at every container width; a child wider than its share widens its track and can push the grid past a narrow container. | never |
|
|
97
|
+
| `{minWidth}` | As many tracks as fit while each stays at least `minWidth` px wide. A container narrower than `minWidth` keeps one `minWidth`-wide track that overflows it. Empty tracks keep their width. | yes |
|
|
98
|
+
| `{minWidth, repeat: 'fit'}` | As above, and empty tracks collapse so the present items stretch across the row. | yes |
|
|
99
|
+
| `{minWidth, max}` | Up to `max` tracks, each at least `minWidth` px and never narrower than an equal share of the row; fewer tracks as the container narrows, down to one track that shrinks to the container rather than overflowing it. `repeat: 'fit'` applies the same way. | yes |
|
|
100
|
+
| omitted, `0`, or negative | One track. | never |
|
|
101
|
+
|
|
102
|
+
- `gap` and `columnGap` count toward the width a reflowing shape has to fit, so
|
|
103
|
+
the full count is reached only when the container has room for the tracks and
|
|
104
|
+
the gaps between them. They never change the count of a numeric `columns`.
|
|
105
|
+
- `height`, `minHeight`, `rowHeight`, `align`, and `justify` size or place the
|
|
106
|
+
container and its items and do not change the column count.
|
|
107
|
+
- GridSpan spans `k` tracks, or the whole row for `'full'`. `'full'` never
|
|
108
|
+
adds tracks. A numeric span wider than the grid's current tracks makes the
|
|
109
|
+
browser add implicit tracks to the grid for the remainder; they persist for
|
|
110
|
+
every row, items placed before and after the span flow into them, and once
|
|
111
|
+
they exist `'full'` covers only the explicit tracks. In a reflowing grid keep
|
|
112
|
+
spans within the smallest count the grid can reach or use `'full'`.
|
|
113
|
+
- For columns that reflow on small screens, use `{minWidth, max}`: `max` is
|
|
114
|
+
what lets the last track shrink to a narrow container, so `{minWidth}` alone
|
|
115
|
+
still overflows a phone narrower than `minWidth`. A numeric `columns` never
|
|
116
|
+
reflows, so a four-up numeric row on a phone renders four narrow tracks, or
|
|
117
|
+
overflows when a child cannot shrink to its share; that is the fixed
|
|
118
|
+
contract, not a defect.
|
|
119
|
+
|
|
113
120
|
### Representative states
|
|
114
121
|
|
|
115
122
|
| State | Required invariant | Allowed variation |
|
|
@@ -178,8 +185,19 @@ relationship.
|
|
|
178
185
|
|
|
179
186
|
## Decision log
|
|
180
187
|
|
|
181
|
-
|
|
182
|
-
|
|
188
|
+
### DEC-1 — Numeric `columns` stays fixed; reflow uses the existing object form
|
|
189
|
+
|
|
190
|
+
**Decider:** `cixzhang`, `2026-10-11`
|
|
191
|
+
|
|
192
|
+
`columns={N}` means exactly N equal tracks at every width, and a grid that
|
|
193
|
+
reflows on small screens uses `{minWidth, max?, repeat?}`. Two shapes, one
|
|
194
|
+
responsibility each: the number is the fixed contract, the object form is the
|
|
195
|
+
responsive one, and the record and the docs say so where a caller first meets
|
|
196
|
+
`columns`.
|
|
197
|
+
|
|
198
|
+
Rejected: redefining the number as "at most N" (breaks every released numeric
|
|
199
|
+
grid and needs an opt-out shape); a `{count, isFixed}` shape (duplicates the
|
|
200
|
+
number and the `max` cap).
|
|
183
201
|
|
|
184
202
|
## Open questions
|
|
185
203
|
|
package/src/Grid/Grid.tsx
CHANGED
|
@@ -31,7 +31,13 @@ export type GridAlignment = 'start' | 'center' | 'end' | 'stretch';
|
|
|
31
31
|
/**
|
|
32
32
|
* Column configuration for Grid.
|
|
33
33
|
*
|
|
34
|
-
*
|
|
34
|
+
* A number means exactly N equal columns at every width; it never reflows, so
|
|
35
|
+
* `columns={4}` on a phone renders four narrow tracks. For columns that reflow
|
|
36
|
+
* on small screens, use the object form: `{minWidth: 240, max: 4}` renders at
|
|
37
|
+
* most four columns, each at least 240px, and fewer when the container is
|
|
38
|
+
* narrower.
|
|
39
|
+
*
|
|
40
|
+
* - `number` — exactly N equal-width columns (e.g. `columns={3}`); never reflows
|
|
35
41
|
* - `object` — responsive columns based on minimum child width:
|
|
36
42
|
* - `minWidth` — minimum width (px) for each column track
|
|
37
43
|
* - `repeat` — `'fill'` (default) preserves empty tracks for consistent widths;
|
|
@@ -53,9 +59,12 @@ export interface GridProps extends BaseProps<HTMLDivElement> {
|
|
|
53
59
|
/** Ref forwarded to the root element */
|
|
54
60
|
ref?: React.Ref<HTMLDivElement>;
|
|
55
61
|
/**
|
|
56
|
-
* Column configuration.
|
|
57
|
-
*
|
|
58
|
-
*
|
|
62
|
+
* Column configuration. A number is exactly N equal columns and never
|
|
63
|
+
* reflows; for columns that reflow on small screens, use
|
|
64
|
+
* `{minWidth, max}`.
|
|
65
|
+
* - `number` — exactly N equal-width columns at every width
|
|
66
|
+
* - `{minWidth, max?, repeat?}` — responsive columns with a px floor and an
|
|
67
|
+
* optional cap
|
|
59
68
|
*
|
|
60
69
|
* @see GridColumns
|
|
61
70
|
*/
|
|
@@ -18,13 +18,21 @@ import {
|
|
|
18
18
|
beforeEach,
|
|
19
19
|
afterAll,
|
|
20
20
|
} from 'vitest';
|
|
21
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
render,
|
|
23
|
+
renderHook,
|
|
24
|
+
screen,
|
|
25
|
+
fireEvent,
|
|
26
|
+
waitFor,
|
|
27
|
+
act,
|
|
28
|
+
} from '@testing-library/react';
|
|
22
29
|
import {renderToString} from 'react-dom/server';
|
|
23
30
|
import {hydrateRoot} from 'react-dom/client';
|
|
24
31
|
import {StrictMode} from 'react';
|
|
25
32
|
import {Button} from '../Button/Button';
|
|
26
33
|
import {Theme, defineTheme} from '../theme';
|
|
27
34
|
import {HoverCard} from './HoverCard';
|
|
35
|
+
import {useHoverCard} from './useHoverCard';
|
|
28
36
|
import {__resetInteractionModalityForTest} from '../utils/interactionModality';
|
|
29
37
|
|
|
30
38
|
// Store original matches to restore later
|
|
@@ -64,6 +72,43 @@ beforeEach(() => {
|
|
|
64
72
|
vi.mocked(HTMLElement.prototype.hidePopover).mockClear();
|
|
65
73
|
});
|
|
66
74
|
|
|
75
|
+
describe('useHoverCard identity', () => {
|
|
76
|
+
it('keeps callback refs stable across unchanged rerenders', () => {
|
|
77
|
+
const {result, rerender} = renderHook(() => useHoverCard());
|
|
78
|
+
const firstRef = result.current.ref;
|
|
79
|
+
const firstInteractionRef = result.current.interactionRef;
|
|
80
|
+
|
|
81
|
+
rerender();
|
|
82
|
+
|
|
83
|
+
expect(result.current.ref).toBe(firstRef);
|
|
84
|
+
expect(result.current.interactionRef).toBe(firstInteractionRef);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('does not rebind trigger listeners on rerender', () => {
|
|
88
|
+
function Harness() {
|
|
89
|
+
const card = useHoverCard();
|
|
90
|
+
return (
|
|
91
|
+
<div>
|
|
92
|
+
<span ref={card.ref}>Trigger</span>
|
|
93
|
+
{card.renderHoverCard(<div>Card</div>)}
|
|
94
|
+
</div>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
const {getByText, rerender} = render(<Harness />);
|
|
98
|
+
const trigger = getByText('Trigger');
|
|
99
|
+
const added: string[] = [];
|
|
100
|
+
const add = trigger.addEventListener.bind(trigger);
|
|
101
|
+
trigger.addEventListener = ((type: string, ...rest: unknown[]) => {
|
|
102
|
+
added.push(type);
|
|
103
|
+
return (add as (...args: unknown[]) => void)(type, ...rest);
|
|
104
|
+
}) as typeof trigger.addEventListener;
|
|
105
|
+
|
|
106
|
+
rerender(<Harness />);
|
|
107
|
+
|
|
108
|
+
expect(added).toEqual([]);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
67
112
|
describe('HoverCard', () => {
|
|
68
113
|
it('renders trigger element', () => {
|
|
69
114
|
render(
|