@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 @@
|
|
|
1
|
+
{"version":3,"file":"item-anatomy.js","sources":["../../../src/patterns/element-anatomy/item-anatomy.tsx"],"sourcesContent":["// @benchmark-unverified-blanket: file-level retraction per M22 (d) — claims herein not individually URL-cited; treat as unverified visual/usage rumor unless retrofit per-claim. Hook escape preserved.\n// code-quality-allow: file-size — foundational SSOT for Family 1+2 row primitives + all item-anatomy helpers(ItemContent / ItemIcon / ItemPrefix / ItemSuffix / ItemInlineAction 等),拆檔會讓 primitive 跨檔 import 滿天飛\nimport * as React from \"react\"\nimport type { LucideIcon } from \"lucide-react\"\nimport { cva } from \"class-variance-authority\"\nimport { cn } from \"@/lib/utils\"\nimport { Avatar, type AvatarProps } from \"@/design-system/components/Avatar/avatar\"\nimport {\n Tooltip,\n TooltipTrigger,\n TooltipContent,\n} from \"@/design-system/components/Tooltip/tooltip\"\n\n// ── InlineActionConfig ─────────────────────────────────────────────────────\n// 宣告式 API:consumer 只宣告 intent,host 根據 size tier 自動渲染。\n// 跨 Family 消費(Field family + Row family),SSOT 住 patterns/element-anatomy/\n// 對齊 inline-action.spec.md「API 設計」節。\n//\n// Canonical 實作:`ItemInlineAction`(本檔內)。\nexport interface InlineActionConfig {\n icon: LucideIcon\n /** aria-label,同時作為 tooltip 來源 */\n label: string\n /**\n * 點擊 handler。可選接收 React 事件物件——有需要時可呼叫 `stopPropagation()` 避免\n * 事件冒泡到父層(例如 Select 清除按鈕在 popover trigger 內,不想觸發 popover open)。\n */\n onClick: (e?: React.MouseEvent<HTMLButtonElement>) => void\n}\n\n/**\n * Item Layout — 共用工具與常數\n *\n * 這個檔案不是元件,是 `item-anatomy.spec.md` 的 code-level 實作。\n * 所有 row primitives(`MenuItem` / `TreeItem` / `SidebarMenuButton` /\n * `SelectionItem`)**必須 import 本 module 的 ICON_SIZE 和 helpers**,\n * 不要各自複製常數或 wrapper className——單一 source of truth,避免不一致。\n *\n * 詳細規則見 `item-anatomy.spec.md`。\n */\n\n// ── Types ──────────────────────────────────────────────────────────────────\n\n/** Row primitive 的 size 變體,對齊 `--field-height-*` token family */\nexport type RowSize = \"sm\" | \"md\" | \"lg\"\n\n// ── Constants ──────────────────────────────────────────────────────────────\n\n/**\n * Icon 尺寸(px)per row size。\n *\n * - sm / md: 16px(標準 row icon)\n * - lg: 20px(對齊 text-body-lg 的視覺重量)\n *\n * **用法**:透過 Lucide icon 的 `size` prop 直接傳入,不要用 CSS `[&>svg]:size-*`\n * selector——當 icon 被包在 `<ItemPrefix>` 的 `h-[1lh]` wrapper 內時,\n * `>` 直接子選擇器會失效,Lucide icon 會 fallback 到預設 24px。\n *\n * ```tsx\n * import { ICON_SIZE } from \"@/design-system/patterns/element-anatomy/item-anatomy\"\n *\n * const iconPx = ICON_SIZE[size] // size: RowSize\n * <MyLucideIcon size={iconPx} />\n * ```\n */\nexport const ICON_SIZE: Record<RowSize, number> = {\n sm: 16,\n md: 16,\n lg: 20,\n}\n\n/**\n * Avatar 尺寸(px)per row size,分 inline / block 兩種模式。\n *\n * - **inline**(無 description,大宗情境)→ 對齊第一行 label,用 `h-[1lh]` 容器\n * sm: 20, md: 24, lg: 24\n * - **block**(有 description,avatar 跨越 label + desc 兩行)→ 對齊文字塊中心\n * sm: 32, md: 32, lg: 40\n *\n * **用法**:所有 row primitive 的 consumer(包含 `asChild` pattern 的 Sidebar / Tree\n * consumer)**必須**從本 module import 這個常數,**不可硬寫 `size={24}`**。\n * 硬寫會讓 sm 變體的 avatar 尺寸錯誤、跟 ICON_SIZE 對齊規則脫鉤。\n *\n * ```tsx\n * import { AVATAR_SIZE } from \"@/design-system/patterns/element-anatomy/item-anatomy\"\n *\n * const { size } = useSidebar() // RowSize\n * <Avatar size={AVATAR_SIZE.inline[size]} />\n * ```\n *\n * Canonical 實作:`MenuItem` 的內部 `AVATAR_SIZE` 查表(即將 re-export 自此)。\n */\nexport const AVATAR_SIZE = {\n inline: { sm: 20, md: 24, lg: 24 },\n block: { sm: 32, md: 32, lg: 40 },\n} as const satisfies Record<\"inline\" | \"block\", Record<RowSize, number>>\n\n/**\n * 回傳 uniform prefix slot 的 inline style(CSS variable form)。\n *\n * 用法:Row-primitive 頂層容器(例如 `<SidebarProvider uniformPrefix>` 的 wrapper)\n * 把這個 style 套在 `style` prop 上(預先放在 `--mixed-prefix-slot` 候選位置,\n * 由外層 CSS `:has()` 條件化套到 `--item-prefix-slot`),子樹內所有 `<ItemPrefix>` /\n * `<ItemIcon>` / `<ItemAvatar>` 會自動用固定寬槽對齊。\n *\n * 槽寬 = `AVATAR_SIZE.inline[size]`(20/24/24 @ sm/md/lg)——以最大的 inline\n * prefix(avatar)為基準,icon 在槽內 `justify-center`。\n *\n * ```tsx\n * <ul style={getUniformPrefixSlotStyle(size)}>\n * <ItemIcon icon={Folder} /> ← 16px icon 在 24px 槽內置中\n * <ItemAvatar alt=\"GitHub\" /> ← 24px logo 填滿槽\n * </ul>\n * ```\n */\nexport function getUniformPrefixSlotStyle(size: RowSize): React.CSSProperties {\n return { \"--item-prefix-slot\": `${AVATAR_SIZE.inline[size]}px` } as React.CSSProperties\n}\n\n/**\n * Inline action(suffix 裡的 hover-action icon button)hover 背景尺寸(px)。\n * 規則:icon size + 2px(每邊 +1px,用 absolute positioning 溢出不影響排版)。\n * 對齊 `item-anatomy.spec.md`「Inline Action 設計規格」節。\n */\n// code-quality-allow: dead-export — public constant — DS API surface,consumer 可引(即使當前 internal-only)\nexport const INLINE_ACTION_HOVER_BG_SIZE: Record<RowSize, number> = {\n sm: 18,\n md: 18,\n lg: 22,\n}\n\n/**\n * `ROW_PADDING_BY_SIZE` — Family 1+2 row primitive 的 size → padding + typography 公式 SSOT\n *\n * Formula(canonical):`py = (field-height - 1lh) / 2`,對齊 1em text 到 field-height center。\n *\n * **消費者**:menuItemVariants / sidebarMenuButtonVariants / treeItemVariants / 未來 row primitive\n * **Rationale**:前先 3 cva 各自寫同一公式 → drift risk(item-anatomy.spec 有明文「SidebarMenuButton\n * 獨立實作風險」)。抽本 export 後改**一處全同步**,risk 消失(對齊 Meta-Pattern M17「SSOT 必可傳播」)。\n *\n * 2026-04-24 D Phase 1+2 consolidation 建立。\n */\nexport const ROW_PADDING_BY_SIZE: Record<RowSize, string> = {\n sm: 'text-body leading-compact py-[calc((var(--field-height-sm)-1lh)/2)]',\n md: 'text-body leading-compact py-[calc((var(--field-height-md)-1lh)/2)]',\n lg: 'text-body-lg leading-compact py-[calc((var(--field-height-lg)-1lh)/2)]',\n}\n\n// ── Helper components ──────────────────────────────────────────────────────\n\n/**\n * `<ItemPrefix>` — Row primitive 的 prefix slot wrapper。\n *\n * 把 prefix(icon / avatar / checkbox / indicator)包進 `h-[1lh]` 容器,\n * 強制對齊第一行文字中線。這是 item-layout 的底層規則(詳見 spec「Prefix 垂直對齊」)。\n *\n * **為什麼**:\n * - Row primitive 的 outer flex 是 `items-start`(多行 label 時 prefix 不飄移)\n * - 單行時 prefix 透過這個 wrapper 強制對齊第一行中線,視覺效果 = `items-center`\n * - 不同尺寸的 prefix(icon 16、Avatar 24、Checkbox 20)都能穩定置中\n *\n * 用法:\n * ```tsx\n * <div className=\"flex items-start gap-2\">\n * <ItemPrefix>\n * <Avatar size={24} />\n * </ItemPrefix>\n * <span className=\"min-w-0 flex-1 truncate\">{label}</span>\n * </div>\n * ```\n *\n * asChild 的 consumer(例如 `<SidebarMenuButton asChild>`)**必須自己**用這個 wrapper\n * 包 prefix,不能省略。\n */\nexport const ItemPrefix = React.forwardRef<\n HTMLSpanElement,\n React.HTMLAttributes<HTMLSpanElement>\n>(({ className, ...props }, ref) => (\n <span\n ref={ref}\n className={cn(\n \"h-[1lh] shrink-0 flex items-center\",\n // Uniform prefix slot:讀取 `--item-prefix-slot` CSS variable,\n // fallback `auto`(= 預設行為,不佔額外寬度、不 center)。\n // Row-primitive 頂層容器(例如 `<SidebarProvider uniformPrefix>` 的 wrapper)\n // 若設定這個 var,小尺寸 prefix(icon 16px)會在固定寬槽(24px @ md)內置中,\n // 與大尺寸 prefix(app logo 24px)對齊,達成 label 齊左。\n // 詳見 item-anatomy.spec.md「Uniform prefix slot」節。\n \"min-w-[var(--item-prefix-slot,auto)] justify-center\",\n className\n )}\n {...props}\n />\n))\nItemPrefix.displayName = \"ItemPrefix\"\n\n/**\n * `itemPrefixAlignVariants` — Prefix 對齊高度的 cva SSOT(for 複雜 prefix 結構)。\n *\n * ── 存在的理由 ──\n * `<ItemPrefix>` primitive 預設單一 child(icon / avatar 任一)+ inline 對齊。\n * 但**複雜 prefix 結構**(e.g. MenuItem 可同時含 checkbox + icon/avatar 多 child)\n * 或**block mode**(大 prefix + description 對齊塊中心)需要自建 wrapper cva。\n * 本 cva 把 block formula SSOT 化,讓 MenuItem 等 consumer 共用,\n * 改值一處(token or cva)→ 所有 block-prefix consumer 同步。\n *\n * ── Align 變體(對齊 item-anatomy.spec.md「24px 閾值對齊規則」) ──\n * - `\"inline\"`:`h-[1lh]`,小 prefix(≤ 24px)對齊 label 第一行\n * - `\"block-sm\"` / `\"block-md\"`:大 prefix + scanning desc(sm/md menu),\n * 高度 = label(1lh) + gap(token) + desc(caption × 1.3)\n * - `\"block-lg\"`:大 prefix + scanning desc(lg menu),desc 改為 body × 1.3\n *\n * ── Consumer ──\n * MenuItem(inline / block-sm / block-md / block-lg)。其他 row primitive 簡單情境\n * 直接用 `<ItemPrefix>` 就好,不需此 cva。\n *\n * ── SSOT 傳播 ──\n * gap 用 `var(--item-gap-label-desc)` token;font-size 用 token-awareness\n * (`var(--font-caption-size)` / `var(--font-body-size)`)。改 token → 公式同步。\n */\n// code-quality-allow: long-function — cva variant/styles table — 拆 fn 會失去 type inference + 跨 fn 傳 config 反而難讀\nexport const itemPrefixAlignVariants = cva(\n \"flex items-center gap-2 shrink-0\",\n {\n variants: {\n align: {\n inline: \"h-[1lh]\",\n // sm/md desc = text-caption (12px × 1.3 = 15.6px)\n // block-sm / block-md:scanning mode(body + caption 組合)→ 用 scanning mode 專屬 gap token\n \"block-sm\":\n \"h-[calc(1lh+var(--item-gap-label-desc-scanning)+var(--font-caption-size)*1.3)]\",\n \"block-md\":\n \"h-[calc(1lh+var(--item-gap-label-desc-scanning)+var(--font-caption-size)*1.3)]\",\n // block-lg:scanning-lg mode(body-lg + body compact 組合)→ 用 scanning-lg mode 專屬 gap token\n \"block-lg\":\n \"h-[calc(1lh+var(--item-gap-label-desc-scanning-lg)+var(--font-body-size)*1.3)]\",\n },\n },\n defaultVariants: {\n align: \"inline\",\n },\n }\n)\n\n/**\n * `<ItemLabel>` — Row primitive 的 label span。\n *\n * 預設 `min-w-0 flex-1 truncate`——單行截斷,佔滿剩餘 flex 空間。\n * 加 `data-sidebar=\"menu-label\"` attribute 讓 sidebar icon 模式的 CSS selector 能命中。\n *\n * 用法:\n * ```tsx\n * <ItemLabel>{label}</ItemLabel>\n * ```\n */\nexport const ItemLabel = React.forwardRef<\n HTMLSpanElement,\n React.HTMLAttributes<HTMLSpanElement>\n>(({ className, ...props }, ref) => (\n <span\n ref={ref}\n data-sidebar=\"menu-label\"\n className={cn(\"min-w-0 flex-1 truncate\", className)}\n {...props}\n />\n))\nItemLabel.displayName = \"ItemLabel\"\n\n/**\n * `<ItemContent>` — Row primitive 的 label + optional description 內容區(SSOT)。\n *\n * ── 存在的唯一理由 ──\n * 封裝「flex-col + label + description + `mt-[var(--item-gap-label-desc)]` gap」結構,\n * 避免 13+ 消費者各自 hard-code `mt-0.5`。改 token 一處,全 DS 同步。\n *\n * ── Consumer 偏離 canonical ──\n * 消費端若有**確切合理理由**不用 `<ItemContent>` / 需自訂 label/desc 行為,必在\n * 該元件 `spec.md` 明文寫下 rationale(對齊 item-anatomy canonical「偏離必明文」)。\n * 合法偏離範例:\n * - MenuItem 的 `leading-compact + text-caption` scanning-mode typography\n * → MenuItem spec 明文 rationale + 消費 `--item-gap-label-desc` token 直接用\n * - SelectionItem 的 control slot 跟 block formula 綁定\n * → SelectionItem spec 明文 rationale\n *\n * ── Props ──\n * - `label`:label 內容(ReactNode,必填)\n * - `description`:description 內容(ReactNode,optional)\n * - `mode`:typography tier(scanning vs reading,世界級對齊 Material `dense` / Carbon `size` / Ant `size`)\n * - `\"reading\"`(預設):繼承 parent `text-body`(14px / 1.5 leading),舒適閱讀(Family 2 / Material body-large 派)\n * - `\"scanning\"`:desc 縮為 `text-caption`(12px)+ `leading-compact`(1.3),緊湊掃視\n * (Family 1 Menu item / Material body-medium dense 派)\n * - `descriptionTone`:desc 顏色語意\n * - `\"secondary\"`(預設):`text-fg-secondary`\n * - `\"error\"`:`text-error-text`\n * - `\"muted\"`:`text-fg-muted`\n * - `descriptionWrap`:desc 多行 wrap(預設 true)/ false = truncate\n * - `labelClassName` / `descriptionClassName`:escape hatches(明文 rationale 才用)\n *\n * ── World-class benchmark ──\n * 6 家 DS(Material / Polaris / Atlassian / Apple HIG / Carbon / Ant)一致用\n * 14/20 vs 16/24 兩擋 body 表達 scanning vs reading。API 兩派:(A)密度 prop\n * Material `dense` / Carbon `size` / Ant `size`(本 DS 採 A 派);(B)typography\n * token 手選 Polaris / Atlassian / Apple。\n */\n\nexport interface ItemContentProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"title\"> {\n label: React.ReactNode\n description?: React.ReactNode\n /**\n * Typography mode — **leading 行為**(跟 size 正交的另一維):\n * - `\"reading\"`(預設):default leading(1.5)— Family 2 List item 舒適閱讀\n * - `\"scanning\"`:`leading-compact`(1.3)+ desc 降一 tier —— Menu item / Step 掃視\n */\n mode?: \"reading\" | \"scanning\"\n /**\n * Size tier — **label 字級**(跟 mode 正交的另一維):\n * - `\"md\"`(預設):label `text-body`(14)\n * - `\"lg\"`:label `text-body-lg`(16)— desc 永遠比 label 小一 tier(body)\n *\n * 2 × 2 = 4 typography 組合,對應 4 tokens:\n * - md + reading → body(14/1.5) + body(14/1.5)\n * - md + scanning → body(14/1.3) + caption(12/1.3)\n * - lg + reading → body-lg(16/1.5) + body(14/1.5)\n * - lg + scanning → body-lg(16/1.3) + body(14/1.3)\n */\n size?: \"md\" | \"lg\"\n descriptionTone?: \"secondary\" | \"error\" | \"muted\" | \"disabled\"\n descriptionWrap?: boolean\n /**\n * Description 多行截斷(line-clamp-N)。undefined = 不 clamp(自由 wrap)。\n * Tailwind line-clamp utilities 支援 1-6。\n */\n descriptionClamp?: number\n /**\n * Description `break-words`(default `false`,僅在 consumer 需要時 opt-in)。\n * 使用情境:SelectionItem / Steps 這類 description 可能塞長英文 token 需 break。\n */\n descriptionBreakWords?: boolean\n /**\n * Label truncate (default `true`, 對齊 row-item 大宗 idiom)。\n * Opt-out(`false`)for card-like consumers(e.g. NameCard label 允許 wrap)。\n * World-class 對照:Material `ListItemText primaryTypographyProps.noWrap` default true。\n */\n labelTruncate?: boolean\n labelClassName?: string\n descriptionClassName?: string\n}\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\nexport const ItemContent = React.forwardRef<HTMLDivElement, ItemContentProps>(\n (\n {\n label,\n description,\n mode = \"reading\",\n size = \"md\",\n descriptionTone = \"secondary\",\n descriptionWrap = true,\n descriptionClamp,\n descriptionBreakWords = false,\n labelTruncate = true,\n labelClassName,\n descriptionClassName,\n className,\n ...props\n },\n ref,\n ) => {\n const toneClass = {\n secondary: \"text-fg-secondary\",\n error: \"text-error-text\",\n muted: \"text-fg-muted\",\n disabled: \"text-fg-disabled\",\n }[descriptionTone]\n\n // Typography desc class — 2 維 cross-product (mode × size):\n // ┌───────────┬────────────────────────────┬────────────────────────────┐\n // │ │ size=\"md\"(body label 14) │ size=\"lg\"(body-lg label 16)│\n // ├───────────┼────────────────────────────┼────────────────────────────┤\n // │ reading │ 繼承 body(14/1.5) │ body(14)default leading │\n // │ scanning │ caption(12)+ compact │ body(14)+ compact │\n // └───────────┴────────────────────────────┴────────────────────────────┘\n const isLg = size === \"lg\"\n const modeClass =\n mode === \"scanning\"\n ? isLg\n ? \"text-[length:var(--font-body-size)] leading-compact\"\n : \"text-[length:var(--font-caption-size)] leading-compact\"\n : isLg\n ? \"text-[length:var(--font-body-size)]\"\n : \"\"\n\n const clampClass = descriptionClamp ? `line-clamp-${descriptionClamp}` : \"\"\n\n return (\n <div\n ref={ref}\n className={cn(\"flex flex-col min-w-0 flex-1\", className)}\n {...props}\n >\n <span className={cn(labelTruncate && \"truncate\", labelClassName)}>{label}</span>\n {description && (\n <span\n className={cn(\n // Typography-mode-aware gap token(2026-04-23):\n // (mode, size)2 維正交 → 4 token 對應 4 typography 組合\n mode === \"scanning\"\n ? isLg\n ? \"mt-[var(--item-gap-label-desc-scanning-lg)]\"\n : \"mt-[var(--item-gap-label-desc-scanning)]\"\n : isLg\n ? \"mt-[var(--item-gap-label-desc-reading-lg)]\"\n : \"mt-[var(--item-gap-label-desc-reading)]\",\n modeClass,\n toneClass,\n clampClass,\n descriptionBreakWords && \"break-words\",\n !descriptionWrap && \"truncate\",\n descriptionClassName,\n )}\n >\n {description}\n </span>\n )}\n </div>\n )\n },\n)\nItemContent.displayName = \"ItemContent\"\n\n// ── Row size context ──────────────────────────────────────────────────────\n//\n// 作用:讓 row primitive(Sidebar / SelectMenu / Tree / DropdownMenu)只 propagate\n// 一次 size,整個子樹內的 <ItemIcon> / <ItemAvatar> 都自動查到正確尺寸。\n//\n// **存在的唯一理由**:消除 asChild pattern 下 consumer 硬寫 `size={24}` 的可能性。\n// 過去 consumer 必須 `useSidebar().size` 再手動傳 `AVATAR_SIZE.inline[size]`,\n// 每個 consumer 都是一次漂移機會(曾發生:sm 欄 avatar 顯示 24 而非 20,破壞規格)。\n// 現在 consumer 只寫 `<ItemAvatar alt=\"...\" />`,尺寸由 context 自動決定。\n//\n// Row primitive 實作者的責任:在元件內部用 `<RowSizeProvider value={size}>` 包裹\n// children(不論 asChild 或非 asChild 路徑),這樣任何 descendant 的 ItemIcon /\n// ItemAvatar 都會拿到正確尺寸。\n\nconst RowSizeContext = React.createContext<RowSize | null>(null)\n\nexport const RowSizeProvider = RowSizeContext.Provider\n\n/**\n * 讀取當前 row size。在 row primitive 子樹外呼叫會回 fallback(預設 \"md\")。\n * Row primitive 實作者不該呼叫這個——你們已經知道自己的 size。\n * 這個 hook 是給 `<ItemIcon>` / `<ItemAvatar>` 和 asChild consumer 的逃生艙用。\n */\nexport function useRowSize(fallback: RowSize = \"md\"): RowSize {\n return React.useContext(RowSizeContext) ?? fallback\n}\n\n// ── Prefab prefix elements ────────────────────────────────────────────────\n//\n// `<ItemIcon>` 和 `<ItemAvatar>` 是「prefix slot 的完成品元件」——自帶 ItemPrefix\n// wrapper + 自動查 ICON_SIZE / AVATAR_SIZE。\n//\n// **asChild consumer 必須用這兩個元件,禁止手動寫 `<Avatar size={N} />`**。\n// 禁令理由見 item-anatomy.spec.md「Avatar 尺寸選擇」節。\n\nexport interface ItemIconProps\n extends Omit<React.SVGAttributes<SVGSVGElement>, \"children\"> {\n /** Lucide icon 元件(不是 JSX element) */\n icon: LucideIcon\n}\n\n/**\n * `<ItemIcon icon={Folder} />` — 自動查 `ICON_SIZE[rowSize]`,包在 `ItemPrefix` 內。\n * 永遠對齊第一行 label(item-layout 的 icon 永遠 inline)。\n */\nexport const ItemIcon = React.forwardRef<SVGSVGElement, ItemIconProps>(\n ({ icon: Icon, className, ...props }, ref) => {\n const size = useRowSize()\n return (\n // data-prefix-type=\"icon\" 讓 row-primitive 容器的 `:has()` selector 能偵測類型,\n // 用於 `<SidebarProvider uniformPrefix>` opt-in 後的混用 auto-detection\n <ItemPrefix data-prefix-type=\"icon\">\n <Icon\n ref={ref}\n size={ICON_SIZE[size]}\n className={cn(\"shrink-0\", className)}\n aria-hidden\n {...props}\n />\n </ItemPrefix>\n )\n }\n)\nItemIcon.displayName = \"ItemIcon\"\n\nexport interface ItemAvatarProps extends Omit<AvatarProps, \"size\"> {\n /**\n * 對齊模式(選填,預設 `\"inline\"`):\n * - `\"inline\"`:視覺重量輕的 avatar,對齊第一行 label(20/24/24 @ sm/md/lg)\n * - `\"block\"`:視覺重量重的 avatar,對齊 label + description 文字塊(32/32/40 @ sm/md/lg)\n *\n * 預設 inline——大宗情境(扁平 row、footer user、主清單)都用這個。\n * 只有 avatar 是 item 主體(人物卡、顯著身份辨識)才用 block。\n *\n * 詳見 `item-anatomy.spec.md`「Avatar 尺寸選擇」節,含 24px 對齊模式閾值規則。\n */\n mode?: \"inline\" | \"block\"\n}\n\n/**\n * `<ItemAvatar alt=\"Alan\" color=\"blue\" />` — 自動查 `AVATAR_SIZE[mode][rowSize]`,\n * 包在 `ItemPrefix` 內。\n *\n * asChild consumer 的標準用法——不需知道 size,不需 import AVATAR_SIZE,\n * 不可能硬寫錯誤尺寸。\n */\n// code-quality-allow: long-function — forwardRef body 含 size×color×hoverCard 三正交 prop 渲染分流,拆出 sub-fn 反增 prop drilling\nexport const ItemAvatar = React.forwardRef<HTMLDivElement, ItemAvatarProps>(\n ({ mode = \"inline\", ...props }, ref) => {\n const size = useRowSize()\n return (\n // data-prefix-type=\"avatar\" 讓 row-primitive 容器的 `:has()` selector 能偵測類型,\n // 用於 `<SidebarProvider uniformPrefix>` opt-in 後的混用 auto-detection\n <ItemPrefix data-prefix-type=\"avatar\">\n <Avatar ref={ref} size={AVATAR_SIZE[mode][size]} {...props} />\n </ItemPrefix>\n )\n }\n)\nItemAvatar.displayName = \"ItemAvatar\"\n\n// ── Inline action (suffix slot) ───────────────────────────────────────────\n//\n// 共用 row-primitive 的 suffix inline action 渲染——消除過去 Input / NumberInput /\n// Tag / LinkInput / Combobox 各自複製 18 行 JSX 的狀況(見 `item-anatomy.spec.md`\n// 「Inline Action 設計規格」節)。\n//\n// 規格(完全對齊 item-anatomy.spec.md「Inline Action 設計規格」節):\n// - Icon 尺寸 = ICON_SIZE[rowSize] (16/16/20)\n// - Hover bg 尺寸 = icon + 2 (18/18/22,INLINE_ACTION_HOVER_BG_SIZE)\n// - Hover bg 圓角 = rounded-md (sm/md)/ rounded-md (lg)\n// - Icon 顏色:fg-muted → foreground on hover/active\n// - 必須 <button type=\"button\">,aria-label = label,Tooltip(同 label)\n// - cursor-pointer\n\n// ── Low-level:ItemInlineActionButton ──\n//\n// Root 是 `<button>`(不是 Tooltip wrapper),可直接塞進 Radix `asChild` pattern\n// 例如 `<Collapsible.Trigger asChild>`, `<Popover.Trigger asChild>` 等。\n//\n// 自動從 `RowSizeContext` 查:\n// - 寬高 = ICON_SIZE[size]\n// - Hover bg 尺寸 = INLINE_ACTION_HOVER_BG_SIZE[size]\n// - Hover bg 圓角 = rounded-md (sm/md) / rounded-md (lg)\n// - Icon color = fg-muted → foreground on hover/active\n//\n// 接受任何 button props 並 spread(讓 Radix Slot 可以 merge onClick / data-state 等)\n\nexport interface ItemInlineActionButtonProps\n extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, \"children\"> {\n /** Lucide icon 元件 */\n icon: LucideIcon\n /** 可選:額外加在 icon 上的 className(例如 rotate transition) */\n iconClassName?: string\n /**\n * Size 明確覆寫(用於 fields / Tag 等**不在 `RowSizeProvider` 樹內**的 host):\n * 傳了就以此為準,否則從 `RowSizeContext` 讀(fallback \"md\")。\n *\n * 使用時機:\n * - Row primitive 子樹(Sidebar / TreeView / SelectMenu / DropdownMenu)→ **不傳**,吃 context\n * - Field / Tag(自有 size 系統,沒包 RowSizeProvider)→ **必傳** `size={hostSize}`\n */\n size?: RowSize\n /**\n * Hover / active bg className override(chromatic host 專用,如 Tag solid variant)。\n *\n * 預設 `group-hover/action:bg-neutral-hover group-active/action:bg-neutral-active`。\n * 提供後**完全取代** hover + active 雙態 className,但**不影響** rest 態(永遠 bg-transparent)\n * 與 overlay-trigger 態(`group-data-[state=open]:bg-neutral-selected`)。\n *\n * Consumer 須**同時**包含 hover + active 兩態 selector,例:\n * `'group-hover/action:bg-[var(--my-hover)] group-active/action:bg-[var(--my-active)]'`\n *\n * Use case:Tag solid(blue/green/red 等)需 hover bg 跟 host 色相一致(非 neutral)。\n */\n hoverBgClassName?: string\n /**\n * 標記本 button 是 **overlay trigger**(DropdownMenu / Popover / Tooltip 透過 `asChild` 包覆)。\n *\n * `true` 時:Radix overlay open 期間(`data-state=\"open\"`)button 維持 host hover 樣式\n * — 視覺鎖,讓 user 追溯 floating panel 來源(對齊 shadcn / Radix Themes / Material)。\n *\n * `false` 時(default):無 data-state=open 樣式 — 適用 in-place 互動如 `Collapsible.Trigger`\n * (展開內容接在 trigger 下方,不需追溯)、drag handle、dismiss X 等。\n *\n * Canonical 詳 `inline-action.spec.md`「Overlay trigger canonical」段。\n *\n * @default false\n */\n overlayTrigger?: boolean\n}\n\nexport const ItemInlineActionButton = React.forwardRef<\n HTMLButtonElement,\n ItemInlineActionButtonProps\n>(({ icon: Icon, className, iconClassName, style, type = \"button\", size: sizeProp, hoverBgClassName, overlayTrigger = false, ...rest }, ref) => {\n const contextSize = useRowSize()\n const size = sizeProp ?? contextSize\n const iconPx = ICON_SIZE[size]\n const hoverBgPx = INLINE_ACTION_HOVER_BG_SIZE[size]\n return (\n <button\n ref={ref}\n type={type}\n className={cn(\n \"group/action relative grid place-content-center shrink-0 cursor-pointer\",\n \"text-fg-muted hover:text-foreground active:text-foreground transition-colors\",\n // Overlay trigger active state — 只在 consumer 顯式宣告 overlayTrigger=true 時生效\n // (canonical 2026-05-05:Collapsible / drag / dismiss 等 in-place 互動 ≠ overlay,\n // 不應 leak 視覺 lock。詳 inline-action.spec.md「Overlay trigger canonical」)\n overlayTrigger && \"data-[state=open]:text-foreground\",\n \"focus-visible:outline-2 focus-visible:outline-ring\",\n className\n )}\n style={{ width: iconPx, height: iconPx, ...style }}\n {...rest}\n >\n <span\n aria-hidden\n className={cn(\n \"absolute pointer-events-none\",\n \"rounded-md\",\n \"bg-transparent\",\n hoverBgClassName ?? \"group-hover/action:bg-neutral-hover group-active/action:bg-neutral-active\",\n // Overlay 開啟 = 維持 host hover bg(只在 overlayTrigger=true 時生效)\n overlayTrigger && \"group-data-[state=open]/action:bg-neutral-hover\",\n \"transition-colors\"\n )}\n style={{\n width: hoverBgPx,\n height: hoverBgPx,\n top: \"50%\",\n left: \"50%\",\n transform: \"translate(-50%, -50%)\",\n }}\n />\n <Icon size={iconPx} className={cn(\"relative\", iconClassName)} aria-hidden />\n </button>\n )\n})\nItemInlineActionButton.displayName = \"ItemInlineActionButton\"\n\n// ── High-level:ItemInlineAction ──\n//\n// 宣告式 API:consumer 給 `{ icon, label, onClick }`,元件自動處理 Tooltip +\n// aria-label。適用於 row primitive 的 suffix slot(SidebarMenuButton inlineActions 等)。\n//\n// 內部用 `ItemInlineActionButton`——兩層共用同一套視覺規格、同一個 RowSizeContext 查表。\n// 需要接 Radix asChild(例如 collapsible trigger)時改用 `ItemInlineActionButton`,\n// 那層沒有 Tooltip 包裝,Root 就是 button,可直接塞進 Radix Slot。\n\nexport interface ItemInlineActionProps {\n action: InlineActionConfig\n /** Size 覆寫(見 `ItemInlineActionButtonProps.size`)——Field Controls / Tag 必傳 */\n size?: RowSize\n}\n\nexport const ItemInlineAction = React.forwardRef<\n HTMLButtonElement,\n ItemInlineActionProps\n>(({ action, size }, ref) => (\n <Tooltip>\n <TooltipTrigger asChild>\n <ItemInlineActionButton\n ref={ref}\n icon={action.icon}\n aria-label={action.label}\n onClick={action.onClick}\n size={size}\n />\n </TooltipTrigger>\n <TooltipContent>{action.label}</TooltipContent>\n </Tooltip>\n))\nItemInlineAction.displayName = \"ItemInlineAction\"\n\n// ── Item suffix slot ──────────────────────────────────────────────────────\n//\n// `<ItemSuffix>` — row primitive 的 suffix 容器。對齊 item-layout spec:\n// - `h-[1lh]`:suffix 永遠對齊第一行 label(跟 prefix 解耦)\n// - `ml-auto`:靠右\n// - `gap-2`:多個 inline action 之間的標準間距(item-anatomy.spec.md「Inline Action 設計規格」節)\n// - `hoverReveal` opt-in:opacity 0→1 on 父層 row hover/focus-visible\n//\n// `hoverGroup`(2026-05-05 v8 SSOT 升級):row primitive group selector 參數化。\n// 先前 `/menu-item` hardcode → TreeView(`/tree-item`)/ FileItem(`/row`)/\n// DataTable(`/row`)無法消費,被迫自刻 suffix slot(M1 違反)。\n// 參數化後 4 個 row primitive 都走同一條 SSOT。Tailwind JIT 需 static class,\n// 故用 lookup table 而非動態字串(避免 JIT scan 失敗)。\n\nconst SUFFIX_HOVER_REVEAL_BY_GROUP = {\n \"menu-item\":\n \"opacity-0 group-hover/menu-item:opacity-100 group-has-[:focus-visible]/menu-item:opacity-100 transition-opacity duration-150\",\n \"tree-item\":\n \"opacity-0 group-hover/tree-item:opacity-100 group-has-[:focus-visible]/tree-item:opacity-100 transition-opacity duration-150\",\n row: \"opacity-0 group-hover/row:opacity-100 group-has-[:focus-visible]/row:opacity-100 transition-opacity duration-150\",\n} as const\n\nexport type ItemSuffixHoverGroup = keyof typeof SUFFIX_HOVER_REVEAL_BY_GROUP\n\nexport interface ItemSuffixProps extends React.HTMLAttributes<HTMLSpanElement> {\n /**\n * Hover-reveal:預設隱藏,父層 row hover / keyboard focus-visible 時才淡入。\n * 對齊 TreeView / SidebarMenuButton inline action 行為。預設 false(永遠顯示,如 Badge)。\n *\n * 用 `group-has-[:focus-visible]` 而非 `group-focus-within`——後者會被 mouse click 觸發,\n * 導致 click 後 suffix 永久顯示直到焦點移走。focus-visible 只在鍵盤 tab 時啟動。\n */\n hoverReveal?: boolean\n /**\n * 父層 row 的 group selector(consumer 必加 `group/<name>` 到 row wrapper)。\n * 預設 `'menu-item'`(對齊 MenuItem / Sidebar)。其他 row primitive:\n * `'tree-item'`(TreeView)/ `'row'`(DataTable / FileItem)。\n *\n * 加新 row primitive(且需 hover-reveal)時:在 `SUFFIX_HOVER_REVEAL_BY_GROUP`\n * 加 entry,Tailwind JIT 才能 scan 到 static class。\n */\n hoverGroup?: ItemSuffixHoverGroup\n}\n\nexport const ItemSuffix = React.forwardRef<HTMLSpanElement, ItemSuffixProps>(\n ({ className, hoverReveal = false, hoverGroup = \"menu-item\", ...props }, ref) => (\n <span\n ref={ref}\n className={cn(\n \"h-[1lh] shrink-0 ml-auto flex items-center gap-2\",\n hoverReveal && SUFFIX_HOVER_REVEAL_BY_GROUP[hoverGroup],\n className\n )}\n {...props}\n />\n )\n)\nItemSuffix.displayName = \"ItemSuffix\"\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* @internal — DS-internal primitive(2026-05-23 per `.claude/rules/ui-development.md` Public vs Internal canonical)。
|
|
4
|
+
* end-user app 直接 render `<ChromeHeader />` 無 functioning UI;由 Sidebar / FileViewer / Dialog / Sheet / Popover 等 DS 元件 wrap 消費。
|
|
5
|
+
*
|
|
6
|
+
* ChromeHeader — Fixed-height chrome header primitive(Layout Family B,header-canonical.spec.md)
|
|
7
|
+
*
|
|
8
|
+
* ── 定位 ──
|
|
9
|
+
* Page chrome 級 header 共用 primitive。封裝重複 contract:
|
|
10
|
+
* flex items-center gap-2 shrink-0 h-[var(--chrome-header-height)] border-b border-divider px-[var(--layout-space-loose)]
|
|
11
|
+
*
|
|
12
|
+
* Consumers:Sidebar / FileViewer Toolbar / FileViewer InfoPanel / 未來 page top bar / Drawer
|
|
13
|
+
* 跟 SurfaceHeader(overlay padding-based)是兩個並存家族(per header-canonical.spec.md L23-30)。
|
|
14
|
+
*
|
|
15
|
+
* ── 實作基礎 ──
|
|
16
|
+
* 消費:--chrome-header-height(48/56 density-responsive)/ --layout-space-loose / border-divider
|
|
17
|
+
* 對應 pattern:patterns/header-canonical
|
|
18
|
+
*
|
|
19
|
+
* ── 消費的 SSOT ──
|
|
20
|
+
* - tokens: [--chrome-header-height, --layout-space-loose, --divider]
|
|
21
|
+
* - patterns: [header-canonical(本 pattern), overlay-surface(姊妹 SurfaceHeader)]
|
|
22
|
+
* - spec refs: patterns/header-canonical/header-canonical.spec.md(L23-30 家族區分 / W1 border / W3 tabs size / Layer 3 ChromeHeader API)
|
|
23
|
+
*
|
|
24
|
+
* ── API(per M31 codex 比稿 Step 5 — narrow API,避免 M21 prop variant 風險)──
|
|
25
|
+
* withTabs?: boolean(預設 false)
|
|
26
|
+
* true → 移除自身 border-b,delegate paint 給 TabsList(per W1「Header semantic owner / TabsList paint owner in withTabs state」)
|
|
27
|
+
* lockDensity?: 'inherit' | 'lg'(預設 'inherit')
|
|
28
|
+
* 'inherit' → 跟 page density(html[data-density] 自動)
|
|
29
|
+
* 'lg' → 強制 lg(viewer-fullscreen-chrome escape hatch,FileViewer 永遠 lg-equivalent design intent)
|
|
30
|
+
*
|
|
31
|
+
* 不開:density?: 'md' | 'lg' 自由 prop。M21 違反 — 任意 density 等於 cva-on-pattern。
|
|
32
|
+
*/
|
|
33
|
+
export interface ChromeHeaderProps extends React.HTMLAttributes<HTMLElement> {
|
|
34
|
+
/**
|
|
35
|
+
* 是否內含 Tabs。
|
|
36
|
+
* true(無 tabsSlot)→ 移除自身 border-b,consumer 自畫。
|
|
37
|
+
* 若提供 tabsSlot,自動 column mode + auto suppress border。
|
|
38
|
+
* 對應 patterns/header-canonical/header-canonical.spec.md W1
|
|
39
|
+
*/
|
|
40
|
+
withTabs?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Tabs row slot(2026-05-18 加 per W2/W4 真實能用 + user-mandated fix)。
|
|
43
|
+
* 提供時 ChromeHeader 自動 column 結構:
|
|
44
|
+
* row 1 = children(h-chrome-header-height 固定,px-loose,跟 single-row 模式同)
|
|
45
|
+
* row 2 = tabsSlot 包在 `<div px-loose border-b border-divider>`
|
|
46
|
+
* ↑ wrapper 提供 W2 padding inheritance + W1 全寬 paint(一條線)
|
|
47
|
+
*
|
|
48
|
+
* Consumer 傳:`tabsSlot={<TabsList>...</TabsList>}`,TabsContent 放 ChromeHeader 之外。
|
|
49
|
+
* Standalone Tabs(無 chrome header)該直接用 `<TabsList>` 不需 wrapper。
|
|
50
|
+
*
|
|
51
|
+
* 提供 tabsSlot 自動 withTabs=true,不需另傳 prop。
|
|
52
|
+
*/
|
|
53
|
+
tabsSlot?: React.ReactNode;
|
|
54
|
+
/**
|
|
55
|
+
* 是否鎖死 lg density(viewer-fullscreen chrome 用)。
|
|
56
|
+
* 'inherit'(預設)→ 跟 page density
|
|
57
|
+
* 'lg' → 強制 chrome-header-height lg(56)
|
|
58
|
+
* 對應 patterns/header-canonical/header-canonical.spec.md Layer 3 API
|
|
59
|
+
*/
|
|
60
|
+
lockDensity?: 'inherit' | 'lg';
|
|
61
|
+
/**
|
|
62
|
+
* Leading rail slot(2026-05-21 ship per AppShell primary-header globalHeader 用例,
|
|
63
|
+
* codex M31 Layer C 建議 codify into ChromeHeader API):
|
|
64
|
+
* 固定寬度 = `var(--sidebar-width-icon)` 的左邊容器,內 `justify-center` 排列。
|
|
65
|
+
*
|
|
66
|
+
* 用途:primary-header globalHeader 左側 SidebarTrigger,跟 sidebar 收合 icon 完美水平對齊
|
|
67
|
+
* (sidebar 收合 width = sidebar-width-icon,toggle container 同寬 = toggle center x = sidebar
|
|
68
|
+
* collapsed icon center x = perfect alignment)。
|
|
69
|
+
*
|
|
70
|
+
* 對齊 GitHub global nav 左側固定寬度 logo 區 / Slack thin workspace rail 慣例。
|
|
71
|
+
* 提供時自動結構:
|
|
72
|
+
* row1: [leadingRail (width=sidebar-width-icon)] [children (px-loose flex-1)]
|
|
73
|
+
* 不提供時 fallback 預設 single-row(全 px-loose)。
|
|
74
|
+
*
|
|
75
|
+
* 與 tabsSlot 互斥(tabsSlot 啟動 column mode,本 prop 不生效)。
|
|
76
|
+
*/
|
|
77
|
+
leadingRail?: React.ReactNode;
|
|
78
|
+
}
|
|
79
|
+
export declare const ChromeHeader: React.ForwardRefExoticComponent<ChromeHeaderProps & React.RefAttributes<HTMLElement>>;
|
|
80
|
+
//# sourceMappingURL=chrome-header.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chrome-header.d.ts","sourceRoot":"","sources":["../../../src/patterns/header-canonical/chrome-header.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAG9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,WAAW,iBACf,SAAQ,KAAK,CAAC,cAAc,CAAC,WAAW,CAAC;IACzC;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC1B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,SAAS,GAAG,IAAI,CAAA;IAC9B;;;;;;;;;;;;;;;OAeG;IACH,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC9B;AAED,eAAO,MAAM,YAAY,uFA2FxB,CAAA"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { cn } from "../../lib/utils.js";
|
|
4
|
+
const ChromeHeader = React.forwardRef(
|
|
5
|
+
({ className, withTabs, tabsSlot, lockDensity = "inherit", leadingRail, children, ...props }, ref) => {
|
|
6
|
+
const hasTabs = tabsSlot != null || withTabs === true;
|
|
7
|
+
if (tabsSlot != null) {
|
|
8
|
+
return /* @__PURE__ */ jsxs(
|
|
9
|
+
"header",
|
|
10
|
+
{
|
|
11
|
+
ref,
|
|
12
|
+
"data-density": lockDensity === "lg" ? "lg" : void 0,
|
|
13
|
+
className: cn("flex flex-col shrink-0", className),
|
|
14
|
+
...props,
|
|
15
|
+
children: [
|
|
16
|
+
/* @__PURE__ */ jsx(
|
|
17
|
+
"div",
|
|
18
|
+
{
|
|
19
|
+
className: cn(
|
|
20
|
+
"flex items-center gap-2",
|
|
21
|
+
"h-[var(--chrome-header-height)]",
|
|
22
|
+
"px-[var(--layout-space-loose)]"
|
|
23
|
+
),
|
|
24
|
+
children
|
|
25
|
+
}
|
|
26
|
+
),
|
|
27
|
+
/* @__PURE__ */ jsx("div", { className: "[&>[role=tablist]]:w-full [&>[role=tablist]]:px-[var(--layout-space-loose)]", children: tabsSlot })
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
if (leadingRail != null) {
|
|
33
|
+
return /* @__PURE__ */ jsxs(
|
|
34
|
+
"header",
|
|
35
|
+
{
|
|
36
|
+
ref,
|
|
37
|
+
"data-density": lockDensity === "lg" ? "lg" : void 0,
|
|
38
|
+
className: cn(
|
|
39
|
+
"flex items-center shrink-0",
|
|
40
|
+
"h-[var(--chrome-header-height)]",
|
|
41
|
+
!hasTabs && "border-b border-divider",
|
|
42
|
+
className
|
|
43
|
+
),
|
|
44
|
+
...props,
|
|
45
|
+
children: [
|
|
46
|
+
/* @__PURE__ */ jsx("div", { className: "flex w-[var(--sidebar-width-icon)] shrink-0 items-center justify-center border-r border-divider", children: leadingRail }),
|
|
47
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-1 items-center gap-2 min-w-0 px-[var(--layout-space-loose)]", children })
|
|
48
|
+
]
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
return /* @__PURE__ */ jsx(
|
|
53
|
+
"header",
|
|
54
|
+
{
|
|
55
|
+
ref,
|
|
56
|
+
"data-density": lockDensity === "lg" ? "lg" : void 0,
|
|
57
|
+
className: cn(
|
|
58
|
+
"flex items-center gap-2 shrink-0",
|
|
59
|
+
"h-[var(--chrome-header-height)]",
|
|
60
|
+
"px-[var(--layout-space-loose)]",
|
|
61
|
+
// W1:無 tabs 自畫 border;withTabs=true(無 tabsSlot)consumer 自畫
|
|
62
|
+
!hasTabs && "border-b border-divider",
|
|
63
|
+
className
|
|
64
|
+
),
|
|
65
|
+
...props,
|
|
66
|
+
children
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
);
|
|
71
|
+
ChromeHeader.displayName = "ChromeHeader";
|
|
72
|
+
export {
|
|
73
|
+
ChromeHeader
|
|
74
|
+
};
|
|
75
|
+
//# sourceMappingURL=chrome-header.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chrome-header.js","sources":["../../../src/patterns/header-canonical/chrome-header.tsx"],"sourcesContent":["import * as React from 'react'\nimport { cn } from '@/lib/utils'\n\n/**\n * @internal — DS-internal primitive(2026-05-23 per `.claude/rules/ui-development.md` Public vs Internal canonical)。\n * end-user app 直接 render `<ChromeHeader />` 無 functioning UI;由 Sidebar / FileViewer / Dialog / Sheet / Popover 等 DS 元件 wrap 消費。\n *\n * ChromeHeader — Fixed-height chrome header primitive(Layout Family B,header-canonical.spec.md)\n *\n * ── 定位 ──\n * Page chrome 級 header 共用 primitive。封裝重複 contract:\n * flex items-center gap-2 shrink-0 h-[var(--chrome-header-height)] border-b border-divider px-[var(--layout-space-loose)]\n *\n * Consumers:Sidebar / FileViewer Toolbar / FileViewer InfoPanel / 未來 page top bar / Drawer\n * 跟 SurfaceHeader(overlay padding-based)是兩個並存家族(per header-canonical.spec.md L23-30)。\n *\n * ── 實作基礎 ──\n * 消費:--chrome-header-height(48/56 density-responsive)/ --layout-space-loose / border-divider\n * 對應 pattern:patterns/header-canonical\n *\n * ── 消費的 SSOT ──\n * - tokens: [--chrome-header-height, --layout-space-loose, --divider]\n * - patterns: [header-canonical(本 pattern), overlay-surface(姊妹 SurfaceHeader)]\n * - spec refs: patterns/header-canonical/header-canonical.spec.md(L23-30 家族區分 / W1 border / W3 tabs size / Layer 3 ChromeHeader API)\n *\n * ── API(per M31 codex 比稿 Step 5 — narrow API,避免 M21 prop variant 風險)──\n * withTabs?: boolean(預設 false)\n * true → 移除自身 border-b,delegate paint 給 TabsList(per W1「Header semantic owner / TabsList paint owner in withTabs state」)\n * lockDensity?: 'inherit' | 'lg'(預設 'inherit')\n * 'inherit' → 跟 page density(html[data-density] 自動)\n * 'lg' → 強制 lg(viewer-fullscreen-chrome escape hatch,FileViewer 永遠 lg-equivalent design intent)\n *\n * 不開:density?: 'md' | 'lg' 自由 prop。M21 違反 — 任意 density 等於 cva-on-pattern。\n */\nexport interface ChromeHeaderProps\n extends React.HTMLAttributes<HTMLElement> {\n /**\n * 是否內含 Tabs。\n * true(無 tabsSlot)→ 移除自身 border-b,consumer 自畫。\n * 若提供 tabsSlot,自動 column mode + auto suppress border。\n * 對應 patterns/header-canonical/header-canonical.spec.md W1\n */\n withTabs?: boolean\n /**\n * Tabs row slot(2026-05-18 加 per W2/W4 真實能用 + user-mandated fix)。\n * 提供時 ChromeHeader 自動 column 結構:\n * row 1 = children(h-chrome-header-height 固定,px-loose,跟 single-row 模式同)\n * row 2 = tabsSlot 包在 `<div px-loose border-b border-divider>`\n * ↑ wrapper 提供 W2 padding inheritance + W1 全寬 paint(一條線)\n *\n * Consumer 傳:`tabsSlot={<TabsList>...</TabsList>}`,TabsContent 放 ChromeHeader 之外。\n * Standalone Tabs(無 chrome header)該直接用 `<TabsList>` 不需 wrapper。\n *\n * 提供 tabsSlot 自動 withTabs=true,不需另傳 prop。\n */\n tabsSlot?: React.ReactNode\n /**\n * 是否鎖死 lg density(viewer-fullscreen chrome 用)。\n * 'inherit'(預設)→ 跟 page density\n * 'lg' → 強制 chrome-header-height lg(56)\n * 對應 patterns/header-canonical/header-canonical.spec.md Layer 3 API\n */\n lockDensity?: 'inherit' | 'lg'\n /**\n * Leading rail slot(2026-05-21 ship per AppShell primary-header globalHeader 用例,\n * codex M31 Layer C 建議 codify into ChromeHeader API):\n * 固定寬度 = `var(--sidebar-width-icon)` 的左邊容器,內 `justify-center` 排列。\n *\n * 用途:primary-header globalHeader 左側 SidebarTrigger,跟 sidebar 收合 icon 完美水平對齊\n * (sidebar 收合 width = sidebar-width-icon,toggle container 同寬 = toggle center x = sidebar\n * collapsed icon center x = perfect alignment)。\n *\n * 對齊 GitHub global nav 左側固定寬度 logo 區 / Slack thin workspace rail 慣例。\n * 提供時自動結構:\n * row1: [leadingRail (width=sidebar-width-icon)] [children (px-loose flex-1)]\n * 不提供時 fallback 預設 single-row(全 px-loose)。\n *\n * 與 tabsSlot 互斥(tabsSlot 啟動 column mode,本 prop 不生效)。\n */\n leadingRail?: React.ReactNode\n}\n\nexport const ChromeHeader = React.forwardRef<HTMLElement, ChromeHeaderProps>(\n (\n { className, withTabs, tabsSlot, lockDensity = 'inherit', leadingRail, children, ...props },\n ref,\n ) => {\n const hasTabs = tabsSlot != null || withTabs === true\n\n // Column mode(tabsSlot 提供時)— per W2 + W4\n // 2026-05-20:`<div>` → `<header>`(HTML5 sectional content 允許 multiple `<header>`,\n // page-level / sectional 都 a11y safe;統一 element contract 消除「何時用 header / 何時用 div」\n // 的 consumer drift)。 對應 header-canonical.spec.md 「Element canonical」段。\n if (tabsSlot != null) {\n return (\n <header\n ref={ref}\n data-density={lockDensity === 'lg' ? 'lg' : undefined}\n className={cn('flex flex-col shrink-0', className)}\n {...props}\n >\n {/* Row 1:header content(固定高度,跟 single-row 模式同 visual)*/}\n <div\n className={cn(\n 'flex items-center gap-2',\n 'h-[var(--chrome-header-height)]',\n 'px-[var(--layout-space-loose)]',\n )}\n >\n {children}\n </div>\n {/* Row 2:tabsSlot wrapper — TabsList 全 dialog 寬 + 內 px-loose inset triggers\n 2026-05-18 v3 fix(同 SurfaceHeader,per user verbatim「分隔線寬度應該要填滿整個\n dialog」+「就這樣做」approval):TabsList 自己 px-loose 內 padding 而非 wrapper\n 提供,讓 TabsList border-b 延展全 dialog 寬。對齊 `tabs.spec.md:199` 既有 canonical。*/}\n <div className=\"[&>[role=tablist]]:w-full [&>[role=tablist]]:px-[var(--layout-space-loose)]\">\n {tabsSlot}\n </div>\n </header>\n )\n }\n\n // Leading rail mode(2026-05-21 ship per AppShell primary-header globalHeader):\n // [leadingRail (width=sidebar-width-icon, justify-center)] [children (flex-1 px-loose)]\n // 整 header 仍 fixed-height chrome-header-height + border-b。Rail 容器無 padding(內元素\n // 透過 sidebar-width-icon 幾何自然居中,跟 sidebar 收合 icon center x 對齊)。\n if (leadingRail != null) {\n return (\n <header\n ref={ref}\n data-density={lockDensity === 'lg' ? 'lg' : undefined}\n className={cn(\n 'flex items-center shrink-0',\n 'h-[var(--chrome-header-height)]',\n !hasTabs && 'border-b border-divider',\n className,\n )}\n {...props}\n >\n {/* Rail:固定寬度 = sidebar-width-icon,內容 justify-center 居中(toggle center x\n = rail 寬度的中點 = sidebar 收合 icon center x = 完美 vertical 對齊)\n 2026-05-21 v2 user 抓:rail 右側加 `border-r border-divider`,sidebar 收合到\n icon mode 時 rail 右 border = sidebar 右 border 連成一線(visual continuity)。 */}\n <div className=\"flex w-[var(--sidebar-width-icon)] shrink-0 items-center justify-center border-r border-divider\">\n {leadingRail}\n </div>\n {/* Main:flex-1 + px-loose(沿用 header 既有左右 padding canonical)+ gap-2 */}\n <div className=\"flex flex-1 items-center gap-2 min-w-0 px-[var(--layout-space-loose)]\">\n {children}\n </div>\n </header>\n )\n }\n\n // Single-row(預設 + withTabs=true 但無 tabsSlot 的 backward compat)\n return (\n <header\n ref={ref}\n data-density={lockDensity === 'lg' ? 'lg' : undefined}\n className={cn(\n 'flex items-center gap-2 shrink-0',\n 'h-[var(--chrome-header-height)]',\n 'px-[var(--layout-space-loose)]',\n // W1:無 tabs 自畫 border;withTabs=true(無 tabsSlot)consumer 自畫\n !hasTabs && 'border-b border-divider',\n className,\n )}\n {...props}\n >\n {children}\n </header>\n )\n },\n)\nChromeHeader.displayName = 'ChromeHeader'\n"],"names":[],"mappings":";;;AAkFO,MAAM,eAAe,MAAM;AAAA,EAChC,CACE,EAAE,WAAW,UAAU,UAAU,cAAc,WAAW,aAAa,UAAU,GAAG,MAAA,GACpF,QACG;AACH,UAAM,UAAU,YAAY,QAAQ,aAAa;AAMjD,QAAI,YAAY,MAAM;AACpB,aACE;AAAA,QAAC;AAAA,QAAA;AAAA,UACC;AAAA,UACA,gBAAc,gBAAgB,OAAO,OAAO;AAAA,UAC5C,WAAW,GAAG,0BAA0B,SAAS;AAAA,UAChD,GAAG;AAAA,UAGJ,UAAA;AAAA,YAAA;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,WAAW;AAAA,kBACT;AAAA,kBACA;AAAA,kBACA;AAAA,gBAAA;AAAA,gBAGD;AAAA,cAAA;AAAA,YAAA;AAAA,YAMH,oBAAC,OAAA,EAAI,WAAU,+EACZ,UAAA,SAAA,CACH;AAAA,UAAA;AAAA,QAAA;AAAA,MAAA;AAAA,IAGN;AAMA,QAAI,eAAe,MAAM;AACvB,aACE;AAAA,QAAC;AAAA,QAAA;AAAA,UACC;AAAA,UACA,gBAAc,gBAAgB,OAAO,OAAO;AAAA,UAC5C,WAAW;AAAA,YACT;AAAA,YACA;AAAA,YACA,CAAC,WAAW;AAAA,YACZ;AAAA,UAAA;AAAA,UAED,GAAG;AAAA,UAMJ,UAAA;AAAA,YAAA,oBAAC,OAAA,EAAI,WAAU,mGACZ,UAAA,aACH;AAAA,YAEA,oBAAC,OAAA,EAAI,WAAU,yEACZ,SAAA,CACH;AAAA,UAAA;AAAA,QAAA;AAAA,MAAA;AAAA,IAGN;AAGA,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,gBAAc,gBAAgB,OAAO,OAAO;AAAA,QAC5C,WAAW;AAAA,UACT;AAAA,UACA;AAAA,UACA;AAAA;AAAA,UAEA,CAAC,WAAW;AAAA,UACZ;AAAA,QAAA;AAAA,QAED,GAAG;AAAA,QAEH;AAAA,MAAA;AAAA,IAAA;AAAA,EAGP;AACF;AACA,aAAa,cAAc;"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @internal — DS-internal primitive module(2026-05-23 per `.claude/rules/ui-development.md` Public vs Internal canonical)。
|
|
3
|
+
* end-user app 直接 import 無 functioning UI;由 Tabs / ChipGroup 等 DS 元件 wrap 消費 hook + helper components。
|
|
4
|
+
*/
|
|
5
|
+
import * as React from 'react';
|
|
6
|
+
import { useScrollEdges, useOverflowIndices } from '../../hooks/use-overflow-items';
|
|
7
|
+
/**
|
|
8
|
+
* Horizontal Overflow — canonical primitives + helpers
|
|
9
|
+
*
|
|
10
|
+
* 詳細設計原則與消費規則見 `horizontal-overflow.spec.md`。
|
|
11
|
+
*
|
|
12
|
+
* ── 核心規則 ──
|
|
13
|
+
* 所有 horizontal overflow 的 affordance 一律是 `<Button variant="text" size="sm" iconOnly>`。
|
|
14
|
+
* 無論內容是 Tab / Chip / Step / SegmentedControl item,overflow trigger 永遠同一套。
|
|
15
|
+
* 這是工具層,跟業務層(內容)解耦,不該跟內容爭視覺重量。
|
|
16
|
+
*
|
|
17
|
+
* ── 為什麼存在 ──
|
|
18
|
+
* 過去 Tabs 和 Chip 兩邊各自 copy-paste `ScrollArrow` / `buildFadeMask` / 常數,
|
|
19
|
+
* 導致 Chip menu trigger 漂移成 chip-shape(違反 mental model)。本 module 是
|
|
20
|
+
* single source of truth,消除複製帶來的漂移空間。
|
|
21
|
+
*/
|
|
22
|
+
/** Fade mask 漸變寬度(px)*/
|
|
23
|
+
export declare const FADE_WIDTH = 16;
|
|
24
|
+
/**
|
|
25
|
+
* Scroll arrow 預留的按鈕區寬度(px)。
|
|
26
|
+
* 值對齊 `field-height-sm` 在 lg density 下的最大值(32px),確保 arrow button
|
|
27
|
+
* 在任何 density 都能完整容納 Button text sm iconOnly。
|
|
28
|
+
*/
|
|
29
|
+
export declare const ARROW_BUTTON_WIDTH = 32;
|
|
30
|
+
/** 點一次 scroll arrow 滑動 80% 容器寬度 */
|
|
31
|
+
export declare const SCROLL_PAGE_RATIO = 0.8;
|
|
32
|
+
interface BuildFadeMaskArgs {
|
|
33
|
+
canScroll: boolean;
|
|
34
|
+
atStart: boolean;
|
|
35
|
+
atEnd: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* 預留給 scroll arrow button 的寬度(px)。
|
|
38
|
+
* - Scroll 模式:傳 `ARROW_BUTTON_WIDTH`,fade 會延伸到 arrow 底下,
|
|
39
|
+
* 避免「透明 button icon 跟 item 文字視覺打架」(Material 3 scrim 原理)
|
|
40
|
+
* - Menu 模式:傳 0,沒有 arrow,fade 直接從容器邊緣開始
|
|
41
|
+
*/
|
|
42
|
+
reserveArrowWidth?: number;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* 計算 fade mask 的 `linear-gradient` 字串。
|
|
46
|
+
*
|
|
47
|
+
* 回傳 `undefined` 時代表「不需要 mask」(內容沒溢出),消費者該讓 `maskImage`
|
|
48
|
+
* 等於 undefined 讓 CSS 恢復正常渲染。
|
|
49
|
+
*/
|
|
50
|
+
export declare function buildFadeMask({ canScroll, atStart, atEnd, reserveArrowWidth, }: BuildFadeMaskArgs): string | undefined;
|
|
51
|
+
/**
|
|
52
|
+
* Scroll by page helper。接收 scrollRef 回傳一個 `scrollByPage(direction)` 函式,
|
|
53
|
+
* 呼叫後以 smooth 動畫橫向滑動 `clientWidth × SCROLL_PAGE_RATIO`。
|
|
54
|
+
*
|
|
55
|
+
* 使用 useCallback 並以 scrollRef 為 dependency,確保消費者可以安全把回傳函式
|
|
56
|
+
* 傳給 `<OverflowScrollArrow onClick>`。
|
|
57
|
+
*/
|
|
58
|
+
export declare function useScrollByPage<T extends HTMLElement>(scrollRef: React.RefObject<T | null>): (direction: 'left' | 'right') => void;
|
|
59
|
+
export interface OverflowScrollArrowProps {
|
|
60
|
+
direction: 'left' | 'right';
|
|
61
|
+
onClick: () => void;
|
|
62
|
+
/**
|
|
63
|
+
* 覆寫 aria-label(預設為「向左捲動」/「向右捲動」)。
|
|
64
|
+
* 一般情況不需要傳——預設繁中 label 對所有水平捲動情境都成立。
|
|
65
|
+
*/
|
|
66
|
+
'aria-label'?: string;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Scroll arrow button — canonical horizontal overflow affordance。
|
|
70
|
+
*
|
|
71
|
+
* **絕對定位**在容器的 `left-0` / `right-0`,使用 `pointer-events-none` 外層 +
|
|
72
|
+
* `pointer-events-auto` 內層,讓 fade mask 下方仍可橫向滑動(arrow 只接受點
|
|
73
|
+
* 自身的 click)。
|
|
74
|
+
*
|
|
75
|
+
* Root 是 `<Button variant="text" size="sm" iconOnly>`——跟 Tabs / Chip /
|
|
76
|
+
* 未來所有消費者共用同一個按鈕視覺。
|
|
77
|
+
*/
|
|
78
|
+
export declare const OverflowScrollArrow: React.FC<OverflowScrollArrowProps>;
|
|
79
|
+
export interface OverflowMenuTriggerButtonProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children'> {
|
|
80
|
+
/**
|
|
81
|
+
* Accessible label。消費者應傳入有語境的 label,例如
|
|
82
|
+
* `"頁籤選單(共 5 個)"` 或 `"分類選單(共 8 個)"`。必填。
|
|
83
|
+
*/
|
|
84
|
+
'aria-label': string;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Menu trigger button — canonical overflow menu affordance。
|
|
88
|
+
*
|
|
89
|
+
* Root 是 `<Button variant="text" size="sm" iconOnly startIcon={ChevronDown}>`。
|
|
90
|
+
* **forwardRef + props spread** 讓 Radix `<DropdownMenuTrigger asChild>` 可以
|
|
91
|
+
* 接管 onClick / aria-expanded / data-state 等 attributes。
|
|
92
|
+
*
|
|
93
|
+
* ── 為什麼不包 DropdownMenu ──
|
|
94
|
+
* Menu 的內容(items)取決於消費者的資料結構(Tab 用 DropdownMenuItem + selected,
|
|
95
|
+
* Chip 用 DropdownMenuCheckboxItem),所以只提供 trigger button,消費者自己
|
|
96
|
+
* 把 `<DropdownMenu> <DropdownMenuTrigger asChild> <OverflowMenuTriggerButton /> ... </DropdownMenu>`
|
|
97
|
+
* 串起來。
|
|
98
|
+
*/
|
|
99
|
+
export declare const OverflowMenuTriggerButton: React.ForwardRefExoticComponent<OverflowMenuTriggerButtonProps & React.RefAttributes<HTMLButtonElement>>;
|
|
100
|
+
export { useScrollEdges, useOverflowIndices };
|
|
101
|
+
//# sourceMappingURL=horizontal-overflow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"horizontal-overflow.d.ts","sourceRoot":"","sources":["../../../src/patterns/horizontal-overflow/horizontal-overflow.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAI9B,OAAO,EACL,cAAc,EACd,kBAAkB,EACnB,MAAM,0CAA0C,CAAA;AAEjD;;;;;;;;;;;;;;GAcG;AAIH,wBAAwB;AAExB,eAAO,MAAM,UAAU,KAAK,CAAA;AAE5B;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,KAAK,CAAA;AAEpC,mCAAmC;AAEnC,eAAO,MAAM,iBAAiB,MAAM,CAAA;AAIpC,UAAU,iBAAiB;IACzB,SAAS,EAAE,OAAO,CAAA;IAClB,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,EAAE,OAAO,CAAA;IACd;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAC3B;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,EAC5B,SAAS,EACT,OAAO,EACP,KAAK,EACL,iBAAqB,GACtB,EAAE,iBAAiB,GAAG,MAAM,GAAG,SAAS,CA4BxC;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,WAAW,EACnD,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC,GAAG,IAAI,CAAC,GACnC,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,KAAK,IAAI,CAYvC;AAID,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAA;IAC3B,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,mBAAmB,EAAE,KAAK,CAAC,EAAE,CAAC,wBAAwB,CAyBlE,CAAA;AAED,MAAM,WAAW,8BACf,SAAQ,IAAI,CAAC,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,UAAU,CAAC;IACvE;;;OAGG;IACH,YAAY,EAAE,MAAM,CAAA;CACrB;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,yBAAyB,0GAapC,CAAA;AAMF,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAA"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { ChevronDown, ChevronLeft, ChevronRight } from "lucide-react";
|
|
4
|
+
import { cn } from "../../lib/utils.js";
|
|
5
|
+
import { Button } from "../../components/Button/button.js";
|
|
6
|
+
const FADE_WIDTH = 16;
|
|
7
|
+
const ARROW_BUTTON_WIDTH = 32;
|
|
8
|
+
const SCROLL_PAGE_RATIO = 0.8;
|
|
9
|
+
function buildFadeMask({
|
|
10
|
+
canScroll,
|
|
11
|
+
atStart,
|
|
12
|
+
atEnd,
|
|
13
|
+
reserveArrowWidth = 0
|
|
14
|
+
}) {
|
|
15
|
+
if (!canScroll) return void 0;
|
|
16
|
+
const stops = [];
|
|
17
|
+
if (!atStart) {
|
|
18
|
+
if (reserveArrowWidth > 0) {
|
|
19
|
+
stops.push("transparent 0");
|
|
20
|
+
stops.push(`transparent ${reserveArrowWidth}px`);
|
|
21
|
+
stops.push(`black ${reserveArrowWidth + FADE_WIDTH}px`);
|
|
22
|
+
} else {
|
|
23
|
+
stops.push("transparent 0");
|
|
24
|
+
stops.push(`black ${FADE_WIDTH}px`);
|
|
25
|
+
}
|
|
26
|
+
} else {
|
|
27
|
+
stops.push("black 0");
|
|
28
|
+
}
|
|
29
|
+
if (!atEnd) {
|
|
30
|
+
if (reserveArrowWidth > 0) {
|
|
31
|
+
stops.push(`black calc(100% - ${reserveArrowWidth + FADE_WIDTH}px)`);
|
|
32
|
+
stops.push(`transparent calc(100% - ${reserveArrowWidth}px)`);
|
|
33
|
+
stops.push("transparent 100%");
|
|
34
|
+
} else {
|
|
35
|
+
stops.push(`black calc(100% - ${FADE_WIDTH}px)`);
|
|
36
|
+
stops.push("transparent 100%");
|
|
37
|
+
}
|
|
38
|
+
} else {
|
|
39
|
+
stops.push("black 100%");
|
|
40
|
+
}
|
|
41
|
+
return `linear-gradient(to right, ${stops.join(", ")})`;
|
|
42
|
+
}
|
|
43
|
+
function useScrollByPage(scrollRef) {
|
|
44
|
+
return React.useCallback(
|
|
45
|
+
(direction) => {
|
|
46
|
+
const el = scrollRef.current;
|
|
47
|
+
if (!el) return;
|
|
48
|
+
el.scrollBy({
|
|
49
|
+
left: el.clientWidth * SCROLL_PAGE_RATIO * (direction === "left" ? -1 : 1),
|
|
50
|
+
behavior: "smooth"
|
|
51
|
+
});
|
|
52
|
+
},
|
|
53
|
+
[scrollRef]
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
const OverflowScrollArrow = ({
|
|
57
|
+
direction,
|
|
58
|
+
onClick,
|
|
59
|
+
"aria-label": ariaLabel
|
|
60
|
+
}) => {
|
|
61
|
+
const defaultLabel = direction === "left" ? "向左捲動" : "向右捲動";
|
|
62
|
+
return /* @__PURE__ */ jsx(
|
|
63
|
+
"div",
|
|
64
|
+
{
|
|
65
|
+
className: cn(
|
|
66
|
+
"absolute top-0 bottom-0 flex items-center pointer-events-none z-10",
|
|
67
|
+
direction === "left" ? "left-0" : "right-0"
|
|
68
|
+
),
|
|
69
|
+
children: /* @__PURE__ */ jsx("div", { className: "pointer-events-auto", children: /* @__PURE__ */ jsx(
|
|
70
|
+
Button,
|
|
71
|
+
{
|
|
72
|
+
variant: "text",
|
|
73
|
+
size: "sm",
|
|
74
|
+
iconOnly: true,
|
|
75
|
+
startIcon: direction === "left" ? ChevronLeft : ChevronRight,
|
|
76
|
+
"aria-label": ariaLabel ?? defaultLabel,
|
|
77
|
+
onClick
|
|
78
|
+
}
|
|
79
|
+
) })
|
|
80
|
+
}
|
|
81
|
+
);
|
|
82
|
+
};
|
|
83
|
+
const OverflowMenuTriggerButton = React.forwardRef(({ "aria-label": ariaLabel, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
84
|
+
Button,
|
|
85
|
+
{
|
|
86
|
+
ref,
|
|
87
|
+
variant: "text",
|
|
88
|
+
size: "sm",
|
|
89
|
+
iconOnly: true,
|
|
90
|
+
startIcon: ChevronDown,
|
|
91
|
+
"aria-label": ariaLabel,
|
|
92
|
+
...props
|
|
93
|
+
}
|
|
94
|
+
));
|
|
95
|
+
OverflowMenuTriggerButton.displayName = "OverflowMenuTriggerButton";
|
|
96
|
+
export {
|
|
97
|
+
ARROW_BUTTON_WIDTH,
|
|
98
|
+
FADE_WIDTH,
|
|
99
|
+
OverflowMenuTriggerButton,
|
|
100
|
+
OverflowScrollArrow,
|
|
101
|
+
SCROLL_PAGE_RATIO,
|
|
102
|
+
buildFadeMask,
|
|
103
|
+
useScrollByPage
|
|
104
|
+
};
|
|
105
|
+
//# sourceMappingURL=horizontal-overflow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"horizontal-overflow.js","sources":["../../../src/patterns/horizontal-overflow/horizontal-overflow.tsx"],"sourcesContent":["/**\n * @internal — DS-internal primitive module(2026-05-23 per `.claude/rules/ui-development.md` Public vs Internal canonical)。\n * end-user app 直接 import 無 functioning UI;由 Tabs / ChipGroup 等 DS 元件 wrap 消費 hook + helper components。\n */\n// @benchmark-unverified-blanket: file-level retraction per M22 (d) — claims herein not individually URL-cited; treat as unverified visual/usage rumor unless retrofit per-claim. Hook escape preserved.\nimport * as React from 'react'\nimport { ChevronDown, ChevronLeft, ChevronRight } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { Button } from '@/design-system/components/Button/button'\nimport {\n useScrollEdges,\n useOverflowIndices,\n} from '@/design-system/hooks/use-overflow-items'\n\n/**\n * Horizontal Overflow — canonical primitives + helpers\n *\n * 詳細設計原則與消費規則見 `horizontal-overflow.spec.md`。\n *\n * ── 核心規則 ──\n * 所有 horizontal overflow 的 affordance 一律是 `<Button variant=\"text\" size=\"sm\" iconOnly>`。\n * 無論內容是 Tab / Chip / Step / SegmentedControl item,overflow trigger 永遠同一套。\n * 這是工具層,跟業務層(內容)解耦,不該跟內容爭視覺重量。\n *\n * ── 為什麼存在 ──\n * 過去 Tabs 和 Chip 兩邊各自 copy-paste `ScrollArrow` / `buildFadeMask` / 常數,\n * 導致 Chip menu trigger 漂移成 chip-shape(違反 mental model)。本 module 是\n * single source of truth,消除複製帶來的漂移空間。\n */\n\n// ── Constants ─────────────────────────────────────────────────────────────\n\n/** Fade mask 漸變寬度(px)*/\n// code-quality-allow: dead-export — public constant — DS API surface,consumer 可引(即使當前 internal-only)\nexport const FADE_WIDTH = 16\n\n/**\n * Scroll arrow 預留的按鈕區寬度(px)。\n * 值對齊 `field-height-sm` 在 lg density 下的最大值(32px),確保 arrow button\n * 在任何 density 都能完整容納 Button text sm iconOnly。\n */\nexport const ARROW_BUTTON_WIDTH = 32\n\n/** 點一次 scroll arrow 滑動 80% 容器寬度 */\n// code-quality-allow: dead-export — public constant — DS API surface,consumer 可引(即使當前 internal-only)\nexport const SCROLL_PAGE_RATIO = 0.8\n\n// ── Helpers ───────────────────────────────────────────────────────────────\n\ninterface BuildFadeMaskArgs {\n canScroll: boolean\n atStart: boolean\n atEnd: boolean\n /**\n * 預留給 scroll arrow button 的寬度(px)。\n * - Scroll 模式:傳 `ARROW_BUTTON_WIDTH`,fade 會延伸到 arrow 底下,\n * 避免「透明 button icon 跟 item 文字視覺打架」(Material 3 scrim 原理)\n * - Menu 模式:傳 0,沒有 arrow,fade 直接從容器邊緣開始\n */\n reserveArrowWidth?: number\n}\n\n/**\n * 計算 fade mask 的 `linear-gradient` 字串。\n *\n * 回傳 `undefined` 時代表「不需要 mask」(內容沒溢出),消費者該讓 `maskImage`\n * 等於 undefined 讓 CSS 恢復正常渲染。\n */\nexport function buildFadeMask({\n canScroll,\n atStart,\n atEnd,\n reserveArrowWidth = 0,\n}: BuildFadeMaskArgs): string | undefined {\n if (!canScroll) return undefined\n const stops: string[] = []\n if (!atStart) {\n if (reserveArrowWidth > 0) {\n stops.push('transparent 0')\n stops.push(`transparent ${reserveArrowWidth}px`)\n stops.push(`black ${reserveArrowWidth + FADE_WIDTH}px`)\n } else {\n stops.push('transparent 0')\n stops.push(`black ${FADE_WIDTH}px`)\n }\n } else {\n stops.push('black 0')\n }\n if (!atEnd) {\n if (reserveArrowWidth > 0) {\n stops.push(`black calc(100% - ${reserveArrowWidth + FADE_WIDTH}px)`)\n stops.push(`transparent calc(100% - ${reserveArrowWidth}px)`)\n stops.push('transparent 100%')\n } else {\n stops.push(`black calc(100% - ${FADE_WIDTH}px)`)\n stops.push('transparent 100%')\n }\n } else {\n stops.push('black 100%')\n }\n return `linear-gradient(to right, ${stops.join(', ')})`\n}\n\n/**\n * Scroll by page helper。接收 scrollRef 回傳一個 `scrollByPage(direction)` 函式,\n * 呼叫後以 smooth 動畫橫向滑動 `clientWidth × SCROLL_PAGE_RATIO`。\n *\n * 使用 useCallback 並以 scrollRef 為 dependency,確保消費者可以安全把回傳函式\n * 傳給 `<OverflowScrollArrow onClick>`。\n */\nexport function useScrollByPage<T extends HTMLElement>(\n scrollRef: React.RefObject<T | null>,\n): (direction: 'left' | 'right') => void {\n return React.useCallback(\n (direction: 'left' | 'right') => {\n const el = scrollRef.current\n if (!el) return\n el.scrollBy({\n left: el.clientWidth * SCROLL_PAGE_RATIO * (direction === 'left' ? -1 : 1),\n behavior: 'smooth',\n })\n },\n [scrollRef],\n )\n}\n\n// ── Primitives ────────────────────────────────────────────────────────────\n\nexport interface OverflowScrollArrowProps {\n direction: 'left' | 'right'\n onClick: () => void\n /**\n * 覆寫 aria-label(預設為「向左捲動」/「向右捲動」)。\n * 一般情況不需要傳——預設繁中 label 對所有水平捲動情境都成立。\n */\n 'aria-label'?: string\n}\n\n/**\n * Scroll arrow button — canonical horizontal overflow affordance。\n *\n * **絕對定位**在容器的 `left-0` / `right-0`,使用 `pointer-events-none` 外層 +\n * `pointer-events-auto` 內層,讓 fade mask 下方仍可橫向滑動(arrow 只接受點\n * 自身的 click)。\n *\n * Root 是 `<Button variant=\"text\" size=\"sm\" iconOnly>`——跟 Tabs / Chip /\n * 未來所有消費者共用同一個按鈕視覺。\n */\nexport const OverflowScrollArrow: React.FC<OverflowScrollArrowProps> = ({\n direction,\n onClick,\n 'aria-label': ariaLabel,\n}) => {\n const defaultLabel = direction === 'left' ? '向左捲動' : '向右捲動' // i18n-allow: DS default; consumer override via aria-label prop\n return (\n <div\n className={cn(\n 'absolute top-0 bottom-0 flex items-center pointer-events-none z-10',\n direction === 'left' ? 'left-0' : 'right-0',\n )}\n >\n <div className=\"pointer-events-auto\">\n <Button\n variant=\"text\"\n size=\"sm\"\n iconOnly\n startIcon={direction === 'left' ? ChevronLeft : ChevronRight}\n aria-label={ariaLabel ?? defaultLabel}\n onClick={onClick}\n />\n </div>\n </div>\n )\n}\n\nexport interface OverflowMenuTriggerButtonProps\n extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children'> {\n /**\n * Accessible label。消費者應傳入有語境的 label,例如\n * `\"頁籤選單(共 5 個)\"` 或 `\"分類選單(共 8 個)\"`。必填。\n */\n 'aria-label': string\n}\n\n/**\n * Menu trigger button — canonical overflow menu affordance。\n *\n * Root 是 `<Button variant=\"text\" size=\"sm\" iconOnly startIcon={ChevronDown}>`。\n * **forwardRef + props spread** 讓 Radix `<DropdownMenuTrigger asChild>` 可以\n * 接管 onClick / aria-expanded / data-state 等 attributes。\n *\n * ── 為什麼不包 DropdownMenu ──\n * Menu 的內容(items)取決於消費者的資料結構(Tab 用 DropdownMenuItem + selected,\n * Chip 用 DropdownMenuCheckboxItem),所以只提供 trigger button,消費者自己\n * 把 `<DropdownMenu> <DropdownMenuTrigger asChild> <OverflowMenuTriggerButton /> ... </DropdownMenu>`\n * 串起來。\n */\nexport const OverflowMenuTriggerButton = React.forwardRef<\n HTMLButtonElement,\n OverflowMenuTriggerButtonProps\n>(({ 'aria-label': ariaLabel, ...props }, ref) => (\n <Button\n ref={ref}\n variant=\"text\"\n size=\"sm\"\n iconOnly\n startIcon={ChevronDown}\n aria-label={ariaLabel}\n {...props}\n />\n))\nOverflowMenuTriggerButton.displayName = 'OverflowMenuTriggerButton'\n\n// ── Hook re-exports ──────────────────────────────────────────────────────\n// 讓消費者只從本 module import 所有 overflow 相關的 API。\n\nexport { useScrollEdges, useOverflowIndices }\n"],"names":[],"mappings":";;;;;AAkCO,MAAM,aAAa;AAOnB,MAAM,qBAAqB;AAI3B,MAAM,oBAAoB;AAuB1B,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA,oBAAoB;AACtB,GAA0C;AACxC,MAAI,CAAC,UAAW,QAAO;AACvB,QAAM,QAAkB,CAAA;AACxB,MAAI,CAAC,SAAS;AACZ,QAAI,oBAAoB,GAAG;AACzB,YAAM,KAAK,eAAe;AAC1B,YAAM,KAAK,eAAe,iBAAiB,IAAI;AAC/C,YAAM,KAAK,SAAS,oBAAoB,UAAU,IAAI;AAAA,IACxD,OAAO;AACL,YAAM,KAAK,eAAe;AAC1B,YAAM,KAAK,SAAS,UAAU,IAAI;AAAA,IACpC;AAAA,EACF,OAAO;AACL,UAAM,KAAK,SAAS;AAAA,EACtB;AACA,MAAI,CAAC,OAAO;AACV,QAAI,oBAAoB,GAAG;AACzB,YAAM,KAAK,qBAAqB,oBAAoB,UAAU,KAAK;AACnE,YAAM,KAAK,2BAA2B,iBAAiB,KAAK;AAC5D,YAAM,KAAK,kBAAkB;AAAA,IAC/B,OAAO;AACL,YAAM,KAAK,qBAAqB,UAAU,KAAK;AAC/C,YAAM,KAAK,kBAAkB;AAAA,IAC/B;AAAA,EACF,OAAO;AACL,UAAM,KAAK,YAAY;AAAA,EACzB;AACA,SAAO,6BAA6B,MAAM,KAAK,IAAI,CAAC;AACtD;AASO,SAAS,gBACd,WACuC;AACvC,SAAO,MAAM;AAAA,IACX,CAAC,cAAgC;AAC/B,YAAM,KAAK,UAAU;AACrB,UAAI,CAAC,GAAI;AACT,SAAG,SAAS;AAAA,QACV,MAAM,GAAG,cAAc,qBAAqB,cAAc,SAAS,KAAK;AAAA,QACxE,UAAU;AAAA,MAAA,CACX;AAAA,IACH;AAAA,IACA,CAAC,SAAS;AAAA,EAAA;AAEd;AAwBO,MAAM,sBAA0D,CAAC;AAAA,EACtE;AAAA,EACA;AAAA,EACA,cAAc;AAChB,MAAM;AACJ,QAAM,eAAe,cAAc,SAAS,SAAS;AACrD,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA,cAAc,SAAS,WAAW;AAAA,MAAA;AAAA,MAGpC,UAAA,oBAAC,OAAA,EAAI,WAAU,uBACb,UAAA;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,SAAQ;AAAA,UACR,MAAK;AAAA,UACL,UAAQ;AAAA,UACR,WAAW,cAAc,SAAS,cAAc;AAAA,UAChD,cAAY,aAAa;AAAA,UACzB;AAAA,QAAA;AAAA,MAAA,EACF,CACF;AAAA,IAAA;AAAA,EAAA;AAGN;AAwBO,MAAM,4BAA4B,MAAM,WAG7C,CAAC,EAAE,cAAc,WAAW,GAAG,SAAS,QACxC;AAAA,EAAC;AAAA,EAAA;AAAA,IACC;AAAA,IACA,SAAQ;AAAA,IACR,MAAK;AAAA,IACL,UAAQ;AAAA,IACR,WAAW;AAAA,IACX,cAAY;AAAA,IACX,GAAG;AAAA,EAAA;AACN,CACD;AACD,0BAA0B,cAAc;"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
export interface SurfaceHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
3
|
+
/**
|
|
4
|
+
* 是否內含 Tabs。
|
|
5
|
+
* true(無 tabsSlot)→ 移除自身 border-b。
|
|
6
|
+
* 若提供 tabsSlot,自動 column mode + auto suppress border(不需手動傳 withTabs=true)。
|
|
7
|
+
* 對齊 patterns/header-canonical/header-canonical.spec.md W1
|
|
8
|
+
* 「Header semantic owner / TabsList paint owner in withTabs state」。
|
|
9
|
+
*/
|
|
10
|
+
withTabs?: boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Tabs row slot(2026-05-18 加 per header-canonical.spec.md W2/W4 真實能用 + user-mandated fix)。
|
|
13
|
+
* 提供時 SurfaceHeader 自動 column 結構:
|
|
14
|
+
* row 1 = children(title + actions/dismiss,px-loose py-tight)
|
|
15
|
+
* row 2 = tabsSlot 包在 `<div px-loose border-b border-divider>`
|
|
16
|
+
* ↑ wrapper 提供 W2 padding inheritance + 全寬 paint(W1 視覺一條線)
|
|
17
|
+
*
|
|
18
|
+
* Consumer 傳:`tabsSlot={<TabsList>...</TabsList>}`,TabsContent 仍放 DialogBody 內。
|
|
19
|
+
* `<Tabs>` root 必須 wrap 整 DialogContent(Radix TabsList ↔ TabsContent 同 root 連動)。
|
|
20
|
+
*
|
|
21
|
+
* 提供 tabsSlot 自動 withTabs=true,不需另傳 prop。
|
|
22
|
+
*/
|
|
23
|
+
tabsSlot?: React.ReactNode;
|
|
24
|
+
}
|
|
25
|
+
export declare const SurfaceHeader: React.ForwardRefExoticComponent<SurfaceHeaderProps & React.RefAttributes<HTMLDivElement>>;
|
|
26
|
+
export declare const SurfaceBody: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
|
|
27
|
+
export declare const SurfaceFooter: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
|
|
28
|
+
//# sourceMappingURL=overlay-surface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"overlay-surface.d.ts","sourceRoot":"","sources":["../../../src/patterns/overlay-surface/overlay-surface.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAgE9B,MAAM,WAAW,kBACf,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IAC5C;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC3B;AAED,eAAO,MAAM,aAAa,2FAgExB,CAAA;AAGF,eAAO,MAAM,WAAW,6GAiBtB,CAAA;AAGF,eAAO,MAAM,aAAa,6GAaxB,CAAA"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { cn } from "../../lib/utils.js";
|
|
4
|
+
const CHROME_UNBOUNDED_SLOT = "[&_[data-unbounded]]:my-[calc((var(--chrome-slot-h,var(--field-height-xs))-var(--field-height-sm))/2)]";
|
|
5
|
+
const SurfaceHeader = React.forwardRef(({ className, withTabs, tabsSlot, children, ...props }, ref) => {
|
|
6
|
+
const hasTabs = tabsSlot != null || withTabs === true;
|
|
7
|
+
if (tabsSlot != null) {
|
|
8
|
+
return /* @__PURE__ */ jsxs(
|
|
9
|
+
"div",
|
|
10
|
+
{
|
|
11
|
+
ref,
|
|
12
|
+
className: cn("flex flex-col shrink-0", className),
|
|
13
|
+
...props,
|
|
14
|
+
children: [
|
|
15
|
+
/* @__PURE__ */ jsx(
|
|
16
|
+
"div",
|
|
17
|
+
{
|
|
18
|
+
className: cn(
|
|
19
|
+
"flex items-center gap-2",
|
|
20
|
+
"px-[var(--layout-space-loose)] py-[var(--layout-space-tight)]",
|
|
21
|
+
CHROME_UNBOUNDED_SLOT
|
|
22
|
+
),
|
|
23
|
+
children
|
|
24
|
+
}
|
|
25
|
+
),
|
|
26
|
+
/* @__PURE__ */ jsx("div", { className: "[&>[role=tablist]]:w-full [&>[role=tablist]]:px-[var(--layout-space-loose)]", children: tabsSlot })
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
return /* @__PURE__ */ jsx(
|
|
32
|
+
"div",
|
|
33
|
+
{
|
|
34
|
+
ref,
|
|
35
|
+
className: cn(
|
|
36
|
+
"flex items-center gap-2 shrink-0",
|
|
37
|
+
!hasTabs && "border-b border-divider",
|
|
38
|
+
"px-[var(--layout-space-loose)] py-[var(--layout-space-tight)]",
|
|
39
|
+
CHROME_UNBOUNDED_SLOT,
|
|
40
|
+
className
|
|
41
|
+
),
|
|
42
|
+
...props,
|
|
43
|
+
children
|
|
44
|
+
}
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
SurfaceHeader.displayName = "SurfaceHeader";
|
|
48
|
+
const SurfaceBody = React.forwardRef(({ className, ...props }, ref) => (
|
|
49
|
+
// 2026-05-04 viewport-aware scroll canonical:
|
|
50
|
+
// parent(PopoverContent / HoverCardContent / Dialog / Sheet)是 flex flex-col + max-h + overflow-hidden
|
|
51
|
+
// header / footer shrink-0;Body flex-1 min-h-0 overflow-y-auto → 視窗太小時 body 內 scroll
|
|
52
|
+
// 非 flex-col parent 內 flex-1/min-h-0 no-op,backward compat
|
|
53
|
+
/* @__PURE__ */ jsx(
|
|
54
|
+
"div",
|
|
55
|
+
{
|
|
56
|
+
ref,
|
|
57
|
+
className: cn(
|
|
58
|
+
"flex-1 min-h-0 overflow-y-auto",
|
|
59
|
+
"px-[var(--layout-space-loose)] py-[var(--layout-space-tight)]",
|
|
60
|
+
className
|
|
61
|
+
),
|
|
62
|
+
...props
|
|
63
|
+
}
|
|
64
|
+
)
|
|
65
|
+
));
|
|
66
|
+
SurfaceBody.displayName = "SurfaceBody";
|
|
67
|
+
const SurfaceFooter = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
68
|
+
"div",
|
|
69
|
+
{
|
|
70
|
+
ref,
|
|
71
|
+
className: cn(
|
|
72
|
+
"flex items-center justify-end gap-2 shrink-0 border-t border-divider",
|
|
73
|
+
"px-[var(--layout-space-loose)] py-[var(--layout-space-tight)]",
|
|
74
|
+
className
|
|
75
|
+
),
|
|
76
|
+
...props
|
|
77
|
+
}
|
|
78
|
+
));
|
|
79
|
+
SurfaceFooter.displayName = "SurfaceFooter";
|
|
80
|
+
export {
|
|
81
|
+
SurfaceBody,
|
|
82
|
+
SurfaceFooter,
|
|
83
|
+
SurfaceHeader
|
|
84
|
+
};
|
|
85
|
+
//# sourceMappingURL=overlay-surface.js.map
|