@qijenchen/design-system 0.1.0-beta.80 → 0.1.0-beta.82
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +1 -1
- package/dist/components/Alert/alert.d.ts +5 -0
- package/dist/components/Alert/alert.d.ts.map +1 -1
- package/dist/components/Alert/alert.js +4 -1
- package/dist/components/Alert/alert.js.map +1 -1
- package/dist/components/AppShell/app-shell.d.ts +1 -1
- package/dist/components/AppShell/app-shell.js.map +1 -1
- package/dist/components/AspectRatio/aspect-ratio.d.ts +2 -1
- package/dist/components/AspectRatio/aspect-ratio.d.ts.map +1 -1
- package/dist/components/AspectRatio/aspect-ratio.js +1 -1
- package/dist/components/AspectRatio/aspect-ratio.js.map +1 -1
- package/dist/components/Avatar/avatar.d.ts +3 -2
- package/dist/components/Avatar/avatar.d.ts.map +1 -1
- package/dist/components/Avatar/avatar.js +8 -3
- package/dist/components/Avatar/avatar.js.map +1 -1
- package/dist/components/Badge/badge.d.ts.map +1 -1
- package/dist/components/Badge/badge.js +2 -1
- package/dist/components/Badge/badge.js.map +1 -1
- package/dist/components/Breadcrumb/breadcrumb.d.ts +14 -1
- package/dist/components/Breadcrumb/breadcrumb.d.ts.map +1 -1
- package/dist/components/Breadcrumb/breadcrumb.js +9 -3
- package/dist/components/Breadcrumb/breadcrumb.js.map +1 -1
- package/dist/components/BulkActionBar/bulk-action-bar.d.ts +4 -1
- package/dist/components/BulkActionBar/bulk-action-bar.d.ts.map +1 -1
- package/dist/components/BulkActionBar/bulk-action-bar.js +2 -2
- package/dist/components/BulkActionBar/bulk-action-bar.js.map +1 -1
- package/dist/components/Button/button.d.ts +4 -4
- package/dist/components/Button/button.d.ts.map +1 -1
- package/dist/components/Button/button.js +73 -44
- package/dist/components/Button/button.js.map +1 -1
- package/dist/components/Button/index.d.ts +1 -0
- package/dist/components/Button/index.d.ts.map +1 -1
- package/dist/components/Button/index.js +3 -0
- package/dist/components/Button/index.js.map +1 -1
- package/dist/components/Calendar/calendar.d.ts +3 -1
- package/dist/components/Calendar/calendar.d.ts.map +1 -1
- package/dist/components/Calendar/calendar.js +40 -17
- package/dist/components/Calendar/calendar.js.map +1 -1
- package/dist/components/Carousel/carousel.d.ts +1 -1
- package/dist/components/Carousel/carousel.d.ts.map +1 -1
- package/dist/components/Carousel/carousel.js +3 -1
- package/dist/components/Carousel/carousel.js.map +1 -1
- package/dist/components/Chart/chart.d.ts.map +1 -1
- package/dist/components/Chart/chart.js +1 -1
- package/dist/components/Chart/chart.js.map +1 -1
- package/dist/components/Checkbox/checkbox.d.ts +6 -3
- package/dist/components/Checkbox/checkbox.d.ts.map +1 -1
- package/dist/components/Checkbox/checkbox.js +26 -4
- package/dist/components/Checkbox/checkbox.js.map +1 -1
- package/dist/components/Checkbox/index.d.ts +1 -0
- package/dist/components/Checkbox/index.d.ts.map +1 -1
- package/dist/components/Checkbox/index.js +3 -0
- package/dist/components/Checkbox/index.js.map +1 -1
- package/dist/components/Chip/chip.d.ts +9 -3
- package/dist/components/Chip/chip.d.ts.map +1 -1
- package/dist/components/Chip/chip.js +46 -13
- package/dist/components/Chip/chip.js.map +1 -1
- package/dist/components/Coachmark/coachmark.d.ts +5 -3
- package/dist/components/Coachmark/coachmark.d.ts.map +1 -1
- package/dist/components/Coachmark/coachmark.js +10 -7
- package/dist/components/Coachmark/coachmark.js.map +1 -1
- package/dist/components/Combobox/combobox.d.ts +3 -1
- package/dist/components/Combobox/combobox.d.ts.map +1 -1
- package/dist/components/Combobox/combobox.js +11 -7
- package/dist/components/Combobox/combobox.js.map +1 -1
- package/dist/components/Command/command.d.ts +2 -2
- package/dist/components/Command/command.d.ts.map +1 -1
- package/dist/components/Command/command.js +4 -2
- package/dist/components/Command/command.js.map +1 -1
- package/dist/components/DataTable/active-editor-controller.d.ts +4 -3
- package/dist/components/DataTable/active-editor-controller.d.ts.map +1 -1
- package/dist/components/DataTable/cell-registry.d.ts.map +1 -1
- package/dist/components/DataTable/cell-registry.js +3 -0
- package/dist/components/DataTable/cell-registry.js.map +1 -1
- package/dist/components/DataTable/data-table-filter-panel.d.ts.map +1 -1
- package/dist/components/DataTable/data-table-interaction-layer.d.ts +4 -3
- package/dist/components/DataTable/data-table-interaction-layer.d.ts.map +1 -1
- package/dist/components/DataTable/data-table-interaction-layer.js.map +1 -1
- package/dist/components/DataTable/data-table.d.ts +38 -7
- package/dist/components/DataTable/data-table.d.ts.map +1 -1
- package/dist/components/DataTable/data-table.js +213 -63
- package/dist/components/DataTable/data-table.js.map +1 -1
- package/dist/components/DataTable/filter-tree.d.ts +5 -0
- package/dist/components/DataTable/filter-tree.d.ts.map +1 -1
- package/dist/components/DateGrid/date-grid.d.ts +3 -3
- package/dist/components/DateGrid/date-grid.d.ts.map +1 -1
- package/dist/components/DateGrid/date-grid.js +52 -6
- package/dist/components/DateGrid/date-grid.js.map +1 -1
- package/dist/components/DatePicker/date-picker.d.ts +2 -8
- package/dist/components/DatePicker/date-picker.d.ts.map +1 -1
- package/dist/components/DatePicker/date-picker.js +139 -109
- package/dist/components/DatePicker/date-picker.js.map +1 -1
- package/dist/components/DescriptionList/description-list.d.ts.map +1 -1
- package/dist/components/DescriptionList/description-list.js +1 -1
- package/dist/components/DescriptionList/description-list.js.map +1 -1
- package/dist/components/Dialog/dialog.d.ts +1 -1
- package/dist/components/Dialog/dialog.d.ts.map +1 -1
- package/dist/components/Dialog/dialog.js +2 -1
- package/dist/components/Dialog/dialog.js.map +1 -1
- package/dist/components/DropdownMenu/dropdown-menu.d.ts +4 -4
- package/dist/components/DropdownMenu/dropdown-menu.d.ts.map +1 -1
- package/dist/components/DropdownMenu/dropdown-menu.js +25 -8
- package/dist/components/DropdownMenu/dropdown-menu.js.map +1 -1
- package/dist/components/Empty/empty.d.ts.map +1 -1
- package/dist/components/Empty/empty.js +1 -1
- package/dist/components/Empty/empty.js.map +1 -1
- package/dist/components/Field/field-wrapper.d.ts +1 -0
- package/dist/components/Field/field-wrapper.d.ts.map +1 -1
- package/dist/components/Field/field-wrapper.js +27 -7
- package/dist/components/Field/field-wrapper.js.map +1 -1
- package/dist/components/Field/field.d.ts +1 -1
- package/dist/components/Field/field.d.ts.map +1 -1
- package/dist/components/Field/field.js +2 -1
- package/dist/components/Field/field.js.map +1 -1
- package/dist/components/Field/use-form-validation.d.ts +5 -1
- package/dist/components/Field/use-form-validation.d.ts.map +1 -1
- package/dist/components/Field/use-form-validation.js +31 -21
- package/dist/components/Field/use-form-validation.js.map +1 -1
- package/dist/components/FieldControlGroup/field-control-group.d.ts +3 -3
- package/dist/components/FieldControlGroup/field-control-group.js.map +1 -1
- package/dist/components/FileItem/file-item.d.ts.map +1 -1
- package/dist/components/FileItem/file-item.js +3 -2
- package/dist/components/FileItem/file-item.js.map +1 -1
- package/dist/components/FileUpload/file-upload.d.ts +1 -1
- package/dist/components/FileUpload/file-upload.d.ts.map +1 -1
- package/dist/components/FileUpload/file-upload.js +22 -10
- package/dist/components/FileUpload/file-upload.js.map +1 -1
- package/dist/components/FileViewer/can-render-image.d.ts +6 -0
- package/dist/components/FileViewer/can-render-image.d.ts.map +1 -0
- package/dist/components/FileViewer/can-render-image.js +11 -0
- package/dist/components/FileViewer/can-render-image.js.map +1 -0
- package/dist/components/FileViewer/file-viewer.d.ts +2 -2
- package/dist/components/FileViewer/file-viewer.d.ts.map +1 -1
- package/dist/components/FileViewer/file-viewer.js +24 -16
- package/dist/components/FileViewer/file-viewer.js.map +1 -1
- package/dist/components/FileViewer/image-renderer.d.ts +0 -5
- package/dist/components/FileViewer/image-renderer.d.ts.map +1 -1
- package/dist/components/FileViewer/image-renderer.js +1 -9
- package/dist/components/FileViewer/image-renderer.js.map +1 -1
- package/dist/components/HoverCard/hover-card.d.ts.map +1 -1
- package/dist/components/HoverCard/hover-card.js +3 -1
- package/dist/components/HoverCard/hover-card.js.map +1 -1
- package/dist/components/Input/input.d.ts +26 -3
- package/dist/components/Input/input.d.ts.map +1 -1
- package/dist/components/Input/input.js +12 -8
- package/dist/components/Input/input.js.map +1 -1
- package/dist/components/LinkInput/link-input.d.ts +3 -3
- package/dist/components/LinkInput/link-input.d.ts.map +1 -1
- package/dist/components/LinkInput/link-input.js +29 -28
- package/dist/components/LinkInput/link-input.js.map +1 -1
- package/dist/components/Menu/menu-item.d.ts.map +1 -1
- package/dist/components/Menu/menu-item.js +5 -4
- package/dist/components/Menu/menu-item.js.map +1 -1
- package/dist/components/Notice/notice.d.ts +5 -0
- package/dist/components/Notice/notice.d.ts.map +1 -1
- package/dist/components/Notice/notice.js +11 -5
- package/dist/components/Notice/notice.js.map +1 -1
- package/dist/components/NumberInput/number-input.d.ts +1 -1
- package/dist/components/NumberInput/number-input.d.ts.map +1 -1
- package/dist/components/NumberInput/number-input.js +17 -8
- package/dist/components/NumberInput/number-input.js.map +1 -1
- package/dist/components/OverflowIndicator/overflow-indicator.d.ts +1 -1
- package/dist/components/OverflowIndicator/overflow-indicator.d.ts.map +1 -1
- package/dist/components/OverflowIndicator/overflow-indicator.js +2 -1
- package/dist/components/OverflowIndicator/overflow-indicator.js.map +1 -1
- package/dist/components/Pagination/index.d.ts +2 -0
- package/dist/components/Pagination/index.d.ts.map +1 -0
- package/dist/components/Pagination/index.js +6 -0
- package/dist/components/Pagination/index.js.map +1 -0
- package/dist/components/Pagination/pagination.d.ts +49 -0
- package/dist/components/Pagination/pagination.d.ts.map +1 -0
- package/dist/components/Pagination/pagination.js +182 -0
- package/dist/components/Pagination/pagination.js.map +1 -0
- package/dist/components/PeoplePicker/people-picker.d.ts +3 -3
- package/dist/components/PeoplePicker/people-picker.d.ts.map +1 -1
- package/dist/components/PeoplePicker/people-picker.js +38 -36
- package/dist/components/PeoplePicker/people-picker.js.map +1 -1
- package/dist/components/PeoplePicker/person-display.d.ts +4 -0
- package/dist/components/PeoplePicker/person-display.d.ts.map +1 -1
- package/dist/components/PeoplePicker/person-display.js +3 -3
- package/dist/components/PeoplePicker/person-display.js.map +1 -1
- package/dist/components/Popover/popover.d.ts.map +1 -1
- package/dist/components/Popover/popover.js +58 -32
- package/dist/components/Popover/popover.js.map +1 -1
- package/dist/components/ProfileCard/profile-card.d.ts +11 -3
- package/dist/components/ProfileCard/profile-card.d.ts.map +1 -1
- package/dist/components/ProfileCard/profile-card.js +14 -4
- package/dist/components/ProfileCard/profile-card.js.map +1 -1
- package/dist/components/RadioGroup/radio-group.d.ts +1 -1
- package/dist/components/RadioGroup/radio-group.d.ts.map +1 -1
- package/dist/components/RadioGroup/radio-group.js +28 -3
- package/dist/components/RadioGroup/radio-group.js.map +1 -1
- package/dist/components/Rating/rating.js +3 -3
- package/dist/components/Rating/rating.js.map +1 -1
- package/dist/components/ScrollArea/scroll-area.d.ts +1 -1
- package/dist/components/ScrollArea/scroll-area.js +2 -2
- package/dist/components/ScrollArea/scroll-area.js.map +1 -1
- package/dist/components/SegmentedControl/segmented-control.d.ts +2 -2
- package/dist/components/SegmentedControl/segmented-control.d.ts.map +1 -1
- package/dist/components/SegmentedControl/segmented-control.js +23 -10
- package/dist/components/SegmentedControl/segmented-control.js.map +1 -1
- package/dist/components/Select/select.d.ts +10 -4
- package/dist/components/Select/select.d.ts.map +1 -1
- package/dist/components/Select/select.js +57 -14
- package/dist/components/Select/select.js.map +1 -1
- package/dist/components/SelectMenu/index.js +4 -2
- package/dist/components/SelectMenu/select-menu.d.ts +31 -6
- package/dist/components/SelectMenu/select-menu.d.ts.map +1 -1
- package/dist/components/SelectMenu/select-menu.js +169 -85
- package/dist/components/SelectMenu/select-menu.js.map +1 -1
- package/dist/components/Separator/separator.js +1 -1
- package/dist/components/Separator/separator.js.map +1 -1
- package/dist/components/Sheet/sheet.d.ts +1 -1
- package/dist/components/Sheet/sheet.d.ts.map +1 -1
- package/dist/components/Sheet/sheet.js +3 -2
- package/dist/components/Sheet/sheet.js.map +1 -1
- package/dist/components/Sidebar/sidebar.d.ts +2 -2
- package/dist/components/Sidebar/sidebar.d.ts.map +1 -1
- package/dist/components/Sidebar/sidebar.js +28 -10
- package/dist/components/Sidebar/sidebar.js.map +1 -1
- package/dist/components/Skeleton/skeleton.d.ts +1 -1
- package/dist/components/Skeleton/skeleton.js +2 -2
- package/dist/components/Skeleton/skeleton.js.map +1 -1
- package/dist/components/Slider/slider.d.ts +1 -1
- package/dist/components/Slider/slider.d.ts.map +1 -1
- package/dist/components/Slider/slider.js +7 -3
- package/dist/components/Slider/slider.js.map +1 -1
- package/dist/components/Steps/steps.d.ts.map +1 -1
- package/dist/components/Steps/steps.js +6 -3
- package/dist/components/Steps/steps.js.map +1 -1
- package/dist/components/Switch/switch.d.ts +2 -2
- package/dist/components/Switch/switch.d.ts.map +1 -1
- package/dist/components/Switch/switch.js +47 -9
- package/dist/components/Switch/switch.js.map +1 -1
- package/dist/components/Tabs/tabs.d.ts +8 -5
- package/dist/components/Tabs/tabs.d.ts.map +1 -1
- package/dist/components/Tabs/tabs.js +45 -33
- package/dist/components/Tabs/tabs.js.map +1 -1
- package/dist/components/Tag/tag.d.ts.map +1 -1
- package/dist/components/Tag/tag.js +20 -4
- package/dist/components/Tag/tag.js.map +1 -1
- package/dist/components/Textarea/textarea.d.ts +2 -1
- package/dist/components/Textarea/textarea.d.ts.map +1 -1
- package/dist/components/Textarea/textarea.js +24 -13
- package/dist/components/Textarea/textarea.js.map +1 -1
- package/dist/components/TimePicker/time-picker.d.ts +2 -2
- package/dist/components/TimePicker/time-picker.d.ts.map +1 -1
- package/dist/components/TimePicker/time-picker.js +23 -12
- package/dist/components/TimePicker/time-picker.js.map +1 -1
- package/dist/components/Toast/toast.d.ts +1 -1
- package/dist/components/Toast/toast.d.ts.map +1 -1
- package/dist/components/Toast/toast.js +7 -3
- package/dist/components/Toast/toast.js.map +1 -1
- package/dist/components/Tooltip/tooltip.js +1 -1
- package/dist/components/Tooltip/tooltip.js.map +1 -1
- package/dist/components/TreeView/tree-view.d.ts +1 -1
- package/dist/components/TreeView/tree-view.d.ts.map +1 -1
- package/dist/components/TreeView/tree-view.js +11 -5
- package/dist/components/TreeView/tree-view.js.map +1 -1
- package/dist/hooks/use-controllable.d.ts.map +1 -1
- package/dist/hooks/use-controllable.js +4 -2
- package/dist/hooks/use-controllable.js.map +1 -1
- package/dist/hooks/use-is-touch-device.d.ts.map +1 -1
- package/dist/hooks/use-is-touch-device.js +3 -1
- package/dist/hooks/use-is-touch-device.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -1
- package/dist/patterns/element-anatomy/item-anatomy.d.ts +2 -2
- package/dist/patterns/element-anatomy/item-anatomy.d.ts.map +1 -1
- package/dist/patterns/element-anatomy/item-anatomy.js.map +1 -1
- package/dist/patterns/overlay-surface/overlay-surface.js.map +1 -1
- package/dist/tokens/categorical-color.d.ts +1 -1
- package/dist/tokens/categorical-color.js.map +1 -1
- package/dist/tokens/uiSize/icon-size.d.ts +2 -2
- package/ds-canonical/fork/governance.lock +1 -1
- package/ds-canonical/fork/hooks/check_opacity_token_usage.sh +10 -1
- package/ds-canonical/hooks/check_audit_post_report_validator.sh +13 -0
- package/ds-canonical/hooks/check_full_story_visual_interaction_sweep.sh +4 -3
- package/ds-canonical/hooks/check_opacity_token_usage.sh +10 -1
- package/ds-canonical/hooks/check_plugin_fork_health.sh +30 -22
- package/ds-canonical/hooks/check_story_invariants.sh +17 -5
- package/ds-canonical/hooks/check_substantive_edit_approval_preflight.sh +5 -2
- package/ds-canonical/references/principle-dim-map.json +100 -0
- package/ds-canonical/references/story-baseline-registry.json +4 -3
- package/ds-canonical/skills/deep-audit-cross-codex/SKILL.md +1 -1
- package/ds-canonical/skills/design-system-audit/SKILL.md +2 -2
- package/ds-story-manifest.json +41 -18
- package/llms-full.txt +11 -1
- package/llms.txt +3 -1
- package/package.json +1 -1
- package/src/components/Alert/alert.anatomy.stories.tsx +9 -6
- package/src/components/Alert/alert.spec.md +3 -3
- package/src/components/Alert/alert.tsx +9 -1
- package/src/components/AppShell/app-shell.anatomy.stories.tsx +3 -0
- package/src/components/AppShell/app-shell.spec.md +3 -2
- package/src/components/AppShell/app-shell.tsx +1 -1
- package/src/components/AspectRatio/aspect-ratio.spec.md +1 -2
- package/src/components/AspectRatio/aspect-ratio.tsx +3 -2
- package/src/components/Avatar/avatar.principles.stories.tsx +1 -1
- package/src/components/Avatar/avatar.spec.md +10 -30
- package/src/components/Avatar/avatar.stories.tsx +1 -1
- package/src/components/Avatar/avatar.tsx +30 -16
- package/src/components/Badge/badge.anatomy.stories.tsx +2 -2
- package/src/components/Badge/badge.spec.md +5 -4
- package/src/components/Badge/badge.tsx +4 -1
- package/src/components/Breadcrumb/breadcrumb.anatomy.stories.tsx +2 -2
- package/src/components/Breadcrumb/breadcrumb.principles.stories.tsx +15 -10
- package/src/components/Breadcrumb/breadcrumb.spec.md +5 -3
- package/src/components/Breadcrumb/breadcrumb.tsx +10 -4
- package/src/components/BulkActionBar/bulk-action-bar.anatomy.stories.tsx +3 -2
- package/src/components/BulkActionBar/bulk-action-bar.spec.md +15 -14
- package/src/components/BulkActionBar/bulk-action-bar.tsx +10 -4
- package/src/components/Button/button.anatomy.stories.tsx +6 -6
- package/src/components/Button/button.spec.md +3 -2
- package/src/components/Button/button.tsx +67 -27
- package/src/components/Button/index.ts +1 -0
- package/src/components/Calendar/calendar.anatomy.stories.tsx +5 -3
- package/src/components/Calendar/calendar.principles.stories.tsx +3 -1
- package/src/components/Calendar/calendar.spec.md +9 -2
- package/src/components/Calendar/calendar.stories.tsx +3 -1
- package/src/components/Calendar/calendar.tsx +53 -19
- package/src/components/Carousel/carousel.principles.stories.tsx +1 -1
- package/src/components/Carousel/carousel.tsx +4 -1
- package/src/components/Chart/chart.spec.md +1 -1
- package/src/components/Chart/chart.tsx +2 -1
- package/src/components/Checkbox/checkbox.principles.stories.tsx +1 -1
- package/src/components/Checkbox/checkbox.spec.md +1 -1
- package/src/components/Checkbox/checkbox.tsx +41 -7
- package/src/components/Checkbox/index.ts +1 -0
- package/src/components/Chip/chip.anatomy.stories.tsx +9 -9
- package/src/components/Chip/chip.principles.stories.tsx +3 -3
- package/src/components/Chip/chip.spec.md +7 -4
- package/src/components/Chip/chip.tsx +60 -15
- package/src/components/CircularProgress/circular-progress.anatomy.stories.tsx +1 -1
- package/src/components/CircularProgress/circular-progress.principles.stories.tsx +7 -5
- package/src/components/CircularProgress/circular-progress.spec.md +2 -0
- package/src/components/CircularProgress/circular-progress.stories.tsx +3 -11
- package/src/components/Coachmark/coachmark.anatomy.stories.tsx +2 -2
- package/src/components/Coachmark/coachmark.principles.stories.tsx +1 -1
- package/src/components/Coachmark/coachmark.spec.md +3 -3
- package/src/components/Coachmark/coachmark.tsx +23 -13
- package/src/components/Combobox/combobox.anatomy.stories.tsx +4 -4
- package/src/components/Combobox/combobox.spec.md +1 -1
- package/src/components/Combobox/combobox.tsx +30 -17
- package/src/components/Command/command.principles.stories.tsx +10 -12
- package/src/components/Command/command.spec.md +5 -5
- package/src/components/Command/command.stories.tsx +4 -4
- package/src/components/Command/command.tsx +3 -2
- package/src/components/DataTable/active-editor-controller.ts +4 -3
- package/src/components/DataTable/cell-registry.tsx +8 -0
- package/src/components/DataTable/data-table-filter-panel.tsx +8 -2
- package/src/components/DataTable/data-table-interaction-layer.tsx +8 -7
- package/src/components/DataTable/data-table.anatomy.stories.tsx +4 -4
- package/src/components/DataTable/data-table.css +2 -2
- package/src/components/DataTable/data-table.spec.md +72 -115
- package/src/components/DataTable/data-table.stories.tsx +51 -3
- package/src/components/DataTable/data-table.tsx +348 -63
- package/src/components/DataTable/filter-operators.spec.md +4 -4
- package/src/components/DataTable/filter-tree.ts +192 -42
- package/src/components/DateGrid/date-grid.anatomy.stories.tsx +1 -1
- package/src/components/DateGrid/date-grid.spec.md +11 -8
- package/src/components/DateGrid/date-grid.tsx +63 -8
- package/src/components/DatePicker/date-picker.principles.stories.tsx +1 -1
- package/src/components/DatePicker/date-picker.spec.md +1 -0
- package/src/components/DatePicker/date-picker.stories.tsx +19 -0
- package/src/components/DatePicker/date-picker.tsx +52 -15
- package/src/components/DescriptionList/description-list.spec.md +1 -1
- package/src/components/DescriptionList/description-list.tsx +3 -1
- package/src/components/Dialog/dialog.spec.md +1 -1
- package/src/components/Dialog/dialog.tsx +2 -1
- package/src/components/DropdownMenu/dropdown-menu.anatomy.stories.tsx +3 -1
- package/src/components/DropdownMenu/dropdown-menu.spec.md +8 -5
- package/src/components/DropdownMenu/dropdown-menu.tsx +33 -7
- package/src/components/Empty/empty.anatomy.stories.tsx +1 -1
- package/src/components/Empty/empty.spec.md +2 -2
- package/src/components/Empty/empty.stories.tsx +4 -15
- package/src/components/Empty/empty.tsx +4 -1
- package/src/components/Field/field-controls.spec.md +2 -1
- package/src/components/Field/field-wrapper.tsx +28 -8
- package/src/components/Field/field.anatomy.stories.tsx +7 -7
- package/src/components/Field/field.principles.stories.tsx +1 -1
- package/src/components/Field/field.spec.md +9 -7
- package/src/components/Field/field.tsx +2 -1
- package/src/components/Field/form-validation.spec.md +6 -3
- package/src/components/Field/use-form-validation.ts +49 -21
- package/src/components/FieldControlGroup/field-control-group.anatomy.stories.tsx +11 -10
- package/src/components/FieldControlGroup/field-control-group.principles.stories.tsx +3 -3
- package/src/components/FieldControlGroup/field-control-group.spec.md +3 -3
- package/src/components/FieldControlGroup/field-control-group.tsx +3 -3
- package/src/components/FileItem/file-item.anatomy.stories.tsx +6 -4
- package/src/components/FileItem/file-item.tsx +3 -2
- package/src/components/FileUpload/file-upload.anatomy.stories.tsx +1 -1
- package/src/components/FileUpload/file-upload.spec.md +4 -1
- package/src/components/FileUpload/file-upload.tsx +36 -10
- package/src/components/FileViewer/can-render-image.ts +15 -0
- package/src/components/FileViewer/file-viewer.anatomy.stories.tsx +6 -4
- package/src/components/FileViewer/file-viewer.spec.md +2 -2
- package/src/components/FileViewer/file-viewer.tsx +47 -21
- package/src/components/FileViewer/image-renderer.tsx +3 -8
- package/src/components/HoverCard/hover-card.spec.md +1 -1
- package/src/components/HoverCard/hover-card.tsx +3 -1
- package/src/components/Input/input.anatomy.stories.tsx +4 -4
- package/src/components/Input/input.principles.stories.tsx +1 -1
- package/src/components/Input/input.spec.md +1 -1
- package/src/components/Input/input.stories.tsx +11 -5
- package/src/components/Input/input.tsx +11 -9
- package/src/components/LinkInput/link-input.anatomy.stories.tsx +2 -0
- package/src/components/LinkInput/link-input.tsx +49 -34
- package/src/components/Menu/menu-item.anatomy.stories.tsx +1 -1
- package/src/components/Menu/menu-item.spec.md +4 -3
- package/src/components/Menu/menu-item.tsx +15 -4
- package/src/components/Notice/notice.anatomy.stories.tsx +10 -5
- package/src/components/Notice/notice.spec.md +4 -3
- package/src/components/Notice/notice.tsx +22 -5
- package/src/components/NumberInput/number-input.anatomy.stories.tsx +5 -4
- package/src/components/NumberInput/number-input.principles.stories.tsx +2 -1
- package/src/components/NumberInput/number-input.spec.md +2 -1
- package/src/components/NumberInput/number-input.tsx +25 -9
- package/src/components/OverflowIndicator/overflow-indicator.anatomy.stories.tsx +3 -3
- package/src/components/OverflowIndicator/overflow-indicator.spec.md +1 -1
- package/src/components/OverflowIndicator/overflow-indicator.tsx +2 -1
- package/src/components/Pagination/index.ts +5 -0
- package/src/components/Pagination/pagination.anatomy.stories.tsx +143 -0
- package/src/components/Pagination/pagination.principles.stories.tsx +81 -0
- package/src/components/Pagination/pagination.spec.md +150 -0
- package/src/components/Pagination/pagination.stories.tsx +98 -0
- package/src/components/Pagination/pagination.tsx +263 -0
- package/src/components/PeoplePicker/people-picker.anatomy.stories.tsx +1 -1
- package/src/components/PeoplePicker/people-picker.spec.md +4 -4
- package/src/components/PeoplePicker/people-picker.stories.tsx +1 -1
- package/src/components/PeoplePicker/people-picker.tsx +65 -55
- package/src/components/PeoplePicker/person-display.tsx +10 -4
- package/src/components/Popover/popover.anatomy.stories.tsx +2 -2
- package/src/components/Popover/popover.spec.md +13 -14
- package/src/components/Popover/popover.tsx +88 -39
- package/src/components/ProfileCard/profile-card.anatomy.stories.tsx +4 -4
- package/src/components/ProfileCard/profile-card.principles.stories.tsx +2 -2
- package/src/components/ProfileCard/profile-card.spec.md +1 -2
- package/src/components/ProfileCard/profile-card.stories.tsx +1 -1
- package/src/components/ProfileCard/profile-card.tsx +29 -8
- package/src/components/RadioGroup/radio-group.principles.stories.tsx +1 -1
- package/src/components/RadioGroup/radio-group.spec.md +2 -2
- package/src/components/RadioGroup/radio-group.stories.tsx +3 -1
- package/src/components/RadioGroup/radio-group.tsx +37 -3
- package/src/components/Rating/rating.anatomy.stories.tsx +11 -8
- package/src/components/Rating/rating.principles.stories.tsx +1 -1
- package/src/components/Rating/rating.spec.md +2 -2
- package/src/components/Rating/rating.stories.tsx +1 -1
- package/src/components/Rating/rating.tsx +3 -3
- package/src/components/ScrollArea/scroll-area.spec.md +1 -0
- package/src/components/ScrollArea/scroll-area.tsx +1 -1
- package/src/components/SegmentedControl/segmented-control.anatomy.stories.tsx +3 -3
- package/src/components/SegmentedControl/segmented-control.spec.md +7 -3
- package/src/components/SegmentedControl/segmented-control.tsx +29 -11
- package/src/components/Select/select.anatomy.stories.tsx +2 -2
- package/src/components/Select/select.spec.md +3 -2
- package/src/components/Select/select.tsx +62 -19
- package/src/components/SelectMenu/select-menu.anatomy.stories.tsx +1 -1
- package/src/components/SelectMenu/select-menu.principles.stories.tsx +1 -1
- package/src/components/SelectMenu/select-menu.spec.md +12 -8
- package/src/components/SelectMenu/select-menu.tsx +176 -20
- package/src/components/SelectionControl/selection-item.anatomy.stories.tsx +1 -1
- package/src/components/SelectionControl/selection-item.principles.stories.tsx +2 -2
- package/src/components/SelectionControl/selection-item.spec.md +2 -1
- package/src/components/SelectionControl/selection-item.stories.tsx +1 -1
- package/src/components/Separator/separator.anatomy.stories.tsx +2 -2
- package/src/components/Separator/separator.tsx +1 -1
- package/src/components/Sheet/sheet.spec.md +2 -1
- package/src/components/Sheet/sheet.tsx +4 -2
- package/src/components/Sidebar/sidebar.anatomy.stories.tsx +1 -1
- package/src/components/Sidebar/sidebar.spec.md +2 -1
- package/src/components/Sidebar/sidebar.stories.tsx +6 -4
- package/src/components/Sidebar/sidebar.tsx +41 -13
- package/src/components/Skeleton/skeleton.principles.stories.tsx +1 -1
- package/src/components/Skeleton/skeleton.spec.md +1 -2
- package/src/components/Skeleton/skeleton.tsx +1 -1
- package/src/components/Slider/slider.anatomy.stories.tsx +4 -4
- package/src/components/Slider/slider.spec.md +19 -85
- package/src/components/Slider/slider.tsx +13 -5
- package/src/components/Steps/steps.anatomy.stories.tsx +1 -1
- package/src/components/Steps/steps.spec.md +6 -4
- package/src/components/Steps/steps.tsx +11 -3
- package/src/components/Switch/switch.spec.md +4 -3
- package/src/components/Switch/switch.tsx +59 -10
- package/src/components/Tabs/tabs.anatomy.stories.tsx +7 -6
- package/src/components/Tabs/tabs.principles.stories.tsx +5 -3
- package/src/components/Tabs/tabs.spec.md +15 -48
- package/src/components/Tabs/tabs.tsx +70 -34
- package/src/components/Tag/tag.principles.stories.tsx +2 -2
- package/src/components/Tag/tag.spec.md +11 -43
- package/src/components/Tag/tag.tsx +30 -4
- package/src/components/Textarea/textarea.principles.stories.tsx +2 -1
- package/src/components/Textarea/textarea.spec.md +2 -1
- package/src/components/Textarea/textarea.tsx +20 -10
- package/src/components/TimePicker/time-picker.anatomy.stories.tsx +2 -1
- package/src/components/TimePicker/time-picker.spec.md +2 -2
- package/src/components/TimePicker/time-picker.tsx +35 -16
- package/src/components/Toast/toast.anatomy.stories.tsx +5 -6
- package/src/components/Toast/toast.principles.stories.tsx +10 -11
- package/src/components/Toast/toast.spec.md +5 -5
- package/src/components/Toast/toast.stories.tsx +5 -1
- package/src/components/Toast/toast.tsx +13 -7
- package/src/components/Tooltip/tooltip.spec.md +3 -3
- package/src/components/Tooltip/tooltip.tsx +1 -1
- package/src/components/TreeView/tree-view.anatomy.stories.tsx +6 -1
- package/src/components/TreeView/tree-view.principles.stories.tsx +2 -2
- package/src/components/TreeView/tree-view.spec.md +2 -2
- package/src/components/TreeView/tree-view.tsx +27 -5
- package/src/hooks/use-controllable.ts +7 -2
- package/src/hooks/use-is-touch-device.ts +8 -1
- package/src/index.ts +2 -1
- package/src/patterns/element-anatomy/element-anatomy.spec.md +3 -3
- package/src/patterns/element-anatomy/inline-action.spec.md +1 -1
- package/src/patterns/element-anatomy/item-anatomy.spec.md +5 -14
- package/src/patterns/element-anatomy/item-anatomy.stories.tsx +1 -0
- package/src/patterns/element-anatomy/item-anatomy.tsx +5 -6
- package/src/patterns/header-canonical/header-canonical.css +1 -2
- package/src/patterns/header-canonical/header-canonical.spec.md +5 -5
- package/src/patterns/horizontal-overflow/horizontal-overflow.spec.md +5 -4
- package/src/patterns/horizontal-overflow/horizontal-overflow.stories.tsx +137 -0
- package/src/patterns/overlay-surface/overlay-surface.spec.md +8 -7
- package/src/patterns/overlay-surface/overlay-surface.stories.tsx +116 -0
- package/src/patterns/overlay-surface/overlay-surface.tsx +5 -5
- package/src/patterns/resize-handle/resize-handle.spec.md +1 -1
- package/src/tokens/categorical-color.ts +1 -1
- package/src/tokens/color/color.spec.md +11 -13
- package/src/tokens/color/color.stories.tsx +1 -1
- package/src/tokens/color/primitives.css +7 -5
- package/src/tokens/color/semantic.css +8 -2
- package/src/tokens/density/density.spec.md +2 -0
- package/src/tokens/elevation/elevation.spec.md +1 -1
- package/src/tokens/layoutSpace/layoutSpace.spec.md +2 -1
- package/src/tokens/layoutSpace/layoutSpace.stories.tsx +3 -3
- package/src/tokens/motion/motion.spec.md +1 -1
- package/src/tokens/motion/motion.stories.tsx +142 -0
- package/src/tokens/opacity/opacity.spec.md +1 -1
- package/src/tokens/opacity/opacity.stories.tsx +7 -11
- package/src/tokens/radius/radius.spec.md +1 -1
- package/src/tokens/typography/typography.css +1 -1
- package/src/tokens/typography/typography.spec.md +1 -1
- package/src/tokens/uiSize/icon-size.ts +2 -2
- package/src/tokens/uiSize/uiSize.spec.md +12 -11
- package/src/tokens/uiSize/uiSize.stories.tsx +103 -0
- package/src/tokens/utility-registry.json +5 -4
- package/dist/node_modules/react-hook-form/dist/index.esm.js +0 -1950
- package/dist/node_modules/react-hook-form/dist/index.esm.js.map +0 -1
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
- **加 `column.meta.includeTime: boolean` 旗標** — 對齊 Notion idiom,不另設 datetime column type
|
|
12
12
|
- **加 ValueShape `datetime_single` / `datetime_range`** — `includeTime=true` 時切到這兩個 shape
|
|
13
13
|
- **datetime 不另設 relative shape** — `is_relative` 維持 `date_relative`(relative 邏輯不變;12-shape 表與 code `ValueShape` union 皆無 `datetime_relative`)
|
|
14
|
-
- **filter 比對精度**:`includeTime=true` 走 ms
|
|
15
|
-
- **不新增 `time-only` column type**(業界沒人做,罕見場景)
|
|
14
|
+
- **filter 比對精度**:`includeTime=true` 走 ms 精度;default day 級(`startOfDay` 截斷,含 `is` 日期比對 — 2026-07-04 Q6 實作於 `filter-tree.ts` `datePrecision`)(避開 Airtable 著名地雷:filter 忽略 time)
|
|
15
|
+
- **不新增 `time-only` column type**(業界沒人做,罕見場景)—— 後續 Phase C(2026-05-05)已補 `'time'` columnType 供 cell render(`<TimePicker>`);advanced filter 暫 reuse `DATE_OPS`,`time_*` ValueShape 整合留 Phase D+(見 `filter-operators.ts` registry 註解)
|
|
16
16
|
|
|
17
17
|
## 1. ValueShape canonical(12 種)
|
|
18
18
|
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
| `date_relative` | `<Select>` 預設選項 | 相對日期 |
|
|
29
29
|
| `datetime_single` | `<DatePicker showTime>` | 特定 datetime(`includeTime=true`) |
|
|
30
30
|
| `datetime_range` | `<DatePickerRange showTime>` | datetime 介於(`includeTime=true`) |
|
|
31
|
-
| `select_multi` | `<
|
|
31
|
+
| `select_multi` | `<Combobox>` from `column.meta.options` | 多選(`select.is` 也走這 — OR 語意) |
|
|
32
32
|
| `person_multi` | `<PeoplePicker multiple>` | 多人員(`person.is` 也走這 — OR 語意) |
|
|
33
33
|
| `select_single` | `<Select>` from `column.meta.options` | (預留)罕用 |
|
|
34
34
|
| `person_single` | `<PeoplePicker>` 單選 | (預留)罕用 |
|
|
@@ -133,7 +133,7 @@
|
|
|
133
133
|
| 1-to-1 比對 | ✓ `is` / `is_not`(picker 多選 OR-語意) | ✗ 不適用(陣列比單值 ambiguous) |
|
|
134
134
|
| 集合運算 | ✗ 不適用 | ✓ `has_any_of` / `has_all_of` / `has_none_of` |
|
|
135
135
|
|
|
136
|
-
差異**根源於 cell 資料形狀**,不是 picker 不同。Value picker 兩者都共用 `
|
|
136
|
+
差異**根源於 cell 資料形狀**,不是 picker 不同。Value picker 兩者都共用 `<Combobox>` DS 既有元件(code 真值 `data-table-filter-panel.tsx` select_multi case)。
|
|
137
137
|
|
|
138
138
|
### `person`(4 ops — 跟 select 同邏輯)
|
|
139
139
|
| op key | 中 | en | valueShape | CU | AT | NT |
|
|
@@ -53,6 +53,11 @@ export interface FilterCondition {
|
|
|
53
53
|
op: string
|
|
54
54
|
/** 依 ValueShape 解讀的值 */
|
|
55
55
|
value: unknown
|
|
56
|
+
/** Date 欄位比對精度(2026-07-04 Q6 拍板,兌現 spec「includeTime」雙精度契約):
|
|
57
|
+
* panel 建 condition 時依 column meta 寫入 — includeTime=true → 'ms',否則 'day'
|
|
58
|
+
* (AG Grid midnight-truncate / MUI X setHours(0,0,0,0) 共識;避 Airtable 邊界漏抓地雷)。
|
|
59
|
+
* 非 date 欄位 / 舊 tree 無此欄 → undefined(date ops fallback 'day' 預設)。 */
|
|
60
|
+
datePrecision?: 'day' | 'ms'
|
|
56
61
|
}
|
|
57
62
|
|
|
58
63
|
export interface FilterGroup {
|
|
@@ -105,13 +110,21 @@ function isConditionComplete(c: FilterCondition): boolean {
|
|
|
105
110
|
return c.value !== '' && c.value !== null && c.value !== undefined
|
|
106
111
|
}
|
|
107
112
|
|
|
108
|
-
/** loose lookup — 我們不知 columnType,試所有 type
|
|
113
|
+
/** loose lookup — 我們不知 columnType,試所有 type。
|
|
114
|
+
* D3 perf(2026-07-06):module-level Map cache — OPERATOR_REGISTRY 是 module 常數,
|
|
115
|
+
* 同一 op key 查詢結果永不變;原每 call 線性掃全 registry,而 evaluateTree 熱路徑
|
|
116
|
+
* per-row 呼叫時(10k rows × N conditions)放大成數十萬次比較,cache 後 O(1)。 */
|
|
117
|
+
const opSpecLooseCache = new Map<string, OperatorSpec | null>()
|
|
109
118
|
function getOperatorSpecLoose(op: string): OperatorSpec | null {
|
|
119
|
+
const cached = opSpecLooseCache.get(op)
|
|
120
|
+
if (cached !== undefined) return cached
|
|
121
|
+
let found: OperatorSpec | null = null
|
|
110
122
|
for (const list of Object.values(OPERATOR_REGISTRY)) {
|
|
111
|
-
const
|
|
112
|
-
if (
|
|
123
|
+
const hit = list.find((o) => o.op === op)
|
|
124
|
+
if (hit) { found = hit; break }
|
|
113
125
|
}
|
|
114
|
-
|
|
126
|
+
opSpecLooseCache.set(op, found)
|
|
127
|
+
return found
|
|
115
128
|
}
|
|
116
129
|
|
|
117
130
|
// ── Deep-equal compare(refresh icon 顯示判定用) ─────────────────────────
|
|
@@ -142,6 +155,68 @@ function isConditionEqual(a: FilterCondition, b: FilterCondition): boolean {
|
|
|
142
155
|
|
|
143
156
|
// ── Tree Evaluation(globalFilter approach)──────────────────────────────
|
|
144
157
|
|
|
158
|
+
// ── D3 perf(2026-07-06):per-condition compile-once cache ────────────────
|
|
159
|
+
// TanStack globalFilterFn 逐 row 呼叫 evaluateTree,原實作每 row 重做「跨全 rows 不變」
|
|
160
|
+
// 的常數工作:isConditionComplete(operator registry 掃描)、dateMs(filterValue) 重 parse、
|
|
161
|
+
// filterValue 重 toLowerCase、is_relative 重算時間區間 — 10k rows × N conditions 全額放大。
|
|
162
|
+
// Fix:以 condition 物件為 key 的 WeakMap 快取 filterValue 側預解析結果,row loop 只轉
|
|
163
|
+
// cellValue 側。Panel 更新走 immutable `{ ...c, ...patch }`(新物件 = 新 cache entry),
|
|
164
|
+
// 舊 entry 隨舊 condition 物件 GC — cache 無失效問題。
|
|
165
|
+
interface PreparedCondition {
|
|
166
|
+
/** isConditionComplete 結果(registry 掃描一次,取代原 per-row filter 掃描) */
|
|
167
|
+
complete: boolean
|
|
168
|
+
/** filterValue 側是否 Person(物件或含 Person 的陣列) */
|
|
169
|
+
filterIsPerson: boolean
|
|
170
|
+
/** person 比對 ops 的 filter 側 id array(cellValue 是 Person 時也走 person path,故照 op 準備) */
|
|
171
|
+
filterPersonIds: string[] | null
|
|
172
|
+
/** 文字 ops 用:String(filterValue).toLowerCase() */
|
|
173
|
+
filterTextLower: string | null
|
|
174
|
+
/** is / is_not 陣列(多值 OR 語意)逐元素 lowercase */
|
|
175
|
+
filterTextLowerArr: string[] | null
|
|
176
|
+
/** 單值 date ops(is_before 家族;is / is_not with datePrecision)— datePrecision 已套 */
|
|
177
|
+
filterDateMs: number | null
|
|
178
|
+
/** is_between 兩端(空端 = ±Infinity,語意同原 inline 邏輯) */
|
|
179
|
+
filterDateStart: number | null
|
|
180
|
+
filterDateEnd: number | null
|
|
181
|
+
/** is_relative:快取區間 + 計算當日 stamp(跨日自動重算 — 所有 relative 區間都是
|
|
182
|
+
* calendar day 的純函數,同日內恆定,見 localDayKey) */
|
|
183
|
+
relRange: [number, number] | null
|
|
184
|
+
relDayKey: number
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const PERSON_CAPABLE_OPS = new Set(['is', 'is_not', 'has_any_of', 'has_all_of', 'has_none_of'])
|
|
188
|
+
const DATE_SINGLE_OPS = new Set(['is_before', 'is_after', 'is_on_or_before', 'is_on_or_after'])
|
|
189
|
+
const TEXT_OPS = new Set(['contains', 'does_not_contain', 'starts_with', 'ends_with', 'is', 'is_not'])
|
|
190
|
+
|
|
191
|
+
const preparedCache = new WeakMap<FilterCondition, PreparedCondition>()
|
|
192
|
+
|
|
193
|
+
function prepareCondition(c: FilterCondition): PreparedCondition {
|
|
194
|
+
const hit = preparedCache.get(c)
|
|
195
|
+
if (hit) return hit
|
|
196
|
+
const fv = c.value
|
|
197
|
+
const isBetweenPair = c.op === 'is_between' && Array.isArray(fv) && fv.length === 2
|
|
198
|
+
const prepared: PreparedCondition = {
|
|
199
|
+
complete: isConditionComplete(c),
|
|
200
|
+
filterIsPerson: isPersonObject(fv) || (Array.isArray(fv) && fv.some(isPersonObject)),
|
|
201
|
+
filterPersonIds: PERSON_CAPABLE_OPS.has(c.op)
|
|
202
|
+
? (Array.isArray(fv) ? fv.map(personId) : [personId(fv)])
|
|
203
|
+
: null,
|
|
204
|
+
filterTextLower: TEXT_OPS.has(c.op) ? String(fv).toLowerCase() : null,
|
|
205
|
+
filterTextLowerArr:
|
|
206
|
+
(c.op === 'is' || c.op === 'is_not') && Array.isArray(fv) ? fv.map((v) => String(v).toLowerCase()) : null,
|
|
207
|
+
filterDateMs:
|
|
208
|
+
DATE_SINGLE_OPS.has(c.op) || ((c.op === 'is' || c.op === 'is_not') && c.datePrecision !== undefined)
|
|
209
|
+
? toDateMs(fv, c.datePrecision)
|
|
210
|
+
: null,
|
|
211
|
+
filterDateStart: isBetweenPair ? (fv[0] ? toDateMs(fv[0], c.datePrecision) : -Infinity) : null,
|
|
212
|
+
filterDateEnd: isBetweenPair ? (fv[1] ? toDateMs(fv[1], c.datePrecision) : Infinity) : null,
|
|
213
|
+
relRange: null,
|
|
214
|
+
relDayKey: 0,
|
|
215
|
+
}
|
|
216
|
+
preparedCache.set(c, prepared)
|
|
217
|
+
return prepared
|
|
218
|
+
}
|
|
219
|
+
|
|
145
220
|
/**
|
|
146
221
|
* 整棵 FilterTree 對 row 求 boolean。
|
|
147
222
|
* 配合 useReactTable `globalFilterFn`:
|
|
@@ -159,33 +234,54 @@ export function evaluateTree(tree: FilterTree, row: any): boolean {
|
|
|
159
234
|
if (tree.children.length === 0) return true
|
|
160
235
|
|
|
161
236
|
if (tree.mode === 'flat') {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const results = completed.map((c) => evaluateCondition(c, row))
|
|
165
|
-
return tree.conjunction === 'and' ? results.every(Boolean) : results.some(Boolean)
|
|
237
|
+
// null = 無 complete condition → pass-through(語意同原 completed.length === 0 分支)
|
|
238
|
+
return evaluateConditionsJoined(tree.children, tree.conjunction, row) ?? true
|
|
166
239
|
}
|
|
167
240
|
|
|
168
|
-
// nested
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
241
|
+
// nested — group 沒任何 complete condition 的視為 pass-through(不參與 conjunction),
|
|
242
|
+
// 語意同原 meaningful filter
|
|
243
|
+
let sawMeaningful = false
|
|
244
|
+
for (const g of tree.children) {
|
|
245
|
+
const r = evaluateConditionsJoined(g.children, g.conjunction, row)
|
|
246
|
+
if (r === null) continue
|
|
247
|
+
sawMeaningful = true
|
|
248
|
+
if (tree.conjunction === 'and') {
|
|
249
|
+
if (!r) return false
|
|
250
|
+
} else if (r) {
|
|
251
|
+
return true
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (!sawMeaningful) return true
|
|
255
|
+
return tree.conjunction === 'and'
|
|
174
256
|
}
|
|
175
257
|
|
|
258
|
+
/** 一組 conditions 依 conjunction 求 boolean;null = 組內無 complete condition(pass-through)。
|
|
259
|
+
* D3 perf:prepared.complete 取代原 per-row `children.filter(isConditionComplete)`
|
|
260
|
+
* (registry 掃描 + filter/map 兩個中間陣列 alloc);and/or 短路 — 條件是 pure predicate
|
|
261
|
+
* 無副作用,求值順序 / 省略不影響結果。 */
|
|
176
262
|
// any-allow: row-generic
|
|
177
|
-
function
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
263
|
+
function evaluateConditionsJoined(children: FilterCondition[], conjunction: Conjunction, row: any): boolean | null {
|
|
264
|
+
let sawComplete = false
|
|
265
|
+
for (const c of children) {
|
|
266
|
+
const prepared = prepareCondition(c)
|
|
267
|
+
if (!prepared.complete) continue
|
|
268
|
+
sawComplete = true
|
|
269
|
+
const matched = evaluateCondition(c, prepared, row)
|
|
270
|
+
if (conjunction === 'and') {
|
|
271
|
+
if (!matched) return false
|
|
272
|
+
} else if (matched) {
|
|
273
|
+
return true
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if (!sawComplete) return null
|
|
277
|
+
return conjunction === 'and'
|
|
182
278
|
}
|
|
183
279
|
|
|
184
280
|
// any-allow: row-generic
|
|
185
|
-
function evaluateCondition(cond: FilterCondition, row: any): boolean {
|
|
281
|
+
function evaluateCondition(cond: FilterCondition, prepared: PreparedCondition, row: any): boolean {
|
|
186
282
|
if (!cond.field || !cond.op) return true
|
|
187
283
|
const cellValue = row?.[cond.field]
|
|
188
|
-
return matchOperator(cond.op, cellValue, cond.value)
|
|
284
|
+
return matchOperator(cond.op, cellValue, cond.value, cond.datePrecision, prepared)
|
|
189
285
|
}
|
|
190
286
|
|
|
191
287
|
// PersonValue identity helper(2026-05-07):
|
|
@@ -200,8 +296,24 @@ function isPersonObject(v: unknown): v is { name: string } {
|
|
|
200
296
|
return !!v && typeof v === 'object' && 'name' in v
|
|
201
297
|
}
|
|
202
298
|
|
|
299
|
+
// Q6(2026-07-04):date 比對精度 helper — 'ms' 全精度;其餘(含 undefined)day 級
|
|
300
|
+
// (本地時區 startOfDay 截斷,對齊 AG Grid / MUI X date 欄 midnight 慣例)。
|
|
301
|
+
// D3 perf(2026-07-06):自 matchOperator 內 closure 提升 top-level — prepareCondition
|
|
302
|
+
// (filterValue 側,每 condition 一次)與 matchOperator(cellValue 側,每 row)共用同一實作。
|
|
303
|
+
function toDateMs(v: unknown, datePrecision?: 'day' | 'ms'): number {
|
|
304
|
+
const d = new Date(String(v))
|
|
305
|
+
if (Number.isNaN(d.getTime())) return NaN
|
|
306
|
+
return datePrecision === 'ms' ? d.getTime() : startOfDay(d).getTime()
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/** 本地日曆日 stamp(yyyymmdd)— is_relative 快取跨日失效判定(每 row 1 個 Date alloc,
|
|
310
|
+
* 取代原 relativeKeyToRange 每 row 4-10 個 Date alloc + 區間重算) */
|
|
311
|
+
function localDayKey(d: Date): number {
|
|
312
|
+
return d.getFullYear() * 10000 + (d.getMonth() + 1) * 100 + d.getDate()
|
|
313
|
+
}
|
|
314
|
+
|
|
203
315
|
// code-quality-allow: long-function — 13-operator switch dispatch table,table-driven 重構會把 op-specific guards 拆出反增 indirection
|
|
204
|
-
function matchOperator(op: string, cellValue: unknown, filterValue: unknown): boolean {
|
|
316
|
+
function matchOperator(op: string, cellValue: unknown, filterValue: unknown, datePrecision?: 'day' | 'ms', prepared?: PreparedCondition): boolean {
|
|
205
317
|
// 不需 value 的 op
|
|
206
318
|
switch (op) {
|
|
207
319
|
case 'is_set': return cellValue !== null && cellValue !== undefined && cellValue !== ''
|
|
@@ -216,11 +328,14 @@ function matchOperator(op: string, cellValue: unknown, filterValue: unknown): bo
|
|
|
216
328
|
|
|
217
329
|
// Person-aware specialization(person_single / person_multi)— 物件透過 name 比對。
|
|
218
330
|
// 偵測:cellValue 或 filterValue 任一是 Person object(或包 Person 的陣列)→ 走 personId path。
|
|
331
|
+
// D3 perf:filterValue 側偵測 + id array 消費 prepared 快取(cellValue 側必然 per-row)。
|
|
219
332
|
const cellIsPerson = isPersonObject(cellValue) || (Array.isArray(cellValue) && cellValue.some(isPersonObject))
|
|
220
|
-
const filterIsPerson =
|
|
333
|
+
const filterIsPerson = prepared
|
|
334
|
+
? prepared.filterIsPerson
|
|
335
|
+
: isPersonObject(filterValue) || (Array.isArray(filterValue) && filterValue.some(isPersonObject))
|
|
221
336
|
if (cellIsPerson || filterIsPerson) {
|
|
222
337
|
const cellIds = Array.isArray(cellValue) ? cellValue.map(personId) : [personId(cellValue)]
|
|
223
|
-
const filterIds = Array.isArray(filterValue) ? filterValue.map(personId) : [personId(filterValue)]
|
|
338
|
+
const filterIds = prepared?.filterPersonIds ?? (Array.isArray(filterValue) ? filterValue.map(personId) : [personId(filterValue)])
|
|
224
339
|
switch (op) {
|
|
225
340
|
case 'is': return cellIds.length === 1 && filterIds.length >= 1 && filterIds.includes(cellIds[0])
|
|
226
341
|
case 'is_not': return !(cellIds.length === 1 && filterIds.length >= 1 && filterIds.includes(cellIds[0]))
|
|
@@ -231,9 +346,12 @@ function matchOperator(op: string, cellValue: unknown, filterValue: unknown): bo
|
|
|
231
346
|
}
|
|
232
347
|
}
|
|
233
348
|
|
|
349
|
+
// (date 精度 helper 已提升為 top-level `toDateMs`;文字 ops 的 filterValue lowercase 消費
|
|
350
|
+
// prepared 快取,prepared 缺席 = deprecated dataTableFilterMatch 路徑 inline fallback — D3 perf)
|
|
351
|
+
|
|
234
352
|
switch (op) {
|
|
235
|
-
case 'contains': return String(cellValue ?? '').toLowerCase().includes(String(filterValue).toLowerCase())
|
|
236
|
-
case 'does_not_contain': return !String(cellValue ?? '').toLowerCase().includes(String(filterValue).toLowerCase())
|
|
353
|
+
case 'contains': return String(cellValue ?? '').toLowerCase().includes(prepared?.filterTextLower ?? String(filterValue).toLowerCase())
|
|
354
|
+
case 'does_not_contain': return !String(cellValue ?? '').toLowerCase().includes(prepared?.filterTextLower ?? String(filterValue).toLowerCase())
|
|
237
355
|
// 2026-05-12 Round 6 fix(user 抓 Roadmap 進階篩選「全部」結果空)— impl-vs-spec drift。
|
|
238
356
|
// Per `filter-operators.spec.md` L116「is 直接接受多值,不另設 is_any_of」+ L103-116
|
|
239
357
|
// select(is/is_not + select_multi ValueShape OR 語意)+ L31-32(select.is / person.is 都走
|
|
@@ -241,15 +359,33 @@ function matchOperator(op: string, cellValue: unknown, filterValue: unknown): bo
|
|
|
241
359
|
// array(全選 options)→ String(array)= "v1,v2" → 永遠 != single cellValue → 結果空。
|
|
242
360
|
// Fix:加 array handling,走 OR 語意:any → match;is_not → every-not-match。
|
|
243
361
|
case 'is':
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
362
|
+
// Q6:date 欄位(datePrecision 有值)走日期比對非字串 — 「2026-06-01」與「2026-06-01 14:30」
|
|
363
|
+
// 在 day 精度下相等(原字串小寫相等連同日不同格式都漏)
|
|
364
|
+
if (datePrecision) {
|
|
365
|
+
const a = toDateMs(cellValue, datePrecision)
|
|
366
|
+
const b = prepared?.filterDateMs ?? toDateMs(filterValue, datePrecision)
|
|
367
|
+
return !Number.isNaN(a) && a === b
|
|
368
|
+
}
|
|
369
|
+
if (Array.isArray(filterValue)) {
|
|
370
|
+
const cellLower = String(cellValue ?? '').toLowerCase()
|
|
371
|
+
const filterLowers = prepared?.filterTextLowerArr ?? filterValue.map((v) => String(v).toLowerCase())
|
|
372
|
+
return filterLowers.some((v) => cellLower === v)
|
|
373
|
+
}
|
|
374
|
+
return String(cellValue ?? '').toLowerCase() === (prepared?.filterTextLower ?? String(filterValue).toLowerCase())
|
|
247
375
|
case 'is_not':
|
|
248
|
-
if (
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
376
|
+
if (datePrecision) {
|
|
377
|
+
const a = toDateMs(cellValue, datePrecision)
|
|
378
|
+
const b = prepared?.filterDateMs ?? toDateMs(filterValue, datePrecision)
|
|
379
|
+
return Number.isNaN(a) || a !== b
|
|
380
|
+
}
|
|
381
|
+
if (Array.isArray(filterValue)) {
|
|
382
|
+
const cellLower = String(cellValue ?? '').toLowerCase()
|
|
383
|
+
const filterLowers = prepared?.filterTextLowerArr ?? filterValue.map((v) => String(v).toLowerCase())
|
|
384
|
+
return filterLowers.every((v) => cellLower !== v)
|
|
385
|
+
}
|
|
386
|
+
return String(cellValue ?? '').toLowerCase() !== (prepared?.filterTextLower ?? String(filterValue).toLowerCase())
|
|
387
|
+
case 'starts_with': return String(cellValue ?? '').toLowerCase().startsWith(prepared?.filterTextLower ?? String(filterValue).toLowerCase())
|
|
388
|
+
case 'ends_with': return String(cellValue ?? '').toLowerCase().endsWith(prepared?.filterTextLower ?? String(filterValue).toLowerCase())
|
|
253
389
|
|
|
254
390
|
case 'equals': return Number(cellValue) === Number(filterValue)
|
|
255
391
|
case 'not_equals': return Number(cellValue) !== Number(filterValue)
|
|
@@ -258,20 +394,34 @@ function matchOperator(op: string, cellValue: unknown, filterValue: unknown): bo
|
|
|
258
394
|
case 'lt': return Number(cellValue) < Number(filterValue)
|
|
259
395
|
case 'lte': return Number(cellValue) <= Number(filterValue)
|
|
260
396
|
|
|
261
|
-
case 'is_before': return
|
|
262
|
-
case 'is_after': return
|
|
263
|
-
case 'is_on_or_before': return
|
|
264
|
-
case 'is_on_or_after': return
|
|
397
|
+
case 'is_before': return toDateMs(cellValue, datePrecision) < (prepared?.filterDateMs ?? toDateMs(filterValue, datePrecision))
|
|
398
|
+
case 'is_after': return toDateMs(cellValue, datePrecision) > (prepared?.filterDateMs ?? toDateMs(filterValue, datePrecision))
|
|
399
|
+
case 'is_on_or_before': return toDateMs(cellValue, datePrecision) <= (prepared?.filterDateMs ?? toDateMs(filterValue, datePrecision))
|
|
400
|
+
case 'is_on_or_after': return toDateMs(cellValue, datePrecision) >= (prepared?.filterDateMs ?? toDateMs(filterValue, datePrecision))
|
|
265
401
|
case 'is_between': {
|
|
266
402
|
if (!Array.isArray(filterValue) || filterValue.length !== 2) return true
|
|
267
|
-
const cv =
|
|
268
|
-
const start = filterValue[0] ?
|
|
269
|
-
const end = filterValue[1] ?
|
|
403
|
+
const cv = toDateMs(cellValue, datePrecision)
|
|
404
|
+
const start = prepared?.filterDateStart ?? (filterValue[0] ? toDateMs(filterValue[0], datePrecision) : -Infinity)
|
|
405
|
+
const end = prepared?.filterDateEnd ?? (filterValue[1] ? toDateMs(filterValue[1], datePrecision) : Infinity)
|
|
270
406
|
return cv >= start && cv <= end
|
|
271
407
|
}
|
|
272
408
|
case 'is_relative': {
|
|
273
409
|
// Phase D 完整實作 — relative key → time range → in-range test
|
|
274
|
-
|
|
410
|
+
// D3 perf:relativeKeyToRange 所有輸出都是 calendar day 的純函數(today / this_week /
|
|
411
|
+
// past_7_days… 全是日界),同日內恆定 → 以 localDayKey 判跨日,同日重用快取區間
|
|
412
|
+
// (原每 row 重算 4-10 個 Date alloc)。
|
|
413
|
+
let range: [number, number] | null
|
|
414
|
+
if (prepared) {
|
|
415
|
+
const now = new Date()
|
|
416
|
+
const dayKey = localDayKey(now)
|
|
417
|
+
if (prepared.relDayKey !== dayKey) {
|
|
418
|
+
prepared.relRange = relativeKeyToRange(String(filterValue), now)
|
|
419
|
+
prepared.relDayKey = dayKey
|
|
420
|
+
}
|
|
421
|
+
range = prepared.relRange
|
|
422
|
+
} else {
|
|
423
|
+
range = relativeKeyToRange(String(filterValue))
|
|
424
|
+
}
|
|
275
425
|
if (!range) return true // unknown key,pass-through
|
|
276
426
|
const cv = new Date(String(cellValue)).getTime()
|
|
277
427
|
if (Number.isNaN(cv)) return false // invalid cellValue → 不命中
|
|
@@ -133,7 +133,7 @@ export const Overview = {
|
|
|
133
133
|
</thead>
|
|
134
134
|
<tbody>
|
|
135
135
|
{[
|
|
136
|
-
['mode', "'single' | 'multiple' | 'range'",
|
|
136
|
+
['mode', "'single' | 'multiple' | 'range'", '—', '選擇模式(react-day-picker v9;無預設,未傳時 grid 為非互動純展示)'],
|
|
137
137
|
['selected', 'Date | Date[] | { from; to? }', '—', '受控值,型別依 mode 決定'],
|
|
138
138
|
['onSelect', '(value) => void', '—', '值變動回呼,參數型別依 mode'],
|
|
139
139
|
['defaultMonth', 'Date', 'new Date()', '初始可見月份'],
|
|
@@ -68,17 +68,19 @@ DateGrid 是 internal primitive(見「定位」),一般 consumer 經 `DatePicker
|
|
|
68
68
|
|
|
69
69
|
| mode | 選擇行為 | 典型場景 |
|
|
70
70
|
|------|---------|---------|
|
|
71
|
-
| `single
|
|
71
|
+
| `single` | 單日選取,點新日取代舊選 | DatePicker / 生日 / 到期日 |
|
|
72
72
|
| `multiple` | 可勾選多個不連續日期 | event 報名多日 |
|
|
73
73
|
| `range` | from → to 連續範圍 | 訂單日期範圍 / 查詢時段 |
|
|
74
74
|
|
|
75
|
+
**mode 無預設值**(react-day-picker v9 `mode?: Mode | undefined`,本元件薄包裝不補 default):未傳 `mode` 時 grid 為**非互動純展示**(day 渲染純文字非 button,不可選);DS 內所有消費者(DatePicker / stories)皆顯式傳 `mode`。
|
|
76
|
+
|
|
75
77
|
**range vs multiple 分界**:選的是「連續區間」(只在乎起迄兩端)→ `range`;選的是「各自獨立的日子」(可不連續、逐日增減)→ `multiple`。起迄日場景(訂單 / 查詢時段)固定走 `range`;簽核 / 報名這類逐日勾選走 `multiple`。
|
|
76
78
|
|
|
77
79
|
---
|
|
78
80
|
|
|
79
81
|
## 實作機制:classNames 鏡射 modifier keys(2026-04-21 AR43 修正)
|
|
80
82
|
|
|
81
|
-
**重要**:react-day-picker v9 的 cell 只有 `data-selected / data-disabled / data-today / data-outside / data-focused`
|
|
83
|
+
**重要**:react-day-picker v9 的 cell state DOM attribute 只有 `data-selected / data-disabled / data-today / data-outside / data-focused`(另有非 state 的 `data-day / data-month / data-hidden`);**`data-range-start / data-range-middle / data-range-end` 不存在**。
|
|
82
84
|
|
|
83
85
|
因此用 `[&[data-range-middle=true]]:xxx` 這種 attribute selector **根本不會生效**(舊版做法錯誤)。
|
|
84
86
|
|
|
@@ -106,15 +108,16 @@ DateGrid 是 internal primitive(見「定位」),一般 consumer 經 `DatePicker
|
|
|
106
108
|
| **selected / range 端點** | 藍底白字圓 | button `primary` 底 + `on-emphasis` 字 | range_start / range_end 共用此視覺 |
|
|
107
109
|
| **range 端點 cell bg** | 灰底半圓 track,**高度 = button**,向 middle 外擴 2px bridge gap | `neutral-selected`;class 細節見「Range track canonical」+ tsx | 圓弧半徑 = button 半徑無錯位;舊版 cell-level bg 圓弧半徑 16px 比 button 14px 大 = 視覺 misalign |
|
|
108
110
|
| **range track(中間)** | 灰底矩形,**高度 = button**(28×28 @ md),左右各外擴 2px 接合相鄰 cell | `neutral-selected`;button 透明顯露 track(class 細節見 tsx)| track 高度跟 selected 圓一致,不留 2px「fat」邊;相鄰 pseudo 接合連貫橫向 track |
|
|
109
|
-
| **hover(未選中)** | 藍圈 outline(無 fill) | button hover ring 色 `primary
|
|
111
|
+
| **hover(未選中)** | 藍圈 outline(無 fill) | button hover ring 色 `primary-hover`(2026-07-07 user 拍板統一:瞬時 hover 進 primary 家族 = hover 階,FileUpload / Slider thumb hover 同族;base 專屬持續選中與 focus),無 bg(ring 寬度等 class 細節見 tsx)| 細 ring 對齊 Apple HIG `@benchmark-unverified` / Ant `@benchmark-unverified`(visual,non-source-citable) |
|
|
110
112
|
|
|
111
113
|
## 組合狀態(state stacking order)
|
|
112
114
|
|
|
113
115
|
- today + selected → selected 勝出的**底色**(藍底白字圓);bar 跟著切 on-emphasis(白)保持可見
|
|
114
116
|
- range-start / range-end → selected 規則(cell 半圓 track + button 圓)
|
|
115
117
|
- range-middle → track 規則(cell 灰底矩形 + button 透明)
|
|
116
|
-
- today + range-middle → track(灰底)+ today bar(
|
|
117
|
-
- hover 在 selected / disabled 上被 ring-0 壓制(避免二次 hover 出現方框 bug);range-middle
|
|
118
|
+
- today + range-middle → track(灰底)+ today bar **維持藍色**(2026-07-07 user 拍板:切白只屬「藍底白字圓」的選中日/端點;range 中段是淺灰底,白 bar 近乎隱形 = today 標記消失。range_middle 以 `!bg-primary` 覆寫 today 的 `data-selected` 切白——RDP v9 range 中段日同樣掛 selected modifier 故會誤觸發。對照 Ant panel.ts:cell-today 指示 = colorPrimary,in-range 只換底色、無規則隱藏/改色 today 指示)
|
|
119
|
+
- hover 在 selected / disabled 上被 ring-0 壓制(避免二次 hover 出現方框 bug);range-middle 因同樣掛 selected modifier,button hover ring **一併被 selected 的 ring-0 壓制**(與 selected 一致的 hover 抑制;button 透明僅顯露 track — 2026-07-05 對照 RDP source 修正舊句「hover ring 仍顯示」)
|
|
120
|
+
- **選中日 hover 底色升階**:ring 壓制之外,選中日 hover 時 `bg-primary → bg-primary-hover`(2026-07-06 補明文——「選中之上 hover = 同色相升 hover 階」家族,Checkbox / Switch checked hover 同款;code 已有此行為,本句消 spec-code 落差)
|
|
118
121
|
|
|
119
122
|
## Spacing canonical(2026-05-03 v8)
|
|
120
123
|
|
|
@@ -127,13 +130,13 @@ DateGrid 是 internal primitive(見「定位」),一般 consumer 經 `DatePicker
|
|
|
127
130
|
- **Caption row alignment canonical**:`pt-3 + h-field-xs + mb-3 = 12 + 24 + 12 = 48px`
|
|
128
131
|
- DateGrid month_caption + TimePickerSidePanel header **必走同樣 48px caption row 結構**
|
|
129
132
|
- title text vertical center Y 一致 = 12 + 12 = 24px(from container top)
|
|
130
|
-
- ⚠️ 改 DateGrid `p-3` → 必同步改 `TimePickerSidePanel
|
|
133
|
+
- ⚠️ 改 DateGrid `p-3` → 必同步改 `TimePickerSidePanel pt-3`(root 刻意 bottom = 0,見 date-picker.tsx docblock),否則對齊破
|
|
131
134
|
|
|
132
135
|
**為什麼用 `h-field-sm` 而非固定 `h-9`**:picker grid 在 lg density 下也要跟 Input 系統一起放大,`h-field-sm` 在 md = 28px / lg = 32px,比 Ant/Google 固定 36px(`@benchmark-unverified` visual measurement)稍緊但對齊本 DS 的 field 家族。day cell 尺寸雖固定,但**隨 density 縮放**,視覺跟 popup 內其他欄位(Input / Button)保持比例。
|
|
133
136
|
|
|
134
137
|
## Nav button canonical(2026-05-03 v9)
|
|
135
138
|
|
|
136
|
-
Prev/Next chevron 用 `<Button variant="text" size="xs" iconOnly>`(DS primitive 消費,不 hand-coded inline-flex)。透過 RDP v9 `components.PreviousMonthButton / NextMonthButton` override(`node_modules/react-day-picker/dist/esm
|
|
139
|
+
Prev/Next chevron 用 `<Button variant="text" size="xs" iconOnly>`(DS primitive 消費,不 hand-coded inline-flex)。透過 RDP v9 `components.PreviousMonthButton / NextMonthButton` override(本元件 `navLayout="around"` 下按鈕由 `DayPicker.js` 於首/末月 caption 兩側直渲;無 navLayout 時才走 `Nav.js` — 兩路徑皆消費此 override,`node_modules/react-day-picker/dist/esm/` 證實)。
|
|
137
140
|
|
|
138
141
|
**Icon 顏色 = Button 預設 `text-foreground`(neutral-9 / 85% 黑)**,**對齊 DS 一致設計語言**(M23):
|
|
139
142
|
- Icon-only Button 預設 = neutral-9(本 DS 既有 canonical)
|
|
@@ -176,7 +179,7 @@ react-day-picker v9 自動處理:
|
|
|
176
179
|
- **Focus**:`autoFocus` prop 自動 focus 到選中日(或今天)
|
|
177
180
|
- **Locale**:`locale` prop 控制週首日、星期標頭語言
|
|
178
181
|
|
|
179
|
-
**DS 自訂部分**:day button 的 focus ring 由本元件 classNames 覆寫為 DS canonical(`focus-visible:ring-2 ring-ring`,非 RDP 預設樣式);nav
|
|
182
|
+
**DS 自訂部分**:day button 的 focus ring 由本元件 classNames 覆寫為 DS canonical(`focus-visible:ring-2 ring-ring`,非 RDP 預設樣式);SR 文案經 RDP `labels` API 提供中文 default(nav「上一個月 / 下一個月」、day / weekday / grid 等全繁中,對齊 2026-07-04 全庫 SR label 中文 canonical),consumer 傳 `labels` 可逐鍵覆寫(i18n),不在 components override 內 hardcode `aria-label`(會蓋死覆寫通道)。其餘 consumer 無需額外處理,保留 react-day-picker API 即可。
|
|
180
183
|
|
|
181
184
|
---
|
|
182
185
|
|
|
@@ -23,7 +23,7 @@ import { Button } from '@/design-system/components/Button/button'
|
|
|
23
23
|
* | Nav + Month caption row | h-field-xs(24px)單行,chevron(xs)分居左右 / 月份置中垂直對齊 |
|
|
24
24
|
* | Nav → Weekday gap | 12px(month_caption mb-3) |
|
|
25
25
|
* | Weekday | text-body(14px)text-foreground font-medium(neutral-9,同 caption 權重;撤銷 v3 fg-secondary) |
|
|
26
|
-
* | Cell gap(水平 + 垂直)| 4px(
|
|
26
|
+
* | Cell gap(水平 + 垂直)| 4px(table-native border-separate border-spacing-1;非 gap — grid 會 break,見下方 v7 註) |
|
|
27
27
|
* | Day cell size | h-field-sm w-[var(--field-height-sm)](28×28 md / 32×32 lg) |
|
|
28
28
|
* | Day button | rounded-full 填滿 cell |
|
|
29
29
|
*
|
|
@@ -37,7 +37,7 @@ import { Button } from '@/design-system/components/Button/button'
|
|
|
37
37
|
* | selected / range 端點 | 藍底白字圓 | [&>button]:bg-primary [&>button]:text-on-emphasis rounded-full |
|
|
38
38
|
* | range middle | 灰底矩形 track(bg-neutral-selected = neutral-2),**高度 = cell 高度**(28×28 @ md) | before pseudo: `inset-y-0 -inset-x-[2px]` |
|
|
39
39
|
* | range start/end 半圓 track | 左/右半圓 + selected 圓疊在上,**圓半徑 = button 半徑** | before pseudo: `rounded-l/r-full` + start `left-0 -right-[2px]` / end `-left-[2px] right-0`(向 middle 外擴 2px bridge gap)|
|
|
40
|
-
* | hover(未選中) | 藍圈 outline | hover:ring-[1.5px] hover:ring-primary |
|
|
40
|
+
* | hover(未選中) | 藍圈 outline | hover:ring-[1.5px] hover:ring-primary-hover(2026-07-07 統一:瞬時 hover = hover 階)|
|
|
41
41
|
*
|
|
42
42
|
* ── Range track 高度 canonical(2026-05-03 v6,M8 4 家對照)──
|
|
43
43
|
* Ant Design([picker source](https://github.com/ant-design/ant-design/blob/master/components/date-picker/style/panel.ts))/ Material X DateRangePicker([mui-x source](https://github.com/mui/mui-x/tree/master/packages/x-date-pickers-pro/src/DateRangeCalendar))/ Apple Calendar `@benchmark-unverified`(closed-source)/ Google Calendar `@benchmark-unverified`(closed-source)共識:
|
|
@@ -57,11 +57,19 @@ import { Button } from '@/design-system/components/Button/button'
|
|
|
57
57
|
|
|
58
58
|
export type DateGridProps = React.ComponentProps<typeof DayPicker>
|
|
59
59
|
|
|
60
|
+
// ── SR label 中文 formatter(2026-07-05)──
|
|
61
|
+
// 走 Intl.DateTimeFormat(對齊 TimePicker / Calendar 既有 canonical,不引第二套 date lib);
|
|
62
|
+
// module-level 建一次 — label fn 每個 day cell 都會呼叫,避免 per-call new formatter。
|
|
63
|
+
const ZH_FULL_DATE = new Intl.DateTimeFormat('zh-TW', { dateStyle: 'full' })
|
|
64
|
+
const ZH_MONTH_YEAR = new Intl.DateTimeFormat('zh-TW', { year: 'numeric', month: 'long' })
|
|
65
|
+
const ZH_WEEKDAY = new Intl.DateTimeFormat('zh-TW', { weekday: 'long' })
|
|
66
|
+
|
|
60
67
|
// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding
|
|
61
68
|
const DateGrid = React.forwardRef<HTMLDivElement, DateGridProps>(function DateGrid(
|
|
62
69
|
{
|
|
63
70
|
className,
|
|
64
71
|
classNames,
|
|
72
|
+
labels,
|
|
65
73
|
showOutsideDays = true,
|
|
66
74
|
...props
|
|
67
75
|
},
|
|
@@ -114,7 +122,9 @@ const DateGrid = React.forwardRef<HTMLDivElement, DateGridProps>(function DateGr
|
|
|
114
122
|
'absolute inset-0 z-[1] flex items-center justify-center',
|
|
115
123
|
'font-normal text-body rounded-full transition-colors',
|
|
116
124
|
// Hover 藍圈 1.5px(對齊 Apple HIG / Ant `@benchmark-unverified` visual ring measurement)— ring 在 button 之上 + 透明 bg 不擋 range track
|
|
117
|
-
|
|
125
|
+
// 2026-07-07 user 拍板統一:瞬時 hover 進 primary 家族 = hover 階(FileUpload dropzone /
|
|
126
|
+
// Slider thumb hover 同族;base 專屬持續選中與 focus)——ring-primary → ring-primary-hover
|
|
127
|
+
'hover:ring-[1.5px] hover:ring-primary-hover hover:bg-transparent',
|
|
118
128
|
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
|
|
119
129
|
),
|
|
120
130
|
// today:藍色 underline bar 貼近數字
|
|
@@ -124,7 +134,8 @@ const DateGrid = React.forwardRef<HTMLDivElement, DateGridProps>(function DateGr
|
|
|
124
134
|
'[&>button]:after:bottom-[5px] [&>button]:after:left-1/2 [&>button]:after:-translate-x-1/2',
|
|
125
135
|
'[&>button]:after:w-[40%] [&>button]:after:h-[1.5px] [&>button]:after:rounded-full',
|
|
126
136
|
'[&>button]:after:bg-primary',
|
|
127
|
-
// today + selected:bar 切 on-emphasis(白)
|
|
137
|
+
// today + selected:bar 切 on-emphasis(白)— 只為「藍底白字圓」的選中日/端點設計;
|
|
138
|
+
// range 中段(淺灰底)由 range_middle 的 !bg-primary 覆寫回藍(2026-07-07 user 拍板)。
|
|
128
139
|
'[&[data-selected=true]>button]:after:bg-on-emphasis',
|
|
129
140
|
),
|
|
130
141
|
outside: '[&>button]:text-fg-muted',
|
|
@@ -161,12 +172,53 @@ const DateGrid = React.forwardRef<HTMLDivElement, DateGridProps>(function DateGr
|
|
|
161
172
|
range_middle: cn(
|
|
162
173
|
"before:content-[''] before:absolute before:inset-y-0 before:-inset-x-[2px]",
|
|
163
174
|
'before:bg-neutral-selected before:pointer-events-none',
|
|
164
|
-
//
|
|
175
|
+
// 2026-07-07 user 拍板:range 中段的 today bar 維持藍(切白只屬藍底選中日;白條在
|
|
176
|
+
// neutral-selected 淺灰底上近乎隱形 = today 標記消失)。!important 確定性壓過 today 的
|
|
177
|
+
// data-selected 切白規則(同權重靠 stylesheet 順序不可靠)。Ant 源碼實錘:cell-today
|
|
178
|
+
// 指示 = colorPrimary,in-range 只換底色、無規則隱藏/改色 today 指示(panel.ts)。
|
|
179
|
+
// 非 today 的中段日無 after content,本規則無作用 — 安全。
|
|
180
|
+
'[&>button]:after:!bg-primary',
|
|
181
|
+
// button 透明顯露 track。2026-07-05 對齊 RDP v9 真實行為(deep-audit A.1b):
|
|
182
|
+
// range 中段日同樣掛 selected modifier(useRange rangeIncludesDate 不排除中段)→
|
|
183
|
+
// 上方 selected 的 `[&>button]:hover:ring-0` 一併壓制 hover ring(與 selected 一致
|
|
184
|
+
// 的 hover 抑制),非原註解宣稱的「hover ring 仍顯示」;today bar 亦被 data-selected
|
|
185
|
+
// selector 切 on-emphasis(見 spec「組合狀態」段)。
|
|
165
186
|
'[&>button]:!bg-transparent [&>button]:!text-foreground',
|
|
166
187
|
),
|
|
167
188
|
hidden: 'invisible',
|
|
168
189
|
...classNames,
|
|
169
190
|
}}
|
|
191
|
+
// ── SR label 中文 defaults(2026-07-05,RDP labels API 正門)──
|
|
192
|
+
// 對齊 MUI X localeText「預設可覆寫」idiom + 2026-07-04 全庫 SR label 中文
|
|
193
|
+
// canonical(commit 241676c6):DS 給中文 default,consumer 傳 labels 逐鍵覆寫(i18n)。
|
|
194
|
+
// 禁在 components override 內 spread 後 hardcode aria-label — 會蓋死此覆寫通道
|
|
195
|
+
// (2026-07-05 修正 07-04 的錯誤修法:中文要走 labels 正門,不是蓋 RDP 算好的值)。
|
|
196
|
+
labels={{
|
|
197
|
+
labelPrevious: () => '上一個月',
|
|
198
|
+
labelNext: () => '下一個月',
|
|
199
|
+
// 鏡射 RDP default 結構(labels/labelDayButton.js:today 前綴 / selected 後綴),文案中文化
|
|
200
|
+
labelDayButton: (date, modifiers) => {
|
|
201
|
+
let label = ZH_FULL_DATE.format(date)
|
|
202
|
+
if (modifiers.today) label = `今天,${label}`
|
|
203
|
+
if (modifiers.selected) label = `${label},已選取`
|
|
204
|
+
return label
|
|
205
|
+
},
|
|
206
|
+
// 未傳 mode 的非互動 grid(spec「mode 無預設值」段)day cell 走 labelGridcell
|
|
207
|
+
labelGridcell: (date, modifiers) => {
|
|
208
|
+
let label = ZH_FULL_DATE.format(date)
|
|
209
|
+
if (modifiers?.today) label = `今天,${label}`
|
|
210
|
+
return label
|
|
211
|
+
},
|
|
212
|
+
labelGrid: (date) => ZH_MONTH_YEAR.format(date),
|
|
213
|
+
labelWeekday: (date) => ZH_WEEKDAY.format(date),
|
|
214
|
+
// 以下非 default 渲染面(captionLayout dropdown / showWeekNumber 由 consumer 經
|
|
215
|
+
// {...props} 開啟)— 一併補齊,避免開啟後 SR 中英夾雜
|
|
216
|
+
labelMonthDropdown: () => '選擇月份',
|
|
217
|
+
labelYearDropdown: () => '選擇年份',
|
|
218
|
+
labelWeekNumber: (weekNumber) => `第 ${weekNumber} 週`,
|
|
219
|
+
labelWeekNumberHeader: () => '週數',
|
|
220
|
+
...labels,
|
|
221
|
+
}}
|
|
170
222
|
components={{
|
|
171
223
|
// ── Prev/Next nav(canonical 2026-05-03 v9,DS 一致設計語言)──
|
|
172
224
|
// User 2026-05-03 audit:「icon-only Button icon 都用 neutral-9,只有 dismiss 用 45%」
|
|
@@ -174,14 +226,15 @@ const DateGrid = React.forwardRef<HTMLDivElement, DateGridProps>(function DateGr
|
|
|
174
226
|
// 不開新 tier 自打嘴(撤銷 v6-v8 用 fg-muted override 的 mistake)。
|
|
175
227
|
// RDP v9 `PreviousMonthButton / NextMonthButton` override(node_modules/react-day-picker/dist/esm/components/Nav.js)
|
|
176
228
|
// ⚠️ children: _ 必丟棄(RDP 把 <Chevron> 當 children 傳 → 跟 Button startIcon 重疊變 double chevron)
|
|
229
|
+
// 2026-07-05 修:此處**不** hardcode aria-label — 中文走上方 labels API 正門,
|
|
230
|
+
// RDP 算好 labelPrevious()/labelNext() 後經 {...props} 流入 Button;
|
|
231
|
+
// spread 後 hardcode(07-04 舊修法)= 蓋死 consumer labels 覆寫通道。
|
|
177
232
|
PreviousMonthButton: ({ className, children: _children, ...props }) => (
|
|
178
233
|
<Button variant="text" size="xs" iconOnly startIcon={ChevronLeft}
|
|
179
|
-
aria-label="上一個月"
|
|
180
234
|
className={className} {...props} />
|
|
181
235
|
),
|
|
182
236
|
NextMonthButton: ({ className, children: _children, ...props }) => (
|
|
183
237
|
<Button variant="text" size="xs" iconOnly startIcon={ChevronRight}
|
|
184
|
-
aria-label="下一個月"
|
|
185
238
|
className={className} {...props} />
|
|
186
239
|
),
|
|
187
240
|
}}
|
|
@@ -202,7 +255,9 @@ export const dateGridMeta = {
|
|
|
202
255
|
sizes: {
|
|
203
256
|
|
|
204
257
|
},
|
|
205
|
-
|
|
258
|
+
// 'selected' = 選中日期 / range 端點持續選中(藍底白字圓 bg-primary + range 帶 bg-neutral-selected);
|
|
259
|
+
// 'active' 移除 — 全檔無 Tailwind 按壓 utility,無按壓專屬視覺態(2026-07-07 詞彙統一對抗稽核補修)。
|
|
260
|
+
states: ['default', 'hover', 'selected', 'focus-visible', 'disabled'],
|
|
206
261
|
tokens: {
|
|
207
262
|
bg: ['bg-disabled', 'bg-neutral-selected', 'bg-on-emphasis', 'bg-primary', 'bg-primary-hover', 'bg-transparent'],
|
|
208
263
|
fg: ['text-fg-disabled', 'text-fg-muted', 'text-foreground', 'text-on-emphasis'],
|
|
@@ -32,7 +32,7 @@ const Label = ({ children, warn }: { children: React.ReactNode; warn?: boolean }
|
|
|
32
32
|
|
|
33
33
|
const Section = ({ title, children }: { title: string; children: React.ReactNode }) => (
|
|
34
34
|
<section className="mb-12">
|
|
35
|
-
<h2 className="text-
|
|
35
|
+
<h2 className="text-h3 font-bold text-foreground mb-4 pb-2 border-b border-border">{title}</h2>
|
|
36
36
|
{children}
|
|
37
37
|
</section>
|
|
38
38
|
)
|
|
@@ -229,6 +229,7 @@ DateGrid cell 有 5 種語意視覺,每種用不同形狀/色彩語言避免混
|
|
|
229
229
|
- Range showTime footer **無「此刻」**(對齊 Ant `showNow={multiple ? false : showNow}`)— 只「確定」走 SurfaceFooter justify-end
|
|
230
230
|
- value 格式:`'YYYY-MM-DDTHH:MM:SS'`(local-time 語意,不帶 timezone)
|
|
231
231
|
- needConfirm=true 時 user 編輯先暫存 draft,trigger text 即時讀 draft(canonical 2026-05-03 v8 修);按確定才 onChange;false 時邊編邊 commit
|
|
232
|
+
- **needConfirm 獨立於 showTime**(2026-07-05 D4 codify):date-only `<DatePicker needConfirm>` 亦成立,footer 渲染 gate = `showTime || needConfirm`(single 與 Range 一致)——needConfirm 時「確定」是唯一 commit 路徑,漏渲 footer = draft 永遠無法 commit 的死路(2026-07-05 修 single 漏 `|| needConfirm`)
|
|
232
233
|
- showSeconds=false(default)→ TimeColumns 只顯示 H/M(對齊 Ant 預設)
|
|
233
234
|
- Range cell disable(對齊 Ant `useRangeDisabledDate`):activeEnd='start' + end 已選 → date > end disabled;activeEnd='end' + start 已選 → date < start disabled
|
|
234
235
|
- Range stadium pattern(canonical 2026-05-03 v8):rangeStart/End cell pseudo `before:rounded-l-full / rounded-r-full` 跟 button 圓的左/右半弧 EXACTLY OVERLAY → 無「凸出」(對齊 Ant `cell-range-start::before { border-radius: 9999px 0 0 9999px }`)
|
|
@@ -223,6 +223,25 @@ export const ShowTimePopoverOpen: Story = {
|
|
|
223
223
|
play: openPopoverPlay('[role="combobox"]'),
|
|
224
224
|
}
|
|
225
225
|
|
|
226
|
+
/* ── OpenSnapshot:needConfirm date-only(2026-07-05 D4 修 footer gate 死路)── */
|
|
227
|
+
export const NeedConfirmDateOnlyOpen: Story = {
|
|
228
|
+
name: 'needConfirm:合約生效日確認',
|
|
229
|
+
parameters: { docs: { description: { story: 'date-only + needConfirm:點日期只暫存 draft(trigger 即時顯示),footer「確定」才 commit onChange——footer 渲染 gate = showTime || needConfirm(2026-07-05 D4 修 single 漏 || needConfirm 時「點了日期什麼都不會發生」的死路)。適用選錯成本高的日期(合約生效日 / 帳單結算起算日)。Visual-audit OpenSnapshot verify date-only popover 有渲 footer(此刻 / 確定)。' } } },
|
|
230
|
+
render: () => {
|
|
231
|
+
const [effectiveDate, setEffectiveDate] = React.useState('2026-08-01')
|
|
232
|
+
return (
|
|
233
|
+
<div className="flex flex-col gap-4 max-w-xs" style={{ paddingBottom: 420 }}>
|
|
234
|
+
<p className="text-caption text-fg-muted">
|
|
235
|
+
合約生效日選錯成本高:點日期先暫存,按「確定」才真正寫入。
|
|
236
|
+
</p>
|
|
237
|
+
<DatePicker needConfirm value={effectiveDate} onChange={setEffectiveDate} />
|
|
238
|
+
<p className="text-caption text-fg-muted">已提交值:{effectiveDate || '(空)'}</p>
|
|
239
|
+
</div>
|
|
240
|
+
)
|
|
241
|
+
},
|
|
242
|
+
play: openPopoverPlay('[role="combobox"]'),
|
|
243
|
+
}
|
|
244
|
+
|
|
226
245
|
/* ── OpenSnapshot:showTime Range popover(activeEnd='start')── */
|
|
227
246
|
export const ShowTimeRangePopoverOpen: Story = {
|
|
228
247
|
name: 'showTime 範圍:浮層展開狀態',
|