@qijenchen/design-system 0.1.0-beta.80 → 0.1.0-beta.82
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 -1
- 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 +8 -3
- 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 +4 -1
- package/dist/components/BulkActionBar/bulk-action-bar.d.ts.map +1 -1
- package/dist/components/BulkActionBar/bulk-action-bar.js +2 -2
- package/dist/components/BulkActionBar/bulk-action-bar.js.map +1 -1
- package/dist/components/Button/button.d.ts +4 -4
- package/dist/components/Button/button.d.ts.map +1 -1
- package/dist/components/Button/button.js +73 -44
- 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/Calendar/calendar.d.ts +3 -1
- package/dist/components/Calendar/calendar.d.ts.map +1 -1
- package/dist/components/Calendar/calendar.js +40 -17
- package/dist/components/Calendar/calendar.js.map +1 -1
- package/dist/components/Carousel/carousel.d.ts +1 -1
- package/dist/components/Carousel/carousel.d.ts.map +1 -1
- package/dist/components/Carousel/carousel.js +3 -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 +6 -3
- package/dist/components/Checkbox/checkbox.d.ts.map +1 -1
- package/dist/components/Checkbox/checkbox.js +26 -4
- 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 +9 -3
- package/dist/components/Chip/chip.d.ts.map +1 -1
- package/dist/components/Chip/chip.js +46 -13
- 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 +10 -7
- package/dist/components/Coachmark/coachmark.js.map +1 -1
- package/dist/components/Combobox/combobox.d.ts +3 -1
- package/dist/components/Combobox/combobox.d.ts.map +1 -1
- package/dist/components/Combobox/combobox.js +11 -7
- package/dist/components/Combobox/combobox.js.map +1 -1
- package/dist/components/Command/command.d.ts +2 -2
- package/dist/components/Command/command.d.ts.map +1 -1
- package/dist/components/Command/command.js +4 -2
- 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 +38 -7
- package/dist/components/DataTable/data-table.d.ts.map +1 -1
- package/dist/components/DataTable/data-table.js +213 -63
- 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 +3 -3
- package/dist/components/DateGrid/date-grid.d.ts.map +1 -1
- package/dist/components/DateGrid/date-grid.js +52 -6
- package/dist/components/DateGrid/date-grid.js.map +1 -1
- package/dist/components/DatePicker/date-picker.d.ts +2 -8
- package/dist/components/DatePicker/date-picker.d.ts.map +1 -1
- package/dist/components/DatePicker/date-picker.js +139 -109
- package/dist/components/DatePicker/date-picker.js.map +1 -1
- package/dist/components/DescriptionList/description-list.d.ts.map +1 -1
- package/dist/components/DescriptionList/description-list.js +1 -1
- package/dist/components/DescriptionList/description-list.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 +4 -4
- package/dist/components/DropdownMenu/dropdown-menu.d.ts.map +1 -1
- package/dist/components/DropdownMenu/dropdown-menu.js +25 -8
- package/dist/components/DropdownMenu/dropdown-menu.js.map +1 -1
- package/dist/components/Empty/empty.d.ts.map +1 -1
- package/dist/components/Empty/empty.js +1 -1
- package/dist/components/Empty/empty.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/field.d.ts +1 -1
- package/dist/components/Field/field.d.ts.map +1 -1
- package/dist/components/Field/field.js +2 -1
- package/dist/components/Field/field.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 +31 -21
- 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 +1 -1
- package/dist/components/FileUpload/file-upload.d.ts.map +1 -1
- package/dist/components/FileUpload/file-upload.js +22 -10
- package/dist/components/FileUpload/file-upload.js.map +1 -1
- package/dist/components/FileViewer/can-render-image.d.ts +6 -0
- package/dist/components/FileViewer/can-render-image.d.ts.map +1 -0
- package/dist/components/FileViewer/can-render-image.js +11 -0
- package/dist/components/FileViewer/can-render-image.js.map +1 -0
- package/dist/components/FileViewer/file-viewer.d.ts +2 -2
- package/dist/components/FileViewer/file-viewer.d.ts.map +1 -1
- package/dist/components/FileViewer/file-viewer.js +24 -16
- package/dist/components/FileViewer/file-viewer.js.map +1 -1
- package/dist/components/FileViewer/image-renderer.d.ts +0 -5
- package/dist/components/FileViewer/image-renderer.d.ts.map +1 -1
- package/dist/components/FileViewer/image-renderer.js +1 -9
- package/dist/components/FileViewer/image-renderer.js.map +1 -1
- package/dist/components/HoverCard/hover-card.d.ts.map +1 -1
- package/dist/components/HoverCard/hover-card.js +3 -1
- package/dist/components/HoverCard/hover-card.js.map +1 -1
- package/dist/components/Input/input.d.ts +26 -3
- package/dist/components/Input/input.d.ts.map +1 -1
- package/dist/components/Input/input.js +12 -8
- package/dist/components/Input/input.js.map +1 -1
- package/dist/components/LinkInput/link-input.d.ts +3 -3
- 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 +5 -4
- 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 +1 -1
- package/dist/components/NumberInput/number-input.d.ts.map +1 -1
- package/dist/components/NumberInput/number-input.js +17 -8
- 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/Pagination/index.d.ts +2 -0
- package/dist/components/Pagination/index.d.ts.map +1 -0
- package/dist/components/Pagination/index.js +6 -0
- package/dist/components/Pagination/index.js.map +1 -0
- package/dist/components/Pagination/pagination.d.ts +49 -0
- package/dist/components/Pagination/pagination.d.ts.map +1 -0
- package/dist/components/Pagination/pagination.js +182 -0
- package/dist/components/Pagination/pagination.js.map +1 -0
- package/dist/components/PeoplePicker/people-picker.d.ts +3 -3
- package/dist/components/PeoplePicker/people-picker.d.ts.map +1 -1
- package/dist/components/PeoplePicker/people-picker.js +38 -36
- package/dist/components/PeoplePicker/people-picker.js.map +1 -1
- package/dist/components/PeoplePicker/person-display.d.ts +4 -0
- package/dist/components/PeoplePicker/person-display.d.ts.map +1 -1
- package/dist/components/PeoplePicker/person-display.js +3 -3
- package/dist/components/PeoplePicker/person-display.js.map +1 -1
- package/dist/components/Popover/popover.d.ts.map +1 -1
- package/dist/components/Popover/popover.js +58 -32
- package/dist/components/Popover/popover.js.map +1 -1
- package/dist/components/ProfileCard/profile-card.d.ts +11 -3
- package/dist/components/ProfileCard/profile-card.d.ts.map +1 -1
- package/dist/components/ProfileCard/profile-card.js +14 -4
- 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 +28 -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 +23 -10
- package/dist/components/SegmentedControl/segmented-control.js.map +1 -1
- package/dist/components/Select/select.d.ts +10 -4
- package/dist/components/Select/select.d.ts.map +1 -1
- package/dist/components/Select/select.js +57 -14
- package/dist/components/Select/select.js.map +1 -1
- package/dist/components/SelectMenu/index.js +4 -2
- package/dist/components/SelectMenu/select-menu.d.ts +31 -6
- package/dist/components/SelectMenu/select-menu.d.ts.map +1 -1
- package/dist/components/SelectMenu/select-menu.js +169 -85
- 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 +3 -2
- package/dist/components/Sheet/sheet.js.map +1 -1
- package/dist/components/Sidebar/sidebar.d.ts +2 -2
- package/dist/components/Sidebar/sidebar.d.ts.map +1 -1
- package/dist/components/Sidebar/sidebar.js +28 -10
- 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/Slider/slider.d.ts +1 -1
- package/dist/components/Slider/slider.d.ts.map +1 -1
- package/dist/components/Slider/slider.js +7 -3
- package/dist/components/Slider/slider.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 +47 -9
- package/dist/components/Switch/switch.js.map +1 -1
- package/dist/components/Tabs/tabs.d.ts +8 -5
- package/dist/components/Tabs/tabs.d.ts.map +1 -1
- package/dist/components/Tabs/tabs.js +45 -33
- 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 +20 -4
- 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 +24 -13
- 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 +23 -12
- 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 +7 -3
- 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 +1 -1
- package/dist/components/TreeView/tree-view.d.ts.map +1 -1
- package/dist/components/TreeView/tree-view.js +11 -5
- package/dist/components/TreeView/tree-view.js.map +1 -1
- package/dist/hooks/use-controllable.d.ts.map +1 -1
- package/dist/hooks/use-controllable.js +4 -2
- package/dist/hooks/use-controllable.js.map +1 -1
- package/dist/hooks/use-is-touch-device.d.ts.map +1 -1
- package/dist/hooks/use-is-touch-device.js +3 -1
- package/dist/hooks/use-is-touch-device.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -1
- package/dist/patterns/element-anatomy/item-anatomy.d.ts +2 -2
- package/dist/patterns/element-anatomy/item-anatomy.d.ts.map +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 +41 -18
- package/llms-full.txt +11 -1
- package/llms.txt +3 -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 +10 -30
- package/src/components/Avatar/avatar.stories.tsx +1 -1
- package/src/components/Avatar/avatar.tsx +30 -16
- package/src/components/Badge/badge.anatomy.stories.tsx +2 -2
- package/src/components/Badge/badge.spec.md +5 -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 +5 -3
- package/src/components/Breadcrumb/breadcrumb.tsx +10 -4
- package/src/components/BulkActionBar/bulk-action-bar.anatomy.stories.tsx +3 -2
- package/src/components/BulkActionBar/bulk-action-bar.spec.md +15 -14
- package/src/components/BulkActionBar/bulk-action-bar.tsx +10 -4
- package/src/components/Button/button.anatomy.stories.tsx +6 -6
- package/src/components/Button/button.spec.md +3 -2
- package/src/components/Button/button.tsx +67 -27
- package/src/components/Button/index.ts +1 -0
- package/src/components/Calendar/calendar.anatomy.stories.tsx +5 -3
- package/src/components/Calendar/calendar.principles.stories.tsx +3 -1
- package/src/components/Calendar/calendar.spec.md +9 -2
- package/src/components/Calendar/calendar.stories.tsx +3 -1
- package/src/components/Calendar/calendar.tsx +53 -19
- package/src/components/Carousel/carousel.principles.stories.tsx +1 -1
- package/src/components/Carousel/carousel.tsx +4 -1
- 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 +41 -7
- package/src/components/Checkbox/index.ts +1 -0
- package/src/components/Chip/chip.anatomy.stories.tsx +9 -9
- package/src/components/Chip/chip.principles.stories.tsx +3 -3
- package/src/components/Chip/chip.spec.md +7 -4
- package/src/components/Chip/chip.tsx +60 -15
- 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 +23 -13
- 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 +30 -17
- 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 +3 -2
- 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 +72 -115
- package/src/components/DataTable/data-table.stories.tsx +51 -3
- package/src/components/DataTable/data-table.tsx +348 -63
- package/src/components/DataTable/filter-operators.spec.md +4 -4
- package/src/components/DataTable/filter-tree.ts +192 -42
- package/src/components/DateGrid/date-grid.anatomy.stories.tsx +1 -1
- package/src/components/DateGrid/date-grid.spec.md +11 -8
- package/src/components/DateGrid/date-grid.tsx +63 -8
- 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 +52 -15
- package/src/components/DescriptionList/description-list.spec.md +1 -1
- package/src/components/DescriptionList/description-list.tsx +3 -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 +8 -5
- package/src/components/DropdownMenu/dropdown-menu.tsx +33 -7
- package/src/components/Empty/empty.anatomy.stories.tsx +1 -1
- package/src/components/Empty/empty.spec.md +2 -2
- package/src/components/Empty/empty.stories.tsx +4 -15
- package/src/components/Empty/empty.tsx +4 -1
- package/src/components/Field/field-controls.spec.md +2 -1
- 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/field.tsx +2 -1
- package/src/components/Field/form-validation.spec.md +6 -3
- package/src/components/Field/use-form-validation.ts +49 -21
- 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 +4 -1
- package/src/components/FileUpload/file-upload.tsx +36 -10
- package/src/components/FileViewer/can-render-image.ts +15 -0
- 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 +47 -21
- package/src/components/FileViewer/image-renderer.tsx +3 -8
- package/src/components/HoverCard/hover-card.spec.md +1 -1
- package/src/components/HoverCard/hover-card.tsx +3 -1
- 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 +11 -9
- package/src/components/LinkInput/link-input.anatomy.stories.tsx +2 -0
- package/src/components/LinkInput/link-input.tsx +49 -34
- package/src/components/Menu/menu-item.anatomy.stories.tsx +1 -1
- package/src/components/Menu/menu-item.spec.md +4 -3
- package/src/components/Menu/menu-item.tsx +15 -4
- 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 +25 -9
- 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/Pagination/index.ts +5 -0
- package/src/components/Pagination/pagination.anatomy.stories.tsx +143 -0
- package/src/components/Pagination/pagination.principles.stories.tsx +81 -0
- package/src/components/Pagination/pagination.spec.md +150 -0
- package/src/components/Pagination/pagination.stories.tsx +98 -0
- package/src/components/Pagination/pagination.tsx +263 -0
- 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 +65 -55
- package/src/components/PeoplePicker/person-display.tsx +10 -4
- package/src/components/Popover/popover.anatomy.stories.tsx +2 -2
- package/src/components/Popover/popover.spec.md +13 -14
- package/src/components/Popover/popover.tsx +88 -39
- 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 +29 -8
- 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 +37 -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 +3 -3
- package/src/components/SegmentedControl/segmented-control.spec.md +7 -3
- package/src/components/SegmentedControl/segmented-control.tsx +29 -11
- package/src/components/Select/select.anatomy.stories.tsx +2 -2
- package/src/components/Select/select.spec.md +3 -2
- package/src/components/Select/select.tsx +62 -19
- 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 +12 -8
- package/src/components/SelectMenu/select-menu.tsx +176 -20
- 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 +2 -1
- package/src/components/Sheet/sheet.tsx +4 -2
- 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 +41 -13
- 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.anatomy.stories.tsx +4 -4
- package/src/components/Slider/slider.spec.md +19 -85
- package/src/components/Slider/slider.tsx +13 -5
- package/src/components/Steps/steps.anatomy.stories.tsx +1 -1
- package/src/components/Steps/steps.spec.md +6 -4
- package/src/components/Steps/steps.tsx +11 -3
- package/src/components/Switch/switch.spec.md +4 -3
- package/src/components/Switch/switch.tsx +59 -10
- package/src/components/Tabs/tabs.anatomy.stories.tsx +7 -6
- package/src/components/Tabs/tabs.principles.stories.tsx +5 -3
- package/src/components/Tabs/tabs.spec.md +15 -48
- package/src/components/Tabs/tabs.tsx +70 -34
- package/src/components/Tag/tag.principles.stories.tsx +2 -2
- package/src/components/Tag/tag.spec.md +11 -43
- package/src/components/Tag/tag.tsx +30 -4
- package/src/components/Textarea/textarea.principles.stories.tsx +2 -1
- package/src/components/Textarea/textarea.spec.md +2 -1
- package/src/components/Textarea/textarea.tsx +20 -10
- 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 +35 -16
- 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 +13 -7
- 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 +2 -2
- package/src/components/TreeView/tree-view.tsx +27 -5
- package/src/hooks/use-controllable.ts +7 -2
- package/src/hooks/use-is-touch-device.ts +8 -1
- package/src/index.ts +2 -1
- package/src/patterns/element-anatomy/element-anatomy.spec.md +3 -3
- package/src/patterns/element-anatomy/inline-action.spec.md +1 -1
- package/src/patterns/element-anatomy/item-anatomy.spec.md +5 -14
- package/src/patterns/element-anatomy/item-anatomy.stories.tsx +1 -0
- package/src/patterns/element-anatomy/item-anatomy.tsx +5 -6
- 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/horizontal-overflow/horizontal-overflow.stories.tsx +137 -0
- package/src/patterns/overlay-surface/overlay-surface.spec.md +8 -7
- package/src/patterns/overlay-surface/overlay-surface.stories.tsx +116 -0
- 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 +11 -13
- package/src/tokens/color/color.stories.tsx +1 -1
- package/src/tokens/color/primitives.css +7 -5
- package/src/tokens/color/semantic.css +8 -2
- 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 +2 -1
- package/src/tokens/layoutSpace/layoutSpace.stories.tsx +3 -3
- package/src/tokens/motion/motion.spec.md +1 -1
- package/src/tokens/motion/motion.stories.tsx +142 -0
- package/src/tokens/opacity/opacity.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/uiSize/uiSize.stories.tsx +103 -0
- 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
|
@@ -222,7 +222,8 @@ export const Overview: Story = {
|
|
|
222
222
|
<tbody>
|
|
223
223
|
{([
|
|
224
224
|
['files', 'FileInfo[]', '—', '檔案清單(shell 依 index 渲染當前)'],
|
|
225
|
-
['open', 'boolean', '—', 'Modal 開關(controlled)'],
|
|
225
|
+
['open', 'boolean', '—', 'Modal 開關(controlled;與 defaultOpen 二擇一)'],
|
|
226
|
+
['defaultOpen', 'boolean', 'false', 'Modal 開關預設(uncontrolled;對齊 Dialog/Sheet/Popover dual-mode canonical)'],
|
|
226
227
|
['onOpenChange', '(open: boolean) => void', '—', 'Modal 開關 callback'],
|
|
227
228
|
['initialIndex', 'number', '0', '初始 active 索引(uncontrolled)'],
|
|
228
229
|
['index', 'number', '—', 'Active 索引(controlled);不傳則 shell 自理'],
|
|
@@ -232,6 +233,7 @@ export const Overview: Story = {
|
|
|
232
233
|
['showFilmstrip', 'boolean', 'false', 'true && files.length>1 → 顯示 filmstrip'],
|
|
233
234
|
['allowDownload', 'boolean', 'true', 'true → toolbar 顯示 download 按鈕'],
|
|
234
235
|
['onDownload', '(file: FileInfo) => void', '—', '自訂 download(跨域檔案必傳)'],
|
|
236
|
+
['labels', 'FileViewerLabels', 'DEFAULT_LABELS', 'i18n 文案 override(partial,與 DS 預設 merge;15 keys 覆蓋 toolbar / InfoPanel / filmstrip 的 aria-label 與 placeholder)'],
|
|
235
237
|
] as const).map(([p, t, d, desc]) => (
|
|
236
238
|
<tr key={p}>
|
|
237
239
|
<Td mono>{p}</Td>
|
|
@@ -410,7 +412,7 @@ export const Inspector: Story = {
|
|
|
410
412
|
</tr>
|
|
411
413
|
<tr>
|
|
412
414
|
<Td>InfoPanel 內 padding</Td>
|
|
413
|
-
<Td mono>px-[var(--layout-space-loose)]
|
|
415
|
+
<Td mono>px-[var(--layout-space-loose)] pt-[var(--layout-space-tight)] pb-[var(--layout-space-loose)]</Td>
|
|
414
416
|
</tr>
|
|
415
417
|
<tr>
|
|
416
418
|
<Td>Filmstrip 高</Td>
|
|
@@ -703,7 +705,7 @@ export const SizeMatrix: Story = {
|
|
|
703
705
|
<tr>
|
|
704
706
|
<Td>Body 水平 padding</Td>
|
|
705
707
|
<Td mono>px-[var(--layout-space-loose)]</Td>
|
|
706
|
-
<Td>
|
|
708
|
+
<Td>16 / 24 px(隨 density)</Td>
|
|
707
709
|
</tr>
|
|
708
710
|
<tr>
|
|
709
711
|
<Td>Body 垂直 padding</Td>
|
|
@@ -713,7 +715,7 @@ export const SizeMatrix: Story = {
|
|
|
713
715
|
<tr>
|
|
714
716
|
<Td>Section 間 gap</Td>
|
|
715
717
|
<Td mono>gap-[var(--layout-space-loose)]</Td>
|
|
716
|
-
<Td>說明 ↔ 檔案資訊
|
|
718
|
+
<Td>說明 ↔ 檔案資訊 16 / 24 px(隨 density;跨範疇 parallel = loose)</Td>
|
|
717
719
|
</tr>
|
|
718
720
|
<tr>
|
|
719
721
|
<Td>檔案資訊 metadata</Td>
|
|
@@ -210,7 +210,7 @@ Shell 看到 `pageNumber` capability 時自動在 toolbar 顯示 page navigator(
|
|
|
210
210
|
- **100% 重設**(user 按 `0` 鍵;雙擊是 fit↔100% 條件式 toggle,見下「雙擊」bullet)— `centerZoomedOut: true` 讓 ≤100% 時自動 center,避免內容停在容器角落
|
|
211
211
|
- 結果:user 看哪,zoom 完 **看哪**,視覺焦點不漂移 — 世界級 media viewer 共識
|
|
212
212
|
- Min scale 10%,max scale 400%
|
|
213
|
-
- **雙擊 = toggle fit-page ↔ 100%**(對齊 Apple Photos / Preview.app / Imgur / PhotoSwipe canonical):當前 zoom 在 fit-page ±5pt 內 → 跳 100% natural;否則 → 回 fit-page。`0` 鍵才是固定重設 100%(`file-viewer.tsx:
|
|
213
|
+
- **雙擊 = toggle fit-page ↔ 100%**(對齊 Apple Photos / Preview.app / Imgur / PhotoSwipe canonical):當前 zoom 在 fit-page ±5pt 內 → 跳 100% natural;否則 → 回 fit-page。`0` 鍵才是固定重設 100%(`file-viewer.tsx:863`),兩者不同(`image-renderer.tsx:144`)
|
|
214
214
|
- 切換檔案時 shell **不重設 zoom**——把「下一張該怎麼初始化 zoom」的決定權交給 renderer:image-renderer 自己 watch `file.url` 變化 → reset loaded → onLoad → 重 fit-page(含 cache 命中時 `<img complete>` 主動觸發,避免 onLoad 不 fire 卡在上一張的 zoom)。原本 shell `setZoom(100)` 在 cache 命中時會卡 100% 不 fit,已移除
|
|
215
215
|
|
|
216
216
|
---
|
|
@@ -362,7 +362,7 @@ Radix DialogPrimitive 自動處理:
|
|
|
362
362
|
- `../DropdownMenu/dropdown-menu.spec.md` — ZoomInput preset / fit 選單消費(取代先前 Popover)
|
|
363
363
|
- `../Empty/empty.spec.md` — FallbackRenderer 佈局
|
|
364
364
|
- `../AspectRatio/aspect-ratio.spec.md` — Filmstrip thumb 固定比例
|
|
365
|
-
- `../ScrollArea/scroll-area.spec.md` — InfoPanel body 高度受限時 scroll(`file-viewer.tsx:
|
|
365
|
+
- `../ScrollArea/scroll-area.spec.md` — InfoPanel body 高度受限時 scroll(`file-viewer.tsx:493`);filmstrip 不用 ScrollArea(刻意走 fade-mask,屬 horizontal-overflow pattern)
|
|
366
366
|
- `../../patterns/horizontal-overflow/horizontal-overflow.spec.md` — Filmstrip 水平捲動 + fade mask
|
|
367
367
|
- `../../patterns/action-bar/action-bar.spec.md` — Toolbar 按鈕順序 canonical
|
|
368
368
|
- `../FileItem/file-item.spec.md` — 上傳清單列元件(與 FileViewer 不同用途,FileItem 是 list item,FileViewer 是 fullscreen preview)
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
FileText,
|
|
16
16
|
} from 'lucide-react'
|
|
17
17
|
import { cn } from '@/lib/utils'
|
|
18
|
+
import { OVERLAY_SIDE_OFFSET } from '@/design-system/tokens/elevation/overlay-geometry'
|
|
18
19
|
import { Button } from '@/design-system/components/Button/button'
|
|
19
20
|
import { Separator } from '@/design-system/components/Separator/separator'
|
|
20
21
|
import { Input } from '@/design-system/components/Input/input'
|
|
@@ -33,14 +34,14 @@ import {
|
|
|
33
34
|
DropdownMenuItem,
|
|
34
35
|
DropdownMenuSeparator,
|
|
35
36
|
} from '@/design-system/components/DropdownMenu/dropdown-menu'
|
|
36
|
-
import {
|
|
37
|
+
import { ARROW_BUTTON_WIDTH,
|
|
37
38
|
useScrollEdges,
|
|
38
39
|
useScrollByPage,
|
|
39
40
|
buildFadeMask,
|
|
40
41
|
OverflowScrollArrow,
|
|
41
42
|
} from '@/design-system/patterns/horizontal-overflow/horizontal-overflow'
|
|
42
43
|
import { useControllable } from '@/design-system/hooks/use-controllable'
|
|
43
|
-
import {
|
|
44
|
+
import { canRenderImage } from './can-render-image'
|
|
44
45
|
import type {
|
|
45
46
|
FileInfo,
|
|
46
47
|
FileRenderer,
|
|
@@ -107,10 +108,18 @@ const fallbackRenderer: FileRenderer = {
|
|
|
107
108
|
component: FallbackRenderer,
|
|
108
109
|
}
|
|
109
110
|
|
|
111
|
+
// 2026-07-06 D3 bundle fix:ImageRenderer 改 React.lazy 動態 import——FileViewer 是
|
|
112
|
+
// rare-use modal overlay,react-zoom-pan-pinch(~12KB gz)不該在 viewer 從未打開時就進
|
|
113
|
+
// consumer 首屏 bundle。canRenderImage(registry 判斷 + Filmstrip 分流需同步呼叫)已拆至
|
|
114
|
+
// ./can-render-image 保持靜態 import。
|
|
115
|
+
const LazyImageRenderer = React.lazy(() =>
|
|
116
|
+
import('./image-renderer').then((m) => ({ default: m.ImageRenderer })),
|
|
117
|
+
)
|
|
118
|
+
|
|
110
119
|
const imageRenderer: FileRenderer = {
|
|
111
120
|
id: 'image',
|
|
112
121
|
canRender: canRenderImage,
|
|
113
|
-
component:
|
|
122
|
+
component: LazyImageRenderer,
|
|
114
123
|
}
|
|
115
124
|
|
|
116
125
|
// Registry 是 module-singleton:新 renderer 透過 registerFileRenderer 加入。
|
|
@@ -256,7 +265,7 @@ const ZoomInput: React.FC<ZoomInputProps> = ({ value, onChange, onFit, labels })
|
|
|
256
265
|
Tailwind 條件 class + CSS variable 都要一起帶) */}
|
|
257
266
|
<DropdownMenuContent
|
|
258
267
|
align="end"
|
|
259
|
-
sideOffset={
|
|
268
|
+
sideOffset={OVERLAY_SIDE_OFFSET} // 2026-07-04:M17 — elevation.spec「primitive default 必 import 該 const」
|
|
260
269
|
// minWidth 對齊 trigger(Input autoWidth),menu 寬度 fit-content 更貼近觸發點視覺中心
|
|
261
270
|
className="min-w-[9rem] w-auto bg-surface-raised text-foreground border-divider"
|
|
262
271
|
data-theme="dark"
|
|
@@ -438,13 +447,16 @@ function formatBytes(bytes: number | undefined): string | undefined {
|
|
|
438
447
|
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`
|
|
439
448
|
}
|
|
440
449
|
|
|
441
|
-
|
|
450
|
+
// React.memo(2026-07-06 D3):同 Filmstrip——wheel-zoom 每 tick 全 chrome re-render,InfoPanel
|
|
451
|
+
// 開啟時(Field/Textarea/DescriptionList subtree)白付。onClose 走 shell handleInfoClose
|
|
452
|
+
// useCallback;file(files[activeIndex] identity 穩定)/readOnly/onDescriptionChange/labels 本已穩定。
|
|
453
|
+
const InfoPanel = React.memo(function InfoPanel({
|
|
442
454
|
file,
|
|
443
455
|
readOnly,
|
|
444
456
|
onDescriptionChange,
|
|
445
457
|
onClose,
|
|
446
458
|
labels,
|
|
447
|
-
})
|
|
459
|
+
}: InfoPanelProps) {
|
|
448
460
|
const [draft, setDraft] = React.useState(file.description ?? '')
|
|
449
461
|
|
|
450
462
|
React.useEffect(() => {
|
|
@@ -537,7 +549,7 @@ const InfoPanel: React.FC<InfoPanelProps> = ({
|
|
|
537
549
|
</ScrollArea>
|
|
538
550
|
</aside>
|
|
539
551
|
)
|
|
540
|
-
}
|
|
552
|
+
})
|
|
541
553
|
|
|
542
554
|
// ─── Filmstrip ────────────────────────────────────────────────────────────────
|
|
543
555
|
|
|
@@ -551,10 +563,13 @@ interface FilmstripProps {
|
|
|
551
563
|
// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding
|
|
552
564
|
const THUMB_SIZE = 64 // px, 固定
|
|
553
565
|
|
|
554
|
-
|
|
566
|
+
// React.memo(2026-07-06 D3):wheel-zoom 每 tick(trackpad 60Hz+)setZoom → shell 全 chrome
|
|
567
|
+
// re-render,Filmstrip(N thumbs × cn + buildFadeMask)是最貴的無關 subtree。props 已全穩定
|
|
568
|
+
// (files=prop / activeIndex=primitive / onSelect=setIndex useCallback / labels=useMemo)→ memo 真命中。
|
|
569
|
+
const Filmstrip = React.memo(function Filmstrip({ files, activeIndex, onSelect, labels }: FilmstripProps) {
|
|
555
570
|
const { scrollRef, atStart, atEnd, canScroll } = useScrollEdges<HTMLDivElement>()
|
|
556
571
|
const scrollByPage = useScrollByPage(scrollRef)
|
|
557
|
-
const maskImage = buildFadeMask({ canScroll, atStart, atEnd, reserveArrowWidth:
|
|
572
|
+
const maskImage = buildFadeMask({ canScroll, atStart, atEnd, reserveArrowWidth: ARROW_BUTTON_WIDTH }) // 2026-07-04:消 SSOT 硬寫(spec 禁止重新定義 ARROW_BUTTON_WIDTH)
|
|
558
573
|
|
|
559
574
|
// 切換當前檔案時,自動 scroll 讓 active thumb 可見
|
|
560
575
|
React.useEffect(() => {
|
|
@@ -649,7 +664,7 @@ const Filmstrip: React.FC<FilmstripProps> = ({ files, activeIndex, onSelect, lab
|
|
|
649
664
|
)}
|
|
650
665
|
</div>
|
|
651
666
|
)
|
|
652
|
-
}
|
|
667
|
+
})
|
|
653
668
|
|
|
654
669
|
// ─── FileViewer (shell) ───────────────────────────────────────────────────────
|
|
655
670
|
|
|
@@ -795,6 +810,10 @@ const FileViewer = React.forwardRef<HTMLDivElement, FileViewerProps>(function Fi
|
|
|
795
810
|
|
|
796
811
|
// Info panel open state(shell own)
|
|
797
812
|
const [infoOpen, setInfoOpen] = React.useState(false)
|
|
813
|
+
// 2026-07-06 D3 perf:抽 useCallback 穩定 identity——InfoPanel 上了 React.memo(見上),
|
|
814
|
+
// inline arrow 每 render 新 identity 會打穿 memo shallow-compare。
|
|
815
|
+
const handleInfoToggle = React.useCallback(() => setInfoOpen((o) => !o), [])
|
|
816
|
+
const handleInfoClose = React.useCallback(() => setInfoOpen(false), [])
|
|
798
817
|
|
|
799
818
|
// Zoom state(shell own,renderer 消費 + 回報)
|
|
800
819
|
const [zoom, setZoom] = React.useState(100)
|
|
@@ -960,7 +979,7 @@ const FileViewer = React.forwardRef<HTMLDivElement, FileViewerProps>(function Fi
|
|
|
960
979
|
onZoomChange={setZoom}
|
|
961
980
|
onFit={handleFit}
|
|
962
981
|
infoOpen={infoOpen}
|
|
963
|
-
onInfoToggle={
|
|
982
|
+
onInfoToggle={handleInfoToggle}
|
|
964
983
|
onDownload={handleDownload}
|
|
965
984
|
allowDownload={allowDownload}
|
|
966
985
|
onClose={() => setOpen(false)}
|
|
@@ -1017,13 +1036,18 @@ const FileViewer = React.forwardRef<HTMLDivElement, FileViewerProps>(function Fi
|
|
|
1017
1036
|
</div>
|
|
1018
1037
|
)}
|
|
1019
1038
|
<div className="w-full h-full">
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1039
|
+
{/* Suspense 兜 React.lazy renderer(ImageRenderer)chunk 載入。fallback=null
|
|
1040
|
+
= 既有 loading 行為——spec「Loading」段:shell 不提供全頁 loading state,
|
|
1041
|
+
loading 由 renderer 自理;chunk 載入期間的空 viewport 與 <img> 載入期間視覺一致。 */}
|
|
1042
|
+
<React.Suspense fallback={null}>
|
|
1043
|
+
<Renderer.component
|
|
1044
|
+
file={file}
|
|
1045
|
+
zoom={zoom}
|
|
1046
|
+
onZoomChange={setZoom}
|
|
1047
|
+
fitRequest={fitRequest}
|
|
1048
|
+
onCapabilitiesChange={setCapabilities}
|
|
1049
|
+
/>
|
|
1050
|
+
</React.Suspense>
|
|
1027
1051
|
</div>
|
|
1028
1052
|
{showArrows && activeIndex < files.length - 1 && (
|
|
1029
1053
|
<div
|
|
@@ -1050,7 +1074,7 @@ const FileViewer = React.forwardRef<HTMLDivElement, FileViewerProps>(function Fi
|
|
|
1050
1074
|
file={file}
|
|
1051
1075
|
readOnly={readOnly}
|
|
1052
1076
|
onDescriptionChange={onDescriptionChange}
|
|
1053
|
-
onClose={
|
|
1077
|
+
onClose={handleInfoClose}
|
|
1054
1078
|
labels={labels}
|
|
1055
1079
|
/>
|
|
1056
1080
|
)}
|
|
@@ -1083,9 +1107,11 @@ export const fileViewerMeta = {
|
|
|
1083
1107
|
sizes: {
|
|
1084
1108
|
|
|
1085
1109
|
},
|
|
1086
|
-
|
|
1110
|
+
// 'selected' = 縮圖列當前頁持續選中(bg-neutral-selected);'active' 移除 — 全檔無按壓視覺
|
|
1111
|
+
// (工具列按壓屬內嵌 Button meta;2026-07-07 詞彙統一 DS-wide 交叉掃補修)。
|
|
1112
|
+
states: ['default', 'hover', 'selected', 'focus-visible', 'disabled'],
|
|
1087
1113
|
tokens: {
|
|
1088
|
-
bg: ['bg-muted', 'bg-overlay', 'bg-surface-raised'],
|
|
1114
|
+
bg: ['bg-muted', 'bg-neutral-selected', 'bg-overlay', 'bg-surface-raised'],
|
|
1089
1115
|
fg: ['text-fg-muted', 'text-foreground'],
|
|
1090
1116
|
ring: ['ring-primary', 'ring-ring'],
|
|
1091
1117
|
},
|
|
@@ -246,11 +246,6 @@ export const ImageRenderer: React.FC<FileRendererProps> = ({
|
|
|
246
246
|
}
|
|
247
247
|
ImageRenderer.displayName = 'ImageRenderer'
|
|
248
248
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
export function canRenderImage(file: { mimeType: string; name: string }): boolean {
|
|
253
|
-
if (file.mimeType.startsWith('image/')) return true
|
|
254
|
-
const ext = file.name.split('.').pop()?.toLowerCase()
|
|
255
|
-
return ext ? IMAGE_EXTS.has(ext) : false
|
|
256
|
-
}
|
|
249
|
+
// canRenderImage 已拆至 ./can-render-image(2026-07-06 D3 bundle fix)——
|
|
250
|
+
// shell 需同步呼叫它,而本檔頂層 import react-zoom-pan-pinch;留在本檔會把整個 lib
|
|
251
|
+
// 拉進 shell 的靜態 import graph,React.lazy 切分就失效。
|
|
@@ -141,7 +141,7 @@ HoverCard 是**純行為 primitive**(只提供 hover 觸發邏輯 + 定位 + 動
|
|
|
141
141
|
|
|
142
142
|
## A11y 預設
|
|
143
143
|
|
|
144
|
-
**ARIA / Pattern**:Radix `hover-card` 刻意**不**設 `role` / `aria-*`,定位為 sighted-mouse-user 增強,content 不暴露給輔助技術(grep Radix source 0 命中 role/aria-*)。Radix 只提供:hover / focus 開、Esc 關 + `data-state`。Trigger 的可聚焦性需 consumer 自補(如 Avatar `hoverCard` 在 `avatar.tsx` 手動加 `tabIndex=0` + `role=
|
|
144
|
+
**ARIA / Pattern**:Radix `hover-card` 刻意**不**設 `role` / `aria-*`,定位為 sighted-mouse-user 增強,content 不暴露給輔助技術(grep Radix source 0 命中 role/aria-*)。Radix 只提供:hover / focus 開、Esc 關 + `data-state`。Trigger 的可聚焦性需 consumer 自補(如 Avatar `hoverCard` 在 `avatar.tsx` 手動加 `tabIndex=0` + `aria-label`;**不掛 role/aria-haspopup**——2026-07-06 拆除,無 activation 行為的 role 宣告 = 對 AT 空承諾,對齊 Radix 官方零 role/aria 定位)。詳 [Radix Accessibility docs](https://www.radix-ui.com/primitives/docs/components/hover-card#accessibility)。
|
|
145
145
|
|
|
146
146
|
**Keyboard 行為**:
|
|
147
147
|
|
|
@@ -54,7 +54,9 @@ const HoverCardContent = React.forwardRef<
|
|
|
54
54
|
// 2026-05-04 viewport-aware max-h SSOT(對齊 Popover):header/footer 永遠 in-viewport,body 壓縮 scroll
|
|
55
55
|
// 2026-05-05 audit dim 35 fix:加 `min-h-0` 完成 M25 chain invariant
|
|
56
56
|
"max-h-[var(--radix-hover-card-content-available-height,100vh)] flex flex-col overflow-hidden min-h-0",
|
|
57
|
-
|
|
57
|
+
// 2026-07-05 補 motion-reduce 豁免:對齊 overlay 家族 canonical(popover.tsx / dialog.tsx /
|
|
58
|
+
// dropdown-menu.tsx / tooltip.tsx 同位置寫法)— prefers-reduced-motion 下停用進出場動畫。
|
|
59
|
+
"data-[state=open]:animate-in data-[state=closed]:animate-out motion-reduce:animate-none",
|
|
58
60
|
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
|
59
61
|
"data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
|
|
60
62
|
"data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2",
|
|
@@ -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,
|
|
@@ -215,17 +211,23 @@ Input.displayName = 'Input'
|
|
|
215
211
|
// 對齊 Carbon read-only / PatternFly inline-edit hidden-input / Cloudscape display-mode 統一 mode 模型。
|
|
216
212
|
|
|
217
213
|
// Story auto-compile metadata — Phase 1 mechanical migration(2026-04-24)
|
|
218
|
-
// Phase 2 fill
|
|
214
|
+
// 2026-07-05 Phase 2 fill(deep-audit A.1b):variants 按 input.tsx variant jsdoc、sizes 按
|
|
215
|
+
// fieldWrapperStyles cva 真值(h-field-sm/md/lg = 28/32/36,uiSize.css)+ ICON_SIZE SSOT
|
|
216
|
+
// (sm/md 16、lg 20)填。`naked` 為 @internal cell-as-input substrate → 不列入 public variants。
|
|
219
217
|
export const inputMeta = {
|
|
220
218
|
component: 'Input',
|
|
221
219
|
family: 4,
|
|
222
220
|
variants: {
|
|
223
|
-
|
|
221
|
+
default: { purpose: 'Field wrapper 完整 chrome(bg-surface + border + hover/focus 回饋)— 表單 / Field 內嵌' },
|
|
222
|
+
bare: { purpose: '透明 chrome,hover / focus 才現 border — Toolbar inline editing(zoom input / config toolbar)' },
|
|
224
223
|
},
|
|
225
224
|
sizes: {
|
|
226
|
-
|
|
225
|
+
sm: { fieldHeight: 28, iconSize: 16, typography: 'body' },
|
|
226
|
+
md: { fieldHeight: 32, iconSize: 16, typography: 'body' },
|
|
227
|
+
lg: { fieldHeight: 36, iconSize: 20, typography: 'body-lg' },
|
|
227
228
|
},
|
|
228
|
-
|
|
229
|
+
// 'active' 移除 — Field chrome 無按壓態(對齊 Textarea / LinkInput meta 既有寫法)(2026-07-07 詞彙統一 DS-wide 按壓訊號盤點:檔內 0 active: utility / 0 *-active token)。
|
|
230
|
+
states: ['default', 'hover', 'focus-visible', 'disabled'],
|
|
229
231
|
tokens: {
|
|
230
232
|
bg: ['bg-surface'],
|
|
231
233
|
fg: ['text-fg-disabled', 'text-fg-muted'],
|
|
@@ -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 ──────────────────────────────────────────────────────────
|
|
@@ -48,7 +48,10 @@ function renderLinkAnchor(value: string, label?: string) {
|
|
|
48
48
|
// ── Component ───────────────────────────────────────────────────────────────
|
|
49
49
|
|
|
50
50
|
export interface LinkInputProps
|
|
51
|
-
|
|
51
|
+
// `defaultValue` 一併 Omit(2026-07-05 deep-audit A.1b):spec.md「controlled-only,不支援
|
|
52
|
+
// defaultValue」宣稱的型別面機械封鎖 — 原本仍在型別 surface 且經 {...props} spread 到已
|
|
53
|
+
// controlled 的 input(React dev warning)。
|
|
54
|
+
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size' | 'value' | 'defaultValue' | 'onChange'>,
|
|
52
55
|
Omit<VariantProps<typeof fieldWrapperStyles>, 'mode' | 'variant'> {
|
|
53
56
|
mode?: FieldMode
|
|
54
57
|
/**
|
|
@@ -110,6 +113,37 @@ const LinkInput = React.forwardRef<HTMLInputElement, LinkInputProps>(
|
|
|
110
113
|
// chrome resolution:per-prop > context > 'default'
|
|
111
114
|
const resolvedVariant: FieldVariant = useResolvedFieldVariant(variantProp)
|
|
112
115
|
|
|
116
|
+
// 2026-07-05 D4:invalid 經 useResolvedFieldInvalid 統一接 fieldCtx.invalid(field-controls.spec.md
|
|
117
|
+
// resolver 契約「error/invalid = prop OR fieldCtx.invalid」)— 原本紅框只吃 errorProp||localError、
|
|
118
|
+
// aria-invalid 卻 inline 加 fieldCtx?.invalid → <Field invalid> 下 AT 聽到 invalid 視覺卻正常框
|
|
119
|
+
// (聚焦還變藍)。紅框與 aria 自此同源。宣告於 display early return 之前(Rules-of-Hooks)。
|
|
120
|
+
const fieldInvalid = useResolvedFieldInvalid(errorProp)
|
|
121
|
+
|
|
122
|
+
// ── Local state / refs ──────────────────────────────────────────────────
|
|
123
|
+
// 2026-07-04 Rules-of-Hooks 修:hooks 必在 display early return 之前宣告,
|
|
124
|
+
// 否則 mode 於 render 間切換 display↔edit 會改變 hook 呼叫數 → React #310 crash
|
|
125
|
+
// (同 beta.76 Combobox 同款修法;僅搬位置,hook 邏輯不變)。
|
|
126
|
+
const [editing, setEditing] = React.useState(false)
|
|
127
|
+
const [localValue, setLocalValue] = React.useState(value ?? '')
|
|
128
|
+
const [localError, setLocalError] = React.useState(false)
|
|
129
|
+
const inputRef = React.useRef<HTMLInputElement | null>(null)
|
|
130
|
+
|
|
131
|
+
// Sync external value → local
|
|
132
|
+
React.useEffect(() => {
|
|
133
|
+
if (!editing) setLocalValue(value ?? '')
|
|
134
|
+
}, [value, editing])
|
|
135
|
+
|
|
136
|
+
// Merge refs
|
|
137
|
+
const setRef = React.useCallback((el: HTMLInputElement | null) => {
|
|
138
|
+
inputRef.current = el
|
|
139
|
+
if (typeof ref === 'function') ref(el)
|
|
140
|
+
else if (ref) (ref as React.MutableRefObject<HTMLInputElement | null>).current = el
|
|
141
|
+
}, [ref])
|
|
142
|
+
|
|
143
|
+
// 2026-05-16 audit codex Round 6:capture rAF + cancel on unmount(defensive hygiene)
|
|
144
|
+
const focusRafIdRef = React.useRef<number>(0)
|
|
145
|
+
React.useEffect(() => () => { if (focusRafIdRef.current) cancelAnimationFrame(focusRafIdRef.current) }, [])
|
|
146
|
+
|
|
113
147
|
// ── mode='display' ─────────────────────────────────────────────────────
|
|
114
148
|
// 純展示:無 input chrome / 無 hover affordance / 無 Pencil edit 入口。
|
|
115
149
|
// 取代既有 LinkInputDisplay sub-component(2026-05-05 Phase B3 retire)。
|
|
@@ -140,30 +174,9 @@ const LinkInput = React.forwardRef<HTMLInputElement, LinkInputProps>(
|
|
|
140
174
|
)
|
|
141
175
|
}
|
|
142
176
|
|
|
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
177
|
const hasValidValue = !!value && isValidUrl(value)
|
|
161
178
|
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) }, [])
|
|
179
|
+
const error = fieldInvalid || localError
|
|
167
180
|
|
|
168
181
|
const handleEdit = () => {
|
|
169
182
|
setEditing(true)
|
|
@@ -233,8 +246,11 @@ const LinkInput = React.forwardRef<HTMLInputElement, LinkInputProps>(
|
|
|
233
246
|
if (showLink) {
|
|
234
247
|
return (
|
|
235
248
|
<div
|
|
236
|
-
|
|
249
|
+
// 2026-07-05 D4:link 顯示分支同樣接 error(errorProp / fieldCtx.invalid)紅框,
|
|
250
|
+
// 否則 <Field invalid> 下 link state 完全無 error 呈現
|
|
251
|
+
className={cn(fieldWrapperStyles({ mode: 'edit', variant: resolvedVariant, size, error }), className)}
|
|
237
252
|
data-field-mode="edit"
|
|
253
|
+
data-error={error ? '' : undefined}
|
|
238
254
|
>
|
|
239
255
|
<span className="flex-1 min-w-0">
|
|
240
256
|
{value && renderLinkAnchor(value, label)}
|
|
@@ -251,11 +267,7 @@ const LinkInput = React.forwardRef<HTMLInputElement, LinkInputProps>(
|
|
|
251
267
|
return (
|
|
252
268
|
<div
|
|
253
269
|
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
|
-
],
|
|
270
|
+
fieldWrapperStyles({ mode: 'edit', variant: resolvedVariant, size, error }),
|
|
259
271
|
className,
|
|
260
272
|
)}
|
|
261
273
|
data-field-mode="edit"
|
|
@@ -270,10 +282,10 @@ const LinkInput = React.forwardRef<HTMLInputElement, LinkInputProps>(
|
|
|
270
282
|
onBlur={handleBlur}
|
|
271
283
|
onKeyDown={handleKeyDown}
|
|
272
284
|
placeholder={placeholder}
|
|
273
|
-
aria-invalid={
|
|
285
|
+
aria-invalid={error || undefined}
|
|
274
286
|
aria-required={fieldCtx?.required || undefined}
|
|
275
287
|
aria-describedby={ariaDescribedByProp ?? fieldCtx?.descriptionId}
|
|
276
|
-
aria-errormessage={ariaErrorMessageProp ?? (
|
|
288
|
+
aria-errormessage={ariaErrorMessageProp ?? (error ? fieldCtx?.errorId : undefined)}
|
|
277
289
|
className={bareInputStyles}
|
|
278
290
|
{...props}
|
|
279
291
|
/>
|
|
@@ -294,9 +306,12 @@ export const linkInputMeta = {
|
|
|
294
306
|
sizes: {
|
|
295
307
|
|
|
296
308
|
},
|
|
297
|
-
states
|
|
309
|
+
// states 對齊真實 state 集:text-input 家族無 'active'(按下)專屬視覺態(同 textareaMeta 修法);
|
|
310
|
+
// readonly / error 為 fieldWrapperStyles mode + errorProp/localError 實有狀態(2026-07-04 audit 對齊)。
|
|
311
|
+
states: ['default', 'hover', 'focus-visible', 'readonly', 'disabled', 'error'],
|
|
298
312
|
tokens: {
|
|
299
|
-
bg
|
|
313
|
+
// bg 經 fieldWrapperStyles 實渲:edit bg-surface / readonly bg-readonly / disabled bg-disabled(field-wrapper.tsx)。
|
|
314
|
+
bg: ['bg-surface', 'bg-readonly', 'bg-disabled'],
|
|
300
315
|
fg: ['text-fg-disabled', 'text-fg-muted', 'text-primary'],
|
|
301
316
|
ring: [],
|
|
302
317
|
},
|
|
@@ -774,7 +774,7 @@ export const Accessibility = {
|
|
|
774
774
|
render: () => (
|
|
775
775
|
<div className="max-w-3xl text-body text-fg-secondary">
|
|
776
776
|
<h3 className="text-h5 text-foreground mb-2">無障礙設計</h3>
|
|
777
|
-
<p className="whitespace-pre-line">{"MenuItem 只負責單行的視覺排版,本身不接管鍵盤與焦點。真正的鍵盤導覽(上下鍵切換、Enter 選取、Esc 關閉)與焦點管理由外層的選單元件(SelectMenu / DropdownMenu)負責——MenuItem 不重複實作這些行為。\n\n 語意 role
|
|
777
|
+
<p className="whitespace-pre-line">{"MenuItem 只負責單行的視覺排版,本身不接管鍵盤與焦點。真正的鍵盤導覽(上下鍵切換、Enter 選取、Esc 關閉)與焦點管理由外層的選單元件(SelectMenu / DropdownMenu)負責——MenuItem 不重複實作這些行為。\n\n 語意 role :直接使用時(如本元件 stories 的 listbox demo)MenuItem 預設渲染為 role=\"option\" 並帶 aria-selected / aria-disabled;巢狀在外層互動節點內時(SelectMenu / Combobox 的 cmdk option、DropdownMenu 的 Radix menuitem)外層會傳 role=\"presentation\",MenuItem 同步抑制 aria-selected / aria-disabled——ARIA 語意由外層節點單一持有,內層僅保留 data-* 樣式屬性。哪一項目前被聚焦與選取由外層選單決定。\n\n 焦點外觀 :聚焦時以 bg-neutral-hover 背景高亮標示被聚焦的選項(cva base 為 outline-none + focus-visible:bg-neutral-hover),對齊 menu/listbox option 的 active-highlight 慣例,而非畫 outline ring。\n\n 驗證 :Storybook a11y addon 面板應 0 critical violation;整個選單用鍵盤即可完整操作(無需滑鼠);文字對比 ≥ 4.5:1、介面元素對比 ≥ 3:1(WCAG AA)。"}</p>
|
|
778
778
|
</div>
|
|
779
779
|
),
|
|
780
780
|
}
|