@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
|
@@ -67,23 +67,65 @@ const radixItemClass = [
|
|
|
67
67
|
].join(' ')
|
|
68
68
|
|
|
69
69
|
// ── Root ──
|
|
70
|
-
|
|
70
|
+
// Radix modal menu 會在 open 時把 trigger 所在的 app subtree 設成 aria-hidden。瀏覽器仍可
|
|
71
|
+
// 讓 subtree 內原本的 trigger 留在 sequential focus order,axe 因而正確報
|
|
72
|
+
// aria-hidden-focus。由 Root SSOT 對所有 DropdownMenu 統一同步 trigger tab stop;Portal
|
|
73
|
+
// 仍保留 React context,所以不需要 consumer/story 各自 patch。
|
|
74
|
+
const DropdownMenuModalOpenContext = React.createContext(false)
|
|
75
|
+
|
|
76
|
+
const DropdownMenu = ({
|
|
77
|
+
open,
|
|
78
|
+
defaultOpen,
|
|
79
|
+
onOpenChange,
|
|
80
|
+
modal = true,
|
|
81
|
+
...props
|
|
82
|
+
}: React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Root>) => {
|
|
83
|
+
const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen ?? false)
|
|
84
|
+
const resolvedOpen = open ?? uncontrolledOpen
|
|
85
|
+
const handleOpenChange = React.useCallback(
|
|
86
|
+
(nextOpen: boolean) => {
|
|
87
|
+
if (open === undefined) setUncontrolledOpen(nextOpen)
|
|
88
|
+
onOpenChange?.(nextOpen)
|
|
89
|
+
},
|
|
90
|
+
[onOpenChange, open],
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<DropdownMenuModalOpenContext.Provider value={modal && resolvedOpen}>
|
|
95
|
+
<DropdownMenuPrimitive.Root
|
|
96
|
+
open={open}
|
|
97
|
+
defaultOpen={defaultOpen}
|
|
98
|
+
onOpenChange={handleOpenChange}
|
|
99
|
+
modal={modal}
|
|
100
|
+
{...props}
|
|
101
|
+
/>
|
|
102
|
+
</DropdownMenuModalOpenContext.Provider>
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
DropdownMenu.displayName = DropdownMenuPrimitive.Root.displayName
|
|
106
|
+
|
|
71
107
|
const DropdownMenuTrigger = React.forwardRef<
|
|
72
108
|
React.ElementRef<typeof DropdownMenuPrimitive.Trigger>,
|
|
73
109
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Trigger>
|
|
74
|
-
>(({ className, ...props }, ref) =>
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
110
|
+
>(({ className, tabIndex, ...props }, ref) => {
|
|
111
|
+
const modalOpen = React.useContext(DropdownMenuModalOpenContext)
|
|
112
|
+
return (
|
|
113
|
+
<DropdownMenuPrimitive.Trigger
|
|
114
|
+
ref={ref}
|
|
115
|
+
// modal open 時 trigger 位於 Radix aria-hidden subtree:移出 sequential focus order;
|
|
116
|
+
// 關閉時仍由 Radix programmatically focus,且恢復 consumer 原 tabIndex。
|
|
117
|
+
tabIndex={modalOpen ? -1 : tabIndex}
|
|
118
|
+
// 2026-07-05:非 asChild 裸用需可見 focus 指示(WCAG 2.4.7)——原 `outline-none` 讓裸 Trigger
|
|
119
|
+
// 完全無 focus ring。消費 Button focus canonical(button.tsx buttonVariants base)。
|
|
120
|
+
// asChild+Button 場景同名 class 重複,無影響。
|
|
121
|
+
className={cn(
|
|
122
|
+
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1',
|
|
123
|
+
className,
|
|
124
|
+
)}
|
|
125
|
+
{...props}
|
|
126
|
+
/>
|
|
127
|
+
)
|
|
128
|
+
})
|
|
87
129
|
DropdownMenuTrigger.displayName = DropdownMenuPrimitive.Trigger.displayName
|
|
88
130
|
// DropdownMenuGroup — 對齊 MenuGroup 的 group separation 設計語言
|
|
89
131
|
//
|
|
@@ -178,7 +220,7 @@ const DropdownMenuContent = React.forwardRef<
|
|
|
178
220
|
<RowSizeProvider value={size}>
|
|
179
221
|
{/* body 恆在 flex-1 ScrollArea 內:超過 clamped 高度即跨-OS 一致捲動(不吃寬度);
|
|
180
222
|
短選單 flex-1 貼內容不塌(同 HoverCard/Popover/DataTable panel 已驗)。py-2 在內層,整 padded 區可捲。 */}
|
|
181
|
-
<ScrollArea className="flex-1 min-h-0">
|
|
223
|
+
<ScrollArea className="flex-1 min-h-0" viewportTabIndex={null}>
|
|
182
224
|
<div className="py-2">{children}</div>
|
|
183
225
|
</ScrollArea>
|
|
184
226
|
</RowSizeProvider>
|
|
@@ -413,7 +455,7 @@ const DropdownMenuLabel = React.forwardRef<
|
|
|
413
455
|
size={size}
|
|
414
456
|
header
|
|
415
457
|
role="presentation"
|
|
416
|
-
className="pointer-events-none"
|
|
458
|
+
className="pointer-events-none text-fg-secondary"
|
|
417
459
|
>
|
|
418
460
|
{children}
|
|
419
461
|
</MenuItem>
|
|
@@ -44,6 +44,11 @@ export interface FieldContextValue {
|
|
|
44
44
|
required: boolean
|
|
45
45
|
invalid: boolean
|
|
46
46
|
size: FieldSize
|
|
47
|
+
/** `size` 是被決定過的(consumer 顯式傳 / control 宣告偏好)還是 Field 的預設 md。
|
|
48
|
+
* 預設 md 不得壓過 control 自己宣告的 fallback —— 詳 useResolvedFieldSize 與 field.spec.md。 */
|
|
49
|
+
sizeExplicit: boolean
|
|
50
|
+
/** Control 於掛載時宣告偏好尺寸(wrapper/HOC 遮蔽靜態 `fieldPreferredSize` 時的補位通道)。 */
|
|
51
|
+
registerPreferredSize?: (preferred: FieldSize) => void
|
|
47
52
|
orientation: FieldOrientation
|
|
48
53
|
controlLayout: FieldControlLayout
|
|
49
54
|
hasFieldWrapper: true
|
|
@@ -166,7 +171,26 @@ export function useResolvedFieldSize<T extends string = FieldSize>(sizeProp?: T
|
|
|
166
171
|
// generic T(預設 FieldSize)讓非-input 控件(SegmentedControl/Rating 的 'xs'|'sm'|'md'|'lg' 超集、各自 fallback)
|
|
167
172
|
// 也走同一 SSOT resolution。fieldCtx.size/surfaceSize 為 FieldSize(T 的子集)→ widen cast 安全。
|
|
168
173
|
// fallback 未傳預設 'md'(input-class 控件向後相容);Rating 傳 'xs'、SegmentedControl 傳 'md'。
|
|
169
|
-
|
|
174
|
+
//
|
|
175
|
+
// 2026-07-28:Field 的 size 只有在 consumer 顯式指定、或 Field 已解析出 control 偏好時才算「決定過」
|
|
176
|
+
// (`sizeExplicit`)。否則那只是 Field 的預設 md,不該壓過控件自己宣告的 fallback ——
|
|
177
|
+
// 舊行為讓「InlineEdit 被包一層 wrapper」時靜默退回 md(詳 field.spec.md 偏好尺寸 SSOT)。
|
|
178
|
+
const contextSize = fieldCtx && fieldCtx.sizeExplicit ? (fieldCtx.size as T | undefined) : undefined
|
|
179
|
+
return (sizeProp ?? contextSize ?? fallback ?? (fieldCtx?.size as T | undefined) ?? (surfaceSize as T | undefined) ?? ('md' as T))
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Control 向外層 Field 宣告偏好尺寸(render-time)。靜態 `fieldPreferredSize` 只有在 control 是
|
|
184
|
+
* Field 的**直接子元件**時才偵測得到;包 wrapper / `React.memo` / HOC 會遮蔽它。本 hook 讓 control
|
|
185
|
+
* 在掛載時主動註冊,Field 的**控件槽高度**因此也能跟上(控件本身的尺寸則由 `useResolvedFieldSize`
|
|
186
|
+
* 同步解析,不等這次註冊)。
|
|
187
|
+
*/
|
|
188
|
+
export function useRegisterFieldPreferredSize(preferred?: FieldSize | null): void {
|
|
189
|
+
const fieldCtx = React.useContext(FieldContext)
|
|
190
|
+
const register = fieldCtx?.registerPreferredSize
|
|
191
|
+
React.useEffect(() => {
|
|
192
|
+
if (preferred && register) register(preferred)
|
|
193
|
+
}, [preferred, register])
|
|
170
194
|
}
|
|
171
195
|
|
|
172
196
|
/**
|
|
@@ -388,5 +388,11 @@ export const fieldViewGeometry = (size: 'sm' | 'md' | 'lg', multiline?: boolean)
|
|
|
388
388
|
size === 'sm' && 'min-h-[var(--field-height-sm)]',
|
|
389
389
|
size === 'md' && 'min-h-[var(--field-height-md)]',
|
|
390
390
|
size === 'lg' && 'min-h-[var(--field-height-lg)]',
|
|
391
|
-
|
|
391
|
+
// 多行 view py 必等於 Textarea edit py(同一 --field-control-py-* token),否則
|
|
392
|
+
// InlineEdit 多行 read↔edit 垂直跳位;scripts/inline-edit-view-geometry-invariant.mjs 機械鎖。
|
|
393
|
+
multiline && 'items-start',
|
|
394
|
+
multiline && size === 'sm' && 'py-[var(--field-control-py-sm)]',
|
|
395
|
+
multiline && size === 'md' && 'py-[var(--field-control-py-md)]',
|
|
396
|
+
multiline && size === 'lg' && 'py-[var(--field-control-py-lg)]',
|
|
397
|
+
!multiline && 'items-center',
|
|
392
398
|
)
|
|
@@ -214,6 +214,12 @@ Field 透過 Context 暴露以下狀態給子元件(Primitive 可以透過 `us
|
|
|
214
214
|
| `orientation` | FieldLabel 的垂直對齊策略(horizontal 模式套用 padding-top 公式) |
|
|
215
215
|
| `hasFieldWrapper` | Primitive 讀到此旗標時應忽略自己的 label / description prop,由 FieldLabel / FieldDescription 接管 |
|
|
216
216
|
|
|
217
|
+
### Control 偏好尺寸(`fieldPreferredSize`)SSOT
|
|
218
|
+
|
|
219
|
+
Field 未收到顯式 `size` 時,依序取 **顯式 prop → control 宣告的 `fieldPreferredSize` → `md`**(`field.tsx` `detectPreferredSize`)。Control 以自身檔案的 static 宣告偏好(唯一現行宣告者 = InlineEdit `'sm'`,owner 句 `InlineEdit/inline-edit.spec.md`)。
|
|
220
|
+
|
|
221
|
+
**失效語意(硬規則)**:偏好只在 control 是 Field 的**直接子元件**時才偵測得到。包一層 wrapper 元件、`React.memo`、HOC 都會遮蔽 static,Field 退回 `md`,且 `fieldCtx.size` 在 `useResolvedFieldSize` 的優先序中**高於** control 自己的 fallback,因此 control 再也搶不回偏好尺寸。**Consumer 對策**:把 state 提升到 Field 外層讓 control 保持直接子元件,或顯式傳 `size`。**禁**:為了包 wrapper 而預期偏好仍然生效。
|
|
222
|
+
|
|
217
223
|
### Primitive 的 Field-aware 行為
|
|
218
224
|
|
|
219
225
|
**Checkbox / Switch / RadioItem** 等 primitive 自己有 `label` / `description` props 可用於**獨立使用場景**(不在 Field 內),但在 Field context 內時:
|
|
@@ -335,7 +341,7 @@ Button 的 height 與 `field-height` 共用同一組 token,放進 inline control
|
|
|
335
341
|
## 邊界案例
|
|
336
342
|
|
|
337
343
|
- **空 Field(無 control children)**:code 不擋——control area 渲染成空容器。Field 是佈局容器,無 control 即無意義,屬 consumer 誤用
|
|
338
|
-
- **RTL
|
|
344
|
+
- **RTL**:不支援;全域 LTR-only compatibility contract 見 `packages/design-system/README.md#compatibility-matrix`。horizontal label / suffix alignment 不在本檔另立支援決策。
|
|
339
345
|
|
|
340
346
|
---
|
|
341
347
|
|
|
@@ -458,6 +464,7 @@ Field 內的資料輸入控件(Input / NumberInput / DatePicker / Select / Com
|
|
|
458
464
|
- `field-control-group.spec.md`
|
|
459
465
|
- `field-controls.spec.md`
|
|
460
466
|
- `form-validation.spec.md`
|
|
467
|
+
- `inline-edit.spec.md`
|
|
461
468
|
- `input.spec.md`
|
|
462
469
|
- `radio-group.spec.md`
|
|
463
470
|
- `switch.spec.md`
|
|
@@ -216,7 +216,16 @@ const Field = React.forwardRef<HTMLDivElement, FieldProps>(
|
|
|
216
216
|
|
|
217
217
|
// 解析 size:consumer 顯式 > control primitive 偏好(如 InlineEdit='sm')> 預設 md
|
|
218
218
|
// (對齊 controlLayout 三層優先序;InlineEdit 進 Field 不指定 size 時自動收 sm)
|
|
219
|
-
|
|
219
|
+
// 靜態偵測只看得到直接子元件;包 wrapper / memo / HOC 時由 control 於掛載時註冊補位,
|
|
220
|
+
// 讓 Field 的控件槽高度也跟上(control 自身尺寸走 useResolvedFieldSize 同步解析)。
|
|
221
|
+
const [registeredSize, setRegisteredSize] = React.useState<FieldSize | null>(null)
|
|
222
|
+
const registerPreferredSize = React.useCallback((preferred: FieldSize) => {
|
|
223
|
+
setRegisteredSize((current) => (current === preferred ? current : preferred))
|
|
224
|
+
}, [])
|
|
225
|
+
const detectedSize = detectPreferredSize(controlNodes)
|
|
226
|
+
const resolvedPreferredSize = sizeProp ?? detectedSize ?? registeredSize
|
|
227
|
+
const size: FieldSize = resolvedPreferredSize ?? 'md'
|
|
228
|
+
const sizeExplicit = resolvedPreferredSize != null
|
|
220
229
|
|
|
221
230
|
const contextValue = React.useMemo<FieldContextValue>(
|
|
222
231
|
() => ({
|
|
@@ -230,11 +239,13 @@ const Field = React.forwardRef<HTMLDivElement, FieldProps>(
|
|
|
230
239
|
required,
|
|
231
240
|
invalid,
|
|
232
241
|
size,
|
|
242
|
+
sizeExplicit,
|
|
243
|
+
registerPreferredSize,
|
|
233
244
|
orientation,
|
|
234
245
|
controlLayout,
|
|
235
246
|
hasFieldWrapper: true,
|
|
236
247
|
}),
|
|
237
|
-
[id, labelId, descriptionId, errorId, mode, variant, disabled, required, invalid, size, orientation, controlLayout]
|
|
248
|
+
[id, labelId, descriptionId, errorId, mode, variant, disabled, required, invalid, size, sizeExplicit, registerPreferredSize, orientation, controlLayout]
|
|
238
249
|
)
|
|
239
250
|
|
|
240
251
|
// Control area:兩種佈局模型,「第一行內容中線」都錨在 field-height/2,
|
|
@@ -426,6 +437,7 @@ const FieldLabel = React.forwardRef<HTMLLabelElement, FieldLabelProps>(
|
|
|
426
437
|
<button
|
|
427
438
|
type="button"
|
|
428
439
|
aria-label={info}
|
|
440
|
+
// 弱化 icon hover 一階(inline-action.spec.md「Icon 色彩」;2026-07-30 前全 DS 唯一合規處)
|
|
429
441
|
className="inline-flex items-center text-fg-muted hover:text-fg-secondary bg-transparent border-0 p-0 cursor-pointer"
|
|
430
442
|
>
|
|
431
443
|
<InfoIcon size={16} aria-hidden />
|
|
@@ -53,14 +53,14 @@ export const Overview: Story = {
|
|
|
53
53
|
),
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
/*
|
|
56
|
+
/* Export id 保留 corpus 穩定;此矩陣只改 child size,FieldControlGroup 本身沒有 size API。 */
|
|
57
57
|
export const SizeMatrix: Story = {
|
|
58
|
-
name: '
|
|
58
|
+
name: '子元件尺寸對齊',
|
|
59
59
|
render: () => (
|
|
60
60
|
<div className="flex flex-col gap-6 w-[420px]">
|
|
61
61
|
{(['sm', 'md', 'lg'] as const).map(size => (
|
|
62
62
|
<div key={size}>
|
|
63
|
-
<p className="text-caption text-fg-muted mb-2">size="{size}"
|
|
63
|
+
<p className="text-caption text-fg-muted mb-2">children size="{size}"(group 無 size prop)</p>
|
|
64
64
|
<FieldControlGroup block>
|
|
65
65
|
<Select size={size} className="!w-[120px]" options={FIELDS} value="name" onChange={() => {}} aria-label="篩選欄位" />
|
|
66
66
|
<Input size={size} className="flex-1" defaultValue="無線滑鼠" aria-label="篩選值" />
|
|
@@ -52,7 +52,6 @@ benchmark:
|
|
|
52
52
|
|
|
53
53
|
```ts
|
|
54
54
|
interface FieldControlGroupProps extends HTMLAttributes<HTMLDivElement> {
|
|
55
|
-
size?: 'sm' | 'md' | 'lg' // default 'md';目前為 no-op(FCG 不傳遞),size 由各 child 自管 — 見「Size cascade」
|
|
56
55
|
block?: boolean // 對齊 Ant Space.Compact `block`,默 false=inline-flex
|
|
57
56
|
}
|
|
58
57
|
```
|
|
@@ -87,7 +86,7 @@ interface FieldControlGroupProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
87
86
|
|
|
88
87
|
## Size
|
|
89
88
|
|
|
90
|
-
**FCG `size` prop
|
|
89
|
+
**FCG 不暴露 `size` prop**:本元件只負責 border-collapse 接合(border / radius / z-index),不傳遞 size 給 children(無 Context Provider / 無 cloneElement)。未實作的 group-level size 不預佔 API。Children 的尺寸完全由 child 自己決定 — 以下三種來源:
|
|
91
90
|
|
|
92
91
|
**繼承 Field 尺寸**(包進 Field 當 control slot):children 各自透過 `useFieldContext().size` 讀外層 `<Field>` 的 size(是 Field 的 context,不是 FCG 的)。
|
|
93
92
|
```tsx
|
|
@@ -100,7 +99,7 @@ interface FieldControlGroupProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
100
99
|
</Field>
|
|
101
100
|
```
|
|
102
101
|
|
|
103
|
-
**Standalone 逐一設尺寸**:**逐一給每個 child 設 `size
|
|
102
|
+
**Standalone 逐一設尺寸**:**逐一給每個 child 設 `size`**。
|
|
104
103
|
```tsx
|
|
105
104
|
<FieldControlGroup>
|
|
106
105
|
<Select size="md" className="!w-[120px]" options={fields} />
|
|
@@ -158,4 +158,5 @@ export const FilterRow: Story = {
|
|
|
158
158
|
},
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
-
/* @story-trait-rationale: AllSizes retired per F migration 2026-05-15 — anatomy.stories.tsx
|
|
161
|
+
/* @story-trait-rationale: AllSizes retired per F migration 2026-05-15 — anatomy.stories.tsx 的
|
|
162
|
+
ChildSizeAlignment 只展示 children 尺寸接合;FieldControlGroup 本身不擁有 size API。 */
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
//
|
|
10
10
|
import * as React from 'react'
|
|
11
11
|
import { cn } from '@/lib/utils'
|
|
12
|
-
import type { FieldSize } from '@/design-system/components/Field/field-context'
|
|
13
12
|
|
|
14
13
|
/**
|
|
15
14
|
* FieldControlGroup — 多個 Field controls 視覺接合成一個 input frame
|
|
@@ -26,10 +25,10 @@ import type { FieldSize } from '@/design-system/components/Field/field-context'
|
|
|
26
25
|
* - z-index:default 2 / hover|focus|focus-within 3 / disabled 0
|
|
27
26
|
* - Container `display: inline-flex`(Ant default)/ `block` prop → `display: flex; width: 100%`
|
|
28
27
|
*
|
|
29
|
-
* **Size**(FCG
|
|
28
|
+
* **Size**(FCG 不擁有 size API;不傳遞 size 給 children,無 Context Provider / 無 cloneElement;見 spec「Size」段):
|
|
30
29
|
* - children 尺寸完全由 child 自己決定;FCG 只負責 border-collapse 接合(border / radius / z-index)
|
|
31
30
|
* - Mode A:整個 FieldControlGroup 包進 Field 當 control slot,children 各自讀外層 `<Field>` 的 context size(是 Field 的 context,不是 FCG 的)
|
|
32
|
-
* - Mode B:standalone 用,逐一給每個 child 設 `size`
|
|
31
|
+
* - Mode B:standalone 用,逐一給每個 child 設 `size`
|
|
33
32
|
*
|
|
34
33
|
* **Width 配置**(Ant Space.Compact W-A canonical):
|
|
35
34
|
* - 子 controls 自管 width(`className="w-[140px]"` / `style={{width:120}}` / `flex-1` etc.)
|
|
@@ -62,14 +61,12 @@ import type { FieldSize } from '@/design-system/components/Field/field-context'
|
|
|
62
61
|
*/
|
|
63
62
|
|
|
64
63
|
export interface FieldControlGroupProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
65
|
-
/** no-op — FCG 不傳遞 size 給 children;Mode A 由 children 讀外層 Field context、Mode B 逐一給每個 child 設 size(見 spec「Size」段) */
|
|
66
|
-
size?: FieldSize
|
|
67
64
|
/** Block 模式:width 100% 撐滿 parent(對齊 Ant Space.Compact `block` prop) */
|
|
68
65
|
block?: boolean
|
|
69
66
|
}
|
|
70
67
|
|
|
71
68
|
const FieldControlGroup = React.forwardRef<HTMLDivElement, FieldControlGroupProps>(
|
|
72
|
-
({
|
|
69
|
+
({ block = false, className, children, ...props }, ref) => {
|
|
73
70
|
return (
|
|
74
71
|
<div
|
|
75
72
|
ref={ref}
|
|
@@ -116,14 +113,14 @@ FieldControlGroup.displayName = 'FieldControlGroup'
|
|
|
116
113
|
// Story auto-compile metadata — Phase 4 migration(2026-05-10 #12 task complete)
|
|
117
114
|
// Per scripts/compile-stories.mjs --check。FieldControlGroup is self-contained
|
|
118
115
|
// structural wrapper(border-collapse pattern for Field controls)。
|
|
119
|
-
// **No own sizes** — size prop
|
|
120
|
-
// 無 cloneElement;children 尺寸自管,見上方 docblock「Size」段
|
|
116
|
+
// **No own sizes** — 不暴露 size prop(不傳遞給 children,無 Context Provider /
|
|
117
|
+
// 無 cloneElement;children 尺寸自管,見上方 docblock「Size」段),
|
|
121
118
|
// so sizes:{} matches spec frontmatter (no sizes declared)。
|
|
122
119
|
export const fieldControlGroupMeta = {
|
|
123
120
|
component: 'FieldControlGroup',
|
|
124
121
|
family: 'self-contained',
|
|
125
122
|
variants: {},
|
|
126
|
-
sizes: {}, // self-contained wrapper;
|
|
123
|
+
sizes: {}, // self-contained wrapper;children 尺寸自管
|
|
127
124
|
states: ['default', 'children-hover', 'children-focus', 'children-disabled'],
|
|
128
125
|
tokens: {
|
|
129
126
|
bg: [], // structural wrapper has no own bg
|
|
@@ -459,7 +459,7 @@ Passive status icon 置中於 action-sized 容器,hover 時 active action 填滿
|
|
|
459
459
|
- **超長檔名**:label 經 `ItemContent` 預設 `truncate` 單行截斷(ellipsis),不換行。
|
|
460
460
|
- **過長 description**:自由換行不截斷(FileItem 未傳 `descriptionClamp`);rich 多行 desc 時 progress bar 隨內容下移並保 8px min gap(見 Rich layout invariant)。
|
|
461
461
|
- **progress 精度**:傳入值直接顯示(rich 的 `%` 文字與 bar 同源),元件不四捨五入;`completed` 強制 100。
|
|
462
|
-
- **RTL
|
|
462
|
+
- **RTL**:不支援;全域 LTR-only compatibility contract 見 `packages/design-system/README.md#compatibility-matrix`。compact progress offset 維持 physical direction。
|
|
463
463
|
- **Dark mode**:走 semantic token 自動 adapt。無 `disabled` prop(展示型 row,非 form control)。
|
|
464
464
|
|
|
465
465
|
---
|
|
@@ -82,7 +82,7 @@ export const HoverSwap = {
|
|
|
82
82
|
</div>
|
|
83
83
|
{/* Rich border card list:無外框 + `gap-2`(item-anatomy「連續 item 貼邊合法性」) */}
|
|
84
84
|
<div className="flex flex-col gap-2">
|
|
85
|
-
<FileItem mode="rich" name="Q1 營收報表.xlsx" status="completed"
|
|
85
|
+
<FileItem mode="rich" name="Q1 營收報表.xlsx" status="completed" data-visual-hover-target
|
|
86
86
|
description="Uploaded to URL" thumbnailSrc="https://i.pravatar.cc/80?u=xls"
|
|
87
87
|
onDownload={noop} actions={deleteBtn} />
|
|
88
88
|
<FileItem mode="rich" name="合約草案 v3.pdf" status="error"
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// @story-trait-rationale: hasInteractiveStates 的 Disabled / States 由 anatomy.stories.tsx StateBehavior auto-compile owns(2026-05-15 F-migration);showcase 層展示真實上傳 / 自訂內容情境。
|
|
2
2
|
import type { Meta } from '@storybook/react'
|
|
3
|
+
import { within } from '@storybook/test'
|
|
3
4
|
import { useState } from 'react'
|
|
4
5
|
import { Image as ImageIcon, X } from 'lucide-react'
|
|
5
6
|
import { FileUpload } from './file-upload'
|
|
@@ -116,6 +117,37 @@ export const WithFileList = {
|
|
|
116
117
|
},
|
|
117
118
|
}
|
|
118
119
|
|
|
120
|
+
// beta.97 弱化 icon hover 一階的真實互動證據:
|
|
121
|
+
// FileUpload 內建 files 清單自己渲染 remove Button,story 不以 className 模擬 hover。
|
|
122
|
+
export const RemoveHoverState = {
|
|
123
|
+
name: '移除檔案懸停狀態',
|
|
124
|
+
tags: ['!autodocs'],
|
|
125
|
+
render: () => (
|
|
126
|
+
<div className="max-w-lg">
|
|
127
|
+
<FileUpload
|
|
128
|
+
title="上傳季度報告"
|
|
129
|
+
description="PDF,單檔最大 20 MB"
|
|
130
|
+
files={[
|
|
131
|
+
{
|
|
132
|
+
id: 'quarterly-report',
|
|
133
|
+
name: '2026-Q2-季度報告.pdf',
|
|
134
|
+
size: 2_500_000,
|
|
135
|
+
status: 'completed',
|
|
136
|
+
},
|
|
137
|
+
]}
|
|
138
|
+
fileListMode="compact"
|
|
139
|
+
onRemove={noop}
|
|
140
|
+
onUpload={noop}
|
|
141
|
+
/>
|
|
142
|
+
</div>
|
|
143
|
+
),
|
|
144
|
+
play: async ({ canvasElement }: { canvasElement: HTMLElement }) => {
|
|
145
|
+
const canvas = within(canvasElement)
|
|
146
|
+
const remove = await canvas.findByRole('button', { name: '移除 2026-Q2-季度報告.pdf' })
|
|
147
|
+
remove.setAttribute('data-visual-hover-target', '')
|
|
148
|
+
},
|
|
149
|
+
}
|
|
150
|
+
|
|
119
151
|
// children 覆寫:Figma 匯入資產客製文案 / 品牌 logo
|
|
120
152
|
export const CustomChildren = {
|
|
121
153
|
name: '自訂內容',
|
|
@@ -208,7 +208,7 @@ const FileUpload = React.forwardRef<HTMLDivElement, FileUploadProps>(
|
|
|
208
208
|
e.stopPropagation()
|
|
209
209
|
onRemove(f.id)
|
|
210
210
|
}}
|
|
211
|
-
className="text-fg-muted hover:text-
|
|
211
|
+
className="text-fg-muted hover:text-fg-secondary"
|
|
212
212
|
/>
|
|
213
213
|
) : undefined
|
|
214
214
|
}
|
|
@@ -1124,7 +1124,7 @@ export const Accessibility = {
|
|
|
1124
1124
|
render: () => (
|
|
1125
1125
|
<div className="max-w-3xl text-body text-fg-secondary">
|
|
1126
1126
|
<h3 className="text-h5 text-foreground mb-2">無障礙設計</h3>
|
|
1127
|
-
<p className="whitespace-pre-line">{"詳 `file-viewer.spec.md` 「A11y 預設」段。摘要:\n\nRadix DialogPrimitive 自動處理:\n- role=\"dialog\" (Radix 1.1.x 不輸出 aria-modal 屬性;modal 隔離靠 focus trap + hideOthers 對背景兄弟樹加 aria-hidden,非 aria-modal)\n- <DialogPrimitive.Title> (sr-only)自動成 aria-labelledby 目標——screen reader 開啟時讀「檔案檢視器:{file.name}」\n- Focus trap
|
|
1127
|
+
<p className="whitespace-pre-line">{"詳 `file-viewer.spec.md` 「A11y 預設」段。摘要:\n\nRadix DialogPrimitive 自動處理:\n- role=\"dialog\" (Radix 1.1.x 不輸出 aria-modal 屬性;modal 隔離靠 focus trap + hideOthers 對背景兄弟樹加 aria-hidden,非 aria-modal)\n- <DialogPrimitive.Title> (sr-only)自動成 aria-labelledby 目標——screen reader 開啟時讀「檔案檢視器:{file.name}」\n- Focus trap:開啟時 Content 本身取得焦點並立即鎖在 viewer 內\n- Esc 關閉(Radix DismissableLayer,document capture-phase 監聽 → 即使焦點在 InfoPanel textarea 內按 Esc 仍會關閉整個 viewer);Backdrop click 關閉為自寫 geometric onClick handler(非 Radix outside-click — Content 為 fixed inset-0 全螢幕覆蓋,判斷 click 落在 img rect 外才關)\n\n自加的 a11y:\n- 所有 iconOnly button 皆有 aria-label (中文,跟 DS 其他元件風格一致)\n- Filmstrip role=\"group\" + thumb <button> + aria-current (非 tablist:選圖導航非切 tabpanel)\n- InfoPanel 用 <aside aria-label=\"檔案詳細資訊\"> 語意標記\n- Content 固定 tabIndex=-1;onOpenAutoFocus 在 consumer 未取消時聚焦 fullscreen shell,不把焦點留在 aria-hidden 背景或誤觸第一個 toolbar tooltip"}</p>
|
|
1128
1128
|
</div>
|
|
1129
1129
|
),
|
|
1130
1130
|
}
|
|
@@ -370,7 +370,7 @@ Radix DialogPrimitive 自動處理:
|
|
|
370
370
|
- 所有 iconOnly button 皆有 `aria-label`(中文,跟 DS 其他元件風格一致)
|
|
371
371
|
- Filmstrip `role="group"` + `aria-label` + thumb `<button>` + `aria-current`(非 tablist;見「Filmstrip 規則」段理由)
|
|
372
372
|
- InfoPanel 用 `<aside aria-label="檔案詳細資訊">` 語意標記
|
|
373
|
-
- `
|
|
373
|
+
- `DialogContent` 固定 `tabIndex={-1}`;`onOpenAutoFocus` 先執行 consumer handler,若未被取消則阻止 Radix 把焦點送到第一個 toolbar control,並主動 `focus({ preventScroll: true })` fullscreen shell。開啟後焦點立即位於 viewer/FocusScope 內,不留在被 `aria-hidden` 的背景 trigger,也不因首個 icon button focus 而誤觸 tooltip。consumer 若有專屬初始焦點可在 handler 中 `preventDefault()` 後自行聚焦。
|
|
374
374
|
|
|
375
375
|
---
|
|
376
376
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import type { Meta, StoryObj } from '@storybook/react'
|
|
3
|
+
import { expect, waitFor } from '@storybook/test'
|
|
3
4
|
import { FileViewer, type FileInfo } from './file-viewer'
|
|
4
5
|
import { Button } from '@/design-system/components/Button/button'
|
|
5
6
|
import { Image as ImageIcon, Paperclip, Camera, Figma } from 'lucide-react'
|
|
@@ -403,4 +404,10 @@ export const OpenSnapshot: Story = {
|
|
|
403
404
|
/>
|
|
404
405
|
)
|
|
405
406
|
},
|
|
407
|
+
play: async () => {
|
|
408
|
+
await waitFor(() => {
|
|
409
|
+
expect(document.activeElement).toHaveAttribute('role', 'dialog')
|
|
410
|
+
expect(document.activeElement).toHaveAttribute('tabindex', '-1')
|
|
411
|
+
})
|
|
412
|
+
},
|
|
406
413
|
}
|
|
@@ -735,7 +735,7 @@ const DEFAULT_LABELS: Required<FileViewerLabels> = {
|
|
|
735
735
|
export interface FileViewerProps
|
|
736
736
|
extends Omit<
|
|
737
737
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>,
|
|
738
|
-
'onOpenChange'
|
|
738
|
+
'onOpenChange' | 'tabIndex'
|
|
739
739
|
> {
|
|
740
740
|
files: FileInfo[]
|
|
741
741
|
initialIndex?: number
|
|
@@ -776,6 +776,7 @@ const FileViewer = React.forwardRef<HTMLDivElement, FileViewerProps>(function Fi
|
|
|
776
776
|
onDownload,
|
|
777
777
|
labels: labelsOverride,
|
|
778
778
|
className,
|
|
779
|
+
onOpenAutoFocus,
|
|
779
780
|
...props
|
|
780
781
|
}, ref) {
|
|
781
782
|
const labels = React.useMemo(
|
|
@@ -793,6 +794,15 @@ const FileViewer = React.forwardRef<HTMLDivElement, FileViewerProps>(function Fi
|
|
|
793
794
|
defaultValue: defaultOpen ?? false,
|
|
794
795
|
onChange: onOpenChange,
|
|
795
796
|
})
|
|
797
|
+
const contentRef = React.useRef<HTMLDivElement | null>(null)
|
|
798
|
+
const setContentRef = React.useCallback(
|
|
799
|
+
(node: HTMLDivElement | null) => {
|
|
800
|
+
contentRef.current = node
|
|
801
|
+
if (typeof ref === 'function') ref(node)
|
|
802
|
+
else if (ref) ref.current = node
|
|
803
|
+
},
|
|
804
|
+
[ref],
|
|
805
|
+
)
|
|
796
806
|
|
|
797
807
|
// Index:uncontrolled fallback
|
|
798
808
|
const [internalIndex, setInternalIndex] = React.useState(initialIndex)
|
|
@@ -949,7 +959,9 @@ const FileViewer = React.forwardRef<HTMLDivElement, FileViewerProps>(function Fi
|
|
|
949
959
|
)}
|
|
950
960
|
/>
|
|
951
961
|
<DialogPrimitive.Content
|
|
952
|
-
ref={
|
|
962
|
+
ref={setContentRef}
|
|
963
|
+
{...props}
|
|
964
|
+
tabIndex={-1}
|
|
953
965
|
className={cn(
|
|
954
966
|
// Edge-to-edge fullscreen,無 inset / 無 radius(與一般 Dialog 差別的所在)
|
|
955
967
|
'fixed inset-0 z-50 outline-none',
|
|
@@ -958,9 +970,15 @@ const FileViewer = React.forwardRef<HTMLDivElement, FileViewerProps>(function Fi
|
|
|
958
970
|
'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
|
|
959
971
|
'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
|
|
960
972
|
)}
|
|
961
|
-
//
|
|
962
|
-
|
|
963
|
-
|
|
973
|
+
// FileViewer 是 fullscreen content-consumption dialog:開啟即把焦點放到 shell,
|
|
974
|
+
// 讓 Radix FocusScope 立即接管、但不誤觸第一個 toolbar control/Tooltip。
|
|
975
|
+
// Consumer handler 可先取消;未取消時由本元件阻止 Radix default 並聚焦 Content。
|
|
976
|
+
onOpenAutoFocus={(event) => {
|
|
977
|
+
onOpenAutoFocus?.(event)
|
|
978
|
+
if (event.defaultPrevented) return
|
|
979
|
+
event.preventDefault()
|
|
980
|
+
contentRef.current?.focus({ preventScroll: true })
|
|
981
|
+
}}
|
|
964
982
|
>
|
|
965
983
|
{/* 鎖 dark subtree。Density 繼承 page(不另設 data-density)。
|
|
966
984
|
header 高度透過 `--chrome-header-height` 自動 density-aware(md=48 / lg=56)。
|
|
@@ -74,6 +74,12 @@ InlineEdit 本體只給:**對齊盒(-mx orientation-aware)+ 委派 view 統一 f
|
|
|
74
74
|
|
|
75
75
|
**尺寸預設 = `sm`**(2026-07-09 user 拍板):view 態無邊框、視覺即純文字,尺寸過大會使版面鬆散;consumer 可傳 `size` 覆寫,標題場景另用 `readClassName` 疊大字級。
|
|
76
76
|
|
|
77
|
+
**在 `<Field>` 內也是 `sm`**(2026-07-28 user 拍板):Field 的預設 `md` 是「沒人決定過」的預設值,不得壓過本元件宣告的 `sm`。實作雙通道 —— 靜態 `InlineEdit.fieldPreferredSize = 'sm'` 供 Field 直接子元件同步偵測,加上掛載時的 `useRegisterFieldPreferredSize('sm')` 補位(包 wrapper / `React.memo` / HOC 會遮蔽靜態);控件自身尺寸則由 `useResolvedFieldSize` 依 `sizeExplicit` 同步解析,不等註冊。**唯有 consumer 顯式 `size`(在 Field 或 InlineEdit 上)才覆寫**。機制與失效語意 owner → `Field/field.spec.md`「Control 偏好尺寸 SSOT」。
|
|
78
|
+
|
|
79
|
+
**單行 view 恆一行**(2026-07-28 user 拍板):`multiline=false` 的 view 態溢出**不換行**,一律 ellipsis,且**截斷才顯 tooltip**顯示完整值(規則 owner:`Tooltip/tooltip.spec.md`「截斷文字 → tooltip」+ `truncated-text.spec.md`)。**實作 = `useTruncated` 自組而非 `<TruncatedText>`**:view 上疊著透明 Pressable(click-to-edit 入口)會吞掉內層所有 hover,tooltip trigger 必須掛在**整個 view 容器**、量測留在內層值 span —— 這正是 truncated-text.spec 對「trigger 需自控」場景指定的 escape(2026-07-28 user 實測抓到 primitive 直用時 tooltip 永不開)。委派 view(`renderRead`)同一條規則、同一個外層 Tooltip:委派控件自帶的截斷 tooltip(Input `<TruncatedText>` / Tag)trigger 在內層,editable 時同樣被 Pressable 攔截 —— 故外層容器 Tooltip 一併服務委派路徑:`useTruncated` 傳自訂 `measure`(`hooks/use-truncated.ts` options.measure 即為此類自訂策略開的口)走訪 wrapper 子樹,任一 `.truncate` 後代溢出即視為截斷;tooltip 內容 = wrapper 當下 DOM 的 `textContent`(完整格式化文字;非 string `T` 不可 `String(value)`)。`editable=false` 不渲染 Pressable → 委派控件自身 tooltip 可達,外層讓位不重複顯示。多行(`multiline`)維持 `pre-wrap` 忠實呈現。
|
|
80
|
+
|
|
81
|
+
**多行 edit 高度隨內容**(2026-07-28 user 拍板):view 態高度由內容決定,edit 態必須同源,否則 read↔edit 跳高。預設 `Textarea autoSize`(`field-sizing: content`),consumer 以 `editMaxRows` 設上限、超過改捲動。與 DataTable cell 共用同一份 `autoSize` 實作(原本只有 cell 有呼叫端膠水 → 兩者漂移)。
|
|
82
|
+
|
|
77
83
|
**核心不變量:view 態 hover = 背景 tint;view 態 focus = Field 藍框(`border-primary`),與 edit 態同源。** hover(滑鼠懸停)用底色、focus(鍵盤選中)用藍框——藍框是 edit focus 的預覽,view→edit 一路都是藍框、不會 ring→框跳一下;靜止(無 hover/focus)則透明邊框不可見 = 純值。
|
|
78
84
|
|
|
79
85
|
## 型別化 view + 多行 + 鍵盤 SSOT(2026-07-09 L2)
|
|
@@ -172,3 +178,9 @@ InlineEdit 是 **composite 編排層**(view 對齊盒 + 隱形 Pressable + 動
|
|
|
172
178
|
- ❌ 空值顯示唯讀展示的半形 `-` dash——InlineEdit 是可編輯欄位,空值 = placeholder 或空白
|
|
173
179
|
- ❌ view 態語意標籤用 heading 時把它塞進 button——用隱形 Pressable 疊加,保留文件大綱
|
|
174
180
|
- ❌ 用 disabled / 灰化表達鎖定——就地編輯無 disabled 態,鎖定 = `editable=false` 純 view 無入口、不灰化(見「editable 閘」)
|
|
181
|
+
|
|
182
|
+
## 被引用(auto-maintained,Dim 3 reciprocal audit)
|
|
183
|
+
|
|
184
|
+
> 本節由 `scripts/add-reciprocal-pointers.mjs` 自動維護,列出在 SSOT 語境下指向本 spec 的其他 spec。若要手動補充,寫在本節之前。
|
|
185
|
+
|
|
186
|
+
- `field.spec.md`
|