@qijenchen/design-system 0.1.0-beta.95 → 0.1.0-beta.99
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/README.md +1 -0
- package/dist/components/AccountMenu/account-menu.d.ts +86 -0
- package/dist/components/AccountMenu/account-menu.d.ts.map +1 -0
- package/dist/components/AccountMenu/account-menu.js +64 -0
- package/dist/components/AccountMenu/account-menu.js.map +1 -0
- package/dist/components/AccountMenu/index.d.ts +2 -0
- package/dist/components/AccountMenu/index.d.ts.map +1 -0
- package/dist/components/AccountMenu/index.js +6 -0
- package/dist/components/AccountMenu/index.js.map +1 -0
- package/dist/components/AppShell/_demo-helpers.d.ts.map +1 -1
- package/dist/components/AppShell/app-shell.d.ts.map +1 -1
- package/dist/components/AppShell/app-shell.js +2 -2
- package/dist/components/AppShell/app-shell.js.map +1 -1
- package/dist/components/Badge/badge.d.ts +1 -1
- package/dist/components/Button/button.d.ts +3 -3
- package/dist/components/Button/button.d.ts.map +1 -1
- package/dist/components/Button/button.js +3 -2
- package/dist/components/Button/button.js.map +1 -1
- package/dist/components/Calendar/calendar.d.ts +10 -21
- package/dist/components/Calendar/calendar.d.ts.map +1 -1
- package/dist/components/Calendar/calendar.js +52 -59
- package/dist/components/Calendar/calendar.js.map +1 -1
- package/dist/components/Combobox/combobox.d.ts.map +1 -1
- package/dist/components/Combobox/combobox.js +1 -0
- package/dist/components/Combobox/combobox.js.map +1 -1
- package/dist/components/Command/command.d.ts.map +1 -1
- package/dist/components/Command/command.js +3 -1
- package/dist/components/Command/command.js.map +1 -1
- package/dist/components/DataTable/cell-registry.d.ts.map +1 -1
- package/dist/components/DataTable/cell-registry.js +1 -5
- package/dist/components/DataTable/cell-registry.js.map +1 -1
- package/dist/components/DataTable/data-table-filter-group.d.ts +6 -2
- package/dist/components/DataTable/data-table-filter-group.d.ts.map +1 -1
- package/dist/components/DataTable/data-table-filter-group.js +32 -27
- package/dist/components/DataTable/data-table-filter-group.js.map +1 -1
- package/dist/components/DataTable/data-table-filter-labels.d.ts +44 -0
- package/dist/components/DataTable/data-table-filter-labels.d.ts.map +1 -0
- package/dist/components/DataTable/data-table-filter-labels.js +55 -0
- package/dist/components/DataTable/data-table-filter-labels.js.map +1 -0
- package/dist/components/DataTable/data-table-filter-panel.d.ts +11 -0
- package/dist/components/DataTable/data-table-filter-panel.d.ts.map +1 -1
- package/dist/components/DataTable/data-table-filter-panel.js +33 -10
- package/dist/components/DataTable/data-table-filter-panel.js.map +1 -1
- package/dist/components/DataTable/data-table-filter-value-picker.d.ts +4 -1
- package/dist/components/DataTable/data-table-filter-value-picker.d.ts.map +1 -1
- package/dist/components/DataTable/data-table-filter-value-picker.js +22 -8
- package/dist/components/DataTable/data-table-filter-value-picker.js.map +1 -1
- package/dist/components/DataTable/data-table.d.ts.map +1 -1
- package/dist/components/DataTable/data-table.js +5 -4
- package/dist/components/DataTable/data-table.js.map +1 -1
- package/dist/components/DataTable/index.js +2 -0
- package/dist/components/DataTable/index.js.map +1 -1
- package/dist/components/DatePicker/date-picker.d.ts.map +1 -1
- package/dist/components/DatePicker/date-picker.js +187 -156
- package/dist/components/DatePicker/date-picker.js.map +1 -1
- package/dist/components/Dialog/dialog.d.ts +1 -1
- package/dist/components/Dialog/dialog.d.ts.map +1 -1
- package/dist/components/Dialog/dialog.js +4 -2
- package/dist/components/Dialog/dialog.js.map +1 -1
- package/dist/components/DropdownMenu/dropdown-menu.d.ts +4 -1
- package/dist/components/DropdownMenu/dropdown-menu.d.ts.map +1 -1
- package/dist/components/DropdownMenu/dropdown-menu.js +46 -14
- package/dist/components/DropdownMenu/dropdown-menu.js.map +1 -1
- package/dist/components/Field/field-context.d.ts +12 -0
- package/dist/components/Field/field-context.d.ts.map +1 -1
- package/dist/components/Field/field-context.js +10 -1
- package/dist/components/Field/field-context.js.map +1 -1
- package/dist/components/Field/field-wrapper.d.ts.map +1 -1
- package/dist/components/Field/field-wrapper.js +7 -1
- package/dist/components/Field/field-wrapper.js.map +1 -1
- package/dist/components/Field/field.d.ts.map +1 -1
- package/dist/components/Field/field.js +11 -2
- package/dist/components/Field/field.js.map +1 -1
- package/dist/components/FieldControlGroup/field-control-group.d.ts +2 -5
- package/dist/components/FieldControlGroup/field-control-group.d.ts.map +1 -1
- package/dist/components/FieldControlGroup/field-control-group.js +2 -2
- package/dist/components/FieldControlGroup/field-control-group.js.map +1 -1
- package/dist/components/FileUpload/file-upload.js +1 -1
- package/dist/components/FileUpload/file-upload.js.map +1 -1
- package/dist/components/FileViewer/file-viewer.d.ts +1 -1
- package/dist/components/FileViewer/file-viewer.d.ts.map +1 -1
- package/dist/components/FileViewer/file-viewer.js +20 -3
- package/dist/components/FileViewer/file-viewer.js.map +1 -1
- package/dist/components/InlineEdit/inline-edit.d.ts +5 -0
- package/dist/components/InlineEdit/inline-edit.d.ts.map +1 -1
- package/dist/components/InlineEdit/inline-edit.js +65 -33
- package/dist/components/InlineEdit/inline-edit.js.map +1 -1
- package/dist/components/Input/input.d.ts.map +1 -1
- package/dist/components/Input/input.js +3 -2
- package/dist/components/Input/input.js.map +1 -1
- package/dist/components/LinkInput/link-input.d.ts.map +1 -1
- package/dist/components/LinkInput/link-input.js +2 -1
- package/dist/components/LinkInput/link-input.js.map +1 -1
- package/dist/components/Menu/menu-item.d.ts.map +1 -1
- package/dist/components/Menu/menu-item.js +4 -2
- package/dist/components/Menu/menu-item.js.map +1 -1
- package/dist/components/PeoplePicker/people-picker.d.ts +2 -0
- package/dist/components/PeoplePicker/people-picker.d.ts.map +1 -1
- package/dist/components/PeoplePicker/people-picker.js +4 -0
- package/dist/components/PeoplePicker/people-picker.js.map +1 -1
- package/dist/components/Popover/popover.d.ts.map +1 -1
- package/dist/components/Popover/popover.js +4 -2
- package/dist/components/Popover/popover.js.map +1 -1
- package/dist/components/ScrollArea/scroll-area.d.ts +4 -3
- package/dist/components/ScrollArea/scroll-area.d.ts.map +1 -1
- package/dist/components/ScrollArea/scroll-area.js +1 -1
- package/dist/components/ScrollArea/scroll-area.js.map +1 -1
- package/dist/components/Select/select.d.ts.map +1 -1
- package/dist/components/Select/select.js +59 -37
- package/dist/components/Select/select.js.map +1 -1
- package/dist/components/SelectMenu/select-menu.d.ts +2 -0
- package/dist/components/SelectMenu/select-menu.d.ts.map +1 -1
- package/dist/components/SelectMenu/select-menu.js +3 -1
- package/dist/components/SelectMenu/select-menu.js.map +1 -1
- package/dist/components/Sheet/sheet.d.ts +1 -1
- package/dist/components/Sidebar/sidebar.js +2 -2
- package/dist/components/Sidebar/sidebar.js.map +1 -1
- package/dist/components/Tabs/tabs.d.ts.map +1 -1
- package/dist/components/Tabs/tabs.js +3 -0
- package/dist/components/Tabs/tabs.js.map +1 -1
- package/dist/components/Tag/tag.d.ts +1 -1
- package/dist/components/Tag/tag.d.ts.map +1 -1
- package/dist/components/Tag/tag.js +4 -3
- package/dist/components/Tag/tag.js.map +1 -1
- package/dist/components/Textarea/textarea.d.ts +11 -0
- package/dist/components/Textarea/textarea.d.ts.map +1 -1
- package/dist/components/Textarea/textarea.js +22 -5
- package/dist/components/Textarea/textarea.js.map +1 -1
- package/dist/components/TimePicker/time-picker.d.ts.map +1 -1
- package/dist/components/TimePicker/time-picker.js +4 -3
- package/dist/components/TimePicker/time-picker.js.map +1 -1
- package/dist/components/Tooltip/tooltip.d.ts +1 -1
- package/dist/components/Tooltip/tooltip.d.ts.map +1 -1
- package/dist/components/Tooltip/tooltip.js +26 -1
- package/dist/components/Tooltip/tooltip.js.map +1 -1
- package/dist/components/TreeView/tree-view.d.ts +2 -0
- package/dist/components/TreeView/tree-view.d.ts.map +1 -1
- package/dist/components/TreeView/tree-view.js +3 -2
- package/dist/components/TreeView/tree-view.js.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/patterns/element-anatomy/item-anatomy.d.ts +10 -1
- package/dist/patterns/element-anatomy/item-anatomy.d.ts.map +1 -1
- package/dist/patterns/element-anatomy/item-anatomy.js +77 -29
- package/dist/patterns/element-anatomy/item-anatomy.js.map +1 -1
- package/dist/patterns/header-canonical/chrome-header.d.ts +11 -4
- package/dist/patterns/header-canonical/chrome-header.d.ts.map +1 -1
- package/dist/patterns/header-canonical/chrome-header.js +1 -1
- package/dist/patterns/header-canonical/chrome-header.js.map +1 -1
- package/dist/patterns/overlay-surface/overlay-surface.d.ts +4 -3
- package/dist/patterns/overlay-surface/overlay-surface.d.ts.map +1 -1
- package/dist/patterns/overlay-surface/overlay-surface.js +1 -0
- package/dist/patterns/overlay-surface/overlay-surface.js.map +1 -1
- package/dist/patterns/resize-handle/resize-handle.d.ts +9 -11
- package/dist/patterns/resize-handle/resize-handle.d.ts.map +1 -1
- package/dist/patterns/resize-handle/resize-handle.js +3 -6
- package/dist/patterns/resize-handle/resize-handle.js.map +1 -1
- package/ds-canonical/adapters/claude/agents/canonical-reviewer.md +1 -1
- package/ds-canonical/adapters/claude-root-instructions.md +1 -1
- package/ds-canonical/adapters/claude-settings-base.json +2 -5
- package/ds-canonical/fork/AGENTS.md +21 -10
- package/ds-canonical/fork/codex/hooks.json +1 -1
- package/ds-canonical/fork/common/instruction.md +21 -10
- package/ds-canonical/fork/consumer/governance-check.mjs +101 -13
- package/ds-canonical/fork/consumer/lock.json +14 -15
- package/ds-canonical/fork/consumer/managed-files/46f10bc55cdfdf834d0749eebff42ea0bb8576f361b36d2b6ba1851354c50150.blob +26 -3
- package/ds-canonical/fork/consumer/managed-files/63061e05969667005020166ea2ae7df3c96663f7a928e7dd3eebc1471022df56.blob +61 -5
- package/ds-canonical/fork/consumer/managed-files/93a89f16f6717d1e4e4e27ec20ef916e3355c7aa890ca74cd9b12db30d8a899f.blob +3 -10
- package/ds-canonical/fork/consumer/managed-files/bc44cabad181f766611dab6a8f52d81100afadd50ac59799015e8ba7bf46d9eb.blob +3 -2
- package/ds-canonical/fork/governance.lock +17 -33
- package/ds-canonical/fork/hooks/check_consumer_code_quality.sh +33 -0
- package/ds-canonical/fork/hooks/check_pattern_invariants.sh +39 -0
- package/ds-canonical/fork/hooks/check_sidebar_menu_button_implicit_wrap.sh +6 -4
- package/ds-canonical/fork/launchers/provider-hook-normalization.mjs +5 -11
- package/ds-canonical/fork/launchers/settings-hooks.json +2 -2
- package/ds-canonical/fork/manifest.json +11 -13
- package/ds-canonical/fork/providers/claude/hook-config.json +1 -7
- package/ds-canonical/fork/providers/codex/hook-config.json +1 -1
- package/ds-canonical/fork/references/story-baseline-registry.json +7 -1
- package/ds-canonical/hooks/check_consumer_code_quality.sh +33 -0
- package/ds-canonical/hooks/check_pattern_invariants.sh +39 -0
- package/ds-canonical/hooks/check_sidebar_menu_button_implicit_wrap.sh +6 -4
- package/ds-canonical/hooks/session_start_governance_check.sh +5 -6
- package/ds-canonical/hooks/tests/test_check_consumer_code_quality.sh +23 -1
- package/ds-canonical/hooks/tests/test_check_pattern_invariants.sh +36 -2
- package/ds-canonical/references/audit-coverage-vs-24-checklist.md +1 -1
- package/ds-canonical/references/failure-class-registry.json +151 -144
- package/ds-canonical/references/preflight-gate-baseline.json +16 -12
- package/ds-canonical/references/story-baseline-registry.json +7 -1
- package/ds-canonical/rules/meta-patterns.md +3 -3
- package/ds-canonical/skills/design-system-audit/SKILL.md +1 -1
- package/ds-canonical/skills/design-system-audit/references/audit-prompts.md +1 -1
- package/ds-canonical/templates/dashboard-app.tsx +31 -8
- package/llms-full.txt +6 -1
- package/llms.txt +2 -1
- package/package.json +1 -1
- package/src/components/Accordion/accordion.principles.stories.tsx +16 -26
- package/src/components/Accordion/accordion.stories.tsx +22 -40
- package/src/components/AccountMenu/account-menu.anatomy.stories.tsx +115 -0
- package/src/components/AccountMenu/account-menu.principles.stories.tsx +99 -0
- package/src/components/AccountMenu/account-menu.spec.md +101 -0
- package/src/components/AccountMenu/account-menu.stories.tsx +61 -0
- package/src/components/AccountMenu/account-menu.tsx +170 -0
- package/src/components/AccountMenu/index.ts +5 -0
- package/src/components/AppShell/_demo-helpers.tsx +53 -53
- package/src/components/AppShell/app-shell.spec.md +5 -1
- package/src/components/AppShell/app-shell.tsx +14 -12
- package/src/components/Avatar/avatar.principles.stories.tsx +4 -4
- package/src/components/Avatar/avatar.stories.tsx +20 -5
- package/src/components/Breadcrumb/breadcrumb.spec.md +1 -1
- package/src/components/Button/button.spec.md +1 -1
- package/src/components/Button/button.stories.tsx +21 -9
- package/src/components/Button/button.tsx +5 -4
- package/src/components/Calendar/calendar.anatomy.stories.tsx +19 -17
- package/src/components/Calendar/calendar.principles.stories.tsx +5 -6
- package/src/components/Calendar/calendar.spec.md +25 -29
- package/src/components/Calendar/calendar.stories.tsx +5 -7
- package/src/components/Calendar/calendar.tsx +75 -87
- package/src/components/Carousel/carousel.principles.stories.tsx +5 -1
- package/src/components/Chart/chart.spec.md +1 -1
- package/src/components/Checkbox/checkbox.anatomy.stories.tsx +14 -5
- package/src/components/Chip/chip.spec.md +1 -1
- package/src/components/CircularProgress/circular-progress.anatomy.stories.tsx +7 -6
- package/src/components/CircularProgress/circular-progress.principles.stories.tsx +8 -6
- package/src/components/CircularProgress/circular-progress.stories.tsx +3 -3
- package/src/components/Combobox/combobox.tsx +1 -0
- package/src/components/Command/command.spec.md +1 -0
- package/src/components/Command/command.tsx +4 -1
- package/src/components/DataTable/cell-registry.tsx +3 -7
- package/src/components/DataTable/data-table-filter-group.tsx +44 -27
- package/src/components/DataTable/data-table-filter-labels.ts +100 -0
- package/src/components/DataTable/data-table-filter-panel.tsx +48 -8
- package/src/components/DataTable/data-table-filter-value-picker.tsx +25 -8
- package/src/components/DataTable/data-table.spec.md +18 -1
- package/src/components/DataTable/data-table.stories.tsx +346 -33
- package/src/components/DataTable/data-table.tsx +11 -4
- package/src/components/DataTable/filter-operators.spec.md +2 -2
- package/src/components/DateGrid/date-grid.anatomy.stories.tsx +24 -20
- package/src/components/DateGrid/date-grid.principles.stories.tsx +27 -21
- package/src/components/DateGrid/date-grid.stories.tsx +22 -13
- package/src/components/DatePicker/date-picker.anatomy.stories.tsx +3 -3
- package/src/components/DatePicker/date-picker.spec.md +4 -4
- package/src/components/DatePicker/date-picker.stories.tsx +33 -9
- package/src/components/DatePicker/date-picker.tsx +210 -147
- package/src/components/Dialog/dialog.spec.md +1 -1
- package/src/components/Dialog/dialog.tsx +8 -2
- package/src/components/DropdownMenu/dropdown-menu.anatomy.stories.tsx +1 -1
- package/src/components/DropdownMenu/dropdown-menu.spec.md +5 -0
- package/src/components/DropdownMenu/dropdown-menu.stories.tsx +3 -17
- package/src/components/DropdownMenu/dropdown-menu.tsx +58 -16
- package/src/components/Field/field-context.ts +25 -1
- package/src/components/Field/field-wrapper.tsx +7 -1
- package/src/components/Field/field.spec.md +8 -1
- package/src/components/Field/field.tsx +14 -2
- package/src/components/FieldControlGroup/field-control-group.anatomy.stories.tsx +3 -3
- package/src/components/FieldControlGroup/field-control-group.spec.md +2 -3
- package/src/components/FieldControlGroup/field-control-group.stories.tsx +2 -1
- package/src/components/FieldControlGroup/field-control-group.tsx +6 -9
- package/src/components/FileItem/file-item.spec.md +1 -1
- package/src/components/FileItem/file-item.stories.tsx +1 -1
- package/src/components/FileUpload/file-upload.stories.tsx +32 -0
- package/src/components/FileUpload/file-upload.tsx +1 -1
- package/src/components/FileViewer/file-viewer.anatomy.stories.tsx +1 -1
- package/src/components/FileViewer/file-viewer.spec.md +1 -1
- package/src/components/FileViewer/file-viewer.stories.tsx +7 -0
- package/src/components/FileViewer/file-viewer.tsx +23 -5
- package/src/components/InlineEdit/inline-edit.spec.md +12 -0
- package/src/components/InlineEdit/inline-edit.stories.tsx +30 -19
- package/src/components/InlineEdit/inline-edit.tsx +94 -31
- package/src/components/Input/input.anatomy.stories.tsx +28 -12
- package/src/components/Input/input.principles.stories.tsx +23 -18
- package/src/components/Input/input.stories.tsx +9 -1
- package/src/components/Input/input.tsx +7 -2
- package/src/components/LinkInput/link-input.principles.stories.tsx +7 -7
- package/src/components/LinkInput/link-input.spec.md +1 -1
- package/src/components/LinkInput/link-input.tsx +5 -1
- package/src/components/Menu/menu-item.anatomy.stories.tsx +1 -1
- package/src/components/Menu/menu-item.tsx +9 -2
- package/src/components/NumberInput/number-input.anatomy.stories.tsx +44 -10
- package/src/components/NumberInput/number-input.principles.stories.tsx +20 -14
- package/src/components/NumberInput/number-input.spec.md +1 -1
- package/src/components/NumberInput/number-input.stories.tsx +12 -5
- package/src/components/PeoplePicker/people-picker.anatomy.stories.tsx +7 -0
- package/src/components/PeoplePicker/people-picker.spec.md +3 -1
- package/src/components/PeoplePicker/people-picker.tsx +5 -0
- package/src/components/Popover/popover.tsx +7 -2
- package/src/components/ProgressBar/progress-bar.principles.stories.tsx +14 -12
- package/src/components/RadioGroup/radio-group.anatomy.stories.tsx +5 -1
- package/src/components/RadioGroup/radio-group.spec.md +1 -1
- package/src/components/Rating/rating.principles.stories.tsx +2 -2
- package/src/components/ScrollArea/scroll-area.anatomy.stories.tsx +1 -1
- package/src/components/ScrollArea/scroll-area.spec.md +3 -3
- package/src/components/ScrollArea/scroll-area.tsx +5 -4
- package/src/components/Select/select.principles.stories.tsx +4 -3
- package/src/components/Select/select.stories.tsx +7 -6
- package/src/components/Select/select.tsx +61 -29
- package/src/components/SelectMenu/select-menu.spec.md +1 -1
- package/src/components/SelectMenu/select-menu.tsx +4 -1
- package/src/components/SelectionControl/selection-item.anatomy.stories.tsx +19 -8
- package/src/components/SelectionControl/selection-item.stories.tsx +7 -7
- package/src/components/Sheet/sheet.spec.md +1 -1
- package/src/components/Sidebar/sidebar.spec.md +3 -2
- package/src/components/Sidebar/sidebar.stories.tsx +67 -37
- package/src/components/Sidebar/sidebar.tsx +3 -3
- package/src/components/Slider/slider.principles.stories.tsx +9 -9
- package/src/components/Steps/steps.spec.md +1 -0
- package/src/components/Steps/steps.stories.tsx +18 -1
- package/src/components/Switch/switch.anatomy.stories.tsx +11 -11
- package/src/components/Switch/switch.principles.stories.tsx +11 -8
- package/src/components/Tabs/tabs.principles.stories.tsx +24 -0
- package/src/components/Tabs/tabs.spec.md +6 -4
- package/src/components/Tabs/tabs.tsx +11 -0
- package/src/components/Tag/tag.anatomy.stories.tsx +5 -2
- package/src/components/Tag/tag.spec.md +4 -3
- package/src/components/Tag/tag.stories.tsx +19 -0
- package/src/components/Tag/tag.tsx +14 -5
- package/src/components/Textarea/textarea.anatomy.stories.tsx +15 -9
- package/src/components/Textarea/textarea.principles.stories.tsx +21 -5
- package/src/components/Textarea/textarea.spec.md +10 -1
- package/src/components/Textarea/textarea.tsx +38 -5
- package/src/components/TimePicker/time-picker.tsx +11 -5
- package/src/components/Tooltip/tooltip.spec.md +10 -0
- package/src/components/Tooltip/tooltip.tsx +34 -3
- package/src/components/TreeView/tree-view.spec.md +3 -3
- package/src/components/TreeView/tree-view.stories.tsx +68 -9
- package/src/components/TreeView/tree-view.tsx +7 -2
- package/src/index.ts +9 -1
- package/src/patterns/element-anatomy/inline-action.spec.md +12 -8
- package/src/patterns/element-anatomy/item-anatomy.spec.md +8 -1
- package/src/patterns/element-anatomy/item-anatomy.stories.tsx +24 -5
- package/src/patterns/element-anatomy/item-anatomy.tsx +120 -35
- package/src/patterns/element-anatomy/truncated-text.spec.md +13 -2
- package/src/patterns/header-canonical/chrome-header.tsx +14 -5
- package/src/patterns/header-canonical/header-canonical.spec.md +10 -7
- package/src/patterns/header-canonical/header-canonical.stories.tsx +77 -0
- package/src/patterns/horizontal-overflow/horizontal-overflow.stories.tsx +2 -0
- package/src/patterns/overlay-surface/overlay-surface.spec.md +1 -0
- package/src/patterns/overlay-surface/overlay-surface.stories.tsx +17 -1
- package/src/patterns/overlay-surface/overlay-surface.tsx +8 -4
- package/src/patterns/resize-handle/resize-handle.spec.md +11 -13
- package/src/patterns/resize-handle/resize-handle.stories.tsx +1 -3
- package/src/patterns/resize-handle/resize-handle.tsx +13 -18
- package/src/tokens/color/semantic.css +3 -1
- package/src/tokens/layoutSpace/layoutSpace.stories.tsx +1 -1
- package/src/tokens/opacity/opacity.stories.tsx +1 -1
- package/src/tokens/radius/radius.stories.tsx +3 -3
- package/src/tokens/uiSize/uiSize.css +11 -0
- package/src/tokens/uiSize/uiSize.spec.md +1 -0
- package/ds-canonical/commands/gov-status.md +0 -9
- package/ds-canonical/fork/consumer/managed-files/77e06f62670dfee01e9b5e8b0f8dad085ce711d75538c1fb0679d9818acaca82.blob +0 -424
- package/ds-canonical/fork/preamble.md +0 -500
- package/ds-canonical/hooks/check_post_main_ssot_propagate.sh +0 -8
- package/ds-canonical/hooks/tests/test_check_post_main_ssot_propagate.sh +0 -37
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
DATE_RELATIVE_GROUPS,
|
|
24
24
|
type ValueShape,
|
|
25
25
|
} from './filter-operators'
|
|
26
|
+
import type { DataTableFilterPanelLabels } from './data-table-filter-labels'
|
|
26
27
|
|
|
27
28
|
// ── FilterValuePicker(value-picker switcher per ValueShape)────────────────
|
|
28
29
|
//
|
|
@@ -47,6 +48,8 @@ export interface FilterValuePickerProps {
|
|
|
47
48
|
disabled?: boolean
|
|
48
49
|
/** 用 column.label 組「{label} 篩選值」(panel 每 row 不顯式 label,a11y 必填) */
|
|
49
50
|
ariaLabel?: string
|
|
51
|
+
/** Resolved panel labels; placeholders and relative-date labels share one SSOT. */
|
|
52
|
+
labels: DataTableFilterPanelLabels
|
|
50
53
|
/** Forward 給內部 Field control 的 className(2026-05-04 #2 fix)
|
|
51
54
|
* 避免外層包 wrapper div 破壞 FieldControlGroup CSS variants(rounded radii / margin overlap) */
|
|
52
55
|
className?: string
|
|
@@ -59,10 +62,11 @@ export function FilterValuePicker({
|
|
|
59
62
|
colInfo,
|
|
60
63
|
disabled,
|
|
61
64
|
ariaLabel,
|
|
65
|
+
labels,
|
|
62
66
|
className,
|
|
63
67
|
}: FilterValuePickerProps) {
|
|
64
68
|
if (!shape || disabled) {
|
|
65
|
-
return <Input size="sm" value="" onChange={() => {}} placeholder=
|
|
69
|
+
return <Input size="sm" value="" onChange={() => {}} placeholder={labels.valuePlaceholder} disabled aria-label={ariaLabel} className={className} />
|
|
66
70
|
}
|
|
67
71
|
|
|
68
72
|
switch (shape) {
|
|
@@ -75,7 +79,7 @@ export function FilterValuePicker({
|
|
|
75
79
|
size="sm"
|
|
76
80
|
value={String(value ?? '')}
|
|
77
81
|
onChange={(e) => onChange(e.target.value)}
|
|
78
|
-
placeholder=
|
|
82
|
+
placeholder={labels.valuePlaceholder}
|
|
79
83
|
aria-label={ariaLabel}
|
|
80
84
|
className={className}
|
|
81
85
|
/>
|
|
@@ -87,7 +91,7 @@ export function FilterValuePicker({
|
|
|
87
91
|
size="sm"
|
|
88
92
|
value={typeof value === 'number' ? value : null}
|
|
89
93
|
onChange={(v) => onChange(v ?? '')}
|
|
90
|
-
placeholder=
|
|
94
|
+
placeholder={labels.numberPlaceholder}
|
|
91
95
|
aria-label={ariaLabel}
|
|
92
96
|
className={className}
|
|
93
97
|
/>
|
|
@@ -121,17 +125,21 @@ export function FilterValuePicker({
|
|
|
121
125
|
// 群組分類:Past / Current / Future(對齊 Linear / Notion idiom),走 Select.groups → SelectMenu
|
|
122
126
|
const opts: SelectOption[] = DATE_RELATIVE_OPTIONS.map((o) => ({
|
|
123
127
|
value: o.value,
|
|
124
|
-
label: o.label,
|
|
128
|
+
label: labels.relativeDateOptionLabels[o.value] ?? o.label,
|
|
125
129
|
group: o.group,
|
|
126
130
|
}))
|
|
131
|
+
const groups = DATE_RELATIVE_GROUPS.map((group) => ({
|
|
132
|
+
key: group.key,
|
|
133
|
+
label: labels.relativeDateGroupLabels[group.key] ?? group.label,
|
|
134
|
+
}))
|
|
127
135
|
return (
|
|
128
136
|
<Select
|
|
129
137
|
size="sm"
|
|
130
138
|
options={opts}
|
|
131
|
-
groups={
|
|
139
|
+
groups={groups}
|
|
132
140
|
value={String(value ?? '')}
|
|
133
141
|
onChange={(v) => onChange(v)}
|
|
134
|
-
placeholder=
|
|
142
|
+
placeholder={labels.relativeDatePlaceholder}
|
|
135
143
|
aria-label={ariaLabel}
|
|
136
144
|
className={className}
|
|
137
145
|
/>
|
|
@@ -149,7 +157,7 @@ export function FilterValuePicker({
|
|
|
149
157
|
options={opts}
|
|
150
158
|
value={String(value ?? '')}
|
|
151
159
|
onChange={(v) => onChange(v)}
|
|
152
|
-
placeholder=
|
|
160
|
+
placeholder={labels.singleSelectPlaceholder}
|
|
153
161
|
aria-label={ariaLabel}
|
|
154
162
|
className={className}
|
|
155
163
|
/>
|
|
@@ -168,7 +176,8 @@ export function FilterValuePicker({
|
|
|
168
176
|
options={opts}
|
|
169
177
|
value={arr}
|
|
170
178
|
onChange={(v) => onChange(v)}
|
|
171
|
-
placeholder=
|
|
179
|
+
placeholder={labels.multiSelectPlaceholder}
|
|
180
|
+
emptyText={labels.multiSelectEmptyText}
|
|
172
181
|
aria-label={ariaLabel}
|
|
173
182
|
className={className}
|
|
174
183
|
/>
|
|
@@ -213,6 +222,10 @@ export function FilterValuePicker({
|
|
|
213
222
|
value={v ?? null}
|
|
214
223
|
people={colInfo?.people ?? []}
|
|
215
224
|
onChange={(next) => onChange(next[0] ?? null)}
|
|
225
|
+
placeholder={labels.peoplePlaceholder}
|
|
226
|
+
searchPlaceholder={labels.peopleSearchPlaceholder}
|
|
227
|
+
searchAriaLabel={labels.peopleSearchAriaLabel}
|
|
228
|
+
emptyText={labels.peopleEmptyText}
|
|
216
229
|
aria-label={ariaLabel}
|
|
217
230
|
className={className}
|
|
218
231
|
/>
|
|
@@ -226,6 +239,10 @@ export function FilterValuePicker({
|
|
|
226
239
|
value={v}
|
|
227
240
|
people={colInfo?.people ?? []}
|
|
228
241
|
onChange={(next) => onChange(next)}
|
|
242
|
+
placeholder={labels.peoplePlaceholder}
|
|
243
|
+
searchPlaceholder={labels.peopleSearchPlaceholder}
|
|
244
|
+
searchAriaLabel={labels.peopleSearchAriaLabel}
|
|
245
|
+
emptyText={labels.peopleEmptyText}
|
|
229
246
|
aria-label={ariaLabel}
|
|
230
247
|
className={className}
|
|
231
248
|
/>
|
|
@@ -299,6 +299,18 @@ type FilterTreeNested = { mode: 'nested'; conjunction: 'and'|'or'; children: Fil
|
|
|
299
299
|
|
|
300
300
|
`FilterGroup['children']` 只能 `FilterCondition[]` — TypeScript 編譯就拒 over-nest,不靠 runtime check。
|
|
301
301
|
|
|
302
|
+
Panel 額外公開兩個正交控制:
|
|
303
|
+
|
|
304
|
+
```ts
|
|
305
|
+
interface DataTableFilterPanelProps<TData> {
|
|
306
|
+
maxConditions?: number
|
|
307
|
+
labels?: Partial<DataTableFilterPanelLabels>
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
- **`maxConditions`**:計算 condition leaf 總數。flat = `tree.children.length`;nested = 所有 `group.children.length` 加總。未傳 = unlimited;有限值先 `floor` 並 clamp 至 ≥ 0,非有限非法值(NaN / -Infinity)fail closed 為 0。到 cap 後 initial-mount auto row、flat add、nested group 內 add、root add group、cell prefill 五條路徑全部拒絕;prefill 即使被拒仍呼叫 `onPrefillConsumed`,避免同一 request 重試迴圈。Controlled `value` 已超 cap 時**不裁資料**,只停用後續新增。
|
|
312
|
+
- **`labels`**:跟 `DATA_TABLE_FILTER_PANEL_DEFAULT_LABELS` shallow + nested maps deep merge。涵蓋 panel chrome、Where/And/Or、欄位/operator/value placeholder 與 accessible name、刪除/新增/移除、multi-select 子 picker 的 trigger / empty 文案、PeoplePicker 的 trigger / search / empty 文案,以及 operator / relative-date group / option label maps。Panel dispatch 必須把這些子 picker 文案完整下傳,不能漏回 Combobox / PeoplePicker 的獨立 zh-TW defaults。Operator 未 override 的 key 回退 `OPERATOR_REGISTRY[].label`,registry 仍是預設文字 SSOT。
|
|
313
|
+
|
|
302
314
|
### 二、求值策略
|
|
303
315
|
|
|
304
316
|
採 TanStack `globalFilter` + 自訂 `globalFilterFn(row, _, tree) => evaluateTree(tree, row.original)`,**棄 `columnFilters`**(N 條同 column 不能 OR)。`evaluateTree` SSOT 在 `filter-tree.ts`。**比對精度(2026-07-04 Q6 實作)**:panel 建 condition 時把 `meta.includeTime` 固化為 `condition.datePrecision`('ms' / 'day';`evaluateTree` 簽名不變)— date ops 預設 day 級(本地 `startOfDay` 截斷,AG Grid / MUI X 慣例;`is` 同步走日期比對非字串),`includeTime=true` 才 ms 全精度(避開 Airtable day-precision 漏邊界地雷)。
|
|
@@ -325,12 +337,15 @@ ValueShape ↔ DS picker 對照(canonical 2026-05-02):
|
|
|
325
337
|
|
|
326
338
|
- 第 1 row conjunction 是靜態 `Where` label(`px-[var(--field-px)]` 對齊下方 Field value 起點 = 12px)
|
|
327
339
|
- field 未選 → operator + value picker disabled;同 group 共用 conjunction(第 2 條 row 是唯一可改的 And/Or Select,改動連動整 group;第 3 條起唯讀顯示當前 conjunction — A6 canonical)
|
|
328
|
-
- **空狀態(兩態,G fix 2026-05-04 v2)**:initial mount 且 value
|
|
340
|
+
- **空狀態(兩態,G fix 2026-05-04 v2)**:initial mount 且 value 空且 `maxConditions` 尚有容量 → auto-add 1 條空 condition row(field 未選 → operator / value 自動 disabled;讓 user 直接看到 row shape,不必先點 CTA;useRef gate 只 mount 一次);`maxConditions=0` 不 auto-add。user 手動刪光 → 只顯 inline `+ 加篩選` CTA、不 re-add(尊重清空意圖,對齊 Notion / Airtable / Linear) <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
|
|
329
341
|
- **CTA 位置**:緊貼最後一條 row(**廢 SurfaceFooter**),條件與「加入」屬同一語境;root-level「加篩選 / 加入篩選器」用 `tertiary`(輕量但有邊界,符合 root-CTA 重量),group 內「加入巢狀篩選」才用 `text`(更輕,inline 於 group 內)
|
|
330
342
|
- **Trash / 刪除**:row 是 form-control row → text Button(non Inline Action,違 item-anatomy canonical)
|
|
331
343
|
- **And/Or Select** `minRows={2}`(2 選項顯式縮 menu 高度);**Where padding** `px-[var(--field-px)]` align Field
|
|
332
344
|
- Header refresh icon:`value !== defaultValue` 顯;ButtonDivider 串接 close X(對齊欄位顯示 chrome canonical)
|
|
333
345
|
- **Relative date 群組**:`DATE_RELATIVE_GROUPS` Past / Current / Future,走 `<Select groups>`
|
|
346
|
+
- **Labels / i18n**:所有 panel-owned 可見文字與 accessible name 經 `labels`;operator 與 relative-date maps 是 nested partial override。Column header / option label 仍由 consumer 的 `ColumnDef` 提供,不由 panel 翻譯。
|
|
347
|
+
- **Readable secondary copy**:`Where` / 第 3 列起 conjunction 與 field placeholder 用 `fg-secondary`,在 `surface` / nested `muted` 背景維持 WCAG AA 正文對比;不可退回只適合弱化 icon / 非正文 metadata 的 `fg-muted`。
|
|
348
|
+
- **Condition cap**:任何 add CTA 到 cap 時保留於原位置但 disabled;group creation 本身帶 1 個 condition,故消耗 1 容量。刪除後立即恢復新增。
|
|
334
349
|
- Trigger button checked(`aria-pressed`):`value` 有 ≥ 1 active condition → on(語意:資料被篩,獨立於 refresh)
|
|
335
350
|
|
|
336
351
|
### 五、Filterable column 判定
|
|
@@ -401,6 +416,7 @@ tableOptions={{ getSubRows, getRowCanExpand, state: { expanded }, onExpandedChan
|
|
|
401
416
|
- Click 分權:chevron stopPropagation 不 fire select
|
|
402
417
|
- Leaf placeholder:同層 sibling 有 expandable 時 leaf 也佔位
|
|
403
418
|
- a11y:`aria-expanded` 套在展開 chevron `<button>`(非 row);`aria-level` 尚未實作(row depth 目前僅以 `--tree-indent-*` 縮排視覺呈現)
|
|
419
|
+
- Visual interaction regression:`NestedRowsExpanderHoverState` 的 play 標記實際 chevron button;`visual-assertions.json` interaction 再由 Playwright 真 pointer hover 並 fail-closed 驗證唯一 target / trusted pointer hit,禁止只 dispatch synthetic hover event 假綠。
|
|
404
420
|
- Selection cascade:default OFF;`selectionCascade` opt-in 待 v2
|
|
405
421
|
|
|
406
422
|
### Drag visual SSOT(2026-05-06 v14.5)
|
|
@@ -484,6 +500,7 @@ DataTable 是 composite multi-section 元件,**不套 SizeMatrix / StateBehavior
|
|
|
484
500
|
**ARIA / Pattern**:DataTable 是 composite tabular widget,**對齊 W3C ARIA Authoring Practices Guide `grid` pattern**(非 `radio-group`——之前 boilerplate 從 RadioGroup spec 誤抄;DataTable 是 multi-row / multi-column composite,not single-choice selection group,a11y semantics 完全不同(grid vs radio-group)<!-- @benchmark-verified: 2026-05-18 D1 rewrite -->):
|
|
485
501
|
|
|
486
502
|
- Root 套 `role="table"`(currently)或 `role="grid"`(future tier,when cell editing 普及)— 詳 [WAI-ARIA APG: grid](https://www.w3.org/WAI/ARIA/apg/patterns/grid/) + [MDN grid role](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Roles/grid_role)
|
|
503
|
+
- Center body 捲動層套 `role="rowgroup"`(非空表):唯讀模式該層帶 `tabIndex=0` + `aria-label`(scrollable-region-focusable),**focusable 的無 role 中間層會被 axe aria-required-children 判為 table 的不合法 owned child**(2026-07-29 WM beta.95 錨例;與 Tabs 2026-07-18 決策1 同型)。rowgroup = 合法 table 子代 + 可具名([WAI-ARIA 1.2 §5.2.8.4 Name from author](https://www.w3.org/TR/wai-aria-1.2/#namecalculation))。空表例外:Empty 塊非 row → 不掛 rowgroup、亦無 overflow 故一併省 tabIndex/名稱
|
|
487
504
|
- Column headers:以 `<div role="columnheader">` 顯式標記(本元件用 div + ARIA role,非語義 `<table>` — 見定位段;不渲染 `<th>` / `scope`)
|
|
488
505
|
- Row headers:目前無對應(平面 row,無 row header 語意;未渲染 `role="rowheader"`)
|
|
489
506
|
- Sortable column:`aria-sort="none" | "ascending" | "descending"` on 該 column header `<div role="columnheader">`
|
|
@@ -2,12 +2,20 @@
|
|
|
2
2
|
// same-row-mixed-allow: file 含 toolbar Button iconOnly + row ItemInlineActionButton,但兩者在不同 row(toolbar 跟 panel row 分離)
|
|
3
3
|
import React from 'react'
|
|
4
4
|
import type { Meta, StoryObj } from '@storybook/react'
|
|
5
|
+
import { expect, userEvent, waitFor, within } from '@storybook/test'
|
|
5
6
|
import { createColumnHelper, type ColumnDef } from '@tanstack/react-table'
|
|
6
7
|
import { Pencil, Trash2, MoreVertical, Search, Filter, Eye, Download, Plus, ArrowUpDown } from 'lucide-react'
|
|
7
8
|
import { DataTable, type DataTableSelection } from './data-table'
|
|
8
9
|
import { DataTableSortManager } from './data-table-sort-manager'
|
|
9
10
|
import { DataTableColumnVisibilityPanel } from './data-table-column-visibility-panel'
|
|
10
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
DataTableFilterPanel,
|
|
13
|
+
evaluateTree,
|
|
14
|
+
createEmptyFilterTree,
|
|
15
|
+
isFilterTreeActive,
|
|
16
|
+
type DataTableFilterPanelLabels,
|
|
17
|
+
type FilterTree,
|
|
18
|
+
} from './data-table-filter-panel'
|
|
11
19
|
import { getFilteredRowModel, type SortingState } from '@tanstack/react-table'
|
|
12
20
|
import { Button } from '@/design-system/components/Button/button'
|
|
13
21
|
import { Empty } from '@/design-system/components/Empty/empty'
|
|
@@ -820,25 +828,23 @@ const NESTED_DATA: TaskRow[] = [
|
|
|
820
828
|
},
|
|
821
829
|
]
|
|
822
830
|
|
|
831
|
+
const NESTED_STATUS_OPTIONS = [
|
|
832
|
+
{ value: 'Not started', label: 'Not started' },
|
|
833
|
+
{ value: 'In progress', label: 'In progress' },
|
|
834
|
+
{ value: 'Blocked', label: 'Blocked' },
|
|
835
|
+
{ value: 'Done', label: 'Done' },
|
|
836
|
+
]
|
|
837
|
+
const nestedTaskColumn = createColumnHelper<TaskRow>()
|
|
838
|
+
const NESTED_COLUMNS = [
|
|
839
|
+
nestedTaskColumn.accessor('task', { header: '任務', meta: { type: 'string', width: 360 } }),
|
|
840
|
+
nestedTaskColumn.accessor('owner', { header: '負責人', meta: { type: 'string', width: 160 } }),
|
|
841
|
+
nestedTaskColumn.accessor('status', { header: '狀態', meta: { type: 'select', options: NESTED_STATUS_OPTIONS, width: 140 } }),
|
|
842
|
+
]
|
|
843
|
+
|
|
823
844
|
export const NestedRows: Story = {
|
|
824
845
|
name: '巢狀列(樹狀表格)',
|
|
825
846
|
render: () => {
|
|
826
847
|
const [expanded, setExpanded] = React.useState<Record<string, boolean>>({ 'task-1': true, 'task-1-1': true })
|
|
827
|
-
const STATUS_OPTIONS = [
|
|
828
|
-
{ value: 'Not started', label: 'Not started' },
|
|
829
|
-
{ value: 'In progress', label: 'In progress' },
|
|
830
|
-
{ value: 'Blocked', label: 'Blocked' },
|
|
831
|
-
{ value: 'Done', label: 'Done' },
|
|
832
|
-
]
|
|
833
|
-
const taskCol = createColumnHelper<TaskRow>()
|
|
834
|
-
const taskColumns = React.useMemo(
|
|
835
|
-
() => [
|
|
836
|
-
taskCol.accessor('task', { header: '任務', meta: { type: 'string', width: 360 } }),
|
|
837
|
-
taskCol.accessor('owner', { header: '負責人', meta: { type: 'string', width: 160 } }),
|
|
838
|
-
taskCol.accessor('status', { header: '狀態', meta: { type: 'select', options: STATUS_OPTIONS, width: 140 } }),
|
|
839
|
-
],
|
|
840
|
-
[]
|
|
841
|
-
)
|
|
842
848
|
return (
|
|
843
849
|
<div>
|
|
844
850
|
{/* 實作:forward TanStack getSubRows;縮排共用 --tree-indent-{sm,md,lg} token(跨 TreeView
|
|
@@ -848,7 +854,7 @@ export const NestedRows: Story = {
|
|
|
848
854
|
點三角形只切換展開,不會順帶選取整列。
|
|
849
855
|
</p>
|
|
850
856
|
<DataTable
|
|
851
|
-
columns={
|
|
857
|
+
columns={NESTED_COLUMNS}
|
|
852
858
|
data={NESTED_DATA}
|
|
853
859
|
height="auto"
|
|
854
860
|
getRowId={(row) => row.id}
|
|
@@ -865,6 +871,42 @@ export const NestedRows: Story = {
|
|
|
865
871
|
},
|
|
866
872
|
}
|
|
867
873
|
|
|
874
|
+
export const NestedRowsExpanderHoverState: Story = {
|
|
875
|
+
name: '巢狀列展開鈕:滑鼠移過狀態',
|
|
876
|
+
tags: ['!autodocs'],
|
|
877
|
+
render: () => {
|
|
878
|
+
const [expanded, setExpanded] = React.useState<Record<string, boolean>>({})
|
|
879
|
+
return (
|
|
880
|
+
<div className="max-w-3xl p-6">
|
|
881
|
+
<p className="text-caption text-fg-secondary mb-3">
|
|
882
|
+
真實滑鼠停在第一列的展開鈕,驗證弱化圖示只前進一階到次要文字色。
|
|
883
|
+
</p>
|
|
884
|
+
<DataTable
|
|
885
|
+
columns={NESTED_COLUMNS}
|
|
886
|
+
data={NESTED_DATA}
|
|
887
|
+
height="auto"
|
|
888
|
+
getRowId={(row) => row.id}
|
|
889
|
+
tableOptions={{
|
|
890
|
+
getSubRows: (row: TaskRow) => row.children,
|
|
891
|
+
getRowCanExpand: (row) => Boolean(row.original.children?.length),
|
|
892
|
+
state: { expanded },
|
|
893
|
+
onExpandedChange: setExpanded as any,
|
|
894
|
+
}}
|
|
895
|
+
/>
|
|
896
|
+
</div>
|
|
897
|
+
)
|
|
898
|
+
},
|
|
899
|
+
play: ({ canvasElement }) => {
|
|
900
|
+
const canvas = within(canvasElement)
|
|
901
|
+
const expander = canvas.getAllByRole('button', { name: '展開' })[0]
|
|
902
|
+
expect(expander).toHaveAttribute('aria-expanded', 'false')
|
|
903
|
+
// play 只做 deterministic target selection;真 hover 完全由 visual-audit
|
|
904
|
+
// 的 Playwright locator.hover() 擁有,避免 synthetic userEvent 假綠。
|
|
905
|
+
expander.dataset.visualHoverTarget = 'true'
|
|
906
|
+
expect(canvasElement.querySelectorAll('[data-visual-hover-target]')).toHaveLength(1)
|
|
907
|
+
},
|
|
908
|
+
}
|
|
909
|
+
|
|
868
910
|
/* ── 巢狀 row × 拖曳重排(tree-table drag)──
|
|
869
911
|
Tree drag 設計準則(2026-05-06 v14.7,對齊 spec.md「Cross-parent drop 禁止」)
|
|
870
912
|
- **Top-level rows**:有 drag handle(absolute pinned to row left edge),可拖重排
|
|
@@ -876,21 +918,6 @@ export const NestedRowsWithDrag: Story = {
|
|
|
876
918
|
render: () => {
|
|
877
919
|
const [expanded, setExpanded] = React.useState<Record<string, boolean>>({ 'task-1': true })
|
|
878
920
|
const [list, setList] = React.useState<TaskRow[]>(NESTED_DATA)
|
|
879
|
-
const STATUS_OPTIONS = [
|
|
880
|
-
{ value: 'Not started', label: 'Not started' },
|
|
881
|
-
{ value: 'In progress', label: 'In progress' },
|
|
882
|
-
{ value: 'Blocked', label: 'Blocked' },
|
|
883
|
-
{ value: 'Done', label: 'Done' },
|
|
884
|
-
]
|
|
885
|
-
const taskCol = createColumnHelper<TaskRow>()
|
|
886
|
-
const taskColumns = React.useMemo(
|
|
887
|
-
() => [
|
|
888
|
-
taskCol.accessor('task', { header: '任務', meta: { type: 'string', width: 360 } }),
|
|
889
|
-
taskCol.accessor('owner', { header: '負責人', meta: { type: 'string', width: 160 } }),
|
|
890
|
-
taskCol.accessor('status', { header: '狀態', meta: { type: 'select', options: STATUS_OPTIONS, width: 140 } }),
|
|
891
|
-
],
|
|
892
|
-
[]
|
|
893
|
-
)
|
|
894
921
|
const handleReorder = (sourceId: string, targetId: string, position: 'before' | 'after') => {
|
|
895
922
|
setList((prev) => {
|
|
896
923
|
// top-level reorder only(sub-rows 不可拖)
|
|
@@ -913,7 +940,7 @@ export const NestedRowsWithDrag: Story = {
|
|
|
913
940
|
顯 invalid signal。Drop indicator 水平 2px primary line — 主檔對齊 TreeView。
|
|
914
941
|
</p>
|
|
915
942
|
<DataTable
|
|
916
|
-
columns={
|
|
943
|
+
columns={NESTED_COLUMNS}
|
|
917
944
|
data={list}
|
|
918
945
|
height="auto"
|
|
919
946
|
getRowId={(row) => row.id}
|
|
@@ -1272,6 +1299,114 @@ const FILTER_COLUMNS = [
|
|
|
1272
1299
|
col.accessor('updatedAt', { header: '更新時間', meta: { type: 'date', filterable: true, includeTime: true } }),
|
|
1273
1300
|
] as const
|
|
1274
1301
|
|
|
1302
|
+
const FILTER_COLUMNS_ENGLISH = [
|
|
1303
|
+
col.accessor('name', { header: 'Name', meta: { type: 'string', filterable: true } }),
|
|
1304
|
+
col.accessor('category', { header: 'Category', meta: {
|
|
1305
|
+
type: 'select',
|
|
1306
|
+
filterable: true,
|
|
1307
|
+
options: [
|
|
1308
|
+
{ value: 'Electronics', label: 'Electronics' },
|
|
1309
|
+
{ value: 'Furniture', label: 'Furniture' },
|
|
1310
|
+
],
|
|
1311
|
+
} }),
|
|
1312
|
+
col.accessor('seller', { header: 'Owner', meta: {
|
|
1313
|
+
type: 'person',
|
|
1314
|
+
filterable: true,
|
|
1315
|
+
people: SELLERS,
|
|
1316
|
+
} }),
|
|
1317
|
+
col.accessor('updatedAt', { header: 'Updated at', meta: { type: 'date', filterable: true } }),
|
|
1318
|
+
] as const
|
|
1319
|
+
|
|
1320
|
+
const FILTER_CHILD_PICKER_COLUMNS = [
|
|
1321
|
+
col.accessor('category', { header: 'Category', meta: {
|
|
1322
|
+
type: 'select',
|
|
1323
|
+
filterable: true,
|
|
1324
|
+
options: [],
|
|
1325
|
+
} }),
|
|
1326
|
+
col.accessor('seller', { header: 'Owner', meta: {
|
|
1327
|
+
type: 'person',
|
|
1328
|
+
filterable: true,
|
|
1329
|
+
people: [],
|
|
1330
|
+
} }),
|
|
1331
|
+
] as const
|
|
1332
|
+
|
|
1333
|
+
const FILTER_PANEL_ENGLISH_LABELS: Partial<DataTableFilterPanelLabels> = {
|
|
1334
|
+
title: 'Filters',
|
|
1335
|
+
resetToDefault: 'Reset to default',
|
|
1336
|
+
close: 'Close',
|
|
1337
|
+
where: 'Where',
|
|
1338
|
+
and: 'And',
|
|
1339
|
+
or: 'Or',
|
|
1340
|
+
conjunctionAriaLabel: 'Conjunction for this group',
|
|
1341
|
+
fieldPlaceholder: 'Choose field',
|
|
1342
|
+
fieldAriaLabel: 'Filter field',
|
|
1343
|
+
operatorPlaceholder: 'Operator',
|
|
1344
|
+
operatorAriaLabel: 'Filter operator',
|
|
1345
|
+
valuePlaceholder: 'Enter value…',
|
|
1346
|
+
numberPlaceholder: 'Enter number…',
|
|
1347
|
+
relativeDatePlaceholder: 'Choose relative date',
|
|
1348
|
+
singleSelectPlaceholder: 'Choose value',
|
|
1349
|
+
multiSelectPlaceholder: 'Choose values…',
|
|
1350
|
+
multiSelectEmptyText: 'No matching values',
|
|
1351
|
+
peoplePlaceholder: 'Choose people',
|
|
1352
|
+
peopleSearchPlaceholder: 'Search people…',
|
|
1353
|
+
peopleSearchAriaLabel: 'Search filter people',
|
|
1354
|
+
peopleEmptyText: 'No matching people',
|
|
1355
|
+
filterValueAriaLabel: (columnLabel) => columnLabel ? `${columnLabel} filter value` : 'Filter value',
|
|
1356
|
+
deleteCondition: 'Delete condition',
|
|
1357
|
+
addFilter: 'Add filter',
|
|
1358
|
+
addGroup: 'Add filter group',
|
|
1359
|
+
addNestedFilter: 'Add nested filter',
|
|
1360
|
+
removeEmptyGroup: 'Remove empty group',
|
|
1361
|
+
operatorLabels: {
|
|
1362
|
+
contains: 'contains',
|
|
1363
|
+
does_not_contain: 'does not contain',
|
|
1364
|
+
is: 'is',
|
|
1365
|
+
is_not: 'is not',
|
|
1366
|
+
starts_with: 'starts with',
|
|
1367
|
+
ends_with: 'ends with',
|
|
1368
|
+
equals: 'equals',
|
|
1369
|
+
not_equals: 'does not equal',
|
|
1370
|
+
gt: 'is greater than',
|
|
1371
|
+
gte: 'is greater than or equal to',
|
|
1372
|
+
lt: 'is less than',
|
|
1373
|
+
lte: 'is less than or equal to',
|
|
1374
|
+
is_before: 'is before',
|
|
1375
|
+
is_after: 'is after',
|
|
1376
|
+
is_on_or_before: 'is on or before',
|
|
1377
|
+
is_on_or_after: 'is on or after',
|
|
1378
|
+
is_between: 'is between',
|
|
1379
|
+
is_relative: 'is relative',
|
|
1380
|
+
has_any_of: 'has any of',
|
|
1381
|
+
has_all_of: 'has all of',
|
|
1382
|
+
has_none_of: 'has none of',
|
|
1383
|
+
is_set: 'is set',
|
|
1384
|
+
is_not_set: 'is not set',
|
|
1385
|
+
is_true: 'is checked',
|
|
1386
|
+
is_false: 'is not checked',
|
|
1387
|
+
},
|
|
1388
|
+
relativeDateGroupLabels: {
|
|
1389
|
+
past: 'Past',
|
|
1390
|
+
current: 'Current',
|
|
1391
|
+
future: 'Future',
|
|
1392
|
+
},
|
|
1393
|
+
relativeDateOptionLabels: {
|
|
1394
|
+
past_30_days: 'Past 30 days',
|
|
1395
|
+
past_7_days: 'Past 7 days',
|
|
1396
|
+
last_month: 'Last month',
|
|
1397
|
+
last_week: 'Last week',
|
|
1398
|
+
yesterday: 'Yesterday',
|
|
1399
|
+
today: 'Today',
|
|
1400
|
+
this_week: 'This week',
|
|
1401
|
+
this_month: 'This month',
|
|
1402
|
+
tomorrow: 'Tomorrow',
|
|
1403
|
+
next_week: 'Next week',
|
|
1404
|
+
next_month: 'Next month',
|
|
1405
|
+
next_7_days: 'Next 7 days',
|
|
1406
|
+
next_30_days: 'Next 30 days',
|
|
1407
|
+
},
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1275
1410
|
/* ── 進階篩選 — 空狀態 ── */
|
|
1276
1411
|
export const FilterPanelEmpty: Story = {
|
|
1277
1412
|
name: '進階篩選 — 空狀態',
|
|
@@ -1442,6 +1577,184 @@ export const FilterPanelLongTagOverflow: Story = {
|
|
|
1442
1577
|
},
|
|
1443
1578
|
}
|
|
1444
1579
|
|
|
1580
|
+
const FilterPanelLabelsAndLimitHarness = () => {
|
|
1581
|
+
const [childPickerLabels, setChildPickerLabels] = React.useState<FilterTree>({
|
|
1582
|
+
mode: 'flat',
|
|
1583
|
+
conjunction: 'and',
|
|
1584
|
+
children: [
|
|
1585
|
+
{ kind: 'cond', id: 'labels-category', field: 'category', op: 'is', value: [] },
|
|
1586
|
+
{ kind: 'cond', id: 'labels-owner', field: 'seller', op: 'is', value: [] },
|
|
1587
|
+
],
|
|
1588
|
+
})
|
|
1589
|
+
const [flat, setFlat] = React.useState<FilterTree>({
|
|
1590
|
+
mode: 'flat',
|
|
1591
|
+
conjunction: 'and',
|
|
1592
|
+
children: [
|
|
1593
|
+
{ kind: 'cond', id: 'flat-1', field: 'updatedAt', op: 'is_relative', value: 'past_7_days' },
|
|
1594
|
+
],
|
|
1595
|
+
})
|
|
1596
|
+
const [nestedCondition, setNestedCondition] = React.useState<FilterTree>({
|
|
1597
|
+
mode: 'nested',
|
|
1598
|
+
conjunction: 'and',
|
|
1599
|
+
children: [{
|
|
1600
|
+
kind: 'group',
|
|
1601
|
+
id: 'nested-condition-group',
|
|
1602
|
+
conjunction: 'and',
|
|
1603
|
+
children: [{ kind: 'cond', id: 'nested-condition-1', field: 'name', op: 'contains', value: 'phone' }],
|
|
1604
|
+
}],
|
|
1605
|
+
})
|
|
1606
|
+
const [nestedGroup, setNestedGroup] = React.useState<FilterTree>({
|
|
1607
|
+
mode: 'nested',
|
|
1608
|
+
conjunction: 'and',
|
|
1609
|
+
children: [{
|
|
1610
|
+
kind: 'group',
|
|
1611
|
+
id: 'nested-group-1',
|
|
1612
|
+
conjunction: 'and',
|
|
1613
|
+
children: [{ kind: 'cond', id: 'nested-group-cond-1', field: 'name', op: 'contains', value: 'phone' }],
|
|
1614
|
+
}],
|
|
1615
|
+
})
|
|
1616
|
+
const [mountCapZero, setMountCapZero] = React.useState<FilterTree>(() => createEmptyFilterTree('flat'))
|
|
1617
|
+
const [prefillValue, setPrefillValue] = React.useState<FilterTree>({
|
|
1618
|
+
mode: 'flat',
|
|
1619
|
+
conjunction: 'and',
|
|
1620
|
+
children: [{ kind: 'cond', id: 'prefill-1', field: 'name', op: 'contains', value: 'phone' }],
|
|
1621
|
+
})
|
|
1622
|
+
const [prefilledColumnId, setPrefilledColumnId] = React.useState<string | undefined>('category')
|
|
1623
|
+
const [prefillConsumed, setPrefillConsumed] = React.useState(false)
|
|
1624
|
+
|
|
1625
|
+
return (
|
|
1626
|
+
<div className="flex flex-col gap-10">
|
|
1627
|
+
<div data-testid="filter-label-child-pickers">
|
|
1628
|
+
<h3 className="text-body font-medium mb-2">English child-picker labels</h3>
|
|
1629
|
+
<DataTableFilterPanel
|
|
1630
|
+
columns={[...FILTER_CHILD_PICKER_COLUMNS]}
|
|
1631
|
+
value={childPickerLabels}
|
|
1632
|
+
onChange={setChildPickerLabels}
|
|
1633
|
+
maxConditions={2}
|
|
1634
|
+
labels={FILTER_PANEL_ENGLISH_LABELS}
|
|
1635
|
+
/>
|
|
1636
|
+
</div>
|
|
1637
|
+
|
|
1638
|
+
<div data-testid="filter-limit-flat">
|
|
1639
|
+
<h3 className="text-body font-medium mb-2">English labels + flat cap</h3>
|
|
1640
|
+
<DataTableFilterPanel
|
|
1641
|
+
columns={[...FILTER_COLUMNS_ENGLISH]}
|
|
1642
|
+
value={flat}
|
|
1643
|
+
onChange={setFlat}
|
|
1644
|
+
maxConditions={2}
|
|
1645
|
+
labels={FILTER_PANEL_ENGLISH_LABELS}
|
|
1646
|
+
/>
|
|
1647
|
+
</div>
|
|
1648
|
+
|
|
1649
|
+
<div data-testid="filter-limit-nested-condition">
|
|
1650
|
+
<h3 className="text-body font-medium mb-2">Nested condition cap</h3>
|
|
1651
|
+
<DataTableFilterPanel
|
|
1652
|
+
columns={[...FILTER_COLUMNS_ENGLISH]}
|
|
1653
|
+
value={nestedCondition}
|
|
1654
|
+
onChange={setNestedCondition}
|
|
1655
|
+
maxConditions={2}
|
|
1656
|
+
labels={FILTER_PANEL_ENGLISH_LABELS}
|
|
1657
|
+
/>
|
|
1658
|
+
</div>
|
|
1659
|
+
|
|
1660
|
+
<div data-testid="filter-limit-nested-group">
|
|
1661
|
+
<h3 className="text-body font-medium mb-2">Nested group cap</h3>
|
|
1662
|
+
<DataTableFilterPanel
|
|
1663
|
+
columns={[...FILTER_COLUMNS_ENGLISH]}
|
|
1664
|
+
value={nestedGroup}
|
|
1665
|
+
onChange={setNestedGroup}
|
|
1666
|
+
maxConditions={2}
|
|
1667
|
+
labels={FILTER_PANEL_ENGLISH_LABELS}
|
|
1668
|
+
/>
|
|
1669
|
+
</div>
|
|
1670
|
+
|
|
1671
|
+
<div data-testid="filter-limit-mount-zero">
|
|
1672
|
+
<h3 className="text-body font-medium mb-2">Initial mount at zero capacity</h3>
|
|
1673
|
+
<DataTableFilterPanel
|
|
1674
|
+
columns={[...FILTER_COLUMNS_ENGLISH]}
|
|
1675
|
+
value={mountCapZero}
|
|
1676
|
+
onChange={setMountCapZero}
|
|
1677
|
+
maxConditions={0}
|
|
1678
|
+
labels={FILTER_PANEL_ENGLISH_LABELS}
|
|
1679
|
+
/>
|
|
1680
|
+
</div>
|
|
1681
|
+
|
|
1682
|
+
<div data-testid="filter-limit-prefill">
|
|
1683
|
+
<h3 className="text-body font-medium mb-2">Prefill at capacity</h3>
|
|
1684
|
+
<p data-testid="prefill-consumed" className="text-caption text-fg-secondary mb-2">
|
|
1685
|
+
Prefill consumed: {prefillConsumed ? 'yes' : 'no'}
|
|
1686
|
+
</p>
|
|
1687
|
+
<DataTableFilterPanel
|
|
1688
|
+
columns={[...FILTER_COLUMNS_ENGLISH]}
|
|
1689
|
+
value={prefillValue}
|
|
1690
|
+
onChange={setPrefillValue}
|
|
1691
|
+
prefilledColumnId={prefilledColumnId}
|
|
1692
|
+
onPrefillConsumed={() => {
|
|
1693
|
+
setPrefilledColumnId(undefined)
|
|
1694
|
+
setPrefillConsumed(true)
|
|
1695
|
+
}}
|
|
1696
|
+
maxConditions={1}
|
|
1697
|
+
labels={FILTER_PANEL_ENGLISH_LABELS}
|
|
1698
|
+
/>
|
|
1699
|
+
</div>
|
|
1700
|
+
</div>
|
|
1701
|
+
)
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
export const FilterPanelLabelsAndLimit: Story = {
|
|
1705
|
+
name: '進階篩選 — 標籤與條件上限',
|
|
1706
|
+
render: () => <FilterPanelLabelsAndLimitHarness />,
|
|
1707
|
+
play: async ({ canvasElement }) => {
|
|
1708
|
+
const canvas = within(canvasElement)
|
|
1709
|
+
const page = within(canvasElement.ownerDocument.body)
|
|
1710
|
+
|
|
1711
|
+
const childPickers = within(canvas.getByTestId('filter-label-child-pickers'))
|
|
1712
|
+
const categoryValue = childPickers.getByRole('combobox', { name: 'Category filter value' })
|
|
1713
|
+
await expect(categoryValue).toHaveTextContent('Choose values…')
|
|
1714
|
+
await userEvent.click(categoryValue)
|
|
1715
|
+
await expect(page.getAllByText('No matching values')).toHaveLength(2)
|
|
1716
|
+
await userEvent.keyboard('{Escape}')
|
|
1717
|
+
|
|
1718
|
+
await userEvent.click(childPickers.getByRole('combobox', { name: 'Owner filter value' }))
|
|
1719
|
+
const peopleSearch = await page.findByPlaceholderText('Search people…')
|
|
1720
|
+
await expect(peopleSearch).toHaveAccessibleName('Search filter people')
|
|
1721
|
+
await expect(page.getAllByText('No matching people')).toHaveLength(2)
|
|
1722
|
+
await userEvent.keyboard('{Escape}')
|
|
1723
|
+
|
|
1724
|
+
const flat = within(canvas.getByTestId('filter-limit-flat'))
|
|
1725
|
+
await expect(flat.getByText('Filters')).toBeInTheDocument()
|
|
1726
|
+
await expect(flat.getByText('Where')).toBeInTheDocument()
|
|
1727
|
+
await expect(flat.getByText('is relative')).toBeInTheDocument()
|
|
1728
|
+
await expect(flat.getByText('Past 7 days')).toBeInTheDocument()
|
|
1729
|
+
await userEvent.click(flat.getByRole('button', { name: 'Add filter' }))
|
|
1730
|
+
await waitFor(() => expect(flat.getAllByLabelText('Filter field')).toHaveLength(2))
|
|
1731
|
+
await expect(flat.getByRole('button', { name: 'Add filter' })).toBeDisabled()
|
|
1732
|
+
|
|
1733
|
+
const nestedCondition = within(canvas.getByTestId('filter-limit-nested-condition'))
|
|
1734
|
+
await userEvent.click(nestedCondition.getByRole('button', { name: 'Add nested filter' }))
|
|
1735
|
+
await waitFor(() => expect(nestedCondition.getAllByLabelText('Filter field')).toHaveLength(2))
|
|
1736
|
+
await expect(nestedCondition.getByRole('button', { name: 'Add nested filter' })).toBeDisabled()
|
|
1737
|
+
await expect(nestedCondition.getByRole('button', { name: 'Add filter group' })).toBeDisabled()
|
|
1738
|
+
|
|
1739
|
+
const nestedGroup = within(canvas.getByTestId('filter-limit-nested-group'))
|
|
1740
|
+
await userEvent.click(nestedGroup.getByRole('button', { name: 'Add filter group' }))
|
|
1741
|
+
await waitFor(() => expect(nestedGroup.getAllByLabelText('Filter field')).toHaveLength(2))
|
|
1742
|
+
for (const button of nestedGroup.getAllByRole('button', { name: 'Add nested filter' })) {
|
|
1743
|
+
await expect(button).toBeDisabled()
|
|
1744
|
+
}
|
|
1745
|
+
await expect(nestedGroup.getByRole('button', { name: 'Add filter group' })).toBeDisabled()
|
|
1746
|
+
|
|
1747
|
+
const mountCapZero = within(canvas.getByTestId('filter-limit-mount-zero'))
|
|
1748
|
+
await expect(mountCapZero.queryAllByLabelText('Filter field')).toHaveLength(0)
|
|
1749
|
+
await expect(mountCapZero.getByRole('button', { name: 'Add filter' })).toBeDisabled()
|
|
1750
|
+
|
|
1751
|
+
const prefill = within(canvas.getByTestId('filter-limit-prefill'))
|
|
1752
|
+
await waitFor(() => expect(prefill.getByTestId('prefill-consumed')).toHaveTextContent('yes'))
|
|
1753
|
+
await expect(prefill.getAllByLabelText('Filter field')).toHaveLength(1)
|
|
1754
|
+
await expect(prefill.getByRole('button', { name: 'Add filter' })).toBeDisabled()
|
|
1755
|
+
},
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1445
1758
|
/* ── 列拖曳重排(Jira-style + 3-panel pinned columns)──────────────────────
|
|
1446
1759
|
enableRowDrag + onRowReorder 整合範例(v3 Jira 設計準則):
|
|
1447
1760
|
- handle 浮在 row 左緣(fixed-position;Button tertiary iconOnly xs = elevated chip)
|
|
@@ -1822,7 +1822,7 @@ function DataTableInner<TData>(
|
|
|
1822
1822
|
type="button"
|
|
1823
1823
|
aria-label={isExpanded ? '收合' : '展開'}
|
|
1824
1824
|
aria-expanded={isExpanded}
|
|
1825
|
-
className="inline-flex items-center justify-center shrink-0 w-4 h-4 mr-2 text-fg-muted hover:text-
|
|
1825
|
+
className="inline-flex items-center justify-center shrink-0 w-4 h-4 mr-2 text-fg-muted hover:text-fg-secondary rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring transition-transform"
|
|
1826
1826
|
style={{ transform: isExpanded ? 'rotate(90deg)' : undefined }}
|
|
1827
1827
|
onClick={(e) => { e.stopPropagation(); toggleExpand?.() }}
|
|
1828
1828
|
>
|
|
@@ -1867,7 +1867,7 @@ function DataTableInner<TData>(
|
|
|
1867
1867
|
type="button"
|
|
1868
1868
|
aria-label={isExpanded ? '收合' : '展開'}
|
|
1869
1869
|
aria-expanded={isExpanded}
|
|
1870
|
-
className="inline-flex items-center justify-center shrink-0 w-4 h-4 mr-2 text-fg-muted hover:text-
|
|
1870
|
+
className="inline-flex items-center justify-center shrink-0 w-4 h-4 mr-2 text-fg-muted hover:text-fg-secondary rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring transition-transform"
|
|
1871
1871
|
style={{ transform: isExpanded ? 'rotate(90deg)' : undefined }}
|
|
1872
1872
|
onClick={(e) => { e.stopPropagation(); toggleExpand?.() }}
|
|
1873
1873
|
>
|
|
@@ -2704,8 +2704,15 @@ function DataTableInner<TData>(
|
|
|
2704
2704
|
// (非 enabled / spreadsheet)outer table 也不 focusable → 這裡補 tabIndex=0 + 具名 +
|
|
2705
2705
|
// inset focus ring,讓鍵盤可聚焦後方向鍵捲動;互動模式已由 outer table keyboard handler
|
|
2706
2706
|
// + focusable cells 覆蓋,不重複加 tab stop。
|
|
2707
|
-
|
|
2708
|
-
aria-
|
|
2707
|
+
// **role="rowgroup" 必要**(2026-07-29 WM beta.95 axe 錨例):focusable 的無 role
|
|
2708
|
+
// 中間層會被 axe aria-required-children 視為 role="table" 的不合法 owned child,
|
|
2709
|
+
// 打斷 table→row ownership(修 scrollable-region-focusable 反引爆 required-children,
|
|
2710
|
+
// 與 Tabs 2026-07-18 決策1 同型)。rowgroup = W3C ARIA 1.2 合法 table 子代 + 可具名
|
|
2711
|
+
// (Name from author §5.2.8.4)+ 容納 rows,兩規則唯一同時滿足解。
|
|
2712
|
+
// 空表例外:Empty 塊非 row → 不掛 rowgroup;空表無 overflow 也無需 tabIndex/名稱。
|
|
2713
|
+
role={isEmpty ? undefined : 'rowgroup'}
|
|
2714
|
+
tabIndex={enabled || spreadsheetMode || isEmpty ? undefined : 0}
|
|
2715
|
+
aria-label={enabled || spreadsheetMode || isEmpty ? undefined : '表格內容,可捲動'}
|
|
2709
2716
|
// overflow-x/y: auto — 沒 overflow 就不顯 bar。wrapper minWidth 仍 trigger H 真 overflow。
|
|
2710
2717
|
// **不**用 scrollbar-gutter: stable — 那會永遠保留 V 軸 15px 空間,
|
|
2711
2718
|
// content fit 時看起來像「永遠有 V 捲軸」(Image #5 bug)。
|
|
@@ -210,7 +210,7 @@
|
|
|
210
210
|
| 4 | `date` 擴 `is_on_or_before` / `is_on_or_after` | ✅ 採用(CU gap) |
|
|
211
211
|
| 5 | `multiSelect` 走 5 ops(含 `has_all_of`)| ✅ |
|
|
212
212
|
| 6 | `is_exactly` 全砍 | ✅ |
|
|
213
|
-
| 7 | label
|
|
213
|
+
| 7 | registry 繁中 label 作預設;panel 以 `labels.operatorLabels` / relative-date maps 提供 consumer i18n override | ✅ 2026-07-31 落地 |
|
|
214
214
|
| 8 | op 命名 snake_case | ✅ |
|
|
215
215
|
| 9 | 統一 `is_set` / `is_not_set`(不混 `is_empty`) | ✅ |
|
|
216
216
|
|
|
@@ -253,7 +253,7 @@
|
|
|
253
253
|
- [x] InputSurface 議題消失(配合砍 `number.between`)
|
|
254
254
|
- [x] FilterTree data model + DataTable 耦合 — 已落地 `filter-tree.ts` + `data-table-filter-panel.tsx`(原規劃 `advanced-filter.draft.md` 未另立檔,內容即本 spec + code)
|
|
255
255
|
- [x] datetime spec — 併入本 spec v3(原規劃 `datetime-picker.draft.md` 不需要,無新元件)
|
|
256
|
-
- [x] i18n 策略 —
|
|
256
|
+
- [x] i18n 策略 — registry 繁中 label 保持預設 SSOT;`DataTableFilterPanel labels` 可 deep-partial override operator、relative-date group / option 與全部 panel chrome(2026-07-31)
|
|
257
257
|
- [→] Phase B.5 lock canonical via `/ensure-canonical`(spec 全收斂後)
|
|
258
258
|
|
|
259
259
|
## Sources(M8 benchmark)
|