@qijenchen/design-system 0.1.0-beta.80 → 0.1.0-beta.81
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/CLAUDE.md +1 -0
- package/dist/components/Alert/alert.d.ts +5 -0
- package/dist/components/Alert/alert.d.ts.map +1 -1
- package/dist/components/Alert/alert.js +4 -1
- package/dist/components/Alert/alert.js.map +1 -1
- package/dist/components/AppShell/app-shell.d.ts +1 -1
- package/dist/components/AppShell/app-shell.js.map +1 -1
- package/dist/components/AspectRatio/aspect-ratio.d.ts +2 -1
- package/dist/components/AspectRatio/aspect-ratio.d.ts.map +1 -1
- package/dist/components/AspectRatio/aspect-ratio.js +1 -1
- package/dist/components/AspectRatio/aspect-ratio.js.map +1 -1
- package/dist/components/Avatar/avatar.d.ts +3 -2
- package/dist/components/Avatar/avatar.d.ts.map +1 -1
- package/dist/components/Avatar/avatar.js +7 -1
- package/dist/components/Avatar/avatar.js.map +1 -1
- package/dist/components/Badge/badge.d.ts.map +1 -1
- package/dist/components/Badge/badge.js +2 -1
- package/dist/components/Badge/badge.js.map +1 -1
- package/dist/components/Breadcrumb/breadcrumb.d.ts +14 -1
- package/dist/components/Breadcrumb/breadcrumb.d.ts.map +1 -1
- package/dist/components/Breadcrumb/breadcrumb.js +9 -3
- package/dist/components/Breadcrumb/breadcrumb.js.map +1 -1
- package/dist/components/BulkActionBar/bulk-action-bar.d.ts +1 -1
- package/dist/components/BulkActionBar/bulk-action-bar.d.ts.map +1 -1
- package/dist/components/BulkActionBar/bulk-action-bar.js.map +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 +68 -42
- package/dist/components/Button/button.js.map +1 -1
- package/dist/components/Button/index.d.ts +1 -0
- package/dist/components/Button/index.d.ts.map +1 -1
- package/dist/components/Button/index.js +3 -0
- package/dist/components/Button/index.js.map +1 -1
- package/dist/components/Carousel/carousel.d.ts.map +1 -1
- package/dist/components/Carousel/carousel.js.map +1 -1
- package/dist/components/Chart/chart.d.ts.map +1 -1
- package/dist/components/Chart/chart.js +1 -1
- package/dist/components/Chart/chart.js.map +1 -1
- package/dist/components/Checkbox/checkbox.d.ts +5 -2
- package/dist/components/Checkbox/checkbox.d.ts.map +1 -1
- package/dist/components/Checkbox/checkbox.js +24 -3
- package/dist/components/Checkbox/checkbox.js.map +1 -1
- package/dist/components/Checkbox/index.d.ts +1 -0
- package/dist/components/Checkbox/index.d.ts.map +1 -1
- package/dist/components/Checkbox/index.js +3 -0
- package/dist/components/Checkbox/index.js.map +1 -1
- package/dist/components/Chip/chip.d.ts +7 -1
- package/dist/components/Chip/chip.d.ts.map +1 -1
- package/dist/components/Chip/chip.js +5 -1
- package/dist/components/Chip/chip.js.map +1 -1
- package/dist/components/Coachmark/coachmark.d.ts +5 -3
- package/dist/components/Coachmark/coachmark.d.ts.map +1 -1
- package/dist/components/Coachmark/coachmark.js +4 -1
- package/dist/components/Coachmark/coachmark.js.map +1 -1
- package/dist/components/Combobox/combobox.d.ts +2 -0
- package/dist/components/Combobox/combobox.d.ts.map +1 -1
- package/dist/components/Combobox/combobox.js +7 -6
- package/dist/components/Combobox/combobox.js.map +1 -1
- package/dist/components/Command/command.d.ts +1 -1
- package/dist/components/Command/command.js +2 -1
- package/dist/components/Command/command.js.map +1 -1
- package/dist/components/DataTable/active-editor-controller.d.ts +4 -3
- package/dist/components/DataTable/active-editor-controller.d.ts.map +1 -1
- package/dist/components/DataTable/cell-registry.d.ts.map +1 -1
- package/dist/components/DataTable/cell-registry.js +3 -0
- package/dist/components/DataTable/cell-registry.js.map +1 -1
- package/dist/components/DataTable/data-table-filter-panel.d.ts.map +1 -1
- package/dist/components/DataTable/data-table-interaction-layer.d.ts +4 -3
- package/dist/components/DataTable/data-table-interaction-layer.d.ts.map +1 -1
- package/dist/components/DataTable/data-table-interaction-layer.js.map +1 -1
- package/dist/components/DataTable/data-table.d.ts +9 -6
- package/dist/components/DataTable/data-table.d.ts.map +1 -1
- package/dist/components/DataTable/data-table.js +142 -50
- package/dist/components/DataTable/data-table.js.map +1 -1
- package/dist/components/DataTable/filter-tree.d.ts +5 -0
- package/dist/components/DataTable/filter-tree.d.ts.map +1 -1
- package/dist/components/DateGrid/date-grid.d.ts +1 -1
- package/dist/components/DateGrid/date-grid.d.ts.map +1 -1
- package/dist/components/DateGrid/date-grid.js +6 -4
- package/dist/components/DateGrid/date-grid.js.map +1 -1
- package/dist/components/DatePicker/date-picker.d.ts.map +1 -1
- package/dist/components/DatePicker/date-picker.js +124 -106
- 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 +2 -1
- package/dist/components/Dialog/dialog.js.map +1 -1
- package/dist/components/DropdownMenu/dropdown-menu.d.ts +1 -1
- package/dist/components/DropdownMenu/dropdown-menu.d.ts.map +1 -1
- package/dist/components/DropdownMenu/dropdown-menu.js +11 -4
- package/dist/components/DropdownMenu/dropdown-menu.js.map +1 -1
- package/dist/components/Field/field-wrapper.d.ts +1 -0
- package/dist/components/Field/field-wrapper.d.ts.map +1 -1
- package/dist/components/Field/field-wrapper.js +27 -7
- package/dist/components/Field/field-wrapper.js.map +1 -1
- package/dist/components/Field/use-form-validation.d.ts +5 -1
- package/dist/components/Field/use-form-validation.d.ts.map +1 -1
- package/dist/components/Field/use-form-validation.js +26 -14
- package/dist/components/Field/use-form-validation.js.map +1 -1
- package/dist/components/FieldControlGroup/field-control-group.d.ts +3 -3
- package/dist/components/FieldControlGroup/field-control-group.js.map +1 -1
- package/dist/components/FileItem/file-item.d.ts.map +1 -1
- package/dist/components/FileItem/file-item.js +3 -2
- package/dist/components/FileItem/file-item.js.map +1 -1
- package/dist/components/FileUpload/file-upload.d.ts.map +1 -1
- package/dist/components/FileUpload/file-upload.js +12 -6
- package/dist/components/FileUpload/file-upload.js.map +1 -1
- package/dist/components/FileViewer/file-viewer.d.ts.map +1 -1
- package/dist/components/FileViewer/file-viewer.js +4 -3
- package/dist/components/FileViewer/file-viewer.js.map +1 -1
- package/dist/components/Input/input.d.ts.map +1 -1
- package/dist/components/Input/input.js +1 -5
- package/dist/components/Input/input.js.map +1 -1
- package/dist/components/LinkInput/link-input.d.ts +2 -2
- package/dist/components/LinkInput/link-input.d.ts.map +1 -1
- package/dist/components/LinkInput/link-input.js +29 -28
- 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 +1 -1
- package/dist/components/Menu/menu-item.js.map +1 -1
- package/dist/components/Notice/notice.d.ts +5 -0
- package/dist/components/Notice/notice.d.ts.map +1 -1
- package/dist/components/Notice/notice.js +11 -5
- package/dist/components/Notice/notice.js.map +1 -1
- package/dist/components/NumberInput/number-input.d.ts.map +1 -1
- package/dist/components/NumberInput/number-input.js +15 -7
- package/dist/components/NumberInput/number-input.js.map +1 -1
- package/dist/components/OverflowIndicator/overflow-indicator.d.ts +1 -1
- package/dist/components/OverflowIndicator/overflow-indicator.d.ts.map +1 -1
- package/dist/components/OverflowIndicator/overflow-indicator.js +2 -1
- package/dist/components/OverflowIndicator/overflow-indicator.js.map +1 -1
- package/dist/components/PeoplePicker/people-picker.d.ts +2 -2
- package/dist/components/PeoplePicker/people-picker.d.ts.map +1 -1
- package/dist/components/PeoplePicker/people-picker.js +36 -35
- package/dist/components/PeoplePicker/people-picker.js.map +1 -1
- package/dist/components/PeoplePicker/person-display.d.ts.map +1 -1
- package/dist/components/PeoplePicker/person-display.js +2 -2
- package/dist/components/PeoplePicker/person-display.js.map +1 -1
- package/dist/components/Popover/popover.js.map +1 -1
- package/dist/components/ProfileCard/profile-card.d.ts +5 -2
- package/dist/components/ProfileCard/profile-card.d.ts.map +1 -1
- package/dist/components/ProfileCard/profile-card.js.map +1 -1
- package/dist/components/RadioGroup/radio-group.d.ts +1 -1
- package/dist/components/RadioGroup/radio-group.d.ts.map +1 -1
- package/dist/components/RadioGroup/radio-group.js +27 -3
- package/dist/components/RadioGroup/radio-group.js.map +1 -1
- package/dist/components/Rating/rating.js +3 -3
- package/dist/components/Rating/rating.js.map +1 -1
- package/dist/components/ScrollArea/scroll-area.d.ts +1 -1
- package/dist/components/ScrollArea/scroll-area.js +2 -2
- package/dist/components/ScrollArea/scroll-area.js.map +1 -1
- package/dist/components/SegmentedControl/segmented-control.d.ts +2 -2
- package/dist/components/SegmentedControl/segmented-control.d.ts.map +1 -1
- package/dist/components/SegmentedControl/segmented-control.js +10 -5
- package/dist/components/SegmentedControl/segmented-control.js.map +1 -1
- package/dist/components/Select/select.d.ts +9 -3
- package/dist/components/Select/select.d.ts.map +1 -1
- package/dist/components/Select/select.js +45 -13
- package/dist/components/Select/select.js.map +1 -1
- package/dist/components/SelectMenu/index.js +2 -1
- package/dist/components/SelectMenu/select-menu.d.ts +17 -4
- package/dist/components/SelectMenu/select-menu.d.ts.map +1 -1
- package/dist/components/SelectMenu/select-menu.js +39 -6
- package/dist/components/SelectMenu/select-menu.js.map +1 -1
- package/dist/components/Separator/separator.js +1 -1
- package/dist/components/Separator/separator.js.map +1 -1
- package/dist/components/Sheet/sheet.d.ts +1 -1
- package/dist/components/Sheet/sheet.d.ts.map +1 -1
- package/dist/components/Sheet/sheet.js +2 -1
- package/dist/components/Sheet/sheet.js.map +1 -1
- package/dist/components/Sidebar/sidebar.d.ts.map +1 -1
- package/dist/components/Sidebar/sidebar.js +23 -8
- package/dist/components/Sidebar/sidebar.js.map +1 -1
- package/dist/components/Skeleton/skeleton.d.ts +1 -1
- package/dist/components/Skeleton/skeleton.js +2 -2
- package/dist/components/Skeleton/skeleton.js.map +1 -1
- package/dist/components/Steps/steps.d.ts.map +1 -1
- package/dist/components/Steps/steps.js +6 -3
- package/dist/components/Steps/steps.js.map +1 -1
- package/dist/components/Switch/switch.d.ts +2 -2
- package/dist/components/Switch/switch.d.ts.map +1 -1
- package/dist/components/Switch/switch.js +38 -8
- package/dist/components/Switch/switch.js.map +1 -1
- package/dist/components/Tabs/tabs.d.ts +5 -2
- package/dist/components/Tabs/tabs.d.ts.map +1 -1
- package/dist/components/Tabs/tabs.js +16 -24
- package/dist/components/Tabs/tabs.js.map +1 -1
- package/dist/components/Tag/tag.d.ts.map +1 -1
- package/dist/components/Tag/tag.js +7 -3
- package/dist/components/Tag/tag.js.map +1 -1
- package/dist/components/Textarea/textarea.d.ts +2 -1
- package/dist/components/Textarea/textarea.d.ts.map +1 -1
- package/dist/components/Textarea/textarea.js +20 -12
- package/dist/components/Textarea/textarea.js.map +1 -1
- package/dist/components/TimePicker/time-picker.d.ts +2 -2
- package/dist/components/TimePicker/time-picker.d.ts.map +1 -1
- package/dist/components/TimePicker/time-picker.js +5 -7
- package/dist/components/TimePicker/time-picker.js.map +1 -1
- package/dist/components/Toast/toast.d.ts +1 -1
- package/dist/components/Toast/toast.d.ts.map +1 -1
- package/dist/components/Toast/toast.js +3 -2
- package/dist/components/Toast/toast.js.map +1 -1
- package/dist/components/Tooltip/tooltip.js +1 -1
- package/dist/components/Tooltip/tooltip.js.map +1 -1
- 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 +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -1
- package/dist/patterns/element-anatomy/item-anatomy.d.ts +1 -1
- package/dist/patterns/element-anatomy/item-anatomy.js.map +1 -1
- package/dist/patterns/overlay-surface/overlay-surface.js.map +1 -1
- package/dist/tokens/categorical-color.d.ts +1 -1
- package/dist/tokens/categorical-color.js.map +1 -1
- package/dist/tokens/uiSize/icon-size.d.ts +2 -2
- package/ds-canonical/fork/governance.lock +1 -1
- package/ds-canonical/fork/hooks/check_opacity_token_usage.sh +10 -1
- package/ds-canonical/hooks/check_audit_post_report_validator.sh +13 -0
- package/ds-canonical/hooks/check_full_story_visual_interaction_sweep.sh +4 -3
- package/ds-canonical/hooks/check_opacity_token_usage.sh +10 -1
- package/ds-canonical/hooks/check_plugin_fork_health.sh +30 -22
- package/ds-canonical/hooks/check_story_invariants.sh +17 -5
- package/ds-canonical/hooks/check_substantive_edit_approval_preflight.sh +5 -2
- package/ds-canonical/references/principle-dim-map.json +100 -0
- package/ds-canonical/references/story-baseline-registry.json +4 -3
- package/ds-canonical/skills/deep-audit-cross-codex/SKILL.md +1 -1
- package/ds-canonical/skills/design-system-audit/SKILL.md +2 -2
- package/ds-story-manifest.json +16 -16
- package/llms-full.txt +6 -1
- package/llms.txt +2 -1
- package/package.json +1 -1
- package/src/components/Alert/alert.anatomy.stories.tsx +9 -6
- package/src/components/Alert/alert.spec.md +3 -3
- package/src/components/Alert/alert.tsx +9 -1
- package/src/components/AppShell/app-shell.anatomy.stories.tsx +3 -0
- package/src/components/AppShell/app-shell.spec.md +3 -2
- package/src/components/AppShell/app-shell.tsx +1 -1
- package/src/components/AspectRatio/aspect-ratio.spec.md +1 -2
- package/src/components/AspectRatio/aspect-ratio.tsx +3 -2
- package/src/components/Avatar/avatar.principles.stories.tsx +1 -1
- package/src/components/Avatar/avatar.spec.md +1 -1
- package/src/components/Avatar/avatar.stories.tsx +1 -1
- package/src/components/Avatar/avatar.tsx +17 -9
- package/src/components/Badge/badge.anatomy.stories.tsx +2 -2
- package/src/components/Badge/badge.spec.md +4 -4
- package/src/components/Badge/badge.tsx +4 -1
- package/src/components/Breadcrumb/breadcrumb.anatomy.stories.tsx +2 -2
- package/src/components/Breadcrumb/breadcrumb.principles.stories.tsx +15 -10
- package/src/components/Breadcrumb/breadcrumb.spec.md +4 -2
- package/src/components/Breadcrumb/breadcrumb.tsx +10 -4
- package/src/components/BulkActionBar/bulk-action-bar.anatomy.stories.tsx +2 -1
- package/src/components/BulkActionBar/bulk-action-bar.spec.md +15 -14
- package/src/components/BulkActionBar/bulk-action-bar.tsx +2 -2
- package/src/components/Button/button.anatomy.stories.tsx +6 -6
- package/src/components/Button/button.spec.md +2 -2
- package/src/components/Button/button.tsx +63 -26
- package/src/components/Button/index.ts +1 -0
- package/src/components/Calendar/calendar.anatomy.stories.tsx +2 -2
- package/src/components/Calendar/calendar.spec.md +7 -1
- package/src/components/Carousel/carousel.principles.stories.tsx +1 -1
- package/src/components/Carousel/carousel.tsx +1 -0
- package/src/components/Chart/chart.spec.md +1 -1
- package/src/components/Chart/chart.tsx +2 -1
- package/src/components/Checkbox/checkbox.principles.stories.tsx +1 -1
- package/src/components/Checkbox/checkbox.spec.md +1 -1
- package/src/components/Checkbox/checkbox.tsx +39 -6
- package/src/components/Checkbox/index.ts +1 -0
- package/src/components/Chip/chip.anatomy.stories.tsx +1 -1
- package/src/components/Chip/chip.spec.md +1 -1
- package/src/components/Chip/chip.tsx +4 -4
- package/src/components/CircularProgress/circular-progress.anatomy.stories.tsx +1 -1
- package/src/components/CircularProgress/circular-progress.principles.stories.tsx +7 -5
- package/src/components/CircularProgress/circular-progress.spec.md +2 -0
- package/src/components/CircularProgress/circular-progress.stories.tsx +3 -11
- package/src/components/Coachmark/coachmark.anatomy.stories.tsx +2 -2
- package/src/components/Coachmark/coachmark.principles.stories.tsx +1 -1
- package/src/components/Coachmark/coachmark.spec.md +3 -3
- package/src/components/Coachmark/coachmark.tsx +13 -5
- package/src/components/Combobox/combobox.anatomy.stories.tsx +4 -4
- package/src/components/Combobox/combobox.spec.md +1 -1
- package/src/components/Combobox/combobox.tsx +23 -16
- package/src/components/Command/command.principles.stories.tsx +10 -12
- package/src/components/Command/command.spec.md +5 -5
- package/src/components/Command/command.stories.tsx +4 -4
- package/src/components/Command/command.tsx +1 -1
- package/src/components/DataTable/active-editor-controller.ts +4 -3
- package/src/components/DataTable/cell-registry.tsx +8 -0
- package/src/components/DataTable/data-table-filter-panel.tsx +8 -2
- package/src/components/DataTable/data-table-interaction-layer.tsx +8 -7
- package/src/components/DataTable/data-table.anatomy.stories.tsx +4 -4
- package/src/components/DataTable/data-table.css +2 -2
- package/src/components/DataTable/data-table.spec.md +25 -25
- package/src/components/DataTable/data-table.stories.tsx +4 -3
- package/src/components/DataTable/data-table.tsx +203 -52
- package/src/components/DataTable/filter-operators.spec.md +4 -4
- package/src/components/DataTable/filter-tree.ts +32 -9
- package/src/components/DateGrid/date-grid.anatomy.stories.tsx +1 -1
- package/src/components/DateGrid/date-grid.spec.md +6 -4
- package/src/components/DateGrid/date-grid.tsx +7 -5
- package/src/components/DatePicker/date-picker.principles.stories.tsx +1 -1
- package/src/components/DatePicker/date-picker.spec.md +1 -0
- package/src/components/DatePicker/date-picker.stories.tsx +19 -0
- package/src/components/DatePicker/date-picker.tsx +33 -12
- package/src/components/DescriptionList/description-list.spec.md +1 -1
- package/src/components/Dialog/dialog.spec.md +1 -1
- package/src/components/Dialog/dialog.tsx +2 -1
- package/src/components/DropdownMenu/dropdown-menu.anatomy.stories.tsx +3 -1
- package/src/components/DropdownMenu/dropdown-menu.spec.md +4 -2
- package/src/components/DropdownMenu/dropdown-menu.tsx +11 -3
- package/src/components/Empty/empty.anatomy.stories.tsx +1 -1
- package/src/components/Empty/empty.spec.md +2 -1
- package/src/components/Empty/empty.stories.tsx +4 -15
- package/src/components/Field/field-controls.spec.md +1 -0
- package/src/components/Field/field-wrapper.tsx +28 -8
- package/src/components/Field/field.anatomy.stories.tsx +7 -7
- package/src/components/Field/field.principles.stories.tsx +1 -1
- package/src/components/Field/field.spec.md +9 -7
- package/src/components/Field/form-validation.spec.md +5 -2
- package/src/components/Field/use-form-validation.ts +38 -14
- package/src/components/FieldControlGroup/field-control-group.anatomy.stories.tsx +11 -10
- package/src/components/FieldControlGroup/field-control-group.principles.stories.tsx +3 -3
- package/src/components/FieldControlGroup/field-control-group.spec.md +3 -3
- package/src/components/FieldControlGroup/field-control-group.tsx +3 -3
- package/src/components/FileItem/file-item.anatomy.stories.tsx +6 -4
- package/src/components/FileItem/file-item.tsx +3 -2
- package/src/components/FileUpload/file-upload.anatomy.stories.tsx +1 -1
- package/src/components/FileUpload/file-upload.spec.md +3 -0
- package/src/components/FileUpload/file-upload.tsx +21 -6
- package/src/components/FileViewer/file-viewer.anatomy.stories.tsx +6 -4
- package/src/components/FileViewer/file-viewer.spec.md +2 -2
- package/src/components/FileViewer/file-viewer.tsx +4 -3
- package/src/components/Input/input.anatomy.stories.tsx +4 -4
- package/src/components/Input/input.principles.stories.tsx +1 -1
- package/src/components/Input/input.spec.md +1 -1
- package/src/components/Input/input.stories.tsx +11 -5
- package/src/components/Input/input.tsx +1 -5
- package/src/components/LinkInput/link-input.anatomy.stories.tsx +2 -0
- package/src/components/LinkInput/link-input.tsx +45 -33
- package/src/components/Menu/menu-item.spec.md +2 -1
- package/src/components/Menu/menu-item.tsx +3 -1
- package/src/components/Notice/notice.anatomy.stories.tsx +10 -5
- package/src/components/Notice/notice.spec.md +4 -3
- package/src/components/Notice/notice.tsx +22 -5
- package/src/components/NumberInput/number-input.anatomy.stories.tsx +5 -4
- package/src/components/NumberInput/number-input.principles.stories.tsx +2 -1
- package/src/components/NumberInput/number-input.spec.md +2 -1
- package/src/components/NumberInput/number-input.tsx +23 -8
- package/src/components/OverflowIndicator/overflow-indicator.anatomy.stories.tsx +3 -3
- package/src/components/OverflowIndicator/overflow-indicator.spec.md +1 -1
- package/src/components/OverflowIndicator/overflow-indicator.tsx +2 -1
- package/src/components/PeoplePicker/people-picker.anatomy.stories.tsx +1 -1
- package/src/components/PeoplePicker/people-picker.spec.md +4 -4
- package/src/components/PeoplePicker/people-picker.stories.tsx +1 -1
- package/src/components/PeoplePicker/people-picker.tsx +63 -54
- package/src/components/PeoplePicker/person-display.tsx +5 -3
- package/src/components/Popover/popover.anatomy.stories.tsx +1 -1
- package/src/components/Popover/popover.spec.md +11 -13
- package/src/components/Popover/popover.tsx +2 -2
- package/src/components/ProfileCard/profile-card.anatomy.stories.tsx +4 -4
- package/src/components/ProfileCard/profile-card.principles.stories.tsx +2 -2
- package/src/components/ProfileCard/profile-card.spec.md +1 -2
- package/src/components/ProfileCard/profile-card.stories.tsx +1 -1
- package/src/components/ProfileCard/profile-card.tsx +5 -2
- package/src/components/RadioGroup/radio-group.principles.stories.tsx +1 -1
- package/src/components/RadioGroup/radio-group.spec.md +2 -2
- package/src/components/RadioGroup/radio-group.stories.tsx +3 -1
- package/src/components/RadioGroup/radio-group.tsx +36 -3
- package/src/components/Rating/rating.anatomy.stories.tsx +11 -8
- package/src/components/Rating/rating.principles.stories.tsx +1 -1
- package/src/components/Rating/rating.spec.md +2 -2
- package/src/components/Rating/rating.stories.tsx +1 -1
- package/src/components/Rating/rating.tsx +3 -3
- package/src/components/ScrollArea/scroll-area.spec.md +1 -0
- package/src/components/ScrollArea/scroll-area.tsx +1 -1
- package/src/components/SegmentedControl/segmented-control.anatomy.stories.tsx +2 -2
- package/src/components/SegmentedControl/segmented-control.spec.md +1 -1
- package/src/components/SegmentedControl/segmented-control.tsx +16 -6
- package/src/components/Select/select.anatomy.stories.tsx +2 -2
- package/src/components/Select/select.spec.md +2 -1
- package/src/components/Select/select.tsx +37 -16
- package/src/components/SelectMenu/select-menu.anatomy.stories.tsx +1 -1
- package/src/components/SelectMenu/select-menu.principles.stories.tsx +1 -1
- package/src/components/SelectMenu/select-menu.spec.md +11 -8
- package/src/components/SelectMenu/select-menu.tsx +92 -10
- package/src/components/SelectionControl/selection-item.anatomy.stories.tsx +1 -1
- package/src/components/SelectionControl/selection-item.principles.stories.tsx +2 -2
- package/src/components/SelectionControl/selection-item.spec.md +2 -1
- package/src/components/SelectionControl/selection-item.stories.tsx +1 -1
- package/src/components/Separator/separator.anatomy.stories.tsx +2 -2
- package/src/components/Separator/separator.tsx +1 -1
- package/src/components/Sheet/sheet.spec.md +1 -1
- package/src/components/Sheet/sheet.tsx +2 -1
- package/src/components/Sidebar/sidebar.anatomy.stories.tsx +1 -1
- package/src/components/Sidebar/sidebar.spec.md +2 -1
- package/src/components/Sidebar/sidebar.stories.tsx +6 -4
- package/src/components/Sidebar/sidebar.tsx +36 -11
- package/src/components/Skeleton/skeleton.principles.stories.tsx +1 -1
- package/src/components/Skeleton/skeleton.spec.md +1 -2
- package/src/components/Skeleton/skeleton.tsx +1 -1
- package/src/components/Slider/slider.spec.md +4 -4
- package/src/components/Steps/steps.anatomy.stories.tsx +1 -1
- package/src/components/Steps/steps.spec.md +5 -4
- package/src/components/Steps/steps.tsx +11 -3
- package/src/components/Switch/switch.spec.md +3 -3
- package/src/components/Switch/switch.tsx +51 -10
- package/src/components/Tabs/tabs.anatomy.stories.tsx +3 -2
- package/src/components/Tabs/tabs.principles.stories.tsx +5 -3
- package/src/components/Tabs/tabs.spec.md +2 -1
- package/src/components/Tabs/tabs.tsx +32 -25
- package/src/components/Tag/tag.principles.stories.tsx +1 -1
- package/src/components/Tag/tag.tsx +7 -3
- package/src/components/Textarea/textarea.spec.md +2 -1
- package/src/components/Textarea/textarea.tsx +16 -9
- package/src/components/TimePicker/time-picker.anatomy.stories.tsx +2 -1
- package/src/components/TimePicker/time-picker.spec.md +2 -2
- package/src/components/TimePicker/time-picker.tsx +6 -8
- package/src/components/Toast/toast.anatomy.stories.tsx +5 -6
- package/src/components/Toast/toast.principles.stories.tsx +10 -11
- package/src/components/Toast/toast.spec.md +5 -5
- package/src/components/Toast/toast.stories.tsx +5 -1
- package/src/components/Toast/toast.tsx +9 -6
- package/src/components/Tooltip/tooltip.spec.md +3 -3
- package/src/components/Tooltip/tooltip.tsx +1 -1
- package/src/components/TreeView/tree-view.anatomy.stories.tsx +6 -1
- package/src/components/TreeView/tree-view.principles.stories.tsx +2 -2
- package/src/components/TreeView/tree-view.spec.md +1 -1
- package/src/components/TreeView/tree-view.tsx +17 -2
- package/src/index.ts +1 -1
- package/src/patterns/element-anatomy/element-anatomy.spec.md +3 -3
- package/src/patterns/element-anatomy/item-anatomy.spec.md +4 -12
- package/src/patterns/element-anatomy/item-anatomy.stories.tsx +1 -0
- package/src/patterns/element-anatomy/item-anatomy.tsx +1 -1
- package/src/patterns/header-canonical/header-canonical.css +1 -2
- package/src/patterns/header-canonical/header-canonical.spec.md +5 -5
- package/src/patterns/horizontal-overflow/horizontal-overflow.spec.md +5 -4
- package/src/patterns/overlay-surface/overlay-surface.spec.md +8 -7
- package/src/patterns/overlay-surface/overlay-surface.tsx +5 -5
- package/src/patterns/resize-handle/resize-handle.spec.md +1 -1
- package/src/tokens/categorical-color.ts +1 -1
- package/src/tokens/color/color.spec.md +6 -6
- package/src/tokens/color/color.stories.tsx +1 -1
- package/src/tokens/color/primitives.css +7 -5
- package/src/tokens/density/density.spec.md +2 -0
- package/src/tokens/elevation/elevation.spec.md +1 -1
- package/src/tokens/layoutSpace/layoutSpace.spec.md +1 -1
- package/src/tokens/layoutSpace/layoutSpace.stories.tsx +3 -3
- package/src/tokens/motion/motion.spec.md +1 -1
- package/src/tokens/opacity/opacity.stories.tsx +7 -11
- package/src/tokens/radius/radius.spec.md +1 -1
- package/src/tokens/typography/typography.css +1 -1
- package/src/tokens/typography/typography.spec.md +1 -1
- package/src/tokens/uiSize/icon-size.ts +2 -2
- package/src/tokens/uiSize/uiSize.spec.md +12 -11
- package/src/tokens/utility-registry.json +5 -4
- package/dist/node_modules/react-hook-form/dist/index.esm.js +0 -1950
- package/dist/node_modules/react-hook-form/dist/index.esm.js.map +0 -1
|
@@ -58,7 +58,7 @@ const SIZE_SPECS: Record<SizeKey, SizeSpec> = {
|
|
|
58
58
|
|
|
59
59
|
const MODE_DESC: Record<ModeKey, string> = {
|
|
60
60
|
edit: '表單可編輯欄位 — bg-surface + border + hover/focus 回饋',
|
|
61
|
-
display: '純展示資料 — 無 chrome(transparent
|
|
61
|
+
display: '純展示資料 — 無 chrome(transparent + !px-0 !py-0 零內距,field-wrapper.tsx Path Ⅰ)+ 文字正常色,空值顯示 —(em dash)',
|
|
62
62
|
readonly: '不可編輯但可見 — bg-readonly(neutral-2) + 無邊框 + 文字正常色',
|
|
63
63
|
disabled: '被停用的欄位 — bg-disabled(neutral-2) + 無邊框 + 文字灰化',
|
|
64
64
|
}
|
|
@@ -360,7 +360,7 @@ const InspectorInner = () => {
|
|
|
360
360
|
</div>
|
|
361
361
|
<div className="flex items-center">
|
|
362
362
|
<div className="flex items-center rounded-md overflow-hidden" style={{ height: 52, outline: `2px solid ${Z.dim.text}22` }}>
|
|
363
|
-
<BpZone w={44} color={Z.pad} label={s.pxToken} sub={`${s.px}px`} />
|
|
363
|
+
<BpZone w={44} color={Z.pad} label={mode === 'display' ? '!px-0' : s.pxToken} sub={mode === 'display' ? '0px' : `${s.px}px`} />
|
|
364
364
|
{hasStartIcon && (
|
|
365
365
|
<>
|
|
366
366
|
<BpZone w={44} color={Z.icon} label={`${s.icon}px`} sub="icon" />
|
|
@@ -374,7 +374,7 @@ const InspectorInner = () => {
|
|
|
374
374
|
<BpZone w={44} color={Z.action} label={`${s.icon}px`} sub="action" />
|
|
375
375
|
</>
|
|
376
376
|
)}
|
|
377
|
-
<BpZone w={44} color={Z.pad} label={s.pxToken} sub={`${s.px}px`} />
|
|
377
|
+
<BpZone w={44} color={Z.pad} label={mode === 'display' ? '!px-0' : s.pxToken} sub={mode === 'display' ? '0px' : `${s.px}px`} />
|
|
378
378
|
</div>
|
|
379
379
|
<div className="ml-3 flex items-center" style={{ height: 52 }}>
|
|
380
380
|
<svg width="10" height="52" className="shrink-0">
|
|
@@ -425,7 +425,7 @@ const InspectorInner = () => {
|
|
|
425
425
|
<div className="px-4 py-1">
|
|
426
426
|
<div className="py-2 border-b border-divider"><span className="text-[10px] font-semibold text-fg-muted uppercase tracking-wider">Layout</span></div>
|
|
427
427
|
<PropRow label="高度" dot={Z.dim.text}><TkVal token={s.heightToken} value={s.height} /></PropRow>
|
|
428
|
-
<PropRow label="左右內距" dot={Z.pad.text}><TkVal token={s.pxToken} value={`${s.px}px`} /></PropRow>
|
|
428
|
+
<PropRow label="左右內距" dot={Z.pad.text}><TkVal token={mode === 'display' ? '!px-0 !py-0' : s.pxToken} value={mode === 'display' ? '0px(零 chrome)' : `${s.px}px`} /></PropRow>
|
|
429
429
|
<PropRow label="元素間距" dot={Z.gap.text}><TkVal token={s.gapToken} value={`${s.gap}px`} /></PropRow>
|
|
430
430
|
{hasStartIcon && (
|
|
431
431
|
<PropRow label="Icon 尺寸" dot={Z.icon.text}>{s.icon}px</PropRow>
|
|
@@ -35,7 +35,7 @@ const Label = ({ children, warn }: { children: React.ReactNode; warn?: boolean }
|
|
|
35
35
|
|
|
36
36
|
const Section = ({ title, children }: { title: string; children: React.ReactNode }) => (
|
|
37
37
|
<section className="mb-12">
|
|
38
|
-
<h2 className="text-
|
|
38
|
+
<h2 className="text-h3 font-bold text-foreground mb-4 pb-2 border-b border-border">{title}</h2>
|
|
39
39
|
{children}
|
|
40
40
|
</section>
|
|
41
41
|
)
|
|
@@ -109,7 +109,7 @@ endAction(host 內嵌 inline action)vs 獨立 `<Button iconOnly>` 的分界詳 `
|
|
|
109
109
|
|
|
110
110
|
## 邊界案例
|
|
111
111
|
|
|
112
|
-
- **Disabled**:`disabled` prop 由 Field SSOT own(`Field/field.spec.md`「Field state machine SSOT」段)。視覺 token:wrapper bg → `bg-disabled`(neutral-2)、text → `text-fg-disabled`(neutral-6,M24 state>emphasis canonical)、startIcon → `text-fg-disabled`、endAction
|
|
112
|
+
- **Disabled**:`disabled` prop 由 Field SSOT own(`Field/field.spec.md`「Field state machine SSOT」段)。視覺 token:wrapper bg → `bg-disabled`(neutral-2)、text → `text-fg-disabled`(neutral-6,M24 state>emphasis canonical)、startIcon → `text-fg-disabled`、endAction 不渲染(僅 edit mode 渲染)、cursor → `cursor-not-allowed`、border 弱化、無 hover/focus ring。`readOnly` 與 `disabled` 視覺分離:readOnly 維持 default text color(可選取複製)、disabled 全面弱化(不可選)。
|
|
113
113
|
- **Loading**:已 codify(見「Loading」段)。`loading=true` 時 endAction slot 自動切 `<CircularProgress/>`,input 仍可編輯(Ant Search 派 idiom)。
|
|
114
114
|
- **Empty(no value)**:placeholder 走 `text-fg-muted`(neutral-7);`disabled + empty` 時 placeholder 切 `text-fg-disabled`(M24 state precedence)。
|
|
115
115
|
- **Dark mode**:走 semantic token 自動 adapt,無 per-component override。
|
|
@@ -2,6 +2,7 @@ import React from 'react'
|
|
|
2
2
|
import type { Meta, StoryObj } from '@storybook/react'
|
|
3
3
|
import { Search, Eye, EyeOff, X } from 'lucide-react'
|
|
4
4
|
import { Input } from './input'
|
|
5
|
+
import { Field, FieldLabel, FieldError } from '@/design-system/components/Field/field'
|
|
5
6
|
import { Button } from '@/design-system/components/Button/button'
|
|
6
7
|
import { Tooltip, TooltipContent, TooltipTrigger } from '@/design-system/components/Tooltip/tooltip'
|
|
7
8
|
|
|
@@ -114,15 +115,20 @@ export const EndAction: Story = {
|
|
|
114
115
|
}
|
|
115
116
|
|
|
116
117
|
/* ── Error ── */
|
|
118
|
+
// 2026-07-04 audit Dim 24/M23(d):錯誤訊息改消費 Field + FieldError primitive(對齊 Textarea / TimePicker /
|
|
119
|
+
// Rating showcase idiom),不手刻 <p text-error>(重複 FieldError 內部樣式 = drift 來源)。
|
|
117
120
|
export const ErrorState: Story = {
|
|
118
121
|
name: '錯誤狀態',
|
|
119
122
|
render: () => (
|
|
120
123
|
<div className="flex flex-col gap-4 max-w-sm">
|
|
121
|
-
<p className="text-caption text-fg-muted">Error 以紅色邊框表示。錯誤訊息由
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
124
|
+
<p className="text-caption text-fg-muted">Error 以紅色邊框表示。錯誤訊息由 FieldError 提供,不在 input 內放狀態 icon</p>
|
|
125
|
+
{/* Field invalid 自動 cascade 到 Input(useResolvedFieldInvalid),不需重複傳 error;
|
|
126
|
+
standalone <Input error> 用法由 anatomy「+ error prop」section 展示 */}
|
|
127
|
+
<Field invalid>
|
|
128
|
+
<FieldLabel>Email</FieldLabel>
|
|
129
|
+
<Input defaultValue="invalid-email@" />
|
|
130
|
+
<FieldError>請輸入有效的 email 地址</FieldError>
|
|
131
|
+
</Field>
|
|
126
132
|
</div>
|
|
127
133
|
),
|
|
128
134
|
}
|
|
@@ -155,11 +155,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
|
155
155
|
return (
|
|
156
156
|
<div
|
|
157
157
|
className={cn(
|
|
158
|
-
fieldWrapperStyles({ mode: resolvedMode, variant: variant, size }),
|
|
159
|
-
isEditable && resolvedError && [
|
|
160
|
-
'border-error hover:border-error-hover',
|
|
161
|
-
'focus-within:border-error focus-within:hover:border-error',
|
|
162
|
-
],
|
|
158
|
+
fieldWrapperStyles({ mode: resolvedMode, variant: variant, size, error: resolvedError }),
|
|
163
159
|
// autoWidth:wrapper 縮到 inline-flex + w-auto,讓寬度由 startIcon + input(field-sizing: content)+ endAction 自然累加
|
|
164
160
|
autoWidth && 'inline-flex w-auto',
|
|
165
161
|
className,
|
|
@@ -257,6 +257,7 @@ export const Overview = {
|
|
|
257
257
|
<tbody>
|
|
258
258
|
{[
|
|
259
259
|
['mode', "'edit' | 'display' | 'readonly' | 'disabled'", "'edit'", 'FieldMode 四模式'],
|
|
260
|
+
['variant', "'default' | 'bare' | 'naked'", "'default'", 'Field chrome variant(bare:toolbar / cell-as-input 透明 chrome)'],
|
|
260
261
|
['size', "'sm' | 'md' | 'lg'", "'md'", '尺寸,與 Button 共用 token'],
|
|
261
262
|
['error', 'boolean', 'false', '錯誤狀態,紅色邊框 + aria-invalid'],
|
|
262
263
|
['value', 'string | null', '—', 'URL 值'],
|
|
@@ -264,6 +265,7 @@ export const Overview = {
|
|
|
264
265
|
['placeholder', 'string', "'https://'", '空值時的佔位文字'],
|
|
265
266
|
['disabled', 'boolean', 'false', '等同 mode="disabled"'],
|
|
266
267
|
['label', 'string', '—', '自訂顯示文字(覆蓋自動 hostname 提取)'],
|
|
268
|
+
['showDisplayEndIcon', 'boolean', 'false', 'display 態改包 Field naked wrapper(D-path opt-in,DataTable cell display↔edit 像素對齊;本元件 edit 無 endIcon,故僅 wrapper 無 ItemSuffix)'],
|
|
267
269
|
].map(([p, t, d, desc]) => (
|
|
268
270
|
<tr key={p}><Td mono>{p}</Td><Td mono>{t}</Td><Td mono>{d}</Td><Td>{desc}</Td></tr>
|
|
269
271
|
))}
|
|
@@ -4,7 +4,7 @@ import type { VariantProps } from 'class-variance-authority'
|
|
|
4
4
|
import { cn } from '@/lib/utils'
|
|
5
5
|
import type { FieldMode, FieldVariant } from '@/design-system/components/Field/field-types'
|
|
6
6
|
import { fieldWrapperStyles, bareInputStyles, EMPTY_DISPLAY, fieldDisplayTextClass } from '@/design-system/components/Field/field-wrapper'
|
|
7
|
-
import { useFieldContext, useResolvedFieldSize, useResolvedFieldDisabled, useResolvedFieldMode, useResolvedFieldVariant } from '@/design-system/components/Field/field-context'
|
|
7
|
+
import { useFieldContext, useResolvedFieldSize, useResolvedFieldDisabled, useResolvedFieldMode, useResolvedFieldVariant, useResolvedFieldInvalid } from '@/design-system/components/Field/field-context'
|
|
8
8
|
import { ItemInlineAction } from '@/design-system/patterns/element-anatomy/item-anatomy'
|
|
9
9
|
|
|
10
10
|
// ── URL Validation ──────────────────────────────────────────────────────────
|
|
@@ -110,6 +110,37 @@ const LinkInput = React.forwardRef<HTMLInputElement, LinkInputProps>(
|
|
|
110
110
|
// chrome resolution:per-prop > context > 'default'
|
|
111
111
|
const resolvedVariant: FieldVariant = useResolvedFieldVariant(variantProp)
|
|
112
112
|
|
|
113
|
+
// 2026-07-05 D4:invalid 經 useResolvedFieldInvalid 統一接 fieldCtx.invalid(field-controls.spec.md
|
|
114
|
+
// resolver 契約「error/invalid = prop OR fieldCtx.invalid」)— 原本紅框只吃 errorProp||localError、
|
|
115
|
+
// aria-invalid 卻 inline 加 fieldCtx?.invalid → <Field invalid> 下 AT 聽到 invalid 視覺卻正常框
|
|
116
|
+
// (聚焦還變藍)。紅框與 aria 自此同源。宣告於 display early return 之前(Rules-of-Hooks)。
|
|
117
|
+
const fieldInvalid = useResolvedFieldInvalid(errorProp)
|
|
118
|
+
|
|
119
|
+
// ── Local state / refs ──────────────────────────────────────────────────
|
|
120
|
+
// 2026-07-04 Rules-of-Hooks 修:hooks 必在 display early return 之前宣告,
|
|
121
|
+
// 否則 mode 於 render 間切換 display↔edit 會改變 hook 呼叫數 → React #310 crash
|
|
122
|
+
// (同 beta.76 Combobox 同款修法;僅搬位置,hook 邏輯不變)。
|
|
123
|
+
const [editing, setEditing] = React.useState(false)
|
|
124
|
+
const [localValue, setLocalValue] = React.useState(value ?? '')
|
|
125
|
+
const [localError, setLocalError] = React.useState(false)
|
|
126
|
+
const inputRef = React.useRef<HTMLInputElement | null>(null)
|
|
127
|
+
|
|
128
|
+
// Sync external value → local
|
|
129
|
+
React.useEffect(() => {
|
|
130
|
+
if (!editing) setLocalValue(value ?? '')
|
|
131
|
+
}, [value, editing])
|
|
132
|
+
|
|
133
|
+
// Merge refs
|
|
134
|
+
const setRef = React.useCallback((el: HTMLInputElement | null) => {
|
|
135
|
+
inputRef.current = el
|
|
136
|
+
if (typeof ref === 'function') ref(el)
|
|
137
|
+
else if (ref) (ref as React.MutableRefObject<HTMLInputElement | null>).current = el
|
|
138
|
+
}, [ref])
|
|
139
|
+
|
|
140
|
+
// 2026-05-16 audit codex Round 6:capture rAF + cancel on unmount(defensive hygiene)
|
|
141
|
+
const focusRafIdRef = React.useRef<number>(0)
|
|
142
|
+
React.useEffect(() => () => { if (focusRafIdRef.current) cancelAnimationFrame(focusRafIdRef.current) }, [])
|
|
143
|
+
|
|
113
144
|
// ── mode='display' ─────────────────────────────────────────────────────
|
|
114
145
|
// 純展示:無 input chrome / 無 hover affordance / 無 Pencil edit 入口。
|
|
115
146
|
// 取代既有 LinkInputDisplay sub-component(2026-05-05 Phase B3 retire)。
|
|
@@ -140,30 +171,9 @@ const LinkInput = React.forwardRef<HTMLInputElement, LinkInputProps>(
|
|
|
140
171
|
)
|
|
141
172
|
}
|
|
142
173
|
|
|
143
|
-
const [editing, setEditing] = React.useState(false)
|
|
144
|
-
const [localValue, setLocalValue] = React.useState(value ?? '')
|
|
145
|
-
const [localError, setLocalError] = React.useState(false)
|
|
146
|
-
const inputRef = React.useRef<HTMLInputElement | null>(null)
|
|
147
|
-
|
|
148
|
-
// Sync external value → local
|
|
149
|
-
React.useEffect(() => {
|
|
150
|
-
if (!editing) setLocalValue(value ?? '')
|
|
151
|
-
}, [value, editing])
|
|
152
|
-
|
|
153
|
-
// Merge refs
|
|
154
|
-
const setRef = React.useCallback((el: HTMLInputElement | null) => {
|
|
155
|
-
inputRef.current = el
|
|
156
|
-
if (typeof ref === 'function') ref(el)
|
|
157
|
-
else if (ref) (ref as React.MutableRefObject<HTMLInputElement | null>).current = el
|
|
158
|
-
}, [ref])
|
|
159
|
-
|
|
160
174
|
const hasValidValue = !!value && isValidUrl(value)
|
|
161
175
|
const showLink = isEditable && hasValidValue && !editing && !localError
|
|
162
|
-
const error =
|
|
163
|
-
|
|
164
|
-
// 2026-05-16 audit codex Round 6:capture rAF + cancel on unmount(defensive hygiene)
|
|
165
|
-
const focusRafIdRef = React.useRef<number>(0)
|
|
166
|
-
React.useEffect(() => () => { if (focusRafIdRef.current) cancelAnimationFrame(focusRafIdRef.current) }, [])
|
|
176
|
+
const error = fieldInvalid || localError
|
|
167
177
|
|
|
168
178
|
const handleEdit = () => {
|
|
169
179
|
setEditing(true)
|
|
@@ -233,8 +243,11 @@ const LinkInput = React.forwardRef<HTMLInputElement, LinkInputProps>(
|
|
|
233
243
|
if (showLink) {
|
|
234
244
|
return (
|
|
235
245
|
<div
|
|
236
|
-
|
|
246
|
+
// 2026-07-05 D4:link 顯示分支同樣接 error(errorProp / fieldCtx.invalid)紅框,
|
|
247
|
+
// 否則 <Field invalid> 下 link state 完全無 error 呈現
|
|
248
|
+
className={cn(fieldWrapperStyles({ mode: 'edit', variant: resolvedVariant, size, error }), className)}
|
|
237
249
|
data-field-mode="edit"
|
|
250
|
+
data-error={error ? '' : undefined}
|
|
238
251
|
>
|
|
239
252
|
<span className="flex-1 min-w-0">
|
|
240
253
|
{value && renderLinkAnchor(value, label)}
|
|
@@ -251,11 +264,7 @@ const LinkInput = React.forwardRef<HTMLInputElement, LinkInputProps>(
|
|
|
251
264
|
return (
|
|
252
265
|
<div
|
|
253
266
|
className={cn(
|
|
254
|
-
fieldWrapperStyles({ mode: 'edit', variant: resolvedVariant, size }),
|
|
255
|
-
error && [
|
|
256
|
-
'border-error hover:border-error-hover',
|
|
257
|
-
'focus-within:border-error focus-within:hover:border-error',
|
|
258
|
-
],
|
|
267
|
+
fieldWrapperStyles({ mode: 'edit', variant: resolvedVariant, size, error }),
|
|
259
268
|
className,
|
|
260
269
|
)}
|
|
261
270
|
data-field-mode="edit"
|
|
@@ -270,10 +279,10 @@ const LinkInput = React.forwardRef<HTMLInputElement, LinkInputProps>(
|
|
|
270
279
|
onBlur={handleBlur}
|
|
271
280
|
onKeyDown={handleKeyDown}
|
|
272
281
|
placeholder={placeholder}
|
|
273
|
-
aria-invalid={
|
|
282
|
+
aria-invalid={error || undefined}
|
|
274
283
|
aria-required={fieldCtx?.required || undefined}
|
|
275
284
|
aria-describedby={ariaDescribedByProp ?? fieldCtx?.descriptionId}
|
|
276
|
-
aria-errormessage={ariaErrorMessageProp ?? (
|
|
285
|
+
aria-errormessage={ariaErrorMessageProp ?? (error ? fieldCtx?.errorId : undefined)}
|
|
277
286
|
className={bareInputStyles}
|
|
278
287
|
{...props}
|
|
279
288
|
/>
|
|
@@ -294,9 +303,12 @@ export const linkInputMeta = {
|
|
|
294
303
|
sizes: {
|
|
295
304
|
|
|
296
305
|
},
|
|
297
|
-
states
|
|
306
|
+
// states 對齊真實 state 集:text-input 家族無 'active'(按下)專屬視覺態(同 textareaMeta 修法);
|
|
307
|
+
// readonly / error 為 fieldWrapperStyles mode + errorProp/localError 實有狀態(2026-07-04 audit 對齊)。
|
|
308
|
+
states: ['default', 'hover', 'focus-visible', 'readonly', 'disabled', 'error'],
|
|
298
309
|
tokens: {
|
|
299
|
-
bg
|
|
310
|
+
// bg 經 fieldWrapperStyles 實渲:edit bg-surface / readonly bg-readonly / disabled bg-disabled(field-wrapper.tsx)。
|
|
311
|
+
bg: ['bg-surface', 'bg-readonly', 'bg-disabled'],
|
|
300
312
|
fg: ['text-fg-disabled', 'text-fg-muted', 'text-primary'],
|
|
301
313
|
ring: [],
|
|
302
314
|
},
|
|
@@ -150,6 +150,7 @@ Menu 的設計目的是「**快速掃視多個選項挑一個**」。垂直空
|
|
|
150
150
|
| default | transparent | foreground | — |
|
|
151
151
|
| hover | `--neutral-hover` | foreground | 滑鼠 hover |
|
|
152
152
|
| selected(單選) | `--neutral-selected` | foreground | bg 高亮(不用 ✓ 勾號,避免影響 prefix 對齊) |
|
|
153
|
+
| **selected × hover(疊加)** | `--neutral-selected`(**selected 勝**) | foreground | 2026-07-04 Q2 拍板 M5 明文:bg 是唯一選中指示器,hover 不得洗掉。code = selected 時關 hover bg(menu-item.tsx)。對齊 Ant Menu `:not(-item-selected)` / VS Code list `:hover:not(.selected)`;消費者(DropdownMenu highlighted)同規則 |
|
|
153
154
|
| selected(多選) | transparent | foreground | checkbox 勾選 |
|
|
154
155
|
| disabled | transparent | `--fg-disabled` | disabled prop |
|
|
155
156
|
|
|
@@ -223,7 +224,7 @@ Menu item 的 prefix icon 跟 label 同色(foreground),不是 fg-muted。P
|
|
|
223
224
|
| 建立操作選單(複製 / 刪除 / 分享)| 用 `DropdownMenu` + `DropdownMenuItem`(內部消費 MenuItem)|
|
|
224
225
|
| 建立選值下拉(選項 + 搜尋)| 用 `SelectMenu`(內部消費 MenuItem)|
|
|
225
226
|
| 建立右鍵選單 | 用未來的 `ContextMenu`(待開發)|
|
|
226
|
-
| 直接在 JSX 中用 `<MenuItem>` | ❌ **禁止**——會失去 menu 外層的 Radix 無障礙 / 鍵盤 /
|
|
227
|
+
| 直接在 JSX 中用 `<MenuItem>` | ❌ **禁止**——會失去 menu 外層的 Radix 無障礙 / 鍵盤 / 焦點管理。唯一例外:本元件**自身 stories** 的 internal 展示(demo 容器以 `role="listbox"` 滿足 a11y parent 要求,見 `menu-item.stories.tsx`),產品 code 無豁免 |
|
|
227
228
|
|
|
228
229
|
### 消費者
|
|
229
230
|
|
|
@@ -202,9 +202,11 @@ const MenuItem = React.forwardRef<HTMLDivElement, MenuItemProps>(
|
|
|
202
202
|
disabled && 'text-fg-disabled cursor-not-allowed',
|
|
203
203
|
className,
|
|
204
204
|
)}
|
|
205
|
+
// 2026-07-04 robustness:先 spread 其餘 props、後放 guarded handlers — 消除
|
|
206
|
+
// 「handler 置前 + 後方 runtime filter 同名 key」對屬性順序的隱性依賴,行為不變
|
|
207
|
+
{...Object.fromEntries(Object.entries(props).filter(([k]) => k !== 'onClick' && k !== 'onKeyDown'))}
|
|
205
208
|
onClick={disabled ? undefined : props.onClick}
|
|
206
209
|
onKeyDown={disabled ? undefined : props.onKeyDown}
|
|
207
|
-
{...Object.fromEntries(Object.entries(props).filter(([k]) => k !== 'onClick' && k !== 'onKeyDown'))}
|
|
208
210
|
>
|
|
209
211
|
{/* Prefix 對齊容器 */}
|
|
210
212
|
{hasPrefix && (
|
|
@@ -96,6 +96,7 @@ export const Overview: Story = {
|
|
|
96
96
|
升級方案
|
|
97
97
|
</Button>
|
|
98
98
|
}
|
|
99
|
+
onDismiss={() => {}} // anatomy 4-slot 展示含 dismiss X(2026-07-05 D4:X 僅在傳 onDismiss 時渲染)
|
|
99
100
|
/>
|
|
100
101
|
</SubtleShell>
|
|
101
102
|
</div>
|
|
@@ -189,13 +190,13 @@ export const Overview: Story = {
|
|
|
189
190
|
<Td mono>dismissible</Td>
|
|
190
191
|
<Td mono>boolean</Td>
|
|
191
192
|
<Td mono>true</Td>
|
|
192
|
-
<Td
|
|
193
|
+
<Td>搭配 onDismiss 顯示關閉按鈕(兩者同時成立才渲染 X)</Td>
|
|
193
194
|
</tr>
|
|
194
195
|
<tr>
|
|
195
196
|
<Td mono>onDismiss</Td>
|
|
196
197
|
<Td mono>() => void</Td>
|
|
197
198
|
<Td mono>undefined</Td>
|
|
198
|
-
<Td>關閉按鈕 callback</Td>
|
|
199
|
+
<Td>關閉按鈕 callback;未傳則不渲染 X(2026-07-05 D4)</Td>
|
|
199
200
|
</tr>
|
|
200
201
|
<tr>
|
|
201
202
|
<Td mono>iconClassName</Td>
|
|
@@ -248,6 +249,7 @@ function NoticeInspector() {
|
|
|
248
249
|
) : undefined
|
|
249
250
|
}
|
|
250
251
|
dismissible={dismissible}
|
|
252
|
+
onDismiss={() => {}} // 2026-07-05 D4:X 僅在傳 onDismiss 時渲染,Inspector 切 dismissible 才看得到 X
|
|
251
253
|
/>
|
|
252
254
|
</SubtleShell>
|
|
253
255
|
</div>
|
|
@@ -481,19 +483,22 @@ export const StateBehavior: Story = {
|
|
|
481
483
|
<div>
|
|
482
484
|
<H3>Dismissible 行為</H3>
|
|
483
485
|
<Desc>
|
|
484
|
-
Alert 預設可 dismiss(持久通知,使用者需主動關);Toast 通常不 dismiss(計時器自動關)。兩者共用 Notice
|
|
486
|
+
Alert 預設可 dismiss(持久通知,使用者需主動關);Toast 通常不 dismiss(計時器自動關)。兩者共用 Notice,透過
|
|
485
487
|
<code className="font-mono text-footnote mx-1">dismissible</code>
|
|
486
|
-
|
|
488
|
+
+
|
|
489
|
+
<code className="font-mono text-footnote mx-1">onDismiss</code>
|
|
490
|
+
控制(X 僅在兩者同時成立時渲染——未接 handler 的 X 是死按鈕,2026-07-05 D4)。
|
|
487
491
|
</Desc>
|
|
488
492
|
<div className="grid grid-cols-2 gap-3 max-w-3xl">
|
|
489
493
|
<div>
|
|
490
|
-
<p className="text-caption text-fg-muted mb-2">dismissible
|
|
494
|
+
<p className="text-caption text-fg-muted mb-2">dismissible + onDismiss</p>
|
|
491
495
|
<SubtleShell variant="info">
|
|
492
496
|
<Notice
|
|
493
497
|
variant="info"
|
|
494
498
|
iconClassName={SUBTLE_ICON_COLOR.info}
|
|
495
499
|
title="Audit log 僅限 Business 方案"
|
|
496
500
|
dismissible
|
|
501
|
+
onDismiss={() => {}}
|
|
497
502
|
/>
|
|
498
503
|
</SubtleShell>
|
|
499
504
|
</div>
|
|
@@ -57,7 +57,7 @@ item-layout 4-slot:
|
|
|
57
57
|
```
|
|
58
58
|
|
|
59
59
|
- Icon: 16px,`h-[1lh]` inline 對齊 first line
|
|
60
|
-
- Dismiss X: `<Button iconOnly dismiss size="xs" />` — chrome corner action group region(Cat 3)。xs 是 **Notification banner family canonical**(Notice / Alert / Toast inherit):ephemeral banner `px-4 py-3` 固定不隨 density,dismiss 邊角小 affordance 輕量不搶眼。詳見 `patterns/overlay-surface/overlay-surface.spec.md`「Chrome dismiss size canonical」(overlay header 走 sm native + 負 margin trick;只有 notification banner family 用 xs)+ `patterns/element-anatomy/
|
|
60
|
+
- Dismiss X: `<Button iconOnly dismiss size="xs" />` — chrome corner action group region(Cat 3)。xs 是 **Notification banner family canonical**(Notice / Alert / Toast inherit):ephemeral banner `px-4 py-3` 固定不隨 density,dismiss 邊角小 affordance 輕量不搶眼。詳見 `patterns/overlay-surface/overlay-surface.spec.md`「Chrome dismiss size canonical」(overlay header 走 sm native + 負 margin trick;只有 notification banner family 用 xs)+ `patterns/element-anatomy/inline-action.spec.md`「Dismiss canonical — X close only」
|
|
61
61
|
- endContent: 通常放 `<Button variant="tertiary" size="xs">`
|
|
62
62
|
|
|
63
63
|
## Variant
|
|
@@ -121,6 +121,7 @@ Notice 是 **Toast / Alert 共用的 layout primitive**,刻意不擁有尺寸與
|
|
|
121
121
|
- **Loading**:Notice 非 async surface,無 loading state。Body 內若 consumer 注入 CTA Button,該 Button 自行處理 loading。
|
|
122
122
|
- **Empty**:Notice 的 `title` 為必填(TypeScript required prop,消費合約),`description` 選填;layout 至少 1 行 title 內容。誤傳空字串時照常 render(無 runtime warn / throw),空 title 的內容責任在 consumer(Alert / Toast)。`neutral` variant 不渲 status icon 時即為 title-only icon-less 形態(見 `NeutralTitleOnly` story),仍合法 render。
|
|
123
123
|
- **Icon-only / variant=neutral**:`neutral` variant 不渲 status icon,layout 自動收斂為 `[title + description?] [endContent?] [dismiss X?]` 三 slot。
|
|
124
|
+
- **`dismissible` 無 `onDismiss`**(2026-07-05 D4):X 渲染條件 = `dismissible`(預設 true)**且**有傳 `onDismiss`——Notice / Alert 為 controlled-only(不自我移除),無 handler 的 X 是可 focus、SR 播報「關閉通知」但按下零反應的死按鈕(假 affordance)。對齊 Polaris Banner(`onDismiss` 存在才顯示 X)/ Ant Alert(`closable` 預設 false)。顯式傳 `dismissible={true}` 卻缺 `onDismiss` 時 dev-only `console.warn` 提示(預設未傳不吵)。
|
|
124
125
|
|
|
125
126
|
---
|
|
126
127
|
|
|
@@ -138,12 +139,12 @@ Notice 是 **Toast / Alert 共用的 layout primitive**,刻意不擁有尺寸與
|
|
|
138
139
|
|
|
139
140
|
**Keyboard 行為**:
|
|
140
141
|
|
|
141
|
-
- Tab — focus dismiss button(若 dismissible)
|
|
142
|
+
- Tab — focus dismiss button(若 dismissible 且有傳 `onDismiss`——X 僅在兩者同時成立時渲染,見「邊界案例」,2026-07-05 D4;consumer 經 `endContent` 注入互動元素時,其 DOM 順序在 dismiss 之前,Tab 先落 endContent)
|
|
142
143
|
- Enter / Space — 觸發 focus 中的 dismiss button(原生 button 行為,呼叫 `onDismiss`)
|
|
143
144
|
|
|
144
145
|
Notice **不**自帶 Esc-to-dismiss 行為(`notice.tsx` 無 keydown handler);dismiss 純粹由 dismiss button 的 `onClick={onDismiss}` 觸發。若 consumer(Alert / Toast)需要 Esc 關閉,於 consumer 層自行掛 keydown。Dismiss 後的焦點處置 Notice 同樣不管理(無 focus restoration 邏輯)——節點移除後焦點落點由 consumer(Alert / Toast host)決定。
|
|
145
146
|
|
|
146
|
-
**Focus
|
|
147
|
+
**Focus**:Notice 自身渲染的唯一 focusable 元素是 dismiss `<Button>`(consumer 經 `endContent` 注入的互動元素——如上方建議的 tertiary Button——由 consumer 自管 a11y),focus indicator 走 Button canonical(`focus-visible:ring-2 ring-ring`,box-shadow ring 非 CSS `outline`);Notice 本身無 focus management(dismiss 後焦點處置見上段,由 consumer 決定)。
|
|
147
148
|
|
|
148
149
|
**驗證**:Storybook a11y addon panel 應 0 critical violation;鍵盤完整可操作(無需滑鼠)。WCAG AA contrast ≥ 4.5:1(text)/ 3:1(UI)。
|
|
149
150
|
|
|
@@ -6,7 +6,7 @@ import * as React from 'react'
|
|
|
6
6
|
import { X as XIcon, Info, CircleCheck, TriangleAlert, XCircle, type LucideIcon } from 'lucide-react'
|
|
7
7
|
import { cn } from '@/lib/utils'
|
|
8
8
|
import { Button } from '@/design-system/components/Button/button'
|
|
9
|
-
import { ItemContent, ItemPrefix, ItemSuffix } from '@/design-system/patterns/element-anatomy/item-anatomy'
|
|
9
|
+
import { ICON_SIZE, ItemContent, ItemPrefix, ItemSuffix } from '@/design-system/patterns/element-anatomy/item-anatomy'
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Notice — Toast / Alert 共用的視覺佈局層
|
|
@@ -75,6 +75,11 @@ export interface NoticeProps
|
|
|
75
75
|
title: React.ReactNode
|
|
76
76
|
description?: React.ReactNode
|
|
77
77
|
endContent?: React.ReactNode
|
|
78
|
+
/**
|
|
79
|
+
* 顯示關閉鈕(預設 true)。**需同時傳 `onDismiss` 才會渲染 X** — Notice 是 controlled-only
|
|
80
|
+
* (不自我移除),無 handler 的 X 是可 focus、SR 播報「關閉通知」但按下零反應的死按鈕
|
|
81
|
+
* (2026-07-05 D4;對齊 Polaris Banner `onDismiss` 存在才顯示 X / Ant Alert `closable` 預設 false)。
|
|
82
|
+
*/
|
|
78
83
|
dismissible?: boolean
|
|
79
84
|
onDismiss?: () => void
|
|
80
85
|
/** ARIA label for the dismiss button. Override for i18n. Default: "關閉通知" */
|
|
@@ -99,7 +104,9 @@ const Notice = React.forwardRef<HTMLDivElement, NoticeProps>(
|
|
|
99
104
|
title,
|
|
100
105
|
description,
|
|
101
106
|
endContent,
|
|
102
|
-
|
|
107
|
+
// 不 destructure-default(保留「顯式傳 true」的可辨識性給下方 dev-warn);等效預設 true 由
|
|
108
|
+
// showDismiss 的 `!== false` 承擔。
|
|
109
|
+
dismissible,
|
|
103
110
|
onDismiss,
|
|
104
111
|
dismissAriaLabel = '關閉通知', // i18n-allow: DS default; consumer override via dismissAriaLabel prop
|
|
105
112
|
iconClassName,
|
|
@@ -110,6 +117,16 @@ const Notice = React.forwardRef<HTMLDivElement, NoticeProps>(
|
|
|
110
117
|
) => {
|
|
111
118
|
const StatusIcon = VARIANT_ICON[variant]
|
|
112
119
|
|
|
120
|
+
// 2026-07-05 D4:X 渲染條件 = dismissible(預設 true)**且**有傳 onDismiss — 原 `dismissible &&`
|
|
121
|
+
// 單獨判斷讓預設用法 <Alert title="…"/> 渲染一顆死 X(controlled-only 不自我移除,onClick=undefined
|
|
122
|
+
// 零反應;keyboard / SR 使用者按 Enter 零回饋 = 假 affordance)。對齊 Polaris Banner(onDismiss
|
|
123
|
+
// 存在才顯示 X)/ Ant Alert(closable 預設 false)。
|
|
124
|
+
const showDismiss = dismissible !== false && onDismiss != null
|
|
125
|
+
if (process.env.NODE_ENV !== 'production' && dismissible === true && onDismiss == null) {
|
|
126
|
+
// 只在「顯式傳 dismissible={true} 卻沒接 onDismiss」的消費端矛盾時提示(預設 undefined 不吵)
|
|
127
|
+
console.warn('[Notice] dismissible=true 但未傳 onDismiss,關閉鈕不會渲染(需同時提供 onDismiss)。')
|
|
128
|
+
}
|
|
129
|
+
|
|
113
130
|
return (
|
|
114
131
|
<div
|
|
115
132
|
ref={ref}
|
|
@@ -118,7 +135,7 @@ const Notice = React.forwardRef<HTMLDivElement, NoticeProps>(
|
|
|
118
135
|
>
|
|
119
136
|
{StatusIcon && (
|
|
120
137
|
<ItemPrefix>
|
|
121
|
-
<StatusIcon size={
|
|
138
|
+
<StatusIcon size={ICON_SIZE.md} className={cn('shrink-0', iconClassName)} aria-hidden />
|
|
122
139
|
</ItemPrefix>
|
|
123
140
|
)}
|
|
124
141
|
|
|
@@ -130,7 +147,7 @@ const Notice = React.forwardRef<HTMLDivElement, NoticeProps>(
|
|
|
130
147
|
labelClassName={description ? 'font-medium' : undefined}
|
|
131
148
|
/>
|
|
132
149
|
|
|
133
|
-
{(endContent ||
|
|
150
|
+
{(endContent || showDismiss) && (
|
|
134
151
|
// 2026-06-15 user 拍板:消費 ItemSuffix primitive(原手刻 div + @row-slot-handcraft-allow
|
|
135
152
|
// 已移除)。item-anatomy.spec.md 把 Notice 的 action/dismiss 對應到 suffix slot;ItemSuffix
|
|
136
153
|
// base geometry(h-[1lh] shrink-0 ml-auto flex items-center gap-2)正是此處所需,hoverReveal
|
|
@@ -138,7 +155,7 @@ const Notice = React.forwardRef<HTMLDivElement, NoticeProps>(
|
|
|
138
155
|
// family canonical,overlay-surface.spec.md「Chrome dismiss size canonical」)。
|
|
139
156
|
<ItemSuffix>
|
|
140
157
|
{endContent}
|
|
141
|
-
{
|
|
158
|
+
{showDismiss && (
|
|
142
159
|
<Button
|
|
143
160
|
data-dismiss
|
|
144
161
|
iconOnly
|
|
@@ -178,7 +178,7 @@ export const Overview = {
|
|
|
178
178
|
<div className="flex flex-col gap-2 items-start">
|
|
179
179
|
<span className="text-[11px] text-fg-muted font-medium">edit</span>
|
|
180
180
|
<div className="inline-flex items-center border-2 border-dashed border-primary/30 rounded-md px-3 py-2.5 gap-2">
|
|
181
|
-
{[{ name: 'wrapper', color: 'info' }, { name: '
|
|
181
|
+
{[{ name: 'wrapper', color: 'info' }, { name: 'input', color: 'success' }, { name: 'endAction', color: 'warning' }].map((s) => (
|
|
182
182
|
<span key={s.name} className="rounded px-2 py-1 text-[11px] font-mono border border-dashed"
|
|
183
183
|
style={{ borderColor: `var(--${s.color})`, backgroundColor: `var(--${s.color}-subtle)`, color: `var(--${s.color})` }}>{s.name}</span>
|
|
184
184
|
))}
|
|
@@ -189,7 +189,7 @@ export const Overview = {
|
|
|
189
189
|
<div className="flex flex-col gap-2 items-start">
|
|
190
190
|
<span className="text-[11px] text-fg-muted font-medium">readonly / disabled</span>
|
|
191
191
|
<div className="inline-flex items-center border-2 border-dashed border-primary/30 rounded-md px-3 py-2.5 gap-2">
|
|
192
|
-
{[{ name: 'wrapper', color: 'info' }, { name: '
|
|
192
|
+
{[{ name: 'wrapper', color: 'info' }, { name: 'formatted span', color: 'success' }].map((s) => (
|
|
193
193
|
<span key={s.name} className="rounded px-2 py-1 text-[11px] font-mono border border-dashed"
|
|
194
194
|
style={{ borderColor: `var(--${s.color})`, backgroundColor: `var(--${s.color}-subtle)`, color: `var(--${s.color})` }}>{s.name}</span>
|
|
195
195
|
))}
|
|
@@ -255,6 +255,7 @@ export const Overview = {
|
|
|
255
255
|
['precision', 'number', '—', '小數位數'],
|
|
256
256
|
['locale', 'string', "'en-US'", '數字格式 locale'],
|
|
257
257
|
['endAction', 'InlineActionConfig', '—', '右側 inline action(宣告式 API)'],
|
|
258
|
+
['endSlot', 'ReactNode', '—', '右側自訂 slot(escape hatch,僅 edit 渲染);與 endAction 互斥,同時傳時 endSlot 優先'],
|
|
258
259
|
['disabled', 'boolean', 'false', '原生屬性;mode 未顯式指定時解析為 disabled(顯式 mode prop 永遠最優先,useResolvedFieldMode SSOT)'],
|
|
259
260
|
['readOnly', 'boolean', 'false', '原生屬性;mode 未顯式指定且無 Field context mode 時解析為 readonly(優先序 mode > 有效 disabled > fieldCtx.mode > readOnly)'],
|
|
260
261
|
].map(([p, t, d, desc]) => (
|
|
@@ -744,7 +745,7 @@ export const StateBehavior = {
|
|
|
744
745
|
<div key={m} className="flex flex-col gap-1 items-start">
|
|
745
746
|
<span className="text-[11px] text-fg-muted font-mono">{m}</span>
|
|
746
747
|
<div className="w-[160px]"><NumberInput mode={m} value={null} prefix="$" /></div>
|
|
747
|
-
<span className="text-[10px] text-fg-muted">{m === 'edit' ? '空白 input' : 'em dash — + fg-muted'}</span>
|
|
748
|
+
<span className="text-[10px] text-fg-muted">{m === 'edit' ? '空白 input' : m === 'disabled' ? 'em dash — + fg-disabled(M24 state > emphasis)' : 'em dash — + fg-muted'}</span>
|
|
748
749
|
</div>
|
|
749
750
|
))}
|
|
750
751
|
</div>
|
|
@@ -781,7 +782,7 @@ export const Accessibility = {
|
|
|
781
782
|
render: () => (
|
|
782
783
|
<div className="max-w-3xl text-body text-fg-secondary">
|
|
783
784
|
<h3 className="text-h5 text-foreground mb-2">無障礙設計</h3>
|
|
784
|
-
<p className="whitespace-pre-line">{"ARIA :使用原生 <input type=\"text\" inputMode=\"decimal\"> ;外層 Field 自動補上標籤、錯誤狀態與描述的關聯。 inputMode=\"decimal\" 讓行動裝置彈出數字鍵盤。\n\n 鍵盤行為 :\n\n- Tab — 移入欄位\n- 數字鍵 — 輸入數值\n\n 焦點 :原生 input
|
|
785
|
+
<p className="whitespace-pre-line">{"ARIA :使用原生 <input type=\"text\" inputMode=\"decimal\"> ;外層 Field 自動補上標籤、錯誤狀態與描述的關聯。 inputMode=\"decimal\" 讓行動裝置彈出數字鍵盤。\n\n 鍵盤行為 :\n\n- Tab — 移入欄位\n- 數字鍵 — 輸入數值\n\n 焦點 :原生 input 以 outline-none 移除預設焦點外框;聚焦時由元件自身 wrapper 的 focus-within 將邊框轉為主色(border-primary)。\n\n 驗證 :Storybook 無障礙檢查面板應為 0 項嚴重問題;不靠滑鼠也能完整操作。文字對比度至少 4.5:1、介面元件至少 3:1(WCAG AA)。"}</p>
|
|
785
786
|
</div>
|
|
786
787
|
),
|
|
787
788
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// @principles-rationale: UsageGuidance merges WhenToUse + WhenNotToUse into single 使用指引 story per refactor task (2026-04-26)
|
|
2
|
+
// @story-baseline: packages/design-system/src/components/DataTable/data-table.stories.tsx#NumberAlignment
|
|
2
3
|
import React from 'react'
|
|
3
4
|
import LinkTo from '@storybook/addon-links/react'
|
|
4
5
|
import type { Meta, StoryObj } from '@storybook/react'
|
|
@@ -35,7 +36,7 @@ const Label = ({ children, warn }: { children: React.ReactNode; warn?: boolean }
|
|
|
35
36
|
|
|
36
37
|
const Section = ({ title, children }: { title: string; children: React.ReactNode }) => (
|
|
37
38
|
<section className="mb-12">
|
|
38
|
-
<h2 className="text-
|
|
39
|
+
<h2 className="text-h3 font-bold text-foreground mb-4 pb-2 border-b border-border">{title}</h2>
|
|
39
40
|
{children}
|
|
40
41
|
</section>
|
|
41
42
|
)
|
|
@@ -106,7 +106,8 @@ col.accessor('price', {
|
|
|
106
106
|
- **Disabled**:由 Field SSOT own(`Field/field-controls.spec.md` State machine 段)。視覺:wrapper bg → `bg-disabled`(neutral-2,`semantic.css` `--bg-disabled`)、formatted text → `text-fg-disabled`(M24 state>emphasis)。Display mode + disabled 維持格式化輸出但 token 切 disabled。
|
|
107
107
|
- **Loading**:NumberInput 不提供 loading state(`NumberInputProps` 無 `loading` prop、無 `CircularProgress` / `aria-busy` 邏輯)。數值輸入為同步操作,無 async fetch 語意;若需 async 場景(如遠端校驗)請走外層 Field validation + `endSlot` 自訂 spinner。
|
|
108
108
|
- **Empty(null / undefined / 空字串)**:Display mode 渲 `—`(em dash + `text-fg-muted`);Edit mode placeholder 走 default placeholder color。
|
|
109
|
-
- **Invalid input**(non-numeric):input 為 `type="text" inputMode="decimal"`,onChange 以 `Number(raw)` parse,NaN 時忽略不觸發 `onChange`(value 維持原值);搭配 Field validation 渲 error variant(`aria-invalid="true"` + `
|
|
109
|
+
- **Invalid input**(non-numeric):input 為 `type="text" inputMode="decimal"`,onChange 以 `Number(raw)` parse,NaN 時忽略不觸發 `onChange`(value 維持原值);搭配 Field validation 渲 error variant(`aria-invalid="true"` + wrapper `border-error`(hover 時 `border-error-hover`)+ 下方 error message)。
|
|
110
|
+
- **輸入中間態**(`-`、`1.`、`0.0` 等 parse 後 lossy 的 raw 字串,2026-07-05 D4 codify):edit mode 以 internal draft string 承載——輸入期間 DOM 顯示 draft 原字串(負號、尾點不被 controlled 回寫吃掉),parse 成功即時同步 `onChange(parsed)`;blur / Enter commit(清 draft、顯示回 committed value,不合法殘字自然還原)、Escape 棄 draft(對齊 form-validation 規則 4)。value 面語意不變:`''` / `'-'` 視為 `null`。對齊 Ant InputNumber / Adobe Spectrum NumberField internal draft canonical。 <!-- @benchmark-unverified -->
|
|
110
111
|
- **負數 / 超大數字**:`toLocaleString(locale)` 自動處理負號與千分位分組,無特殊 prop。
|
|
111
112
|
- **科學記號輸入**(`1e5`):`Number(raw)` parse 為 `100000`,非 NaN 即觸發 `onChange`(`number-input.tsx:136-138`)。
|
|
112
113
|
- **精度上限**:`precision` > 6 為禁止事項(浮點精度雜訊),極端精度走 `Input` + 自驗證。
|
|
@@ -89,6 +89,8 @@ const NumberInput = React.forwardRef<HTMLInputElement, NumberInputProps>(
|
|
|
89
89
|
disabled: disabledProp,
|
|
90
90
|
readOnly,
|
|
91
91
|
id: idProp,
|
|
92
|
+
onBlur: onBlurProp,
|
|
93
|
+
onKeyDown: onKeyDownProp,
|
|
92
94
|
'aria-describedby': ariaDescribedByProp,
|
|
93
95
|
'aria-errormessage': ariaErrorMessageProp,
|
|
94
96
|
...props
|
|
@@ -104,6 +106,15 @@ const NumberInput = React.forwardRef<HTMLInputElement, NumberInputProps>(
|
|
|
104
106
|
// 2026-06-08 SSOT:mode 經 useResolvedFieldMode 統一解析(prop > 有效 disabled > fieldCtx.mode > readOnly > 'edit')
|
|
105
107
|
const resolvedMode: FieldMode = useResolvedFieldMode({ mode: modeProp, disabled, readOnly })
|
|
106
108
|
|
|
109
|
+
// 2026-07-05 D4 draft state:controlled value 回寫會吃掉輸入中間態('-'、'1.'、'0.0' 等
|
|
110
|
+
// parse 後 lossy 的 raw 字串)— 例:已有值 5 全選打「-」→ onChange(null) 回寫把 DOM 重設
|
|
111
|
+
// 為空,負號被吃、無法打出 -5。修法對齊 Ant InputNumber / Adobe Spectrum NumberField 的
|
|
112
|
+
// internal draft string canonical:輸入期間 DOM 顯示以 local draft 優先,parse 成功即時同步
|
|
113
|
+
// onChange(parsed);blur / Enter commit 清 draft 回 value 顯示;Escape 一併棄 draft(對齊
|
|
114
|
+
// form-validation 規則 4 回復原值)。宣告於 non-edit early return 之前(Rules-of-Hooks,
|
|
115
|
+
// 同 LinkInput 2026-07-04 修法)。
|
|
116
|
+
const [draft, setDraft] = React.useState<string | null>(null)
|
|
117
|
+
|
|
107
118
|
// display / readonly / disabled 都顯示格式化值(span 取代 input)
|
|
108
119
|
if (resolvedMode !== 'edit') {
|
|
109
120
|
return (
|
|
@@ -125,10 +136,11 @@ const NumberInput = React.forwardRef<HTMLInputElement, NumberInputProps>(
|
|
|
125
136
|
)
|
|
126
137
|
}
|
|
127
138
|
|
|
128
|
-
// edit
|
|
139
|
+
// edit 模式:raw 數值輸入(draft 承載中間態;value 面語意不變 — ''/'-' 視為空、NaN 忽略)
|
|
129
140
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
130
|
-
if (!onChange) return
|
|
131
141
|
const raw = e.target.value
|
|
142
|
+
setDraft(raw)
|
|
143
|
+
if (!onChange) return
|
|
132
144
|
if (raw === '' || raw === '-') {
|
|
133
145
|
onChange(null)
|
|
134
146
|
return
|
|
@@ -142,11 +154,7 @@ const NumberInput = React.forwardRef<HTMLInputElement, NumberInputProps>(
|
|
|
142
154
|
return (
|
|
143
155
|
<div
|
|
144
156
|
className={cn(
|
|
145
|
-
fieldWrapperStyles({ mode: 'edit', variant: variant, size }),
|
|
146
|
-
error && [
|
|
147
|
-
'border-error hover:border-error-hover',
|
|
148
|
-
'focus-within:border-error focus-within:hover:border-error',
|
|
149
|
-
],
|
|
157
|
+
fieldWrapperStyles({ mode: 'edit', variant: variant, size, error }),
|
|
150
158
|
className,
|
|
151
159
|
)}
|
|
152
160
|
data-field-mode="edit"
|
|
@@ -157,8 +165,15 @@ const NumberInput = React.forwardRef<HTMLInputElement, NumberInputProps>(
|
|
|
157
165
|
type="text"
|
|
158
166
|
inputMode="decimal"
|
|
159
167
|
id={idProp ?? fieldCtx?.id}
|
|
160
|
-
value
|
|
168
|
+
// 輸入期間 draft(raw 字串)優先;非輸入期間才從 value prop 同步顯示(2026-07-05 D4)
|
|
169
|
+
value={draft ?? (value != null ? String(value) : '')}
|
|
161
170
|
onChange={handleChange}
|
|
171
|
+
// blur / Enter = commit(清 draft,顯示回 committed value);Escape 棄 draft
|
|
172
|
+
onBlur={(e) => { setDraft(null); onBlurProp?.(e) }}
|
|
173
|
+
onKeyDown={(e) => {
|
|
174
|
+
if (e.key === 'Enter' || e.key === 'Escape') setDraft(null)
|
|
175
|
+
onKeyDownProp?.(e)
|
|
176
|
+
}}
|
|
162
177
|
aria-invalid={error || undefined}
|
|
163
178
|
aria-required={fieldCtx?.required || undefined}
|
|
164
179
|
aria-describedby={ariaDescribedByProp ?? fieldCtx?.descriptionId}
|
|
@@ -46,8 +46,8 @@ type ShapeKey = 'circle' | 'tag'
|
|
|
46
46
|
const SIZES: SizeKey[] = ['sm', 'md', 'lg']
|
|
47
47
|
const SHAPES: ShapeKey[] = ['circle', 'tag']
|
|
48
48
|
|
|
49
|
-
// font-size 依 shape 分流(re-verified 2026-
|
|
50
|
-
// map(overflow-indicator.tsx:
|
|
49
|
+
// font-size 依 shape 分流(re-verified 2026-07-04):circle shape 走元件內建 triggerText
|
|
50
|
+
// map(overflow-indicator.tsx:28-32),tag shape 走 tagVariants({ size })(tag.tsx:40-44)。
|
|
51
51
|
// circleText / tagText 兩欄分別對應,避免把 circle-only 字級當成兩 shape 通用。
|
|
52
52
|
const SIZE_SPECS: Record<
|
|
53
53
|
SizeKey,
|
|
@@ -467,7 +467,7 @@ export const SizeMatrix: Story = {
|
|
|
467
467
|
<code className="font-mono">tag</code> 走 <code className="font-mono">tagVariants</code>(對齊 Tag 字級)。
|
|
468
468
|
circle shape 的 <code className="font-mono">sm</code> 用
|
|
469
469
|
<code className="font-mono mx-1">text-[10px]</code>
|
|
470
|
-
的理由見 spec
|
|
470
|
+
的理由見 spec「尺寸」段——與 Badge 共享 micro-indicator typography tier 的特殊例外(10px)。
|
|
471
471
|
</p>
|
|
472
472
|
</div>
|
|
473
473
|
|