@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
|
@@ -62,7 +62,7 @@ export const UsageGuidance: Story = {
|
|
|
62
62
|
note="語意鎖「正在載入、正在處理」。永遠旋轉的裝飾會讓 a11y 使用者(螢幕閱讀器)持續收到 loading 通知,也讓視覺使用者無法判斷何時結束"
|
|
63
63
|
>
|
|
64
64
|
<div className="flex items-center gap-2 border border-border rounded-lg p-3 w-72">
|
|
65
|
-
<CircularProgress />
|
|
65
|
+
<CircularProgress aria-label="載入中(裝飾誤用示範)" />
|
|
66
66
|
<span className="text-body">歡迎使用本系統</span>
|
|
67
67
|
</div>
|
|
68
68
|
<Label warn>Welcome banner 裡無限轉的 CircularProgress → 使用者以為系統卡住</Label>
|
|
@@ -76,7 +76,7 @@ export const UsageGuidance: Story = {
|
|
|
76
76
|
{Array.from({ length: 4 }).map((_, i) => (
|
|
77
77
|
<div key={i} className="flex items-center gap-2 border border-border rounded-md px-3 py-2">
|
|
78
78
|
{/* size=16 = field-inline canonical;此例為反例(不該多個一起轉)。 */}
|
|
79
|
-
<CircularProgress size={16} />
|
|
79
|
+
<CircularProgress size={16} aria-label={`載入項目 ${i + 1}`} />
|
|
80
80
|
<span className="text-body text-fg-muted">載入項目 {i + 1}</span>
|
|
81
81
|
</div>
|
|
82
82
|
))}
|
|
@@ -104,7 +104,7 @@ export const UsageGuidance: Story = {
|
|
|
104
104
|
>
|
|
105
105
|
<div className="flex items-center gap-6">
|
|
106
106
|
<div className="flex flex-col items-center gap-2">
|
|
107
|
-
<CircularProgress value={100} size={24} />
|
|
107
|
+
<CircularProgress value={100} size={24} aria-label="進度 100%(穩態誤用)" />
|
|
108
108
|
<Label warn>❌ 穩態停留 100% → 使用者困惑</Label>
|
|
109
109
|
</div>
|
|
110
110
|
<div className="flex flex-col items-center gap-2">
|
|
@@ -166,7 +166,7 @@ export const IndeterminateVsDeterminateRule: Story = {
|
|
|
166
166
|
<Label>✅ 第三方金流驗證:不知道要多久 → 不傳 value</Label>
|
|
167
167
|
</div>
|
|
168
168
|
<div className="flex flex-col items-center gap-2">
|
|
169
|
-
<CircularProgress value={65} affix="value" />
|
|
169
|
+
<CircularProgress value={65} affix="value" aria-label="檔案上傳進度 65%" />
|
|
170
170
|
<Label>✅ 檔案上傳:bytes 已知 → 傳 value={'{N}'}</Label>
|
|
171
171
|
</div>
|
|
172
172
|
</Rule>
|
|
@@ -176,7 +176,7 @@ export const IndeterminateVsDeterminateRule: Story = {
|
|
|
176
176
|
note="value 永遠停在 0% 或隨機亂跳會讓使用者懷疑 app 壞掉。若操作本質不可量化,維持 indeterminate 到底,不要硬傳 value 假裝。"
|
|
177
177
|
>
|
|
178
178
|
<div className="flex flex-col items-center gap-2">
|
|
179
|
-
<CircularProgress value={0} />
|
|
179
|
+
<CircularProgress value={0} aria-label="報表產生進度 0%(假進度誤用)" />
|
|
180
180
|
<Label warn>❌ 生成報表中卻永遠卡 0% → 改用 indeterminate(不傳 value)</Label>
|
|
181
181
|
</div>
|
|
182
182
|
</Rule>
|
|
@@ -209,6 +209,7 @@ export const ConsumptionScenarioRule: Story = {
|
|
|
209
209
|
loading
|
|
210
210
|
defaultValue="cq@"
|
|
211
211
|
placeholder="驗證 email 唯一性..."
|
|
212
|
+
aria-label="Email"
|
|
212
213
|
className="w-72"
|
|
213
214
|
/>
|
|
214
215
|
<Label>驗證 email 唯一性(GitHub 註冊場景)</Label>
|
|
@@ -244,6 +245,7 @@ export const SizeMatchContextRule: Story = {
|
|
|
244
245
|
loading
|
|
245
246
|
defaultValue="驗證中..."
|
|
246
247
|
placeholder="驗證 email..."
|
|
248
|
+
aria-label="Email"
|
|
247
249
|
className="w-60"
|
|
248
250
|
/>
|
|
249
251
|
</Rule>
|
|
@@ -253,7 +255,7 @@ export const SizeMatchContextRule: Story = {
|
|
|
253
255
|
note="中等尺寸,放在 row / card 內容區,視覺重量足夠吸引注意但不喧賓奪主"
|
|
254
256
|
>
|
|
255
257
|
<div className="flex items-center justify-center gap-2 border border-border rounded-lg p-4 w-72">
|
|
256
|
-
<CircularProgress size={24} />
|
|
258
|
+
<CircularProgress size={24} aria-label="載入更多留言" />
|
|
257
259
|
<span className="text-body text-fg-muted">載入更多留言...</span>
|
|
258
260
|
</div>
|
|
259
261
|
<Label>Slack channel 載入更多訊息的 footer</Label>
|
|
@@ -35,7 +35,7 @@ export const Default: Story = {
|
|
|
35
35
|
(Button loading = iconSize / Input loading = iconSize / Empty = iconSize)。
|
|
36
36
|
不 parallel 展示多 sizes 因為 DS 不「提供各種 sizes」讓 consumer 挑,而是透過原則
|
|
37
37
|
在 consumer 端自動決定(見 spec.md「Size canonical」)。 */}
|
|
38
|
-
<CircularProgress />
|
|
38
|
+
<CircularProgress aria-label="載入中" />
|
|
39
39
|
<CircularProgress value={60} aria-label="進度 60%" />
|
|
40
40
|
<CircularProgress value={90} affix="value" aria-label="進度 90%" />
|
|
41
41
|
</div>
|
|
@@ -65,6 +65,7 @@ export const InlineAction: Story = {
|
|
|
65
65
|
loading
|
|
66
66
|
defaultValue="react-"
|
|
67
67
|
placeholder="搜尋 GitHub repositories..."
|
|
68
|
+
aria-label="搜尋 GitHub repositories"
|
|
68
69
|
/>
|
|
69
70
|
|
|
70
71
|
<div className="flex flex-col gap-2">
|
|
@@ -107,7 +108,7 @@ const syncColumns: ColumnDef<SyncRow>[] = [
|
|
|
107
108
|
// 再加 leading-none 嘗試「壓縮行高」——那會讓 svg(16px) 與 text line box 互搶
|
|
108
109
|
// 基線,反而歪掉。
|
|
109
110
|
<span className="inline-flex items-center gap-2 text-fg-muted">
|
|
110
|
-
<CircularProgress size={16} />
|
|
111
|
+
<CircularProgress size={16} aria-label="同步中" />
|
|
111
112
|
同步中
|
|
112
113
|
</span>
|
|
113
114
|
) : (
|
|
@@ -142,4 +143,3 @@ export const FullScreenOverlay: Story = {
|
|
|
142
143
|
</div>
|
|
143
144
|
),
|
|
144
145
|
}
|
|
145
|
-
|
|
@@ -880,6 +880,7 @@ function CustomCombobox({
|
|
|
880
880
|
createLabel={createLabel}
|
|
881
881
|
searchable={searchable && searchIn === 'menu'}
|
|
882
882
|
searchPlaceholder={searchPlaceholder}
|
|
883
|
+
searchAriaLabel={searchAriaLabel}
|
|
883
884
|
size={size}
|
|
884
885
|
open={open}
|
|
885
886
|
onOpenChange={(o) => { setOpen(o); onOpenChange?.(o) }}
|
|
@@ -87,6 +87,7 @@ cmdk 自動處理:
|
|
|
87
87
|
- **搜尋框**:`role="combobox"` + `aria-expanded` / `aria-controls` 指向 list
|
|
88
88
|
- **鍵盤導覽**:cmdk 提供 ↑ / ↓ 移動 highlight、Enter 選取(另支援 vim-style Ctrl+n/p/j/k、Home/End)。cmdk 本身無 Esc handler — Esc 關閉僅在 `CommandDialog`(Cmd+K)模式由 Radix Dialog 的 DismissableLayer 提供;inline `<Command>` 模式按 Esc 無反應
|
|
89
89
|
- **空狀態**:`<CommandEmpty>` 自動帶 `role="presentation"`,不干擾 screen reader 的 list 朗讀
|
|
90
|
+
- **分隔線**:`<CommandSeparator>` 是純視覺分組線,固定 `role="presentation"`;不冒充 listbox 選項或其他可導覽項目
|
|
90
91
|
|
|
91
92
|
Consumer 無需額外處理 a11y,保留 cmdk 原結構 + 使用 `<CommandInput>` / `<CommandList>` / `<CommandItem>` 即可。
|
|
92
93
|
|
|
@@ -131,7 +131,10 @@ const CommandSeparator = React.forwardRef<
|
|
|
131
131
|
ref={ref}
|
|
132
132
|
className={cn("h-px bg-divider", className)}
|
|
133
133
|
{...props}
|
|
134
|
-
|
|
134
|
+
asChild
|
|
135
|
+
>
|
|
136
|
+
<div role="presentation" />
|
|
137
|
+
</CommandPrimitive.Separator>
|
|
135
138
|
))
|
|
136
139
|
CommandSeparator.displayName = CommandPrimitive.Separator.displayName
|
|
137
140
|
|
|
@@ -148,20 +148,16 @@ function StringCell({ value, meta, mode, size, autoRowHeight, onCommit, onCancel
|
|
|
148
148
|
// → cell 進 edit shrink)。改 CSS `field-sizing: content`(Chrome 123+ / FF 122+ /
|
|
149
149
|
// Safari 17+)讓 textarea 自動 grow to content,匹配 view wrap 真實高度。
|
|
150
150
|
// Fallback rows 仍保留給舊 browser(rows attr 在 field-sizing 支援時被覆蓋)。
|
|
151
|
-
const newlineRows = (v.match(/\n/g) || []).length + 1
|
|
152
|
-
const wrapRows = Math.ceil(v.length / 40)
|
|
153
|
-
const estimateRows = Math.min(10, Math.max(1, newlineRows, wrapRows))
|
|
154
151
|
return (
|
|
155
152
|
<Textarea
|
|
156
153
|
autoFocus
|
|
157
154
|
variant="naked"
|
|
158
155
|
aria-label={ariaLabel}
|
|
159
156
|
size={sizeForInput(size)}
|
|
160
|
-
rows
|
|
157
|
+
// 2026-07-28:原本 field-sizing + rows 估算是本檔的呼叫端膠水,只有 cell 有、InlineEdit 沒有
|
|
158
|
+
// → 兩者 edit 高度漂移。已提升為 Textarea `autoSize` 能力,兩個語境共用同一份實作。
|
|
159
|
+
autoSize
|
|
161
160
|
defaultValue={v}
|
|
162
|
-
// any-allow: CSS `field-sizing` 屬性 Chrome 123+/FF 122+/Safari 17+ 支援但 TypeScript lib.dom
|
|
163
|
-
// 尚未加 type;narrow 到 CSSProperties 仍需 cast,保留 single-site any 較 type aug 簡潔。
|
|
164
|
-
style={{ fieldSizing: 'content' } as React.CSSProperties}
|
|
165
161
|
onChange={(e) => onDraft?.((e.target as HTMLTextAreaElement).value)}
|
|
166
162
|
onBlur={(e) => onCommit?.((e.target as HTMLTextAreaElement).value)}
|
|
167
163
|
// 多行:走 edit-in-place 鍵盤結算 SSOT(IME guard + Esc + Cmd/Ctrl+Enter commit;
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
} from './filter-operators'
|
|
28
28
|
import type { Conjunction, FilterCondition, FilterGroup } from './filter-tree'
|
|
29
29
|
import { FilterValuePicker } from './data-table-filter-value-picker'
|
|
30
|
+
import type { DataTableFilterPanelLabels } from './data-table-filter-labels'
|
|
30
31
|
|
|
31
32
|
// ── Shared internal types + operator helpers(panel orchestration 也消費)────
|
|
32
33
|
|
|
@@ -40,9 +41,12 @@ export interface FilterColumn {
|
|
|
40
41
|
includeTime?: boolean
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
function getOperatorOptions(
|
|
44
|
+
function getOperatorOptions(
|
|
45
|
+
type: ColumnType | undefined,
|
|
46
|
+
operatorLabels: DataTableFilterPanelLabels['operatorLabels'],
|
|
47
|
+
): SelectOption[] {
|
|
44
48
|
const registry = type && OPERATOR_REGISTRY[type] ? OPERATOR_REGISTRY[type] : OPERATOR_REGISTRY.string
|
|
45
|
-
return registry.map((op) => ({ value: op.op, label: op.label }))
|
|
49
|
+
return registry.map((op) => ({ value: op.op, label: operatorLabels[op.op] ?? op.label }))
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
export function getDefaultOperator(type?: ColumnType): string {
|
|
@@ -56,25 +60,25 @@ export const datePrecisionOf = (col: FilterColumn | undefined): 'day' | 'ms' | u
|
|
|
56
60
|
|
|
57
61
|
// ── ConjunctionLabel ───────────────────────────────────────────────────
|
|
58
62
|
|
|
59
|
-
const CONJ_OPTIONS: SelectOption[] = [
|
|
60
|
-
{ value: 'and', label: 'And' },
|
|
61
|
-
{ value: 'or', label: 'Or' },
|
|
62
|
-
]
|
|
63
|
-
|
|
64
63
|
function ConjunctionLabel({
|
|
65
|
-
index, conjunction, onChange,
|
|
66
|
-
}: {
|
|
64
|
+
index, conjunction, onChange, labels,
|
|
65
|
+
}: {
|
|
66
|
+
index: number
|
|
67
|
+
conjunction: Conjunction
|
|
68
|
+
onChange: (c: Conjunction) => void
|
|
69
|
+
labels: DataTableFilterPanelLabels
|
|
70
|
+
}) {
|
|
67
71
|
// index === 0:首 row 顯示靜態「Where」label
|
|
68
72
|
// index === 1:**唯一可改**的 AND/OR Select(連動整 group conjunction)
|
|
69
73
|
// index ≥ 2:被連動的 row,read-only 顯示當前 conjunction 文字(同 Where 視覺,A6 canonical)
|
|
70
74
|
// 對齊 Airtable / Notion / Linear 共識 @benchmark-unverified(non-OSS)
|
|
71
75
|
// px-[var(--field-px)] 對齊 Field 內部 padding 12px(Q13)
|
|
72
76
|
if (index === 0) {
|
|
73
|
-
return <div className="w-20 shrink-0 text-body text-fg-
|
|
77
|
+
return <div className="w-20 shrink-0 text-body text-fg-secondary px-[var(--field-px)] self-center">{labels.where}</div>
|
|
74
78
|
}
|
|
75
79
|
if (index >= 2) {
|
|
76
|
-
const label = conjunction === 'and' ?
|
|
77
|
-
return <div className="w-20 shrink-0 text-body text-fg-
|
|
80
|
+
const label = conjunction === 'and' ? labels.and : labels.or
|
|
81
|
+
return <div className="w-20 shrink-0 text-body text-fg-secondary px-[var(--field-px)] self-center">{label}</div>
|
|
78
82
|
}
|
|
79
83
|
// index === 1:可切換的 AND/OR Select
|
|
80
84
|
// minRows={2} — And/Or 2 選項,顯式縮 menu 高度避免 reserve 3 row 空白(Q5)
|
|
@@ -82,11 +86,14 @@ function ConjunctionLabel({
|
|
|
82
86
|
<div className="w-20 shrink-0">
|
|
83
87
|
<Select
|
|
84
88
|
size="sm"
|
|
85
|
-
options={
|
|
89
|
+
options={[
|
|
90
|
+
{ value: 'and', label: labels.and },
|
|
91
|
+
{ value: 'or', label: labels.or },
|
|
92
|
+
]}
|
|
86
93
|
value={conjunction}
|
|
87
94
|
onChange={(v) => onChange(v as Conjunction)}
|
|
88
95
|
minRows={2}
|
|
89
|
-
aria-label=
|
|
96
|
+
aria-label={labels.conjunctionAriaLabel}
|
|
90
97
|
/>
|
|
91
98
|
</div>
|
|
92
99
|
)
|
|
@@ -96,7 +103,7 @@ function ConjunctionLabel({
|
|
|
96
103
|
|
|
97
104
|
export function FilterRow({
|
|
98
105
|
index, condition, conjunction, filterableColumns, fieldOptions,
|
|
99
|
-
onChangeConjunction, onChangeField, onChangeOp, onChangeValue, onRemove,
|
|
106
|
+
onChangeConjunction, onChangeField, onChangeOp, onChangeValue, onRemove, labels,
|
|
100
107
|
}: {
|
|
101
108
|
index: number
|
|
102
109
|
condition: FilterCondition
|
|
@@ -108,9 +115,10 @@ export function FilterRow({
|
|
|
108
115
|
onChangeOp: (v: string) => void
|
|
109
116
|
onChangeValue: (v: unknown) => void
|
|
110
117
|
onRemove: () => void
|
|
118
|
+
labels: DataTableFilterPanelLabels
|
|
111
119
|
}) {
|
|
112
120
|
const colInfo = filterableColumns.find((c) => c.id === condition.field)
|
|
113
|
-
const operatorOptions = getOperatorOptions(colInfo?.type)
|
|
121
|
+
const operatorOptions = getOperatorOptions(colInfo?.type, labels.operatorLabels)
|
|
114
122
|
const hasField = !!condition.field
|
|
115
123
|
const opSpec = colInfo ? getOperatorSpec(colInfo.type, condition.op) : null
|
|
116
124
|
const valueShape: ValueShape | null = colInfo && opSpec
|
|
@@ -129,7 +137,7 @@ export function FilterRow({
|
|
|
129
137
|
// - **#2 fix**:FilterValuePicker 直接是 FieldControlGroup direct child(無 wrapper div),CSS variants 命中正確
|
|
130
138
|
return (
|
|
131
139
|
<div className="flex items-center gap-2">
|
|
132
|
-
<ConjunctionLabel index={index} conjunction={conjunction} onChange={onChangeConjunction} />
|
|
140
|
+
<ConjunctionLabel index={index} conjunction={conjunction} onChange={onChangeConjunction} labels={labels} />
|
|
133
141
|
{/* **#9 fix(2026-05-04 v4)**:Field controls trigger `w-full` override 外 className,改用 Tailwind `!`
|
|
134
142
|
important 強制 override(`!w-[160px]` / `!w-[120px]`),value 用 `!flex-1 !min-w-0`。
|
|
135
143
|
Select 元件本身沒 destructure `style` prop 所以 inline style flex-basis 行不通,只能用 className。 */}
|
|
@@ -138,13 +146,13 @@ export function FilterRow({
|
|
|
138
146
|
`--data-table-filter-field-width` / `--data-table-filter-op-width`(SSOT in uiSize.css)
|
|
139
147
|
Behavior preserved 完好如初:flat + nested 同 width(token 是 design constant) */}
|
|
140
148
|
<Select
|
|
141
|
-
className="!w-[var(--data-table-filter-field-width)] flex-shrink-0"
|
|
149
|
+
className="!w-[var(--data-table-filter-field-width)] flex-shrink-0 [&_.text-fg-muted]:!text-fg-secondary"
|
|
142
150
|
size="sm"
|
|
143
151
|
options={fieldOptions}
|
|
144
152
|
value={condition.field}
|
|
145
153
|
onChange={onChangeField}
|
|
146
|
-
placeholder=
|
|
147
|
-
aria-label=
|
|
154
|
+
placeholder={labels.fieldPlaceholder}
|
|
155
|
+
aria-label={labels.fieldAriaLabel}
|
|
148
156
|
/>
|
|
149
157
|
<Select
|
|
150
158
|
className={hasValueCell ? '!w-[var(--data-table-filter-op-width)] flex-shrink-0' : '!flex-1 !min-w-0'}
|
|
@@ -153,8 +161,8 @@ export function FilterRow({
|
|
|
153
161
|
value={condition.op}
|
|
154
162
|
onChange={onChangeOp}
|
|
155
163
|
disabled={!hasField}
|
|
156
|
-
placeholder=
|
|
157
|
-
aria-label=
|
|
164
|
+
placeholder={labels.operatorPlaceholder}
|
|
165
|
+
aria-label={labels.operatorAriaLabel}
|
|
158
166
|
/>
|
|
159
167
|
{hasValueCell && (
|
|
160
168
|
<FilterValuePicker
|
|
@@ -163,13 +171,14 @@ export function FilterRow({
|
|
|
163
171
|
onChange={onChangeValue}
|
|
164
172
|
colInfo={colInfo}
|
|
165
173
|
disabled={!hasField}
|
|
166
|
-
ariaLabel={colInfo
|
|
174
|
+
ariaLabel={labels.filterValueAriaLabel(colInfo?.label)}
|
|
175
|
+
labels={labels}
|
|
167
176
|
className="!flex-1 !min-w-0"
|
|
168
177
|
/>
|
|
169
178
|
)}
|
|
170
179
|
</FieldControlGroup>
|
|
171
180
|
{/* Trash 用 text Button — filter row 是 form-control row,Field 同高對齊(28 md) */}
|
|
172
|
-
<Button variant="text" size="sm" iconOnly startIcon={Trash2} aria-label=
|
|
181
|
+
<Button variant="text" size="sm" iconOnly startIcon={Trash2} aria-label={labels.deleteCondition} onClick={onRemove} />
|
|
173
182
|
</div>
|
|
174
183
|
)
|
|
175
184
|
}
|
|
@@ -180,6 +189,7 @@ export function GroupBlock({
|
|
|
180
189
|
index, group, rootConjunction, filterableColumns, fieldOptions,
|
|
181
190
|
onChangeRootConjunction, onChangeGroupConjunction,
|
|
182
191
|
onChangeCondition, onRemoveCondition, onAddCondition, onRemoveGroup,
|
|
192
|
+
canAddCondition, labels,
|
|
183
193
|
}: {
|
|
184
194
|
index: number
|
|
185
195
|
group: FilterGroup
|
|
@@ -192,11 +202,13 @@ export function GroupBlock({
|
|
|
192
202
|
onRemoveCondition: (condId: string) => void
|
|
193
203
|
onAddCondition: () => void
|
|
194
204
|
onRemoveGroup: () => void
|
|
205
|
+
canAddCondition: boolean
|
|
206
|
+
labels: DataTableFilterPanelLabels
|
|
195
207
|
}) {
|
|
196
208
|
return (
|
|
197
209
|
<div className="flex items-start gap-2">
|
|
198
210
|
<div className="pt-2">
|
|
199
|
-
<ConjunctionLabel index={index} conjunction={rootConjunction} onChange={onChangeRootConjunction} />
|
|
211
|
+
<ConjunctionLabel index={index} conjunction={rootConjunction} onChange={onChangeRootConjunction} labels={labels} />
|
|
200
212
|
</div>
|
|
201
213
|
{/* Group container 灰底 — `bg-muted`(`--muted` neutral-2,user 2026-05-09 拍板 Q3 A)。對齊 color.spec.md L651-654 「table header / tab / code block / skeleton」靜態低重要 surface semantic */}
|
|
202
214
|
<div className="flex-1 min-w-0 rounded-md bg-muted p-2 flex flex-col gap-2">
|
|
@@ -216,13 +228,18 @@ export function GroupBlock({
|
|
|
216
228
|
onChangeOp={(v) => onChangeCondition(cond.id, { op: v, value: '' })}
|
|
217
229
|
onChangeValue={(v) => onChangeCondition(cond.id, { value: v })}
|
|
218
230
|
onRemove={() => onRemoveCondition(cond.id)}
|
|
231
|
+
labels={labels}
|
|
219
232
|
/>
|
|
220
233
|
))}
|
|
221
234
|
{/* Q9 — text variant 對齊 inline 派 + 視覺輕量 */}
|
|
222
235
|
<div className="flex items-center justify-between">
|
|
223
|
-
<Button variant="text" size="sm" startIcon={Plus} onClick={onAddCondition}
|
|
236
|
+
<Button variant="text" size="sm" startIcon={Plus} onClick={onAddCondition} disabled={!canAddCondition}>
|
|
237
|
+
{labels.addNestedFilter}
|
|
238
|
+
</Button>
|
|
224
239
|
{group.children.length === 0 && (
|
|
225
|
-
<Button variant="text" size="sm" startIcon={Trash2} danger onClick={onRemoveGroup}
|
|
240
|
+
<Button variant="text" size="sm" startIcon={Trash2} danger onClick={onRemoveGroup}>
|
|
241
|
+
{labels.removeEmptyGroup}
|
|
242
|
+
</Button>
|
|
226
243
|
)}
|
|
227
244
|
</div>
|
|
228
245
|
</div>
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// @internal — DataTableFilterPanel chrome/operator/value-picker labels shared by
|
|
2
|
+
// panel orchestration, row/group rendering, and value-picker dispatch.
|
|
3
|
+
|
|
4
|
+
import type { DATE_RELATIVE_GROUPS, DATE_RELATIVE_OPTIONS } from './filter-operators'
|
|
5
|
+
|
|
6
|
+
type RelativeDateGroupKey = (typeof DATE_RELATIVE_GROUPS)[number]['key']
|
|
7
|
+
type RelativeDateValue = (typeof DATE_RELATIVE_OPTIONS)[number]['value']
|
|
8
|
+
|
|
9
|
+
export interface DataTableFilterPanelLabels {
|
|
10
|
+
title: string
|
|
11
|
+
resetToDefault: string
|
|
12
|
+
close: string
|
|
13
|
+
where: string
|
|
14
|
+
and: string
|
|
15
|
+
or: string
|
|
16
|
+
conjunctionAriaLabel: string
|
|
17
|
+
fieldPlaceholder: string
|
|
18
|
+
fieldAriaLabel: string
|
|
19
|
+
operatorPlaceholder: string
|
|
20
|
+
operatorAriaLabel: string
|
|
21
|
+
valuePlaceholder: string
|
|
22
|
+
numberPlaceholder: string
|
|
23
|
+
relativeDatePlaceholder: string
|
|
24
|
+
singleSelectPlaceholder: string
|
|
25
|
+
multiSelectPlaceholder: string
|
|
26
|
+
multiSelectEmptyText: string
|
|
27
|
+
peoplePlaceholder: string
|
|
28
|
+
peopleSearchPlaceholder: string
|
|
29
|
+
peopleSearchAriaLabel: string
|
|
30
|
+
peopleEmptyText: string
|
|
31
|
+
filterValueAriaLabel: (columnLabel?: string) => string
|
|
32
|
+
deleteCondition: string
|
|
33
|
+
addFilter: string
|
|
34
|
+
addGroup: string
|
|
35
|
+
addNestedFilter: string
|
|
36
|
+
removeEmptyGroup: string
|
|
37
|
+
/**
|
|
38
|
+
* Operator key → visible label override. Missing keys fall back to
|
|
39
|
+
* `OPERATOR_REGISTRY` labels, preserving the registry as the default SSOT.
|
|
40
|
+
*/
|
|
41
|
+
operatorLabels: Partial<Record<string, string>>
|
|
42
|
+
/** Relative-date group/value overrides; missing keys retain registry defaults. */
|
|
43
|
+
relativeDateGroupLabels: Partial<Record<RelativeDateGroupKey, string>>
|
|
44
|
+
relativeDateOptionLabels: Partial<Record<RelativeDateValue, string>>
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// code-quality-allow: dead-export — public i18n defaults, re-exported by panel.
|
|
48
|
+
export const DATA_TABLE_FILTER_PANEL_DEFAULT_LABELS: DataTableFilterPanelLabels = {
|
|
49
|
+
title: '篩選',
|
|
50
|
+
resetToDefault: '恢復預設',
|
|
51
|
+
close: '關閉',
|
|
52
|
+
where: 'Where',
|
|
53
|
+
and: 'And',
|
|
54
|
+
or: 'Or',
|
|
55
|
+
conjunctionAriaLabel: '連接詞 — 同 group 共用',
|
|
56
|
+
fieldPlaceholder: '選擇欄位',
|
|
57
|
+
fieldAriaLabel: '篩選欄位',
|
|
58
|
+
operatorPlaceholder: '運算子',
|
|
59
|
+
operatorAriaLabel: '篩選運算子',
|
|
60
|
+
valuePlaceholder: '輸入值…',
|
|
61
|
+
numberPlaceholder: '輸入數字…',
|
|
62
|
+
relativeDatePlaceholder: '選擇相對日期',
|
|
63
|
+
singleSelectPlaceholder: '選擇值',
|
|
64
|
+
multiSelectPlaceholder: '選擇值…',
|
|
65
|
+
multiSelectEmptyText: '沒有符合的值',
|
|
66
|
+
peoplePlaceholder: '選擇人員',
|
|
67
|
+
peopleSearchPlaceholder: '搜尋人員…',
|
|
68
|
+
peopleSearchAriaLabel: '搜尋篩選人員',
|
|
69
|
+
peopleEmptyText: '沒有符合的人員',
|
|
70
|
+
filterValueAriaLabel: (columnLabel) => columnLabel ? `${columnLabel} 篩選值` : '篩選值',
|
|
71
|
+
deleteCondition: '刪除',
|
|
72
|
+
addFilter: '加篩選',
|
|
73
|
+
addGroup: '加入篩選器',
|
|
74
|
+
addNestedFilter: '加入巢狀篩選',
|
|
75
|
+
removeEmptyGroup: '移除空群組',
|
|
76
|
+
operatorLabels: {},
|
|
77
|
+
relativeDateGroupLabels: {},
|
|
78
|
+
relativeDateOptionLabels: {},
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function resolveDataTableFilterPanelLabels(
|
|
82
|
+
overrides?: Partial<DataTableFilterPanelLabels>,
|
|
83
|
+
): DataTableFilterPanelLabels {
|
|
84
|
+
return {
|
|
85
|
+
...DATA_TABLE_FILTER_PANEL_DEFAULT_LABELS,
|
|
86
|
+
...overrides,
|
|
87
|
+
operatorLabels: {
|
|
88
|
+
...DATA_TABLE_FILTER_PANEL_DEFAULT_LABELS.operatorLabels,
|
|
89
|
+
...overrides?.operatorLabels,
|
|
90
|
+
},
|
|
91
|
+
relativeDateGroupLabels: {
|
|
92
|
+
...DATA_TABLE_FILTER_PANEL_DEFAULT_LABELS.relativeDateGroupLabels,
|
|
93
|
+
...overrides?.relativeDateGroupLabels,
|
|
94
|
+
},
|
|
95
|
+
relativeDateOptionLabels: {
|
|
96
|
+
...DATA_TABLE_FILTER_PANEL_DEFAULT_LABELS.relativeDateOptionLabels,
|
|
97
|
+
...overrides?.relativeDateOptionLabels,
|
|
98
|
+
},
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -31,6 +31,11 @@ import {
|
|
|
31
31
|
type FilterTreeFlat,
|
|
32
32
|
type FilterTreeNested,
|
|
33
33
|
} from './filter-tree'
|
|
34
|
+
import {
|
|
35
|
+
DATA_TABLE_FILTER_PANEL_DEFAULT_LABELS,
|
|
36
|
+
resolveDataTableFilterPanelLabels,
|
|
37
|
+
type DataTableFilterPanelLabels,
|
|
38
|
+
} from './data-table-filter-labels'
|
|
34
39
|
|
|
35
40
|
// Re-export public API from filter-tree(consumer 既有 import path 不變)
|
|
36
41
|
export {
|
|
@@ -40,6 +45,8 @@ export {
|
|
|
40
45
|
dataTableFilterMatch,
|
|
41
46
|
}
|
|
42
47
|
export type { Conjunction, FilterCondition, FilterGroup, FilterTree, FilterTreeFlat, FilterTreeNested }
|
|
48
|
+
export { DATA_TABLE_FILTER_PANEL_DEFAULT_LABELS }
|
|
49
|
+
export type { DataTableFilterPanelLabels }
|
|
43
50
|
|
|
44
51
|
/**
|
|
45
52
|
* DataTableFilterPanel — ClickUp-style 進階篩選 panel
|
|
@@ -123,6 +130,17 @@ const newEmptyGroup = (): FilterGroup => ({
|
|
|
123
130
|
children: [newEmptyCondition()],
|
|
124
131
|
})
|
|
125
132
|
|
|
133
|
+
const countFilterConditions = (tree: FilterTree): number =>
|
|
134
|
+
tree.mode === 'flat'
|
|
135
|
+
? tree.children.length
|
|
136
|
+
: tree.children.reduce((total, group) => total + group.children.length, 0)
|
|
137
|
+
|
|
138
|
+
const normalizeMaxConditions = (maxConditions: number | undefined): number => {
|
|
139
|
+
if (maxConditions === undefined || maxConditions === Number.POSITIVE_INFINITY) return Number.POSITIVE_INFINITY
|
|
140
|
+
if (!Number.isFinite(maxConditions)) return 0
|
|
141
|
+
return Math.max(0, Math.floor(maxConditions))
|
|
142
|
+
}
|
|
143
|
+
|
|
126
144
|
// ── Component Props ─────────────────────────────────────────────────────
|
|
127
145
|
|
|
128
146
|
export interface DataTableFilterPanelProps<TData> {
|
|
@@ -144,6 +162,14 @@ export interface DataTableFilterPanelProps<TData> {
|
|
|
144
162
|
/** Cell ⌄ menu「Filter by this」帶入的 column id(自動 add 一條 condition) */
|
|
145
163
|
prefilledColumnId?: string
|
|
146
164
|
onPrefillConsumed?: () => void
|
|
165
|
+
/**
|
|
166
|
+
* Condition leaf 上限(flat children 或 nested 所有 group.children 的總和)。
|
|
167
|
+
* 預設不限。到上限後所有 add 路徑與 prefill 都 fail closed;controlled value
|
|
168
|
+
* 已超過上限時保留原資料、不裁切,只停用後續新增。
|
|
169
|
+
*/
|
|
170
|
+
maxConditions?: number
|
|
171
|
+
/** Panel chrome/operator/value-picker labels. Nested maps are deep-merged with defaults. */
|
|
172
|
+
labels?: Partial<DataTableFilterPanelLabels>
|
|
147
173
|
onClose?: () => void
|
|
148
174
|
className?: string
|
|
149
175
|
}
|
|
@@ -158,6 +184,8 @@ function DataTableFilterPanelInner<TData>({
|
|
|
158
184
|
defaultValue,
|
|
159
185
|
prefilledColumnId,
|
|
160
186
|
onPrefillConsumed,
|
|
187
|
+
maxConditions,
|
|
188
|
+
labels: labelsOverride,
|
|
161
189
|
onClose,
|
|
162
190
|
className,
|
|
163
191
|
}: DataTableFilterPanelProps<TData>, ref: React.ForwardedRef<HTMLDivElement>): React.ReactElement {
|
|
@@ -166,6 +194,13 @@ function DataTableFilterPanelInner<TData>({
|
|
|
166
194
|
() => filterableColumns.map((c) => ({ value: c.id, label: c.label })),
|
|
167
195
|
[filterableColumns],
|
|
168
196
|
)
|
|
197
|
+
const labels = React.useMemo(
|
|
198
|
+
() => resolveDataTableFilterPanelLabels(labelsOverride),
|
|
199
|
+
[labelsOverride],
|
|
200
|
+
)
|
|
201
|
+
const conditionCount = countFilterConditions(value)
|
|
202
|
+
const normalizedMaxConditions = normalizeMaxConditions(maxConditions)
|
|
203
|
+
const canAddCondition = conditionCount < normalizedMaxConditions
|
|
169
204
|
// K13 後 firstCol 不再被 add* 消費(改用 newEmpty*),這裡只留 prefill effect 用(已直接讀 prefilledColumnId)。
|
|
170
205
|
|
|
171
206
|
// **G fix(2026-05-04 v2)**:initial-mount 預設 1 empty row(field 未選 → op+value 自動 disabled)
|
|
@@ -179,6 +214,7 @@ function DataTableFilterPanelInner<TData>({
|
|
|
179
214
|
initialMountDoneRef.current = true
|
|
180
215
|
if (filterableColumns.length === 0) return
|
|
181
216
|
if (value.children.length > 0) return
|
|
217
|
+
if (!canAddCondition) return
|
|
182
218
|
if (value.mode === 'flat') {
|
|
183
219
|
onChange({ ...value, children: [newEmptyCondition()] } as FilterTreeFlat)
|
|
184
220
|
} else {
|
|
@@ -191,7 +227,7 @@ function DataTableFilterPanelInner<TData>({
|
|
|
191
227
|
React.useEffect(() => {
|
|
192
228
|
if (!prefilledColumnId) return
|
|
193
229
|
const colInfo = filterableColumns.find((c) => c.id === prefilledColumnId)
|
|
194
|
-
if (colInfo) {
|
|
230
|
+
if (colInfo && canAddCondition) {
|
|
195
231
|
const cond: FilterCondition = {
|
|
196
232
|
kind: 'cond',
|
|
197
233
|
id: newId(),
|
|
@@ -234,7 +270,7 @@ function DataTableFilterPanelInner<TData>({
|
|
|
234
270
|
onChange({ ...flatTree, children: flatTree.children.filter((c) => c.id !== id) })
|
|
235
271
|
}
|
|
236
272
|
const addFlatCondition = () => {
|
|
237
|
-
if (!flatTree) return
|
|
273
|
+
if (!flatTree || !canAddCondition) return
|
|
238
274
|
// K13 fix(2026-05-04):加篩選 → empty row(field 未選 → op+value disabled)
|
|
239
275
|
// World-class:Notion / Coda / ClickUp 不 auto-select;對齊 initial mount canonical
|
|
240
276
|
onChange({ ...flatTree, children: [...flatTree.children, newEmptyCondition()] })
|
|
@@ -275,7 +311,7 @@ function DataTableFilterPanelInner<TData>({
|
|
|
275
311
|
}
|
|
276
312
|
// K13 fix(2026-05-04):同 addFlatCondition,巢狀內加條件也 empty row
|
|
277
313
|
const addConditionToGroup = (groupId: string) => {
|
|
278
|
-
if (!nestedTree) return
|
|
314
|
+
if (!nestedTree || !canAddCondition) return
|
|
279
315
|
onChange({
|
|
280
316
|
...nestedTree,
|
|
281
317
|
children: nestedTree.children.map((g) =>
|
|
@@ -288,7 +324,7 @@ function DataTableFilterPanelInner<TData>({
|
|
|
288
324
|
onChange({ ...nestedTree, children: nestedTree.children.filter((g) => g.id !== groupId) })
|
|
289
325
|
}
|
|
290
326
|
const addGroup = () => {
|
|
291
|
-
if (!nestedTree) return
|
|
327
|
+
if (!nestedTree || !canAddCondition) return
|
|
292
328
|
// K13:加群組也用 empty group
|
|
293
329
|
onChange({ ...nestedTree, children: [...nestedTree.children, newEmptyGroup()] })
|
|
294
330
|
}
|
|
@@ -321,14 +357,14 @@ function DataTableFilterPanelInner<TData>({
|
|
|
321
357
|
)}>
|
|
322
358
|
{/* Popover 派輕量 chrome — slot 走 COMPACT_HEADER_SLOT(=21,衍生自 PopoverTitle text-body line-box),header 自然 ~45px */}
|
|
323
359
|
<SurfaceHeader className={COMPACT_HEADER_SLOT}>
|
|
324
|
-
<PopoverTitle className="flex-1"
|
|
360
|
+
<PopoverTitle className="flex-1">{labels.title}</PopoverTitle>
|
|
325
361
|
{/* Refresh icon — 只在 value ≠ defaultValue 時顯示(對齊 sort modified-from-default UX)
|
|
326
362
|
含 ButtonDivider 對齊「欄位顯示」+「排序」chrome corner action canonical(2026-05-04) */}
|
|
327
363
|
{defaultValue && !isFilterTreeEqual(value, defaultValue) && (
|
|
328
364
|
<>
|
|
329
365
|
<Button
|
|
330
366
|
variant="text" size="sm" iconOnly startIcon={RotateCcw}
|
|
331
|
-
aria-label=
|
|
367
|
+
aria-label={labels.resetToDefault}
|
|
332
368
|
onClick={() => onChange(defaultValue)}
|
|
333
369
|
/>
|
|
334
370
|
{onClose && <ButtonDivider />}
|
|
@@ -336,7 +372,7 @@ function DataTableFilterPanelInner<TData>({
|
|
|
336
372
|
)}
|
|
337
373
|
{onClose && (
|
|
338
374
|
<PopoverClose asChild>
|
|
339
|
-
<Button data-dismiss iconOnly dismiss size="sm" startIcon={XIcon} aria-label=
|
|
375
|
+
<Button data-dismiss iconOnly dismiss size="sm" startIcon={XIcon} aria-label={labels.close} onClick={onClose} />
|
|
340
376
|
</PopoverClose>
|
|
341
377
|
)}
|
|
342
378
|
</SurfaceHeader>
|
|
@@ -361,6 +397,7 @@ function DataTableFilterPanelInner<TData>({
|
|
|
361
397
|
onChangeOp={(v) => updateFlatCondition(cond.id, { op: v, value: '' })}
|
|
362
398
|
onChangeValue={(v) => updateFlatCondition(cond.id, { value: v })}
|
|
363
399
|
onRemove={() => removeFlatCondition(cond.id)}
|
|
400
|
+
labels={labels}
|
|
364
401
|
/>
|
|
365
402
|
))}
|
|
366
403
|
|
|
@@ -378,6 +415,8 @@ function DataTableFilterPanelInner<TData>({
|
|
|
378
415
|
onRemoveCondition={(condId) => removeGroupCondition(group.id, condId)}
|
|
379
416
|
onAddCondition={() => addConditionToGroup(group.id)}
|
|
380
417
|
onRemoveGroup={() => removeGroup(group.id)}
|
|
418
|
+
canAddCondition={canAddCondition}
|
|
419
|
+
labels={labels}
|
|
381
420
|
/>
|
|
382
421
|
))}
|
|
383
422
|
|
|
@@ -388,8 +427,9 @@ function DataTableFilterPanelInner<TData>({
|
|
|
388
427
|
<Button
|
|
389
428
|
variant="tertiary" size="sm" startIcon={Plus}
|
|
390
429
|
onClick={value.mode === 'flat' ? addFlatCondition : addGroup}
|
|
430
|
+
disabled={!canAddCondition}
|
|
391
431
|
>
|
|
392
|
-
{value.mode === 'nested' ?
|
|
432
|
+
{value.mode === 'nested' ? labels.addGroup : labels.addFilter}
|
|
393
433
|
</Button>
|
|
394
434
|
</div>
|
|
395
435
|
</SurfaceBody>
|