@qijenchen/design-system 0.1.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +163 -0
- package/dist/components/Accordion/accordion.d.ts +37 -0
- package/dist/components/Accordion/accordion.d.ts.map +1 -0
- package/dist/components/Accordion/accordion.js +78 -0
- package/dist/components/Accordion/accordion.js.map +1 -0
- package/dist/components/Alert/alert.d.ts +47 -0
- package/dist/components/Alert/alert.d.ts.map +1 -0
- package/dist/components/Alert/alert.js +132 -0
- package/dist/components/Alert/alert.js.map +1 -0
- package/dist/components/AppShell/_demo-helpers.d.ts +49 -0
- package/dist/components/AppShell/_demo-helpers.d.ts.map +1 -0
- package/dist/components/AppShell/app-shell.d.ts +76 -0
- package/dist/components/AppShell/app-shell.d.ts.map +1 -0
- package/dist/components/AppShell/app-shell.js +214 -0
- package/dist/components/AppShell/app-shell.js.map +1 -0
- package/dist/components/AspectRatio/aspect-ratio.d.ts +40 -0
- package/dist/components/AspectRatio/aspect-ratio.d.ts.map +1 -0
- package/dist/components/AspectRatio/aspect-ratio.js +23 -0
- package/dist/components/AspectRatio/aspect-ratio.js.map +1 -0
- package/dist/components/Avatar/avatar.d.ts +85 -0
- package/dist/components/Avatar/avatar.d.ts.map +1 -0
- package/dist/components/Avatar/avatar.js +195 -0
- package/dist/components/Avatar/avatar.js.map +1 -0
- package/dist/components/Badge/badge.d.ts +43 -0
- package/dist/components/Badge/badge.d.ts.map +1 -0
- package/dist/components/Badge/badge.js +69 -0
- package/dist/components/Badge/badge.js.map +1 -0
- package/dist/components/Breadcrumb/breadcrumb.d.ts +163 -0
- package/dist/components/Breadcrumb/breadcrumb.d.ts.map +1 -0
- package/dist/components/Breadcrumb/breadcrumb.js +300 -0
- package/dist/components/Breadcrumb/breadcrumb.js.map +1 -0
- package/dist/components/BulkActionBar/bulk-action-bar.d.ts +46 -0
- package/dist/components/BulkActionBar/bulk-action-bar.d.ts.map +1 -0
- package/dist/components/BulkActionBar/bulk-action-bar.js +78 -0
- package/dist/components/BulkActionBar/bulk-action-bar.js.map +1 -0
- package/dist/components/Button/button-group.d.ts +49 -0
- package/dist/components/Button/button-group.d.ts.map +1 -0
- package/dist/components/Button/button-group.js +46 -0
- package/dist/components/Button/button-group.js.map +1 -0
- package/dist/components/Button/button.d.ts +203 -0
- package/dist/components/Button/button.d.ts.map +1 -0
- package/dist/components/Button/button.js +309 -0
- package/dist/components/Button/button.js.map +1 -0
- package/dist/components/Calendar/calendar.d.ts +81 -0
- package/dist/components/Calendar/calendar.d.ts.map +1 -0
- package/dist/components/Calendar/calendar.js +282 -0
- package/dist/components/Calendar/calendar.js.map +1 -0
- package/dist/components/Carousel/carousel.d.ts +61 -0
- package/dist/components/Carousel/carousel.d.ts.map +1 -0
- package/dist/components/Carousel/carousel.js +276 -0
- package/dist/components/Carousel/carousel.js.map +1 -0
- package/dist/components/Chart/chart.d.ts +94 -0
- package/dist/components/Chart/chart.d.ts.map +1 -0
- package/dist/components/Chart/chart.js +233 -0
- package/dist/components/Chart/chart.js.map +1 -0
- package/dist/components/Checkbox/checkbox-group.d.ts +58 -0
- package/dist/components/Checkbox/checkbox-group.d.ts.map +1 -0
- package/dist/components/Checkbox/checkbox-group.js +28 -0
- package/dist/components/Checkbox/checkbox-group.js.map +1 -0
- package/dist/components/Checkbox/checkbox.d.ts +73 -0
- package/dist/components/Checkbox/checkbox.d.ts.map +1 -0
- package/dist/components/Checkbox/checkbox.js +125 -0
- package/dist/components/Checkbox/checkbox.js.map +1 -0
- package/dist/components/Chip/chip.d.ts +54 -0
- package/dist/components/Chip/chip.d.ts.map +1 -0
- package/dist/components/Chip/chip.js +224 -0
- package/dist/components/Chip/chip.js.map +1 -0
- package/dist/components/CircularProgress/circular-progress.d.ts +40 -0
- package/dist/components/CircularProgress/circular-progress.d.ts.map +1 -0
- package/dist/components/CircularProgress/circular-progress.js +118 -0
- package/dist/components/CircularProgress/circular-progress.js.map +1 -0
- package/dist/components/Coachmark/coachmark.d.ts +100 -0
- package/dist/components/Coachmark/coachmark.d.ts.map +1 -0
- package/dist/components/Coachmark/coachmark.js +107 -0
- package/dist/components/Coachmark/coachmark.js.map +1 -0
- package/dist/components/Combobox/combobox.d.ts +150 -0
- package/dist/components/Combobox/combobox.d.ts.map +1 -0
- package/dist/components/Combobox/combobox.js +595 -0
- package/dist/components/Combobox/combobox.js.map +1 -0
- package/dist/components/Command/command.d.ts +106 -0
- package/dist/components/Command/command.d.ts.map +1 -0
- package/dist/components/Command/command.js +123 -0
- package/dist/components/Command/command.js.map +1 -0
- package/dist/components/DataTable/active-editor-controller.d.ts +66 -0
- package/dist/components/DataTable/active-editor-controller.d.ts.map +1 -0
- package/dist/components/DataTable/cell-registry.d.ts +37 -0
- package/dist/components/DataTable/cell-registry.d.ts.map +1 -0
- package/dist/components/DataTable/cell-registry.js +377 -0
- package/dist/components/DataTable/cell-registry.js.map +1 -0
- package/dist/components/DataTable/column-types.d.ts +145 -0
- package/dist/components/DataTable/column-types.d.ts.map +1 -0
- package/dist/components/DataTable/column-types.js +17 -0
- package/dist/components/DataTable/column-types.js.map +1 -0
- package/dist/components/DataTable/data-table-column-visibility-panel.d.ts +49 -0
- package/dist/components/DataTable/data-table-column-visibility-panel.d.ts.map +1 -0
- package/dist/components/DataTable/data-table-filter-panel.d.ts +30 -0
- package/dist/components/DataTable/data-table-filter-panel.d.ts.map +1 -0
- package/dist/components/DataTable/data-table-interaction-layer.d.ts +78 -0
- package/dist/components/DataTable/data-table-interaction-layer.d.ts.map +1 -0
- package/dist/components/DataTable/data-table-interaction-layer.js +220 -0
- package/dist/components/DataTable/data-table-interaction-layer.js.map +1 -0
- package/dist/components/DataTable/data-table-sort-manager.d.ts +19 -0
- package/dist/components/DataTable/data-table-sort-manager.d.ts.map +1 -0
- package/dist/components/DataTable/data-table.d.ts +181 -0
- package/dist/components/DataTable/data-table.d.ts.map +1 -0
- package/dist/components/DataTable/data-table.js +1851 -0
- package/dist/components/DataTable/data-table.js.map +1 -0
- package/dist/components/DataTable/filter-operators.d.ts +116 -0
- package/dist/components/DataTable/filter-operators.d.ts.map +1 -0
- package/dist/components/DataTable/filter-tree.d.ts +66 -0
- package/dist/components/DataTable/filter-tree.d.ts.map +1 -0
- package/dist/components/DataTable/lib/column-meta.d.ts +49 -0
- package/dist/components/DataTable/lib/column-meta.d.ts.map +1 -0
- package/dist/components/DateGrid/date-grid.d.ts +61 -0
- package/dist/components/DateGrid/date-grid.d.ts.map +1 -0
- package/dist/components/DateGrid/date-grid.js +168 -0
- package/dist/components/DateGrid/date-grid.js.map +1 -0
- package/dist/components/DatePicker/date-picker.d.ts +119 -0
- package/dist/components/DatePicker/date-picker.d.ts.map +1 -0
- package/dist/components/DatePicker/date-picker.js +743 -0
- package/dist/components/DatePicker/date-picker.js.map +1 -0
- package/dist/components/DescriptionList/description-list.d.ts +60 -0
- package/dist/components/DescriptionList/description-list.d.ts.map +1 -0
- package/dist/components/DescriptionList/description-list.js +77 -0
- package/dist/components/DescriptionList/description-list.js.map +1 -0
- package/dist/components/Dialog/dialog.d.ts +54 -0
- package/dist/components/Dialog/dialog.d.ts.map +1 -0
- package/dist/components/Dialog/dialog.js +151 -0
- package/dist/components/Dialog/dialog.js.map +1 -0
- package/dist/components/DropdownMenu/dropdown-menu.d.ts +111 -0
- package/dist/components/DropdownMenu/dropdown-menu.d.ts.map +1 -0
- package/dist/components/DropdownMenu/dropdown-menu.js +288 -0
- package/dist/components/DropdownMenu/dropdown-menu.js.map +1 -0
- package/dist/components/Empty/empty.d.ts +40 -0
- package/dist/components/Empty/empty.d.ts.map +1 -0
- package/dist/components/Empty/empty.js +66 -0
- package/dist/components/Empty/empty.js.map +1 -0
- package/dist/components/Field/field-context.d.ts +77 -0
- package/dist/components/Field/field-context.d.ts.map +1 -0
- package/dist/components/Field/field-context.js +37 -0
- package/dist/components/Field/field-context.js.map +1 -0
- package/dist/components/Field/field-types.d.ts +5 -0
- package/dist/components/Field/field-types.d.ts.map +1 -0
- package/dist/components/Field/field-types.js +13 -0
- package/dist/components/Field/field-types.js.map +1 -0
- package/dist/components/Field/field-wrapper.d.ts +17 -0
- package/dist/components/Field/field-wrapper.d.ts.map +1 -0
- package/dist/components/Field/field-wrapper.js +252 -0
- package/dist/components/Field/field-wrapper.js.map +1 -0
- package/dist/components/Field/field.d.ts +127 -0
- package/dist/components/Field/field.d.ts.map +1 -0
- package/dist/components/Field/field.js +295 -0
- package/dist/components/Field/field.js.map +1 -0
- package/dist/components/FieldControlGroup/field-control-group.d.ts +74 -0
- package/dist/components/FieldControlGroup/field-control-group.d.ts.map +1 -0
- package/dist/components/FieldControlGroup/field-control-group.js +62 -0
- package/dist/components/FieldControlGroup/field-control-group.js.map +1 -0
- package/dist/components/FileItem/file-item.d.ts +44 -0
- package/dist/components/FileItem/file-item.d.ts.map +1 -0
- package/dist/components/FileItem/file-item.js +202 -0
- package/dist/components/FileItem/file-item.js.map +1 -0
- package/dist/components/FileUpload/file-upload.d.ts +97 -0
- package/dist/components/FileUpload/file-upload.d.ts.map +1 -0
- package/dist/components/FileUpload/file-upload.js +231 -0
- package/dist/components/FileUpload/file-upload.js.map +1 -0
- package/dist/components/FileViewer/file-viewer-types.d.ts +73 -0
- package/dist/components/FileViewer/file-viewer-types.d.ts.map +1 -0
- package/dist/components/FileViewer/file-viewer.d.ts +82 -0
- package/dist/components/FileViewer/file-viewer.d.ts.map +1 -0
- package/dist/components/FileViewer/file-viewer.js +752 -0
- package/dist/components/FileViewer/file-viewer.js.map +1 -0
- package/dist/components/FileViewer/image-renderer.d.ts +9 -0
- package/dist/components/FileViewer/image-renderer.d.ts.map +1 -0
- package/dist/components/FileViewer/image-renderer.js +165 -0
- package/dist/components/FileViewer/image-renderer.js.map +1 -0
- package/dist/components/HoverCard/hover-card.d.ts +30 -0
- package/dist/components/HoverCard/hover-card.d.ts.map +1 -0
- package/dist/components/HoverCard/hover-card.js +61 -0
- package/dist/components/HoverCard/hover-card.js.map +1 -0
- package/dist/components/Input/input.d.ts +72 -0
- package/dist/components/Input/input.d.ts.map +1 -0
- package/dist/components/Input/input.js +148 -0
- package/dist/components/Input/input.js.map +1 -0
- package/dist/components/LinkInput/link-input.d.ts +46 -0
- package/dist/components/LinkInput/link-input.d.ts.map +1 -0
- package/dist/components/LinkInput/link-input.js +215 -0
- package/dist/components/LinkInput/link-input.js.map +1 -0
- package/dist/components/Menu/menu-item.d.ts +83 -0
- package/dist/components/Menu/menu-item.d.ts.map +1 -0
- package/dist/components/Menu/menu-item.js +209 -0
- package/dist/components/Menu/menu-item.js.map +1 -0
- package/dist/components/NameCard/name-card.d.ts +85 -0
- package/dist/components/NameCard/name-card.d.ts.map +1 -0
- package/dist/components/NameCard/name-card.js +153 -0
- package/dist/components/NameCard/name-card.js.map +1 -0
- package/dist/components/Notice/notice.d.ts +69 -0
- package/dist/components/Notice/notice.d.ts.map +1 -0
- package/dist/components/Notice/notice.js +121 -0
- package/dist/components/Notice/notice.js.map +1 -0
- package/dist/components/NumberInput/number-input.d.ts +57 -0
- package/dist/components/NumberInput/number-input.d.ts.map +1 -0
- package/dist/components/NumberInput/number-input.js +131 -0
- package/dist/components/NumberInput/number-input.js.map +1 -0
- package/dist/components/OverflowIndicator/overflow-indicator.d.ts +23 -0
- package/dist/components/OverflowIndicator/overflow-indicator.d.ts.map +1 -0
- package/dist/components/OverflowIndicator/overflow-indicator.js +111 -0
- package/dist/components/OverflowIndicator/overflow-indicator.js.map +1 -0
- package/dist/components/PeoplePicker/avatar-stack-overflow.d.ts +57 -0
- package/dist/components/PeoplePicker/avatar-stack-overflow.d.ts.map +1 -0
- package/dist/components/PeoplePicker/avatar-stack-overflow.js +35 -0
- package/dist/components/PeoplePicker/avatar-stack-overflow.js.map +1 -0
- package/dist/components/PeoplePicker/people-picker-helpers.d.ts +7 -0
- package/dist/components/PeoplePicker/people-picker-helpers.d.ts.map +1 -0
- package/dist/components/PeoplePicker/people-picker-helpers.js +25 -0
- package/dist/components/PeoplePicker/people-picker-helpers.js.map +1 -0
- package/dist/components/PeoplePicker/people-picker.d.ts +77 -0
- package/dist/components/PeoplePicker/people-picker.d.ts.map +1 -0
- package/dist/components/PeoplePicker/people-picker.js +263 -0
- package/dist/components/PeoplePicker/people-picker.js.map +1 -0
- package/dist/components/PeoplePicker/person-display.d.ts +66 -0
- package/dist/components/PeoplePicker/person-display.d.ts.map +1 -0
- package/dist/components/PeoplePicker/person-display.js +203 -0
- package/dist/components/PeoplePicker/person-display.js.map +1 -0
- package/dist/components/Popover/popover.d.ts +50 -0
- package/dist/components/Popover/popover.d.ts.map +1 -0
- package/dist/components/Popover/popover.js +113 -0
- package/dist/components/Popover/popover.js.map +1 -0
- package/dist/components/ProgressBar/progress-bar.d.ts +37 -0
- package/dist/components/ProgressBar/progress-bar.d.ts.map +1 -0
- package/dist/components/ProgressBar/progress-bar.js +86 -0
- package/dist/components/ProgressBar/progress-bar.js.map +1 -0
- package/dist/components/RadioGroup/radio-group.d.ts +78 -0
- package/dist/components/RadioGroup/radio-group.d.ts.map +1 -0
- package/dist/components/RadioGroup/radio-group.js +153 -0
- package/dist/components/RadioGroup/radio-group.js.map +1 -0
- package/dist/components/Rating/rating.d.ts +46 -0
- package/dist/components/Rating/rating.d.ts.map +1 -0
- package/dist/components/Rating/rating.js +179 -0
- package/dist/components/Rating/rating.js.map +1 -0
- package/dist/components/ScrollArea/scroll-area.d.ts +45 -0
- package/dist/components/ScrollArea/scroll-area.d.ts.map +1 -0
- package/dist/components/ScrollArea/scroll-area.js +65 -0
- package/dist/components/ScrollArea/scroll-area.js.map +1 -0
- package/dist/components/SegmentedControl/segmented-control.d.ts +102 -0
- package/dist/components/SegmentedControl/segmented-control.d.ts.map +1 -0
- package/dist/components/SegmentedControl/segmented-control.js +171 -0
- package/dist/components/SegmentedControl/segmented-control.js.map +1 -0
- package/dist/components/Select/select.d.ts +102 -0
- package/dist/components/Select/select.d.ts.map +1 -0
- package/dist/components/Select/select.js +435 -0
- package/dist/components/Select/select.js.map +1 -0
- package/dist/components/SelectMenu/select-menu.d.ts +103 -0
- package/dist/components/SelectMenu/select-menu.d.ts.map +1 -0
- package/dist/components/SelectMenu/select-menu.js +239 -0
- package/dist/components/SelectMenu/select-menu.js.map +1 -0
- package/dist/components/SelectionControl/selection-item.d.ts +69 -0
- package/dist/components/SelectionControl/selection-item.d.ts.map +1 -0
- package/dist/components/SelectionControl/selection-item.js +142 -0
- package/dist/components/SelectionControl/selection-item.js.map +1 -0
- package/dist/components/Separator/separator.d.ts +17 -0
- package/dist/components/Separator/separator.d.ts.map +1 -0
- package/dist/components/Separator/separator.js +39 -0
- package/dist/components/Separator/separator.js.map +1 -0
- package/dist/components/Sheet/sheet.d.ts +56 -0
- package/dist/components/Sheet/sheet.d.ts.map +1 -0
- package/dist/components/Sheet/sheet.js +145 -0
- package/dist/components/Sheet/sheet.js.map +1 -0
- package/dist/components/Sidebar/sidebar.d.ts +195 -0
- package/dist/components/Sidebar/sidebar.d.ts.map +1 -0
- package/dist/components/Sidebar/sidebar.js +826 -0
- package/dist/components/Sidebar/sidebar.js.map +1 -0
- package/dist/components/Skeleton/skeleton.d.ts +16 -0
- package/dist/components/Skeleton/skeleton.d.ts.map +1 -0
- package/dist/components/Skeleton/skeleton.js +30 -0
- package/dist/components/Skeleton/skeleton.js.map +1 -0
- package/dist/components/Slider/slider.d.ts +48 -0
- package/dist/components/Slider/slider.d.ts.map +1 -0
- package/dist/components/Slider/slider.js +108 -0
- package/dist/components/Slider/slider.js.map +1 -0
- package/dist/components/Steps/steps.d.ts +71 -0
- package/dist/components/Steps/steps.d.ts.map +1 -0
- package/dist/components/Steps/steps.js +583 -0
- package/dist/components/Steps/steps.js.map +1 -0
- package/dist/components/Switch/switch.d.ts +112 -0
- package/dist/components/Switch/switch.d.ts.map +1 -0
- package/dist/components/Switch/switch.js +179 -0
- package/dist/components/Switch/switch.js.map +1 -0
- package/dist/components/Tabs/tabs.d.ts +104 -0
- package/dist/components/Tabs/tabs.d.ts.map +1 -0
- package/dist/components/Tabs/tabs.js +316 -0
- package/dist/components/Tabs/tabs.js.map +1 -0
- package/dist/components/Tag/tag.d.ts +86 -0
- package/dist/components/Tag/tag.d.ts.map +1 -0
- package/dist/components/Tag/tag.js +172 -0
- package/dist/components/Tag/tag.js.map +1 -0
- package/dist/components/Textarea/textarea.d.ts +74 -0
- package/dist/components/Textarea/textarea.d.ts.map +1 -0
- package/dist/components/Textarea/textarea.js +224 -0
- package/dist/components/Textarea/textarea.js.map +1 -0
- package/dist/components/TimePicker/time-columns.d.ts +46 -0
- package/dist/components/TimePicker/time-columns.d.ts.map +1 -0
- package/dist/components/TimePicker/time-columns.js +173 -0
- package/dist/components/TimePicker/time-columns.js.map +1 -0
- package/dist/components/TimePicker/time-picker.d.ts +94 -0
- package/dist/components/TimePicker/time-picker.d.ts.map +1 -0
- package/dist/components/TimePicker/time-picker.js +253 -0
- package/dist/components/TimePicker/time-picker.js.map +1 -0
- package/dist/components/Toast/toast.d.ts +61 -0
- package/dist/components/Toast/toast.d.ts.map +1 -0
- package/dist/components/Toast/toast.js +76 -0
- package/dist/components/Toast/toast.js.map +1 -0
- package/dist/components/Tooltip/tooltip.d.ts +20 -0
- package/dist/components/Tooltip/tooltip.d.ts.map +1 -0
- package/dist/components/Tooltip/tooltip.js +53 -0
- package/dist/components/Tooltip/tooltip.js.map +1 -0
- package/dist/components/TreeView/tree-view.d.ts +166 -0
- package/dist/components/TreeView/tree-view.d.ts.map +1 -0
- package/dist/components/TreeView/tree-view.js +617 -0
- package/dist/components/TreeView/tree-view.js.map +1 -0
- package/dist/hooks/use-controllable.d.ts +16 -0
- package/dist/hooks/use-controllable.d.ts.map +1 -0
- package/dist/hooks/use-controllable.js +26 -0
- package/dist/hooks/use-controllable.js.map +1 -0
- package/dist/hooks/use-is-narrow-viewport.d.ts +2 -0
- package/dist/hooks/use-is-narrow-viewport.d.ts.map +1 -0
- package/dist/hooks/use-is-narrow-viewport.js +19 -0
- package/dist/hooks/use-is-narrow-viewport.js.map +1 -0
- package/dist/hooks/use-is-touch-device.d.ts +8 -0
- package/dist/hooks/use-is-touch-device.d.ts.map +1 -0
- package/dist/hooks/use-is-touch-device.js +16 -0
- package/dist/hooks/use-is-touch-device.js.map +1 -0
- package/dist/hooks/use-overflow-items.d.ts +124 -0
- package/dist/hooks/use-overflow-items.d.ts.map +1 -0
- package/dist/hooks/use-overflow-items.js +97 -0
- package/dist/hooks/use-overflow-items.js.map +1 -0
- package/dist/index.d.ts +74 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +371 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/drag-visual.d.ts +158 -0
- package/dist/lib/drag-visual.d.ts.map +1 -0
- package/dist/lib/drag-visual.js +96 -0
- package/dist/lib/drag-visual.js.map +1 -0
- package/dist/lib/i18n/i18n-context.d.ts +105 -0
- package/dist/lib/i18n/i18n-context.d.ts.map +1 -0
- package/dist/lib/multi-select-ordering.d.ts +54 -0
- package/dist/lib/multi-select-ordering.d.ts.map +1 -0
- package/dist/lib/multi-select-ordering.js +13 -0
- package/dist/lib/multi-select-ordering.js.map +1 -0
- package/dist/lib/utils.d.ts +12 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +79 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/patterns/element-anatomy/item-anatomy.d.ts +370 -0
- package/dist/patterns/element-anatomy/item-anatomy.d.ts.map +1 -0
- package/dist/patterns/element-anatomy/item-anatomy.js +272 -0
- package/dist/patterns/element-anatomy/item-anatomy.js.map +1 -0
- package/dist/patterns/header-canonical/chrome-header.d.ts +80 -0
- package/dist/patterns/header-canonical/chrome-header.d.ts.map +1 -0
- package/dist/patterns/header-canonical/chrome-header.js +75 -0
- package/dist/patterns/header-canonical/chrome-header.js.map +1 -0
- package/dist/patterns/horizontal-overflow/horizontal-overflow.d.ts +101 -0
- package/dist/patterns/horizontal-overflow/horizontal-overflow.d.ts.map +1 -0
- package/dist/patterns/horizontal-overflow/horizontal-overflow.js +105 -0
- package/dist/patterns/horizontal-overflow/horizontal-overflow.js.map +1 -0
- package/dist/patterns/overlay-surface/overlay-surface.d.ts +28 -0
- package/dist/patterns/overlay-surface/overlay-surface.d.ts.map +1 -0
- package/dist/patterns/overlay-surface/overlay-surface.js +85 -0
- package/dist/patterns/overlay-surface/overlay-surface.js.map +1 -0
- package/dist/patterns/resize-handle/resize-handle.d.ts +102 -0
- package/dist/patterns/resize-handle/resize-handle.d.ts.map +1 -0
- package/dist/patterns/resize-handle/resize-handle.js +74 -0
- package/dist/patterns/resize-handle/resize-handle.js.map +1 -0
- package/dist/react-day-picker.css +457 -0
- package/dist/stories-helpers/anatomy/anatomy-utils.d.ts +40 -0
- package/dist/stories-helpers/anatomy/anatomy-utils.d.ts.map +1 -0
- package/dist/tokens/elevation/overlay-geometry.d.ts +12 -0
- package/dist/tokens/elevation/overlay-geometry.d.ts.map +1 -0
- package/dist/tokens/elevation/overlay-geometry.js +7 -0
- package/dist/tokens/elevation/overlay-geometry.js.map +1 -0
- package/dist/tokens/motion/motion.d.ts +15 -0
- package/dist/tokens/motion/motion.d.ts.map +1 -0
- package/dist/tokens/motion/motion.js +9 -0
- package/dist/tokens/motion/motion.js.map +1 -0
- package/dist/tokens/uiSize/icon-size.d.ts +53 -0
- package/dist/tokens/uiSize/icon-size.d.ts.map +1 -0
- package/package.json +92 -0
- package/src/README.md +32 -0
- package/src/components/Accordion/accordion.tsx +104 -0
- package/src/components/Alert/alert.tsx +188 -0
- package/src/components/AppShell/_demo-helpers.tsx +198 -0
- package/src/components/AppShell/app-shell.tsx +364 -0
- package/src/components/AspectRatio/aspect-ratio.tsx +58 -0
- package/src/components/Avatar/avatar.tsx +368 -0
- package/src/components/Badge/badge.tsx +104 -0
- package/src/components/Breadcrumb/breadcrumb.tsx +619 -0
- package/src/components/BulkActionBar/bulk-action-bar.tsx +156 -0
- package/src/components/Button/button-group.tsx +96 -0
- package/src/components/Button/button.tsx +539 -0
- package/src/components/Calendar/calendar.tsx +411 -0
- package/src/components/Carousel/carousel.tsx +371 -0
- package/src/components/Chart/chart.tsx +376 -0
- package/src/components/Checkbox/checkbox-group.tsx +94 -0
- package/src/components/Checkbox/checkbox.tsx +237 -0
- package/src/components/Chip/chip.tsx +359 -0
- package/src/components/CircularProgress/circular-progress.tsx +204 -0
- package/src/components/Coachmark/coachmark.tsx +255 -0
- package/src/components/Combobox/combobox.tsx +826 -0
- package/src/components/Command/command.tsx +187 -0
- package/src/components/DataTable/active-editor-controller.ts +72 -0
- package/src/components/DataTable/cell-registry.tsx +520 -0
- package/src/components/DataTable/column-types.ts +180 -0
- package/src/components/DataTable/data-table-column-visibility-panel.tsx +261 -0
- package/src/components/DataTable/data-table-filter-panel.tsx +813 -0
- package/src/components/DataTable/data-table-interaction-layer.tsx +483 -0
- package/src/components/DataTable/data-table-sort-manager.tsx +210 -0
- package/src/components/DataTable/data-table.css +165 -0
- package/src/components/DataTable/data-table.tsx +2924 -0
- package/src/components/DataTable/filter-operators.ts +225 -0
- package/src/components/DataTable/filter-tree.ts +313 -0
- package/src/components/DataTable/lib/column-meta.ts +79 -0
- package/src/components/DateGrid/date-grid.tsx +209 -0
- package/src/components/DatePicker/date-picker.tsx +1114 -0
- package/src/components/DescriptionList/description-list.tsx +141 -0
- package/src/components/Dialog/dialog.tsx +267 -0
- package/src/components/DropdownMenu/dropdown-menu.tsx +475 -0
- package/src/components/Empty/empty.tsx +108 -0
- package/src/components/Field/field-context.ts +136 -0
- package/src/components/Field/field-types.ts +52 -0
- package/src/components/Field/field-wrapper.tsx +348 -0
- package/src/components/Field/field.tsx +535 -0
- package/src/components/FieldControlGroup/field-control-group.tsx +136 -0
- package/src/components/FileItem/file-item.tsx +322 -0
- package/src/components/FileUpload/file-upload.tsx +326 -0
- package/src/components/FileViewer/file-viewer-types.ts +76 -0
- package/src/components/FileViewer/file-viewer.tsx +1065 -0
- package/src/components/FileViewer/image-renderer.tsx +256 -0
- package/src/components/HoverCard/hover-card.tsx +79 -0
- package/src/components/Input/input.tsx +233 -0
- package/src/components/LinkInput/link-input.tsx +304 -0
- package/src/components/Menu/menu-item.tsx +334 -0
- package/src/components/NameCard/name-card.tsx +319 -0
- package/src/components/Notice/notice.tsx +196 -0
- package/src/components/NumberInput/number-input.tsx +203 -0
- package/src/components/OverflowIndicator/overflow-indicator.tsx +156 -0
- package/src/components/PeoplePicker/avatar-stack-overflow.ts +100 -0
- package/src/components/PeoplePicker/people-picker-helpers.ts +76 -0
- package/src/components/PeoplePicker/people-picker.tsx +455 -0
- package/src/components/PeoplePicker/person-display.tsx +358 -0
- package/src/components/Popover/popover.tsx +183 -0
- package/src/components/ProgressBar/progress-bar.tsx +157 -0
- package/src/components/README.md +58 -0
- package/src/components/RadioGroup/radio-group.tsx +261 -0
- package/src/components/Rating/rating.tsx +295 -0
- package/src/components/ScrollArea/scroll-area.tsx +110 -0
- package/src/components/SegmentedControl/segmented-control.tsx +304 -0
- package/src/components/Select/select.tsx +658 -0
- package/src/components/SelectMenu/select-menu.tsx +430 -0
- package/src/components/SelectionControl/selection-item.tsx +261 -0
- package/src/components/Separator/separator.tsx +48 -0
- package/src/components/Sheet/sheet.tsx +240 -0
- package/src/components/Sidebar/sidebar.tsx +1280 -0
- package/src/components/Skeleton/skeleton.tsx +35 -0
- package/src/components/Slider/slider.tsx +158 -0
- package/src/components/Steps/steps.tsx +850 -0
- package/src/components/Switch/switch.tsx +285 -0
- package/src/components/Tabs/tabs.tsx +515 -0
- package/src/components/Tag/tag.tsx +246 -0
- package/src/components/Textarea/textarea.tsx +280 -0
- package/src/components/TimePicker/time-columns.tsx +260 -0
- package/src/components/TimePicker/time-picker.tsx +419 -0
- package/src/components/Toast/toast.tsx +129 -0
- package/src/components/Tooltip/tooltip.tsx +68 -0
- package/src/components/TreeView/tree-view.tsx +1031 -0
- package/src/hooks/use-controllable.ts +40 -0
- package/src/hooks/use-is-narrow-viewport.ts +19 -0
- package/src/hooks/use-is-touch-device.ts +21 -0
- package/src/hooks/use-overflow-items.ts +256 -0
- package/src/index.ts +85 -0
- package/src/lib/README.md +82 -0
- package/src/lib/drag-visual.ts +272 -0
- package/src/lib/i18n/README.md +60 -0
- package/src/lib/i18n/i18n-context.tsx +129 -0
- package/src/lib/multi-select-ordering.ts +61 -0
- package/src/lib/utils.ts +93 -0
- package/src/patterns/README.md +67 -0
- package/src/patterns/element-anatomy/item-anatomy.tsx +744 -0
- package/src/patterns/header-canonical/chrome-header.tsx +175 -0
- package/src/patterns/header-canonical/header-canonical.css +27 -0
- package/src/patterns/horizontal-overflow/horizontal-overflow.tsx +217 -0
- package/src/patterns/overlay-surface/overlay-surface.tsx +191 -0
- package/src/patterns/resize-handle/resize-handle.tsx +188 -0
- package/src/stories-helpers/anatomy/anatomy-utils.tsx +64 -0
- package/src/styles/preset.css +31 -0
- package/src/styles/tokens.css +35 -0
- package/src/tokens/README.md +53 -0
- package/src/tokens/color/primitives.css +429 -0
- package/src/tokens/color/semantic.css +539 -0
- package/src/tokens/elevation/overlay-geometry.ts +13 -0
- package/src/tokens/layoutSpace/layoutSpace.css +36 -0
- package/src/tokens/motion/motion.css +30 -0
- package/src/tokens/motion/motion.ts +17 -0
- package/src/tokens/opacity/opacity.css +23 -0
- package/src/tokens/radius/radius.css +19 -0
- package/src/tokens/typography/typography.css +118 -0
- package/src/tokens/uiSize/icon-size.ts +52 -0
- package/src/tokens/uiSize/uiSize.css +125 -0
|
@@ -0,0 +1,658 @@
|
|
|
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
|
+
// 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: pre-existing Select architecture(2026-05-08 D-path)— selectedItemRenderer 由 CustomSelectTriggerContent 消費(edit + trigger 模式),ReadonlyDisplay 走 separate bare-span path(no D-path)。display→edit unify deferred 下 cycle per spec contract (a) note。本 turn 只加 `nakedCellRowModeAlign` import,no behavior change to renderer symmetry contract。
|
|
4
|
+
import * as React from 'react'
|
|
5
|
+
import { X, ChevronDown } from 'lucide-react'
|
|
6
|
+
import type { LucideIcon } from 'lucide-react'
|
|
7
|
+
import { cn } from '@/lib/utils'
|
|
8
|
+
import type { FieldMode, FieldVariant } from '@/design-system/components/Field/field-types'
|
|
9
|
+
import { fieldWrapperStyles, bareInputStyles, EMPTY_DISPLAY, nakedCellRowModeAlign, fieldDisplayTextClass } from '@/design-system/components/Field/field-wrapper'
|
|
10
|
+
import { Tag } from '@/design-system/components/Tag/tag'
|
|
11
|
+
import { ItemInlineAction, ItemPrefix, ItemSuffix } from '@/design-system/patterns/element-anatomy/item-anatomy'
|
|
12
|
+
import { useFieldContext } from '@/design-system/components/Field/field-context'
|
|
13
|
+
import { SelectMenu, type SelectMenuOption } from '@/design-system/components/SelectMenu/select-menu'
|
|
14
|
+
import { useIsTouchDevice } from '@/design-system/hooks/use-is-touch-device'
|
|
15
|
+
import { useControllable } from '@/design-system/hooks/use-controllable'
|
|
16
|
+
import { ICON_SIZE } from '@/design-system/tokens/uiSize/icon-size'
|
|
17
|
+
|
|
18
|
+
// ── Tag padding per size ────────────────────────────────────────────────────
|
|
19
|
+
const tagPadding: Record<string, string> = {
|
|
20
|
+
sm: 'px-[calc((var(--field-height-sm)_-_1.25rem)_/_2)]',
|
|
21
|
+
md: 'px-[calc((var(--field-height-md)_-_1.5rem)_/_2)]',
|
|
22
|
+
lg: 'px-[calc((var(--field-height-lg)_-_1.5rem)_/_2)]',
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ── Display ─────────────────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Select 用的 option schema(2026-05-10 Issue 4 + post-prune unify):**explicit extends
|
|
29
|
+
* SelectMenuOption(primitive SSOT)** — 任何 SelectMenuOption 加 field 都自動繼承,不會 drift。
|
|
30
|
+
*
|
|
31
|
+
* Why `extends SelectMenuOption`(per user 「全盤檢查避免下次又改壞或是偏移」要求):
|
|
32
|
+
* - **schema SSOT 機械強制**:TypeScript inheritance 跟著 primitive 走,wrapper consumer 永遠
|
|
33
|
+
* 拿得到 primitive 所有 surface field
|
|
34
|
+
* - **Hook lint**(M30 `check_wrapper_primitive_schema_drift.sh`):grep `interface .*Option`
|
|
35
|
+
* 未 `extends` SelectMenuOption / 同名重複 declare 直接 BLOCK
|
|
36
|
+
*
|
|
37
|
+
* Wrapper-only field(`tagVariant`)— Select 獨有 `display='tag'` 用,SelectMenu primitive 不該知道
|
|
38
|
+
* 此 wrapper-only concern,所以 wrapper 層 extend 加上,不污染 primitive。
|
|
39
|
+
*
|
|
40
|
+
* 對齊 Polaris ChoiceList / Material Autocomplete / Carbon Dropdown 的 wrapper-vs-primitive
|
|
41
|
+
* schema-extension idiom。
|
|
42
|
+
*/
|
|
43
|
+
export interface SelectOption extends SelectMenuOption {
|
|
44
|
+
/** Tag 模式的顏色。只在 display='tag' 時生效,對應 Tag 的 variant。Wrapper-only。 */
|
|
45
|
+
tagVariant?: string
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** 分組設定 — 對齊 SelectMenuGroupConfig SSOT */
|
|
49
|
+
export interface SelectGroupConfig {
|
|
50
|
+
key: string
|
|
51
|
+
label: string
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ── Types ───────────────────────────────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
export interface SelectProps
|
|
57
|
+
extends Omit<React.SelectHTMLAttributes<HTMLSelectElement>, 'size' | 'value' | 'defaultValue' | 'onChange'> {
|
|
58
|
+
mode?: FieldMode
|
|
59
|
+
/** Field chrome variant. Default = context.variant ?? 'default'. Per-prop override. */
|
|
60
|
+
variant?: FieldVariant
|
|
61
|
+
error?: boolean
|
|
62
|
+
size?: 'sm' | 'md' | 'lg'
|
|
63
|
+
options: SelectOption[]
|
|
64
|
+
/** 分組顯示(對齊 SelectMenu groups SSOT)。option.group 對應 groups[].key */
|
|
65
|
+
groups?: SelectGroupConfig[]
|
|
66
|
+
/** Controlled value(consumer 自管 state)。傳 `value` + `onChange` 表示 controlled mode。 */
|
|
67
|
+
value?: string | null
|
|
68
|
+
/** Uncontrolled 初始值(2026-05-21 D3 audit add per user verbatim「決策三照妳建議」+「都給我做到好」)。
|
|
69
|
+
* 不傳 `value` 時 Select 自管 internal state,以 `defaultValue` 為初始值,選變更時 fire `onChange`
|
|
70
|
+
* callback 通知 consumer(但 state 仍歸 Select)。對齊 Radix Select(`defaultValue`)+ shadcn Input
|
|
71
|
+
* (`defaultValue`)+ React `<input>` dual-mode canonical。
|
|
72
|
+
* 互斥規則:同時傳 `value` + `defaultValue` 走 controlled(value 勝),`defaultValue` 僅 first-mount 用。 */
|
|
73
|
+
defaultValue?: string | null
|
|
74
|
+
onChange?: (value: string) => void
|
|
75
|
+
placeholder?: string
|
|
76
|
+
clearable?: boolean
|
|
77
|
+
display?: 'plain' | 'tag'
|
|
78
|
+
startIcon?: LucideIcon
|
|
79
|
+
/** 啟用搜尋(desktop 時 field 變 input,打字即篩選) */
|
|
80
|
+
searchable?: boolean
|
|
81
|
+
/** Loading state(2026-05-15 audit B fix per user verbatim「dropdown 隨時可開,讀取在 panel 中間 CircularProgress」)。
|
|
82
|
+
* Forward 給 SelectMenu primitive SSOT;dropdown 開啟時取代 options 顯 CircularProgress + loadingText。
|
|
83
|
+
* Trigger 不變(chevron 保留 user 隨時可點開)。對齊 Field family loading SSOT + Empty 元件 `<Empty icon={CircularProgress}/>` compose。*/
|
|
84
|
+
loading?: boolean
|
|
85
|
+
|
|
86
|
+
/** Menu list 最小列數(空狀態 / 選項少時的視覺一致 reserve)。預設 3 — 選項 < 3 時顯式縮(如 And/Or 兩選項) */
|
|
87
|
+
minRows?: number
|
|
88
|
+
/** Initial open state(uncontrolled)。對齊 Radix Popover defaultOpen canonical;DataTable cell-as-input
|
|
89
|
+
* click → 1 step open menu(Airtable / Notion canonical),consumer pass `defaultOpen` 達成。
|
|
90
|
+
* Note:Native Select(mobile)無 popover 概念,此 prop 僅 Custom path 生效。 */
|
|
91
|
+
defaultOpen?: boolean
|
|
92
|
+
/** open state 變更 callback(對齊 Radix Popover onOpenChange canonical)。
|
|
93
|
+
* DataTable cell-as-input 用:open=false 時 cell 自動 exit edit mode(避免 dismiss 後卡住)。 */
|
|
94
|
+
onOpenChange?: (open: boolean) => void
|
|
95
|
+
/**
|
|
96
|
+
* Display mode 顯 picker intrinsic end icon(2026-05-08 D path Phase 1)。
|
|
97
|
+
* 預設 false:`mode="display"` 純展示 bare span(向後相容)。
|
|
98
|
+
* `variant="naked" && mode="display"` 場景(DataTable cell)opt-in 設 true → wrap 進
|
|
99
|
+
* Field naked-display + 渲 ChevronDown ItemSuffix。**只 display mode 生效**;readonly /
|
|
100
|
+
* disabled / edit 已有 Field wrapper + suffix(不受此 prop 影響)。
|
|
101
|
+
* Authority:`data-table.spec.md:204` + `inline-action.spec.md:157`「Field family endAction(自動繼承)」。
|
|
102
|
+
* @default false
|
|
103
|
+
*/
|
|
104
|
+
showDisplayEndIcon?: boolean
|
|
105
|
+
/**
|
|
106
|
+
* Trigger 內「已選項目」客製 render(2026-05-07 v15.5)。
|
|
107
|
+
*
|
|
108
|
+
* 設了 → trigger 不走純文字 / Tag 預設 path,改用 consumer 提供的 ReactNode(收 selectedOpt)。
|
|
109
|
+
* Searchable+open 仍走 input(搜尋優先)。Empty value(no selection)仍走 placeholder。
|
|
110
|
+
*
|
|
111
|
+
* 用例:PeoplePicker 用此 slot 把 single 選中的 person render 成 PersonDisplay
|
|
112
|
+
* (avatar + name)而非純文字 label。對齊 PeoplePicker = Select wrapper SSOT。
|
|
113
|
+
*/
|
|
114
|
+
selectedItemRenderer?: (selectedOpt: SelectOption) => React.ReactNode
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ── Icon / size helpers ─────────────────────────────────────────────────────
|
|
118
|
+
// 2026-05-18 改 import ICON_SIZE SSOT(per user『做完』approval,消除 M17 違反)
|
|
119
|
+
const getIconSize = (size: string) => ICON_SIZE[size as 'sm' | 'md' | 'lg']
|
|
120
|
+
|
|
121
|
+
// ── Shared sub-components ───────────────────────────────────────────────────
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Inline clear button for Select trigger.
|
|
125
|
+
* 共用 SSOT — Native + Custom 兩變體統一消費。差別僅 onClick 內是否 stopPropagation
|
|
126
|
+
* (Custom trigger 是 combobox `<div>`,點 clear 不可冒泡到打開 menu;Native `<select>` 自有原生
|
|
127
|
+
* 行為,不需 stopPropagation)。
|
|
128
|
+
*
|
|
129
|
+
* 消費的 SSOT:
|
|
130
|
+
* - patterns/element-anatomy/item-anatomy.spec.md → ItemInlineAction(canonical row inline action)
|
|
131
|
+
*/
|
|
132
|
+
function SelectClearButton({
|
|
133
|
+
size,
|
|
134
|
+
onClear,
|
|
135
|
+
stopPropagation = false,
|
|
136
|
+
}: {
|
|
137
|
+
size: 'sm' | 'md' | 'lg'
|
|
138
|
+
onClear: () => void
|
|
139
|
+
stopPropagation?: boolean
|
|
140
|
+
}) {
|
|
141
|
+
return (
|
|
142
|
+
<span className="relative z-10">
|
|
143
|
+
<ItemInlineAction
|
|
144
|
+
size={size}
|
|
145
|
+
action={{
|
|
146
|
+
icon: X,
|
|
147
|
+
label: '清除選取', // i18n-allow: DS default inline-action label
|
|
148
|
+
onClick: stopPropagation ? (e) => { e?.stopPropagation(); onClear() } : () => onClear(),
|
|
149
|
+
}}
|
|
150
|
+
/>
|
|
151
|
+
</span>
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
SelectClearButton.displayName = 'SelectClearButton'
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Trigger content for CustomSelect — 三種顯示模式分支(searchable+open / text / tag)
|
|
158
|
+
* 抽出降低 `CustomSelect` forwardRef body 長度;邏輯本質是純展示分流,無 hook / ref。
|
|
159
|
+
*/
|
|
160
|
+
function CustomSelectTriggerContent({
|
|
161
|
+
searchable,
|
|
162
|
+
open,
|
|
163
|
+
isTextDisplay,
|
|
164
|
+
size,
|
|
165
|
+
value,
|
|
166
|
+
selectedLabel,
|
|
167
|
+
selectedOpt,
|
|
168
|
+
SelectedIcon,
|
|
169
|
+
StartIcon,
|
|
170
|
+
iconSize,
|
|
171
|
+
placeholder,
|
|
172
|
+
search,
|
|
173
|
+
setSearch,
|
|
174
|
+
inputRef,
|
|
175
|
+
selectedItemRenderer,
|
|
176
|
+
}: {
|
|
177
|
+
searchable: boolean
|
|
178
|
+
open: boolean
|
|
179
|
+
isTextDisplay: boolean
|
|
180
|
+
size: 'sm' | 'md' | 'lg'
|
|
181
|
+
value?: string | null
|
|
182
|
+
selectedLabel: string
|
|
183
|
+
selectedOpt?: SelectOption
|
|
184
|
+
SelectedIcon?: LucideIcon
|
|
185
|
+
StartIcon?: LucideIcon
|
|
186
|
+
iconSize: number
|
|
187
|
+
placeholder?: string
|
|
188
|
+
search: string
|
|
189
|
+
setSearch: (v: string) => void
|
|
190
|
+
inputRef: React.RefObject<HTMLInputElement | null>
|
|
191
|
+
selectedItemRenderer?: (selectedOpt: SelectOption) => React.ReactNode
|
|
192
|
+
}): React.ReactNode {
|
|
193
|
+
// Searchable + open: 顯示搜尋 input
|
|
194
|
+
// 2026-05-15 Bug 2 fix(Claude+Codex Step 5 比稿 consensus,user verbatim「就 A」):
|
|
195
|
+
// 撤掉 native `<input placeholder=selectedLabel>` 不可靠 ellipsis renderer(browser-specific
|
|
196
|
+
// placeholder painting,user 抓「placeholder 直接被截掉沒 ellipsis」)。改 span overlay:
|
|
197
|
+
// - input native placeholder 限「搜尋…」/「請選擇人員」trigger empty hint(無 selectedLabel)
|
|
198
|
+
// - sibling `<span aria-hidden pointer-events-none absolute inset-0 truncate>` 在 search='' 且
|
|
199
|
+
// 有 selectedLabel 時 overlay 顯該人名(memory aid,truncate-with-ellipsis 可控)
|
|
200
|
+
// 對齊 spec.md §B row 4「open + inline-search + 選 1 人 → input cursor + placeholder = 該人名 + ellipsis」。
|
|
201
|
+
// a11y guard(per codex Q2 reply):input aria-label / accessible name 來自 field/label/aria-label,
|
|
202
|
+
// **不**依賴 placeholder 當 label;overlay span aria-hidden + pointer-events-none。
|
|
203
|
+
if (searchable && open) {
|
|
204
|
+
const triggerEmptyPlaceholder = placeholder || '搜尋…' // i18n-allow: DS fallback
|
|
205
|
+
const showSelectedOverlay = !search && selectedLabel
|
|
206
|
+
return (
|
|
207
|
+
<span className="relative flex-1 min-w-0 inline-flex items-center">
|
|
208
|
+
{StartIcon && <ItemPrefix><StartIcon size={iconSize} className="text-fg-muted pointer-events-none" aria-hidden /></ItemPrefix>}
|
|
209
|
+
<input
|
|
210
|
+
ref={inputRef as React.RefObject<HTMLInputElement>}
|
|
211
|
+
value={search}
|
|
212
|
+
onChange={(e) => setSearch(e.target.value)}
|
|
213
|
+
// Native placeholder 限 trigger empty hint(無 selectedLabel 時);若已 selected,留空交給 overlay span
|
|
214
|
+
placeholder={showSelectedOverlay ? '' : triggerEmptyPlaceholder}
|
|
215
|
+
className={cn(bareInputStyles, 'cursor-text')}
|
|
216
|
+
autoFocus
|
|
217
|
+
/>
|
|
218
|
+
{showSelectedOverlay && (
|
|
219
|
+
// 2026-05-16 Bug B 真 root cause fix(Claude+Codex M31 Step 5 比稿 consensus,user verbatim
|
|
220
|
+
// 「修了一百次還沒好」+ codex cite W3C CSS Overflow / MDN / Mozilla Bug 972664#c1):
|
|
221
|
+
// 原 `inline-flex items-center truncate` 套同一 span,text 變 anonymous flex item →
|
|
222
|
+
// `text-overflow:ellipsis` 對 anonymous item 不 styleable → ellipsis dots 不可見(text 純 clip)。
|
|
223
|
+
// 對齊 `person-display.tsx:148` 既有 DS canonical:outer flex container + inner truncate 真實 box。
|
|
224
|
+
// DS-wide grep 29 個 truncate 都遵此 pattern,只本處違反 — 修齊。
|
|
225
|
+
<span
|
|
226
|
+
aria-hidden="true"
|
|
227
|
+
className="pointer-events-none absolute inset-0 flex items-center text-fg-muted"
|
|
228
|
+
>
|
|
229
|
+
<span className="min-w-0 flex-1 truncate">{selectedLabel}</span>
|
|
230
|
+
</span>
|
|
231
|
+
)}
|
|
232
|
+
</span>
|
|
233
|
+
)
|
|
234
|
+
}
|
|
235
|
+
// **selectedItemRenderer slot**(2026-05-07 v15.5):consumer 客製 selected display(e.g.
|
|
236
|
+
// PeoplePicker 接 PersonDisplay)。優先於 isTextDisplay / Tag 預設 path,但 empty value
|
|
237
|
+
// 仍走 placeholder。對齊 PeoplePicker = Select wrapper SSOT。
|
|
238
|
+
if (selectedItemRenderer && value && selectedOpt) {
|
|
239
|
+
return (
|
|
240
|
+
<>
|
|
241
|
+
{StartIcon && <ItemPrefix><StartIcon size={iconSize} className="text-fg-muted pointer-events-none" aria-hidden /></ItemPrefix>}
|
|
242
|
+
{/* 2026-05-14 item-anatomy SSOT fix(per codex H2 propagation 斷點):加 nakedCellRowModeAlign
|
|
243
|
+
→ autoRowHeight cell 內 selected renderer 也對齊 first-line,不再 vertical-center 整 row。 */}
|
|
244
|
+
<span className={cn("flex-1 min-w-0 inline-flex items-center", nakedCellRowModeAlign)}>{selectedItemRenderer(selectedOpt)}</span>
|
|
245
|
+
</>
|
|
246
|
+
)
|
|
247
|
+
}
|
|
248
|
+
// Text display: 純文字 + optional value icon
|
|
249
|
+
if (isTextDisplay) {
|
|
250
|
+
return (
|
|
251
|
+
<>
|
|
252
|
+
{StartIcon && <ItemPrefix><StartIcon size={iconSize} className="text-fg-muted pointer-events-none" aria-hidden /></ItemPrefix>}
|
|
253
|
+
{!StartIcon && SelectedIcon && value && <ItemPrefix><SelectedIcon size={iconSize} className="pointer-events-none" aria-hidden /></ItemPrefix>}
|
|
254
|
+
<span className={cn('flex-1 min-w-0 truncate', !value && 'text-fg-muted')}>
|
|
255
|
+
{value ? selectedLabel : (placeholder ?? '選擇…')}
|
|
256
|
+
</span>
|
|
257
|
+
</>
|
|
258
|
+
)
|
|
259
|
+
}
|
|
260
|
+
// Tag display: 用 option 的 tagVariant
|
|
261
|
+
return (
|
|
262
|
+
<>
|
|
263
|
+
{value && selectedOpt?.tagVariant
|
|
264
|
+
? <Tag size={size} color={selectedOpt.tagVariant as 'blue' | 'green' | 'red' | 'yellow' | 'neutral'} className="shrink-0 pointer-events-none">{selectedLabel}</Tag>
|
|
265
|
+
: value
|
|
266
|
+
? <Tag size={size} className="shrink-0 pointer-events-none">{selectedLabel}</Tag>
|
|
267
|
+
: <span className="text-fg-muted">{placeholder ?? '選擇…'}</span>
|
|
268
|
+
}
|
|
269
|
+
<span className="flex-1" />
|
|
270
|
+
</>
|
|
271
|
+
)
|
|
272
|
+
}
|
|
273
|
+
CustomSelectTriggerContent.displayName = 'CustomSelectTriggerContent'
|
|
274
|
+
|
|
275
|
+
// ── Shared readonly/disabled/display render ─────────────────────────────────
|
|
276
|
+
function ReadonlyDisplay({
|
|
277
|
+
mode, variant: variantProp, size, options, value, display, startIcon: StartIcon, className, placeholder, showDisplayEndIcon,
|
|
278
|
+
}: Pick<SelectProps, 'mode' | 'variant' | 'size' | 'options' | 'value' | 'display' | 'startIcon' | 'className' | 'placeholder' | 'showDisplayEndIcon'>) {
|
|
279
|
+
const resolvedMode = mode ?? 'readonly'
|
|
280
|
+
const variant = variantProp ?? 'default'
|
|
281
|
+
const sz = size ?? 'md'
|
|
282
|
+
const iconSize = getIconSize(sz)
|
|
283
|
+
const label = options?.find(o => o.value === value)?.label ?? value
|
|
284
|
+
const iconColor = resolvedMode === 'disabled' ? 'text-fg-disabled' : 'text-fg-muted'
|
|
285
|
+
const isTextDisplay = display !== 'tag'
|
|
286
|
+
// K10+K14 fix(2026-05-04):disabled mode placeholder/empty 顯示色 → fg-disabled(neutral-6),非 fg-muted(neutral-7)
|
|
287
|
+
// user canonical:disabled 顯著性優於 muted。同時 plain mode 必須 respect placeholder prop(之前忽略 = bug)
|
|
288
|
+
const emptyColorCls = resolvedMode === 'disabled' ? 'text-fg-disabled' : 'text-fg-muted'
|
|
289
|
+
const emptyText = placeholder ?? EMPTY_DISPLAY
|
|
290
|
+
|
|
291
|
+
// mode='display':2 path(2026-05-08 D path Phase 1 Select canary)
|
|
292
|
+
// ❌ 預設(無 showDisplayEndIcon):純內容輸出 bare span/Tag(原行為,backward compat)
|
|
293
|
+
// 對齊原 SelectDisplay sub-component(retired)。readonly / disabled 仍走下方 fieldWrapperStyles。
|
|
294
|
+
// ✅ showDisplayEndIcon=true(DataTable cell opt-in):Field naked-display wrapper +
|
|
295
|
+
// ChevronDown ItemSuffix。SSOT canonical 跟 readonly/edit/disabled mode 同 DOM 結構。
|
|
296
|
+
// Authority: data-table.spec.md:204 + inline-action.spec.md:157「Field family endAction」
|
|
297
|
+
if (resolvedMode === 'display') {
|
|
298
|
+
if (!showDisplayEndIcon) {
|
|
299
|
+
// 2026-05-14 I2 fix(spec contract (e) display typography canonical):bare span 必套
|
|
300
|
+
// `fieldDisplayTextClass(sz)`(sm/md→text-body,lg→text-body-lg)— 對齊跨 Field
|
|
301
|
+
// family display 視覺尺寸統一。
|
|
302
|
+
if (!value) return <span className={cn(fieldDisplayTextClass(sz), 'text-fg-muted', className)}>{emptyText}</span>
|
|
303
|
+
if (isTextDisplay) return <span className={cn(fieldDisplayTextClass(sz), 'truncate', className)}>{label}</span>
|
|
304
|
+
const selOpt = options?.find(o => o.value === value)
|
|
305
|
+
const tVariant = selOpt?.tagVariant as 'blue' | 'green' | 'red' | 'yellow' | 'neutral' | undefined
|
|
306
|
+
return <Tag size={sz} color={tVariant} className={className}>{label}</Tag>
|
|
307
|
+
}
|
|
308
|
+
// D path opt-in: Field naked-display wrapper + ItemSuffix ChevronDown
|
|
309
|
+
const selOpt = options?.find(o => o.value === value)
|
|
310
|
+
const tVariant = selOpt?.tagVariant as 'blue' | 'green' | 'red' | 'yellow' | 'neutral' | undefined
|
|
311
|
+
return (
|
|
312
|
+
<div
|
|
313
|
+
className={cn(fieldWrapperStyles({ mode: 'display', variant, size: sz }), value && !isTextDisplay && tagPadding[sz], className)}
|
|
314
|
+
data-field-mode="display"
|
|
315
|
+
>
|
|
316
|
+
{isTextDisplay ? (
|
|
317
|
+
<span className={cn(bareInputStyles, 'flex-1 min-w-0 truncate', !value && emptyColorCls)}>
|
|
318
|
+
{value ? label : emptyText}
|
|
319
|
+
</span>
|
|
320
|
+
) : value ? (
|
|
321
|
+
<Tag size={sz} color={tVariant}>{label}</Tag>
|
|
322
|
+
) : (
|
|
323
|
+
<span className={cn('flex-1 min-w-0', emptyColorCls)}>{emptyText}</span>
|
|
324
|
+
)}
|
|
325
|
+
<ItemSuffix><ChevronDown size={iconSize} className="text-fg-muted pointer-events-none" aria-hidden /></ItemSuffix>
|
|
326
|
+
</div>
|
|
327
|
+
)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (isTextDisplay) {
|
|
331
|
+
return (
|
|
332
|
+
<div className={cn(fieldWrapperStyles({ mode: resolvedMode, variant, size: sz }), className)} data-field-mode={resolvedMode}>
|
|
333
|
+
{StartIcon && <ItemPrefix><StartIcon size={iconSize} className={cn('pointer-events-none', iconColor)} aria-hidden /></ItemPrefix>}
|
|
334
|
+
<span className={cn('flex-1 min-w-0 truncate', resolvedMode === 'disabled' && 'text-fg-disabled')}>
|
|
335
|
+
{value ? label : <span className={emptyColorCls}>{emptyText}</span>}
|
|
336
|
+
</span>
|
|
337
|
+
</div>
|
|
338
|
+
)
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const selectedOpt = options?.find(o => o.value === value)
|
|
342
|
+
const tagVariant = selectedOpt?.tagVariant as 'blue' | 'green' | 'red' | 'yellow' | 'neutral' | undefined
|
|
343
|
+
|
|
344
|
+
return (
|
|
345
|
+
<div className={cn(fieldWrapperStyles({ mode: resolvedMode, variant, size: sz }), value && tagPadding[sz], className)} data-field-mode={resolvedMode}>
|
|
346
|
+
{value ? <Tag size={sz} color={tagVariant}>{label}</Tag> : <span className={emptyColorCls}>{emptyText}</span>}
|
|
347
|
+
</div>
|
|
348
|
+
)
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// ── Native Select (mobile) ─────────────────────────────────────────────
|
|
352
|
+
|
|
353
|
+
// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding
|
|
354
|
+
const NativeSelect = React.forwardRef<HTMLSelectElement, SelectProps>(
|
|
355
|
+
({ mode = 'edit', variant: variantProp, error: errorProp = false, size = 'md', options, value: valueProp, defaultValue, onChange, placeholder, className, disabled: disabledProp, clearable = false, display = 'plain', startIcon: StartIcon, showDisplayEndIcon, id: idProp, 'aria-describedby': ariaDescribedByProp, 'aria-errormessage': ariaErrorMessageProp, ...props }, ref) => {
|
|
356
|
+
const fieldCtx = useFieldContext()
|
|
357
|
+
const error = errorProp || (fieldCtx?.invalid ?? false)
|
|
358
|
+
const disabled = disabledProp ?? fieldCtx?.disabled
|
|
359
|
+
const resolvedMode = disabled ? 'disabled' : mode
|
|
360
|
+
const variant: FieldVariant = variantProp ?? fieldCtx?.variant ?? 'default'
|
|
361
|
+
const iconSize = getIconSize(size)
|
|
362
|
+
// 2026-05-21 D3 audit:Controlled / Uncontrolled dual-mode via 既有 SSOT hook(同 CustomSelect)
|
|
363
|
+
const [value, setValue] = useControllable<string | null>({
|
|
364
|
+
value: valueProp,
|
|
365
|
+
defaultValue: defaultValue ?? null,
|
|
366
|
+
onChange: onChange ? (next) => onChange(next ?? '') : undefined,
|
|
367
|
+
})
|
|
368
|
+
const handleNativeChange = (v: string) => setValue(v)
|
|
369
|
+
const showClear = clearable && value && resolvedMode === 'edit'
|
|
370
|
+
const isTextDisplay = display === 'plain'
|
|
371
|
+
const selectRef = React.useRef<HTMLSelectElement | null>(null)
|
|
372
|
+
const setSelectRef = React.useCallback((el: HTMLSelectElement | null) => {
|
|
373
|
+
selectRef.current = el
|
|
374
|
+
if (typeof ref === 'function') ref(el)
|
|
375
|
+
else if (ref) (ref as React.MutableRefObject<HTMLSelectElement | null>).current = el
|
|
376
|
+
}, [ref])
|
|
377
|
+
|
|
378
|
+
if (resolvedMode !== 'edit') {
|
|
379
|
+
return <ReadonlyDisplay mode={resolvedMode} variant={variant} size={size} options={options} value={value} display={display} startIcon={StartIcon} className={className} placeholder={placeholder} showDisplayEndIcon={showDisplayEndIcon} />
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const selectEl = (
|
|
383
|
+
<select
|
|
384
|
+
ref={setSelectRef}
|
|
385
|
+
id={idProp ?? fieldCtx?.id}
|
|
386
|
+
value={value ?? ''}
|
|
387
|
+
onChange={(e) => handleNativeChange(e.target.value)}
|
|
388
|
+
disabled={disabled}
|
|
389
|
+
aria-invalid={error || undefined}
|
|
390
|
+
aria-required={fieldCtx?.required || undefined}
|
|
391
|
+
aria-describedby={ariaDescribedByProp ?? fieldCtx?.descriptionId}
|
|
392
|
+
aria-errormessage={ariaErrorMessageProp ?? (error ? fieldCtx?.errorId : undefined)}
|
|
393
|
+
className={cn(bareInputStyles, 'cursor-pointer appearance-none', !value && 'text-fg-muted', !isTextDisplay && value && 'absolute inset-0 w-full h-full opacity-0 z-0')}
|
|
394
|
+
{...props}
|
|
395
|
+
>
|
|
396
|
+
{placeholder && <option value="" disabled>{placeholder}</option>}
|
|
397
|
+
{options.map(opt => <option key={opt.value} value={opt.value}>{opt.label}</option>)}
|
|
398
|
+
</select>
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
const clearEl = showClear ? (
|
|
402
|
+
<SelectClearButton size={size ?? 'md'} onClear={() => handleNativeChange('')} />
|
|
403
|
+
) : null
|
|
404
|
+
|
|
405
|
+
const chevronEl = (
|
|
406
|
+
<ItemSuffix className="relative z-10 pointer-events-none">
|
|
407
|
+
<ChevronDown size={iconSize} className="text-fg-muted" aria-hidden />
|
|
408
|
+
</ItemSuffix>
|
|
409
|
+
)
|
|
410
|
+
const selectedOpt = options?.find(o => o.value === value)
|
|
411
|
+
const label = selectedOpt?.label ?? value
|
|
412
|
+
const nativeTagVariant = selectedOpt?.tagVariant as 'blue' | 'green' | 'red' | 'yellow' | 'neutral' | undefined
|
|
413
|
+
const SelectedOptIcon = selectedOpt?.icon
|
|
414
|
+
|
|
415
|
+
if (!isTextDisplay) {
|
|
416
|
+
return (
|
|
417
|
+
<div className={cn(fieldWrapperStyles({ mode: 'edit', variant: variant, size }), value && tagPadding[size], 'relative',
|
|
418
|
+
error && ['border-error hover:border-error-hover', 'focus-within:border-error focus-within:hover:border-error'], className)}
|
|
419
|
+
style={{ paddingRight: '0.75rem' }} data-field-mode="edit" data-error={error ? '' : undefined}>
|
|
420
|
+
{value ? <Tag size={size} color={nativeTagVariant} className="shrink-0 relative z-10 pointer-events-none">{label}</Tag> : <span className="text-fg-muted">{placeholder ?? '選擇...'}</span>}
|
|
421
|
+
{selectEl}
|
|
422
|
+
<span className="flex-1" />
|
|
423
|
+
{clearEl}
|
|
424
|
+
{chevronEl}
|
|
425
|
+
</div>
|
|
426
|
+
)
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return (
|
|
430
|
+
<div className={cn(fieldWrapperStyles({ mode: 'edit', variant: variant, size }),
|
|
431
|
+
error && ['border-error hover:border-error-hover', 'focus-within:border-error focus-within:hover:border-error'], className)}
|
|
432
|
+
data-field-mode="edit" data-error={error ? '' : undefined}>
|
|
433
|
+
{StartIcon && <ItemPrefix><StartIcon size={iconSize} className="text-fg-muted pointer-events-none" aria-hidden /></ItemPrefix>}
|
|
434
|
+
{!StartIcon && SelectedOptIcon && value && <ItemPrefix><SelectedOptIcon size={iconSize} className="pointer-events-none" aria-hidden /></ItemPrefix>}
|
|
435
|
+
{selectEl}
|
|
436
|
+
{clearEl}
|
|
437
|
+
{chevronEl}
|
|
438
|
+
</div>
|
|
439
|
+
)
|
|
440
|
+
}
|
|
441
|
+
)
|
|
442
|
+
NativeSelect.displayName = 'NativeSelect'
|
|
443
|
+
|
|
444
|
+
// ── Custom Select (desktop — consumes SelectMenu) ────────────────────────
|
|
445
|
+
|
|
446
|
+
// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding
|
|
447
|
+
const CustomSelect = React.forwardRef<HTMLDivElement, SelectProps>(
|
|
448
|
+
({ mode = 'edit', variant: variantProp, error: errorProp = false, size = 'md', options, groups, value: valueProp, defaultValue, onChange, placeholder, className, disabled: disabledProp, clearable = false, display = 'plain', startIcon: StartIcon, searchable = false, loading, minRows, defaultOpen = false, onOpenChange, selectedItemRenderer, showDisplayEndIcon, id: idProp, 'aria-describedby': ariaDescribedByProp, 'aria-errormessage': ariaErrorMessageProp, 'aria-label': ariaLabel }, ref) => {
|
|
449
|
+
const fieldCtx = useFieldContext()
|
|
450
|
+
const error = errorProp || (fieldCtx?.invalid ?? false)
|
|
451
|
+
const disabled = disabledProp ?? fieldCtx?.disabled
|
|
452
|
+
const resolvedMode = disabled ? 'disabled' : mode
|
|
453
|
+
const variant: FieldVariant = variantProp ?? fieldCtx?.variant ?? 'default'
|
|
454
|
+
const iconSize = getIconSize(size)
|
|
455
|
+
// 2026-05-21 D3 audit:Controlled / Uncontrolled dual-mode via 既有 SSOT hook(M17 對齊,取代自刻 isControlled pattern)。
|
|
456
|
+
// Phase B codex 抓:之前 Custom clear 走 `onChange?.('')` 沒 setInternalValue → uncontrolled clear 失效。useControllable 統一 setter 修。
|
|
457
|
+
// onChange forward coerce null → ''(consumer 簽名 `(value: string) => void`,null 是 internal empty signal)。
|
|
458
|
+
const [value, setValue] = useControllable<string | null>({
|
|
459
|
+
value: valueProp,
|
|
460
|
+
defaultValue: defaultValue ?? null,
|
|
461
|
+
onChange: onChange ? (next) => onChange(next ?? '') : undefined,
|
|
462
|
+
})
|
|
463
|
+
const showClear = clearable && value && resolvedMode === 'edit'
|
|
464
|
+
const isTextDisplay = display === 'plain'
|
|
465
|
+
|
|
466
|
+
const [open, setOpen] = React.useState(defaultOpen)
|
|
467
|
+
const [search, setSearch] = React.useState('')
|
|
468
|
+
const inputRef = React.useRef<HTMLInputElement>(null)
|
|
469
|
+
|
|
470
|
+
// 關閉時清搜尋
|
|
471
|
+
React.useEffect(() => { if (!open) setSearch('') }, [open])
|
|
472
|
+
|
|
473
|
+
// **React #310 fix(2026-05-04)**:所有 hooks 必在任何 early return 前 call,
|
|
474
|
+
// 否則 disabled→edit 切換時 hook count 變動 → React 死亡。
|
|
475
|
+
// 原本 useMemo(L280, L291) 在 early return 之後 = latent bug,K13 觸發(filter Op 從 disabled
|
|
476
|
+
// 變 edit 當 user 選欄位)。修法:把所有 useMemo 提到 early return 之前。
|
|
477
|
+
const selectedOpt = options?.find(o => o.value === value)
|
|
478
|
+
// 2026-05-06 v9.1:value 不在 options 也要顯示原值(不沉默丟失)。原 fallback `''` 致
|
|
479
|
+
// SelectCell 開 edit 時若 cell value 不在當前 options(e.g. 上游資料漂移 / options async
|
|
480
|
+
// 後到 / 跨 dataset),trigger 顯示空白 — user 報「value 不見」。對齊 ReadonlyDisplay 同
|
|
481
|
+
// 級 fallback `?? value`。
|
|
482
|
+
const selectedLabel = selectedOpt?.label ?? value ?? ''
|
|
483
|
+
const SelectedIcon = selectedOpt?.icon
|
|
484
|
+
// ── 過濾選項 ──
|
|
485
|
+
const filteredOptions = searchable && search
|
|
486
|
+
? options.filter(o => o.label.toLowerCase().includes(search.toLowerCase()))
|
|
487
|
+
: options
|
|
488
|
+
// ── 轉換 SelectOption → SelectMenuOption(必在 early return 前) ──
|
|
489
|
+
// Issue 4(2026-05-10):forward avatar / description / disabled SSOT(per SelectMenuOption schema)。
|
|
490
|
+
const menuOptions: SelectMenuOption[] = React.useMemo(
|
|
491
|
+
() => filteredOptions.map(opt => ({
|
|
492
|
+
value: opt.value,
|
|
493
|
+
label: opt.label,
|
|
494
|
+
icon: isTextDisplay ? opt.icon : undefined,
|
|
495
|
+
avatar: opt.avatar,
|
|
496
|
+
description: opt.description,
|
|
497
|
+
disabled: opt.disabled,
|
|
498
|
+
group: opt.group,
|
|
499
|
+
})),
|
|
500
|
+
[filteredOptions, isTextDisplay]
|
|
501
|
+
)
|
|
502
|
+
// ── Tag display 自訂 label 渲染(必在 early return 前) ──
|
|
503
|
+
const renderLabel = React.useMemo(() => {
|
|
504
|
+
if (isTextDisplay) return undefined
|
|
505
|
+
return (menuOpt: SelectMenuOption) => {
|
|
506
|
+
const srcOpt = options.find(o => o.value === menuOpt.value)
|
|
507
|
+
if (srcOpt?.tagVariant) {
|
|
508
|
+
return <Tag size={size} color={srcOpt.tagVariant as 'blue' | 'green' | 'red' | 'yellow' | 'neutral'}>{menuOpt.label}</Tag>
|
|
509
|
+
}
|
|
510
|
+
return menuOpt.label
|
|
511
|
+
}
|
|
512
|
+
}, [isTextDisplay, options, size])
|
|
513
|
+
|
|
514
|
+
// **React #310 fix v2(2026-05-04)**:`handleValueChange` useCallback 也必在 early return 前
|
|
515
|
+
// 原本 L306(early return 後)→ disabled→edit 切換時 hook count 仍變 → #310 持續
|
|
516
|
+
// 2026-05-21 D3:`useControllable` 統一 controlled / uncontrolled state + onChange forward,不再手動 if-branch。
|
|
517
|
+
const handleValueChange = React.useCallback(
|
|
518
|
+
(newValue: string | string[]) => {
|
|
519
|
+
setValue(Array.isArray(newValue) ? newValue[0] : newValue)
|
|
520
|
+
},
|
|
521
|
+
[setValue]
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
// Early return AFTER all hooks(disabled / readonly / display mode 走 ReadonlyDisplay)
|
|
525
|
+
if (resolvedMode !== 'edit') {
|
|
526
|
+
return <ReadonlyDisplay mode={resolvedMode} variant={variant} size={size} options={options} value={value} display={display} startIcon={StartIcon} className={className} placeholder={placeholder} showDisplayEndIcon={showDisplayEndIcon} />
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// 2026-05-21 D3 Phase B codex 抓:Custom clear 用 setValue 不直接 onChange,uncontrolled clear 才能真清 internal state。
|
|
530
|
+
const clearEl = showClear ? (
|
|
531
|
+
<SelectClearButton size={size ?? 'md'} onClear={() => setValue('')} stopPropagation />
|
|
532
|
+
) : null
|
|
533
|
+
|
|
534
|
+
const chevronEl = (
|
|
535
|
+
<ItemSuffix>
|
|
536
|
+
<ChevronDown size={iconSize} className={cn('text-fg-muted transition-transform', open && 'rotate-180')} aria-hidden />
|
|
537
|
+
</ItemSuffix>
|
|
538
|
+
)
|
|
539
|
+
|
|
540
|
+
const triggerContent = (
|
|
541
|
+
<CustomSelectTriggerContent
|
|
542
|
+
searchable={searchable}
|
|
543
|
+
open={open}
|
|
544
|
+
isTextDisplay={isTextDisplay}
|
|
545
|
+
size={size}
|
|
546
|
+
value={value}
|
|
547
|
+
selectedLabel={selectedLabel}
|
|
548
|
+
selectedOpt={selectedOpt}
|
|
549
|
+
SelectedIcon={SelectedIcon}
|
|
550
|
+
StartIcon={StartIcon}
|
|
551
|
+
iconSize={iconSize}
|
|
552
|
+
placeholder={placeholder}
|
|
553
|
+
search={search}
|
|
554
|
+
setSearch={setSearch}
|
|
555
|
+
inputRef={inputRef}
|
|
556
|
+
selectedItemRenderer={selectedItemRenderer}
|
|
557
|
+
/>
|
|
558
|
+
)
|
|
559
|
+
|
|
560
|
+
// hooks(filteredOptions / menuOptions / renderLabel / handleValueChange)已全 hoist(React #310 fix v2)
|
|
561
|
+
|
|
562
|
+
const trigger = (
|
|
563
|
+
<div
|
|
564
|
+
ref={ref}
|
|
565
|
+
id={idProp ?? fieldCtx?.id}
|
|
566
|
+
role="combobox"
|
|
567
|
+
aria-expanded={open}
|
|
568
|
+
aria-haspopup="listbox"
|
|
569
|
+
aria-label={ariaLabel}
|
|
570
|
+
aria-invalid={error || undefined}
|
|
571
|
+
aria-required={fieldCtx?.required || undefined}
|
|
572
|
+
aria-describedby={ariaDescribedByProp ?? fieldCtx?.descriptionId}
|
|
573
|
+
aria-errormessage={ariaErrorMessageProp ?? (error ? fieldCtx?.errorId : undefined)}
|
|
574
|
+
tabIndex={0}
|
|
575
|
+
className={cn(
|
|
576
|
+
fieldWrapperStyles({ mode: 'edit', variant: variant, size }),
|
|
577
|
+
!isTextDisplay && value && !searchable && tagPadding[size],
|
|
578
|
+
// 2026-05-06 v13.3 SSOT retire:per-control `open && 'border-primary'` 移除。Field default
|
|
579
|
+
// state machine `data-[state=open]:border-border-hover`(灰深)處理 open;若 trigger focused
|
|
580
|
+
// (Radix focus on open),focus-within:!border-primary 強制勝出顯藍。focus dominates everything
|
|
581
|
+
// 全 DS 一致(Material/Polaris/Ant 共識),改 Field default 一處全 control 自動跟動。
|
|
582
|
+
error && ['border-error hover:border-error-hover', 'focus-within:border-error focus-within:hover:border-error'],
|
|
583
|
+
'cursor-pointer',
|
|
584
|
+
className,
|
|
585
|
+
)}
|
|
586
|
+
style={!isTextDisplay ? { paddingRight: '0.75rem' } : undefined}
|
|
587
|
+
data-field-mode="edit"
|
|
588
|
+
data-error={error ? '' : undefined}
|
|
589
|
+
onKeyDown={(e) => {
|
|
590
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
591
|
+
if (!searchable) { e.preventDefault(); setOpen(true) }
|
|
592
|
+
}
|
|
593
|
+
if (e.key === 'Escape') setOpen(false)
|
|
594
|
+
}}
|
|
595
|
+
>
|
|
596
|
+
{triggerContent}
|
|
597
|
+
{clearEl}
|
|
598
|
+
{chevronEl}
|
|
599
|
+
</div>
|
|
600
|
+
)
|
|
601
|
+
|
|
602
|
+
return (
|
|
603
|
+
<SelectMenu
|
|
604
|
+
options={menuOptions}
|
|
605
|
+
groups={groups}
|
|
606
|
+
value={value ?? null}
|
|
607
|
+
onValueChange={handleValueChange}
|
|
608
|
+
searchable={false}
|
|
609
|
+
loading={loading}
|
|
610
|
+
size={size}
|
|
611
|
+
minRows={minRows}
|
|
612
|
+
open={open}
|
|
613
|
+
onOpenChange={(o) => { setOpen(o); onOpenChange?.(o) }}
|
|
614
|
+
renderLabel={renderLabel}
|
|
615
|
+
onOpenAutoFocus={searchable ? (e) => { e.preventDefault(); inputRef.current?.focus() } : undefined}
|
|
616
|
+
>
|
|
617
|
+
{trigger}
|
|
618
|
+
</SelectMenu>
|
|
619
|
+
)
|
|
620
|
+
}
|
|
621
|
+
)
|
|
622
|
+
CustomSelect.displayName = 'CustomSelect'
|
|
623
|
+
|
|
624
|
+
// ── Public component(自動偵測 mobile / desktop)──────────────────────────────
|
|
625
|
+
|
|
626
|
+
const Select = React.forwardRef<HTMLSelectElement | HTMLDivElement, SelectProps>(
|
|
627
|
+
(props, ref) => {
|
|
628
|
+
const isMobile = useIsTouchDevice()
|
|
629
|
+
|
|
630
|
+
if (isMobile) {
|
|
631
|
+
return <NativeSelect ref={ref as React.Ref<HTMLSelectElement>} {...props} />
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
return <CustomSelect ref={ref as React.Ref<HTMLDivElement>} {...props} />
|
|
635
|
+
}
|
|
636
|
+
)
|
|
637
|
+
Select.displayName = 'Select'
|
|
638
|
+
|
|
639
|
+
// Story auto-compile metadata — Phase 1 mechanical migration(2026-04-24)
|
|
640
|
+
// Phase 2 fill needed: purpose descriptions + when rationale + world-class refs
|
|
641
|
+
export const selectMeta = {
|
|
642
|
+
component: 'Select',
|
|
643
|
+
family: 4,
|
|
644
|
+
variants: {
|
|
645
|
+
|
|
646
|
+
},
|
|
647
|
+
sizes: {
|
|
648
|
+
|
|
649
|
+
},
|
|
650
|
+
states: ['default', 'hover', 'active', 'focus-visible', 'disabled'],
|
|
651
|
+
tokens: {
|
|
652
|
+
bg: [],
|
|
653
|
+
fg: ['text-fg-disabled', 'text-fg-muted'],
|
|
654
|
+
ring: [],
|
|
655
|
+
},
|
|
656
|
+
} as const
|
|
657
|
+
|
|
658
|
+
export { Select }
|