@qijenchen/design-system 0.1.0-beta.83 → 0.1.0-beta.85
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 +8 -8
- package/README.md +3 -2
- package/dist/components/Alert/alert.js.map +1 -1
- package/dist/components/AppShell/_demo-helpers.d.ts.map +1 -1
- package/dist/components/AppShell/app-shell.d.ts +1 -1
- package/dist/components/AppShell/app-shell.d.ts.map +1 -1
- package/dist/components/AppShell/app-shell.js +22 -2
- package/dist/components/AppShell/app-shell.js.map +1 -1
- package/dist/components/Avatar/avatar.d.ts.map +1 -1
- package/dist/components/Avatar/avatar.js +4 -4
- package/dist/components/Avatar/avatar.js.map +1 -1
- package/dist/components/Breadcrumb/breadcrumb.d.ts.map +1 -1
- package/dist/components/Breadcrumb/breadcrumb.js +1 -1
- package/dist/components/Breadcrumb/breadcrumb.js.map +1 -1
- package/dist/components/Button/button.d.ts +2 -2
- package/dist/components/Button/button.d.ts.map +1 -1
- package/dist/components/Button/button.js +12 -3
- package/dist/components/Button/button.js.map +1 -1
- package/dist/components/Calendar/calendar.d.ts +13 -5
- package/dist/components/Calendar/calendar.d.ts.map +1 -1
- package/dist/components/Calendar/calendar.js +7 -1
- package/dist/components/Calendar/calendar.js.map +1 -1
- package/dist/components/Carousel/carousel.d.ts.map +1 -1
- package/dist/components/Carousel/carousel.js +8 -3
- 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 -0
- package/dist/components/Chart/chart.js.map +1 -1
- package/dist/components/Checkbox/checkbox.d.ts +1 -1
- package/dist/components/Checkbox/checkbox.d.ts.map +1 -1
- package/dist/components/Checkbox/checkbox.js +8 -4
- package/dist/components/Checkbox/checkbox.js.map +1 -1
- package/dist/components/Chip/chip.d.ts +1 -1
- package/dist/components/Chip/chip.d.ts.map +1 -1
- package/dist/components/Chip/chip.js.map +1 -1
- package/dist/components/Coachmark/coachmark.d.ts +10 -3
- package/dist/components/Coachmark/coachmark.d.ts.map +1 -1
- package/dist/components/Coachmark/coachmark.js.map +1 -1
- package/dist/components/Combobox/combobox.d.ts +5 -1
- package/dist/components/Combobox/combobox.d.ts.map +1 -1
- package/dist/components/Combobox/combobox.js +30 -13
- package/dist/components/Combobox/combobox.js.map +1 -1
- package/dist/components/DataTable/cell-registry.d.ts.map +1 -1
- package/dist/components/DataTable/cell-registry.js +53 -62
- package/dist/components/DataTable/cell-registry.js.map +1 -1
- package/dist/components/DataTable/column-types.d.ts +10 -4
- package/dist/components/DataTable/column-types.d.ts.map +1 -1
- package/dist/components/DataTable/column-types.js.map +1 -1
- package/dist/components/DataTable/data-table-column-visibility-panel.d.ts +1 -1
- package/dist/components/DataTable/data-table-column-visibility-panel.js +183 -0
- package/dist/components/DataTable/data-table-column-visibility-panel.js.map +1 -0
- package/dist/components/DataTable/data-table-filter-group.d.ts +47 -0
- package/dist/components/DataTable/data-table-filter-group.d.ts.map +1 -0
- package/dist/components/DataTable/data-table-filter-group.js +154 -0
- package/dist/components/DataTable/data-table-filter-group.js.map +1 -0
- package/dist/components/DataTable/data-table-filter-panel.d.ts.map +1 -1
- package/dist/components/DataTable/data-table-filter-panel.js +285 -0
- package/dist/components/DataTable/data-table-filter-panel.js.map +1 -0
- package/dist/components/DataTable/data-table-filter-value-picker.d.ts +29 -0
- package/dist/components/DataTable/data-table-filter-value-picker.d.ts.map +1 -0
- package/dist/components/DataTable/data-table-filter-value-picker.js +187 -0
- package/dist/components/DataTable/data-table-filter-value-picker.js.map +1 -0
- package/dist/components/DataTable/data-table-interaction-layer.d.ts +11 -15
- package/dist/components/DataTable/data-table-interaction-layer.d.ts.map +1 -1
- package/dist/components/DataTable/data-table-interaction-layer.js +1 -3
- package/dist/components/DataTable/data-table-interaction-layer.js.map +1 -1
- package/dist/components/DataTable/data-table-sort-manager.js +140 -0
- package/dist/components/DataTable/data-table-sort-manager.js.map +1 -0
- package/dist/components/DataTable/data-table.d.ts +5 -6
- package/dist/components/DataTable/data-table.d.ts.map +1 -1
- package/dist/components/DataTable/data-table.js +36 -15
- package/dist/components/DataTable/data-table.js.map +1 -1
- package/dist/components/DataTable/filter-operators.js +140 -0
- package/dist/components/DataTable/filter-operators.js.map +1 -0
- package/dist/components/DataTable/filter-tree.js +312 -0
- package/dist/components/DataTable/filter-tree.js.map +1 -0
- package/dist/components/DataTable/index.d.ts +3 -0
- package/dist/components/DataTable/index.d.ts.map +1 -1
- package/dist/components/DataTable/index.js +12 -1
- package/dist/components/DataTable/index.js.map +1 -1
- package/dist/components/DataTable/lib/column-meta.js +25 -0
- package/dist/components/DataTable/lib/column-meta.js.map +1 -0
- package/dist/components/DateGrid/date-grid.d.ts.map +1 -1
- package/dist/components/DateGrid/date-grid.js +3 -1
- package/dist/components/DateGrid/date-grid.js.map +1 -1
- package/dist/components/DatePicker/date-picker.d.ts +7 -1
- package/dist/components/DatePicker/date-picker.d.ts.map +1 -1
- package/dist/components/DatePicker/date-picker.js +105 -74
- package/dist/components/DatePicker/date-picker.js.map +1 -1
- package/dist/components/Dialog/dialog.d.ts +20 -3
- package/dist/components/Dialog/dialog.d.ts.map +1 -1
- package/dist/components/Dialog/dialog.js +38 -20
- package/dist/components/Dialog/dialog.js.map +1 -1
- package/dist/components/DropdownMenu/dropdown-menu.d.ts.map +1 -1
- package/dist/components/DropdownMenu/dropdown-menu.js +3 -1
- package/dist/components/DropdownMenu/dropdown-menu.js.map +1 -1
- package/dist/components/Field/field-context.d.ts +45 -5
- package/dist/components/Field/field-context.d.ts.map +1 -1
- package/dist/components/Field/field-context.js +19 -0
- package/dist/components/Field/field-context.js.map +1 -1
- package/dist/components/Field/field-edit-keys.d.ts +21 -0
- package/dist/components/Field/field-edit-keys.d.ts.map +1 -0
- package/dist/components/Field/field-edit-keys.js +19 -0
- package/dist/components/Field/field-edit-keys.js.map +1 -0
- package/dist/components/Field/field-types.d.ts +20 -1
- package/dist/components/Field/field-types.d.ts.map +1 -1
- package/dist/components/Field/field-types.js.map +1 -1
- package/dist/components/Field/field-wrapper.d.ts +3 -2
- package/dist/components/Field/field-wrapper.d.ts.map +1 -1
- package/dist/components/Field/field-wrapper.js +38 -39
- package/dist/components/Field/field-wrapper.js.map +1 -1
- package/dist/components/Field/field.d.ts +2 -1
- package/dist/components/Field/field.d.ts.map +1 -1
- package/dist/components/Field/field.js.map +1 -1
- package/dist/components/Field/index.d.ts +1 -0
- package/dist/components/Field/index.d.ts.map +1 -1
- package/dist/components/Field/index.js +2 -0
- package/dist/components/Field/index.js.map +1 -1
- package/dist/components/FieldControlGroup/field-control-group.d.ts +1 -1
- 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 +1 -1
- 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.js.map +1 -1
- package/dist/components/FileViewer/file-viewer.d.ts.map +1 -1
- package/dist/components/FileViewer/file-viewer.js +7 -2
- package/dist/components/FileViewer/file-viewer.js.map +1 -1
- package/dist/components/FileViewer/image-renderer.d.ts.map +1 -1
- package/dist/components/FileViewer/image-renderer.js +22 -1
- 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 +5 -3
- package/dist/components/HoverCard/hover-card.js.map +1 -1
- package/dist/components/InlineEdit/index.d.ts +2 -0
- package/dist/components/InlineEdit/index.d.ts.map +1 -0
- package/dist/components/InlineEdit/index.js +5 -0
- package/dist/components/InlineEdit/index.js.map +1 -0
- package/dist/components/InlineEdit/inline-edit.d.ts +59 -0
- package/dist/components/InlineEdit/inline-edit.d.ts.map +1 -0
- package/dist/components/InlineEdit/inline-edit.js +164 -0
- package/dist/components/InlineEdit/inline-edit.js.map +1 -0
- package/dist/components/Input/input.d.ts +6 -11
- package/dist/components/Input/input.d.ts.map +1 -1
- package/dist/components/Input/input.js +43 -13
- package/dist/components/Input/input.js.map +1 -1
- package/dist/components/LinkInput/link-input.d.ts +2 -2
- package/dist/components/LinkInput/link-input.d.ts.map +1 -1
- package/dist/components/LinkInput/link-input.js +6 -5
- package/dist/components/LinkInput/link-input.js.map +1 -1
- package/dist/components/Menu/menu-item.d.ts +14 -2
- package/dist/components/Menu/menu-item.d.ts.map +1 -1
- package/dist/components/Menu/menu-item.js +25 -3
- package/dist/components/Menu/menu-item.js.map +1 -1
- package/dist/components/NumberInput/number-input.d.ts +3 -3
- package/dist/components/NumberInput/number-input.d.ts.map +1 -1
- package/dist/components/NumberInput/number-input.js +6 -5
- package/dist/components/NumberInput/number-input.js.map +1 -1
- package/dist/components/OverflowIndicator/overflow-indicator.d.ts.map +1 -1
- package/dist/components/OverflowIndicator/overflow-indicator.js +2 -6
- package/dist/components/OverflowIndicator/overflow-indicator.js.map +1 -1
- package/dist/components/Pagination/pagination.js +1 -1
- package/dist/components/Pagination/pagination.js.map +1 -1
- package/dist/components/PeoplePicker/index.js +7 -1
- package/dist/components/PeoplePicker/index.js.map +1 -1
- package/dist/components/PeoplePicker/people-picker.d.ts +9 -2
- package/dist/components/PeoplePicker/people-picker.d.ts.map +1 -1
- package/dist/components/PeoplePicker/people-picker.js +23 -10
- package/dist/components/PeoplePicker/people-picker.js.map +1 -1
- package/dist/components/PeoplePicker/person-display.d.ts +6 -3
- package/dist/components/PeoplePicker/person-display.d.ts.map +1 -1
- package/dist/components/PeoplePicker/person-display.js +19 -14
- 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 +3 -1
- package/dist/components/Popover/popover.js.map +1 -1
- package/dist/components/ProfileCard/profile-card.d.ts +2 -2
- package/dist/components/ProfileCard/profile-card.js +3 -3
- package/dist/components/ProfileCard/profile-card.js.map +1 -1
- package/dist/components/RadioGroup/radio-group.d.ts.map +1 -1
- package/dist/components/RadioGroup/radio-group.js +15 -9
- package/dist/components/RadioGroup/radio-group.js.map +1 -1
- package/dist/components/ScrollArea/scroll-area.d.ts +11 -1
- package/dist/components/ScrollArea/scroll-area.d.ts.map +1 -1
- package/dist/components/ScrollArea/scroll-area.js +6 -3
- package/dist/components/ScrollArea/scroll-area.js.map +1 -1
- package/dist/components/Select/select.d.ts +46 -5
- package/dist/components/Select/select.d.ts.map +1 -1
- package/dist/components/Select/select.js +109 -28
- package/dist/components/Select/select.js.map +1 -1
- package/dist/components/SelectMenu/select-menu.d.ts +3 -1
- package/dist/components/SelectMenu/select-menu.d.ts.map +1 -1
- package/dist/components/SelectMenu/select-menu.js +8 -1
- package/dist/components/SelectMenu/select-menu.js.map +1 -1
- package/dist/components/SelectionControl/boolean-value.d.ts +11 -0
- package/dist/components/SelectionControl/boolean-value.d.ts.map +1 -0
- package/dist/components/SelectionControl/boolean-value.js +23 -0
- package/dist/components/SelectionControl/boolean-value.js.map +1 -0
- package/dist/components/SelectionControl/index.d.ts +1 -0
- package/dist/components/SelectionControl/index.d.ts.map +1 -1
- package/dist/components/SelectionControl/index.js +2 -0
- package/dist/components/SelectionControl/index.js.map +1 -1
- package/dist/components/Sheet/index.js +2 -1
- package/dist/components/Sheet/sheet.d.ts +7 -1
- package/dist/components/Sheet/sheet.d.ts.map +1 -1
- package/dist/components/Sheet/sheet.js +5 -2
- package/dist/components/Sheet/sheet.js.map +1 -1
- package/dist/components/Sidebar/sidebar.d.ts +9 -4
- package/dist/components/Sidebar/sidebar.d.ts.map +1 -1
- package/dist/components/Sidebar/sidebar.js +8 -10
- package/dist/components/Sidebar/sidebar.js.map +1 -1
- package/dist/components/Slider/slider.d.ts.map +1 -1
- package/dist/components/Slider/slider.js.map +1 -1
- package/dist/components/Switch/switch.d.ts +6 -6
- package/dist/components/Switch/switch.d.ts.map +1 -1
- package/dist/components/Switch/switch.js +3 -2
- package/dist/components/Switch/switch.js.map +1 -1
- package/dist/components/Tabs/tabs.d.ts.map +1 -1
- package/dist/components/Tabs/tabs.js +15 -1
- package/dist/components/Tabs/tabs.js.map +1 -1
- package/dist/components/Textarea/textarea.d.ts +2 -2
- package/dist/components/Textarea/textarea.d.ts.map +1 -1
- package/dist/components/Textarea/textarea.js +50 -42
- package/dist/components/Textarea/textarea.js.map +1 -1
- package/dist/components/TimePicker/time-columns.d.ts.map +1 -1
- package/dist/components/TimePicker/time-columns.js +42 -38
- package/dist/components/TimePicker/time-columns.js.map +1 -1
- package/dist/components/TimePicker/time-picker.d.ts.map +1 -1
- package/dist/components/TimePicker/time-picker.js +8 -6
- package/dist/components/TimePicker/time-picker.js.map +1 -1
- package/dist/components/Tooltip/tooltip.d.ts.map +1 -1
- package/dist/components/Tooltip/tooltip.js +7 -5
- package/dist/components/Tooltip/tooltip.js.map +1 -1
- package/dist/components/TreeView/tree-view.d.ts +56 -1
- package/dist/components/TreeView/tree-view.d.ts.map +1 -1
- package/dist/components/TreeView/tree-view.js +211 -12
- package/dist/components/TreeView/tree-view.js.map +1 -1
- package/dist/index.d.ts +115 -58
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +95 -108
- package/dist/index.js.map +1 -1
- package/dist/patterns/element-anatomy/item-anatomy.d.ts +7 -6
- 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/tokens/categorical-color.js +15 -0
- package/dist/tokens/categorical-color.js.map +1 -1
- package/dist/tokens/motion/motion.d.ts +7 -6
- package/dist/tokens/motion/motion.d.ts.map +1 -1
- package/dist/tokens/motion/motion.js +6 -6
- package/dist/tokens/motion/motion.js.map +1 -1
- package/dist/tokens/motion/overlay-motion.d.ts +19 -0
- package/dist/tokens/motion/overlay-motion.d.ts.map +1 -0
- package/dist/tokens/motion/overlay-motion.js +9 -0
- package/dist/tokens/motion/overlay-motion.js.map +1 -0
- package/dist/tokens/uiSize/icon-size.js +5 -0
- package/dist/tokens/uiSize/icon-size.js.map +1 -0
- package/ds-canonical/fork/governance.lock +31 -19
- package/ds-canonical/fork/hooks/_log-fire.sh +46 -0
- package/ds-canonical/fork/hooks/block_prototype_imports.py +15 -4
- package/ds-canonical/fork/hooks/check_consumer_app_invariants.sh +145 -13
- package/ds-canonical/fork/hooks/check_ds_anchor_preflight.sh +2 -1
- package/ds-canonical/fork/hooks/check_dynamic_tailwind_class.sh +47 -0
- package/ds-canonical/fork/hooks/check_escape_marker_abuse.sh +27 -0
- package/ds-canonical/fork/hooks/check_field_family_invariants.sh +61 -1
- package/ds-canonical/fork/hooks/check_layout_space_magic_numbers.sh +28 -4
- package/ds-canonical/fork/hooks/check_opacity_token_usage.sh +28 -0
- package/ds-canonical/fork/hooks/check_story_determinism.sh +2 -0
- package/ds-canonical/fork/hooks/check_story_invariants.sh +119 -0
- package/ds-canonical/fork/hooks/check_tabs_content_chrome_body_double_gap.sh +29 -0
- package/ds-canonical/fork/manifest.json +15 -0
- package/ds-canonical/fork/preamble.md +20 -18
- package/ds-canonical/fork/skills/bug-fix-rhythm/SKILL.md +3 -3
- package/ds-canonical/fork/skills/code-quality-audit/SKILL.md +3 -3
- package/ds-canonical/fork/skills/delivery-handoff/SKILL.md +1 -1
- package/ds-canonical/fork/skills/product-ui-audit/SKILL.md +3 -3
- package/ds-canonical/fork/skills/propose-options/SKILL.md +3 -3
- package/ds-canonical/fork/skills/prototype/SKILL.md +1 -1
- package/ds-canonical/fork/skills/scan-similar-bugs/SKILL.md +2 -2
- package/ds-canonical/fork/skills/visual-audit/SKILL.md +2 -2
- package/ds-canonical/hooks/README.md +1 -1
- package/ds-canonical/hooks/block_prototype_imports.py +15 -4
- package/ds-canonical/hooks/check_audit_post_report_validator.sh +65 -7
- package/ds-canonical/hooks/check_codex_brief_invariants.sh +28 -1
- package/ds-canonical/hooks/check_consumer_app_invariants.sh +145 -13
- package/ds-canonical/hooks/check_datatable_invariants.sh +6 -3
- package/ds-canonical/hooks/check_ds_anchor_preflight.sh +2 -1
- package/ds-canonical/hooks/check_dynamic_tailwind_class.sh +47 -0
- package/ds-canonical/hooks/check_escape_marker_abuse.sh +27 -0
- package/ds-canonical/hooks/check_field_family_invariants.sh +61 -1
- package/ds-canonical/hooks/check_layout_space_magic_numbers.sh +28 -4
- package/ds-canonical/hooks/check_naming_and_abstraction.sh +1 -1
- package/ds-canonical/hooks/check_opacity_token_usage.sh +28 -0
- package/ds-canonical/hooks/check_pixel_quantified_audit.sh +3 -2
- package/ds-canonical/hooks/check_post_main_ssot_propagate.sh +6 -1
- package/ds-canonical/hooks/check_ssot_header_declaration.sh +3 -1
- package/ds-canonical/hooks/check_story_determinism.sh +2 -0
- package/ds-canonical/hooks/check_substantive_edit_approval_preflight.sh +8 -7
- package/ds-canonical/hooks/check_tabs_content_chrome_body_double_gap.sh +29 -0
- package/ds-canonical/hooks/lib/_story_compile_drift.sh +1 -1
- package/ds-canonical/hooks/lib/_token_hygiene.sh +10 -1
- package/ds-canonical/hooks/stop_self_audit.sh +31 -0
- package/ds-canonical/hooks/tests/test_block_prototype_imports.sh +24 -0
- package/ds-canonical/hooks/tests/test_check_audit_post_report_validator.sh +44 -0
- package/ds-canonical/hooks/tests/test_check_codex_brief_invariants.sh +43 -2
- package/ds-canonical/hooks/tests/test_check_consumer_ds_primitive_misuse.sh +112 -0
- package/ds-canonical/hooks/tests/test_check_consumer_story_baseline.sh +9 -2
- package/ds-canonical/hooks/tests/test_check_dynamic_tailwind_class.sh +76 -0
- package/ds-canonical/hooks/tests/test_check_escape_marker_abuse.sh +24 -0
- package/ds-canonical/hooks/tests/test_check_field_family_invariants.sh +27 -2
- package/ds-canonical/hooks/tests/test_check_opacity_token_usage.sh +39 -0
- package/ds-canonical/hooks/tests/test_check_tabs_content_chrome_body_double_gap.sh +76 -0
- package/ds-canonical/references/audit-coverage-vs-24-checklist.md +1 -1
- package/ds-canonical/references/build-ui-canonicals.md +2 -1
- package/ds-canonical/references/failure-class-registry.json +145 -0
- package/ds-canonical/references/governance-audit-coverage.md +33 -0
- package/ds-canonical/references/item-anatomy-recipe.md +2 -2
- package/ds-canonical/references/scenario-definition.md +6 -9
- package/ds-canonical/references/ssot-consultation.md +2 -2
- package/ds-canonical/references/ssot-index.md +5 -4
- package/ds-canonical/references/story-baseline-registry.json +8 -2
- package/ds-canonical/references/structural-token-retention.md +6 -9
- package/ds-canonical/rules/meta-patterns.md +8 -8
- package/ds-canonical/rules/self-verify.md +4 -3
- package/ds-canonical/rules/story-rules.md +2 -2
- package/ds-canonical/rules/ui-development.md +2 -2
- package/ds-canonical/skills/bug-fix-rhythm/SKILL.md +3 -3
- package/ds-canonical/skills/code-quality-audit/SKILL.md +3 -3
- package/ds-canonical/skills/codex-collab/SKILL.md +9 -7
- package/ds-canonical/skills/codex-collab/references/brief-template.md +1 -1
- package/ds-canonical/skills/codify-principle/SKILL.md +2 -2
- package/ds-canonical/skills/component-quality-gate/SKILL.md +1 -1
- package/ds-canonical/skills/component-quality-gate/references/checklist.md +2 -2
- package/ds-canonical/skills/deep-audit-cross-codex/SKILL.md +51 -58
- package/ds-canonical/skills/deep-audit-cross-codex/references/phase-a-workflow.md +29 -0
- package/ds-canonical/skills/deep-audit-cross-codex/references/phase-b-codex-brief.md +29 -10
- package/ds-canonical/skills/deep-audit-cross-codex/references/triage-rubric.md +21 -0
- package/ds-canonical/skills/delivery-handoff/SKILL.md +1 -1
- package/ds-canonical/skills/design-system-audit/SKILL.md +26 -25
- package/ds-canonical/skills/design-system-audit/references/audit-prompts.md +13 -1
- package/ds-canonical/skills/design-system-audit/references/rule-placement.md +1 -1
- package/ds-canonical/skills/governance-health/SKILL.md +1 -1
- package/ds-canonical/skills/knowledge-prune/SKILL.md +3 -0
- package/ds-canonical/skills/knowledge-prune/references/prune-dimensions-d5-d10.md +1 -0
- package/ds-canonical/skills/new-component/SKILL.md +5 -5
- package/ds-canonical/skills/new-component/references/new-component-checklist.md +1 -1
- package/ds-canonical/skills/product-ui-audit/SKILL.md +3 -3
- package/ds-canonical/skills/propose-options/SKILL.md +3 -3
- package/ds-canonical/skills/prototype/SKILL.md +1 -1
- package/ds-canonical/skills/scan-similar-bugs/SKILL.md +2 -2
- package/ds-canonical/skills/story-auto-compile-migrate/SKILL.md +3 -3
- package/ds-canonical/skills/story-writing/SKILL.md +3 -3
- package/ds-canonical/skills/visual-audit/SKILL.md +2 -2
- package/ds-story-manifest.json +29 -20
- package/llms-full.txt +22 -1
- package/llms.txt +5 -4
- package/package.json +7 -3
- package/src/components/Accordion/accordion.principles.stories.tsx +1 -1
- package/src/components/Accordion/accordion.stories.tsx +16 -1
- package/src/components/Alert/alert.principles.stories.tsx +1 -1
- package/src/components/Alert/alert.spec.md +8 -10
- package/src/components/Alert/alert.stories.tsx +28 -28
- package/src/components/Alert/alert.tsx +1 -1
- package/src/components/AppShell/_demo-helpers.tsx +6 -3
- package/src/components/AppShell/app-shell.spec.md +4 -4
- package/src/components/AppShell/app-shell.tsx +28 -2
- package/src/components/AspectRatio/aspect-ratio.spec.md +1 -1
- package/src/components/AspectRatio/aspect-ratio.stories.tsx +7 -89
- package/src/components/Avatar/avatar.anatomy.stories.tsx +7 -3
- package/src/components/Avatar/avatar.principles.stories.tsx +19 -15
- package/src/components/Avatar/avatar.spec.md +2 -2
- package/src/components/Avatar/avatar.stories.tsx +8 -6
- package/src/components/Avatar/avatar.tsx +13 -5
- package/src/components/Badge/badge.principles.stories.tsx +1 -1
- package/src/components/Badge/badge.spec.md +2 -0
- package/src/components/Badge/badge.stories.tsx +4 -2
- package/src/components/Breadcrumb/breadcrumb.principles.stories.tsx +6 -4
- package/src/components/Breadcrumb/breadcrumb.stories.tsx +6 -5
- package/src/components/Breadcrumb/breadcrumb.tsx +3 -2
- package/src/components/BulkActionBar/bulk-action-bar.spec.md +4 -2
- package/src/components/Button/button.principles.stories.tsx +1 -1
- package/src/components/Button/button.spec.md +2 -2
- package/src/components/Button/button.tsx +18 -3
- package/src/components/Calendar/calendar.anatomy.stories.tsx +12 -10
- package/src/components/Calendar/calendar.principles.stories.tsx +14 -6
- package/src/components/Calendar/calendar.spec.md +5 -4
- package/src/components/Calendar/calendar.stories.tsx +7 -4
- package/src/components/Calendar/calendar.tsx +26 -7
- package/src/components/Carousel/carousel.anatomy.stories.tsx +5 -4
- package/src/components/Carousel/carousel.spec.md +2 -2
- package/src/components/Carousel/carousel.tsx +11 -3
- package/src/components/Chart/chart.anatomy.stories.tsx +7 -4
- package/src/components/Chart/chart.principles.stories.tsx +1 -1
- package/src/components/Chart/chart.stories.tsx +2 -8
- package/src/components/Chart/chart.tsx +3 -0
- package/src/components/Checkbox/checkbox.anatomy.stories.tsx +28 -49
- package/src/components/Checkbox/checkbox.principles.stories.tsx +1 -1
- package/src/components/Checkbox/checkbox.spec.md +13 -26
- package/src/components/Checkbox/checkbox.stories.tsx +11 -8
- package/src/components/Checkbox/checkbox.tsx +18 -13
- package/src/components/Chip/chip.anatomy.stories.tsx +44 -24
- package/src/components/Chip/chip.principles.stories.tsx +1 -1
- package/src/components/Chip/chip.spec.md +5 -4
- package/src/components/Chip/chip.tsx +5 -1
- package/src/components/CircularProgress/circular-progress.principles.stories.tsx +7 -4
- package/src/components/CircularProgress/circular-progress.spec.md +3 -3
- package/src/components/CircularProgress/circular-progress.stories.tsx +2 -2
- package/src/components/Coachmark/coachmark.anatomy.stories.tsx +2 -2
- package/src/components/Coachmark/coachmark.principles.stories.tsx +2 -1
- package/src/components/Coachmark/coachmark.spec.md +5 -5
- package/src/components/Coachmark/coachmark.stories.tsx +3 -1
- package/src/components/Coachmark/coachmark.tsx +10 -3
- package/src/components/Combobox/combobox.anatomy.stories.tsx +2 -2
- package/src/components/Combobox/combobox.tsx +53 -19
- package/src/components/Command/command.anatomy.stories.tsx +2 -2
- package/src/components/Command/command.principles.stories.tsx +1 -1
- package/src/components/Command/command.stories.tsx +42 -10
- package/src/components/DataTable/cell-registry.tsx +86 -63
- package/src/components/DataTable/column-types.ts +10 -1
- package/src/components/DataTable/data-table-column-visibility-panel.tsx +1 -1
- package/src/components/DataTable/data-table-filter-group.tsx +231 -0
- package/src/components/DataTable/data-table-filter-panel.tsx +16 -430
- package/src/components/DataTable/data-table-filter-value-picker.tsx +237 -0
- package/src/components/DataTable/data-table-interaction-layer.tsx +15 -19
- package/src/components/DataTable/data-table.anatomy.stories.tsx +15 -13
- package/src/components/DataTable/data-table.principles.stories.tsx +9 -9
- package/src/components/DataTable/data-table.spec.md +33 -13
- package/src/components/DataTable/data-table.stories.tsx +16 -17
- package/src/components/DataTable/data-table.tsx +107 -61
- package/src/components/DataTable/filter-operators.spec.md +3 -3
- package/src/components/DataTable/index.ts +3 -0
- package/src/components/DateGrid/date-grid.anatomy.stories.tsx +4 -4
- package/src/components/DateGrid/date-grid.principles.stories.tsx +1 -1
- package/src/components/DateGrid/date-grid.spec.md +1 -1
- package/src/components/DateGrid/date-grid.tsx +3 -1
- package/src/components/DatePicker/date-picker.anatomy.stories.tsx +8 -8
- package/src/components/DatePicker/date-picker.principles.stories.tsx +3 -3
- package/src/components/DatePicker/date-picker.spec.md +3 -3
- package/src/components/DatePicker/date-picker.stories.tsx +4 -5
- package/src/components/DatePicker/date-picker.tsx +99 -45
- package/src/components/DescriptionList/description-list.anatomy.stories.tsx +12 -10
- package/src/components/DescriptionList/description-list.principles.stories.tsx +2 -2
- package/src/components/DescriptionList/description-list.spec.md +3 -3
- package/src/components/DescriptionList/description-list.stories.tsx +7 -6
- package/src/components/Dialog/dialog.anatomy.stories.tsx +1 -0
- package/src/components/Dialog/dialog.spec.md +9 -0
- package/src/components/Dialog/dialog.stories.tsx +66 -5
- package/src/components/Dialog/dialog.tsx +60 -25
- package/src/components/DropdownMenu/dropdown-menu.anatomy.stories.tsx +1 -1
- package/src/components/DropdownMenu/dropdown-menu.principles.stories.tsx +8 -6
- package/src/components/DropdownMenu/dropdown-menu.spec.md +5 -5
- package/src/components/DropdownMenu/dropdown-menu.tsx +18 -9
- package/src/components/Empty/empty.anatomy.stories.tsx +1 -1
- package/src/components/Empty/empty.spec.md +2 -2
- package/src/components/Field/field-context.ts +69 -5
- package/src/components/Field/field-controls.spec.md +62 -8
- package/src/components/Field/field-edit-keys.ts +66 -0
- package/src/components/Field/field-types.ts +58 -9
- package/src/components/Field/field-wrapper.tsx +50 -42
- package/src/components/Field/field.anatomy.stories.tsx +9 -6
- package/src/components/Field/field.spec.md +1 -1
- package/src/components/Field/field.stories.tsx +1 -1
- package/src/components/Field/field.tsx +2 -1
- package/src/components/Field/form-validation.spec.md +3 -3
- package/src/components/Field/index.ts +1 -0
- package/src/components/FieldControlGroup/field-control-group.principles.stories.tsx +6 -6
- package/src/components/FieldControlGroup/field-control-group.spec.md +9 -4
- package/src/components/FieldControlGroup/field-control-group.tsx +1 -1
- package/src/components/FileItem/file-item.anatomy.stories.tsx +1 -1
- package/src/components/FileItem/file-item.spec.md +5 -8
- package/src/components/FileItem/file-item.stories.tsx +36 -25
- package/src/components/FileItem/file-item.tsx +8 -6
- package/src/components/FileUpload/file-upload.anatomy.stories.tsx +6 -6
- package/src/components/FileUpload/file-upload.principles.stories.tsx +5 -5
- package/src/components/FileUpload/file-upload.spec.md +1 -1
- package/src/components/FileUpload/file-upload.tsx +1 -1
- package/src/components/FileViewer/file-viewer.anatomy.stories.tsx +4 -3
- package/src/components/FileViewer/file-viewer.principles.stories.tsx +3 -3
- package/src/components/FileViewer/file-viewer.spec.md +30 -12
- package/src/components/FileViewer/file-viewer.stories.tsx +1 -2
- package/src/components/FileViewer/file-viewer.tsx +12 -4
- package/src/components/FileViewer/image-renderer.tsx +35 -7
- package/src/components/HoverCard/hover-card.anatomy.stories.tsx +6 -2
- package/src/components/HoverCard/hover-card.principles.stories.tsx +6 -3
- package/src/components/HoverCard/hover-card.spec.md +5 -5
- package/src/components/HoverCard/hover-card.stories.tsx +16 -16
- package/src/components/HoverCard/hover-card.tsx +5 -3
- package/src/components/InlineEdit/index.ts +5 -0
- package/src/components/InlineEdit/inline-edit.spec.md +149 -0
- package/src/components/InlineEdit/inline-edit.stories.tsx +246 -0
- package/src/components/InlineEdit/inline-edit.tsx +287 -0
- package/src/components/Input/input.anatomy.stories.tsx +8 -7
- package/src/components/Input/input.principles.stories.tsx +2 -12
- package/src/components/Input/input.spec.md +13 -18
- package/src/components/Input/input.tsx +69 -22
- package/src/components/LinkInput/link-input.anatomy.stories.tsx +1 -1
- package/src/components/LinkInput/link-input.principles.stories.tsx +2 -2
- package/src/components/LinkInput/link-input.tsx +11 -10
- package/src/components/Menu/menu-item.anatomy.stories.tsx +3 -3
- package/src/components/Menu/menu-item.principles.stories.tsx +1 -1
- package/src/components/Menu/menu-item.spec.md +22 -15
- package/src/components/Menu/menu-item.stories.tsx +39 -11
- package/src/components/Menu/menu-item.tsx +38 -4
- package/src/components/Notice/notice.principles.stories.tsx +1 -1
- package/src/components/Notice/notice.spec.md +16 -3
- package/src/components/NumberInput/number-input.anatomy.stories.tsx +3 -3
- package/src/components/NumberInput/number-input.principles.stories.tsx +1 -1
- package/src/components/NumberInput/number-input.spec.md +4 -4
- package/src/components/NumberInput/number-input.tsx +19 -12
- package/src/components/OverflowIndicator/overflow-indicator.anatomy.stories.tsx +10 -8
- package/src/components/OverflowIndicator/overflow-indicator.principles.stories.tsx +3 -3
- package/src/components/OverflowIndicator/overflow-indicator.spec.md +4 -2
- package/src/components/OverflowIndicator/overflow-indicator.stories.tsx +71 -63
- package/src/components/OverflowIndicator/overflow-indicator.tsx +9 -8
- package/src/components/Pagination/pagination.spec.md +1 -1
- package/src/components/Pagination/pagination.stories.tsx +2 -6
- package/src/components/Pagination/pagination.tsx +1 -1
- package/src/components/PeoplePicker/people-picker.anatomy.stories.tsx +4 -4
- package/src/components/PeoplePicker/people-picker.principles.stories.tsx +2 -2
- package/src/components/PeoplePicker/people-picker.spec.md +6 -6
- package/src/components/PeoplePicker/people-picker.tsx +56 -16
- package/src/components/PeoplePicker/person-display.tsx +32 -16
- package/src/components/Popover/popover.anatomy.stories.tsx +2 -2
- package/src/components/Popover/popover.tsx +3 -1
- package/src/components/ProfileCard/profile-card.anatomy.stories.tsx +12 -23
- package/src/components/ProfileCard/profile-card.spec.md +3 -3
- package/src/components/ProfileCard/profile-card.tsx +6 -6
- package/src/components/ProgressBar/progress-bar.anatomy.stories.tsx +3 -1
- package/src/components/ProgressBar/progress-bar.principles.stories.tsx +1 -1
- package/src/components/ProgressBar/progress-bar.stories.tsx +50 -42
- package/src/components/RadioGroup/radio-group.anatomy.stories.tsx +10 -28
- package/src/components/RadioGroup/radio-group.stories.tsx +29 -32
- package/src/components/RadioGroup/radio-group.tsx +41 -15
- package/src/components/Rating/rating.spec.md +11 -3
- package/src/components/ScrollArea/scroll-area.anatomy.stories.tsx +1 -1
- package/src/components/ScrollArea/scroll-area.principles.stories.tsx +1 -1
- package/src/components/ScrollArea/scroll-area.spec.md +7 -3
- package/src/components/ScrollArea/scroll-area.stories.tsx +1 -1
- package/src/components/ScrollArea/scroll-area.tsx +17 -4
- package/src/components/Select/select.anatomy.stories.tsx +3 -3
- package/src/components/Select/select.spec.md +29 -17
- package/src/components/Select/select.stories.tsx +33 -0
- package/src/components/Select/select.tsx +190 -36
- package/src/components/SelectMenu/select-menu.anatomy.stories.tsx +4 -4
- package/src/components/SelectMenu/select-menu.spec.md +15 -7
- package/src/components/SelectMenu/select-menu.tsx +16 -3
- package/src/components/SelectionControl/boolean-value.tsx +39 -0
- package/src/components/SelectionControl/index.ts +1 -0
- package/src/components/SelectionControl/selection-item.anatomy.stories.tsx +2 -2
- package/src/components/SelectionControl/selection-item.principles.stories.tsx +2 -2
- package/src/components/SelectionControl/selection-item.spec.md +3 -3
- package/src/components/SelectionControl/selection-item.stories.tsx +3 -3
- package/src/components/Separator/separator.spec.md +2 -2
- package/src/components/Separator/separator.stories.tsx +6 -27
- package/src/components/Sheet/sheet.anatomy.stories.tsx +1 -1
- package/src/components/Sheet/sheet.principles.stories.tsx +1 -1
- package/src/components/Sheet/sheet.spec.md +2 -2
- package/src/components/Sheet/sheet.tsx +9 -5
- package/src/components/Sidebar/sidebar.anatomy.stories.tsx +16 -12
- package/src/components/Sidebar/sidebar.principles.stories.tsx +10 -8
- package/src/components/Sidebar/sidebar.spec.md +13 -15
- package/src/components/Sidebar/sidebar.stories.tsx +8 -7
- package/src/components/Sidebar/sidebar.tsx +37 -25
- package/src/components/Skeleton/skeleton.stories.tsx +4 -11
- package/src/components/Slider/slider.anatomy.stories.tsx +3 -3
- package/src/components/Slider/slider.principles.stories.tsx +2 -2
- package/src/components/Slider/slider.spec.md +5 -5
- package/src/components/Slider/slider.tsx +3 -1
- package/src/components/Steps/steps.anatomy.stories.tsx +4 -4
- package/src/components/Steps/steps.principles.stories.tsx +1 -1
- package/src/components/Steps/steps.spec.md +1 -1
- package/src/components/Switch/switch.anatomy.stories.tsx +7 -7
- package/src/components/Switch/switch.principles.stories.tsx +4 -4
- package/src/components/Switch/switch.spec.md +8 -8
- package/src/components/Switch/switch.stories.tsx +9 -24
- package/src/components/Switch/switch.tsx +17 -13
- package/src/components/Tabs/tabs.anatomy.stories.tsx +10 -7
- package/src/components/Tabs/tabs.spec.md +1 -1
- package/src/components/Tabs/tabs.tsx +21 -1
- package/src/components/Textarea/textarea.anatomy.stories.tsx +1 -1
- package/src/components/Textarea/textarea.principles.stories.tsx +1 -1
- package/src/components/Textarea/textarea.spec.md +4 -4
- package/src/components/Textarea/textarea.tsx +72 -46
- package/src/components/TimePicker/time-columns.tsx +6 -2
- package/src/components/TimePicker/time-picker.principles.stories.tsx +1 -1
- package/src/components/TimePicker/time-picker.spec.md +3 -3
- package/src/components/TimePicker/time-picker.tsx +16 -10
- package/src/components/Toast/toast.stories.tsx +7 -4
- package/src/components/Tooltip/tooltip.anatomy.stories.tsx +4 -4
- package/src/components/Tooltip/tooltip.principles.stories.tsx +3 -3
- package/src/components/Tooltip/tooltip.spec.md +1 -1
- package/src/components/Tooltip/tooltip.stories.tsx +4 -39
- package/src/components/Tooltip/tooltip.tsx +8 -6
- package/src/components/TreeView/tree-view.anatomy.stories.tsx +6 -3
- package/src/components/TreeView/tree-view.spec.md +42 -11
- package/src/components/TreeView/tree-view.stories.tsx +1 -0
- package/src/components/TreeView/tree-view.tsx +322 -17
- package/src/index.ts +588 -60
- package/src/patterns/action-bar/action-bar.spec.md +9 -0
- package/src/patterns/action-bar/action-bar.stories.tsx +6 -3
- package/src/patterns/element-anatomy/inline-action.spec.md +2 -0
- package/src/patterns/element-anatomy/item-anatomy.spec.md +14 -5
- package/src/patterns/element-anatomy/item-anatomy.stories.tsx +6 -4
- package/src/patterns/element-anatomy/item-anatomy.tsx +7 -6
- package/src/patterns/header-canonical/header-canonical.spec.md +5 -4
- package/src/patterns/overlay-surface/overlay-surface.spec.md +22 -1
- package/src/styles/tokens.css +3 -0
- package/src/tokens/README.md +1 -1
- package/src/tokens/color/color.spec.md +4 -4
- package/src/tokens/color/color.stories.tsx +3 -3
- package/src/tokens/density/density.stories.tsx +16 -3
- package/src/tokens/layoutSpace/layoutSpace.spec.md +3 -1
- package/src/tokens/motion/motion.css +33 -21
- package/src/tokens/motion/motion.spec.md +32 -15
- package/src/tokens/motion/motion.stories.tsx +58 -5
- package/src/tokens/motion/motion.ts +7 -6
- package/src/tokens/motion/overlay-motion.ts +26 -0
- package/src/tokens/orphan-tokens.spec.md +4 -4
- package/src/tokens/token-system.spec.md +1 -0
- package/src/tokens/uiSize/uiSize.css +1 -1
- package/src/tokens/uiSize/uiSize.spec.md +1 -1
- package/src/tokens/uiSize/uiSize.stories.tsx +4 -2
- package/src/tokens/utility-registry.json +160 -23
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
// @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.
|
|
2
2
|
// code-quality-allow: file-size — Select 含 3 子元件(NativeSelect/CustomSelect/ReadonlyDisplay)+ helpers + 4-mode renderer + Field SSOT consumption,split-into-files 會破壞 file-local helper closure
|
|
3
|
-
// @renderer-symmetry-allow:
|
|
3
|
+
// @renderer-symmetry-allow: 2026-07-08 WM 戰役 A 案回歸修正 — ReadonlyDisplay 現已消費 selectedItemRenderer(display bare-span / D-path / readonly / disabled 四分支),對齊 field-controls.spec.md 共享 contract (a)「display/readonly/disabled/edit 4 mode 共享同一 renderer」。前 note「display→edit unify deferred」已兌現(值內容層);chrome 結構 unify(D-path)仍為 opt-in showDisplayEndIcon。
|
|
4
4
|
import * as React from 'react'
|
|
5
5
|
import { X, ChevronDown } from 'lucide-react'
|
|
6
6
|
import type { LucideIcon } from 'lucide-react'
|
|
7
7
|
import { cn } from '@/lib/utils'
|
|
8
|
-
import type { FieldMode, FieldVariant } from '@/design-system/components/Field/field-types'
|
|
9
|
-
import { fieldWrapperStyles, bareInputStyles,
|
|
8
|
+
import type { FieldMode, FieldVariant, FieldVariantInternal, FieldWidth } from '@/design-system/components/Field/field-types'
|
|
9
|
+
import { fieldWrapperStyles, bareInputStyles, nakedCellRowModeAlign, fieldDisplayTextClass } from '@/design-system/components/Field/field-wrapper'
|
|
10
10
|
import { Tag } from '@/design-system/components/Tag/tag'
|
|
11
11
|
import { ItemInlineAction, ItemPrefix, ItemSuffix } from '@/design-system/patterns/element-anatomy/item-anatomy'
|
|
12
|
-
import { useFieldContext, useResolvedFieldSize, useResolvedFieldDisabled, useResolvedFieldMode, useResolvedFieldVariant, useResolvedFieldInvalid } from '@/design-system/components/Field/field-context'
|
|
12
|
+
import { useFieldContext, useResolvedFieldSize, useResolvedFieldDisabled, useResolvedFieldMode, useResolvedFieldVariant, useResolvedFieldInvalid, useFieldEmptyDisplay, fieldEmptyColorClass } from '@/design-system/components/Field/field-context'
|
|
13
13
|
import { SelectMenu, forwardKeyToListbox, useActiveDescendant, type SelectMenuOption } from '@/design-system/components/SelectMenu/select-menu'
|
|
14
14
|
import { useIsTouchDevice } from '@/design-system/hooks/use-is-touch-device'
|
|
15
15
|
import { useControllable } from '@/design-system/hooks/use-controllable'
|
|
@@ -53,11 +53,58 @@ export interface SelectGroupConfig {
|
|
|
53
53
|
|
|
54
54
|
// ── Types ───────────────────────────────────────────────────────────────────
|
|
55
55
|
|
|
56
|
+
/**
|
|
57
|
+
* 原生 `<select>` 屬性 allowlist(2026-07-14 API 策展 D,user 拍板「全部收窄」)。
|
|
58
|
+
*
|
|
59
|
+
* 舊契約 `extends Omit<SelectHTMLAttributes, ...>` 型別承諾**全部**原生 select 屬性,但桌機
|
|
60
|
+
* 自訂 combobox(div trigger + hidden input mirror)只有部分真生效 — 其餘 silent no-op /
|
|
61
|
+
* 被元件內部覆寫,「型別承諾 ≠ 實作出口」= footgun。改 `Pick<>` 顯式 allowlist,**只承諾
|
|
62
|
+
* 兩路徑(desktop CustomSelect / mobile NativeSelect)皆有實作出口的屬性**,盤點以 code 為準:
|
|
63
|
+
*
|
|
64
|
+
* - `id` — 桌機 trigger div / mobile `<select>`,皆 `idProp ?? fieldCtx?.id`
|
|
65
|
+
* - `name` / `form` / `required` — 桌機經 hidden input mirror(D2 2026-07-13,見 hiddenInputEl
|
|
66
|
+
* docblock)/ mobile 原生 `<select>` spread
|
|
67
|
+
* - `disabled` — 兩路徑 `useResolvedFieldDisabled` cascade → mode 分流
|
|
68
|
+
* - `className` / `style` — 兩路徑 field wrapper
|
|
69
|
+
* - `onFocus` / `onBlur` — 桌機 rest spread 到 focusable trigger(tabIndex=0)/ mobile spread
|
|
70
|
+
* - `onKeyDown` — 桌機 consumer handler 先跑再走元件導覽(dim-9)/ mobile 原生
|
|
71
|
+
* - `aria-label` / `aria-describedby` / `aria-errormessage` — 兩路徑顯式接線(fieldCtx fallback)
|
|
72
|
+
* - `autoFocus` / `autoComplete` — **mobile-only**:NativeSelect 原生 `<select>` 生效;桌機
|
|
73
|
+
* custom combobox 無原生對應 = documented no-op(桌機 autofill 由 hidden input mirror 的
|
|
74
|
+
* option-match onChange guard 承擔)
|
|
75
|
+
* - `data-*` — 兩路徑 spread passthrough(dim-9 契約;index signature 顯式承諾,見下)
|
|
76
|
+
*
|
|
77
|
+
* 被砍者(原 extends 承諾但從未生效 / 被元件覆寫,不再進型別):`multiple` / `children` /
|
|
78
|
+
* `tabIndex`(桌機恆 0)/ `aria-labelledby`(桌機由 fieldCtx.labelId / aria-label 管理,
|
|
79
|
+
* consumer 值會被覆寫)/ 其餘 generic HTMLAttributes(title / dir / onClick 等)。
|
|
80
|
+
* 需要時先接線(兩路徑等效)再入表,**不回 extends 全開**。
|
|
81
|
+
*
|
|
82
|
+
* 世界級對照:自訂 combobox 皆走 curated 原生屬性子集,無人 extends 全部 SelectHTMLAttributes —
|
|
83
|
+
* Radix Select.Root 顯式列舉 name/disabled/required/form/autoComplete
|
|
84
|
+
* (radix-ui.com/primitives/docs/components/select#root)/ Headless UI Listbox name/form/disabled
|
|
85
|
+
* (headlessui.com/react/listbox#using-with-html-forms)/ React Aria Select name/autoComplete/
|
|
86
|
+
* isDisabled/isRequired(react-spectrum.adobe.com/react-aria/Select.html)。
|
|
87
|
+
*/
|
|
56
88
|
export interface SelectProps
|
|
57
|
-
extends
|
|
89
|
+
extends Pick<
|
|
90
|
+
React.SelectHTMLAttributes<HTMLSelectElement>,
|
|
91
|
+
// ── 兩路徑皆真生效 ──
|
|
92
|
+
| 'id' | 'className' | 'style'
|
|
93
|
+
| 'name' | 'form' | 'required' | 'disabled'
|
|
94
|
+
| 'onFocus' | 'onBlur' | 'onKeyDown'
|
|
95
|
+
| 'aria-label' | 'aria-describedby' | 'aria-errormessage'
|
|
96
|
+
// ── mobile-only(NativeSelect 原生 <select> 生效;桌機 custom combobox documented no-op)──
|
|
97
|
+
| 'autoFocus' | 'autoComplete'
|
|
98
|
+
> {
|
|
99
|
+
/** `data-*` passthrough(桌機 trigger div rest spread / mobile `<select>` spread;dim-9 契約)。 */
|
|
100
|
+
[dataAttribute: `data-${string}`]: unknown
|
|
58
101
|
mode?: FieldMode
|
|
59
102
|
/** Field chrome variant. Default = context.variant ?? 'default'. Per-prop override. */
|
|
60
103
|
variant?: FieldVariant
|
|
104
|
+
/** 寬度軸 — `fill` 填滿容器(default)/ `hug` 依內容收縮:value 寬 + slot 元素寬 + gap +
|
|
105
|
+
* field 內 padding;chrome 與互動不變。場景 = detail pane inline metadata。
|
|
106
|
+
* SSOT → field-controls.spec.md「寬度軸(width: fill / hug)」。 */
|
|
107
|
+
width?: FieldWidth
|
|
61
108
|
error?: boolean
|
|
62
109
|
size?: 'sm' | 'md' | 'lg'
|
|
63
110
|
options: SelectOption[]
|
|
@@ -104,13 +151,16 @@ export interface SelectProps
|
|
|
104
151
|
*/
|
|
105
152
|
showDisplayEndIcon?: boolean
|
|
106
153
|
/**
|
|
107
|
-
*
|
|
154
|
+
* 「已選項目」客製 render(2026-05-07 v15.5;2026-07-08 A 案回歸修正擴及 4 mode)。
|
|
108
155
|
*
|
|
109
|
-
* 設了 →
|
|
156
|
+
* 設了 → 已選值不走純文字 / Tag 預設 path,改用 consumer 提供的 ReactNode(收 selectedOpt)。
|
|
157
|
+
* **display / readonly / disabled / edit 4 mode 共享同一 renderer**(field-controls.spec.md
|
|
158
|
+
* 共享 contract (a),禁 edit-only)— renderer 輸出屬「值內容」(status icon+text 等語意呈現),
|
|
159
|
+
* display 態照常渲染;A 案撤的是 affordance(chevron/outline),非值內容(field.spec.md L6 分層)。
|
|
110
160
|
* Searchable+open 仍走 input(搜尋優先)。Empty value(no selection)仍走 placeholder。
|
|
111
161
|
*
|
|
112
162
|
* 用例:PeoplePicker 用此 slot 把 single 選中的 person render 成 PersonDisplay
|
|
113
|
-
* (avatar + name)而非純文字 label。對齊 PeoplePicker = Select wrapper SSOT。
|
|
163
|
+
* (avatar + name)而非純文字 label;DataTable status 欄 icon+text。對齊 PeoplePicker = Select wrapper SSOT。
|
|
114
164
|
*/
|
|
115
165
|
selectedItemRenderer?: (selectedOpt: SelectOption) => React.ReactNode
|
|
116
166
|
/** 搜尋無結果提示(2026-07-04 Q4 拍板接線 — 原 spec 宣稱可覆寫但 prop 從未轉發)。
|
|
@@ -295,19 +345,26 @@ CustomSelectTriggerContent.displayName = 'CustomSelectTriggerContent'
|
|
|
295
345
|
|
|
296
346
|
// ── Shared readonly/disabled/display render ─────────────────────────────────
|
|
297
347
|
function ReadonlyDisplay({
|
|
298
|
-
mode, variant: variantProp, size, options, value, display, startIcon: StartIcon, className, placeholder, showDisplayEndIcon,
|
|
299
|
-
}: Pick<SelectProps, 'mode' | '
|
|
348
|
+
mode, variant: variantProp, width, size, options, value, display, startIcon: StartIcon, className, placeholder, showDisplayEndIcon, selectedItemRenderer,
|
|
349
|
+
}: Pick<SelectProps, 'mode' | 'width' | 'size' | 'options' | 'value' | 'display' | 'startIcon' | 'className' | 'placeholder' | 'showDisplayEndIcon' | 'selectedItemRenderer'> & {
|
|
350
|
+
/** @internal 2026-07-14 API 策展 E:內部 render helper 吃 FieldVariantInternal(naked 由 cell-registry 通道傳入)*/
|
|
351
|
+
variant?: FieldVariantInternal
|
|
352
|
+
}) {
|
|
300
353
|
const resolvedMode = mode ?? 'readonly'
|
|
354
|
+
const emptyDisplay = useFieldEmptyDisplay()
|
|
301
355
|
const variant = variantProp ?? 'default'
|
|
302
356
|
const sz = size ?? 'md'
|
|
303
357
|
const iconSize = getIconSize(sz)
|
|
304
|
-
|
|
358
|
+
// selectedOpt 提前 hoist(2026-07-08 A 案回歸修正):display / D-path / readonly / disabled
|
|
359
|
+
// 四分支都需要 — renderer 值內容(field-controls.spec.md contract (a) 4-mode 共享)+ tagVariant 查找。
|
|
360
|
+
const selectedOpt = options?.find(o => o.value === value)
|
|
361
|
+
const label = selectedOpt?.label ?? value
|
|
305
362
|
const iconColor = resolvedMode === 'disabled' ? 'text-fg-disabled' : 'text-fg-muted'
|
|
306
363
|
const isTextDisplay = display !== 'tag'
|
|
307
364
|
// K10+K14 fix(2026-05-04):disabled mode placeholder/empty 顯示色 → fg-disabled(neutral-6),非 fg-muted(neutral-7)
|
|
308
365
|
// user canonical:disabled 顯著性優於 muted。同時 plain mode 必須 respect placeholder prop(之前忽略 = bug)
|
|
309
|
-
const emptyColorCls = resolvedMode
|
|
310
|
-
const emptyText = placeholder ??
|
|
366
|
+
const emptyColorCls = fieldEmptyColorClass(resolvedMode)
|
|
367
|
+
const emptyText = placeholder ?? emptyDisplay
|
|
311
368
|
|
|
312
369
|
// mode='display':2 path(2026-05-08 D path Phase 1 Select canary)
|
|
313
370
|
// ❌ 預設(無 showDisplayEndIcon):純內容輸出 bare span/Tag(原行為,backward compat)
|
|
@@ -320,21 +377,35 @@ function ReadonlyDisplay({
|
|
|
320
377
|
// 2026-05-14 I2 fix(spec contract (e) display typography canonical):bare span 必套
|
|
321
378
|
// `fieldDisplayTextClass(sz)`(sm/md→text-body,lg→text-body-lg)— 對齊跨 Field
|
|
322
379
|
// family display 視覺尺寸統一。
|
|
323
|
-
if (!value) return <span className={cn(fieldDisplayTextClass(sz),
|
|
380
|
+
if (!value) return <span className={cn(fieldDisplayTextClass(sz), emptyColorCls, className)}>{emptyText}</span>
|
|
381
|
+
// 2026-07-08 A 案回歸修正:selectedItemRenderer(值內容:status icon+text 等語意呈現)
|
|
382
|
+
// display 態照常渲染 — 無 chrome 無 chevron,只有值內容本身。分層原則(field.spec.md L6):
|
|
383
|
+
// 本 renderer 歸「值內容」,恆顯;A 案撤的是 affordance(chevron/outline),非值內容。
|
|
384
|
+
// 對齊 field-controls.spec.md 共享 contract (a)「4 mode 共享同一 renderer(禁 edit-only)」。
|
|
385
|
+
if (selectedItemRenderer && selectedOpt) {
|
|
386
|
+
return (
|
|
387
|
+
<span className={cn(fieldDisplayTextClass(sz), 'inline-flex items-center min-w-0', nakedCellRowModeAlign, className)}>
|
|
388
|
+
{selectedItemRenderer(selectedOpt)}
|
|
389
|
+
</span>
|
|
390
|
+
)
|
|
391
|
+
}
|
|
324
392
|
if (isTextDisplay) return <span className={cn(fieldDisplayTextClass(sz), 'truncate', className)}>{label}</span>
|
|
325
|
-
const
|
|
326
|
-
const tVariant = selOpt?.tagVariant as 'blue' | 'green' | 'red' | 'yellow' | 'neutral' | undefined
|
|
393
|
+
const tVariant = selectedOpt?.tagVariant as 'blue' | 'green' | 'red' | 'yellow' | 'neutral' | undefined
|
|
327
394
|
return <Tag size={sz} color={tVariant} className={className}>{label}</Tag>
|
|
328
395
|
}
|
|
329
396
|
// D path opt-in: Field naked-display wrapper + ItemSuffix ChevronDown
|
|
330
|
-
const
|
|
331
|
-
const tVariant = selOpt?.tagVariant as 'blue' | 'green' | 'red' | 'yellow' | 'neutral' | undefined
|
|
397
|
+
const tVariant = selectedOpt?.tagVariant as 'blue' | 'green' | 'red' | 'yellow' | 'neutral' | undefined
|
|
332
398
|
return (
|
|
333
399
|
<div
|
|
334
|
-
className={cn(fieldWrapperStyles({ mode: 'display', variant, size: sz }), value && !isTextDisplay && tagPadding[sz], className)}
|
|
400
|
+
className={cn(fieldWrapperStyles({ mode: 'display', variant, width, size: sz }), value && !isTextDisplay && tagPadding[sz], className)}
|
|
335
401
|
data-field-mode="display"
|
|
336
402
|
>
|
|
337
|
-
{
|
|
403
|
+
{selectedItemRenderer && value && selectedOpt ? (
|
|
404
|
+
// renderer 優先(同 CustomSelectTriggerContent 優先序):值內容歸 renderer,chevron 歸 D-path chrome
|
|
405
|
+
<span className={cn('flex-1 min-w-0 inline-flex items-center', nakedCellRowModeAlign)}>
|
|
406
|
+
{selectedItemRenderer(selectedOpt)}
|
|
407
|
+
</span>
|
|
408
|
+
) : isTextDisplay ? (
|
|
338
409
|
<span className={cn(bareInputStyles, 'flex-1 min-w-0 truncate', !value && emptyColorCls)}>
|
|
339
410
|
{value ? label : emptyText}
|
|
340
411
|
</span>
|
|
@@ -356,9 +427,25 @@ function ReadonlyDisplay({
|
|
|
356
427
|
const showIndicator = variant === 'naked' ? !!showDisplayEndIcon : resolvedMode === 'disabled'
|
|
357
428
|
const ariaDisabled = resolvedMode === 'disabled' ? true : undefined
|
|
358
429
|
|
|
430
|
+
// 2026-07-08 A 案回歸修正:readonly / disabled 同樣消費 selectedItemRenderer(值內容 4-mode
|
|
431
|
+
// 共享,field-controls.spec.md contract (a);對照 PeoplePicker readonly/disabled 渲 PersonDisplay
|
|
432
|
+
// 先例)。renderer 優先於 isTextDisplay / Tag 預設 path(同 CustomSelectTriggerContent 優先序);
|
|
433
|
+
// disabled 時 wrapper span 帶 text-fg-disabled 供未自帶色的文字繼承(M24 disabled > muted)。
|
|
434
|
+
if (selectedItemRenderer && value && selectedOpt) {
|
|
435
|
+
return (
|
|
436
|
+
<div className={cn(fieldWrapperStyles({ mode: resolvedMode, variant, width, size: sz }), className)} data-field-mode={resolvedMode} aria-disabled={ariaDisabled}>
|
|
437
|
+
{StartIcon && <ItemPrefix><StartIcon size={iconSize} className={cn('pointer-events-none', iconColor)} aria-hidden /></ItemPrefix>}
|
|
438
|
+
<span className={cn('flex-1 min-w-0 inline-flex items-center', nakedCellRowModeAlign, resolvedMode === 'disabled' && 'text-fg-disabled')}>
|
|
439
|
+
{selectedItemRenderer(selectedOpt)}
|
|
440
|
+
</span>
|
|
441
|
+
{showIndicator && <ItemSuffix className="pointer-events-none"><ChevronDown size={iconSize} className={cn('shrink-0', iconColor)} aria-hidden /></ItemSuffix>}
|
|
442
|
+
</div>
|
|
443
|
+
)
|
|
444
|
+
}
|
|
445
|
+
|
|
359
446
|
if (isTextDisplay) {
|
|
360
447
|
return (
|
|
361
|
-
<div className={cn(fieldWrapperStyles({ mode: resolvedMode, variant, size: sz }), className)} data-field-mode={resolvedMode} aria-disabled={ariaDisabled}>
|
|
448
|
+
<div className={cn(fieldWrapperStyles({ mode: resolvedMode, variant, width, size: sz }), className)} data-field-mode={resolvedMode} aria-disabled={ariaDisabled}>
|
|
362
449
|
{StartIcon && <ItemPrefix><StartIcon size={iconSize} className={cn('pointer-events-none', iconColor)} aria-hidden /></ItemPrefix>}
|
|
363
450
|
<span className={cn('flex-1 min-w-0 truncate', resolvedMode === 'disabled' && 'text-fg-disabled')}>
|
|
364
451
|
{value ? label : <span className={emptyColorCls}>{emptyText}</span>}
|
|
@@ -368,11 +455,10 @@ function ReadonlyDisplay({
|
|
|
368
455
|
)
|
|
369
456
|
}
|
|
370
457
|
|
|
371
|
-
const selectedOpt = options?.find(o => o.value === value)
|
|
372
458
|
const tagVariant = selectedOpt?.tagVariant as 'blue' | 'green' | 'red' | 'yellow' | 'neutral' | undefined
|
|
373
459
|
|
|
374
460
|
return (
|
|
375
|
-
<div className={cn(fieldWrapperStyles({ mode: resolvedMode, variant, size: sz }), value && tagPadding[sz], className)} style={{ paddingRight: 'var(--field-px)' }} data-field-mode={resolvedMode} aria-disabled={ariaDisabled}>
|
|
461
|
+
<div className={cn(fieldWrapperStyles({ mode: resolvedMode, variant, width, size: sz }), value && tagPadding[sz], className)} style={{ paddingRight: 'var(--field-px)' }} data-field-mode={resolvedMode} aria-disabled={ariaDisabled}>
|
|
376
462
|
{value ? <Tag size={sz} color={tagVariant}>{label}</Tag> : <span className={emptyColorCls}>{emptyText}</span>}
|
|
377
463
|
{showIndicator && <ItemSuffix className="pointer-events-none"><ChevronDown size={iconSize} className={cn('shrink-0', iconColor)} aria-hidden /></ItemSuffix>}
|
|
378
464
|
</div>
|
|
@@ -383,10 +469,13 @@ function ReadonlyDisplay({
|
|
|
383
469
|
|
|
384
470
|
// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding
|
|
385
471
|
const NativeSelect = React.forwardRef<HTMLSelectElement, SelectProps>(
|
|
386
|
-
({ mode, variant: variantProp, error: errorProp = false, size: sizeProp, options, value: valueProp, defaultValue, onChange, placeholder, className, disabled: disabledProp, clearable = false, display = 'plain', startIcon: StartIcon, showDisplayEndIcon, id: idProp, 'aria-describedby': ariaDescribedByProp, 'aria-errormessage': ariaErrorMessageProp,
|
|
472
|
+
({ mode, variant: variantProp, width, error: errorProp = false, size: sizeProp, options, value: valueProp, defaultValue, onChange, placeholder, className, disabled: disabledProp, clearable = false, display = 'plain', startIcon: StartIcon, showDisplayEndIcon, id: idProp, 'aria-describedby': ariaDescribedByProp, 'aria-errormessage': ariaErrorMessageProp,
|
|
387
473
|
// 2026-07-04:Custom-path-only props 顯式 destructure 丟棄(不 spread 到原生 <select> 消 React
|
|
388
474
|
// unknown-prop warning);各 prop docblock 已註「僅 Custom path 生效」語意
|
|
389
|
-
|
|
475
|
+
// 2026-07-08 A 案回歸修正:selectedItemRenderer 從丟棄名單移出 — Native path 的
|
|
476
|
+
// ReadonlyDisplay(display/readonly/disabled)同樣消費值內容 renderer(contract (a) 4-mode
|
|
477
|
+
// 共享,值內容不因 pointer type 而異);native <select> edit 路徑仍不消費(原生 option 無法客製 render)。
|
|
478
|
+
searchable: _searchable, groups: _groups, loading: _loading, minRows: _minRows, emptyText: _emptyText, defaultOpen: _defaultOpen, onOpenChange: _onOpenChange, selectedItemRenderer,
|
|
390
479
|
...props }, ref) => {
|
|
391
480
|
const fieldCtx = useFieldContext()
|
|
392
481
|
const error = useResolvedFieldInvalid(errorProp)
|
|
@@ -395,7 +484,7 @@ const NativeSelect = React.forwardRef<HTMLSelectElement, SelectProps>(
|
|
|
395
484
|
const size = useResolvedFieldSize(sizeProp)
|
|
396
485
|
// 2026-06-08 SSOT:mode 經 useResolvedFieldMode(prop > 有效 disabled > fieldCtx.mode > 'edit');修 <Field mode="display"> 漏 cascade
|
|
397
486
|
const resolvedMode = useResolvedFieldMode({ mode, disabled })
|
|
398
|
-
const variant:
|
|
487
|
+
const variant: FieldVariantInternal = useResolvedFieldVariant(variantProp)
|
|
399
488
|
const iconSize = getIconSize(size)
|
|
400
489
|
// 2026-05-21 D3 audit:Controlled / Uncontrolled dual-mode via 既有 SSOT hook(同 CustomSelect)
|
|
401
490
|
const [value, setValue] = useControllable<string | null>({
|
|
@@ -414,7 +503,7 @@ const NativeSelect = React.forwardRef<HTMLSelectElement, SelectProps>(
|
|
|
414
503
|
}, [ref])
|
|
415
504
|
|
|
416
505
|
if (resolvedMode !== 'edit') {
|
|
417
|
-
return <ReadonlyDisplay mode={resolvedMode} variant={variant} size={size} options={options} value={value} display={display} startIcon={StartIcon} className={className} placeholder={placeholder} showDisplayEndIcon={showDisplayEndIcon} />
|
|
506
|
+
return <ReadonlyDisplay mode={resolvedMode} variant={variant} width={width} size={size} options={options} value={value} display={display} startIcon={StartIcon} className={className} placeholder={placeholder} showDisplayEndIcon={showDisplayEndIcon} selectedItemRenderer={selectedItemRenderer} />
|
|
418
507
|
}
|
|
419
508
|
|
|
420
509
|
const selectEl = (
|
|
@@ -453,7 +542,7 @@ const NativeSelect = React.forwardRef<HTMLSelectElement, SelectProps>(
|
|
|
453
542
|
|
|
454
543
|
if (!isTextDisplay) {
|
|
455
544
|
return (
|
|
456
|
-
<div className={cn(fieldWrapperStyles({ mode: 'edit', variant: variant, size, error }), value && tagPadding[size], 'relative',
|
|
545
|
+
<div className={cn(fieldWrapperStyles({ mode: 'edit', variant: variant, width, size, error }), value && tagPadding[size], 'relative',
|
|
457
546
|
className)}
|
|
458
547
|
style={{ paddingRight: 'var(--field-px)' }} data-field-mode="edit" data-error={error ? '' : undefined}>
|
|
459
548
|
{value ? <Tag size={size} color={nativeTagVariant} className="shrink-0 relative z-10 pointer-events-none">{label}</Tag> : <span className="text-fg-muted">{placeholder ?? '選擇...'}</span>}
|
|
@@ -466,7 +555,7 @@ const NativeSelect = React.forwardRef<HTMLSelectElement, SelectProps>(
|
|
|
466
555
|
}
|
|
467
556
|
|
|
468
557
|
return (
|
|
469
|
-
<div className={cn(fieldWrapperStyles({ mode: 'edit', variant: variant, size, error }),
|
|
558
|
+
<div className={cn(fieldWrapperStyles({ mode: 'edit', variant: variant, width, size, error }),
|
|
470
559
|
className)}
|
|
471
560
|
data-field-mode="edit" data-error={error ? '' : undefined}>
|
|
472
561
|
{StartIcon && <ItemPrefix><StartIcon size={iconSize} className="text-fg-muted pointer-events-none" aria-hidden /></ItemPrefix>}
|
|
@@ -484,7 +573,10 @@ NativeSelect.displayName = 'NativeSelect'
|
|
|
484
573
|
|
|
485
574
|
// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding
|
|
486
575
|
const CustomSelect = React.forwardRef<HTMLDivElement, SelectProps>(
|
|
487
|
-
({ mode, variant: variantProp, error: errorProp = false, size: sizeProp, options, groups, value: valueProp, defaultValue, onChange, placeholder, className, disabled: disabledProp, clearable = false, display = 'plain', startIcon: StartIcon, searchable = false, loading, minRows, emptyText, defaultOpen = false, onOpenChange, selectedItemRenderer, showDisplayEndIcon, id: idProp, 'aria-describedby': ariaDescribedByProp, 'aria-errormessage': ariaErrorMessageProp, 'aria-label': ariaLabel
|
|
576
|
+
({ mode, variant: variantProp, width, error: errorProp = false, size: sizeProp, options, groups, value: valueProp, defaultValue, onChange, placeholder, className, disabled: disabledProp, name, required, form, clearable = false, display = 'plain', startIcon: StartIcon, searchable = false, loading, minRows, emptyText, defaultOpen = false, onOpenChange, selectedItemRenderer, showDisplayEndIcon, id: idProp, 'aria-describedby': ariaDescribedByProp, 'aria-errormessage': ariaErrorMessageProp, 'aria-label': ariaLabel, onKeyDown: onKeyDownProp, style: styleProp,
|
|
577
|
+
// 2026-07-14 API 策展 D:mobile-only props(allowlist 註記)desktop 顯式丟棄 — div trigger 無原生
|
|
578
|
+
// 對應,不 spread 進 DOM(對稱 NativeSelect 丟棄 custom-path-only props 的既有 pattern)
|
|
579
|
+
autoFocus: _autoFocus, autoComplete: _autoComplete, ...rest }, ref) => {
|
|
488
580
|
const fieldCtx = useFieldContext()
|
|
489
581
|
const error = useResolvedFieldInvalid(errorProp)
|
|
490
582
|
const disabled = useResolvedFieldDisabled(disabledProp)
|
|
@@ -492,7 +584,7 @@ const CustomSelect = React.forwardRef<HTMLDivElement, SelectProps>(
|
|
|
492
584
|
const size = useResolvedFieldSize(sizeProp)
|
|
493
585
|
// 2026-06-08 SSOT:mode 經 useResolvedFieldMode(prop > 有效 disabled > fieldCtx.mode > 'edit');修 <Field mode="display"> 漏 cascade
|
|
494
586
|
const resolvedMode = useResolvedFieldMode({ mode, disabled })
|
|
495
|
-
const variant:
|
|
587
|
+
const variant: FieldVariantInternal = useResolvedFieldVariant(variantProp)
|
|
496
588
|
const iconSize = getIconSize(size)
|
|
497
589
|
// 2026-05-21 D3 audit:Controlled / Uncontrolled dual-mode via 既有 SSOT hook(M17 對齊,取代自刻 isControlled pattern)。
|
|
498
590
|
// Phase B codex 抓:之前 Custom clear 走 `onChange?.('')` 沒 setInternalValue → uncontrolled clear 失效。useControllable 統一 setter 修。
|
|
@@ -571,7 +663,7 @@ const CustomSelect = React.forwardRef<HTMLDivElement, SelectProps>(
|
|
|
571
663
|
|
|
572
664
|
// Early return AFTER all hooks(disabled / readonly / display mode 走 ReadonlyDisplay)
|
|
573
665
|
if (resolvedMode !== 'edit') {
|
|
574
|
-
return <ReadonlyDisplay mode={resolvedMode} variant={variant} size={size} options={options} value={value} display={display} startIcon={StartIcon} className={className} placeholder={placeholder} showDisplayEndIcon={showDisplayEndIcon} />
|
|
666
|
+
return <ReadonlyDisplay mode={resolvedMode} variant={variant} width={width} size={size} options={options} value={value} display={display} startIcon={StartIcon} className={className} placeholder={placeholder} showDisplayEndIcon={showDisplayEndIcon} selectedItemRenderer={selectedItemRenderer} />
|
|
575
667
|
}
|
|
576
668
|
|
|
577
669
|
// 2026-05-21 D3 Phase B codex 抓:Custom clear 用 setValue 不直接 onChange,uncontrolled clear 才能真清 internal state。
|
|
@@ -585,6 +677,44 @@ const CustomSelect = React.forwardRef<HTMLDivElement, SelectProps>(
|
|
|
585
677
|
</ItemSuffix>
|
|
586
678
|
)
|
|
587
679
|
|
|
680
|
+
// ── Hidden input form mirror(2026-07-13 D2 拍板 Option A;MUI SelectInput idiom)──
|
|
681
|
+
// 桌機自訂 combobox 無原生 form control,`name` / `required` / `form` 歷史上被靜默丟棄
|
|
682
|
+
// (mobile NativeSelect 卻經 {...props} 生效 = 雙路徑提交語義不對稱 footgun)。
|
|
683
|
+
// 補 visually-hidden `<input>` 鏡像,讓桌機也參與原生表單(裸 <form> submit / FormData):
|
|
684
|
+
// - value **單向鏡射**受控 state(useControllable)= projection,非第二 SSOT(M17);
|
|
685
|
+
// 空值時 value=''(不採 Radix hidden <select> 完整 option tree — 其無空 option 時
|
|
686
|
+
// default 第一項,radix-ui/primitives issue #3521 documented footgun)
|
|
687
|
+
// - 原生 required 僅收顯式 `required` prop(裸表單 opt-in);fieldCtx.required 續走
|
|
688
|
+
// aria-required + useFormValidation(RHF)方法論(form-validation.spec.md 規則 1-9),
|
|
689
|
+
// 不對既有 Field+RHF consumer 注入瀏覽器原生 bubble
|
|
690
|
+
// - onChange 唯一用途 = 瀏覽器 autofill:match 既有非 disabled option 才經 canonical
|
|
691
|
+
// setter 寫回(對齊 MUI SelectInput handleChange 同款 guard),其餘輸入忽略
|
|
692
|
+
// - aria-hidden + tabIndex={-1}:AT 與 Tab 序皆不見(semantics 由 role=combobox trigger
|
|
693
|
+
// own);form-validation 規則 8 focus-first-error 以 DOM name 定位 → 可聚焦 mirror →
|
|
694
|
+
// trigger focus-within 顯示 focus 邊框(桌機 Select 脫離「非 native 控件略過」fallback)
|
|
695
|
+
// 世界級對照:MUI Select 非原生模式 opacity-0 hidden input 攜 name/required
|
|
696
|
+
// (github.com/mui/material-ui .../Select/SelectInput.js;Select.test.js 官方測試涵蓋
|
|
697
|
+
// required 阻止提交 + FormData 值)+ React Spectrum HiddenSelect(github.com/adobe/
|
|
698
|
+
// react-spectrum .../@react-aria/select/src/HiddenSelect.tsx)+ Headless UI Listbox
|
|
699
|
+
// name → hidden input kept in sync(headlessui.com/react/listbox#using-with-html-forms)。
|
|
700
|
+
const hiddenInputEl = (
|
|
701
|
+
<input
|
|
702
|
+
type="text"
|
|
703
|
+
name={name}
|
|
704
|
+
form={form}
|
|
705
|
+
value={value ?? ''}
|
|
706
|
+
onChange={(e) => {
|
|
707
|
+
const matched = options.find((o) => o.value === e.target.value && !o.disabled)
|
|
708
|
+
if (matched) setValue(matched.value)
|
|
709
|
+
}}
|
|
710
|
+
required={required || undefined}
|
|
711
|
+
disabled={disabled}
|
|
712
|
+
aria-hidden="true"
|
|
713
|
+
tabIndex={-1}
|
|
714
|
+
className="absolute bottom-0 left-0 w-full opacity-0 pointer-events-none"
|
|
715
|
+
/>
|
|
716
|
+
)
|
|
717
|
+
|
|
588
718
|
const triggerContent = (
|
|
589
719
|
<CustomSelectTriggerContent
|
|
590
720
|
searchable={searchable}
|
|
@@ -612,6 +742,16 @@ const CustomSelect = React.forwardRef<HTMLDivElement, SelectProps>(
|
|
|
612
742
|
|
|
613
743
|
const trigger = (
|
|
614
744
|
<div
|
|
745
|
+
// passthrough(2026-07-14 dim-9 修;同日 API 策展 D 收窄):SelectProps allowlist
|
|
746
|
+
// (Pick<SelectHTMLAttributes>,見 SelectProps docblock)承諾的 data-* / onFocus /
|
|
747
|
+
// onBlur 等 attrs,mobile NativeSelect 經 {...props} spread 到 <select> 生效,desktop
|
|
748
|
+
// 原本完整列舉全丟 → 同一 <Select> API 隨 pointer type 靜默失效(consumer data-testid /
|
|
749
|
+
// onBlur mobile 有、desktop 無)。rest 先 spread —— component 契約 attrs(role / aria-* /
|
|
750
|
+
// id / tabIndex / onKeyDown / className)在後,不可被覆寫;consumer onKeyDown 經
|
|
751
|
+
// onKeyDownProp 在 handler 內先跑(見下)。mobile-only attrs(autoFocus / autoComplete)
|
|
752
|
+
// 已在 destructure 層丟棄不進 rest(div trigger 無原生語意;desktop 表單語意由 hidden
|
|
753
|
+
// input mirror 承擔,見 D2 docblock)。
|
|
754
|
+
{...(rest as unknown as React.HTMLAttributes<HTMLDivElement>)}
|
|
615
755
|
ref={ref}
|
|
616
756
|
id={idProp ?? fieldCtx?.id}
|
|
617
757
|
role="combobox"
|
|
@@ -623,23 +763,36 @@ const CustomSelect = React.forwardRef<HTMLDivElement, SelectProps>(
|
|
|
623
763
|
// (field-context.ts labelId jsDoc 明文);consumer aria-label 優先(對齊 slider.tsx:166 guard canonical)。
|
|
624
764
|
aria-labelledby={ariaLabel ? undefined : fieldCtx?.labelId}
|
|
625
765
|
aria-invalid={error || undefined}
|
|
626
|
-
aria-required
|
|
766
|
+
// D2(2026-07-13):顯式 required prop 同步進 aria-required(mirror 本身 aria-hidden,
|
|
767
|
+
// AT 看不到其 required;語意由 trigger 播報 — 對齊 mobile 原生 <select required> 行為)
|
|
768
|
+
aria-required={(required || fieldCtx?.required) || undefined}
|
|
627
769
|
aria-describedby={ariaDescribedByProp ?? fieldCtx?.descriptionId}
|
|
628
770
|
aria-errormessage={ariaErrorMessageProp ?? (error ? fieldCtx?.errorId : undefined)}
|
|
629
771
|
tabIndex={0}
|
|
630
772
|
className={cn(
|
|
631
|
-
fieldWrapperStyles({ mode: 'edit', variant: variant, size, error }),
|
|
773
|
+
fieldWrapperStyles({ mode: 'edit', variant: variant, width, size, error }),
|
|
632
774
|
!isTextDisplay && value && !searchable && tagPadding[size],
|
|
633
775
|
// 2026-05-06 v13.3 SSOT retire:per-control `open && 'border-primary'` 移除。Field default
|
|
634
776
|
// state machine `data-[state=open]:border-border-hover`(灰深)處理 open。
|
|
635
777
|
// 2026-07-04 Q1:error border 同樣收進 fieldWrapperStyles error variant(error 勝 focus 色)。
|
|
636
|
-
|
|
778
|
+
// D2(2026-07-13):`relative` = hidden input mirror 的 absolute 定位容器
|
|
779
|
+
// (fieldWrapperStyles base 無 relative;對齊 NativeSelect tag 分支同款顯式加法)。
|
|
780
|
+
'relative cursor-pointer',
|
|
637
781
|
className,
|
|
638
782
|
)}
|
|
639
|
-
style={!isTextDisplay ? { paddingRight: 'var(--field-px)' } : undefined}
|
|
783
|
+
style={styleProp || !isTextDisplay ? { ...styleProp, ...(!isTextDisplay ? { paddingRight: 'var(--field-px)' } : undefined) } : undefined}
|
|
640
784
|
data-field-mode="edit"
|
|
641
785
|
data-error={error ? '' : undefined}
|
|
642
786
|
onKeyDown={(e) => {
|
|
787
|
+
// passthrough(dim-9):consumer onKeyDown 先跑 —— 對齊 native path(<select> 上
|
|
788
|
+
// consumer handler 同樣最先收到);component 導覽邏輯照舊在後。
|
|
789
|
+
onKeyDownProp?.(e as unknown as React.KeyboardEvent<HTMLSelectElement>)
|
|
790
|
+
// 2026-07-14 dim-10 修:內層清除按鈕(SelectClearButton → ItemInlineAction <button>)
|
|
791
|
+
// 的 Enter/Space 會 bubble 到本 handler 被 preventDefault 吞掉 → 鍵盤無法清除。
|
|
792
|
+
// 事件源自 descendant button 時不執行 trigger 鍵盤邏輯(保留原生 activation)。
|
|
793
|
+
// 注意:不能一刀切 e.target !== e.currentTarget — searchable inline <input> 的
|
|
794
|
+
// 鍵盤事件仍需下方 forwardKeyToListbox 轉送選單導覽。
|
|
795
|
+
if (e.target !== e.currentTarget && (e.target as HTMLElement).closest?.('button')) return
|
|
643
796
|
// 2026-07-05 D4 P0:open 後 ArrowUp/Down/Enter 轉送 cmdk root(見 select-menu.tsx
|
|
644
797
|
// forwardKeyToListbox docblock — 原「open 後不攔讓方向鍵導覽」在跨 DOM 子樹機制上不成立)
|
|
645
798
|
if (open && forwardKeyToListbox(listboxId, e)) return
|
|
@@ -657,6 +810,7 @@ const CustomSelect = React.forwardRef<HTMLDivElement, SelectProps>(
|
|
|
657
810
|
{triggerContent}
|
|
658
811
|
{clearEl}
|
|
659
812
|
{chevronEl}
|
|
813
|
+
{hiddenInputEl}
|
|
660
814
|
</div>
|
|
661
815
|
)
|
|
662
816
|
|
|
@@ -34,10 +34,10 @@ const statusOptions: SelectMenuOption[] = [
|
|
|
34
34
|
]
|
|
35
35
|
|
|
36
36
|
const reviewerOptions: SelectMenuOption[] = [
|
|
37
|
-
{ value: 'alice', label: 'Ada Chen', avatar: { alt: 'Ada Chen', color: 'indigo' }, description: 'Frontend' },
|
|
38
|
-
{ value: 'bob', label: '張美真', avatar: { alt: '張美真', color: 'magenta' }, description: 'Platform' },
|
|
39
|
-
{ value: 'carol', label: '林伯彥', avatar: { alt: '林伯彥', color: 'green' }, description: 'Infra' },
|
|
40
|
-
{ value: 'dave', label: '黃怡君', avatar: { alt: '黃怡君', color: 'turquoise' }, description: 'QA' },
|
|
37
|
+
{ value: 'alice', label: 'Ada Chen', avatar: { src: 'https://i.pravatar.cc/48?u=ada-chen', alt: 'Ada Chen', color: 'indigo' }, description: 'Frontend' },
|
|
38
|
+
{ value: 'bob', label: '張美真', avatar: { src: 'https://i.pravatar.cc/48?u=zhang-meizhen', alt: '張美真', color: 'magenta' }, description: 'Platform' },
|
|
39
|
+
{ value: 'carol', label: '林伯彥', avatar: { src: 'https://i.pravatar.cc/48?u=lin-boyan', alt: '林伯彥', color: 'green' }, description: 'Infra' },
|
|
40
|
+
{ value: 'dave', label: '黃怡君', avatar: { src: 'https://i.pravatar.cc/48?u=huang-yijun', alt: '黃怡君', color: 'turquoise' }, description: 'QA' },
|
|
41
41
|
]
|
|
42
42
|
|
|
43
43
|
const labelOptions: SelectMenuOption[] = [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
component: SelectMenu
|
|
3
|
-
family: 4
|
|
3
|
+
family: composite # 2026-07-14 修自我矛盾:原 Phase-1 mechanical 填 4,與 body「Layout Family:非上述 family — composite / multi-section」矛盾;Family 4 是 trigger 側 field control(Select / Combobox)的事
|
|
4
4
|
variants: {}
|
|
5
5
|
sizes: {}
|
|
6
6
|
traits:
|
|
@@ -29,7 +29,7 @@ SelectMenu 是 **Popover + Command 組成的完整下拉選單浮層**——提
|
|
|
29
29
|
|
|
30
30
|
## Controlled-only rationale(Dim 26)
|
|
31
31
|
|
|
32
|
-
本元件刻意採 **controlled-only** 模式——**scope 限 value 軸**:value 軸走 `value` + `onValueChange`(型別宣告 optional、無 runtime 強制,但 internal 消費者 Select / Combobox / PeoplePicker 一律傳入),不支援 `defaultValue` uncontrolled fallback。open 軸自 2026-06-11 R2 起走 `useControllable` dual-mode(`open` + `defaultOpen` + `onOpenChange`,`select-menu.tsx:
|
|
32
|
+
本元件刻意採 **controlled-only** 模式——**scope 限 value 軸**:value 軸走 `value` + `onValueChange`(型別宣告 optional、無 runtime 強制,但 internal 消費者 Select / Combobox / PeoplePicker 一律傳入),不支援 `defaultValue` uncontrolled fallback。open 軸自 2026-06-11 R2 起走 `useControllable` dual-mode(`open` + `defaultOpen` + `onOpenChange`,`select-menu.tsx:187-191`),不在 controlled-only 範圍。
|
|
33
33
|
|
|
34
34
|
**為什麼**:
|
|
35
35
|
- 內部狀態複雜(search filter / range / menu open state)跟 `value` 雙向 sync 會產生 race condition
|
|
@@ -93,7 +93,7 @@ Popover(浮動容器,handle 展開 / 定位)
|
|
|
93
93
|
|
|
94
94
|
## Creatable(建立新選項)
|
|
95
95
|
|
|
96
|
-
透過 `creatable` + `onCreate` 啟用。search 非空且**無 label 完全相同(忽略大小寫)的既有選項**時,顯示 create row(`Plus` icon + `createLabel(query)`,預設「直接使用「xxx
|
|
96
|
+
透過 `creatable` + `onCreate` 啟用。search 非空且**無 label 完全相同(忽略大小寫)的既有選項**時,顯示 create row(`Plus` icon + `createLabel(query)`,預設「直接使用「xxx」」,`select-menu.tsx:164`;顯示/隱藏判斷 `:261-266`,render `:471-495`)——完全同名時自動隱藏,防重複建立。
|
|
97
97
|
|
|
98
98
|
**何時啟用**:
|
|
99
99
|
- Tag input 允許使用者建立新 tag
|
|
@@ -160,7 +160,7 @@ Popover(浮動容器,handle 展開 / 定位)
|
|
|
160
160
|
- **Disabled trigger**:trigger 由 consumer(Select / Combobox / PeoplePicker)的 `disabled` prop own,本元件不獨立 disable trigger。
|
|
161
161
|
- **Loading**:已 codify(見「Loading」段),`loading=true` 且無可顯示選項時 empty slot 渲 panel-center Empty + CircularProgress 48px(stale options 保留)。
|
|
162
162
|
- **Empty**:已 codify(見「Empty state」段),搜尋無結果 + 非 creatable 時渲 emptyText;creatable 時保留 create row(可鍵盤選取)。
|
|
163
|
-
- **Creatable + search 與既有選項完全同名**(忽略大小寫):create row 隱藏(防重複建立,`select-menu.tsx:
|
|
163
|
+
- **Creatable + search 與既有選項完全同名**(忽略大小寫):create row 隱藏(防重複建立,`select-menu.tsx:261-266`);選取既有選項為唯一路徑。
|
|
164
164
|
- **Dark mode**:走 Popover / MenuItem semantic token 自動 adapt。
|
|
165
165
|
- **Density**:row height 由 `MenuItem` SSOT 控(sm/md/lg);SelectMenu 不獨立 own density。
|
|
166
166
|
- **大量選項(> 100)**:建議上游分頁 / 搜尋收斂或考慮 windowing(對齊 Ant Select / MUI Autocomplete 慣例);本元件無虛擬化。
|
|
@@ -169,9 +169,13 @@ Popover(浮動容器,handle 展開 / 定位)
|
|
|
169
169
|
|
|
170
170
|
## 為何無 ColorMatrix
|
|
171
171
|
|
|
172
|
-
SelectMenu 是**多區塊 composite primitive
|
|
172
|
+
SelectMenu 是**多區塊 composite primitive**,色彩全數消費 DS semantic token、不新增 token;但 2026-07-05 D4 後互動色的 **owner 分層**如下(修正舊述「視覺完全繼承內層 primitive」):
|
|
173
173
|
|
|
174
|
-
-
|
|
174
|
+
- **surface / border / elevation**:`PopoverContent` 自設 `bg-surface-raised` + `border-border` + `--elevation-200`(`select-menu.tsx:308-320`)。
|
|
175
|
+
- **hover / selected 互動 bg**:owner 是**外層 cmdk `CommandItem`** —— 鍵盤 cursor base `bg-neutral-hover`(command.tsx `data-[selected=true]`)+ 單選 persistent selected `bg-neutral-selected` + selected × cursor 加深一階 `bg-neutral-selected-active`(`select-menu.tsx:439-442`,鏡射 DropdownMenu Q2/D4 canonical)。
|
|
176
|
+
- **內層 `MenuItem`**:強制 `!bg-transparent` 純視覺排版(`select-menu.tsx:460` 選項列 / `:489` create 列),不 own 互動 bg。
|
|
177
|
+
|
|
178
|
+
**無 ColorMatrix 的理由不變**:上述全是既有 token family(`neutral-hover / neutral-selected / neutral-selected-active`,與 DropdownMenu / MenuItem 同組),SelectMenu 不擁有獨立色彩決策;加 ColorMatrix 只會重複 MenuItem / DropdownMenu / Popover 的矩陣。
|
|
175
179
|
|
|
176
180
|
對應 anatomy story:保留 `Overview` / `Inspector` / `SizeMatrix` / `StateBehavior`,額外追加元件特有的 `ModeMatrix`(single / multi / searchable / creatable / grouped 等功能組合矩陣,這是 SelectMenu 真正的決策面向——取代 ColorMatrix)。
|
|
177
181
|
|
|
@@ -179,7 +183,7 @@ SelectMenu 是**多區塊 composite primitive**,不擁有獨立色彩決策:
|
|
|
179
183
|
|
|
180
184
|
## shadcn passthrough 例外說明
|
|
181
185
|
|
|
182
|
-
SelectMenu 是 **composite**(Popover trigger + Command search + 滾動 MenuItem list + 浮動 surface),純 declarative API。**套 `forwardRef` 簽名但 ref 不附著、無 `...props` spread**(shadcn forwardRef + displayName 統一;`select-menu.tsx:
|
|
186
|
+
SelectMenu 是 **composite**(Popover trigger + Command search + 滾動 MenuItem list + 浮動 surface),純 declarative API。**套 `forwardRef` 簽名但 ref 不附著、無 `...props` spread**(shadcn forwardRef + displayName 統一;`select-menu.tsx:151-154` rationale):
|
|
183
187
|
|
|
184
188
|
- **沒有單一 DOM root 可 ref**:trigger / search input / list / content portal 各自 DOM tree 離散
|
|
185
189
|
- **`...props` spread 目標不明**:composite 的 root wrapper 只是 control 容器,spread 到那裡 consumer 無從預期作用
|
|
@@ -192,6 +196,10 @@ SelectMenu 是 **composite**(Popover trigger + Command search + 滾動 MenuItem
|
|
|
192
196
|
|
|
193
197
|
---
|
|
194
198
|
|
|
199
|
+
## Option schema 增項紀錄
|
|
200
|
+
|
|
201
|
+
- `iconClassName?`(2026-07-08):option icon 染色通道(status 類彩色選項;WM 戰役——原窄 schema 逼 consumer 手刻 bare trigger)。disabled 色仍勝出(menu-item.tsx 順序保證)。
|
|
202
|
+
|
|
195
203
|
## 相關
|
|
196
204
|
|
|
197
205
|
- `../Menu/menu-item.spec.md` — 選項 row 的 item-layout 共用規則(SelectMenu 消費 MenuItem)
|
|
@@ -44,6 +44,8 @@ export interface SelectMenuOption {
|
|
|
44
44
|
label: string
|
|
45
45
|
description?: string
|
|
46
46
|
icon?: LucideIcon
|
|
47
|
+
/** icon 染色 className(2026-07-08:status 類彩色 option;M30 primitive schema 擴充) */
|
|
48
|
+
iconClassName?: string
|
|
47
49
|
avatar?: AvatarData
|
|
48
50
|
disabled?: boolean
|
|
49
51
|
group?: string
|
|
@@ -352,8 +354,13 @@ const SelectMenu = React.forwardRef<HTMLElement, SelectMenuProps>(function Selec
|
|
|
352
354
|
// `state.value ||` guard 不覆蓋(dist source 驗證);Popover 關閉即 unmount(無
|
|
353
355
|
// forceMount)→ 每次開啟 remount 重新生效。需搭配下方 CommandItem value={opt.value}。
|
|
354
356
|
defaultValue={selectedOption?.value}
|
|
355
|
-
// 2026-07-06 A11y:combobox accessible name
|
|
356
|
-
//
|
|
357
|
+
// 2026-07-06 A11y:combobox accessible name。2026-07-14 修正註解(cmdk 1.1.1 dist 實證):
|
|
358
|
+
// cmdk **無條件**渲 sr-only <label htmlFor={inputId}>(dist/index.mjs
|
|
359
|
+
// `createElement("label",{htmlFor:U.inputId,id:U.labelId,...},b)` — label prop 只決定
|
|
360
|
+
// 文字內容,不決定 label 元素是否渲染)。故 non-searchable 時「空 label + htmlFor 指向
|
|
361
|
+
// 不存在的 input」仍存在(upstream cmdk 限制;空 label 無 accessible name,不產生朗讀
|
|
362
|
+
// 內容)。僅 searchable(真的有 input)時傳內容 = 避免 non-searchable 帶 search
|
|
363
|
+
// placeholder 的 accessible name 卻無對應 input;真要消除 orphan label 需 upstream 修。
|
|
357
364
|
label={searchable ? searchPlaceholder : undefined}
|
|
358
365
|
className="bg-transparent"
|
|
359
366
|
>
|
|
@@ -437,6 +444,7 @@ const SelectMenu = React.forwardRef<HTMLElement, SelectMenuProps>(function Selec
|
|
|
437
444
|
<MenuItem
|
|
438
445
|
size={size}
|
|
439
446
|
startIcon={opt.icon}
|
|
447
|
+
startIconClassName={opt.iconClassName}
|
|
440
448
|
avatar={opt.avatar}
|
|
441
449
|
description={opt.description}
|
|
442
450
|
checkbox={multiple}
|
|
@@ -535,7 +543,12 @@ SelectMenu.displayName = 'SelectMenu'
|
|
|
535
543
|
// Phase 2 fill needed: purpose descriptions + when rationale + world-class refs
|
|
536
544
|
export const selectMenuMeta = {
|
|
537
545
|
component: 'SelectMenu',
|
|
538
|
-
family
|
|
546
|
+
// 2026-07-14 修 family 自我矛盾:原 Phase-1 mechanical 填 4(Field control),與 spec body
|
|
547
|
+
// 「Layout Family:非上述 family — composite / multi-section」矛盾。SelectMenu 是 Popover +
|
|
548
|
+
// Command composite overlay(trigger 由 consumer Select / Combobox own,Family 4 是 trigger
|
|
549
|
+
// 側 field control 的事);對齊 DS composite 慣例(Accordion / Command / Popover meta 皆
|
|
550
|
+
// family: null + spec frontmatter composite)。
|
|
551
|
+
family: null, // non-family composite / overlay(對齊 spec frontmatter composite)
|
|
539
552
|
variants: {
|
|
540
553
|
|
|
541
554
|
},
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// @internal — boolean 值展示符號 SSOT(display / readonly 態);consumer 用 Checkbox / Switch 不直用。
|
|
2
|
+
// ── 消費的 SSOT ──
|
|
3
|
+
// - lucide-react Check / X(DS icon canonical:成功/勾 = Check、關閉/否 = X,見 ui-development.md「Icon canonical」)
|
|
4
|
+
// - components/Field/field-context.ts(FieldSize 型別 → icon 尺寸對齊 value 文字)
|
|
5
|
+
// - tokens/color:text-foreground(展示值中性色,同 readonly value / 空值 canonical)
|
|
6
|
+
//
|
|
7
|
+
// 世界級對照(M22,2026-07-09 本 session 查證):read-only boolean 顯示 = 勾/叉 icon,中性色(非紅綠):
|
|
8
|
+
// MUI X DataGrid boolean cell = Check / Close(X)icon,default 中性色(非 red/green)
|
|
9
|
+
// Refine(Ant)BooleanField https://refine.dev/docs/ui-integrations/ant-design/components/fields/boolean-field/ = 勾 / 叉
|
|
10
|
+
// React-admin BooleanField https://marmelab.com/react-admin/BooleanField.html = TrueIcon / FalseIcon(預設 勾 / 叉)
|
|
11
|
+
// 為何中性色不用紅叉綠勾:false = 「否」值本身,非「錯誤 / 被拒」;error / warning 是另一條 error token 通道,
|
|
12
|
+
// 不靠此 glyph 表達。true/false 對稱同色 = MUI/React-admin 共識。
|
|
13
|
+
import { Check, X } from 'lucide-react'
|
|
14
|
+
import { cn } from '@/lib/utils'
|
|
15
|
+
import type { FieldSize } from '@/design-system/components/Field/field-context'
|
|
16
|
+
|
|
17
|
+
// icon 尺寸對齊 value 文字(fieldDisplayTextClass:sm/md → text-body 14px、lg → text-body-lg 16px)。
|
|
18
|
+
const booleanIconSize: Record<FieldSize, string> = {
|
|
19
|
+
sm: 'size-3.5', // 14px
|
|
20
|
+
md: 'size-3.5', // 14px
|
|
21
|
+
lg: 'size-4', // 16px
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* BooleanValueIcon — boolean 值的展示符號(true = Check 勾 / false = X 叉),中性 `text-foreground` 色。
|
|
26
|
+
* Checkbox / Switch 的 display + readonly 態、DataTable boolean cell(經 Checkbox)全消費此 SSOT。
|
|
27
|
+
*/
|
|
28
|
+
export function BooleanValueIcon({
|
|
29
|
+
checked,
|
|
30
|
+
size = 'md',
|
|
31
|
+
className,
|
|
32
|
+
}: {
|
|
33
|
+
checked: boolean
|
|
34
|
+
size?: FieldSize
|
|
35
|
+
className?: string
|
|
36
|
+
}) {
|
|
37
|
+
const Icon = checked ? Check : X
|
|
38
|
+
return <Icon aria-hidden className={cn(booleanIconSize[size], 'text-foreground', className)} />
|
|
39
|
+
}
|
|
@@ -439,7 +439,7 @@ export const PrefixAlignment: Story = {
|
|
|
439
439
|
<div className="border border-divider rounded-md p-4 bg-muted">
|
|
440
440
|
<SelectionItem
|
|
441
441
|
control={<Checkbox defaultChecked />}
|
|
442
|
-
avatar={{ alt: 'Ada Chen', color: 'indigo' }}
|
|
442
|
+
avatar={{ src: 'https://i.pravatar.cc/48?u=ada-chen', alt: 'Ada Chen', color: 'indigo' }}
|
|
443
443
|
label="Ada Chen"
|
|
444
444
|
/>
|
|
445
445
|
</div>
|
|
@@ -451,7 +451,7 @@ export const PrefixAlignment: Story = {
|
|
|
451
451
|
<div className="border border-divider rounded-md p-4 bg-muted">
|
|
452
452
|
<SelectionItem
|
|
453
453
|
control={<Checkbox defaultChecked />}
|
|
454
|
-
avatar={{ alt: 'Ada Chen', color: 'indigo' }}
|
|
454
|
+
avatar={{ src: 'https://i.pravatar.cc/64?u=ada-chen', alt: 'Ada Chen', color: 'indigo' }}
|
|
455
455
|
label="Ada Chen"
|
|
456
456
|
description="Design Engineer · Frontend team"
|
|
457
457
|
/>
|