@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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"input.js","sources":["../../../src/components/Input/input.tsx"],"sourcesContent":["// @benchmark-unverified-blanket: file-level retraction per M22 (d) — claims herein not individually URL-cited; treat as unverified visual/usage rumor unless retrofit per-claim. Hook escape preserved.\nimport * as React from 'react'\nimport { type VariantProps } from 'class-variance-authority'\nimport type { LucideIcon } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport type { FieldMode, FieldVariant } from '@/design-system/components/Field/field-types'\nimport { fieldWrapperStyles, bareInputStyles, EMPTY_DISPLAY } from '@/design-system/components/Field/field-wrapper'\nimport { useFieldContext, useResolvedFieldSize, useResolvedFieldDisabled, useResolvedFieldMode, useResolvedFieldVariant, useResolvedFieldInvalid } from '@/design-system/components/Field/field-context'\nimport { ItemInlineAction, ItemPrefix, type InlineActionConfig } from '@/design-system/patterns/element-anatomy/item-anatomy'\nimport { CircularProgress } from '@/design-system/components/CircularProgress/circular-progress'\nimport { ICON_SIZE } from '@/design-system/tokens/uiSize/icon-size'\n\n// ── Types ───────────────────────────────────────────────────────────────────\n\nexport interface InputProps\n extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'>,\n Omit<VariantProps<typeof fieldWrapperStyles>, 'mode' | 'variant'> {\n /** Field display mode */\n mode?: FieldMode\n /**\n * Visual chrome(正交於 mode);Phase B1(2026-05-05)從 `variant` 改名 `chrome`,對齊 FieldContext.variant 透傳。\n * 公開 variant 兩個:\n * - `'default'`(預設)— Field wrapper 完整 variant:bg-surface + 明顯 border + hover/focus 回饋。適用表單、Field 內嵌。\n * - `'bare'` — 透明 variant,hover / focus 才出現 border。適用 Toolbar inline editing(如 FileViewer zoom input / chart config toolbar / rich text toolbar number input)。保留 padding / typography / height,只拿掉背景和常態 border。\n *\n * @internal `'naked'` — 完全無 chrome / 無 border / 無 focus ring。單獨使用無視覺邊界,**不可直接 standalone 用**;僅供 DS 內部 cell-as-input 組合(host cell 自管 border + focus visual,正被 `FieldSurfaceContext='table-cell'` 取代)。consumer 請用 `default` / `bare`。\n *\n * 透傳:在 `<Field variant=\"default|bare\">` 內自動繼承 context.variant;per-prop override context。\n * 世界級對照(bare):VS Code settings input / Figma toolbar number / Notion prop input。\n */\n variant?: FieldVariant\n /** Error 狀態(正交於 mode)。border-error + aria-invalid。 */\n error?: boolean\n /** 左側靜態 icon — 輔助理解 input 用途(如 Search)。fg-muted。 */\n startIcon?: LucideIcon\n /** 右側 inline action — 宣告式 API,Field 根據 size 自動渲染。 */\n endAction?: InlineActionConfig\n /**\n * 右側 slot(ReactNode)— escape hatch 供 consumer 放自訂元素(如 DropdownMenuTrigger asChild + ItemInlineActionButton)。\n * 跟 `endAction` 互斥(同時傳 endSlot 會優先,endAction 被忽略)。\n *\n * **使用情境**:ZoomInput 需要 chevron 作 DropdownMenuTrigger anchor,config-only API 無法做到。\n * **禁止情境**:表單欄位 / 一般 inline action → 用 `endAction` 宣告式 API。\n */\n endSlot?: React.ReactNode\n /**\n * Loading 狀態(async 驗證 / debounce fetch 中)。\n * - **input 保持可編輯**(user 可以邊改邊讀,debounce 場景 UX 最好)\n * - 世界級對照:Ant Input.Search 派(input editable during loading);非 Material readonly 派\n * - 自動在 endAction slot 塞 `<CircularProgress size={iconSize}/>`(與 endAction prop 互斥)\n * - 宣告 `aria-busy=\"true\"` 讓 screen reader 感知處理中\n */\n loading?: boolean\n /**\n * Auto-width:Input 寬度 = 內容寬(value / placeholder 文字寬)+ startIcon + endAction + padding。\n * 使用 CSS `field-sizing: content`(Chrome 123+ / Safari 17.4+;Firefox 還在實驗)。\n *\n * **使用情境**:\n * - Inline edit(VS Code setting row / Figma property toolbar number input)\n * - ZoomInput(FileViewer 縮放比例:輸入「100%」自動縮到三位數寬)\n * - Tag / Chip 內 inline rename\n *\n * **不要用在**:表單 Field(Field 需要欄寬對齊,不該隨值跳動)\n *\n * **fallback**:不支援 `field-sizing` 的瀏覽器會退化為 `w-auto`(wrapper 縮到 content 尺寸,\n * input 本身有 min-width 避免消失)。UX 上稍不一致但不致斷;若必須精準對齊所有瀏覽器,\n * consumer 可自行傳 `style={{ width: ... }}` 顯式寬度,不走 auto。\n */\n autoWidth?: boolean\n}\n\n// ── Component ───────────────────────────────────────────────────────────────\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\nconst Input = React.forwardRef<HTMLInputElement, InputProps>(\n (\n {\n mode: modeProp,\n variant: variantProp,\n error = false,\n size: sizeProp,\n startIcon: StartIcon,\n endAction,\n endSlot,\n loading = false,\n autoWidth = false,\n className,\n disabled: disabledProp,\n readOnly,\n value,\n id: idProp,\n 'aria-describedby': ariaDescribedByProp,\n 'aria-errormessage': ariaErrorMessageProp,\n ...props\n },\n ref\n ) => {\n // ── FieldContext 自動讀取(在 <Field> 內時,invalid / disabled / mode / chrome 由 context 接管) ──\n const fieldCtx = useFieldContext()\n // 2026-05-31 #11/#12:size + disabled 從 Field context cascade(對齊 NumberInput number-input.tsx:100-101\n // + MUI FormControl)。原 Input 不讀 fieldCtx.size/disabled → <Field size=\"lg\"> / <Field disabled> 對 Input 無效。\n const size = useResolvedFieldSize(sizeProp)\n const disabled = useResolvedFieldDisabled(disabledProp)\n // chrome 透傳:per-prop override context;context 沒值則 'default'\n const variant: FieldVariant = useResolvedFieldVariant(variantProp)\n // 2026-06-08 SSOT:mode 經 useResolvedFieldMode 統一解析(prop > 有效 disabled > fieldCtx.mode > readOnly > 'edit')。\n // loading 期間 input 保持可編輯(Ant Input.Search 派),只用 aria-busy + endAction Spinner,不動 mode。\n const resolvedMode: FieldMode = useResolvedFieldMode({ mode: modeProp, disabled, readOnly })\n const isEditable = resolvedMode === 'edit'\n const isDisplay = resolvedMode === 'display'\n // error 合併:自身 error prop OR Field context invalid\n const resolvedError = useResolvedFieldInvalid(error)\n // 2026-05-18 改 import ICON_SIZE SSOT(per user『做完』approval,消除 M17 違反 7+ 重複 ternary)\n const iconSize = ICON_SIZE[size as 'sm' | 'md' | 'lg']\n const iconColor = resolvedMode === 'disabled' ? 'text-fg-disabled' : 'text-fg-muted'\n\n // ── display mode:純展示,渲染 <span> 取代 <input> ──\n // 對齊 Carbon read-only / PatternFly inline-edit hidden-input / Cloudscape display-mode\n if (isDisplay) {\n const displayValue = value != null && value !== '' ? String(value) : null\n return (\n <div\n className={cn(\n fieldWrapperStyles({ mode: 'display', variant: variant, size }),\n autoWidth && 'inline-flex w-auto',\n className,\n )}\n data-field-mode=\"display\"\n >\n {StartIcon && (\n <ItemPrefix>\n <StartIcon\n size={iconSize}\n className={cn('pointer-events-none', iconColor)}\n aria-hidden\n />\n </ItemPrefix>\n )}\n <span\n className={cn(\n bareInputStyles,\n // B1 fix(2026-05-05):display mode 單行 ellipsis 截斷(對齊 Notion / Airtable / Linear\n // cell display canonical:single-line value 過長 → ellipsis。Textarea display 走 wrap path,\n // 不在此處;Input display 永遠 single-line。)\n 'truncate',\n displayValue == null && 'text-fg-muted',\n )}\n >\n {displayValue ?? EMPTY_DISPLAY}\n </span>\n </div>\n )\n }\n\n return (\n <div\n className={cn(\n fieldWrapperStyles({ mode: resolvedMode, variant: variant, size }),\n isEditable && resolvedError && [\n 'border-error hover:border-error-hover',\n 'focus-within:border-error focus-within:hover:border-error',\n ],\n // autoWidth:wrapper 縮到 inline-flex + w-auto,讓寬度由 startIcon + input(field-sizing: content)+ endAction 自然累加\n autoWidth && 'inline-flex w-auto',\n className,\n )}\n data-field-mode={resolvedMode}\n data-error={isEditable && resolvedError ? '' : undefined}\n aria-busy={loading || undefined}\n >\n {StartIcon && (\n <ItemPrefix>\n <StartIcon\n size={iconSize}\n className={cn('pointer-events-none', iconColor)}\n aria-hidden\n />\n </ItemPrefix>\n )}\n <input\n ref={ref}\n type=\"text\"\n id={idProp ?? fieldCtx?.id}\n value={value as string | number | readonly string[] | undefined}\n readOnly={resolvedMode === 'readonly'}\n disabled={resolvedMode === 'disabled'}\n aria-invalid={resolvedError || undefined}\n aria-required={fieldCtx?.required || undefined}\n aria-describedby={ariaDescribedByProp ?? fieldCtx?.descriptionId}\n aria-errormessage={ariaErrorMessageProp ?? (resolvedError ? fieldCtx?.errorId : undefined)}\n className={cn(\n bareInputStyles,\n resolvedMode === 'disabled' && 'text-fg-disabled placeholder:text-fg-disabled cursor-not-allowed',\n // autoWidth:input 本身 field-sizing:content(Chrome 123+ / Safari 17.4+),寬度跟 value 文字寬。\n // w-auto 關掉預設 w-full;min-w-0 讓 flex shrink 不卡住。\n autoWidth && '[field-sizing:content] w-auto min-w-0',\n )}\n {...props}\n />\n {loading ? (\n <CircularProgress size={iconSize} className=\"shrink-0\" />\n ) : endSlot && isEditable ? (\n // endSlot escape hatch:consumer 自控右側 slot(如 DropdownMenuTrigger asChild wrap)\n endSlot\n ) : endAction && isEditable ? (\n <ItemInlineAction action={endAction} size={size ?? 'md'} />\n ) : null}\n </div>\n )\n }\n)\nInput.displayName = 'Input'\n\n// Phase B1(2026-05-05):InputDisplay 退場。改用 `<Input mode=\"display\" value={...} />`\n// 對齊 Carbon read-only / PatternFly inline-edit hidden-input / Cloudscape display-mode 統一 mode 模型。\n\n// Story auto-compile metadata — Phase 1 mechanical migration(2026-04-24)\n// Phase 2 fill needed: purpose descriptions + when rationale + world-class refs\nexport const inputMeta = {\n component: 'Input',\n family: 4,\n variants: {\n\n },\n sizes: {\n\n },\n states: ['default', 'hover', 'active', 'focus-visible', 'disabled'],\n tokens: {\n bg: ['bg-surface'],\n fg: ['text-fg-disabled', 'text-fg-muted'],\n ring: [],\n },\n} as const\n\nexport { Input }\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"input.js","sources":["../../../src/components/Input/input.tsx"],"sourcesContent":["// @benchmark-unverified-blanket: file-level retraction per M22 (d) — claims herein not individually URL-cited; treat as unverified visual/usage rumor unless retrofit per-claim. Hook escape preserved.\nimport * as React from 'react'\nimport { type VariantProps } from 'class-variance-authority'\nimport type { LucideIcon } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport type { FieldMode, FieldVariant } from '@/design-system/components/Field/field-types'\nimport { fieldWrapperStyles, bareInputStyles, EMPTY_DISPLAY } from '@/design-system/components/Field/field-wrapper'\nimport { useFieldContext, useResolvedFieldSize, useResolvedFieldDisabled, useResolvedFieldMode, useResolvedFieldVariant, useResolvedFieldInvalid } from '@/design-system/components/Field/field-context'\nimport { ItemInlineAction, ItemPrefix, type InlineActionConfig } from '@/design-system/patterns/element-anatomy/item-anatomy'\nimport { CircularProgress } from '@/design-system/components/CircularProgress/circular-progress'\nimport { ICON_SIZE } from '@/design-system/tokens/uiSize/icon-size'\n\n// ── Types ───────────────────────────────────────────────────────────────────\n\nexport interface InputProps\n extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'>,\n Omit<VariantProps<typeof fieldWrapperStyles>, 'mode' | 'variant'> {\n /** Field display mode */\n mode?: FieldMode\n /**\n * Visual chrome(正交於 mode);Phase B1(2026-05-05)從 `variant` 改名 `chrome`,對齊 FieldContext.variant 透傳。\n * 公開 variant 兩個:\n * - `'default'`(預設)— Field wrapper 完整 variant:bg-surface + 明顯 border + hover/focus 回饋。適用表單、Field 內嵌。\n * - `'bare'` — 透明 variant,hover / focus 才出現 border。適用 Toolbar inline editing(如 FileViewer zoom input / chart config toolbar / rich text toolbar number input)。保留 padding / typography / height,只拿掉背景和常態 border。\n *\n * @internal `'naked'` — 完全無 chrome / 無 border / 無 focus ring。單獨使用無視覺邊界,**不可直接 standalone 用**;僅供 DS 內部 cell-as-input 組合(host cell 自管 border + focus visual,正被 `FieldSurfaceContext='table-cell'` 取代)。consumer 請用 `default` / `bare`。\n *\n * 透傳:在 `<Field variant=\"default|bare\">` 內自動繼承 context.variant;per-prop override context。\n * 世界級對照(bare):VS Code settings input / Figma toolbar number / Notion prop input。\n */\n variant?: FieldVariant\n /** Error 狀態(正交於 mode)。border-error + aria-invalid。 */\n error?: boolean\n /** 左側靜態 icon — 輔助理解 input 用途(如 Search)。fg-muted。 */\n startIcon?: LucideIcon\n /** 右側 inline action — 宣告式 API,Field 根據 size 自動渲染。 */\n endAction?: InlineActionConfig\n /**\n * 右側 slot(ReactNode)— escape hatch 供 consumer 放自訂元素(如 DropdownMenuTrigger asChild + ItemInlineActionButton)。\n * 跟 `endAction` 互斥(同時傳 endSlot 會優先,endAction 被忽略)。\n *\n * **使用情境**:ZoomInput 需要 chevron 作 DropdownMenuTrigger anchor,config-only API 無法做到。\n * **禁止情境**:表單欄位 / 一般 inline action → 用 `endAction` 宣告式 API。\n */\n endSlot?: React.ReactNode\n /**\n * Loading 狀態(async 驗證 / debounce fetch 中)。\n * - **input 保持可編輯**(user 可以邊改邊讀,debounce 場景 UX 最好)\n * - 世界級對照:Ant Input.Search 派(input editable during loading);非 Material readonly 派\n * - 自動在 endAction slot 塞 `<CircularProgress size={iconSize}/>`(與 endAction prop 互斥)\n * - 宣告 `aria-busy=\"true\"` 讓 screen reader 感知處理中\n */\n loading?: boolean\n /**\n * Auto-width:Input 寬度 = 內容寬(value / placeholder 文字寬)+ startIcon + endAction + padding。\n * 使用 CSS `field-sizing: content`(Chrome 123+ / Safari 17.4+;Firefox 還在實驗)。\n *\n * **使用情境**:\n * - Inline edit(VS Code setting row / Figma property toolbar number input)\n * - ZoomInput(FileViewer 縮放比例:輸入「100%」自動縮到三位數寬)\n * - Tag / Chip 內 inline rename\n *\n * **不要用在**:表單 Field(Field 需要欄寬對齊,不該隨值跳動)\n *\n * **fallback**:不支援 `field-sizing` 的瀏覽器會退化為 `w-auto`(wrapper 縮到 content 尺寸,\n * input 本身有 min-width 避免消失)。UX 上稍不一致但不致斷;若必須精準對齊所有瀏覽器,\n * consumer 可自行傳 `style={{ width: ... }}` 顯式寬度,不走 auto。\n */\n autoWidth?: boolean\n}\n\n// ── Component ───────────────────────────────────────────────────────────────\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\nconst Input = React.forwardRef<HTMLInputElement, InputProps>(\n (\n {\n mode: modeProp,\n variant: variantProp,\n error = false,\n size: sizeProp,\n startIcon: StartIcon,\n endAction,\n endSlot,\n loading = false,\n autoWidth = false,\n className,\n disabled: disabledProp,\n readOnly,\n value,\n id: idProp,\n 'aria-describedby': ariaDescribedByProp,\n 'aria-errormessage': ariaErrorMessageProp,\n ...props\n },\n ref\n ) => {\n // ── FieldContext 自動讀取(在 <Field> 內時,invalid / disabled / mode / chrome 由 context 接管) ──\n const fieldCtx = useFieldContext()\n // 2026-05-31 #11/#12:size + disabled 從 Field context cascade(對齊 NumberInput number-input.tsx:100-101\n // + MUI FormControl)。原 Input 不讀 fieldCtx.size/disabled → <Field size=\"lg\"> / <Field disabled> 對 Input 無效。\n const size = useResolvedFieldSize(sizeProp)\n const disabled = useResolvedFieldDisabled(disabledProp)\n // chrome 透傳:per-prop override context;context 沒值則 'default'\n const variant: FieldVariant = useResolvedFieldVariant(variantProp)\n // 2026-06-08 SSOT:mode 經 useResolvedFieldMode 統一解析(prop > 有效 disabled > fieldCtx.mode > readOnly > 'edit')。\n // loading 期間 input 保持可編輯(Ant Input.Search 派),只用 aria-busy + endAction Spinner,不動 mode。\n const resolvedMode: FieldMode = useResolvedFieldMode({ mode: modeProp, disabled, readOnly })\n const isEditable = resolvedMode === 'edit'\n const isDisplay = resolvedMode === 'display'\n // error 合併:自身 error prop OR Field context invalid\n const resolvedError = useResolvedFieldInvalid(error)\n // 2026-05-18 改 import ICON_SIZE SSOT(per user『做完』approval,消除 M17 違反 7+ 重複 ternary)\n const iconSize = ICON_SIZE[size as 'sm' | 'md' | 'lg']\n const iconColor = resolvedMode === 'disabled' ? 'text-fg-disabled' : 'text-fg-muted'\n\n // ── display mode:純展示,渲染 <span> 取代 <input> ──\n // 對齊 Carbon read-only / PatternFly inline-edit hidden-input / Cloudscape display-mode\n if (isDisplay) {\n const displayValue = value != null && value !== '' ? String(value) : null\n return (\n <div\n className={cn(\n fieldWrapperStyles({ mode: 'display', variant: variant, size }),\n autoWidth && 'inline-flex w-auto',\n className,\n )}\n data-field-mode=\"display\"\n >\n {StartIcon && (\n <ItemPrefix>\n <StartIcon\n size={iconSize}\n className={cn('pointer-events-none', iconColor)}\n aria-hidden\n />\n </ItemPrefix>\n )}\n <span\n className={cn(\n bareInputStyles,\n // B1 fix(2026-05-05):display mode 單行 ellipsis 截斷(對齊 Notion / Airtable / Linear\n // cell display canonical:single-line value 過長 → ellipsis。Textarea display 走 wrap path,\n // 不在此處;Input display 永遠 single-line。)\n 'truncate',\n displayValue == null && 'text-fg-muted',\n )}\n >\n {displayValue ?? EMPTY_DISPLAY}\n </span>\n </div>\n )\n }\n\n return (\n <div\n className={cn(\n fieldWrapperStyles({ mode: resolvedMode, variant: variant, size, error: resolvedError }),\n // autoWidth:wrapper 縮到 inline-flex + w-auto,讓寬度由 startIcon + input(field-sizing: content)+ endAction 自然累加\n autoWidth && 'inline-flex w-auto',\n className,\n )}\n data-field-mode={resolvedMode}\n data-error={isEditable && resolvedError ? '' : undefined}\n aria-busy={loading || undefined}\n >\n {StartIcon && (\n <ItemPrefix>\n <StartIcon\n size={iconSize}\n className={cn('pointer-events-none', iconColor)}\n aria-hidden\n />\n </ItemPrefix>\n )}\n <input\n ref={ref}\n type=\"text\"\n id={idProp ?? fieldCtx?.id}\n value={value as string | number | readonly string[] | undefined}\n readOnly={resolvedMode === 'readonly'}\n disabled={resolvedMode === 'disabled'}\n aria-invalid={resolvedError || undefined}\n aria-required={fieldCtx?.required || undefined}\n aria-describedby={ariaDescribedByProp ?? fieldCtx?.descriptionId}\n aria-errormessage={ariaErrorMessageProp ?? (resolvedError ? fieldCtx?.errorId : undefined)}\n className={cn(\n bareInputStyles,\n resolvedMode === 'disabled' && 'text-fg-disabled placeholder:text-fg-disabled cursor-not-allowed',\n // autoWidth:input 本身 field-sizing:content(Chrome 123+ / Safari 17.4+),寬度跟 value 文字寬。\n // w-auto 關掉預設 w-full;min-w-0 讓 flex shrink 不卡住。\n autoWidth && '[field-sizing:content] w-auto min-w-0',\n )}\n {...props}\n />\n {loading ? (\n <CircularProgress size={iconSize} className=\"shrink-0\" />\n ) : endSlot && isEditable ? (\n // endSlot escape hatch:consumer 自控右側 slot(如 DropdownMenuTrigger asChild wrap)\n endSlot\n ) : endAction && isEditable ? (\n <ItemInlineAction action={endAction} size={size ?? 'md'} />\n ) : null}\n </div>\n )\n }\n)\nInput.displayName = 'Input'\n\n// Phase B1(2026-05-05):InputDisplay 退場。改用 `<Input mode=\"display\" value={...} />`\n// 對齊 Carbon read-only / PatternFly inline-edit hidden-input / Cloudscape display-mode 統一 mode 模型。\n\n// Story auto-compile metadata — Phase 1 mechanical migration(2026-04-24)\n// 2026-07-05 Phase 2 fill(deep-audit A.1b):variants 按 input.tsx variant jsdoc、sizes 按\n// fieldWrapperStyles cva 真值(h-field-sm/md/lg = 28/32/36,uiSize.css)+ ICON_SIZE SSOT\n// (sm/md 16、lg 20)填。`naked` 為 @internal cell-as-input substrate → 不列入 public variants。\nexport const inputMeta = {\n component: 'Input',\n family: 4,\n variants: {\n default: { purpose: 'Field wrapper 完整 chrome(bg-surface + border + hover/focus 回饋)— 表單 / Field 內嵌' },\n bare: { purpose: '透明 chrome,hover / focus 才現 border — Toolbar inline editing(zoom input / config toolbar)' },\n },\n sizes: {\n sm: { fieldHeight: 28, iconSize: 16, typography: 'body' },\n md: { fieldHeight: 32, iconSize: 16, typography: 'body' },\n lg: { fieldHeight: 36, iconSize: 20, typography: 'body-lg' },\n },\n // 'active' 移除 — Field chrome 無按壓態(對齊 Textarea / LinkInput meta 既有寫法)(2026-07-07 詞彙統一 DS-wide 按壓訊號盤點:檔內 0 active: utility / 0 *-active token)。\n states: ['default', 'hover', 'focus-visible', 'disabled'],\n tokens: {\n bg: ['bg-surface'],\n fg: ['text-fg-disabled', 'text-fg-muted'],\n ring: [],\n },\n} as const\n\nexport { Input }\n"],"names":[],"mappings":"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"}
|
|
@@ -2,7 +2,7 @@ import * as React from 'react';
|
|
|
2
2
|
import type { VariantProps } from 'class-variance-authority';
|
|
3
3
|
import type { FieldMode, FieldVariant } from '../../components/Field/field-types';
|
|
4
4
|
import { fieldWrapperStyles } from '../../components/Field/field-wrapper';
|
|
5
|
-
export interface LinkInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size' | 'value' | 'onChange'>, Omit<VariantProps<typeof fieldWrapperStyles>, 'mode' | 'variant'> {
|
|
5
|
+
export interface LinkInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size' | 'value' | 'defaultValue' | 'onChange'>, Omit<VariantProps<typeof fieldWrapperStyles>, 'mode' | 'variant'> {
|
|
6
6
|
mode?: FieldMode;
|
|
7
7
|
/**
|
|
8
8
|
* Visual chrome(2026-05-05 Phase B3)。對齊 FieldContext.variant 透傳。
|
|
@@ -35,9 +35,9 @@ export declare const linkInputMeta: {
|
|
|
35
35
|
readonly family: 4;
|
|
36
36
|
readonly variants: {};
|
|
37
37
|
readonly sizes: {};
|
|
38
|
-
readonly states: readonly ["default", "hover", "
|
|
38
|
+
readonly states: readonly ["default", "hover", "focus-visible", "readonly", "disabled", "error"];
|
|
39
39
|
readonly tokens: {
|
|
40
|
-
readonly bg: readonly [];
|
|
40
|
+
readonly bg: readonly ["bg-surface", "bg-readonly", "bg-disabled"];
|
|
41
41
|
readonly fg: readonly ["text-fg-disabled", "text-fg-muted", "text-primary"];
|
|
42
42
|
readonly ring: readonly [];
|
|
43
43
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"link-input.d.ts","sourceRoot":"","sources":["../../../src/components/LinkInput/link-input.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAE5D,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,8CAA8C,CAAA;AAC3F,OAAO,EAAE,kBAAkB,EAAyD,MAAM,gDAAgD,CAAA;AA4C1I,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"link-input.d.ts","sourceRoot":"","sources":["../../../src/components/LinkInput/link-input.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAE5D,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,8CAA8C,CAAA;AAC3F,OAAO,EAAE,kBAAkB,EAAyD,MAAM,gDAAgD,CAAA;AA4C1I,MAAM,WAAW,cAIf,SAAQ,IAAI,CAAC,KAAK,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,cAAc,GAAG,UAAU,CAAC,EACvG,IAAI,CAAC,YAAY,CAAC,OAAO,kBAAkB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnE,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,YAAY,CAAA;IACtB,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAClC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,mBAAmB;IACnB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;;;;;OAMG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAC7B;AAGD,QAAA,MAAM,SAAS,yFAmNd,CAAA;AAKD,eAAO,MAAM,aAAa;;;;;;;;;;;CAkBhB,CAAA;AAEV,OAAO,EAAE,SAAS,EAAE,CAAA"}
|
|
@@ -3,7 +3,7 @@ import * as React from "react";
|
|
|
3
3
|
import { Pencil } from "lucide-react";
|
|
4
4
|
import { cn } from "../../lib/utils.js";
|
|
5
5
|
import { EMPTY_DISPLAY, fieldDisplayTextClass, fieldWrapperStyles, bareInputStyles } from "../Field/field-wrapper.js";
|
|
6
|
-
import { useFieldContext, useResolvedFieldSize, useResolvedFieldDisabled, useResolvedFieldMode, useResolvedFieldVariant } from "../Field/field-context.js";
|
|
6
|
+
import { useFieldContext, useResolvedFieldSize, useResolvedFieldDisabled, useResolvedFieldMode, useResolvedFieldVariant, useResolvedFieldInvalid } from "../Field/field-context.js";
|
|
7
7
|
import { ItemInlineAction } from "../../patterns/element-anatomy/item-anatomy.js";
|
|
8
8
|
function isValidUrl(value) {
|
|
9
9
|
if (!value) return true;
|
|
@@ -59,20 +59,7 @@ const LinkInput = React.forwardRef(
|
|
|
59
59
|
const resolvedMode = useResolvedFieldMode({ mode: modeProp, disabled, readOnly });
|
|
60
60
|
const isEditable = resolvedMode === "edit";
|
|
61
61
|
const resolvedVariant = useResolvedFieldVariant(variantProp);
|
|
62
|
-
|
|
63
|
-
if (!showDisplayEndIcon) {
|
|
64
|
-
if (!value) return /* @__PURE__ */ jsx("span", { className: cn(fieldDisplayTextClass(size), "text-fg-muted block truncate"), children: EMPTY_DISPLAY });
|
|
65
|
-
return /* @__PURE__ */ jsx("span", { className: cn(fieldDisplayTextClass(size), "block truncate"), children: renderLinkAnchor(value, label) });
|
|
66
|
-
}
|
|
67
|
-
return /* @__PURE__ */ jsx(
|
|
68
|
-
"div",
|
|
69
|
-
{
|
|
70
|
-
className: cn(fieldWrapperStyles({ mode: "display", variant: resolvedVariant, size }), className),
|
|
71
|
-
"data-field-mode": "display",
|
|
72
|
-
children: /* @__PURE__ */ jsx("span", { className: "flex-1 min-w-0 truncate", children: value ? renderLinkAnchor(value, label) : /* @__PURE__ */ jsx("span", { className: "text-fg-muted", children: EMPTY_DISPLAY }) })
|
|
73
|
-
}
|
|
74
|
-
);
|
|
75
|
-
}
|
|
62
|
+
const fieldInvalid = useResolvedFieldInvalid(errorProp);
|
|
76
63
|
const [editing, setEditing] = React.useState(false);
|
|
77
64
|
const [localValue, setLocalValue] = React.useState(value ?? "");
|
|
78
65
|
const [localError, setLocalError] = React.useState(false);
|
|
@@ -85,13 +72,27 @@ const LinkInput = React.forwardRef(
|
|
|
85
72
|
if (typeof ref === "function") ref(el);
|
|
86
73
|
else if (ref) ref.current = el;
|
|
87
74
|
}, [ref]);
|
|
88
|
-
const hasValidValue = !!value && isValidUrl(value);
|
|
89
|
-
const showLink = isEditable && hasValidValue && !editing && !localError;
|
|
90
|
-
const error = errorProp || localError;
|
|
91
75
|
const focusRafIdRef = React.useRef(0);
|
|
92
76
|
React.useEffect(() => () => {
|
|
93
77
|
if (focusRafIdRef.current) cancelAnimationFrame(focusRafIdRef.current);
|
|
94
78
|
}, []);
|
|
79
|
+
if (resolvedMode === "display") {
|
|
80
|
+
if (!showDisplayEndIcon) {
|
|
81
|
+
if (!value) return /* @__PURE__ */ jsx("span", { className: cn(fieldDisplayTextClass(size), "text-fg-muted block truncate"), children: EMPTY_DISPLAY });
|
|
82
|
+
return /* @__PURE__ */ jsx("span", { className: cn(fieldDisplayTextClass(size), "block truncate"), children: renderLinkAnchor(value, label) });
|
|
83
|
+
}
|
|
84
|
+
return /* @__PURE__ */ jsx(
|
|
85
|
+
"div",
|
|
86
|
+
{
|
|
87
|
+
className: cn(fieldWrapperStyles({ mode: "display", variant: resolvedVariant, size }), className),
|
|
88
|
+
"data-field-mode": "display",
|
|
89
|
+
children: /* @__PURE__ */ jsx("span", { className: "flex-1 min-w-0 truncate", children: value ? renderLinkAnchor(value, label) : /* @__PURE__ */ jsx("span", { className: "text-fg-muted", children: EMPTY_DISPLAY }) })
|
|
90
|
+
}
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
const hasValidValue = !!value && isValidUrl(value);
|
|
94
|
+
const showLink = isEditable && hasValidValue && !editing && !localError;
|
|
95
|
+
const error = fieldInvalid || localError;
|
|
95
96
|
const handleEdit = () => {
|
|
96
97
|
setEditing(true);
|
|
97
98
|
if (focusRafIdRef.current) cancelAnimationFrame(focusRafIdRef.current);
|
|
@@ -144,8 +145,9 @@ const LinkInput = React.forwardRef(
|
|
|
144
145
|
return /* @__PURE__ */ jsxs(
|
|
145
146
|
"div",
|
|
146
147
|
{
|
|
147
|
-
className: cn(fieldWrapperStyles({ mode: "edit", variant: resolvedVariant, size }), className),
|
|
148
|
+
className: cn(fieldWrapperStyles({ mode: "edit", variant: resolvedVariant, size, error }), className),
|
|
148
149
|
"data-field-mode": "edit",
|
|
150
|
+
"data-error": error ? "" : void 0,
|
|
149
151
|
children: [
|
|
150
152
|
/* @__PURE__ */ jsx("span", { className: "flex-1 min-w-0", children: value && renderLinkAnchor(value, label) }),
|
|
151
153
|
/* @__PURE__ */ jsx(
|
|
@@ -163,11 +165,7 @@ const LinkInput = React.forwardRef(
|
|
|
163
165
|
"div",
|
|
164
166
|
{
|
|
165
167
|
className: cn(
|
|
166
|
-
fieldWrapperStyles({ mode: "edit", variant: resolvedVariant, size }),
|
|
167
|
-
error && [
|
|
168
|
-
"border-error hover:border-error-hover",
|
|
169
|
-
"focus-within:border-error focus-within:hover:border-error"
|
|
170
|
-
],
|
|
168
|
+
fieldWrapperStyles({ mode: "edit", variant: resolvedVariant, size, error }),
|
|
171
169
|
className
|
|
172
170
|
),
|
|
173
171
|
"data-field-mode": "edit",
|
|
@@ -183,10 +181,10 @@ const LinkInput = React.forwardRef(
|
|
|
183
181
|
onBlur: handleBlur,
|
|
184
182
|
onKeyDown: handleKeyDown,
|
|
185
183
|
placeholder,
|
|
186
|
-
"aria-invalid": error ||
|
|
184
|
+
"aria-invalid": error || void 0,
|
|
187
185
|
"aria-required": (fieldCtx == null ? void 0 : fieldCtx.required) || void 0,
|
|
188
186
|
"aria-describedby": ariaDescribedByProp ?? (fieldCtx == null ? void 0 : fieldCtx.descriptionId),
|
|
189
|
-
"aria-errormessage": ariaErrorMessageProp ?? (error
|
|
187
|
+
"aria-errormessage": ariaErrorMessageProp ?? (error ? fieldCtx == null ? void 0 : fieldCtx.errorId : void 0),
|
|
190
188
|
className: bareInputStyles,
|
|
191
189
|
...props
|
|
192
190
|
}
|
|
@@ -201,9 +199,12 @@ const linkInputMeta = {
|
|
|
201
199
|
family: 4,
|
|
202
200
|
variants: {},
|
|
203
201
|
sizes: {},
|
|
204
|
-
states
|
|
202
|
+
// states 對齊真實 state 集:text-input 家族無 'active'(按下)專屬視覺態(同 textareaMeta 修法);
|
|
203
|
+
// readonly / error 為 fieldWrapperStyles mode + errorProp/localError 實有狀態(2026-07-04 audit 對齊)。
|
|
204
|
+
states: ["default", "hover", "focus-visible", "readonly", "disabled", "error"],
|
|
205
205
|
tokens: {
|
|
206
|
-
bg
|
|
206
|
+
// bg 經 fieldWrapperStyles 實渲:edit bg-surface / readonly bg-readonly / disabled bg-disabled(field-wrapper.tsx)。
|
|
207
|
+
bg: ["bg-surface", "bg-readonly", "bg-disabled"],
|
|
207
208
|
fg: ["text-fg-disabled", "text-fg-muted", "text-primary"],
|
|
208
209
|
ring: []
|
|
209
210
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"link-input.js","sources":["../../../src/components/LinkInput/link-input.tsx"],"sourcesContent":["import * as React from 'react'\nimport { Pencil } from 'lucide-react'\nimport type { VariantProps } from 'class-variance-authority'\nimport { cn } from '@/lib/utils'\nimport type { FieldMode, FieldVariant } from '@/design-system/components/Field/field-types'\nimport { fieldWrapperStyles, bareInputStyles, EMPTY_DISPLAY, fieldDisplayTextClass } from '@/design-system/components/Field/field-wrapper'\nimport { useFieldContext, useResolvedFieldSize, useResolvedFieldDisabled, useResolvedFieldMode, useResolvedFieldVariant } from '@/design-system/components/Field/field-context'\nimport { ItemInlineAction } from '@/design-system/patterns/element-anatomy/item-anatomy'\n\n// ── URL Validation ──────────────────────────────────────────────────────────\n\nfunction isValidUrl(value: string): boolean {\n if (!value) return true\n try {\n const url = new URL(value)\n return url.protocol === 'http:' || url.protocol === 'https:'\n } catch {\n return false\n }\n}\n\nfunction formatHostname(url: string): string {\n try {\n return new URL(url).hostname.replace(/^www\\./, '')\n } catch {\n return url\n }\n}\n\n// ── Display rendering(inline,2026-05-05 Phase B3 retire LinkInputDisplay)──\n// 取代 LinkInputDisplay sub-component:純展示 a tag,無 input chrome、無 hover affordance。\n// edit mode 內 link state(showLink branch)也共用此 helper,確保「編輯態的 link 顯示」與\n// display mode 的視覺完全一致(SSOT)。\nfunction renderLinkAnchor(value: string, label?: string) {\n const displayText = label || formatHostname(value)\n return (\n <a\n href={value}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"block truncate min-w-0 text-primary hover:text-primary-hover hover:underline transition-colors\"\n >\n {displayText}\n </a>\n )\n}\n\n// ── Component ───────────────────────────────────────────────────────────────\n\nexport interface LinkInputProps\n extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size' | 'value' | 'onChange'>,\n Omit<VariantProps<typeof fieldWrapperStyles>, 'mode' | 'variant'> {\n mode?: FieldMode\n /**\n * Visual chrome(2026-05-05 Phase B3)。對齊 FieldContext.variant 透傳。\n * - `'default'`(預設)— Field wrapper 完整 chrome(form / Field 內嵌)。\n * - `'bare'` — 透明 variant,hover/focus 才現 border(Toolbar inline edit / DataTable cell-as-input)。\n *\n * mode='display' 時 chrome 無視覺意義(display 完全無 wrapper);chrome 僅作用於 edit / readonly / disabled。\n */\n variant?: FieldVariant\n error?: boolean\n value?: string | null\n onChange?: (value: string) => void\n placeholder?: string\n className?: string\n disabled?: boolean\n /** 自訂顯示文字(非編輯時) */\n label?: string\n /**\n * Display 是否包 Field naked wrapper(D-path opt-in,2026-05-08)\n * — DataTable cell display↔edit 像素級對齊用。預設 false(裸 anchor,backward compat)。\n * 設 true 時 display 走 fieldWrapperStyles(naked variant)包覆 anchor,\n * 與 cell edit (`<Input naked>`) 同 DOM 結構,消除 Layer-B padding mismatch。\n * **本元件 edit 無 endIcon(UrlCell 用 plain Input edit)→ display 也無 ItemSuffix**(僅 wrapper)。\n */\n showDisplayEndIcon?: boolean\n}\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\nconst LinkInput = React.forwardRef<HTMLInputElement, LinkInputProps>(\n (\n {\n mode: modeProp,\n variant: variantProp,\n error: errorProp = false,\n size: sizeProp,\n value,\n onChange,\n placeholder = 'https://',\n className,\n disabled: disabledProp,\n label,\n showDisplayEndIcon = false,\n readOnly,\n id: idProp,\n 'aria-describedby': ariaDescribedByProp,\n 'aria-errormessage': ariaErrorMessageProp,\n ...props\n },\n ref\n ) => {\n const fieldCtx = useFieldContext()\n const size = useResolvedFieldSize(sizeProp)\n const disabled = useResolvedFieldDisabled(disabledProp)\n // 2026-06-08 SSOT:mode 經 useResolvedFieldMode 統一解析(prop > 有效 disabled > fieldCtx.mode > readOnly > 'edit')。\n // spec field-controls.spec.md L125「readOnly 原生屬性自動覆蓋 mode」契約落地。\n const resolvedMode: FieldMode = useResolvedFieldMode({ mode: modeProp, disabled, readOnly })\n const isEditable = resolvedMode === 'edit'\n // chrome resolution:per-prop > context > 'default'\n const resolvedVariant: FieldVariant = useResolvedFieldVariant(variantProp)\n\n // ── mode='display' ─────────────────────────────────────────────────────\n // 純展示:無 input chrome / 無 hover affordance / 無 Pencil edit 入口。\n // 取代既有 LinkInputDisplay sub-component(2026-05-05 Phase B3 retire)。\n // Default(showDisplayEndIcon=false):無 wrapper 裸 anchor — backward compat。\n // Opt-in(showDisplayEndIcon=true,2026-05-08 D-path):Field naked wrapper 包覆 anchor,\n // 與 cell edit (`<Input naked>`) 同 DOM 結構消除像素偏移(無 ItemSuffix,因 edit 也無 endIcon)。\n if (resolvedMode === 'display') {\n if (!showDisplayEndIcon) {\n // 2026-05-14 I2 fix(spec contract (e) display typography canonical):非 D-path bare\n // anchor / span 必套 `fieldDisplayTextClass(size)`(sm/md→text-body,lg→text-body-lg)\n // — 對齊跨 Field family display 視覺尺寸統一。原無 font-size class → 用 browser default\n // 字體 → 跟其他 Field display 不一致(user 抓 I2)。truncate 同需,長 URL ellipsis(I1)。\n if (!value) return <span className={cn(fieldDisplayTextClass(size), 'text-fg-muted block truncate')}>{EMPTY_DISPLAY}</span>\n return <span className={cn(fieldDisplayTextClass(size), 'block truncate')}>{renderLinkAnchor(value, label)}</span>\n }\n return (\n <div\n className={cn(fieldWrapperStyles({ mode: 'display', variant: resolvedVariant, size }), className)}\n data-field-mode=\"display\"\n >\n <span className=\"flex-1 min-w-0 truncate\">\n {value\n ? renderLinkAnchor(value, label)\n : <span className=\"text-fg-muted\">{EMPTY_DISPLAY}</span>\n }\n </span>\n </div>\n )\n }\n\n const [editing, setEditing] = React.useState(false)\n const [localValue, setLocalValue] = React.useState(value ?? '')\n const [localError, setLocalError] = React.useState(false)\n const inputRef = React.useRef<HTMLInputElement | null>(null)\n\n // Sync external value → local\n React.useEffect(() => {\n if (!editing) setLocalValue(value ?? '')\n }, [value, editing])\n\n // Merge refs\n const setRef = React.useCallback((el: HTMLInputElement | null) => {\n inputRef.current = el\n if (typeof ref === 'function') ref(el)\n else if (ref) (ref as React.MutableRefObject<HTMLInputElement | null>).current = el\n }, [ref])\n\n const hasValidValue = !!value && isValidUrl(value)\n const showLink = isEditable && hasValidValue && !editing && !localError\n const error = errorProp || localError\n\n // 2026-05-16 audit codex Round 6:capture rAF + cancel on unmount(defensive hygiene)\n const focusRafIdRef = React.useRef<number>(0)\n React.useEffect(() => () => { if (focusRafIdRef.current) cancelAnimationFrame(focusRafIdRef.current) }, [])\n\n const handleEdit = () => {\n setEditing(true)\n if (focusRafIdRef.current) cancelAnimationFrame(focusRafIdRef.current)\n focusRafIdRef.current = requestAnimationFrame(() => {\n focusRafIdRef.current = 0\n inputRef.current?.focus()\n })\n }\n\n const handleBlur = () => {\n setEditing(false)\n const trimmed = localValue.trim()\n if (!trimmed) {\n // Empty is OK — clear value\n setLocalError(false)\n onChange?.('')\n return\n }\n if (isValidUrl(trimmed)) {\n setLocalError(false)\n onChange?.(trimmed)\n } else {\n setLocalError(true)\n }\n }\n\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n setLocalValue(e.target.value)\n // Clear error on edit (blur validation)\n if (localError) setLocalError(false)\n }\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (e.key === 'Enter') inputRef.current?.blur()\n if (e.key === 'Escape') {\n setLocalValue(value ?? '')\n setLocalError(false)\n setEditing(false)\n }\n }\n\n // readonly — 顯示藍色連結(可點擊)\n // disabled — 顯示純文字 fg-disabled(不可點擊)\n if (!isEditable) {\n const displayText = value ? (label || formatHostname(value)) : null\n return (\n <div\n className={cn(fieldWrapperStyles({ mode: resolvedMode, variant: resolvedVariant, size }), className)}\n data-field-mode={resolvedMode}\n >\n <span className=\"flex-1 min-w-0 truncate\">\n {resolvedMode === 'disabled'\n ? (displayText\n ? <span className=\"text-fg-disabled\">{displayText}</span>\n : <span className=\"text-fg-muted\">{EMPTY_DISPLAY}</span>)\n : (value\n ? renderLinkAnchor(value, label)\n : <span className=\"text-fg-muted\">{EMPTY_DISPLAY}</span>)\n }\n </span>\n </div>\n )\n }\n\n // edit — link display mode(有合法 URL 且未在編輯中)\n if (showLink) {\n return (\n <div\n className={cn(fieldWrapperStyles({ mode: 'edit', variant: resolvedVariant, size }), className)}\n data-field-mode=\"edit\"\n >\n <span className=\"flex-1 min-w-0\">\n {value && renderLinkAnchor(value, label)}\n </span>\n <ItemInlineAction\n size={size ?? 'md'}\n action={{ icon: Pencil, label: '編輯連結', onClick: handleEdit }} // i18n-allow: DS default inline-action label\n />\n </div>\n )\n }\n\n // edit — text input mode(正在編輯、無值、或格式錯誤)\n return (\n <div\n className={cn(\n fieldWrapperStyles({ mode: 'edit', variant: resolvedVariant, size }),\n error && [\n 'border-error hover:border-error-hover',\n 'focus-within:border-error focus-within:hover:border-error',\n ],\n className,\n )}\n data-field-mode=\"edit\"\n data-error={error ? '' : undefined}\n >\n <input\n ref={setRef}\n type=\"url\"\n id={idProp ?? fieldCtx?.id}\n value={localValue}\n onChange={handleChange}\n onBlur={handleBlur}\n onKeyDown={handleKeyDown}\n placeholder={placeholder}\n aria-invalid={(error || fieldCtx?.invalid) || undefined}\n aria-required={fieldCtx?.required || undefined}\n aria-describedby={ariaDescribedByProp ?? fieldCtx?.descriptionId}\n aria-errormessage={ariaErrorMessageProp ?? ((error || fieldCtx?.invalid) ? fieldCtx?.errorId : undefined)}\n className={bareInputStyles}\n {...props}\n />\n </div>\n )\n }\n)\nLinkInput.displayName = 'LinkInput'\n\n// Story auto-compile metadata — Phase 1 mechanical migration(2026-04-24)\n// Phase 2 fill needed: purpose descriptions + when rationale + world-class refs\nexport const linkInputMeta = {\n component: 'LinkInput',\n family: 4,\n variants: {\n\n },\n sizes: {\n\n },\n states: ['default', 'hover', 'active', 'focus-visible', 'disabled'],\n tokens: {\n bg: [],\n fg: ['text-fg-disabled', 'text-fg-muted', 'text-primary'],\n ring: [],\n },\n} as const\n\nexport { LinkInput }\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"link-input.js","sources":["../../../src/components/LinkInput/link-input.tsx"],"sourcesContent":["import * as React from 'react'\nimport { Pencil } from 'lucide-react'\nimport type { VariantProps } from 'class-variance-authority'\nimport { cn } from '@/lib/utils'\nimport type { FieldMode, FieldVariant } from '@/design-system/components/Field/field-types'\nimport { fieldWrapperStyles, bareInputStyles, EMPTY_DISPLAY, fieldDisplayTextClass } from '@/design-system/components/Field/field-wrapper'\nimport { useFieldContext, useResolvedFieldSize, useResolvedFieldDisabled, useResolvedFieldMode, useResolvedFieldVariant, useResolvedFieldInvalid } from '@/design-system/components/Field/field-context'\nimport { ItemInlineAction } from '@/design-system/patterns/element-anatomy/item-anatomy'\n\n// ── URL Validation ──────────────────────────────────────────────────────────\n\nfunction isValidUrl(value: string): boolean {\n if (!value) return true\n try {\n const url = new URL(value)\n return url.protocol === 'http:' || url.protocol === 'https:'\n } catch {\n return false\n }\n}\n\nfunction formatHostname(url: string): string {\n try {\n return new URL(url).hostname.replace(/^www\\./, '')\n } catch {\n return url\n }\n}\n\n// ── Display rendering(inline,2026-05-05 Phase B3 retire LinkInputDisplay)──\n// 取代 LinkInputDisplay sub-component:純展示 a tag,無 input chrome、無 hover affordance。\n// edit mode 內 link state(showLink branch)也共用此 helper,確保「編輯態的 link 顯示」與\n// display mode 的視覺完全一致(SSOT)。\nfunction renderLinkAnchor(value: string, label?: string) {\n const displayText = label || formatHostname(value)\n return (\n <a\n href={value}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"block truncate min-w-0 text-primary hover:text-primary-hover hover:underline transition-colors\"\n >\n {displayText}\n </a>\n )\n}\n\n// ── Component ───────────────────────────────────────────────────────────────\n\nexport interface LinkInputProps\n // `defaultValue` 一併 Omit(2026-07-05 deep-audit A.1b):spec.md「controlled-only,不支援\n // defaultValue」宣稱的型別面機械封鎖 — 原本仍在型別 surface 且經 {...props} spread 到已\n // controlled 的 input(React dev warning)。\n extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size' | 'value' | 'defaultValue' | 'onChange'>,\n Omit<VariantProps<typeof fieldWrapperStyles>, 'mode' | 'variant'> {\n mode?: FieldMode\n /**\n * Visual chrome(2026-05-05 Phase B3)。對齊 FieldContext.variant 透傳。\n * - `'default'`(預設)— Field wrapper 完整 chrome(form / Field 內嵌)。\n * - `'bare'` — 透明 variant,hover/focus 才現 border(Toolbar inline edit / DataTable cell-as-input)。\n *\n * mode='display' 時 chrome 無視覺意義(display 完全無 wrapper);chrome 僅作用於 edit / readonly / disabled。\n */\n variant?: FieldVariant\n error?: boolean\n value?: string | null\n onChange?: (value: string) => void\n placeholder?: string\n className?: string\n disabled?: boolean\n /** 自訂顯示文字(非編輯時) */\n label?: string\n /**\n * Display 是否包 Field naked wrapper(D-path opt-in,2026-05-08)\n * — DataTable cell display↔edit 像素級對齊用。預設 false(裸 anchor,backward compat)。\n * 設 true 時 display 走 fieldWrapperStyles(naked variant)包覆 anchor,\n * 與 cell edit (`<Input naked>`) 同 DOM 結構,消除 Layer-B padding mismatch。\n * **本元件 edit 無 endIcon(UrlCell 用 plain Input edit)→ display 也無 ItemSuffix**(僅 wrapper)。\n */\n showDisplayEndIcon?: boolean\n}\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\nconst LinkInput = React.forwardRef<HTMLInputElement, LinkInputProps>(\n (\n {\n mode: modeProp,\n variant: variantProp,\n error: errorProp = false,\n size: sizeProp,\n value,\n onChange,\n placeholder = 'https://',\n className,\n disabled: disabledProp,\n label,\n showDisplayEndIcon = false,\n readOnly,\n id: idProp,\n 'aria-describedby': ariaDescribedByProp,\n 'aria-errormessage': ariaErrorMessageProp,\n ...props\n },\n ref\n ) => {\n const fieldCtx = useFieldContext()\n const size = useResolvedFieldSize(sizeProp)\n const disabled = useResolvedFieldDisabled(disabledProp)\n // 2026-06-08 SSOT:mode 經 useResolvedFieldMode 統一解析(prop > 有效 disabled > fieldCtx.mode > readOnly > 'edit')。\n // spec field-controls.spec.md L125「readOnly 原生屬性自動覆蓋 mode」契約落地。\n const resolvedMode: FieldMode = useResolvedFieldMode({ mode: modeProp, disabled, readOnly })\n const isEditable = resolvedMode === 'edit'\n // chrome resolution:per-prop > context > 'default'\n const resolvedVariant: FieldVariant = useResolvedFieldVariant(variantProp)\n\n // 2026-07-05 D4:invalid 經 useResolvedFieldInvalid 統一接 fieldCtx.invalid(field-controls.spec.md\n // resolver 契約「error/invalid = prop OR fieldCtx.invalid」)— 原本紅框只吃 errorProp||localError、\n // aria-invalid 卻 inline 加 fieldCtx?.invalid → <Field invalid> 下 AT 聽到 invalid 視覺卻正常框\n // (聚焦還變藍)。紅框與 aria 自此同源。宣告於 display early return 之前(Rules-of-Hooks)。\n const fieldInvalid = useResolvedFieldInvalid(errorProp)\n\n // ── Local state / refs ──────────────────────────────────────────────────\n // 2026-07-04 Rules-of-Hooks 修:hooks 必在 display early return 之前宣告,\n // 否則 mode 於 render 間切換 display↔edit 會改變 hook 呼叫數 → React #310 crash\n // (同 beta.76 Combobox 同款修法;僅搬位置,hook 邏輯不變)。\n const [editing, setEditing] = React.useState(false)\n const [localValue, setLocalValue] = React.useState(value ?? '')\n const [localError, setLocalError] = React.useState(false)\n const inputRef = React.useRef<HTMLInputElement | null>(null)\n\n // Sync external value → local\n React.useEffect(() => {\n if (!editing) setLocalValue(value ?? '')\n }, [value, editing])\n\n // Merge refs\n const setRef = React.useCallback((el: HTMLInputElement | null) => {\n inputRef.current = el\n if (typeof ref === 'function') ref(el)\n else if (ref) (ref as React.MutableRefObject<HTMLInputElement | null>).current = el\n }, [ref])\n\n // 2026-05-16 audit codex Round 6:capture rAF + cancel on unmount(defensive hygiene)\n const focusRafIdRef = React.useRef<number>(0)\n React.useEffect(() => () => { if (focusRafIdRef.current) cancelAnimationFrame(focusRafIdRef.current) }, [])\n\n // ── mode='display' ─────────────────────────────────────────────────────\n // 純展示:無 input chrome / 無 hover affordance / 無 Pencil edit 入口。\n // 取代既有 LinkInputDisplay sub-component(2026-05-05 Phase B3 retire)。\n // Default(showDisplayEndIcon=false):無 wrapper 裸 anchor — backward compat。\n // Opt-in(showDisplayEndIcon=true,2026-05-08 D-path):Field naked wrapper 包覆 anchor,\n // 與 cell edit (`<Input naked>`) 同 DOM 結構消除像素偏移(無 ItemSuffix,因 edit 也無 endIcon)。\n if (resolvedMode === 'display') {\n if (!showDisplayEndIcon) {\n // 2026-05-14 I2 fix(spec contract (e) display typography canonical):非 D-path bare\n // anchor / span 必套 `fieldDisplayTextClass(size)`(sm/md→text-body,lg→text-body-lg)\n // — 對齊跨 Field family display 視覺尺寸統一。原無 font-size class → 用 browser default\n // 字體 → 跟其他 Field display 不一致(user 抓 I2)。truncate 同需,長 URL ellipsis(I1)。\n if (!value) return <span className={cn(fieldDisplayTextClass(size), 'text-fg-muted block truncate')}>{EMPTY_DISPLAY}</span>\n return <span className={cn(fieldDisplayTextClass(size), 'block truncate')}>{renderLinkAnchor(value, label)}</span>\n }\n return (\n <div\n className={cn(fieldWrapperStyles({ mode: 'display', variant: resolvedVariant, size }), className)}\n data-field-mode=\"display\"\n >\n <span className=\"flex-1 min-w-0 truncate\">\n {value\n ? renderLinkAnchor(value, label)\n : <span className=\"text-fg-muted\">{EMPTY_DISPLAY}</span>\n }\n </span>\n </div>\n )\n }\n\n const hasValidValue = !!value && isValidUrl(value)\n const showLink = isEditable && hasValidValue && !editing && !localError\n const error = fieldInvalid || localError\n\n const handleEdit = () => {\n setEditing(true)\n if (focusRafIdRef.current) cancelAnimationFrame(focusRafIdRef.current)\n focusRafIdRef.current = requestAnimationFrame(() => {\n focusRafIdRef.current = 0\n inputRef.current?.focus()\n })\n }\n\n const handleBlur = () => {\n setEditing(false)\n const trimmed = localValue.trim()\n if (!trimmed) {\n // Empty is OK — clear value\n setLocalError(false)\n onChange?.('')\n return\n }\n if (isValidUrl(trimmed)) {\n setLocalError(false)\n onChange?.(trimmed)\n } else {\n setLocalError(true)\n }\n }\n\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n setLocalValue(e.target.value)\n // Clear error on edit (blur validation)\n if (localError) setLocalError(false)\n }\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (e.key === 'Enter') inputRef.current?.blur()\n if (e.key === 'Escape') {\n setLocalValue(value ?? '')\n setLocalError(false)\n setEditing(false)\n }\n }\n\n // readonly — 顯示藍色連結(可點擊)\n // disabled — 顯示純文字 fg-disabled(不可點擊)\n if (!isEditable) {\n const displayText = value ? (label || formatHostname(value)) : null\n return (\n <div\n className={cn(fieldWrapperStyles({ mode: resolvedMode, variant: resolvedVariant, size }), className)}\n data-field-mode={resolvedMode}\n >\n <span className=\"flex-1 min-w-0 truncate\">\n {resolvedMode === 'disabled'\n ? (displayText\n ? <span className=\"text-fg-disabled\">{displayText}</span>\n : <span className=\"text-fg-muted\">{EMPTY_DISPLAY}</span>)\n : (value\n ? renderLinkAnchor(value, label)\n : <span className=\"text-fg-muted\">{EMPTY_DISPLAY}</span>)\n }\n </span>\n </div>\n )\n }\n\n // edit — link display mode(有合法 URL 且未在編輯中)\n if (showLink) {\n return (\n <div\n // 2026-07-05 D4:link 顯示分支同樣接 error(errorProp / fieldCtx.invalid)紅框,\n // 否則 <Field invalid> 下 link state 完全無 error 呈現\n className={cn(fieldWrapperStyles({ mode: 'edit', variant: resolvedVariant, size, error }), className)}\n data-field-mode=\"edit\"\n data-error={error ? '' : undefined}\n >\n <span className=\"flex-1 min-w-0\">\n {value && renderLinkAnchor(value, label)}\n </span>\n <ItemInlineAction\n size={size ?? 'md'}\n action={{ icon: Pencil, label: '編輯連結', onClick: handleEdit }} // i18n-allow: DS default inline-action label\n />\n </div>\n )\n }\n\n // edit — text input mode(正在編輯、無值、或格式錯誤)\n return (\n <div\n className={cn(\n fieldWrapperStyles({ mode: 'edit', variant: resolvedVariant, size, error }),\n className,\n )}\n data-field-mode=\"edit\"\n data-error={error ? '' : undefined}\n >\n <input\n ref={setRef}\n type=\"url\"\n id={idProp ?? fieldCtx?.id}\n value={localValue}\n onChange={handleChange}\n onBlur={handleBlur}\n onKeyDown={handleKeyDown}\n placeholder={placeholder}\n aria-invalid={error || undefined}\n aria-required={fieldCtx?.required || undefined}\n aria-describedby={ariaDescribedByProp ?? fieldCtx?.descriptionId}\n aria-errormessage={ariaErrorMessageProp ?? (error ? fieldCtx?.errorId : undefined)}\n className={bareInputStyles}\n {...props}\n />\n </div>\n )\n }\n)\nLinkInput.displayName = 'LinkInput'\n\n// Story auto-compile metadata — Phase 1 mechanical migration(2026-04-24)\n// Phase 2 fill needed: purpose descriptions + when rationale + world-class refs\nexport const linkInputMeta = {\n component: 'LinkInput',\n family: 4,\n variants: {\n\n },\n sizes: {\n\n },\n // states 對齊真實 state 集:text-input 家族無 'active'(按下)專屬視覺態(同 textareaMeta 修法);\n // readonly / error 為 fieldWrapperStyles mode + errorProp/localError 實有狀態(2026-07-04 audit 對齊)。\n states: ['default', 'hover', 'focus-visible', 'readonly', 'disabled', 'error'],\n tokens: {\n // bg 經 fieldWrapperStyles 實渲:edit bg-surface / readonly bg-readonly / disabled bg-disabled(field-wrapper.tsx)。\n bg: ['bg-surface', 'bg-readonly', 'bg-disabled'],\n fg: ['text-fg-disabled', 'text-fg-muted', 'text-primary'],\n ring: [],\n },\n} as const\n\nexport { LinkInput }\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menu-item.d.ts","sourceRoot":"","sources":["../../../src/components/Menu/menu-item.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AACjE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAG9C,OAAO,EAAU,KAAK,UAAU,EAAE,MAAM,0CAA0C,CAAA;AA0BlF,QAAA,MAAM,gBAAgB;;8EAkBrB,CAAA;AASD,MAAM,WAAW,aACf,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,UAAU,CAAC,EAC5D,YAAY,CAAC,OAAO,gBAAgB,CAAC;IACvC,eAAe;IACf,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB,0BAA0B;IAC1B,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC7B,sCAAsC;IACtC,SAAS,CAAC,EAAE,UAAU,CAAA;IACtB,sDAAsD;IACtD,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB,6BAA6B;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,oBAAoB;IACpB,OAAO,CAAC,EAAE,OAAO,GAAG,eAAe,CAAA;IACnC,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,6BAA6B;IAC7B,GAAG,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACrB,mEAAmE;IACnE,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC5B,SAAS;IACT,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,uCAAuC;IACvC,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB;;;;;;;;;;OAUG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IAC/B;;;;;;;;;;;OAWG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;CAC/B;AAeD,QAAA,MAAM,QAAQ,
|
|
1
|
+
{"version":3,"file":"menu-item.d.ts","sourceRoot":"","sources":["../../../src/components/Menu/menu-item.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AACjE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAG9C,OAAO,EAAU,KAAK,UAAU,EAAE,MAAM,0CAA0C,CAAA;AA0BlF,QAAA,MAAM,gBAAgB;;8EAkBrB,CAAA;AASD,MAAM,WAAW,aACf,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,UAAU,CAAC,EAC5D,YAAY,CAAC,OAAO,gBAAgB,CAAC;IACvC,eAAe;IACf,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB,0BAA0B;IAC1B,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC7B,sCAAsC;IACtC,SAAS,CAAC,EAAE,UAAU,CAAA;IACtB,sDAAsD;IACtD,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB,6BAA6B;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,oBAAoB;IACpB,OAAO,CAAC,EAAE,OAAO,GAAG,eAAe,CAAA;IACnC,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,6BAA6B;IAC7B,GAAG,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACrB,mEAAmE;IACnE,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC5B,SAAS;IACT,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,uCAAuC;IACvC,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB;;;;;;;;;;OAUG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IAC/B;;;;;;;;;;;OAWG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;CAC/B;AAeD,QAAA,MAAM,QAAQ,sFAuJb,CAAA;AAeD,MAAM,WAAW,cAAe,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IAC1E,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC1B;AAED,QAAA,MAAM,SAAS,uFAWd,CAAA;AAKD,MAAM,WAAW,eAAgB,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IAC3E,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC1B;AAED,QAAA,MAAM,UAAU,wFAUf,CAAA;AAID,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;CAef,CAAA;AAEV,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,EAAE,gBAAgB,EAAE,CAAA"}
|
|
@@ -78,13 +78,14 @@ const MenuItem = React.forwardRef(
|
|
|
78
78
|
}
|
|
79
79
|
);
|
|
80
80
|
}
|
|
81
|
+
const isPresentation = props.role === "presentation";
|
|
81
82
|
return /* @__PURE__ */ jsxs(
|
|
82
83
|
"div",
|
|
83
84
|
{
|
|
84
85
|
ref,
|
|
85
|
-
role: "option",
|
|
86
|
-
"aria-selected": selected || checked === true || void 0,
|
|
87
|
-
"aria-disabled": disabled || void 0,
|
|
86
|
+
role: isPresentation ? "presentation" : "option",
|
|
87
|
+
"aria-selected": isPresentation ? void 0 : selected || checked === true || void 0,
|
|
88
|
+
"aria-disabled": isPresentation ? void 0 : disabled || void 0,
|
|
88
89
|
"data-selected": selected ? "" : void 0,
|
|
89
90
|
"data-disabled": disabled ? "" : void 0,
|
|
90
91
|
className: cn(
|
|
@@ -96,9 +97,9 @@ const MenuItem = React.forwardRef(
|
|
|
96
97
|
disabled && "text-fg-disabled cursor-not-allowed",
|
|
97
98
|
className
|
|
98
99
|
),
|
|
100
|
+
...Object.fromEntries(Object.entries(props).filter(([k]) => k !== "onClick" && k !== "onKeyDown")),
|
|
99
101
|
onClick: disabled ? void 0 : props.onClick,
|
|
100
102
|
onKeyDown: disabled ? void 0 : props.onKeyDown,
|
|
101
|
-
...Object.fromEntries(Object.entries(props).filter(([k]) => k !== "onClick" && k !== "onKeyDown")),
|
|
102
103
|
children: [
|
|
103
104
|
hasPrefix && /* @__PURE__ */ jsxs("div", { className: cn(itemPrefixAlignVariants({ align: prefixAlign })), children: [
|
|
104
105
|
checkbox && /* @__PURE__ */ jsx(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menu-item.js","sources":["../../../src/components/Menu/menu-item.tsx"],"sourcesContent":["/**\n * @internal — DS-internal 單元(per `.claude/rules/ui-development.md` Public vs Internal canonical;spec frontmatter `isInternal`)。\n * 不進 root barrel front-door;由 DropdownMenu / SelectMenu 等 menu 類 DS 元件 wrap 消費,end-user app 請用 wrapper 元件。\n */\n// @benchmark-unverified-blanket: file-level retraction per M22 (d) — claims herein not individually URL-cited; treat as unverified visual/usage rumor unless retrofit per-claim. Hook escape preserved.\nimport * as React from 'react'\nimport { cva, type VariantProps } from 'class-variance-authority'\nimport type { LucideIcon } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { Checkbox } from '@/design-system/components/Checkbox/checkbox'\nimport { Avatar, type AvatarData } from '@/design-system/components/Avatar/avatar'\n// Row primitive 共用常數——統一從 item-layout pattern module 引入\nimport { ICON_SIZE, AVATAR_SIZE, ItemContent, itemPrefixAlignVariants, ROW_PADDING_BY_SIZE } from '@/design-system/patterns/element-anatomy/item-anatomy'\n\n/**\n * MenuItem — 所有 menu 類元件的共用視覺佈局層\n *\n * SelectMenu、DropdownMenu、未來的 ContextMenu 等都消費這個元件。\n * 它只負責 layout(padding、gap、prefix alignment、typography),\n * 互動行為由各 menu 的 Radix primitive 外層控制。\n *\n * ── 結構 ──\n * [checkbox?] [startIcon? | avatar?] [label + description?]\n *\n * ── Prefix 對齊規則(24px 閾值)──\n * prefix ≤ 24px → h-[1lh],對齊第一行 label\n * prefix > 24px → h-[calc(1lh+var(--item-gap-label-desc-scanning)+desc_1lh)](size-aware -scanning[-lg] 變體),對齊文字區塊\n * 無 description → prefix 上限 24px\n *\n * ── Size ──\n * sm / md / lg — 單行高度對齊 field-height token\n */\n\nconst CHECKBOX_SIZE: Record<string, 'sm' | 'md' | 'lg'> = { sm: 'sm', md: 'md', lg: 'lg' }\n\n// ── Item variants ──\nconst menuItemVariants = cva(\n [\n 'flex items-start gap-2 px-3 w-full',\n 'cursor-pointer select-none',\n 'transition-colors duration-150',\n 'outline-none',\n 'focus-visible:bg-neutral-hover',\n ],\n {\n variants: {\n // 消費 ROW_PADDING_BY_SIZE SSOT(item-anatomy.tsx 導出)\n // 改一處 → 全 row primitive 自動同步(消除前 SidebarMenuButton 獨立實作 risk)\n size: ROW_PADDING_BY_SIZE,\n },\n defaultVariants: {\n size: 'md',\n },\n }\n)\n\n// ── Prefix alignment container ──\n// 消費 `itemPrefixAlignVariants`(SSOT from patterns/element-anatomy/item-anatomy.tsx)\n// 原本 MenuItem 自管 cva 造成 drift 風險(block formula 改動需同步);改為共用 SSOT\n// 讓公式只有一份,改 item-anatomy 一處 → MenuItem 自動同步。\n\n// ── Component ──\n\nexport interface MenuItemProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, 'children'>,\n VariantProps<typeof menuItemVariants> {\n /** Label 文字 */\n children: React.ReactNode\n /** 次要說明文字,顯示在 label 下方 */\n description?: React.ReactNode\n /** 左側 icon(LucideIcon),與 avatar 互斥 */\n startIcon?: LucideIcon\n /** 左側頭像資料(AvatarData),元件內部渲染 Avatar。與 startIcon 互斥 */\n avatar?: AvatarData\n /** 顯示 checkbox(多選模式由父層控制) */\n checkbox?: boolean\n /** Checkbox 選中狀態 */\n checked?: boolean | 'indeterminate'\n /** 單選選中(bg-neutral-selected 背景高亮,持續選中狀態) */\n selected?: boolean\n /** 後綴 Tag(ReactNode),靠右對齊 */\n tag?: React.ReactNode\n /** 後綴自訂內容(ReactNode),用於 DropdownMenu 的 badge/endIcon/shortcut 等 */\n endContent?: React.ReactNode\n /** 停用 */\n disabled?: boolean\n /** 作為群組標題(不可選,font-medium,fg-muted) */\n header?: boolean\n /**\n * Label 最大行數(line-clamp 截斷,超過顯示 ellipsis)。\n *\n * - `undefined`(預設 prop 值未傳)→ 套用元件預設 `1`(單行截斷,符合選單快速掃視需求)\n * - 數字(1-6)→ 截斷到該行數;>6 超出 line-clamp utility 支援範圍 → 不截斷(同 'none',見 lineClampClass)\n * - `'none'` → **明確**不截斷,自然 wrap 任意行數\n *\n * 為什麼用 `'none'` 而不是 `undefined` 表達不截斷?React props 的 destructure default\n * 在 `undefined` 時會接管,所以 `<MenuItem labelMaxLines={undefined}>` 等同沒傳,\n * 會 fallback 到預設 `1`。要明確覆寫成「不截斷」,必須用一個非 undefined 的 sentinel。\n */\n labelMaxLines?: number | 'none'\n /**\n * Description 最大行數。\n *\n * - `undefined`(預設 prop 值未傳)→ 套用元件預設 `1`(跟 label 對稱,維持掃視節奏)\n * - 數字(1-6)→ 截斷到該行數;>6 超出 line-clamp utility 支援範圍 → 不截斷(同 'none',見 lineClampClass)\n * - `'none'` → 明確不截斷\n *\n * 為什麼預設 1?Menu 的設計目的是「快速掃視多個選項挑一個」,垂直空間是\n * 寶貴的——一個過高的 item 會破壞 row rhythm,讓使用者眼睛重新校準。description\n * 跟 label 對稱地截到 1 行,確保所有 item 高度一致(無 desc / 有 desc 兩種高度)。\n * Consumer 若有合理理由要 2 行 description,可顯式 override。\n */\n descMaxLines?: number | 'none'\n}\n\n/** 把 maxLines 轉成 line-clamp class;'none' / 0 → 空字串 */\nfunction lineClampClass(maxLines: number | 'none'): string {\n if (maxLines === 'none' || !maxLines) return ''\n if (maxLines === 1) return 'line-clamp-1'\n if (maxLines === 2) return 'line-clamp-2'\n if (maxLines === 3) return 'line-clamp-3'\n if (maxLines === 4) return 'line-clamp-4'\n if (maxLines === 5) return 'line-clamp-5'\n if (maxLines === 6) return 'line-clamp-6'\n return ''\n}\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\nconst MenuItem = React.forwardRef<HTMLDivElement, MenuItemProps>(\n (\n {\n children,\n description,\n startIcon: StartIcon,\n avatar,\n checkbox,\n checked,\n selected,\n tag,\n endContent,\n disabled,\n header,\n size,\n labelMaxLines = 1,\n descMaxLines = 1,\n className,\n ...props\n },\n ref\n ) => {\n const sizeKey = size ?? 'md'\n const labelClampClass = lineClampClass(labelMaxLines)\n const descClampClass = lineClampClass(descMaxLines)\n const iconPx = ICON_SIZE[sizeKey]\n\n // ── 決定 avatar 容器尺寸 + 對齊模式 ──\n // 有 description → 使用 block 尺寸(32/40px),block 對齊\n // 無 description → 使用 inline 尺寸(20/24px),inline 對齊\n const avatarPx = avatar\n ? (description ? AVATAR_SIZE.block[sizeKey] : AVATAR_SIZE.inline[sizeKey])\n : 0\n const isBlockAlign = avatarPx > 24 && !!description\n\n const prefixAlign = isBlockAlign\n ? (`block-${sizeKey}` as const)\n : 'inline'\n\n const hasPrefix = !!StartIcon || !!avatar || checkbox\n\n // ── Header variant ──\n if (header) {\n return (\n <div\n ref={ref}\n className={cn(\n menuItemVariants({ size }),\n 'font-medium text-fg-muted cursor-default pointer-events-none',\n className,\n )}\n role=\"presentation\"\n {...props}\n >\n {children}\n </div>\n )\n }\n\n return (\n <div\n ref={ref}\n role=\"option\"\n aria-selected={selected || (checked === true) || undefined}\n aria-disabled={disabled || undefined}\n data-selected={selected ? '' : undefined}\n data-disabled={disabled ? '' : undefined}\n className={cn(\n menuItemVariants({ size }),\n !disabled && !selected && 'hover:bg-neutral-hover',\n !disabled && selected && 'bg-neutral-selected',\n // disabled 用 cursor-not-allowed(對齊 Button + Material/Polaris/Atlassian);\n // pointer-events-none 會讓 cursor 失效,改用 aria-disabled + onClick guard\n disabled && 'text-fg-disabled cursor-not-allowed',\n className,\n )}\n onClick={disabled ? undefined : props.onClick}\n onKeyDown={disabled ? undefined : props.onKeyDown}\n {...Object.fromEntries(Object.entries(props).filter(([k]) => k !== 'onClick' && k !== 'onKeyDown'))}\n >\n {/* Prefix 對齊容器 */}\n {hasPrefix && (\n <div className={cn(itemPrefixAlignVariants({ align: prefixAlign }))}>\n {checkbox && (\n <Checkbox\n size={CHECKBOX_SIZE[sizeKey]}\n checked={checked === true ? true : checked === 'indeterminate' ? 'indeterminate' : false}\n disabled={disabled}\n tabIndex={-1}\n className=\"pointer-events-none\"\n />\n )}\n {StartIcon && (\n <StartIcon\n data-prefix-type=\"icon\"\n size={iconPx}\n className={cn('shrink-0', disabled && 'text-fg-disabled')}\n aria-hidden\n />\n )}\n {avatar && (\n <Avatar\n data-prefix-type=\"avatar\"\n src={avatar.src}\n alt={avatar.alt}\n color={avatar.color}\n hoverCard={avatar.hoverCard}\n size={avatarPx}\n />\n )}\n </div>\n )}\n\n {/* Content — 消費 ItemContent primitive(SSOT)。\n scanning mode 跟隨 size:sm/md = caption,lg = body(desc 比 label lg 16 小 1 tier)。\n labelClampClass / descClampClass 透過 className escape hatch 傳入(MenuItem 特化 labelMaxLines / descMaxLines 語意)。 */}\n <ItemContent\n label={children}\n description={description}\n mode=\"scanning\"\n size={sizeKey === 'lg' ? 'lg' : 'md'}\n descriptionTone={disabled ? 'disabled' : 'secondary'}\n labelTruncate={false}\n labelClassName={cn(labelClampClass || 'break-words', disabled && 'text-fg-disabled')}\n descriptionClassName={cn(descClampClass || 'break-words')}\n />\n\n {(tag || endContent) && (\n // @row-slot-handcraft-allow: MenuItem 自身即 row primitive(item-anatomy Family 1 owner),此 tag/endContent 容器是 MenuItem 自有 suffix slot 實作,非外部 consumer hand-craft → 不再 wrap ItemSuffix\n <div className={cn(\n 'flex items-center gap-2 shrink-0 h-[1lh] ml-auto',\n disabled && 'opacity-disabled',\n )}>\n {tag}\n {endContent}\n </div>\n )}\n </div>\n )\n }\n)\nMenuItem.displayName = 'MenuItem'\n\n// ── Group ──\n// MenuGroup — Menu-like group primitive(跨元件設計語言統一,SSOT 見\n// `patterns/element-anatomy/item-anatomy.spec.md`「Group auto-separation」)\n//\n// 設計語言:每個 group 上下 8px + 相鄰 group 間 border-divider + 視覺 gap 16px\n//\n// 本元件用 Pattern A:`py-2 [&+&]:border-t [&+&]:border-divider`\n// (適用外層容器無 py-2 的情境,例 Command.List——Group 自帶邊界 padding)\n//\n// **視覺必須跟 DropdownMenuGroup 等價**(見 dropdown-menu.tsx Pattern B 對照)。\n// 改動前先讀 item-anatomy.spec.md「Group auto-separation」確認視覺不漂移。\n\nexport interface MenuGroupProps extends React.HTMLAttributes<HTMLDivElement> {\n children: React.ReactNode\n}\n\nconst MenuGroup = React.forwardRef<HTMLDivElement, MenuGroupProps>(\n ({ children, className, ...props }, ref) => (\n <div\n ref={ref}\n role=\"group\"\n className={cn('py-2 [&+&]:border-t [&+&]:border-divider', className)}\n {...props}\n >\n {children}\n </div>\n )\n)\nMenuGroup.displayName = 'MenuGroup'\n\n// ── Footer ──\n\nexport interface MenuFooterProps extends React.HTMLAttributes<HTMLDivElement> {\n children: React.ReactNode\n}\n\nconst MenuFooter = React.forwardRef<HTMLDivElement, MenuFooterProps>(\n ({ children, className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn('py-2 border-t border-divider', className)}\n {...props}\n >\n {children}\n </div>\n )\n)\nMenuFooter.displayName = 'MenuFooter'\n\n// Story auto-compile metadata — Phase 1+2 migration\nexport const menuItemMeta = {\n component: 'MenuItem',\n family: 1,\n variants: {},\n sizes: {\n sm: {},\n md: {},\n lg: {},\n },\n defaultSize: 'md',\n states: ['default', 'hover', 'selected', 'focus-visible', 'disabled'],\n tokens: {\n bg: ['bg-neutral-hover', 'bg-neutral-selected'],\n fg: ['text-fg-muted', 'text-fg-disabled'],\n },\n} as const\n\nexport { MenuItem, MenuGroup, MenuFooter, menuItemVariants }\n"],"names":[],"mappings":";;;;;;;AAiCA,MAAM,gBAAoD,EAAE,IAAI,MAAM,IAAI,MAAM,IAAI,KAAA;AAGpF,MAAM,mBAAmB;AAAA,EACvB;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAAA,EAEF;AAAA,IACE,UAAU;AAAA;AAAA;AAAA,MAGR,MAAM;AAAA,IAAA;AAAA,IAER,iBAAiB;AAAA,MACf,MAAM;AAAA,IAAA;AAAA,EACR;AAEJ;AA8DA,SAAS,eAAe,UAAmC;AACzD,MAAI,aAAa,UAAU,CAAC,SAAU,QAAO;AAC7C,MAAI,aAAa,EAAG,QAAO;AAC3B,MAAI,aAAa,EAAG,QAAO;AAC3B,MAAI,aAAa,EAAG,QAAO;AAC3B,MAAI,aAAa,EAAG,QAAO;AAC3B,MAAI,aAAa,EAAG,QAAO;AAC3B,MAAI,aAAa,EAAG,QAAO;AAC3B,SAAO;AACT;AAGA,MAAM,WAAW,MAAM;AAAA,EACrB,CACE;AAAA,IACE;AAAA,IACA;AAAA,IACA,WAAW;AAAA,IACX;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,gBAAgB;AAAA,IAChB,eAAe;AAAA,IACf;AAAA,IACA,GAAG;AAAA,EAAA,GAEL,QACG;AACH,UAAM,UAAU,QAAQ;AACxB,UAAM,kBAAkB,eAAe,aAAa;AACpD,UAAM,iBAAiB,eAAe,YAAY;AAClD,UAAM,SAAS,UAAU,OAAO;AAKhC,UAAM,WAAW,SACZ,cAAc,YAAY,MAAM,OAAO,IAAI,YAAY,OAAO,OAAO,IACtE;AACJ,UAAM,eAAe,WAAW,MAAM,CAAC,CAAC;AAExC,UAAM,cAAc,eACf,SAAS,OAAO,KACjB;AAEJ,UAAM,YAAY,CAAC,CAAC,aAAa,CAAC,CAAC,UAAU;AAG7C,QAAI,QAAQ;AACV,aACE;AAAA,QAAC;AAAA,QAAA;AAAA,UACC;AAAA,UACA,WAAW;AAAA,YACT,iBAAiB,EAAE,MAAM;AAAA,YACzB;AAAA,YACA;AAAA,UAAA;AAAA,UAEF,MAAK;AAAA,UACJ,GAAG;AAAA,UAEH;AAAA,QAAA;AAAA,MAAA;AAAA,IAGP;AAEA,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,MAAK;AAAA,QACL,iBAAe,YAAa,YAAY,QAAS;AAAA,QACjD,iBAAe,YAAY;AAAA,QAC3B,iBAAe,WAAW,KAAK;AAAA,QAC/B,iBAAe,WAAW,KAAK;AAAA,QAC/B,WAAW;AAAA,UACT,iBAAiB,EAAE,MAAM;AAAA,UACzB,CAAC,YAAY,CAAC,YAAY;AAAA,UAC1B,CAAC,YAAY,YAAY;AAAA;AAAA;AAAA,UAGzB,YAAY;AAAA,UACZ;AAAA,QAAA;AAAA,QAEF,SAAS,WAAW,SAAY,MAAM;AAAA,QACtC,WAAW,WAAW,SAAY,MAAM;AAAA,QACvC,GAAG,OAAO,YAAY,OAAO,QAAQ,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,MAAM,MAAM,aAAa,MAAM,WAAW,CAAC;AAAA,QAGjG,UAAA;AAAA,UAAA,aACC,qBAAC,OAAA,EAAI,WAAW,GAAG,wBAAwB,EAAE,OAAO,aAAa,CAAC,GAC/D,UAAA;AAAA,YAAA,YACC;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,MAAM,cAAc,OAAO;AAAA,gBAC3B,SAAS,YAAY,OAAO,OAAO,YAAY,kBAAkB,kBAAkB;AAAA,gBACnF;AAAA,gBACA,UAAU;AAAA,gBACV,WAAU;AAAA,cAAA;AAAA,YAAA;AAAA,YAGb,aACC;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,oBAAiB;AAAA,gBACjB,MAAM;AAAA,gBACN,WAAW,GAAG,YAAY,YAAY,kBAAkB;AAAA,gBACxD,eAAW;AAAA,cAAA;AAAA,YAAA;AAAA,YAGd,UACC;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,oBAAiB;AAAA,gBACjB,KAAK,OAAO;AAAA,gBACZ,KAAK,OAAO;AAAA,gBACZ,OAAO,OAAO;AAAA,gBACd,WAAW,OAAO;AAAA,gBAClB,MAAM;AAAA,cAAA;AAAA,YAAA;AAAA,UACR,GAEJ;AAAA,UAMF;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,OAAO;AAAA,cACP;AAAA,cACA,MAAK;AAAA,cACL,MAAM,YAAY,OAAO,OAAO;AAAA,cAChC,iBAAiB,WAAW,aAAa;AAAA,cACzC,eAAe;AAAA,cACf,gBAAgB,GAAG,mBAAmB,eAAe,YAAY,kBAAkB;AAAA,cACnF,sBAAsB,GAAG,kBAAkB,aAAa;AAAA,YAAA;AAAA,UAAA;AAAA,WAGxD,OAAO;AAAA,UAEP,qBAAC,SAAI,WAAW;AAAA,YACd;AAAA,YACA,YAAY;AAAA,UAAA,GAEX,UAAA;AAAA,YAAA;AAAA,YACA;AAAA,UAAA,EAAA,CACH;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAIR;AACF;AACA,SAAS,cAAc;AAkBvB,MAAM,YAAY,MAAM;AAAA,EACtB,CAAC,EAAE,UAAU,WAAW,GAAG,MAAA,GAAS,QAClC;AAAA,IAAC;AAAA,IAAA;AAAA,MACC;AAAA,MACA,MAAK;AAAA,MACL,WAAW,GAAG,4CAA4C,SAAS;AAAA,MAClE,GAAG;AAAA,MAEH;AAAA,IAAA;AAAA,EAAA;AAGP;AACA,UAAU,cAAc;AAQxB,MAAM,aAAa,MAAM;AAAA,EACvB,CAAC,EAAE,UAAU,WAAW,GAAG,MAAA,GAAS,QAClC;AAAA,IAAC;AAAA,IAAA;AAAA,MACC;AAAA,MACA,WAAW,GAAG,gCAAgC,SAAS;AAAA,MACtD,GAAG;AAAA,MAEH;AAAA,IAAA;AAAA,EAAA;AAGP;AACA,WAAW,cAAc;AAGlB,MAAM,eAAe;AAAA,EAC1B,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,UAAU,CAAA;AAAA,EACV,OAAO;AAAA,IACL,IAAI,CAAA;AAAA,IACJ,IAAI,CAAA;AAAA,IACJ,IAAI,CAAA;AAAA,EAAC;AAAA,EAEP,aAAa;AAAA,EACb,QAAQ,CAAC,WAAW,SAAS,YAAY,iBAAiB,UAAU;AAAA,EACpE,QAAQ;AAAA,IACN,IAAI,CAAC,oBAAoB,qBAAqB;AAAA,IAC9C,IAAI,CAAC,iBAAiB,kBAAkB;AAAA,EAAA;AAE5C;"}
|
|
1
|
+
{"version":3,"file":"menu-item.js","sources":["../../../src/components/Menu/menu-item.tsx"],"sourcesContent":["/**\n * @internal — DS-internal 單元(per `.claude/rules/ui-development.md` Public vs Internal canonical;spec frontmatter `isInternal`)。\n * 不進 root barrel front-door;由 DropdownMenu / SelectMenu 等 menu 類 DS 元件 wrap 消費,end-user app 請用 wrapper 元件。\n */\n// @benchmark-unverified-blanket: file-level retraction per M22 (d) — claims herein not individually URL-cited; treat as unverified visual/usage rumor unless retrofit per-claim. Hook escape preserved.\nimport * as React from 'react'\nimport { cva, type VariantProps } from 'class-variance-authority'\nimport type { LucideIcon } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { Checkbox } from '@/design-system/components/Checkbox/checkbox'\nimport { Avatar, type AvatarData } from '@/design-system/components/Avatar/avatar'\n// Row primitive 共用常數——統一從 item-layout pattern module 引入\nimport { ICON_SIZE, AVATAR_SIZE, ItemContent, itemPrefixAlignVariants, ROW_PADDING_BY_SIZE } from '@/design-system/patterns/element-anatomy/item-anatomy'\n\n/**\n * MenuItem — 所有 menu 類元件的共用視覺佈局層\n *\n * SelectMenu、DropdownMenu、未來的 ContextMenu 等都消費這個元件。\n * 它只負責 layout(padding、gap、prefix alignment、typography),\n * 互動行為由各 menu 的 Radix primitive 外層控制。\n *\n * ── 結構 ──\n * [checkbox?] [startIcon? | avatar?] [label + description?]\n *\n * ── Prefix 對齊規則(24px 閾值)──\n * prefix ≤ 24px → h-[1lh],對齊第一行 label\n * prefix > 24px → h-[calc(1lh+var(--item-gap-label-desc-scanning)+desc_1lh)](size-aware -scanning[-lg] 變體),對齊文字區塊\n * 無 description → prefix 上限 24px\n *\n * ── Size ──\n * sm / md / lg — 單行高度對齊 field-height token\n */\n\nconst CHECKBOX_SIZE: Record<string, 'sm' | 'md' | 'lg'> = { sm: 'sm', md: 'md', lg: 'lg' }\n\n// ── Item variants ──\nconst menuItemVariants = cva(\n [\n 'flex items-start gap-2 px-3 w-full',\n 'cursor-pointer select-none',\n 'transition-colors duration-150',\n 'outline-none',\n 'focus-visible:bg-neutral-hover',\n ],\n {\n variants: {\n // 消費 ROW_PADDING_BY_SIZE SSOT(item-anatomy.tsx 導出)\n // 改一處 → 全 row primitive 自動同步(消除前 SidebarMenuButton 獨立實作 risk)\n size: ROW_PADDING_BY_SIZE,\n },\n defaultVariants: {\n size: 'md',\n },\n }\n)\n\n// ── Prefix alignment container ──\n// 消費 `itemPrefixAlignVariants`(SSOT from patterns/element-anatomy/item-anatomy.tsx)\n// 原本 MenuItem 自管 cva 造成 drift 風險(block formula 改動需同步);改為共用 SSOT\n// 讓公式只有一份,改 item-anatomy 一處 → MenuItem 自動同步。\n\n// ── Component ──\n\nexport interface MenuItemProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, 'children'>,\n VariantProps<typeof menuItemVariants> {\n /** Label 文字 */\n children: React.ReactNode\n /** 次要說明文字,顯示在 label 下方 */\n description?: React.ReactNode\n /** 左側 icon(LucideIcon),與 avatar 互斥 */\n startIcon?: LucideIcon\n /** 左側頭像資料(AvatarData),元件內部渲染 Avatar。與 startIcon 互斥 */\n avatar?: AvatarData\n /** 顯示 checkbox(多選模式由父層控制) */\n checkbox?: boolean\n /** Checkbox 選中狀態 */\n checked?: boolean | 'indeterminate'\n /** 單選選中(bg-neutral-selected 背景高亮,持續選中狀態) */\n selected?: boolean\n /** 後綴 Tag(ReactNode),靠右對齊 */\n tag?: React.ReactNode\n /** 後綴自訂內容(ReactNode),用於 DropdownMenu 的 badge/endIcon/shortcut 等 */\n endContent?: React.ReactNode\n /** 停用 */\n disabled?: boolean\n /** 作為群組標題(不可選,font-medium,fg-muted) */\n header?: boolean\n /**\n * Label 最大行數(line-clamp 截斷,超過顯示 ellipsis)。\n *\n * - `undefined`(預設 prop 值未傳)→ 套用元件預設 `1`(單行截斷,符合選單快速掃視需求)\n * - 數字(1-6)→ 截斷到該行數;>6 超出 line-clamp utility 支援範圍 → 不截斷(同 'none',見 lineClampClass)\n * - `'none'` → **明確**不截斷,自然 wrap 任意行數\n *\n * 為什麼用 `'none'` 而不是 `undefined` 表達不截斷?React props 的 destructure default\n * 在 `undefined` 時會接管,所以 `<MenuItem labelMaxLines={undefined}>` 等同沒傳,\n * 會 fallback 到預設 `1`。要明確覆寫成「不截斷」,必須用一個非 undefined 的 sentinel。\n */\n labelMaxLines?: number | 'none'\n /**\n * Description 最大行數。\n *\n * - `undefined`(預設 prop 值未傳)→ 套用元件預設 `1`(跟 label 對稱,維持掃視節奏)\n * - 數字(1-6)→ 截斷到該行數;>6 超出 line-clamp utility 支援範圍 → 不截斷(同 'none',見 lineClampClass)\n * - `'none'` → 明確不截斷\n *\n * 為什麼預設 1?Menu 的設計目的是「快速掃視多個選項挑一個」,垂直空間是\n * 寶貴的——一個過高的 item 會破壞 row rhythm,讓使用者眼睛重新校準。description\n * 跟 label 對稱地截到 1 行,確保所有 item 高度一致(無 desc / 有 desc 兩種高度)。\n * Consumer 若有合理理由要 2 行 description,可顯式 override。\n */\n descMaxLines?: number | 'none'\n}\n\n/** 把 maxLines 轉成 line-clamp class;'none' / 0 → 空字串 */\nfunction lineClampClass(maxLines: number | 'none'): string {\n if (maxLines === 'none' || !maxLines) return ''\n if (maxLines === 1) return 'line-clamp-1'\n if (maxLines === 2) return 'line-clamp-2'\n if (maxLines === 3) return 'line-clamp-3'\n if (maxLines === 4) return 'line-clamp-4'\n if (maxLines === 5) return 'line-clamp-5'\n if (maxLines === 6) return 'line-clamp-6'\n return ''\n}\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\nconst MenuItem = React.forwardRef<HTMLDivElement, MenuItemProps>(\n (\n {\n children,\n description,\n startIcon: StartIcon,\n avatar,\n checkbox,\n checked,\n selected,\n tag,\n endContent,\n disabled,\n header,\n size,\n labelMaxLines = 1,\n descMaxLines = 1,\n className,\n ...props\n },\n ref\n ) => {\n const sizeKey = size ?? 'md'\n const labelClampClass = lineClampClass(labelMaxLines)\n const descClampClass = lineClampClass(descMaxLines)\n const iconPx = ICON_SIZE[sizeKey]\n\n // ── 決定 avatar 容器尺寸 + 對齊模式 ──\n // 有 description → 使用 block 尺寸(32/40px),block 對齊\n // 無 description → 使用 inline 尺寸(20/24px),inline 對齊\n const avatarPx = avatar\n ? (description ? AVATAR_SIZE.block[sizeKey] : AVATAR_SIZE.inline[sizeKey])\n : 0\n const isBlockAlign = avatarPx > 24 && !!description\n\n const prefixAlign = isBlockAlign\n ? (`block-${sizeKey}` as const)\n : 'inline'\n\n const hasPrefix = !!StartIcon || !!avatar || checkbox\n\n // ── Header variant ──\n if (header) {\n return (\n <div\n ref={ref}\n className={cn(\n menuItemVariants({ size }),\n 'font-medium text-fg-muted cursor-default pointer-events-none',\n className,\n )}\n role=\"presentation\"\n {...props}\n >\n {children}\n </div>\n )\n }\n\n // ── ARIA 單一 owner gate(2026-07-05)──\n // consumer(DropdownMenu 各 item / SelectMenu option row)把 MenuItem 巢狀在 Radix / cmdk\n // 互動節點內時傳 role=\"presentation\":外層 primitive 是唯一 ARIA owner(aria-checked /\n // aria-disabled 由外層宣告)。presentation 節點不得掛 ARIA state(axe aria-prohibited-attr\n // + 內外雙重宣告)→ 同步抑制 role=\"option\" 與 aria-selected / aria-disabled 預設;\n // data-selected / data-disabled 保留(純樣式 hook,非 ARIA)。正規 listbox option 用法\n //(不傳 role)與 select-all row(role=\"checkbox\")不受影響。\n const isPresentation = props.role === 'presentation'\n\n return (\n <div\n ref={ref}\n role={isPresentation ? 'presentation' : 'option'}\n aria-selected={isPresentation ? undefined : selected || (checked === true) || undefined}\n aria-disabled={isPresentation ? undefined : disabled || undefined}\n data-selected={selected ? '' : undefined}\n data-disabled={disabled ? '' : undefined}\n className={cn(\n menuItemVariants({ size }),\n !disabled && !selected && 'hover:bg-neutral-hover',\n !disabled && selected && 'bg-neutral-selected',\n // disabled 用 cursor-not-allowed(對齊 Button + Material/Polaris/Atlassian);\n // pointer-events-none 會讓 cursor 失效,改用 aria-disabled + onClick guard\n disabled && 'text-fg-disabled cursor-not-allowed',\n className,\n )}\n // 2026-07-04 robustness:先 spread 其餘 props、後放 guarded handlers — 消除\n // 「handler 置前 + 後方 runtime filter 同名 key」對屬性順序的隱性依賴,行為不變\n {...Object.fromEntries(Object.entries(props).filter(([k]) => k !== 'onClick' && k !== 'onKeyDown'))}\n onClick={disabled ? undefined : props.onClick}\n onKeyDown={disabled ? undefined : props.onKeyDown}\n >\n {/* Prefix 對齊容器 */}\n {hasPrefix && (\n <div className={cn(itemPrefixAlignVariants({ align: prefixAlign }))}>\n {checkbox && (\n <Checkbox\n size={CHECKBOX_SIZE[sizeKey]}\n checked={checked === true ? true : checked === 'indeterminate' ? 'indeterminate' : false}\n disabled={disabled}\n tabIndex={-1}\n className=\"pointer-events-none\"\n />\n )}\n {StartIcon && (\n <StartIcon\n data-prefix-type=\"icon\"\n size={iconPx}\n className={cn('shrink-0', disabled && 'text-fg-disabled')}\n aria-hidden\n />\n )}\n {avatar && (\n <Avatar\n data-prefix-type=\"avatar\"\n src={avatar.src}\n alt={avatar.alt}\n color={avatar.color}\n hoverCard={avatar.hoverCard}\n size={avatarPx}\n />\n )}\n </div>\n )}\n\n {/* Content — 消費 ItemContent primitive(SSOT)。\n scanning mode 跟隨 size:sm/md = caption,lg = body(desc 比 label lg 16 小 1 tier)。\n labelClampClass / descClampClass 透過 className escape hatch 傳入(MenuItem 特化 labelMaxLines / descMaxLines 語意)。 */}\n <ItemContent\n label={children}\n description={description}\n mode=\"scanning\"\n size={sizeKey === 'lg' ? 'lg' : 'md'}\n descriptionTone={disabled ? 'disabled' : 'secondary'}\n labelTruncate={false}\n labelClassName={cn(labelClampClass || 'break-words', disabled && 'text-fg-disabled')}\n descriptionClassName={cn(descClampClass || 'break-words')}\n />\n\n {(tag || endContent) && (\n // @row-slot-handcraft-allow: MenuItem 自身即 row primitive(item-anatomy Family 1 owner),此 tag/endContent 容器是 MenuItem 自有 suffix slot 實作,非外部 consumer hand-craft → 不再 wrap ItemSuffix\n <div className={cn(\n 'flex items-center gap-2 shrink-0 h-[1lh] ml-auto',\n disabled && 'opacity-disabled',\n )}>\n {tag}\n {endContent}\n </div>\n )}\n </div>\n )\n }\n)\nMenuItem.displayName = 'MenuItem'\n\n// ── Group ──\n// MenuGroup — Menu-like group primitive(跨元件設計語言統一,SSOT 見\n// `patterns/element-anatomy/item-anatomy.spec.md`「Group auto-separation」)\n//\n// 設計語言:每個 group 上下 8px + 相鄰 group 間 border-divider + 視覺 gap 16px\n//\n// 本元件用 Pattern A:`py-2 [&+&]:border-t [&+&]:border-divider`\n// (適用外層容器無 py-2 的情境,例 Command.List——Group 自帶邊界 padding)\n//\n// **視覺必須跟 DropdownMenuGroup 等價**(見 dropdown-menu.tsx Pattern B 對照)。\n// 改動前先讀 item-anatomy.spec.md「Group auto-separation」確認視覺不漂移。\n\nexport interface MenuGroupProps extends React.HTMLAttributes<HTMLDivElement> {\n children: React.ReactNode\n}\n\nconst MenuGroup = React.forwardRef<HTMLDivElement, MenuGroupProps>(\n ({ children, className, ...props }, ref) => (\n <div\n ref={ref}\n role=\"group\"\n className={cn('py-2 [&+&]:border-t [&+&]:border-divider', className)}\n {...props}\n >\n {children}\n </div>\n )\n)\nMenuGroup.displayName = 'MenuGroup'\n\n// ── Footer ──\n\nexport interface MenuFooterProps extends React.HTMLAttributes<HTMLDivElement> {\n children: React.ReactNode\n}\n\nconst MenuFooter = React.forwardRef<HTMLDivElement, MenuFooterProps>(\n ({ children, className, ...props }, ref) => (\n <div\n ref={ref}\n className={cn('py-2 border-t border-divider', className)}\n {...props}\n >\n {children}\n </div>\n )\n)\nMenuFooter.displayName = 'MenuFooter'\n\n// Story auto-compile metadata — Phase 1+2 migration\nexport const menuItemMeta = {\n component: 'MenuItem',\n family: 1,\n variants: {},\n sizes: {\n sm: {},\n md: {},\n lg: {},\n },\n defaultSize: 'md',\n states: ['default', 'hover', 'selected', 'focus-visible', 'disabled'],\n tokens: {\n bg: ['bg-neutral-hover', 'bg-neutral-selected'],\n fg: ['text-fg-muted', 'text-fg-disabled'],\n },\n} as const\n\nexport { MenuItem, MenuGroup, MenuFooter, menuItemVariants }\n"],"names":[],"mappings":"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"}
|
|
@@ -42,6 +42,11 @@ export interface NoticeProps extends Omit<React.HTMLAttributes<HTMLDivElement>,
|
|
|
42
42
|
title: React.ReactNode;
|
|
43
43
|
description?: React.ReactNode;
|
|
44
44
|
endContent?: React.ReactNode;
|
|
45
|
+
/**
|
|
46
|
+
* 顯示關閉鈕(預設 true)。**需同時傳 `onDismiss` 才會渲染 X** — Notice 是 controlled-only
|
|
47
|
+
* (不自我移除),無 handler 的 X 是可 focus、SR 播報「關閉通知」但按下零反應的死按鈕
|
|
48
|
+
* (2026-07-05 D4;對齊 Polaris Banner `onDismiss` 存在才顯示 X / Ant Alert `closable` 預設 false)。
|
|
49
|
+
*/
|
|
45
50
|
dismissible?: boolean;
|
|
46
51
|
onDismiss?: () => void;
|
|
47
52
|
/** ARIA label for the dismiss button. Override for i18n. Default: "關閉通知" */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notice.d.ts","sourceRoot":"","sources":["../../../src/components/Notice/notice.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAM9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAA;AAUhF,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CAM3D,CAAA;AAYD,MAAM,WAAW,WACf,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IAC3D,OAAO,CAAC,EAAE,aAAa,CAAA;IACvB,KAAK,EAAE,KAAK,CAAC,SAAS,CAAA;IACtB,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC7B,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC5B,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;IACtB,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;OAIG;IACH,IAAI,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAA;IACzB;;OAEG;IACH,WAAW,CAAC,EAAE,QAAQ,GAAG,WAAW,GAAG,KAAK,CAAA;CAC7C;AAED,QAAA,MAAM,MAAM,
|
|
1
|
+
{"version":3,"file":"notice.d.ts","sourceRoot":"","sources":["../../../src/components/Notice/notice.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAM9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAA;AAUhF,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CAM3D,CAAA;AAYD,MAAM,WAAW,WACf,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IAC3D,OAAO,CAAC,EAAE,aAAa,CAAA;IACvB,KAAK,EAAE,KAAK,CAAC,SAAS,CAAA;IACtB,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC7B,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC5B;;;;OAIG;IACH,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;IACtB,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;OAIG;IACH,IAAI,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAA;IACzB;;OAEG;IACH,WAAW,CAAC,EAAE,QAAQ,GAAG,WAAW,GAAG,KAAK,CAAA;CAC7C;AAED,QAAA,MAAM,MAAM,oFA0EX,CAAA;AA+BD,wBAAgB,eAAe,IAAI,MAAM,GAAG,OAAO,CAGlD;AAID,eAAO,MAAM,UAAU;;;;;;;;;;;CAeb,CAAA;AAEV,OAAO,EAAE,MAAM,EAAE,CAAA"}
|
|
@@ -3,7 +3,7 @@ import * as React from "react";
|
|
|
3
3
|
import { XCircle, TriangleAlert, CircleCheck, Info, X } from "lucide-react";
|
|
4
4
|
import { cn } from "../../lib/utils.js";
|
|
5
5
|
import { Button } from "../Button/button.js";
|
|
6
|
-
import { ItemPrefix, ItemContent, ItemSuffix } from "../../patterns/element-anatomy/item-anatomy.js";
|
|
6
|
+
import { ItemPrefix, ICON_SIZE, ItemContent, ItemSuffix } from "../../patterns/element-anatomy/item-anatomy.js";
|
|
7
7
|
const VARIANT_ICON = {
|
|
8
8
|
neutral: null,
|
|
9
9
|
info: Info,
|
|
@@ -33,7 +33,9 @@ const Notice = React.forwardRef(
|
|
|
33
33
|
title,
|
|
34
34
|
description,
|
|
35
35
|
endContent,
|
|
36
|
-
|
|
36
|
+
// 不 destructure-default(保留「顯式傳 true」的可辨識性給下方 dev-warn);等效預設 true 由
|
|
37
|
+
// showDismiss 的 `!== false` 承擔。
|
|
38
|
+
dismissible,
|
|
37
39
|
onDismiss,
|
|
38
40
|
dismissAriaLabel = "關閉通知",
|
|
39
41
|
// i18n-allow: DS default; consumer override via dismissAriaLabel prop
|
|
@@ -42,6 +44,10 @@ const Notice = React.forwardRef(
|
|
|
42
44
|
...props
|
|
43
45
|
}, ref) => {
|
|
44
46
|
const StatusIcon = VARIANT_ICON[variant];
|
|
47
|
+
const showDismiss = dismissible !== false && onDismiss != null;
|
|
48
|
+
if (process.env.NODE_ENV !== "production" && dismissible === true && onDismiss == null) {
|
|
49
|
+
console.warn("[Notice] dismissible=true 但未傳 onDismiss,關閉鈕不會渲染(需同時提供 onDismiss)。");
|
|
50
|
+
}
|
|
45
51
|
return /* @__PURE__ */ jsxs(
|
|
46
52
|
"div",
|
|
47
53
|
{
|
|
@@ -49,7 +55,7 @@ const Notice = React.forwardRef(
|
|
|
49
55
|
className: cn(NOTICE_LAYOUT, className),
|
|
50
56
|
...props,
|
|
51
57
|
children: [
|
|
52
|
-
StatusIcon && /* @__PURE__ */ jsx(ItemPrefix, { children: /* @__PURE__ */ jsx(StatusIcon, { size:
|
|
58
|
+
StatusIcon && /* @__PURE__ */ jsx(ItemPrefix, { children: /* @__PURE__ */ jsx(StatusIcon, { size: ICON_SIZE.md, className: cn("shrink-0", iconClassName), "aria-hidden": true }) }),
|
|
53
59
|
/* @__PURE__ */ jsx(
|
|
54
60
|
ItemContent,
|
|
55
61
|
{
|
|
@@ -58,14 +64,14 @@ const Notice = React.forwardRef(
|
|
|
58
64
|
labelClassName: description ? "font-medium" : void 0
|
|
59
65
|
}
|
|
60
66
|
),
|
|
61
|
-
(endContent ||
|
|
67
|
+
(endContent || showDismiss) && // 2026-06-15 user 拍板:消費 ItemSuffix primitive(原手刻 div + @row-slot-handcraft-allow
|
|
62
68
|
// 已移除)。item-anatomy.spec.md 把 Notice 的 action/dismiss 對應到 suffix slot;ItemSuffix
|
|
63
69
|
// base geometry(h-[1lh] shrink-0 ml-auto flex items-center gap-2)正是此處所需,hoverReveal
|
|
64
70
|
// 預設 false 故無 row inline-action 機制干擾。內裝 dismiss = Button iconOnly dismiss xs(banner
|
|
65
71
|
// family canonical,overlay-surface.spec.md「Chrome dismiss size canonical」)。
|
|
66
72
|
/* @__PURE__ */ jsxs(ItemSuffix, { children: [
|
|
67
73
|
endContent,
|
|
68
|
-
|
|
74
|
+
showDismiss && /* @__PURE__ */ jsx(
|
|
69
75
|
Button,
|
|
70
76
|
{
|
|
71
77
|
"data-dismiss": true,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notice.js","sources":["../../../src/components/Notice/notice.tsx"],"sourcesContent":["/**\n * @internal — DS-internal 單元(per `.claude/rules/ui-development.md` Public vs Internal canonical;spec frontmatter `isInternal`)。\n * 不進 root barrel front-door;由 Alert / Toast wrap 消費,end-user app 請用 wrapper 元件。\n */\nimport * as React from 'react'\nimport { X as XIcon, Info, CircleCheck, TriangleAlert, XCircle, type LucideIcon } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { Button } from '@/design-system/components/Button/button'\nimport { ItemContent, ItemPrefix, ItemSuffix } from '@/design-system/patterns/element-anatomy/item-anatomy'\n\n/**\n * Notice — Toast / Alert 共用的視覺佈局層\n *\n * ── Typography: Family 2 reading-md(2026-06-15 user 拍板,off-grid 偏移收斂)──\n * title: text-body (14px) default leading 1.5 — 有 description 時加 font-medium\n * description: text-body (14px) default leading 1.5 + text-fg-secondary (neutral-8)\n * 14px 配 14px — 視覺層級靠 font-weight + color 區分,不靠 font-size。\n * **行高 = reading 預設 1.5**(非 compact):Notice 是 Family 2 reading consumer,\n * ItemContent 預設 mode='reading' + gap token `--item-gap-label-desc-reading` 名實相符。\n * 原 `leading-compact`(1.3)= reading-gap + scanning-行高 混搭的 off-grid 偏移,已移除。\n *\n * ── Padding(固定,不隨 density 變) ──\n * px = px-4(16px)\n * py = py-3(12px)\n * gap = gap-2(8px)\n * Toast/Alert 是浮動通知,不是工作區域元件——density 控制表單/選單的緊湊度,\n * 通知的尺寸應該固定,不隨 density 縮放。\n *\n * ── Icon: md tier ──\n * icon size: 16px(ICON_SIZE.md)\n *\n * ── Dismiss X(chrome corner close,Cat 3 Action group region)──\n * 用 Button iconOnly dismiss **size=\"xs\"** — 非 Inline Action、非自刻 button。\n * Rationale(Notification banner family canonical):\n * - Notice / Alert / Toast 屬 **notification banner family**(ephemeral、px-4 py-3 固定不隨 density),\n * dismiss 是邊角小 affordance,xs 視覺不搶眼不跟 content 競爭。見 `overlay-surface.spec.md`\n * 「Chrome dismiss size canonical」(overlay header 走 sm native + 負 margin trick;xs 只用於 notification banner family)\n * - Close 左側可加 refresh / share(action group region),皆統一 xs\n * - `dismiss` prop 自動套 variant=\"text\" + fg-muted override\n * SSOT:patterns/element-anatomy/inline-action.spec.md「Dismiss canonical — X close only」\n * + components/Alert/alert.spec.md「Chrome corner close X canonical」。\n */\n\nexport type NoticeVariant = 'neutral' | 'info' | 'success' | 'warning' | 'error'\n\nconst VARIANT_ICON: Record<NoticeVariant, LucideIcon | null> = {\n neutral: null,\n info: Info,\n success: CircleCheck,\n warning: TriangleAlert,\n error: XCircle,\n}\n\nexport const SUBTLE_ICON_COLOR: Record<NoticeVariant, string> = {\n neutral: 'text-fg-muted',\n info: 'text-info-text',\n success: 'text-success-text',\n warning: 'text-warning-text',\n error: 'text-error-text',\n}\n\nconst NOTICE_LAYOUT = [\n 'flex items-start gap-2 w-full',\n // 2026-06-15 user 拍板:Notice = 乾淨 Family 2 reading-md consumer。\n // 原 `leading-compact`(1.3)是 off-grid 偏移(reading gap token + scanning 行高混搭、未文件化),\n // 已移除 → label/desc 走 text-body reading 預設 1.5,與 ItemContent 預設 mode='reading' + gap\n // token `--item-gap-label-desc-reading` 名實相符。詳 notice.spec.md「Typography」段。\n 'text-body',\n 'px-4 py-3',\n].join(' ')\n\nexport interface NoticeProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, 'title'> {\n variant?: NoticeVariant\n title: React.ReactNode\n description?: React.ReactNode\n endContent?: React.ReactNode\n dismissible?: boolean\n onDismiss?: () => void\n /** ARIA label for the dismiss button. Override for i18n. Default: \"關閉通知\" */\n dismissAriaLabel?: string\n iconClassName?: string\n /**\n * ARIA role 由 wrapping consumer 決定(Alert / Toast / 自管 host),Notice 預設不帶 role。\n * Notice 是 layout primitive,Alert / Toast 是 live region 擁有者——避免 nested live region\n * 造成 screen reader 重複朗讀。明文傳遞才覆寫。\n */\n role?: 'status' | 'alert'\n /**\n * 對應 role 的 aria-live 策略,wrapping consumer 決定;Notice 預設 undefined 不帶 live region。\n */\n 'aria-live'?: 'polite' | 'assertive' | 'off'\n}\n\nconst Notice = React.forwardRef<HTMLDivElement, NoticeProps>(\n (\n {\n variant = 'neutral',\n title,\n description,\n endContent,\n dismissible = true,\n onDismiss,\n dismissAriaLabel = '關閉通知', // i18n-allow: DS default; consumer override via dismissAriaLabel prop\n iconClassName,\n className,\n ...props\n },\n ref,\n ) => {\n const StatusIcon = VARIANT_ICON[variant]\n\n return (\n <div\n ref={ref}\n className={cn(NOTICE_LAYOUT, className)}\n {...props}\n >\n {StatusIcon && (\n <ItemPrefix>\n <StatusIcon size={16} className={cn('shrink-0', iconClassName)} aria-hidden />\n </ItemPrefix>\n )}\n\n {/* Title + description 消費 ItemContent primitive(SSOT)。\n Label 有 desc 時 font-medium(Notice idiom:title 跟 desc 對照時 title 要更重)。 */}\n <ItemContent\n label={title}\n description={description}\n labelClassName={description ? 'font-medium' : undefined}\n />\n\n {(endContent || dismissible) && (\n // 2026-06-15 user 拍板:消費 ItemSuffix primitive(原手刻 div + @row-slot-handcraft-allow\n // 已移除)。item-anatomy.spec.md 把 Notice 的 action/dismiss 對應到 suffix slot;ItemSuffix\n // base geometry(h-[1lh] shrink-0 ml-auto flex items-center gap-2)正是此處所需,hoverReveal\n // 預設 false 故無 row inline-action 機制干擾。內裝 dismiss = Button iconOnly dismiss xs(banner\n // family canonical,overlay-surface.spec.md「Chrome dismiss size canonical」)。\n <ItemSuffix>\n {endContent}\n {dismissible && (\n <Button\n data-dismiss\n iconOnly\n dismiss\n size=\"xs\"\n startIcon={XIcon}\n aria-label={dismissAriaLabel}\n onClick={onDismiss}\n />\n )}\n </ItemSuffix>\n )}\n </div>\n )\n },\n)\nNotice.displayName = 'Notice'\n\n// Singleton MutationObserver + subscription fan-out(2026-04-22 D3 perf audit):\n// 先前每個 useInverseTheme consumer(Alert / Toast / Notice instance 等)各建一個 MO,\n// N 個 Notice = N 個 observers。singleton 共用一個 MO + pub/sub 讓 theme swap 只做一次 DOM read。\nlet themeObserverStarted = false\nconst themeSubscribers = new Set<() => void>()\n\nfunction getInverseTheme(): 'dark' | 'light' {\n if (typeof document === 'undefined') return 'dark'\n const current = document.documentElement.getAttribute('data-theme') ?? 'light'\n return current === 'dark' ? 'light' : 'dark'\n}\n\nfunction startThemeObserver() {\n if (themeObserverStarted || typeof document === 'undefined') return\n themeObserverStarted = true\n const root = document.documentElement\n const observer = new MutationObserver(() => {\n themeSubscribers.forEach((cb) => cb())\n })\n observer.observe(root, { attributes: true, attributeFilter: ['data-theme'] })\n}\n\nfunction subscribe(cb: () => void): () => void {\n startThemeObserver()\n themeSubscribers.add(cb)\n return () => themeSubscribers.delete(cb)\n}\n\nexport function useInverseTheme(): 'dark' | 'light' {\n // useSyncExternalStore canonical (React 18+):單一 external source 被 N consumers 訂閱\n return React.useSyncExternalStore(subscribe, getInverseTheme, getInverseTheme)\n}\n\n// Story auto-compile metadata — Phase 1 mechanical migration(2026-04-24)\n// Phase 2 fill needed: purpose descriptions + when rationale + world-class refs\nexport const noticeMeta = {\n component: 'Notice',\n family: 2, // Family 2(List item layout)消費者 — 對齊 notice.spec.md frontmatter family: 2 + body「Layout Family」段\n variants: {\n\n },\n sizes: {\n\n },\n states: ['default'], // 無互動 layout primitive(spec「邊界案例」:不擁有 disabled;hover / focus 屬內嵌 dismiss Button)\n tokens: {\n bg: [],\n fg: ['text-error-text', 'text-fg-muted', 'text-fg-secondary', 'text-info-text', 'text-success-text', 'text-warning-text'],\n ring: [],\n },\n} as const\n\nexport { Notice }\n"],"names":["XIcon"],"mappings":";;;;;;AA6CA,MAAM,eAAyD;AAAA,EAC7D,SAAS;AAAA,EACT,MAAM;AAAA,EACN,SAAS;AAAA,EACT,SAAS;AAAA,EACT,OAAO;AACT;AAEO,MAAM,oBAAmD;AAAA,EAC9D,SAAS;AAAA,EACT,MAAM;AAAA,EACN,SAAS;AAAA,EACT,SAAS;AAAA,EACT,OAAO;AACT;AAEA,MAAM,gBAAgB;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA;AAAA,EAKA;AAAA,EACA;AACF,EAAE,KAAK,GAAG;AAyBV,MAAM,SAAS,MAAM;AAAA,EACnB,CACE;AAAA,IACE,UAAU;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,IACA,cAAc;AAAA,IACd;AAAA,IACA,mBAAmB;AAAA;AAAA,IACnB;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EAAA,GAEL,QACG;AACH,UAAM,aAAa,aAAa,OAAO;AAEvC,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,WAAW,GAAG,eAAe,SAAS;AAAA,QACrC,GAAG;AAAA,QAEH,UAAA;AAAA,UAAA,cACC,oBAAC,YAAA,EACC,UAAA,oBAAC,YAAA,EAAW,MAAM,IAAI,WAAW,GAAG,YAAY,aAAa,GAAG,eAAW,MAAC,GAC9E;AAAA,UAKF;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,OAAO;AAAA,cACP;AAAA,cACA,gBAAgB,cAAc,gBAAgB;AAAA,YAAA;AAAA,UAAA;AAAA,WAG9C,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA,+BAMb,YAAA,EACE,UAAA;AAAA,YAAA;AAAA,YACA,eACC;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,gBAAY;AAAA,gBACZ,UAAQ;AAAA,gBACR,SAAO;AAAA,gBACP,MAAK;AAAA,gBACL,WAAWA;AAAAA,gBACX,cAAY;AAAA,gBACZ,SAAS;AAAA,cAAA;AAAA,YAAA;AAAA,UACX,EAAA,CAEJ;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAIR;AACF;AACA,OAAO,cAAc;AAKrB,IAAI,uBAAuB;AAC3B,MAAM,uCAAuB,IAAA;AAE7B,SAAS,kBAAoC;AAC3C,MAAI,OAAO,aAAa,YAAa,QAAO;AAC5C,QAAM,UAAU,SAAS,gBAAgB,aAAa,YAAY,KAAK;AACvE,SAAO,YAAY,SAAS,UAAU;AACxC;AAEA,SAAS,qBAAqB;AAC5B,MAAI,wBAAwB,OAAO,aAAa,YAAa;AAC7D,yBAAuB;AACvB,QAAM,OAAO,SAAS;AACtB,QAAM,WAAW,IAAI,iBAAiB,MAAM;AAC1C,qBAAiB,QAAQ,CAAC,OAAO,GAAA,CAAI;AAAA,EACvC,CAAC;AACD,WAAS,QAAQ,MAAM,EAAE,YAAY,MAAM,iBAAiB,CAAC,YAAY,GAAG;AAC9E;AAEA,SAAS,UAAU,IAA4B;AAC7C,qBAAA;AACA,mBAAiB,IAAI,EAAE;AACvB,SAAO,MAAM,iBAAiB,OAAO,EAAE;AACzC;AAEO,SAAS,kBAAoC;AAElD,SAAO,MAAM,qBAAqB,WAAW,iBAAiB,eAAe;AAC/E;AAIO,MAAM,aAAa;AAAA,EACxB,WAAW;AAAA,EACX,QAAQ;AAAA;AAAA,EACR,UAAU,CAAA;AAAA,EAGV,OAAO,CAAA;AAAA,EAGP,QAAQ,CAAC,SAAS;AAAA;AAAA,EAClB,QAAQ;AAAA,IACN,IAAI,CAAA;AAAA,IACJ,IAAI,CAAC,mBAAmB,iBAAiB,qBAAqB,kBAAkB,qBAAqB,mBAAmB;AAAA,IACxH,MAAM,CAAA;AAAA,EAAC;AAEX;"}
|
|
1
|
+
{"version":3,"file":"notice.js","sources":["../../../src/components/Notice/notice.tsx"],"sourcesContent":["/**\n * @internal — DS-internal 單元(per `.claude/rules/ui-development.md` Public vs Internal canonical;spec frontmatter `isInternal`)。\n * 不進 root barrel front-door;由 Alert / Toast wrap 消費,end-user app 請用 wrapper 元件。\n */\nimport * as React from 'react'\nimport { X as XIcon, Info, CircleCheck, TriangleAlert, XCircle, type LucideIcon } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { Button } from '@/design-system/components/Button/button'\nimport { ICON_SIZE, ItemContent, ItemPrefix, ItemSuffix } from '@/design-system/patterns/element-anatomy/item-anatomy'\n\n/**\n * Notice — Toast / Alert 共用的視覺佈局層\n *\n * ── Typography: Family 2 reading-md(2026-06-15 user 拍板,off-grid 偏移收斂)──\n * title: text-body (14px) default leading 1.5 — 有 description 時加 font-medium\n * description: text-body (14px) default leading 1.5 + text-fg-secondary (neutral-8)\n * 14px 配 14px — 視覺層級靠 font-weight + color 區分,不靠 font-size。\n * **行高 = reading 預設 1.5**(非 compact):Notice 是 Family 2 reading consumer,\n * ItemContent 預設 mode='reading' + gap token `--item-gap-label-desc-reading` 名實相符。\n * 原 `leading-compact`(1.3)= reading-gap + scanning-行高 混搭的 off-grid 偏移,已移除。\n *\n * ── Padding(固定,不隨 density 變) ──\n * px = px-4(16px)\n * py = py-3(12px)\n * gap = gap-2(8px)\n * Toast/Alert 是浮動通知,不是工作區域元件——density 控制表單/選單的緊湊度,\n * 通知的尺寸應該固定,不隨 density 縮放。\n *\n * ── Icon: md tier ──\n * icon size: 16px(ICON_SIZE.md)\n *\n * ── Dismiss X(chrome corner close,Cat 3 Action group region)──\n * 用 Button iconOnly dismiss **size=\"xs\"** — 非 Inline Action、非自刻 button。\n * Rationale(Notification banner family canonical):\n * - Notice / Alert / Toast 屬 **notification banner family**(ephemeral、px-4 py-3 固定不隨 density),\n * dismiss 是邊角小 affordance,xs 視覺不搶眼不跟 content 競爭。見 `overlay-surface.spec.md`\n * 「Chrome dismiss size canonical」(overlay header 走 sm native + 負 margin trick;xs 只用於 notification banner family)\n * - Close 左側可加 refresh / share(action group region),皆統一 xs\n * - `dismiss` prop 自動套 variant=\"text\" + fg-muted override\n * SSOT:patterns/element-anatomy/inline-action.spec.md「Dismiss canonical — X close only」\n * + components/Alert/alert.spec.md「Chrome corner close X canonical」。\n */\n\nexport type NoticeVariant = 'neutral' | 'info' | 'success' | 'warning' | 'error'\n\nconst VARIANT_ICON: Record<NoticeVariant, LucideIcon | null> = {\n neutral: null,\n info: Info,\n success: CircleCheck,\n warning: TriangleAlert,\n error: XCircle,\n}\n\nexport const SUBTLE_ICON_COLOR: Record<NoticeVariant, string> = {\n neutral: 'text-fg-muted',\n info: 'text-info-text',\n success: 'text-success-text',\n warning: 'text-warning-text',\n error: 'text-error-text',\n}\n\nconst NOTICE_LAYOUT = [\n 'flex items-start gap-2 w-full',\n // 2026-06-15 user 拍板:Notice = 乾淨 Family 2 reading-md consumer。\n // 原 `leading-compact`(1.3)是 off-grid 偏移(reading gap token + scanning 行高混搭、未文件化),\n // 已移除 → label/desc 走 text-body reading 預設 1.5,與 ItemContent 預設 mode='reading' + gap\n // token `--item-gap-label-desc-reading` 名實相符。詳 notice.spec.md「Typography」段。\n 'text-body',\n 'px-4 py-3',\n].join(' ')\n\nexport interface NoticeProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, 'title'> {\n variant?: NoticeVariant\n title: React.ReactNode\n description?: React.ReactNode\n endContent?: React.ReactNode\n /**\n * 顯示關閉鈕(預設 true)。**需同時傳 `onDismiss` 才會渲染 X** — Notice 是 controlled-only\n * (不自我移除),無 handler 的 X 是可 focus、SR 播報「關閉通知」但按下零反應的死按鈕\n * (2026-07-05 D4;對齊 Polaris Banner `onDismiss` 存在才顯示 X / Ant Alert `closable` 預設 false)。\n */\n dismissible?: boolean\n onDismiss?: () => void\n /** ARIA label for the dismiss button. Override for i18n. Default: \"關閉通知\" */\n dismissAriaLabel?: string\n iconClassName?: string\n /**\n * ARIA role 由 wrapping consumer 決定(Alert / Toast / 自管 host),Notice 預設不帶 role。\n * Notice 是 layout primitive,Alert / Toast 是 live region 擁有者——避免 nested live region\n * 造成 screen reader 重複朗讀。明文傳遞才覆寫。\n */\n role?: 'status' | 'alert'\n /**\n * 對應 role 的 aria-live 策略,wrapping consumer 決定;Notice 預設 undefined 不帶 live region。\n */\n 'aria-live'?: 'polite' | 'assertive' | 'off'\n}\n\nconst Notice = React.forwardRef<HTMLDivElement, NoticeProps>(\n (\n {\n variant = 'neutral',\n title,\n description,\n endContent,\n // 不 destructure-default(保留「顯式傳 true」的可辨識性給下方 dev-warn);等效預設 true 由\n // showDismiss 的 `!== false` 承擔。\n dismissible,\n onDismiss,\n dismissAriaLabel = '關閉通知', // i18n-allow: DS default; consumer override via dismissAriaLabel prop\n iconClassName,\n className,\n ...props\n },\n ref,\n ) => {\n const StatusIcon = VARIANT_ICON[variant]\n\n // 2026-07-05 D4:X 渲染條件 = dismissible(預設 true)**且**有傳 onDismiss — 原 `dismissible &&`\n // 單獨判斷讓預設用法 <Alert title=\"…\"/> 渲染一顆死 X(controlled-only 不自我移除,onClick=undefined\n // 零反應;keyboard / SR 使用者按 Enter 零回饋 = 假 affordance)。對齊 Polaris Banner(onDismiss\n // 存在才顯示 X)/ Ant Alert(closable 預設 false)。\n const showDismiss = dismissible !== false && onDismiss != null\n if (process.env.NODE_ENV !== 'production' && dismissible === true && onDismiss == null) {\n // 只在「顯式傳 dismissible={true} 卻沒接 onDismiss」的消費端矛盾時提示(預設 undefined 不吵)\n console.warn('[Notice] dismissible=true 但未傳 onDismiss,關閉鈕不會渲染(需同時提供 onDismiss)。')\n }\n\n return (\n <div\n ref={ref}\n className={cn(NOTICE_LAYOUT, className)}\n {...props}\n >\n {StatusIcon && (\n <ItemPrefix>\n <StatusIcon size={ICON_SIZE.md} className={cn('shrink-0', iconClassName)} aria-hidden />\n </ItemPrefix>\n )}\n\n {/* Title + description 消費 ItemContent primitive(SSOT)。\n Label 有 desc 時 font-medium(Notice idiom:title 跟 desc 對照時 title 要更重)。 */}\n <ItemContent\n label={title}\n description={description}\n labelClassName={description ? 'font-medium' : undefined}\n />\n\n {(endContent || showDismiss) && (\n // 2026-06-15 user 拍板:消費 ItemSuffix primitive(原手刻 div + @row-slot-handcraft-allow\n // 已移除)。item-anatomy.spec.md 把 Notice 的 action/dismiss 對應到 suffix slot;ItemSuffix\n // base geometry(h-[1lh] shrink-0 ml-auto flex items-center gap-2)正是此處所需,hoverReveal\n // 預設 false 故無 row inline-action 機制干擾。內裝 dismiss = Button iconOnly dismiss xs(banner\n // family canonical,overlay-surface.spec.md「Chrome dismiss size canonical」)。\n <ItemSuffix>\n {endContent}\n {showDismiss && (\n <Button\n data-dismiss\n iconOnly\n dismiss\n size=\"xs\"\n startIcon={XIcon}\n aria-label={dismissAriaLabel}\n onClick={onDismiss}\n />\n )}\n </ItemSuffix>\n )}\n </div>\n )\n },\n)\nNotice.displayName = 'Notice'\n\n// Singleton MutationObserver + subscription fan-out(2026-04-22 D3 perf audit):\n// 先前每個 useInverseTheme consumer(Alert / Toast / Notice instance 等)各建一個 MO,\n// N 個 Notice = N 個 observers。singleton 共用一個 MO + pub/sub 讓 theme swap 只做一次 DOM read。\nlet themeObserverStarted = false\nconst themeSubscribers = new Set<() => void>()\n\nfunction getInverseTheme(): 'dark' | 'light' {\n if (typeof document === 'undefined') return 'dark'\n const current = document.documentElement.getAttribute('data-theme') ?? 'light'\n return current === 'dark' ? 'light' : 'dark'\n}\n\nfunction startThemeObserver() {\n if (themeObserverStarted || typeof document === 'undefined') return\n themeObserverStarted = true\n const root = document.documentElement\n const observer = new MutationObserver(() => {\n themeSubscribers.forEach((cb) => cb())\n })\n observer.observe(root, { attributes: true, attributeFilter: ['data-theme'] })\n}\n\nfunction subscribe(cb: () => void): () => void {\n startThemeObserver()\n themeSubscribers.add(cb)\n return () => themeSubscribers.delete(cb)\n}\n\nexport function useInverseTheme(): 'dark' | 'light' {\n // useSyncExternalStore canonical (React 18+):單一 external source 被 N consumers 訂閱\n return React.useSyncExternalStore(subscribe, getInverseTheme, getInverseTheme)\n}\n\n// Story auto-compile metadata — Phase 1 mechanical migration(2026-04-24)\n// Phase 2 fill needed: purpose descriptions + when rationale + world-class refs\nexport const noticeMeta = {\n component: 'Notice',\n family: 2, // Family 2(List item layout)消費者 — 對齊 notice.spec.md frontmatter family: 2 + body「Layout Family」段\n variants: {\n\n },\n sizes: {\n\n },\n states: ['default'], // 無互動 layout primitive(spec「邊界案例」:不擁有 disabled;hover / focus 屬內嵌 dismiss Button)\n tokens: {\n bg: [],\n fg: ['text-error-text', 'text-fg-muted', 'text-fg-secondary', 'text-info-text', 'text-success-text', 'text-warning-text'],\n ring: [],\n },\n} as const\n\nexport { Notice }\n"],"names":["XIcon"],"mappings":"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"}
|
|
@@ -46,7 +46,7 @@ export declare const numberInputMeta: {
|
|
|
46
46
|
readonly family: 4;
|
|
47
47
|
readonly variants: {};
|
|
48
48
|
readonly sizes: {};
|
|
49
|
-
readonly states: readonly ["default", "hover", "
|
|
49
|
+
readonly states: readonly ["default", "hover", "focus-visible", "disabled"];
|
|
50
50
|
readonly tokens: {
|
|
51
51
|
readonly bg: readonly [];
|
|
52
52
|
readonly fg: readonly ["text-fg-disabled", "text-fg-muted"];
|