@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,826 @@
|
|
|
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
|
+
// @renderer-symmetry-allow: ComboboxTagStack(display path)接 consumer tagRenderer 是 Stream C 下 cycle 工作 — 2026-05-12 先 ship Issues 2/3/4 surgical fixes(placeholder vocabulary + cell surface metrics + placeholder truncate),tagRenderer display-path unify deferred per field-controls.spec.md 共享 contract a。當前 multi=1 顯示已透過 PeoplePicker tagRenderer 線 314 PersonDisplay SSOT 對齊;其他 Combobox consumer 走 default `<Tag>` 純文字 backward-compat。
|
|
3
|
+
// code-quality-allow: file-size — Combobox 含 NativeCombobox/CustomCombobox/useOverflowCount/OverflowTagList/ComboboxTagStack 5 子元件 + 共用 helpers,split-into-files 會破壞 measurement closures + 重複 type definitions。當前 751 lines 在 800 hard cap 內。
|
|
4
|
+
import * as React from 'react'
|
|
5
|
+
import { X, ChevronDown } from 'lucide-react'
|
|
6
|
+
import { cn } from '@/lib/utils'
|
|
7
|
+
import type { FieldMode, FieldVariant } from '@/design-system/components/Field/field-types'
|
|
8
|
+
import { fieldWrapperStyles, EMPTY_DISPLAY, nakedCellRowModeAlign, fieldDisplayTextClass } from '@/design-system/components/Field/field-wrapper'
|
|
9
|
+
import { useFieldContext } from '@/design-system/components/Field/field-context'
|
|
10
|
+
import { Tag } from '@/design-system/components/Tag/tag'
|
|
11
|
+
import { ItemInlineAction, ItemSuffix } from '@/design-system/patterns/element-anatomy/item-anatomy'
|
|
12
|
+
import { OverflowIndicator } from '@/design-system/components/OverflowIndicator/overflow-indicator'
|
|
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 { ICON_SIZE } from '@/design-system/tokens/uiSize/icon-size'
|
|
16
|
+
|
|
17
|
+
// ── constants ───────────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
const GAP = 4
|
|
20
|
+
|
|
21
|
+
const tagPadding: Record<string, string> = {
|
|
22
|
+
sm: 'px-[calc((var(--field-height-sm)_-_1.25rem)_/_2)]',
|
|
23
|
+
md: 'px-[calc((var(--field-height-md)_-_1.5rem)_/_2)]',
|
|
24
|
+
lg: 'px-[calc((var(--field-height-lg)_-_1.5rem)_/_2)]',
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Combobox option schema(2026-05-10 post-Issue-4 audit unify):**explicit extends
|
|
29
|
+
* SelectMenuOption(primitive SSOT)** — 避免重蹈先前 PeoplePicker 改壞的 wrapper schema drift。
|
|
30
|
+
*
|
|
31
|
+
* Why `extends SelectMenuOption`(per user 「全盤檢查避免下次又改壞或是偏移」要求):
|
|
32
|
+
* 原 `interface SelectOption { value: string; label: string }` 是 weak schema,跟 Select 的
|
|
33
|
+
* `SelectOption`(同名)雙重宣告但欄位不同 → TypeScript 不抓(同名 interface 在不同 file
|
|
34
|
+
* 各 export,consumer import 到哪個版本看 import path)→ schema drift。
|
|
35
|
+
* PeoplePicker multi-mode 走 Combobox 路徑,dropdown menu rows lose avatar / description —
|
|
36
|
+
* user 看到「single mode 有 avatar / multi mode 沒 avatar」inconsistency。
|
|
37
|
+
*
|
|
38
|
+
* Fix(post-Issue-4 follow-up):extend SelectMenuOption → 全 primitive surface 自動繼承。
|
|
39
|
+
* Wrapper-only field 都沒有 → empty body interface(future 加 wrapper-only field 加在此處)。
|
|
40
|
+
* `menuOptions` mapping(below)forward 全 SelectMenuOption surface。
|
|
41
|
+
*
|
|
42
|
+
* 對齊 Polaris ChoiceList / Material Autocomplete / Carbon Dropdown 的 wrapper-vs-primitive
|
|
43
|
+
* schema-extension idiom。Hook `check_wrapper_primitive_schema_drift.sh`(M30 機械強制)。
|
|
44
|
+
*/
|
|
45
|
+
export interface SelectOption extends SelectMenuOption {
|
|
46
|
+
// (no wrapper-only fields yet — kept for future扩 + same-name SSOT cross-wrapper)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ── useOverflowCount (unchanged) ────────────────────────────────────────────
|
|
50
|
+
|
|
51
|
+
function useOverflowCount(
|
|
52
|
+
containerRef: React.RefObject<HTMLDivElement | null>,
|
|
53
|
+
tagEls: React.MutableRefObject<(HTMLDivElement | null)[]>,
|
|
54
|
+
overflowEl: React.RefObject<HTMLDivElement | null>,
|
|
55
|
+
totalCount: number,
|
|
56
|
+
enabled: boolean,
|
|
57
|
+
gap: number = GAP, // (2026-05-07 v15.13)stack avatar 模式傳 0
|
|
58
|
+
visibleCountOverride?: number, // 2026-05-15 Bug 3 fix:override DOM measurement(PeoplePicker stack 走 formula primitive)
|
|
59
|
+
): { visibleCount: number; ready: boolean } {
|
|
60
|
+
const [state, setState] = React.useState({ visibleCount: totalCount, ready: !enabled })
|
|
61
|
+
// 2026-05-18 Round 6 fix(per Codex M31 Round 6 H7 verdict + Step 5 共識):
|
|
62
|
+
// `ofEl.offsetWidth` 在 expanded state(visibleCount === totalCount → overflow=0 →
|
|
63
|
+
// `OverflowIndicator` line 92 `return null` → ofEl wrapper empty)= 0,fallback 60;
|
|
64
|
+
// 在 collapsed state(+N rendered)= 真實寬(~28-32px)。同 `available` 在兩 state 給
|
|
65
|
+
// 不同 verdict → 臨界值區 `max(B+g+Q, B+g+O) ≤ available < B+g+F` 振盪。Cache last
|
|
66
|
+
// non-zero measurement → measurement state-independent → oscillation 收斂。
|
|
67
|
+
// 初始 60 沿用舊 fallback(無 measure 史 ok-ish over-estimate)。
|
|
68
|
+
const lastOverflowWRef = React.useRef<number>(60)
|
|
69
|
+
|
|
70
|
+
// 2026-05-16 RACE FIX(user 抓「逐個 click 滿 6」vs「取消全選→再全選」same length 不同 visible):
|
|
71
|
+
//
|
|
72
|
+
// 原 useEffect + 雙 rAF 沒 capture rAF IDs → cleanup 不 cancel pending rAFs。
|
|
73
|
+
// Path B(length=6→0→6):length=0 時 override=undefined 走 internal calc 排 rAF,
|
|
74
|
+
// 然後 length=6 + override=N → deps change → cleanup 跑(disconnect ResizeObserver 但
|
|
75
|
+
// 不 cancel rAF)→ 新 useEffect 跑 override 寫 el.hidden → 舊 rAF 仍 fire → 跑舊
|
|
76
|
+
// internal calc → 覆寫 el.hidden 用 internal measurement(不一致於 override formula)。
|
|
77
|
+
//
|
|
78
|
+
// Fix:
|
|
79
|
+
// 1. useEffect → useLayoutEffect:tighter timing,measurement 在 paint 前 sync
|
|
80
|
+
// 2. Capture rAF IDs,cancel on cleanup
|
|
81
|
+
// 3. scheduleCalc 函式包裝,cancel in-flight rAF 才排新一輪(避免 ResizeObserver
|
|
82
|
+
// re-fire 堆 rAF)
|
|
83
|
+
//
|
|
84
|
+
// 對齊 2026-05-14 I3 fix comment「user 抓『全選 vs 逐個勾 result 不同』」 — 當時 fix
|
|
85
|
+
// 只加 double-rAF 但漏 cancel,本次補完 race close。
|
|
86
|
+
// 2026-05-18 Round 5 fix(per visual test probe):useLayoutEffect 在 nested component 場景
|
|
87
|
+
// 會在 parent ref attach 前 fire(child layout effect 先於 parent ref attach)→ containerRef.current
|
|
88
|
+
// null → early return → calc never runs → setProperty never called → CSS var unset → tag overflow。
|
|
89
|
+
// 改 useEffect:fires AFTER paint,所有 refs 都 attach。double-rAF guard ensures layout done。
|
|
90
|
+
// Trade-off:可能 1-2 frame flicker,但 functional setState guard + paint target measurement 已 cover。
|
|
91
|
+
React.useEffect(() => {
|
|
92
|
+
if (!enabled || totalCount === 0) { setState({ visibleCount: totalCount, ready: true }); return }
|
|
93
|
+
if (visibleCountOverride !== undefined) {
|
|
94
|
+
for (let i = 0; i < tagEls.current.length; i++) {
|
|
95
|
+
const el = tagEls.current[i]
|
|
96
|
+
if (el) el.hidden = i >= visibleCountOverride
|
|
97
|
+
}
|
|
98
|
+
const ofEl = overflowEl.current
|
|
99
|
+
if (ofEl) ofEl.hidden = visibleCountOverride >= totalCount
|
|
100
|
+
setState({ visibleCount: visibleCountOverride, ready: true }); return
|
|
101
|
+
}
|
|
102
|
+
// totalCount=1 fast path:single-tag case 直接 visible 不跑 measurement loop。
|
|
103
|
+
// (歷史:c90d029 曾移除此 bypass,後復原 — 移除會造成 narrow cell 1-selected 跑 unbounded Tag
|
|
104
|
+
// measurement 後 visibleCount=0 → 顯 +1 indicator 而非 single tag,違反 PeoplePicker length===1
|
|
105
|
+
// 走 PersonDisplay SSOT。)
|
|
106
|
+
// 2026-05-16 Round 5 codex edge case fix:explicit unhide DOM nodes(對齊 override branch)。
|
|
107
|
+
// 原 fast-path 只設 React state 不動 `el.hidden`,如 wrappers 之前 hidden 殘留(從 length>1 降到 1)
|
|
108
|
+
// 可能視覺漏顯。Override branch L78-80 同 contract 對齊。
|
|
109
|
+
if (totalCount === 1) {
|
|
110
|
+
for (let i = 0; i < tagEls.current.length; i++) {
|
|
111
|
+
const el = tagEls.current[i]
|
|
112
|
+
if (el) el.hidden = i >= 1
|
|
113
|
+
}
|
|
114
|
+
const ofEl = overflowEl.current
|
|
115
|
+
if (ofEl) ofEl.hidden = true
|
|
116
|
+
setState({ visibleCount: 1, ready: true }); return
|
|
117
|
+
}
|
|
118
|
+
const container = containerRef.current
|
|
119
|
+
if (!container) return
|
|
120
|
+
|
|
121
|
+
const calc = () => {
|
|
122
|
+
const cs = getComputedStyle(container)
|
|
123
|
+
const available = container.clientWidth - (parseFloat(cs.paddingLeft) || 0) - (parseFloat(cs.paddingRight) || 0)
|
|
124
|
+
// 2026-05-18 Round 5 fix(per user 拍板「那就開始做」+ Codex M31 Round 5 verdict):
|
|
125
|
+
// inject available 成 CSS var,Tag 用 explicit length 而非 cyclic percentage(避 CSS Sizing 3
|
|
126
|
+
// §5.2.1 cyclic percentage 退化問題)。
|
|
127
|
+
container.style.setProperty('--combobox-tag-area-inline-size', `${available}px`)
|
|
128
|
+
for (const el of tagEls.current) if (el) el.hidden = false
|
|
129
|
+
const ofEl = overflowEl.current
|
|
130
|
+
if (ofEl) ofEl.hidden = false
|
|
131
|
+
// 2026-05-18 Round 6 fix:cache last non-zero ofEl width 破 expanded/collapsed state
|
|
132
|
+
// 量測二態(expanded → ofEl 空 offsetWidth=0 / collapsed → real ~28-32px)。沒 cache
|
|
133
|
+
// 之前同 `available` 在兩 state 給不同 verdict → 永動。詳本 hook 頂部 ref 註解。
|
|
134
|
+
const measuredOverflowW = ofEl?.offsetWidth || 0
|
|
135
|
+
if (measuredOverflowW > 0) lastOverflowWRef.current = measuredOverflowW
|
|
136
|
+
const overflowW = lastOverflowWRef.current
|
|
137
|
+
// **#3 fix(2026-05-04)**:width-check 先於 count++,並處理 i=0 邊界(1 tag 自身就太寬 → 全 hidden 顯 +N)
|
|
138
|
+
// 之前 bug:greedy `count++` 永遠至少 = 1,1-tag-too-wide case 視覺呈半個 tag clipped + +N(錯)
|
|
139
|
+
// 修後:1 tag 太寬時 count = 0,全 N tags 走 +N 顯 indicator
|
|
140
|
+
// 2026-05-18 Round 5:量 paint target `[data-tag-root]` 而非 wrapper(per codex Round 5 verdict)。
|
|
141
|
+
// wrapper basis:auto 自由 grow,offsetWidth ≠ Tag actual paint width。
|
|
142
|
+
let used = 0, count = 0
|
|
143
|
+
for (let i = 0; i < totalCount; i++) {
|
|
144
|
+
const el = tagEls.current[i]
|
|
145
|
+
if (!el) continue
|
|
146
|
+
const tagRoot = el.querySelector('[data-tag-root]') as HTMLElement | null
|
|
147
|
+
const w = tagRoot ? tagRoot.getBoundingClientRect().width : el.offsetWidth
|
|
148
|
+
const next = used + (count > 0 ? gap : 0) + w
|
|
149
|
+
const remaining = totalCount - count - 1
|
|
150
|
+
// width check FIRST(無 `count > 0` 短路):任何超寬都 break,包含 i=0 case
|
|
151
|
+
if (remaining > 0 && next + gap + overflowW > available) break
|
|
152
|
+
if (remaining === 0 && next > available) break
|
|
153
|
+
used = next; count++
|
|
154
|
+
}
|
|
155
|
+
for (let i = 0; i < tagEls.current.length; i++) { const el = tagEls.current[i]; if (el) el.hidden = i >= count }
|
|
156
|
+
if (ofEl) ofEl.hidden = count >= totalCount
|
|
157
|
+
// 2026-05-18 Round 5 last guard(per Codex Round 5 verdict):safety net 防 measurement drift
|
|
158
|
+
// (sub-pixel / rounding)。verify last visible tag rect.right ≤ container right,超出遞減 count。
|
|
159
|
+
const containerRect = container.getBoundingClientRect()
|
|
160
|
+
while (count > 0) {
|
|
161
|
+
const lastEl = tagEls.current[count - 1]
|
|
162
|
+
const tagRoot = lastEl?.querySelector('[data-tag-root]') as HTMLElement | null
|
|
163
|
+
if (!tagRoot) break
|
|
164
|
+
const tagRect = tagRoot.getBoundingClientRect()
|
|
165
|
+
if (tagRect.right <= containerRect.right + 0.5) break
|
|
166
|
+
count--
|
|
167
|
+
if (lastEl) lastEl.hidden = true
|
|
168
|
+
}
|
|
169
|
+
if (ofEl) ofEl.hidden = count >= totalCount
|
|
170
|
+
// 2026-05-18 A' fix functional setState value-equal guard(per Codex Round 3 verdict):
|
|
171
|
+
// sync calc 在 useLayoutEffect 內 + ResizeObserver re-fire 同時跑 → 若每次都 new object setState
|
|
172
|
+
// 觸發 re-render 即使值沒變,可能 cascade。回 prev 不更新 = avoid 抖動。
|
|
173
|
+
setState(prev => (prev.visibleCount === count && prev.ready) ? prev : { visibleCount: count, ready: true })
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// 2026-05-14 I3 fix(per codex M31 verdict + user 抓「全選 vs 逐個勾 result 不同」):
|
|
177
|
+
// double-rAF ensures layout 完成 before measurement(原 single rAF 在 batched render
|
|
178
|
+
// 場景 tag 還 0-width)。Plus observe per-item ResizeObserver — 任何 tag width 變動
|
|
179
|
+
// 都 trigger recalc(deterministic regardless of commit order)。
|
|
180
|
+
//
|
|
181
|
+
// 2026-05-16 Race close:capture rAF IDs + cancel on cleanup(原版 race I3 沒 close
|
|
182
|
+
// 完;user 抓 path A 逐個 click vs path B 取消全選再全選 same length 不同 visible)。
|
|
183
|
+
// 2026-05-18 A' fix(per Codex Round 3 共識,user 拍板「執行」)— sync calc in useLayoutEffect:
|
|
184
|
+
// React 18 `useLayoutEffect` 在 DOM commit 後、瀏覽器繪製前同步跑,sync calc 在 paint 前
|
|
185
|
+
// 完成 `el.hidden` 設定 + functional setState guard(value-equal 不更新 = 避免 ResizeObserver
|
|
186
|
+
// 抖動 cascade rerender)。double-rAF 改 fallback only(ResizeObserver / async update path)。
|
|
187
|
+
// 解 user verbatim「tag 過長 / 過多會先全顯再變 +N 閃動」root cause(per codex Round 3 cite
|
|
188
|
+
// `combobox.tsx:248 render 沒設 hidden + L129 calc imperative 寫 DOM`)。
|
|
189
|
+
// 對齊 React docs https://react.dev/reference/react/useLayoutEffect pre-paint guarantee。
|
|
190
|
+
calc()
|
|
191
|
+
let rafId1 = 0, rafId2 = 0
|
|
192
|
+
const scheduleCalc = () => {
|
|
193
|
+
if (rafId1) { cancelAnimationFrame(rafId1); rafId1 = 0 }
|
|
194
|
+
if (rafId2) { cancelAnimationFrame(rafId2); rafId2 = 0 }
|
|
195
|
+
rafId1 = requestAnimationFrame(() => {
|
|
196
|
+
rafId1 = 0
|
|
197
|
+
rafId2 = requestAnimationFrame(() => {
|
|
198
|
+
rafId2 = 0
|
|
199
|
+
calc()
|
|
200
|
+
})
|
|
201
|
+
})
|
|
202
|
+
}
|
|
203
|
+
scheduleCalc()
|
|
204
|
+
const containerObs = new ResizeObserver(scheduleCalc)
|
|
205
|
+
containerObs.observe(container)
|
|
206
|
+
const itemObs = new ResizeObserver(scheduleCalc)
|
|
207
|
+
for (const el of tagEls.current) {
|
|
208
|
+
if (el) itemObs.observe(el)
|
|
209
|
+
}
|
|
210
|
+
return () => {
|
|
211
|
+
if (rafId1) cancelAnimationFrame(rafId1)
|
|
212
|
+
if (rafId2) cancelAnimationFrame(rafId2)
|
|
213
|
+
containerObs.disconnect()
|
|
214
|
+
itemObs.disconnect()
|
|
215
|
+
}
|
|
216
|
+
}, [containerRef, totalCount, enabled, gap, visibleCountOverride]) // 2026-05-15 Bug 3 fix:visibleCountOverride 入 deps,override 改 trigger recalc
|
|
217
|
+
|
|
218
|
+
return state
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ── OverflowTagList (unchanged) ──────────────────────────────────────────────
|
|
222
|
+
|
|
223
|
+
type ComboboxOverflowShape = 'circle' | 'tag'
|
|
224
|
+
|
|
225
|
+
// 2026-05-16 fix:overflow chip wrapper 必能跟 tag wrapper 套同 overlap class
|
|
226
|
+
// (per user 物理模型「avatar 和 +N 都是同尺寸圓形 + 同 step」)。原 chip wrapper
|
|
227
|
+
// 只有 `shrink-0`,在 stack 模式 -ml-0.5 不 apply → chip 不 overlap → 視覺多 22px
|
|
228
|
+
// 額外空間 → length=4→4 / length=5→2+3 saw bug 物理根因。
|
|
229
|
+
// PeoplePicker stack mode pass `'-ml-0.5 first:ml-0 relative inline-flex'` 對齊。
|
|
230
|
+
interface OverflowTagListProps {
|
|
231
|
+
containerRef: React.RefObject<HTMLDivElement | null>
|
|
232
|
+
items: { value: string; label: string }[]
|
|
233
|
+
size: 'sm' | 'md' | 'lg'
|
|
234
|
+
wrap: boolean
|
|
235
|
+
renderTag: (item: { value: string; label: string }, index: number) => React.ReactNode
|
|
236
|
+
/**
|
|
237
|
+
* 2026-05-14 I4 fix(per codex M31 verdict + user 抓「display overflow 有 avatar / edit 無」):
|
|
238
|
+
* Optional renderer for hidden items in `+N` overflow popover。Default fallback = `<Tag>{label}</Tag>`
|
|
239
|
+
* (純文字 chip,backward-compat)。Consumer pass 此 prop 讓 hidden items 顯示同 avatar 視覺
|
|
240
|
+
* (對齊 display MultiPersonDisplay overflow popover Tag avatar SSOT)。
|
|
241
|
+
*/
|
|
242
|
+
renderHiddenTag?: (item: { value: string; label: string }) => React.ReactNode
|
|
243
|
+
onRemove?: (value: string) => void
|
|
244
|
+
trailing?: React.ReactNode
|
|
245
|
+
/** Tag area gap in px(default 4)。Stack mode 傳 0 讓 negative margin 生效 */
|
|
246
|
+
gap?: number
|
|
247
|
+
/**
|
|
248
|
+
* Optional class merged into each tag's outer measurement wrapper `<div className="shrink-0">`.
|
|
249
|
+
* (2026-05-07 v15.13)為 PeoplePicker stack mode 提供 hook point — 讓 stack avatar 走
|
|
250
|
+
* `-ml-0.5 first:ml-0 relative inline-flex group/avatar` 達成 overlap + dismiss group selector,
|
|
251
|
+
* 同時保留 `useOverflowCount` 量測 wrapper(必要,不可移除)。
|
|
252
|
+
*
|
|
253
|
+
* **Caveat(Q2 known tradeoff)**:`-ml-0.5` 負 margin 不改 each wrapper 的 `offsetWidth` →
|
|
254
|
+
* `useOverflowCount` 累加按完整寬計算 → 視覺實際塞得進的 tag 數 > 量測判定能塞的數 →
|
|
255
|
+
* **+N indicator 偏保守**(視覺還有空間但已顯 `+N`)。當前 v1 接受此 tradeoff;若窄 trigger
|
|
256
|
+
* + 多人場景明顯不對,future 可加 `overlapPx` prop 讓量測補償。
|
|
257
|
+
*/
|
|
258
|
+
tagWrapperClassName?: string
|
|
259
|
+
/** 2026-05-16 fix:overflow chip 圓形 wrapper 套此 class(stack 模式套 `-ml-0.5` overlap),
|
|
260
|
+
* 讓 chip 跟 avatar 同 step 物理 — 避免「chip 多 24px 額外空間」造成 saw transition。*/
|
|
261
|
+
overflowWrapperClassName?: string
|
|
262
|
+
/**
|
|
263
|
+
* Overflow indicator(+N)形狀(2026-05-12 Round 7 fix,user 抓 PeoplePicker stack +N 該圓形):
|
|
264
|
+
* - `'tag'`(default,backward-compat)— 矩形 chip(對齊 Combobox 文字 tag)
|
|
265
|
+
* - `'circle'`(opt-in for avatar stack consumers)— 圓形 avatar-shape +N(對齊 GitHub picker idiom)
|
|
266
|
+
* PeoplePicker stack mode pass `'circle'`,Combobox 文字 tag 自走 `'tag'`。
|
|
267
|
+
*/
|
|
268
|
+
overflowShape?: ComboboxOverflowShape
|
|
269
|
+
/**
|
|
270
|
+
* 2026-05-15 Bug 3 fix:override visible count via formula-based primitive(PeoplePicker stack
|
|
271
|
+
* 用 `avatar-stack-overflow` primitive deterministic formula 計算 visible,bypass DOM offsetWidth
|
|
272
|
+
* measurement)。對齊 user SSOT「同 cell width 同 overflow 判斷」。
|
|
273
|
+
*/
|
|
274
|
+
visibleCountOverride?: number
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function OverflowTagList({ containerRef, items, size, wrap, renderTag, renderHiddenTag, onRemove, trailing, tagWrapperClassName, overflowWrapperClassName, gap = GAP, overflowShape = 'tag', visibleCountOverride }: OverflowTagListProps) {
|
|
278
|
+
const tagEls = React.useRef<(HTMLDivElement | null)[]>([])
|
|
279
|
+
const overflowEl = React.useRef<HTMLDivElement>(null)
|
|
280
|
+
const { visibleCount, ready } = useOverflowCount(containerRef, tagEls, overflowEl, items.length, !wrap, gap, visibleCountOverride)
|
|
281
|
+
tagEls.current.length = items.length
|
|
282
|
+
|
|
283
|
+
if (wrap) return <>{items.map((item, i) => renderTag(item, i))}{trailing}</>
|
|
284
|
+
|
|
285
|
+
const overflow = items.length - visibleCount
|
|
286
|
+
const hiddenItems = items.slice(visibleCount)
|
|
287
|
+
|
|
288
|
+
// 2026-05-18 A' fix(per Codex Round 3 共識,user 拍板「執行」)— 撤掉舊的 `style={{ opacity: ready ? 1 : 0 }}`
|
|
289
|
+
// gate(掛在 `<span className="contents">` 上,但 CSS Display 3 spec 規定 `display:contents` 元素不產生 box,
|
|
290
|
+
// parent opacity 對 children 無效 → gate 從沒生效是 dead code)。Flicker 已改 sync calc in useLayoutEffect 解
|
|
291
|
+
// (L153 `calc()` 直跑,paint 前 hidden 設好)。`ready` state 保留供未來 instrumentation / debug,不再當 visual gate。
|
|
292
|
+
// 保留 `<span className="contents">` 維持 fragment-like rendering(parent JSX 期望 single child)。
|
|
293
|
+
void ready // intentional: ready 留供 future debug,目前無 consumer
|
|
294
|
+
return (
|
|
295
|
+
<span className="contents">
|
|
296
|
+
{items.map((item, i) => (
|
|
297
|
+
// 2026-05-14 I5 fix(per codex M31 verdict + user 抓「avatar stack 堆疊方向不一致」):
|
|
298
|
+
// 加 z-index per-index — 前 item z 高(對齊 MultiPersonDisplay zIndex: visible.length - i
|
|
299
|
+
// canonical + MUI AvatarGroup surplus pattern)。display + edit stack 堆疊方向統一。
|
|
300
|
+
<div key={item.value} ref={el => { tagEls.current[i] = el }} className={cn('shrink-0 max-w-full', tagWrapperClassName)} style={{ zIndex: items.length - i }}>{renderTag(item, i)}</div>
|
|
301
|
+
))}
|
|
302
|
+
<div ref={overflowEl} className={cn('shrink-0', overflowWrapperClassName)}>
|
|
303
|
+
<OverflowIndicator count={overflow} shape={overflowShape} size={size}>
|
|
304
|
+
{hiddenItems.map(item => (
|
|
305
|
+
renderHiddenTag
|
|
306
|
+
? <React.Fragment key={item.value}>{renderHiddenTag(item)}</React.Fragment>
|
|
307
|
+
: <Tag key={item.value} size="sm" onDismiss={onRemove ? () => onRemove(item.value) : undefined}>
|
|
308
|
+
{item.label}
|
|
309
|
+
</Tag>
|
|
310
|
+
))}
|
|
311
|
+
</OverflowIndicator>
|
|
312
|
+
</div>
|
|
313
|
+
{trailing}
|
|
314
|
+
</span>
|
|
315
|
+
)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// ── Internal tag-stack renderer (consumed by ReadonlyMultiSelect / mode='display') ───
|
|
319
|
+
//
|
|
320
|
+
// Phase B2(2026-05-05):原 ComboboxDisplay sub-component 已 retire,改 inline `<Combobox mode="display">`。
|
|
321
|
+
// 本 helper 只負責 tag-stack 內容渲染(OverflowTagList 消費),不包 Field wrapper。
|
|
322
|
+
function ComboboxTagStack({
|
|
323
|
+
value, options, tagSize = 'md', wrap = false, containerRef: externalRef, disabled = false,
|
|
324
|
+
}: {
|
|
325
|
+
value?: string[] | null; options?: SelectOption[]; tagSize?: 'sm' | 'md' | 'lg'
|
|
326
|
+
wrap?: boolean; containerRef?: React.RefObject<HTMLDivElement | null>; disabled?: boolean
|
|
327
|
+
}) {
|
|
328
|
+
const ownRef = React.useRef<HTMLDivElement>(null)
|
|
329
|
+
if (!value || value.length === 0) return <span className="text-fg-muted">{EMPTY_DISPLAY}</span>
|
|
330
|
+
const items = value.map(v => ({ value: v, label: options?.find(o => o.value === v)?.label ?? v }))
|
|
331
|
+
const disabledClass = disabled ? 'bg-disabled text-fg-disabled' : undefined
|
|
332
|
+
|
|
333
|
+
const content = (
|
|
334
|
+
<OverflowTagList containerRef={externalRef ?? ownRef} items={items} size={tagSize} wrap={wrap}
|
|
335
|
+
// 2026-05-18 7B' fix(per user 拍板「執行」+ Codex Round 3 共識)— 移除 `unbounded`,Tag 回預設
|
|
336
|
+
// `max-w-40` cap(160px)+ 內建 `[data-tag-text] truncate min-w-0` 自帶 ellipsis。原 unbounded 是
|
|
337
|
+
// 「cell-as-input narrow cell < 160px」設計(`tag.tsx:85-90`),但 generic Combobox tag display
|
|
338
|
+
// 應走 Tag canonical cap-with-ellipsis(per `data-table.spec.md:235`「Tag 文字內部 truncate;
|
|
339
|
+
// multiSelect 動態 +N」+ `data-table.spec.md:467`「Tag 不可被外層 overflow-hidden 裁掉邊框」)。
|
|
340
|
+
// Trade-off:長 tag 觸發 +N 提前(160 + gap + overflowW > available 較 quick)— acceptable per user。
|
|
341
|
+
// Round 2 dynamic slot maxWidth 提案經 user + codex 共識撤回(3 chicken-and-egg fatal,KISS 勝)。
|
|
342
|
+
renderTag={(item) => <Tag size={tagSize} className={cn('shrink-0', disabledClass)}>{item.label}</Tag>} />
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
if (externalRef) return content
|
|
346
|
+
// 2026-05-05 v9 fix(Bug 4):display path 內 wrapper 必須 `flex-1 min-w-0`,否則在 cell flex
|
|
347
|
+
// parent 下不認領完整可用寬度 → OverflowTagList 量得寬度小於 edit path → 顯 `+N` 多於 edit。
|
|
348
|
+
// edit path tagAreaRef wrapper 已是 `flex-1 min-w-0`(NativeCombobox/CustomCombobox line 258 / 354),
|
|
349
|
+
// display 必對稱才 SSOT。
|
|
350
|
+
// 2026-05-15 F1 Q3 fix(per user round 3 verbatim「單人選取時 Tag 越界蓋 indicator」):
|
|
351
|
+
// `overflow-visible` → `overflow-hidden` 讓 narrow cell width 強制 clip(Tag 內建 truncate
|
|
352
|
+
// 處理 text ellipsis,stack `-ml-0.5` 負 margin 在 wrapper 內不受影響)。對齊
|
|
353
|
+
// `data-table.spec.md:233`「禁硬裁無 ellipsis」+ MUI X / Ant Table column.ellipsis 共識。
|
|
354
|
+
// (2026-05-14 nakedCellRowModeAlign 同保留 — autoRowHeight cell first-line align canonical。)
|
|
355
|
+
return (
|
|
356
|
+
<div ref={ownRef} className={cn('flex-1 min-w-0 flex items-center', nakedCellRowModeAlign, wrap ? 'flex-wrap' : 'overflow-hidden')} style={{ gap: GAP }}>
|
|
357
|
+
{content}
|
|
358
|
+
</div>
|
|
359
|
+
)
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// ── Types ───────────────────────────────────────────────────────────────────
|
|
363
|
+
|
|
364
|
+
export interface ComboboxProps {
|
|
365
|
+
mode?: FieldMode
|
|
366
|
+
/** Field chrome variant. Default = context.variant ?? 'default'. Per-prop override. */
|
|
367
|
+
variant?: FieldVariant
|
|
368
|
+
error?: boolean
|
|
369
|
+
size?: 'sm' | 'md' | 'lg'
|
|
370
|
+
options: SelectOption[]
|
|
371
|
+
value?: string[]
|
|
372
|
+
onChange?: (value: string[]) => void
|
|
373
|
+
placeholder?: string
|
|
374
|
+
className?: string
|
|
375
|
+
disabled?: boolean
|
|
376
|
+
wrap?: boolean
|
|
377
|
+
clearable?: boolean
|
|
378
|
+
/** 啟用搜尋 */
|
|
379
|
+
searchable?: boolean
|
|
380
|
+
/** Loading state(2026-05-15 audit B fix per user verbatim「dropdown 隨時可開,讀取在 panel 中間 CircularProgress」)。
|
|
381
|
+
* Forward 給 SelectMenu primitive SSOT;dropdown 開啟時取代 options 顯 CircularProgress + loadingText。
|
|
382
|
+
* Trigger 不變(user 隨時可開)。對齊 MUI Autocomplete `loadingText` + Field SSOT + Empty 元件 compose。*/
|
|
383
|
+
loading?: boolean
|
|
384
|
+
/** 搜尋框位置:menu(浮層內,預設)或 trigger(inline input) */
|
|
385
|
+
searchIn?: 'menu' | 'trigger'
|
|
386
|
+
/** 搜尋框 placeholder(未有選項時顯示)。Default: 「搜尋…」 */
|
|
387
|
+
searchPlaceholder?: string
|
|
388
|
+
/** 搜尋框 ARIA label。Default: 「搜尋選項」 */
|
|
389
|
+
searchAriaLabel?: string
|
|
390
|
+
/** Empty-selection placeholder text。Default: 「選擇…」 */
|
|
391
|
+
emptyPlaceholder?: string
|
|
392
|
+
/** a11y:無 Field wrapper 時提供 role='combobox' 的 accessible name(axe aria-input-field-name) */
|
|
393
|
+
'aria-label'?: string
|
|
394
|
+
/** Initial open state(uncontrolled)— 對齊 Select.defaultOpen / Radix Popover canonical。
|
|
395
|
+
* DataTable cell-as-input 1-step open 用 */
|
|
396
|
+
defaultOpen?: boolean
|
|
397
|
+
/** open state 變更 callback。DataTable cell-as-input 用:open=false → cell exit edit */
|
|
398
|
+
onOpenChange?: (open: boolean) => void
|
|
399
|
+
/**
|
|
400
|
+
* Selected tag pill 客製 render(2026-05-07 v15.5)。
|
|
401
|
+
*
|
|
402
|
+
* 設了 → 每個 selected tag pill 走 consumer 提供的 ReactNode(收 item={value, label}
|
|
403
|
+
* + onRemove,consumer 自己組 onDismiss);沒設 → 走預設 `<Tag>` text-only pill。
|
|
404
|
+
*
|
|
405
|
+
* 用例:PeoplePicker(multi)用此 slot 把 selected tag 換成 avatar + name pill,而非
|
|
406
|
+
* 純文字 Tag。對齊 PeoplePicker = Combobox wrapper SSOT。
|
|
407
|
+
*/
|
|
408
|
+
tagRenderer?: (item: { value: string; label: string }, onRemove: () => void) => React.ReactNode
|
|
409
|
+
/**
|
|
410
|
+
* 2026-05-14 I4 fix:Optional renderer for hidden items in `+N` overflow popover
|
|
411
|
+
* (對齊 display MultiPersonDisplay overflow popover 含 avatar SSOT)。PeoplePicker stack
|
|
412
|
+
* pass 此 prop 讓 hidden items 顯 avatar + name(同 display path)。Default fallback
|
|
413
|
+
* `<Tag>{label}</Tag>` 純文字 backward-compat。
|
|
414
|
+
*/
|
|
415
|
+
renderHiddenTag?: (item: { value: string; label: string }) => React.ReactNode
|
|
416
|
+
/**
|
|
417
|
+
* Optional class merged into each tag's outer measurement wrapper (2026-05-07 v15.13)。
|
|
418
|
+
* Stack avatar 模式用此 hook point 達成 sibling-level overlap (`-ml-0.5`) + group selector
|
|
419
|
+
* (`group/avatar`)— 既保留 Combobox 必要 measurement wrapper,又讓 dismiss/overlap 視覺生效。
|
|
420
|
+
*/
|
|
421
|
+
tagWrapperClassName?: string
|
|
422
|
+
/** 2026-05-16:overflow chip wrapper 套此 class(對齊 tagWrapperClassName)。Stack 模式
|
|
423
|
+
* pass `-ml-0.5 first:ml-0` 讓 chip 跟 avatar 同 overlap step,物理上 chip = 1 個 slot 不
|
|
424
|
+
* 外加 24px。Default undefined = chip 不 overlap(text-tag mode 等)。*/
|
|
425
|
+
overflowWrapperClassName?: string
|
|
426
|
+
/**
|
|
427
|
+
* Tag area gap in px (2026-05-07 v15.13)。預設 4(pill mode 標準 spacing)。
|
|
428
|
+
* Stack avatar 模式傳 0,讓 `tagWrapperClassName` 的 `-ml-0.5` negative margin 生效
|
|
429
|
+
* (CSS `gap` 套在 flex container 上會強制 sibling spacing,蓋過 negative margin)。
|
|
430
|
+
* **Q2 known tradeoff**:0 後 useOverflowCount 仍按 wrapper.offsetWidth 累加(不含 overlap
|
|
431
|
+
* 補償)→ +N 偏保守。當前接受;若需精準可 future 加 `overlapPx` 補償邏輯。
|
|
432
|
+
*/
|
|
433
|
+
tagAreaGapPx?: number
|
|
434
|
+
/**
|
|
435
|
+
* tagAreaRef container 左 paddingLeft(px,2026-05-12 加,for PeoplePicker Avatar inset)。
|
|
436
|
+
* Default undefined = no extra padding(Field wrapper `tagPadding[size]` calc 公式自然 inset)。
|
|
437
|
+
* 設值時 tagAreaRef 增 `style.paddingLeft`,**useOverflowCount 的 `available = clientWidth -
|
|
438
|
+
* paddingLeft - paddingRight` 自動 include**(`parseFloat(cs.paddingLeft)` 從 container CSS 抓)
|
|
439
|
+
* → width calc 不漂移,無 side-effect。
|
|
440
|
+
*
|
|
441
|
+
* **2026-05-13 v2 deprecate path**:原 PeoplePicker pass `{8}` 假設「Combobox tagPadding=4px,4+8=12」
|
|
442
|
+
* 但 `tagPadding[size]` 是 density-dependent calc `(field-height - icon-size) / 2`,只在 md size +
|
|
443
|
+
* default density 才 = 4px;其他 size/density 漂 6/8px → 4+8=12 公式破。改 PeoplePicker 直接 inject
|
|
444
|
+
* `!px-3` className 到 Combobox Field wrapper(per people-picker.spec.md:94 v2),`tagAreaPaddingLeftPx`
|
|
445
|
+
* 走 undefined。Future 仍保留此 prop 給其他 consumer 精準調整 padding,但 PeoplePicker 已不再用。
|
|
446
|
+
*/
|
|
447
|
+
tagAreaPaddingLeftPx?: number
|
|
448
|
+
/**
|
|
449
|
+
* Overflow indicator (+N) 形狀(2026-05-12 Round 7,opt-in 給 avatar stack consumer):
|
|
450
|
+
* - `'tag'`(default)— 矩形 chip(Combobox 文字 tag default)
|
|
451
|
+
* - `'circle'`(opt-in)— 圓形 avatar-shape(PeoplePicker stack 用)
|
|
452
|
+
*/
|
|
453
|
+
overflowShape?: ComboboxOverflowShape
|
|
454
|
+
/**
|
|
455
|
+
* 2026-05-15 Bug 3 fix:override visible count via formula-based primitive(opt-in;default 走
|
|
456
|
+
* DOM-based `useOverflowCount`)。PeoplePicker stack mode 用 `avatar-stack-overflow` primitive
|
|
457
|
+
* deterministic formula 計算 visible count,forward 給 Combobox bypass DOM offsetWidth
|
|
458
|
+
* measurement,避免 dual-algorithm drift。對齊 user SSOT「同 cell width 同 overflow 判斷」。
|
|
459
|
+
*/
|
|
460
|
+
visibleCountOverride?: number
|
|
461
|
+
/**
|
|
462
|
+
* Display 是否渲 ChevronDown + Field naked wrapper(D-path opt-in,2026-05-08)
|
|
463
|
+
* — DataTable cell display↔edit 像素級對齊用。預設 false(裸 tag stack,backward compat)。
|
|
464
|
+
* 設 true 時 display 走 fieldWrapperStyles(naked variant)+ ItemSuffix ChevronDown,
|
|
465
|
+
* 與 edit 同 DOM 結構,消除 Layer-B padding mismatch。
|
|
466
|
+
*/
|
|
467
|
+
showDisplayEndIcon?: boolean
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// 2026-05-18 改 import ICON_SIZE SSOT(per user『做完』approval,消除 M17 違反)
|
|
471
|
+
const getIconSize = (size: string) => ICON_SIZE[size as 'sm' | 'md' | 'lg']
|
|
472
|
+
|
|
473
|
+
// ── Shared readonly/disabled/display render ─────────────────────────────────
|
|
474
|
+
|
|
475
|
+
function ReadonlyMultiSelect({
|
|
476
|
+
mode, variant: variantProp, size, options, value, wrap, className, showDisplayEndIcon = false,
|
|
477
|
+
}: Pick<ComboboxProps, 'mode' | 'variant' | 'size' | 'options' | 'value' | 'wrap' | 'className' | 'showDisplayEndIcon'>) {
|
|
478
|
+
const resolvedMode = mode ?? 'readonly'
|
|
479
|
+
const variant = variantProp ?? 'default'
|
|
480
|
+
const sz = size ?? 'md'
|
|
481
|
+
const iconSize = sz === 'lg' ? 20 : 16
|
|
482
|
+
const containerRef = React.useRef<HTMLDivElement>(null)
|
|
483
|
+
const hasTags = (value?.length ?? 0) > 0
|
|
484
|
+
|
|
485
|
+
// mode='display'(Phase B2 2026-05-05):純內容輸出 — tag stack 不包 Field wrapper / 不 reserve 高度。
|
|
486
|
+
// 對齊原 ComboboxDisplay sub-component(retired)。
|
|
487
|
+
// Opt-in(showDisplayEndIcon=true,2026-05-08 D-path):Field naked wrapper + ItemSuffix ChevronDown,
|
|
488
|
+
// 與 edit 同結構消除 cell display↔edit 像素偏移(Layer-B padding mismatch)。
|
|
489
|
+
if (resolvedMode === 'display') {
|
|
490
|
+
if (!showDisplayEndIcon) {
|
|
491
|
+
// 2026-05-14 I2 fix(spec contract (e) display typography canonical):empty bare span 套
|
|
492
|
+
// `fieldDisplayTextClass(sz)`(sm/md→text-body,lg→text-body-lg)— 對齊跨 Field family 統一。
|
|
493
|
+
if (!hasTags) return <span className={cn(fieldDisplayTextClass(sz), 'text-fg-muted', className)}>{EMPTY_DISPLAY}</span>
|
|
494
|
+
return (
|
|
495
|
+
<ComboboxTagStack value={value} options={options} tagSize={sz} wrap={wrap} />
|
|
496
|
+
)
|
|
497
|
+
}
|
|
498
|
+
return (
|
|
499
|
+
<div
|
|
500
|
+
className={cn(fieldWrapperStyles({ mode: 'display', variant, size: sz }), hasTags && tagPadding[sz], className)}
|
|
501
|
+
data-field-mode="display"
|
|
502
|
+
>
|
|
503
|
+
{hasTags ? (
|
|
504
|
+
<ComboboxTagStack value={value} options={options} tagSize={sz} wrap={wrap} />
|
|
505
|
+
) : (
|
|
506
|
+
<span className={cn('flex-1 min-w-0', 'text-fg-muted')}>{EMPTY_DISPLAY}</span>
|
|
507
|
+
)}
|
|
508
|
+
<ItemSuffix className="pointer-events-none">
|
|
509
|
+
<ChevronDown size={iconSize} className="shrink-0 text-fg-muted" aria-hidden />
|
|
510
|
+
</ItemSuffix>
|
|
511
|
+
</div>
|
|
512
|
+
)
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
return (
|
|
516
|
+
<div ref={containerRef}
|
|
517
|
+
className={cn(fieldWrapperStyles({ mode: resolvedMode, variant, size: sz }), hasTags && tagPadding[sz],
|
|
518
|
+
// 2026-05-18 #6A Round 1 Step 1/4(per user 拍板「決策6選a」+ codex M31 Step 5 verdict cite combobox.tsx:451):
|
|
519
|
+
// readonly/disabled path 對齊 L293 display wrapper 已 ship 的 overflow-hidden fix。
|
|
520
|
+
// M10 propagation:原 overflow-visible 讓 readonly tag 越界蓋 indicator,跟 display 不對稱。
|
|
521
|
+
wrap ? 'flex-wrap py-1' : 'overflow-hidden', className)}
|
|
522
|
+
style={{ gap: GAP, ...(wrap ? { height: 'auto' } : undefined) }} data-field-mode={resolvedMode}>
|
|
523
|
+
{hasTags ? (
|
|
524
|
+
<ComboboxTagStack value={value} options={options} tagSize={sz} wrap={wrap}
|
|
525
|
+
containerRef={containerRef} disabled={resolvedMode === 'disabled'} />
|
|
526
|
+
) : (
|
|
527
|
+
<span className="text-fg-muted">{EMPTY_DISPLAY}</span>
|
|
528
|
+
)}
|
|
529
|
+
</div>
|
|
530
|
+
)
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// ── Native Combobox (mobile) ────────────────────────────────────────
|
|
534
|
+
|
|
535
|
+
// 2026-05-16 Bug A root cause fix(Claude+Codex M31 Step 5 比稿 consensus,user verbatim
|
|
536
|
+
// 「圖二/圖三 同 180px 不同 length 不同 visible — 跟 user 一開始抓的問題一模一樣」):
|
|
537
|
+
// 公開 `Combobox.forwardRef` 之前用 `(props, _ref)` 把 ref drop,內部 `NativeCombobox` /
|
|
538
|
+
// `CustomCombobox` 從未拿 ref → PeoplePicker `stackContainerRef.current` 永遠 null →
|
|
539
|
+
// `useLayoutEffect` early return → `visibleCountOverride` 永遠 undefined →
|
|
540
|
+
// Combobox 走原 internal `useOverflowCount` 60px chip fallback bug → drift。
|
|
541
|
+
// Fix:internal `__triggerRef` prop(underscore = internal-only)attach root div;
|
|
542
|
+
// 公開 `Combobox.forwardRef` 把 `ref` forward 為 `__triggerRef`。對齊 codex DS-wide iceberg
|
|
543
|
+
// audit:`SelectMenu` / `DateGrid` / `Toast` 的 `_ref` 是 intentional documented(no DOM
|
|
544
|
+
// target);唯本處 actionable drop。
|
|
545
|
+
type ComboboxInternalProps = ComboboxProps & { __triggerRef?: React.Ref<HTMLDivElement> }
|
|
546
|
+
|
|
547
|
+
function NativeCombobox({
|
|
548
|
+
mode = 'edit', variant: variantProp, error = false, size = 'md', options, value = [], onChange, placeholder,
|
|
549
|
+
className, disabled, wrap = false, clearable = false, showDisplayEndIcon = false,
|
|
550
|
+
__triggerRef,
|
|
551
|
+
}: ComboboxInternalProps) {
|
|
552
|
+
const fieldCtx = useFieldContext()
|
|
553
|
+
const variant: FieldVariant = variantProp ?? fieldCtx?.variant ?? 'default'
|
|
554
|
+
const resolvedMode = disabled ? 'disabled' : mode
|
|
555
|
+
const iconSize = getIconSize(size)
|
|
556
|
+
const showClear = clearable && value.length > 0 && resolvedMode === 'edit'
|
|
557
|
+
|
|
558
|
+
const handleRemove = (v: string) => onChange?.(value.filter(x => x !== v))
|
|
559
|
+
const handleAdd = (v: string) => { if (!value.includes(v)) onChange?.([...value, v]) }
|
|
560
|
+
|
|
561
|
+
if (resolvedMode !== 'edit') {
|
|
562
|
+
return <ReadonlyMultiSelect mode={resolvedMode} variant={variant} size={size} options={options} value={value} wrap={wrap} className={className} showDisplayEndIcon={showDisplayEndIcon} />
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
const items = value.map(v => ({ value: v, label: options.find(o => o.value === v)?.label ?? v }))
|
|
566
|
+
const unselected = options.filter(o => !value.includes(o.value))
|
|
567
|
+
const selectRef = React.useRef<HTMLSelectElement>(null)
|
|
568
|
+
const tagAreaRef = React.useRef<HTMLDivElement>(null)
|
|
569
|
+
const tagHeight = size === 'sm' ? 20 : 24
|
|
570
|
+
|
|
571
|
+
const selectDropdown = unselected.length > 0 ? (
|
|
572
|
+
<select ref={selectRef} value="" onChange={(e) => handleAdd(e.target.value)}
|
|
573
|
+
className={cn('bg-transparent outline-none border-none p-0 text-[inherit] font-[inherit] leading-[inherit] text-fg-muted cursor-pointer appearance-none',
|
|
574
|
+
value.length > 0 ? 'absolute inset-0 w-full h-full opacity-0 z-0 cursor-pointer' : 'relative z-10 flex-1 min-w-20')}>
|
|
575
|
+
<option value="" disabled>{placeholder ?? '選擇...'}</option>
|
|
576
|
+
{unselected.map(opt => <option key={opt.value} value={opt.value}>{opt.label}</option>)}
|
|
577
|
+
</select>
|
|
578
|
+
) : null
|
|
579
|
+
|
|
580
|
+
return (
|
|
581
|
+
<div ref={__triggerRef} className={cn(fieldWrapperStyles({ mode: 'edit', variant: variant, size }), value.length > 0 && tagPadding[size], 'relative',
|
|
582
|
+
wrap && 'items-start py-1', error && ['border-error hover:border-error-hover', 'focus-within:border-error focus-within:hover:border-error'], className)}
|
|
583
|
+
style={{ paddingRight: '0.75rem', ...(wrap ? { height: 'auto' } : undefined) }} data-field-mode="edit" data-error={error ? '' : undefined}
|
|
584
|
+
onClick={(e) => { if (e.target === e.currentTarget) { selectRef.current?.showPicker?.(); selectRef.current?.focus() } }}>
|
|
585
|
+
{/* 2026-05-18 F2 sync(per user verbatim「modifying 修好 PeoplePicker stack 後改壞 Combobox tag display」
|
|
586
|
+
+ 「tag 應該要判斷所在空間最多可以呈現幾個tag(包括+n)去自動判斷何時要變成+n」):
|
|
587
|
+
edit path tagArea 對齊 display path L293 已 ship 的 `overflow-hidden` fix。原 `overflow-visible`
|
|
588
|
+
讓 tag 視覺越界蓋 chevron / +N indicator(useOverflowCount measurement 對但 CSS overflow 仍露)。
|
|
589
|
+
M10 violation root cause:2026-05-15 F1 Q3 只 fix display path,edit + Native(L518)沒同步。 */}
|
|
590
|
+
<div ref={tagAreaRef} className={cn('flex-1 min-w-0 flex items-center relative', nakedCellRowModeAlign, wrap ? 'flex-wrap' : 'overflow-hidden')} style={{ gap: GAP }}
|
|
591
|
+
onClick={(e) => { if (e.target === e.currentTarget) { selectRef.current?.showPicker?.(); selectRef.current?.focus() } }}>
|
|
592
|
+
<OverflowTagList containerRef={tagAreaRef} items={items} size={size} wrap={wrap}
|
|
593
|
+
renderTag={(item) => (
|
|
594
|
+
<Tag size={size} className="shrink-0 relative z-10" onClick={() => { selectRef.current?.showPicker?.(); selectRef.current?.focus() }}
|
|
595
|
+
onDismiss={() => handleRemove(item.value)}>{item.label}</Tag>
|
|
596
|
+
)} onRemove={handleRemove} trailing={value.length === 0 ? selectDropdown : undefined} />
|
|
597
|
+
</div>
|
|
598
|
+
{value.length > 0 && selectDropdown}
|
|
599
|
+
<ItemSuffix className={cn('relative z-10 pointer-events-none', wrap && 'self-start')}
|
|
600
|
+
style={wrap ? { height: tagHeight } : undefined}>
|
|
601
|
+
{showClear && (
|
|
602
|
+
<span className="pointer-events-auto">
|
|
603
|
+
<ItemInlineAction
|
|
604
|
+
size={size ?? 'md'}
|
|
605
|
+
action={{ icon: X, label: '清除全部', onClick: () => onChange?.([]) }} // i18n-allow: DS default inline-action label
|
|
606
|
+
/>
|
|
607
|
+
</span>
|
|
608
|
+
)}
|
|
609
|
+
<ChevronDown size={iconSize} className="shrink-0 text-fg-muted pointer-events-none" aria-hidden />
|
|
610
|
+
</ItemSuffix>
|
|
611
|
+
</div>
|
|
612
|
+
)
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// ── Custom Combobox (desktop — consumes SelectMenu) ───────────────────
|
|
616
|
+
|
|
617
|
+
function CustomCombobox({
|
|
618
|
+
mode = 'edit', variant: variantProp, error: errorProp = false, size = 'md', options, value = [], onChange, placeholder,
|
|
619
|
+
className, disabled: disabledProp, wrap = false, clearable = false, searchable = false, loading, searchIn = 'menu',
|
|
620
|
+
searchPlaceholder = '搜尋…', // i18n-allow: DS default
|
|
621
|
+
searchAriaLabel = '搜尋選項', // i18n-allow: DS default
|
|
622
|
+
emptyPlaceholder = '選擇…', // i18n-allow: DS default
|
|
623
|
+
defaultOpen = false,
|
|
624
|
+
onOpenChange,
|
|
625
|
+
__triggerRef,
|
|
626
|
+
tagRenderer,
|
|
627
|
+
renderHiddenTag,
|
|
628
|
+
tagWrapperClassName,
|
|
629
|
+
overflowWrapperClassName,
|
|
630
|
+
tagAreaGapPx,
|
|
631
|
+
visibleCountOverride,
|
|
632
|
+
tagAreaPaddingLeftPx,
|
|
633
|
+
overflowShape,
|
|
634
|
+
showDisplayEndIcon = false,
|
|
635
|
+
'aria-label': ariaLabel,
|
|
636
|
+
}: ComboboxInternalProps) {
|
|
637
|
+
const tagAreaGap = tagAreaGapPx ?? GAP
|
|
638
|
+
const fieldCtx = useFieldContext()
|
|
639
|
+
const error = errorProp || (fieldCtx?.invalid ?? false)
|
|
640
|
+
const disabled = disabledProp ?? fieldCtx?.disabled
|
|
641
|
+
const resolvedMode = disabled ? 'disabled' : mode
|
|
642
|
+
const variant: FieldVariant = variantProp ?? fieldCtx?.variant ?? 'default'
|
|
643
|
+
const iconSize = getIconSize(size)
|
|
644
|
+
const showClear = clearable && value.length > 0 && resolvedMode === 'edit'
|
|
645
|
+
const [open, setOpen] = React.useState(defaultOpen)
|
|
646
|
+
const [search, setSearch] = React.useState('')
|
|
647
|
+
// 2026-05-12 Q3 fix:trigger 內 inline 搜尋 input ref,onOpenAutoFocus 時 explicit focus
|
|
648
|
+
// 讓 user 看到 cursor 知道可 inline search(跟 Select inputRef SSOT 同模式)。
|
|
649
|
+
const inputRef = React.useRef<HTMLInputElement>(null)
|
|
650
|
+
// a11y: 為 listbox 容器(SelectMenu 內 PopoverContent)建立穩定 id,讓 trigger 的
|
|
651
|
+
// aria-controls 能指向它(WAI-ARIA combobox pattern 要求)。React.useId 在 SSR/CSR 都穩定。
|
|
652
|
+
const listboxId = React.useId()
|
|
653
|
+
|
|
654
|
+
React.useEffect(() => { if (!open) setSearch('') }, [open])
|
|
655
|
+
|
|
656
|
+
if (resolvedMode !== 'edit') {
|
|
657
|
+
return <ReadonlyMultiSelect mode={resolvedMode} variant={variant} size={size} options={options} value={value} wrap={wrap} className={className} showDisplayEndIcon={showDisplayEndIcon} />
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
const items = React.useMemo(
|
|
661
|
+
() => value.map(v => ({ value: v, label: options.find(o => o.value === v)?.label ?? v })),
|
|
662
|
+
[value, options]
|
|
663
|
+
)
|
|
664
|
+
const tagAreaRef = React.useRef<HTMLDivElement>(null)
|
|
665
|
+
const tagHeight = size === 'sm' ? 20 : 24
|
|
666
|
+
|
|
667
|
+
const handleRemove = (v: string) => onChange?.(value.filter(x => x !== v))
|
|
668
|
+
|
|
669
|
+
// searchIn='trigger' 時由 trigger input 過濾,不走 SelectMenu 內建搜尋
|
|
670
|
+
const filteredOptions = React.useMemo(
|
|
671
|
+
() => (searchable && searchIn === 'trigger' && search
|
|
672
|
+
? options.filter(o => o.label.toLowerCase().includes(search.toLowerCase()))
|
|
673
|
+
: options),
|
|
674
|
+
[searchable, searchIn, search, options]
|
|
675
|
+
)
|
|
676
|
+
|
|
677
|
+
// 轉換 SelectOption → SelectMenuOption
|
|
678
|
+
// 2026-05-10 post-Issue-4 follow-up:forward 全 SelectMenuOption surface(avatar / description /
|
|
679
|
+
// disabled / icon / group)— 修先前 PeoplePicker multi-mode dropdown 漏 avatar drift bug。
|
|
680
|
+
const menuOptions: SelectMenuOption[] = React.useMemo(
|
|
681
|
+
() => filteredOptions.map(opt => ({
|
|
682
|
+
value: opt.value,
|
|
683
|
+
label: opt.label,
|
|
684
|
+
icon: opt.icon,
|
|
685
|
+
avatar: opt.avatar,
|
|
686
|
+
description: opt.description,
|
|
687
|
+
disabled: opt.disabled,
|
|
688
|
+
group: opt.group,
|
|
689
|
+
})),
|
|
690
|
+
[filteredOptions]
|
|
691
|
+
)
|
|
692
|
+
|
|
693
|
+
const chevronEl = <ChevronDown size={iconSize} className={cn('shrink-0 text-fg-muted transition-transform', open && 'rotate-180')} aria-hidden />
|
|
694
|
+
|
|
695
|
+
const trigger = (
|
|
696
|
+
<div
|
|
697
|
+
ref={__triggerRef}
|
|
698
|
+
id={fieldCtx?.id}
|
|
699
|
+
role="combobox" aria-expanded={open} aria-controls={listboxId} tabIndex={0}
|
|
700
|
+
aria-label={ariaLabel}
|
|
701
|
+
aria-invalid={error || undefined}
|
|
702
|
+
aria-required={fieldCtx?.required || undefined}
|
|
703
|
+
aria-describedby={fieldCtx?.descriptionId}
|
|
704
|
+
aria-errormessage={error ? fieldCtx?.errorId : undefined}
|
|
705
|
+
className={cn(fieldWrapperStyles({ mode: 'edit', variant: variant, size }), value.length > 0 && tagPadding[size], 'relative cursor-pointer',
|
|
706
|
+
wrap && 'items-start py-1',
|
|
707
|
+
// 2026-05-06 v13.3 SSOT retire:per-control `open && 'border-primary'` 移除。Field default
|
|
708
|
+
// 統一處理 — open=灰深(data-state)/ focus=藍(focus-within !important)。改一處全 control 跟動。
|
|
709
|
+
error && ['border-error hover:border-error-hover', 'focus-within:border-error focus-within:hover:border-error'], className)}
|
|
710
|
+
style={{ paddingRight: '0.75rem', ...(wrap ? { height: 'auto' } : undefined) }}
|
|
711
|
+
data-field-mode="edit" data-error={error ? '' : undefined}>
|
|
712
|
+
{/* 2026-05-18 #6A Round 1 Step 2/4(per user 拍板「決策6選a」+ codex M31 Step 5 verdict cite combobox.tsx:648):
|
|
713
|
+
CustomCombobox edit non-wrap tagArea 對齊 L293 display + L451 readonly + L518 native edit 已 ship 的 overflow-hidden fix。
|
|
714
|
+
原 overflow-visible 讓 tag 越界蓋 chevron / +N indicator(user 圖三)。M10 propagation 完整 4-path align。 */}
|
|
715
|
+
<div ref={tagAreaRef} className={cn('flex-1 min-w-0 flex items-center relative', nakedCellRowModeAlign, wrap ? 'flex-wrap' : 'overflow-hidden')} style={{ gap: tagAreaGap, paddingLeft: tagAreaPaddingLeftPx }}>
|
|
716
|
+
{value.length > 0 ? (
|
|
717
|
+
<OverflowTagList containerRef={tagAreaRef} items={items} size={size} wrap={wrap}
|
|
718
|
+
tagWrapperClassName={tagWrapperClassName}
|
|
719
|
+
overflowWrapperClassName={overflowWrapperClassName}
|
|
720
|
+
gap={tagAreaGap}
|
|
721
|
+
overflowShape={overflowShape}
|
|
722
|
+
visibleCountOverride={visibleCountOverride}
|
|
723
|
+
renderTag={(item) => (
|
|
724
|
+
tagRenderer
|
|
725
|
+
? tagRenderer(item, () => handleRemove(item.value))
|
|
726
|
+
: <Tag size={size} className="shrink-0 relative z-10"
|
|
727
|
+
onDismiss={() => handleRemove(item.value)}>{item.label}</Tag>
|
|
728
|
+
)}
|
|
729
|
+
renderHiddenTag={renderHiddenTag}
|
|
730
|
+
onRemove={handleRemove}
|
|
731
|
+
trailing={searchable && searchIn === 'trigger' ? (
|
|
732
|
+
<input ref={inputRef} value={search} onChange={(e) => setSearch(e.target.value)}
|
|
733
|
+
// 2026-05-15 Drift A fix(per user verbatim SSOT clarification「未選 → placeholder 顯示請選擇之類」):
|
|
734
|
+
// items.length === 0(empty selection)→ 用 `placeholder` trigger empty prop(「請選擇…」),
|
|
735
|
+
// **不**用 `searchPlaceholder`(「搜尋…」);後者僅在 panel-top search input 場景才合理。
|
|
736
|
+
// items.length > 0(已選)→ no placeholder,純 cursor(對齊 Combobox empty cursor SSOT)。
|
|
737
|
+
// SSOT 對齊 select.tsx:185 `placeholder={selectedLabel || placeholder || '搜尋…'}`
|
|
738
|
+
// empty-state fallback to trigger placeholder canonical。
|
|
739
|
+
placeholder={items.length === 0 ? placeholder : ''} onClick={(e) => { e.stopPropagation(); setOpen(true) }}
|
|
740
|
+
aria-label={searchAriaLabel}
|
|
741
|
+
className="flex-1 min-w-[60px] bg-transparent outline-none text-body leading-compact relative z-10" />
|
|
742
|
+
) : undefined} />
|
|
743
|
+
) : (
|
|
744
|
+
/* 2026-05-12 Stream C Issue 3 fix(codex Q3 Cluster C):placeholder span 必 flex-1 min-w-0
|
|
745
|
+
truncate,narrow container 時單行省略(對齊 Combobox text-tag truncate canonical)。
|
|
746
|
+
原 hardcode wraps in narrow trigger → user 抓「placeholder 文字 wrap multi-line」。 */
|
|
747
|
+
<span className="flex-1 min-w-0 truncate text-fg-muted">{placeholder ?? emptyPlaceholder}</span>
|
|
748
|
+
)}
|
|
749
|
+
</div>
|
|
750
|
+
<ItemSuffix className={cn('relative z-10 pointer-events-none', wrap && 'self-start')}
|
|
751
|
+
style={wrap ? { height: tagHeight } : undefined}>
|
|
752
|
+
{showClear && (
|
|
753
|
+
<span className="pointer-events-auto">
|
|
754
|
+
<ItemInlineAction
|
|
755
|
+
size={size ?? 'md'}
|
|
756
|
+
action={{
|
|
757
|
+
icon: X,
|
|
758
|
+
label: '清除全部', // i18n-allow: DS default inline-action label
|
|
759
|
+
onClick: (e) => { e?.stopPropagation(); onChange?.([]) },
|
|
760
|
+
}}
|
|
761
|
+
/>
|
|
762
|
+
</span>
|
|
763
|
+
)}
|
|
764
|
+
{chevronEl}
|
|
765
|
+
</ItemSuffix>
|
|
766
|
+
</div>
|
|
767
|
+
)
|
|
768
|
+
|
|
769
|
+
return (
|
|
770
|
+
<SelectMenu
|
|
771
|
+
loading={loading}
|
|
772
|
+
options={menuOptions}
|
|
773
|
+
value={value}
|
|
774
|
+
onValueChange={onChange as (value: string | string[]) => void}
|
|
775
|
+
multiple
|
|
776
|
+
searchable={searchable && searchIn === 'menu'}
|
|
777
|
+
searchPlaceholder={searchPlaceholder}
|
|
778
|
+
size={size}
|
|
779
|
+
open={open}
|
|
780
|
+
onOpenChange={(o) => { setOpen(o); onOpenChange?.(o) }}
|
|
781
|
+
// 2026-05-12 Q3 fix(user 抓「inline-searchable 開浮層應出現 cursor」)— 跟 Select line 550
|
|
782
|
+
// 同 SSOT pattern:preventDefault Radix default focus + 顯式 focus inline input → 開時
|
|
783
|
+
// cursor 直接 visible,user 知道可 inline search。
|
|
784
|
+
onOpenAutoFocus={searchIn === 'trigger' ? (e) => { e.preventDefault(); inputRef.current?.focus() } : undefined}
|
|
785
|
+
contentId={listboxId}
|
|
786
|
+
>
|
|
787
|
+
{trigger}
|
|
788
|
+
</SelectMenu>
|
|
789
|
+
)
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// ── Public component ────────────────────────────────────────────────────────
|
|
793
|
+
|
|
794
|
+
const Combobox = React.forwardRef<HTMLDivElement, ComboboxProps>(
|
|
795
|
+
(props, ref) => {
|
|
796
|
+
// 2026-05-16 真 root cause fix:之前用 `_ref` drop ref。修為 forward 給 internal
|
|
797
|
+
// `__triggerRef`,讓 PeoplePicker stack 透過 ref 量 trigger DOM(visibleCountOverride
|
|
798
|
+
// 才生效)。對齊 React forwardRef public-API canonical(MUI Autocomplete / Radix
|
|
799
|
+
// Popover.Trigger 共識)+ codex M31 Step 5 比稿 verdict + DS-wide ref-drop iceberg audit。
|
|
800
|
+
const isMobile = useIsTouchDevice()
|
|
801
|
+
if (isMobile) return <NativeCombobox {...props} __triggerRef={ref} />
|
|
802
|
+
return <CustomCombobox {...props} __triggerRef={ref} />
|
|
803
|
+
}
|
|
804
|
+
)
|
|
805
|
+
Combobox.displayName = 'Combobox'
|
|
806
|
+
|
|
807
|
+
// Story auto-compile metadata — Phase 1 mechanical migration(2026-04-24)
|
|
808
|
+
// Phase 2 fill needed: purpose descriptions + when rationale + world-class refs
|
|
809
|
+
export const comboboxMeta = {
|
|
810
|
+
component: 'Combobox',
|
|
811
|
+
family: 4,
|
|
812
|
+
variants: {
|
|
813
|
+
|
|
814
|
+
},
|
|
815
|
+
sizes: {
|
|
816
|
+
|
|
817
|
+
},
|
|
818
|
+
states: ['default', 'hover', 'active', 'focus-visible', 'disabled'],
|
|
819
|
+
tokens: {
|
|
820
|
+
bg: ['bg-disabled', 'bg-transparent'],
|
|
821
|
+
fg: ['text-fg-disabled', 'text-fg-muted'],
|
|
822
|
+
ring: [],
|
|
823
|
+
},
|
|
824
|
+
} as const
|
|
825
|
+
|
|
826
|
+
export { Combobox }
|