@comitor/ui 2.1.0 → 3.0.1
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 +63 -24
- package/dist/chart/chart.d.ts +6 -1
- package/dist/chart/chart.js +3 -1
- package/dist/chart/chart.js.map +1 -1
- package/dist/chart/comitor-chart.d.ts +4 -1
- package/dist/chart/comitor-chart.js +3 -1
- package/dist/chart/comitor-chart.js.map +1 -1
- package/dist/components/composite/avatar-group.d.ts +9 -6
- package/dist/components/composite/avatar-group.js +6 -2
- package/dist/components/composite/avatar-group.js.map +1 -1
- package/dist/components/composite/combobox.d.ts +11 -11
- package/dist/components/composite/combobox.js +25 -11
- package/dist/components/composite/combobox.js.map +1 -1
- package/dist/components/composite/command-palette-constants.d.ts +15 -5
- package/dist/components/composite/command-palette-constants.js +3 -2
- package/dist/components/composite/command-palette-constants.js.map +1 -1
- package/dist/components/composite/command-palette.d.ts +2 -2
- package/dist/components/composite/command-palette.js +8 -5
- package/dist/components/composite/command-palette.js.map +1 -1
- package/dist/components/composite/confirm-dialog.d.ts +8 -8
- package/dist/components/composite/confirm-dialog.js +8 -3
- package/dist/components/composite/confirm-dialog.js.map +1 -1
- package/dist/components/composite/copy-button.d.ts +7 -3
- package/dist/components/composite/copy-button.js +7 -3
- package/dist/components/composite/copy-button.js.map +1 -1
- package/dist/components/composite/data-table-constants.d.ts +7 -3
- package/dist/components/composite/data-table-constants.js +2 -9
- package/dist/components/composite/data-table-constants.js.map +1 -1
- package/dist/components/composite/data-table.js +3 -3
- package/dist/components/composite/data-table.js.map +1 -1
- package/dist/components/composite/date-picker-constants.d.ts +32 -9
- package/dist/components/composite/date-picker-constants.js +16 -11
- package/dist/components/composite/date-picker-constants.js.map +1 -1
- package/dist/components/composite/date-picker.d.ts +19 -10
- package/dist/components/composite/date-picker.js +33 -9
- package/dist/components/composite/date-picker.js.map +1 -1
- package/dist/components/composite/filter-chips.d.ts +4 -3
- package/dist/components/composite/filter-chips.js +6 -2
- package/dist/components/composite/filter-chips.js.map +1 -1
- package/dist/components/composite/inline-edit.d.ts +14 -3
- package/dist/components/composite/inline-edit.js +8 -4
- package/dist/components/composite/inline-edit.js.map +1 -1
- package/dist/components/composite/relative-time.d.ts +4 -4
- package/dist/components/composite/relative-time.js +4 -2
- package/dist/components/composite/relative-time.js.map +1 -1
- package/dist/components/composite/route-tabs-nav.d.ts +9 -0
- package/dist/components/composite/route-tabs-nav.js +11 -0
- package/dist/components/composite/route-tabs-nav.js.map +1 -0
- package/dist/components/composite/route-tabs.d.ts +4 -1
- package/dist/components/composite/route-tabs.js +3 -2
- package/dist/components/composite/route-tabs.js.map +1 -1
- package/dist/components/composite/search-filter-bar-constants.d.ts +18 -8
- package/dist/components/composite/search-filter-bar-constants.js +2 -14
- package/dist/components/composite/search-filter-bar-constants.js.map +1 -1
- package/dist/components/composite/search-filter-bar.d.ts +5 -2
- package/dist/components/composite/search-filter-bar.js +2 -2
- package/dist/components/composite/search-filter-bar.js.map +1 -1
- package/dist/components/composite/stat-card.d.ts +1 -1
- package/dist/components/composite/table-pagination-constants.d.ts +16 -11
- package/dist/components/composite/table-pagination-constants.js +2 -12
- package/dist/components/composite/table-pagination-constants.js.map +1 -1
- package/dist/components/composite/table-pagination.d.ts +6 -3
- package/dist/components/composite/table-pagination.js +3 -5
- package/dist/components/composite/table-pagination.js.map +1 -1
- package/dist/components/composite/toast.d.ts +5 -2
- package/dist/components/composite/toast.js +3 -1
- package/dist/components/composite/toast.js.map +1 -1
- package/dist/components/ui/action-bar-constants.d.ts +8 -4
- package/dist/components/ui/action-bar-constants.js +2 -5
- package/dist/components/ui/action-bar-constants.js.map +1 -1
- package/dist/components/ui/action-bar.d.ts +5 -4
- package/dist/components/ui/action-bar.js +47 -3
- package/dist/components/ui/action-bar.js.map +1 -1
- package/dist/components/ui/alert.d.ts +1 -1
- package/dist/components/ui/badge-overflow.d.ts +2 -2
- package/dist/components/ui/badge-overflow.js +4 -2
- package/dist/components/ui/badge-overflow.js.map +1 -1
- package/dist/components/ui/badge.d.ts +1 -1
- package/dist/components/ui/banner.d.ts +6 -5
- package/dist/components/ui/banner.js +3 -1
- package/dist/components/ui/banner.js.map +1 -1
- package/dist/components/ui/breadcrumb.d.ts +11 -4
- package/dist/components/ui/breadcrumb.js +7 -2
- package/dist/components/ui/breadcrumb.js.map +1 -1
- package/dist/components/ui/button.d.ts +2 -2
- package/dist/components/ui/calendar.d.ts +12 -5
- package/dist/components/ui/calendar.js +38 -3
- package/dist/components/ui/calendar.js.map +1 -1
- package/dist/components/ui/carousel.d.ts +4 -3
- package/dist/components/ui/carousel.js +11 -4
- package/dist/components/ui/carousel.js.map +1 -1
- package/dist/components/ui/circular-progress.js +10 -2
- package/dist/components/ui/circular-progress.js.map +1 -1
- package/dist/components/ui/command.d.ts +5 -4
- package/dist/components/ui/command.js +6 -2
- package/dist/components/ui/command.js.map +1 -1
- package/dist/components/ui/cropper-constants.d.ts +8 -3
- package/dist/components/ui/cropper-constants.js +2 -6
- package/dist/components/ui/cropper-constants.js.map +1 -1
- package/dist/components/ui/cropper.d.ts +2 -1
- package/dist/components/ui/cropper.js +2 -2
- package/dist/components/ui/cropper.js.map +1 -1
- package/dist/components/ui/dialog.d.ts +2 -2
- package/dist/components/ui/dialog.js +4 -1
- package/dist/components/ui/dialog.js.map +1 -1
- package/dist/components/ui/item.d.ts +2 -2
- package/dist/components/ui/kanban-constants.d.ts +7 -1
- package/dist/components/ui/kanban-constants.js +2 -17
- package/dist/components/ui/kanban-constants.js.map +1 -1
- package/dist/components/ui/kanban.d.ts +2 -2
- package/dist/components/ui/kanban.js +11 -5
- package/dist/components/ui/kanban.js.map +1 -1
- package/dist/components/ui/key-value-constants.d.ts +7 -3
- package/dist/components/ui/key-value-constants.js +2 -7
- package/dist/components/ui/key-value-constants.js.map +1 -1
- package/dist/components/ui/key-value.d.ts +5 -5
- package/dist/components/ui/key-value.js +72 -78
- package/dist/components/ui/key-value.js.map +1 -1
- package/dist/components/ui/mask-input-constants.d.ts +2 -3
- package/dist/components/ui/mask-input-constants.js +36 -77
- package/dist/components/ui/mask-input-constants.js.map +1 -1
- package/dist/components/ui/mask-input.d.ts +7 -4
- package/dist/components/ui/mask-input.js +22 -22
- package/dist/components/ui/mask-input.js.map +1 -1
- package/dist/components/ui/mention.d.ts +3 -2
- package/dist/components/ui/mention.js +4 -6
- package/dist/components/ui/mention.js.map +1 -1
- package/dist/components/ui/pagination.d.ts +13 -9
- package/dist/components/ui/pagination.js +15 -6
- package/dist/components/ui/pagination.js.map +1 -1
- package/dist/components/ui/phone-input-constants.d.ts +17 -6
- package/dist/components/ui/phone-input-constants.js +2 -5
- package/dist/components/ui/phone-input-constants.js.map +1 -1
- package/dist/components/ui/phone-input.d.ts +18 -11
- package/dist/components/ui/phone-input.js +14 -12
- package/dist/components/ui/phone-input.js.map +1 -1
- package/dist/components/ui/progress.d.ts +2 -2
- package/dist/components/ui/rating.d.ts +2 -1
- package/dist/components/ui/rating.js +8 -4
- package/dist/components/ui/rating.js.map +1 -1
- package/dist/components/ui/scroll-spy.js +3 -1
- package/dist/components/ui/scroll-spy.js.map +1 -1
- package/dist/components/ui/sheet.d.ts +2 -2
- package/dist/components/ui/sheet.js +10 -1
- package/dist/components/ui/sheet.js.map +1 -1
- package/dist/components/ui/slider.d.ts +1 -1
- package/dist/components/ui/slider.js +15 -1
- package/dist/components/ui/slider.js.map +1 -1
- package/dist/components/ui/sonner.js +2 -0
- package/dist/components/ui/sonner.js.map +1 -1
- package/dist/components/ui/spinner.js +4 -1
- package/dist/components/ui/spinner.js.map +1 -1
- package/dist/components/ui/stepper-constants.d.ts +7 -2
- package/dist/components/ui/stepper-constants.js +2 -5
- package/dist/components/ui/stepper-constants.js.map +1 -1
- package/dist/components/ui/stepper.d.ts +2 -1
- package/dist/components/ui/stepper.js +2 -2
- package/dist/components/ui/stepper.js.map +1 -1
- package/dist/components/ui/tags-input.d.ts +3 -2
- package/dist/components/ui/tags-input.js +4 -2
- package/dist/components/ui/tags-input.js.map +1 -1
- package/dist/components/ui/time-picker-constants.d.ts +12 -9
- package/dist/components/ui/time-picker-constants.js +2 -10
- package/dist/components/ui/time-picker-constants.js.map +1 -1
- package/dist/components/ui/time-picker.d.ts +11 -6
- package/dist/components/ui/time-picker.js +5 -7
- package/dist/components/ui/time-picker.js.map +1 -1
- package/dist/components/ui/toggle-group.js +12 -1
- package/dist/components/ui/toggle-group.js.map +1 -1
- package/dist/components/ui/toggle.d.ts +1 -1
- package/dist/components/ui/tour-constants.d.ts +7 -2
- package/dist/components/ui/tour-constants.js +2 -8
- package/dist/components/ui/tour-constants.js.map +1 -1
- package/dist/components/ui/tour.d.ts +3 -2
- package/dist/components/ui/tour.js +5 -4
- package/dist/components/ui/tour.js.map +1 -1
- package/dist/data-grid/data-grid-cell-variants.js +4 -4
- package/dist/data-grid/data-grid-cell-variants.js.map +1 -1
- package/dist/data-grid/data-grid-column-header.js +1 -1
- package/dist/data-grid/data-grid-column-header.js.map +1 -1
- package/dist/data-grid/data-grid-filter-menu.d.ts +5 -4
- package/dist/data-grid/data-grid-filter-menu.js +13 -8
- package/dist/data-grid/data-grid-filter-menu.js.map +1 -1
- package/dist/data-grid/data-grid-keyboard-shortcuts.d.ts +4 -4
- package/dist/data-grid/data-grid-keyboard-shortcuts.js.map +1 -1
- package/dist/data-grid/data-grid-locale.d.ts +58 -6
- package/dist/data-grid/data-grid-locale.js +41 -9
- package/dist/data-grid/data-grid-locale.js.map +1 -1
- package/dist/data-grid/data-grid-row-height-menu.d.ts +4 -3
- package/dist/data-grid/data-grid-row-height-menu.js.map +1 -1
- package/dist/data-grid/data-grid-sort-menu.d.ts +4 -4
- package/dist/data-grid/data-grid-sort-menu.js +1 -1
- package/dist/data-grid/data-grid-sort-menu.js.map +1 -1
- package/dist/data-grid/data-grid-view-menu.d.ts +4 -4
- package/dist/data-grid/data-grid-view-menu.js.map +1 -1
- package/dist/data-grid/data-grid.d.ts +4 -3
- package/dist/data-grid/data-grid.js +2 -3
- package/dist/data-grid/data-grid.js.map +1 -1
- package/dist/data-grid/index.d.ts +1 -0
- package/dist/data-grid/index.js.map +1 -1
- package/dist/data-grid/messages-en.d.ts +23 -4
- package/dist/data-grid/messages-en.js +20 -0
- package/dist/data-grid/messages-en.js.map +1 -1
- package/dist/data-grid/messages-vi.d.ts +3 -1
- package/dist/data-grid/messages-vi.js +14 -0
- package/dist/data-grid/messages-vi.js.map +1 -1
- package/dist/data-grid/messages.d.ts +29 -3
- package/dist/data-grid/messages.js.map +1 -1
- package/dist/data-grid/sortable.js +16 -19
- package/dist/data-grid/sortable.js.map +1 -1
- package/dist/data-grid/types.d.ts +1 -1
- package/dist/data-grid/use-data-grid-undo-redo.d.ts +5 -4
- package/dist/data-grid/use-data-grid-undo-redo.js +2 -2
- package/dist/data-grid/use-data-grid-undo-redo.js.map +1 -1
- package/dist/data-grid/use-data-grid.d.ts +23 -8
- package/dist/data-grid/use-data-grid.js +8 -6
- package/dist/data-grid/use-data-grid.js.map +1 -1
- package/dist/data-grid/utils.d.ts +24 -4
- package/dist/data-grid/utils.js +15 -5
- package/dist/data-grid/utils.js.map +1 -1
- package/dist/i18n/locale-provider.d.ts +127 -0
- package/dist/i18n/locale-provider.js +47 -0
- package/dist/i18n/locale-provider.js.map +1 -0
- package/dist/i18n/locale.d.ts +27 -0
- package/dist/i18n/locale.js +10 -0
- package/dist/i18n/locale.js.map +1 -0
- package/dist/i18n/messages.d.ts +116 -0
- package/dist/i18n/messages.js +149 -0
- package/dist/i18n/messages.js.map +1 -0
- package/dist/i18n/namespaces/action-bar.d.ts +7 -0
- package/dist/i18n/namespaces/action-bar.js +15 -0
- package/dist/i18n/namespaces/action-bar.js.map +1 -0
- package/dist/i18n/namespaces/avatar-group.d.ts +14 -0
- package/dist/i18n/namespaces/avatar-group.js +13 -0
- package/dist/i18n/namespaces/avatar-group.js.map +1 -0
- package/dist/i18n/namespaces/badge-overflow.d.ts +12 -0
- package/dist/i18n/namespaces/badge-overflow.js +11 -0
- package/dist/i18n/namespaces/badge-overflow.js.map +1 -0
- package/dist/i18n/namespaces/banner.d.ts +9 -0
- package/dist/i18n/namespaces/banner.js +7 -0
- package/dist/i18n/namespaces/banner.js.map +1 -0
- package/dist/i18n/namespaces/breadcrumb.d.ts +11 -0
- package/dist/i18n/namespaces/breadcrumb.js +13 -0
- package/dist/i18n/namespaces/breadcrumb.js.map +1 -0
- package/dist/i18n/namespaces/calendar.d.ts +40 -0
- package/dist/i18n/namespaces/calendar.js +25 -0
- package/dist/i18n/namespaces/calendar.js.map +1 -0
- package/dist/i18n/namespaces/carousel.d.ts +15 -0
- package/dist/i18n/namespaces/carousel.js +17 -0
- package/dist/i18n/namespaces/carousel.js.map +1 -0
- package/dist/i18n/namespaces/combobox.d.ts +21 -0
- package/dist/i18n/namespaces/combobox.js +23 -0
- package/dist/i18n/namespaces/combobox.js.map +1 -0
- package/dist/i18n/namespaces/command-palette.d.ts +20 -0
- package/dist/i18n/namespaces/command-palette.js +19 -0
- package/dist/i18n/namespaces/command-palette.js.map +1 -0
- package/dist/i18n/namespaces/command.d.ts +11 -0
- package/dist/i18n/namespaces/command.js +13 -0
- package/dist/i18n/namespaces/command.js.map +1 -0
- package/dist/i18n/namespaces/confirm-dialog.d.ts +13 -0
- package/dist/i18n/namespaces/confirm-dialog.js +15 -0
- package/dist/i18n/namespaces/confirm-dialog.js.map +1 -0
- package/dist/i18n/namespaces/contrast.d.ts +7 -0
- package/dist/i18n/namespaces/contrast.js +13 -0
- package/dist/i18n/namespaces/contrast.js.map +1 -0
- package/dist/i18n/namespaces/copy-button.d.ts +11 -0
- package/dist/i18n/namespaces/copy-button.js +13 -0
- package/dist/i18n/namespaces/copy-button.js.map +1 -0
- package/dist/i18n/namespaces/cropper.d.ts +7 -0
- package/dist/i18n/namespaces/cropper.js +17 -0
- package/dist/i18n/namespaces/cropper.js.map +1 -0
- package/dist/i18n/namespaces/data-table.d.ts +7 -0
- package/dist/i18n/namespaces/data-table.js +23 -0
- package/dist/i18n/namespaces/data-table.js.map +1 -0
- package/dist/i18n/namespaces/date-picker.d.ts +42 -0
- package/dist/i18n/namespaces/date-picker.js +35 -0
- package/dist/i18n/namespaces/date-picker.js.map +1 -0
- package/dist/i18n/namespaces/dialog.d.ts +9 -0
- package/dist/i18n/namespaces/dialog.js +7 -0
- package/dist/i18n/namespaces/dialog.js.map +1 -0
- package/dist/i18n/namespaces/display-axis.d.ts +25 -0
- package/dist/i18n/namespaces/display-axis.js +21 -0
- package/dist/i18n/namespaces/display-axis.js.map +1 -0
- package/dist/i18n/namespaces/file-upload.d.ts +11 -0
- package/dist/i18n/namespaces/file-upload.js +19 -0
- package/dist/i18n/namespaces/file-upload.js.map +1 -0
- package/dist/i18n/namespaces/filter-chips.d.ts +11 -0
- package/dist/i18n/namespaces/filter-chips.js +13 -0
- package/dist/i18n/namespaces/filter-chips.js.map +1 -0
- package/dist/i18n/namespaces/image-upload-field.d.ts +12 -0
- package/dist/i18n/namespaces/image-upload-field.js +23 -0
- package/dist/i18n/namespaces/image-upload-field.js.map +1 -0
- package/dist/i18n/namespaces/inline-edit.d.ts +15 -0
- package/dist/i18n/namespaces/inline-edit.js +17 -0
- package/dist/i18n/namespaces/inline-edit.js.map +1 -0
- package/dist/i18n/namespaces/kanban.d.ts +11 -0
- package/dist/i18n/namespaces/kanban.js +39 -0
- package/dist/i18n/namespaces/kanban.js.map +1 -0
- package/dist/i18n/namespaces/key-value.d.ts +10 -0
- package/dist/i18n/namespaces/key-value.js +19 -0
- package/dist/i18n/namespaces/key-value.js.map +1 -0
- package/dist/i18n/namespaces/mention.d.ts +9 -0
- package/dist/i18n/namespaces/mention.js +7 -0
- package/dist/i18n/namespaces/mention.js.map +1 -0
- package/dist/i18n/namespaces/pagination.d.ts +19 -0
- package/dist/i18n/namespaces/pagination.js +21 -0
- package/dist/i18n/namespaces/pagination.js.map +1 -0
- package/dist/i18n/namespaces/phone-input.d.ts +10 -0
- package/dist/i18n/namespaces/phone-input.js +17 -0
- package/dist/i18n/namespaces/phone-input.js.map +1 -0
- package/dist/i18n/namespaces/rating.d.ts +12 -0
- package/dist/i18n/namespaces/rating.js +12 -0
- package/dist/i18n/namespaces/rating.js.map +1 -0
- package/dist/i18n/namespaces/route-tabs.d.ts +9 -0
- package/dist/i18n/namespaces/route-tabs.js +7 -0
- package/dist/i18n/namespaces/route-tabs.js.map +1 -0
- package/dist/i18n/namespaces/scroll-spy.d.ts +9 -0
- package/dist/i18n/namespaces/scroll-spy.js +7 -0
- package/dist/i18n/namespaces/scroll-spy.js.map +1 -0
- package/dist/i18n/namespaces/search-filter-bar.d.ts +15 -0
- package/dist/i18n/namespaces/search-filter-bar.js +32 -0
- package/dist/i18n/namespaces/search-filter-bar.js.map +1 -0
- package/dist/i18n/namespaces/sheet.d.ts +9 -0
- package/dist/i18n/namespaces/sheet.js +7 -0
- package/dist/i18n/namespaces/sheet.js.map +1 -0
- package/dist/i18n/namespaces/shell.d.ts +21 -0
- package/dist/i18n/namespaces/shell.js +75 -0
- package/dist/i18n/namespaces/shell.js.map +1 -0
- package/dist/i18n/namespaces/spinner.d.ts +9 -0
- package/dist/i18n/namespaces/spinner.js +7 -0
- package/dist/i18n/namespaces/spinner.js.map +1 -0
- package/dist/i18n/namespaces/stepper.d.ts +7 -0
- package/dist/i18n/namespaces/stepper.js +15 -0
- package/dist/i18n/namespaces/stepper.js.map +1 -0
- package/dist/i18n/namespaces/table-pagination.d.ts +7 -0
- package/dist/i18n/namespaces/table-pagination.js +29 -0
- package/dist/i18n/namespaces/table-pagination.js.map +1 -0
- package/dist/i18n/namespaces/tags-input.d.ts +12 -0
- package/dist/i18n/namespaces/tags-input.js +11 -0
- package/dist/i18n/namespaces/tags-input.js.map +1 -0
- package/dist/i18n/namespaces/time-picker.d.ts +15 -0
- package/dist/i18n/namespaces/time-picker.js +25 -0
- package/dist/i18n/namespaces/time-picker.js.map +1 -0
- package/dist/i18n/namespaces/toast.d.ts +16 -0
- package/dist/i18n/namespaces/toast.js +7 -0
- package/dist/i18n/namespaces/toast.js.map +1 -0
- package/dist/i18n/namespaces/tour.d.ts +7 -0
- package/dist/i18n/namespaces/tour.js +21 -0
- package/dist/i18n/namespaces/tour.js.map +1 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +24 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/toast-language.d.ts +8 -0
- package/dist/lib/toast-language.js +19 -0
- package/dist/lib/toast-language.js.map +1 -0
- package/dist/shell/app-header.js.map +1 -1
- package/dist/shell/app-shell.d.ts +20 -2
- package/dist/shell/app-shell.js +4 -1
- package/dist/shell/app-shell.js.map +1 -1
- package/dist/shell/contrast-constants.d.ts +9 -1
- package/dist/shell/contrast-constants.js +2 -4
- package/dist/shell/contrast-constants.js.map +1 -1
- package/dist/shell/contrast-toggle.d.ts +4 -1
- package/dist/shell/contrast-toggle.js +2 -2
- package/dist/shell/contrast-toggle.js.map +1 -1
- package/dist/shell/display-axis-constants.d.ts +2 -1
- package/dist/shell/display-axis-constants.js.map +1 -1
- package/dist/shell/display-axis-controls.d.ts +15 -6
- package/dist/shell/display-axis-controls.js +11 -9
- package/dist/shell/display-axis-controls.js.map +1 -1
- package/dist/shell/display-axis-provider.d.ts +1 -1
- package/dist/shell/mobile-menu.js.map +1 -1
- package/dist/shell/notifications-button.d.ts +1 -1
- package/dist/shell/notifications-button.js +3 -2
- package/dist/shell/notifications-button.js.map +1 -1
- package/dist/shell/shell-context.d.ts +4 -1
- package/dist/shell/shell-context.js +2 -4
- package/dist/shell/shell-context.js.map +1 -1
- package/dist/shell/sidebar.d.ts +1 -1
- package/dist/shell/sidebar.js.map +1 -1
- package/dist/shell/theme-toggle.d.ts +6 -3
- package/dist/shell/theme-toggle.js +2 -2
- package/dist/shell/theme-toggle.js.map +1 -1
- package/dist/shell/types.d.ts +14 -8
- package/dist/shell/types.js +2 -35
- package/dist/shell/types.js.map +1 -1
- package/dist/shell/user-menu.d.ts +6 -6
- package/dist/shell/user-menu.js.map +1 -1
- package/dist/shell/utils.d.ts +1 -1
- package/dist/shell/utils.js.map +1 -1
- package/dist/shell/workspace-switcher.js +1 -1
- package/dist/shell/workspace-switcher.js.map +1 -1
- package/dist/uploader/file-upload-constants.d.ts +13 -0
- package/dist/uploader/file-upload-constants.js +4 -16
- package/dist/uploader/file-upload-constants.js.map +1 -1
- package/dist/uploader/file-upload.d.ts +4 -1
- package/dist/uploader/file-upload.js +2 -2
- package/dist/uploader/file-upload.js.map +1 -1
- package/dist/uploader/image-upload-field.d.ts +5 -1
- package/dist/uploader/image-upload-field.js +2 -2
- package/dist/uploader/image-upload-field.js.map +1 -1
- package/dist/uploader/use-file-upload.d.ts +4 -0
- package/dist/uploader/use-file-upload.js +6 -2
- package/dist/uploader/use-file-upload.js.map +1 -1
- package/package.json +4 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/ui/rating.tsx"],"sourcesContent":["\"use client\";\n\n/*\n * Port từ Dice UI (https://diceui.com/docs/components/rating) — MIT, © 2024 Sadman Sakib.\n * Giữ nguyên cấu trúc thượng nguồn để còn đối chiếu ngược được; mọi chỗ đổi đều có chú thích.\n */\n\nimport { useDirection } from \"@radix-ui/react-direction\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { Star } from \"lucide-react\";\nimport * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\nimport { useComposedRefs } from \"../../lib/compose-refs.js\";\nimport { useAsRef } from \"../../lib/use-as-ref.js\";\nimport { useIsomorphicLayoutEffect } from \"../../lib/use-isomorphic-layout-effect.js\";\nimport { useLazyRef } from \"../../lib/use-lazy-ref.js\";\nimport { VisuallyHiddenInput } from \"../../lib/visually-hidden-input.js\";\n\ntype Direction = \"ltr\" | \"rtl\";\ntype Orientation = \"horizontal\" | \"vertical\";\ntype ActivationMode = \"automatic\" | \"manual\";\ntype Size = \"default\" | \"sm\" | \"lg\";\ntype Step = 0.5 | 1;\ntype DataState = \"full\" | \"partial\" | \"empty\";\ntype FocusIntent = \"first\" | \"last\" | \"prev\" | \"next\";\n\ntype RootElement = React.ComponentRef<typeof Rating>;\ntype ItemElement = React.ComponentRef<typeof RatingItem>;\n\nconst ROOT_NAME = \"Rating\";\nconst ITEM_NAME = \"RatingItem\";\n\nconst ENTRY_FOCUS = \"ratingFocusGroup.onEntryFocus\";\nconst EVENT_OPTIONS = { bubbles: false, cancelable: true };\n\nfunction getItemId(id: string, value: number) {\n return `${id}-item-${value}`;\n}\n\nfunction getPartialFillGradientId(id: string, step: Step) {\n return `partial-fill-gradient-${id}-${step}`;\n}\n\nconst MAP_KEY_TO_FOCUS_INTENT: Record<string, FocusIntent> = {\n ArrowLeft: \"prev\",\n ArrowUp: \"prev\",\n ArrowRight: \"next\",\n ArrowDown: \"next\",\n Home: \"first\",\n End: \"last\"\n};\n\nfunction getDirectionAwareKey(key: string, dir?: Direction) {\n if (dir !== \"rtl\") return key;\n return key === \"ArrowLeft\" ? \"ArrowRight\" : key === \"ArrowRight\" ? \"ArrowLeft\" : key;\n}\n\nfunction getFocusIntent(event: React.KeyboardEvent<ItemElement>, dir?: Direction, orientation?: Orientation) {\n const key = getDirectionAwareKey(event.key, dir);\n if (orientation === \"horizontal\" && [\"ArrowUp\", \"ArrowDown\"].includes(key)) return undefined;\n if (orientation === \"vertical\" && [\"ArrowLeft\", \"ArrowRight\"].includes(key)) return undefined;\n return MAP_KEY_TO_FOCUS_INTENT[key];\n}\n\nfunction focusFirst(candidates: React.RefObject<ItemElement | null>[], preventScroll = false) {\n const PREVIOUSLY_FOCUSED_ELEMENT = document.activeElement;\n for (const candidateRef of candidates) {\n const candidate = candidateRef.current;\n if (!candidate) continue;\n if (candidate === PREVIOUSLY_FOCUSED_ELEMENT) return;\n candidate.focus({ preventScroll });\n if (document.activeElement !== PREVIOUSLY_FOCUSED_ELEMENT) return;\n }\n}\n\ninterface StoreState {\n value: number;\n hoveredValue: number | null;\n}\n\ninterface Store {\n subscribe: (callback: () => void) => () => void;\n getState: () => StoreState;\n setState: <K extends keyof StoreState>(key: K, value: StoreState[K]) => void;\n notify: () => void;\n}\n\nconst StoreContext = React.createContext<Store | null>(null);\n\nfunction useStoreContext(consumerName: string) {\n const context = React.useContext(StoreContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` phải nằm bên trong \\`${ROOT_NAME}\\``);\n }\n return context;\n}\n\nfunction useStore<T>(selector: (state: StoreState) => T, ogStore?: Store | null): T {\n const contextStore = React.useContext(StoreContext);\n\n const store = ogStore ?? contextStore;\n\n if (!store) {\n throw new Error(`\\`useRating\\` phải được gọi bên trong \\`${ROOT_NAME}\\``);\n }\n\n const getSnapshot = React.useCallback(() => selector(store.getState()), [store, selector]);\n\n return React.useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);\n}\n\ninterface ItemData {\n id: string;\n ref: React.RefObject<ItemElement | null>;\n value: number;\n disabled: boolean;\n}\n\ninterface RatingContextValue {\n rootId: string;\n dir: Direction;\n orientation: Orientation;\n activationMode: ActivationMode;\n size: Size;\n max: number;\n step: Step;\n clearable: boolean;\n disabled: boolean;\n readOnly: boolean;\n itemLabel: (value: number, max: number) => string;\n getAutoIndex: (instanceId: string) => number;\n}\n\nconst RatingContext = React.createContext<RatingContextValue | null>(null);\n\nfunction useRatingContext(consumerName: string) {\n const context = React.useContext(RatingContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` phải nằm bên trong \\`${ROOT_NAME}\\``);\n }\n return context;\n}\n\ninterface FocusContextValue {\n tabStopId: string | null;\n onItemFocus: (tabStopId: string) => void;\n onItemShiftTab: () => void;\n onFocusableItemAdd: () => void;\n onFocusableItemRemove: () => void;\n onItemRegister: (item: ItemData) => void;\n onItemUnregister: (id: string) => void;\n getItems: () => ItemData[];\n}\n\nconst FocusContext = React.createContext<FocusContextValue | null>(null);\n\nfunction useFocusContext(consumerName: string) {\n const context = React.useContext(FocusContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` phải nằm bên trong \\`FocusProvider\\``);\n }\n return context;\n}\n\ninterface RatingProps extends React.ComponentProps<\"div\"> {\n value?: number;\n defaultValue?: number;\n onValueChange?: (value: number) => void;\n onHover?: (value: number | null) => void;\n max?: number;\n activationMode?: ActivationMode;\n dir?: Direction;\n orientation?: Orientation;\n size?: Size;\n asChild?: boolean;\n step?: Step;\n clearable?: boolean;\n disabled?: boolean;\n readOnly?: boolean;\n required?: boolean;\n name?: string;\n /**\n * Tên của TỪNG ngôi sao cho trình đọc màn hình — mặc định `(value) => value + \" sao\"`.\n *\n * ⚠ Thêm so với thượng nguồn. Ở bản gốc mỗi `RatingItem` là một `role=\"radio\"` chỉ chứa một\n * `<svg>`, tức một nút KHÔNG TÊN: trình đọc màn hình đọc \"nút radio, 3 trên 5\" mà không nói 3\n * CÁI GÌ. Là HÀM chứ không phải chuỗi mẫu vì có số chèn vào giữa câu (README §5).\n *\n * Nhóm vẫn cần tên riêng: truyền `aria-label` (vd \"Mức hài lòng\") cho chính `Rating`.\n */\n itemLabel?: (value: number, max: number) => string;\n}\n\nconst defaultItemLabel = (value: number) => `${value} sao`;\n\nfunction Rating(props: RatingProps) {\n const {\n value: valueProp,\n defaultValue = 0,\n onValueChange,\n onHover,\n onFocus: onFocusProp,\n onMouseDown: onMouseDownProp,\n dir: dirProp,\n orientation = \"horizontal\",\n activationMode = \"automatic\",\n size = \"default\",\n max = 5,\n step = 1,\n clearable = false,\n asChild,\n disabled = false,\n readOnly = false,\n required = false,\n itemLabel = defaultItemLabel,\n className,\n children,\n id,\n name,\n ref,\n ...rootProps\n } = props;\n\n const dir = useDirection(dirProp);\n const instanceId = React.useId();\n const rootId = id ?? instanceId;\n\n const listenersRef = useLazyRef(() => new Set<() => void>());\n const stateRef = useLazyRef<StoreState>(() => ({\n value: valueProp ?? defaultValue,\n hoveredValue: null\n }));\n\n const propsRef = useAsRef({\n onValueChange,\n onHover,\n onFocus: onFocusProp,\n onMouseDown: onMouseDownProp,\n step\n });\n\n const store = React.useMemo<Store>(() => {\n return {\n subscribe: (cb) => {\n listenersRef.current.add(cb);\n return () => listenersRef.current.delete(cb);\n },\n getState: () => stateRef.current,\n setState: (key, value) => {\n if (Object.is(stateRef.current[key], value)) return;\n\n if (key === \"value\" && typeof value === \"number\") {\n stateRef.current.value = value;\n propsRef.current.onValueChange?.(value);\n } else if (key === \"hoveredValue\") {\n stateRef.current.hoveredValue = value as number | null;\n propsRef.current.onHover?.(value as number | null);\n } else {\n stateRef.current[key] = value;\n }\n\n store.notify();\n },\n notify: () => {\n for (const cb of listenersRef.current) {\n cb();\n }\n }\n };\n }, [listenersRef, stateRef, propsRef]);\n\n useIsomorphicLayoutEffect(() => {\n if (valueProp !== undefined) {\n store.setState(\"value\", valueProp);\n }\n }, [valueProp]);\n\n const value = useStore((state) => state.value, store);\n\n const [formTrigger, setFormTrigger] = React.useState<RootElement | null>(null);\n const composedRef = useComposedRefs(ref, (node) => setFormTrigger(node));\n const isFormControl = formTrigger ? !!formTrigger.closest(\"form\") : true;\n\n const [tabStopId, setTabStopId] = React.useState<string | null>(null);\n const [isTabbingBackOut, setIsTabbingBackOut] = React.useState(false);\n const [focusableItemCount, setFocusableItemCount] = React.useState(0);\n const isClickFocusRef = React.useRef(false);\n const itemsRef = React.useRef<Map<string, ItemData>>(new Map());\n\n const autoIndexMapRef = React.useRef(new Map<string, number>());\n const nextAutoIndexRef = React.useRef(0);\n\n const getAutoIndex = React.useCallback((instanceId: string) => {\n const existingIndex = autoIndexMapRef.current.get(instanceId);\n if (existingIndex !== undefined) {\n return existingIndex;\n }\n\n const newIndex = nextAutoIndexRef.current++;\n autoIndexMapRef.current.set(instanceId, newIndex);\n return newIndex;\n }, []);\n\n const onItemFocus = React.useCallback((tabStopId: string) => {\n setTabStopId(tabStopId);\n }, []);\n\n const onItemShiftTab = React.useCallback(() => {\n setIsTabbingBackOut(true);\n }, []);\n\n const onFocusableItemAdd = React.useCallback(() => {\n setFocusableItemCount((prevCount) => prevCount + 1);\n }, []);\n\n const onFocusableItemRemove = React.useCallback(() => {\n setFocusableItemCount((prevCount) => prevCount - 1);\n }, []);\n\n const onItemRegister = React.useCallback((item: ItemData) => {\n itemsRef.current.set(item.id, item);\n }, []);\n\n const onItemUnregister = React.useCallback((id: string) => {\n itemsRef.current.delete(id);\n }, []);\n\n const getItems = React.useCallback(() => {\n return Array.from(itemsRef.current.values())\n .filter((item) => item.ref.current)\n .sort((a, b) => {\n const elementA = a.ref.current;\n const elementB = b.ref.current;\n if (!elementA || !elementB) return 0;\n const position = elementA.compareDocumentPosition(elementB);\n if (position & Node.DOCUMENT_POSITION_FOLLOWING) {\n return -1;\n }\n if (position & Node.DOCUMENT_POSITION_PRECEDING) {\n return 1;\n }\n return 0;\n });\n }, []);\n\n const onBlur = React.useCallback(\n (event: React.FocusEvent<HTMLDivElement>) => {\n rootProps.onBlur?.(event);\n if (event.defaultPrevented) return;\n\n setIsTabbingBackOut(false);\n },\n [rootProps.onBlur]\n );\n\n const onFocus = React.useCallback(\n (event: React.FocusEvent<HTMLDivElement>) => {\n propsRef.current.onFocus?.(event);\n if (event.defaultPrevented) return;\n\n const isKeyboardFocus = !isClickFocusRef.current;\n if (event.target === event.currentTarget && isKeyboardFocus && !isTabbingBackOut) {\n const entryFocusEvent = new CustomEvent(ENTRY_FOCUS, EVENT_OPTIONS);\n event.currentTarget.dispatchEvent(entryFocusEvent);\n\n if (!entryFocusEvent.defaultPrevented) {\n const items = Array.from(itemsRef.current.values()).filter((item) => !item.disabled);\n // Nửa sao: tìm mục đại diện cho giá trị đang chọn bằng giá trị TRẦN (3,5 → mục 4).\n const selectedItem =\n propsRef.current.step < 1\n ? items.find((item) => item.value === Math.ceil(value))\n : items.find((item) => item.value === value);\n const currentItem = items.find((item) => item.id === tabStopId);\n\n const candidateItems = [selectedItem, currentItem, ...items].filter(Boolean) as ItemData[];\n const candidateRefs = candidateItems.map((item) => item.ref);\n focusFirst(candidateRefs, false);\n }\n }\n isClickFocusRef.current = false;\n },\n [propsRef, isTabbingBackOut, value, tabStopId]\n );\n\n const onMouseDown = React.useCallback(\n (event: React.MouseEvent<HTMLDivElement>) => {\n propsRef.current.onMouseDown?.(event);\n\n if (event.defaultPrevented) return;\n\n isClickFocusRef.current = true;\n },\n [propsRef]\n );\n\n const contextValue = React.useMemo<RatingContextValue>(\n () => ({\n rootId,\n dir,\n orientation,\n activationMode,\n disabled,\n readOnly,\n size,\n max,\n step,\n clearable,\n itemLabel,\n getAutoIndex\n }),\n [rootId, dir, orientation, activationMode, disabled, readOnly, size, max, step, clearable, itemLabel, getAutoIndex]\n );\n\n const focusContextValue = React.useMemo<FocusContextValue>(\n () => ({\n tabStopId,\n onItemFocus,\n onItemShiftTab,\n onFocusableItemAdd,\n onFocusableItemRemove,\n onItemRegister,\n onItemUnregister,\n getItems\n }),\n [\n tabStopId,\n onItemFocus,\n onItemShiftTab,\n onFocusableItemAdd,\n onFocusableItemRemove,\n onItemRegister,\n onItemUnregister,\n getItems\n ]\n );\n\n const RootPrimitive = asChild ? Slot : \"div\";\n\n /*\n * ⚠ Khác thượng nguồn: gradient của NỬA SAO phải nằm BÊN TRONG phần tử gốc. Trong SVG,\n * `stopColor=\"currentColor\"` lấy `color` tính ở chỗ gradient được KHAI, không phải ở chỗ nó được\n * dùng — bản gốc đặt `<svg>` này làm anh em của phần tử gốc, nên nửa sao ăn màu chữ của trang\n * (đo trên trình duyệt: #0E1116 ở light, #F7F7F8 ở dark) thay vì màu sao `text-primary-ink`.\n * Với `asChild` thì Slot không nhận thêm con được, nên gradient đành đứng ngoài như bản gốc —\n * khi đó app phải tự đặt màu chữ cho phần tử BAO NGOÀI.\n */\n const partialFillGradient = (\n <svg width=\"0\" height=\"0\" aria-hidden=\"true\" focusable=\"false\" style={{ position: \"absolute\" }}>\n <defs>\n <linearGradient id={getPartialFillGradientId(rootId, step)}>\n {dir === \"rtl\" ? (\n <>\n <stop offset=\"50%\" stopColor=\"transparent\" />\n <stop offset=\"50%\" stopColor=\"currentColor\" />\n </>\n ) : (\n <>\n <stop offset=\"50%\" stopColor=\"currentColor\" />\n <stop offset=\"50%\" stopColor=\"transparent\" />\n </>\n )}\n </linearGradient>\n </defs>\n </svg>\n );\n\n return (\n <StoreContext.Provider value={store}>\n <RatingContext.Provider value={contextValue}>\n <FocusContext.Provider value={focusContextValue}>\n <RootPrimitive\n id={rootId}\n role=\"radiogroup\"\n aria-orientation={orientation}\n aria-disabled={disabled || undefined}\n aria-readonly={readOnly || undefined}\n aria-required={required || undefined}\n data-disabled={disabled ? \"\" : undefined}\n data-readonly={readOnly ? \"\" : undefined}\n data-orientation={orientation}\n data-slot=\"rating\"\n dir={dir}\n tabIndex={isTabbingBackOut || focusableItemCount === 0 ? -1 : 0}\n {...rootProps}\n ref={composedRef}\n className={cn(\n /*\n * Ngôi sao là thành phần PHI VĂN BẢN mang nghĩa (nó LÀ giá trị) → màu đi qua bậc\n * `-ink` của tone, không phải token tô nền. Bản gốc `text-primary` đọc thẳng màu\n * TÔ NỀN; ở bảng mặc định hai token trùng nhau (`--primary-ink: var(--primary)`),\n * nhưng bảng tương phản cao kéo `--primary-ink` xuống bậc tối — và chỉ `-ink` mới\n * được đổi theo. Hợp đồng §1 của docs/hop-dong-mau.md.\n */\n \"flex gap-1 text-primary-ink outline-none\",\n orientation === \"horizontal\" ? \"flex-row items-center\" : \"flex-col items-start\",\n className\n )}\n onBlur={onBlur}\n onFocus={onFocus}\n onMouseDown={onMouseDown}\n >\n {asChild ? (\n children\n ) : (\n <>\n {children}\n {partialFillGradient}\n </>\n )}\n </RootPrimitive>\n {asChild ? partialFillGradient : null}\n {isFormControl && (\n <VisuallyHiddenInput\n type=\"hidden\"\n control={formTrigger}\n name={name}\n value={value}\n disabled={disabled}\n readOnly={readOnly}\n required={required}\n />\n )}\n </FocusContext.Provider>\n </RatingContext.Provider>\n </StoreContext.Provider>\n );\n}\n\ninterface RatingItemProps extends Omit<React.ComponentProps<\"button\">, \"children\"> {\n index?: number;\n asChild?: boolean;\n children?: React.ReactNode | ((dataState: DataState) => React.ReactNode);\n}\n\nfunction RatingItem(props: RatingItemProps) {\n const {\n index,\n asChild,\n onClick: onClickProp,\n onFocus: onFocusProp,\n onKeyDown: onKeyDownProp,\n onMouseDown: onMouseDownProp,\n onMouseEnter: onMouseEnterProp,\n onMouseMove: onMouseMoveProp,\n onMouseLeave: onMouseLeaveProp,\n disabled,\n className,\n children,\n ref,\n ...itemProps\n } = props;\n\n const itemRef = React.useRef<ItemElement>(null);\n const composedRef = useComposedRefs(ref, itemRef);\n\n const context = useRatingContext(ITEM_NAME);\n\n const instanceId = React.useId();\n\n const actualIndex = React.useMemo(() => {\n if (index !== undefined) {\n return index;\n }\n\n return context.getAutoIndex(instanceId);\n }, [index, context, instanceId]);\n\n const itemValue = actualIndex + 1;\n const store = useStoreContext(ITEM_NAME);\n const focusContext = useFocusContext(ITEM_NAME);\n const value = useStore((state) => state.value);\n const hoveredValue = useStore((state) => state.hoveredValue);\n const clearable = context.clearable;\n const step = context.step;\n const activationMode = context.activationMode;\n\n const itemId = getItemId(context.rootId, itemValue);\n const isDisabled = context.disabled || disabled;\n const isReadOnly = context.readOnly;\n const isTabStop = focusContext.tabStopId === itemId;\n\n const displayValue = hoveredValue ?? value;\n const isFilled = displayValue >= itemValue;\n const isPartiallyFilled = step < 1 && displayValue >= itemValue - step && displayValue < itemValue;\n const isHovered = hoveredValue !== null && hoveredValue < itemValue;\n /*\n * ⚠ Khác thượng nguồn: bản gốc đặt `aria-checked={isFilled}`, tức chọn 4 sao thì BỐN nút radio\n * cùng báo \"đã chọn\" — một radiogroup có nhiều radio được chọn là trạng thái không tồn tại với\n * trình đọc màn hình. Ở đây chỉ đúng MỘT mục được chọn: mục chứa giá trị (3,5 → mục 4), và đọc\n * theo `value` thật chứ không theo sao đang được rê chuột (`displayValue`).\n */\n const isChecked = value > 0 && Math.ceil(value) === itemValue;\n\n const isMouseClickRef = React.useRef(false);\n\n const propsRef = useAsRef({\n onClick: onClickProp,\n onFocus: onFocusProp,\n onKeyDown: onKeyDownProp,\n onMouseDown: onMouseDownProp,\n onMouseEnter: onMouseEnterProp,\n onMouseMove: onMouseMoveProp,\n onMouseLeave: onMouseLeaveProp\n });\n\n useIsomorphicLayoutEffect(() => {\n focusContext.onItemRegister({\n id: itemId,\n ref: itemRef,\n value: itemValue,\n disabled: !!isDisabled\n });\n\n if (!isDisabled) {\n focusContext.onFocusableItemAdd();\n }\n\n return () => {\n focusContext.onItemUnregister(itemId);\n if (!isDisabled) {\n focusContext.onFocusableItemRemove();\n }\n };\n }, [focusContext, itemId, itemValue, isDisabled]);\n\n const onClick = React.useCallback(\n (event: React.MouseEvent<ItemElement>) => {\n propsRef.current.onClick?.(event);\n if (event.defaultPrevented) return;\n\n if (!isDisabled && !isReadOnly) {\n let newValue = itemValue;\n\n if (step < 1) {\n const rect = event.currentTarget.getBoundingClientRect();\n const clickX = event.clientX - rect.left;\n const isLeftHalf = clickX < rect.width / 2;\n\n if (context.dir === \"rtl\") {\n if (!isLeftHalf) {\n newValue = itemValue - step;\n }\n } else {\n if (isLeftHalf) {\n newValue = itemValue - step;\n }\n }\n }\n\n if (clearable && value === newValue) {\n newValue = 0;\n }\n\n store.setState(\"value\", newValue);\n }\n },\n [isDisabled, isReadOnly, clearable, step, value, itemValue, store, context.dir, propsRef]\n );\n\n const onFocus = React.useCallback(\n (event: React.FocusEvent<ItemElement>) => {\n propsRef.current.onFocus?.(event);\n if (event.defaultPrevented) return;\n\n focusContext.onItemFocus(itemId);\n\n const isKeyboardFocus = !isMouseClickRef.current;\n\n if (!isDisabled && !isReadOnly && activationMode !== \"manual\" && isKeyboardFocus) {\n // Nửa sao: nếu giá trị đang là nửa-bậc THUỘC mục này (3,5 khi mục là 4) thì đừng đổi nó.\n const isHalfStepValue = step < 1 && value === itemValue - step;\n\n if (!isHalfStepValue) {\n const newValue = clearable && value === itemValue ? 0 : itemValue;\n store.setState(\"value\", newValue);\n }\n }\n\n isMouseClickRef.current = false;\n },\n [focusContext, itemId, activationMode, isDisabled, isReadOnly, clearable, value, itemValue, step, store, propsRef]\n );\n\n const onKeyDown = React.useCallback(\n (event: React.KeyboardEvent<ItemElement>) => {\n propsRef.current.onKeyDown?.(event);\n if (event.defaultPrevented) return;\n\n if ((event.key === \"Enter\" || event.key === \" \") && activationMode === \"manual\") {\n event.preventDefault();\n if (!isDisabled && !isReadOnly && itemRef.current) {\n itemRef.current.click();\n }\n return;\n }\n\n if (event.key === \"Tab\" && event.shiftKey) {\n focusContext.onItemShiftTab();\n return;\n }\n\n if (event.target !== event.currentTarget) return;\n\n const focusIntent = getFocusIntent(event, context.dir, context.orientation);\n\n if (focusIntent !== undefined) {\n if (event.metaKey || event.ctrlKey || event.altKey || event.shiftKey) return;\n event.preventDefault();\n\n // Nửa sao: mũi tên tăng/giảm đúng một `step` thay vì nhảy sang mục kế.\n if (step < 1 && (focusIntent === \"prev\" || focusIntent === \"next\")) {\n if (!isDisabled && !isReadOnly) {\n let newValue = value;\n\n if (focusIntent === \"next\") {\n newValue = Math.min(value + step, context.max);\n } else {\n newValue = Math.max(value - step, 0);\n }\n\n store.setState(\"value\", newValue);\n\n // Đưa focus tới mục đại diện cho giá trị mới.\n const items = focusContext.getItems().filter((item) => !item.disabled);\n const targetItem = items.find((item) => item.value === Math.ceil(newValue));\n if (targetItem?.ref.current) {\n queueMicrotask(() => targetItem.ref.current?.focus());\n }\n }\n return;\n }\n\n // Bậc nguyên, hoặc Home/End: điều hướng như thường.\n const items = focusContext.getItems().filter((item) => !item.disabled);\n let candidateRefs = items.map((item) => item.ref);\n\n if (focusIntent === \"last\") {\n candidateRefs.reverse();\n } else if (focusIntent === \"prev\" || focusIntent === \"next\") {\n if (focusIntent === \"prev\") candidateRefs.reverse();\n const currentIndex = candidateRefs.findIndex((ref) => ref.current === event.currentTarget);\n candidateRefs = candidateRefs.slice(currentIndex + 1);\n }\n\n queueMicrotask(() => focusFirst(candidateRefs));\n }\n },\n [\n focusContext,\n context.dir,\n context.orientation,\n activationMode,\n isDisabled,\n isReadOnly,\n step,\n value,\n context.max,\n store,\n propsRef\n ]\n );\n\n const onMouseDown = React.useCallback(\n (event: React.MouseEvent<ItemElement>) => {\n propsRef.current.onMouseDown?.(event);\n if (event.defaultPrevented) return;\n\n isMouseClickRef.current = true;\n\n if (isDisabled) {\n event.preventDefault();\n } else {\n focusContext.onItemFocus(itemId);\n }\n },\n [focusContext, itemId, isDisabled, propsRef]\n );\n\n const onMouseEnter = React.useCallback(\n (event: React.MouseEvent<ItemElement>) => {\n propsRef.current.onMouseEnter?.(event);\n if (event.defaultPrevented) return;\n\n if (!isDisabled && !isReadOnly) {\n let hoverValue = itemValue;\n\n if (step < 1) {\n const rect = event.currentTarget.getBoundingClientRect();\n const mouseX = event.clientX - rect.left;\n const isLeftHalf = mouseX < rect.width / 2;\n\n if (context.dir === \"rtl\") {\n if (!isLeftHalf) {\n hoverValue = itemValue - step;\n }\n } else {\n if (isLeftHalf) {\n hoverValue = itemValue - step;\n }\n }\n }\n\n store.setState(\"hoveredValue\", hoverValue);\n }\n },\n [isDisabled, isReadOnly, step, itemValue, store, context.dir, propsRef]\n );\n\n const onMouseLeave = React.useCallback(\n (event: React.MouseEvent<ItemElement>) => {\n propsRef.current.onMouseLeave?.(event);\n if (event.defaultPrevented) return;\n\n if (!isDisabled && !isReadOnly) {\n store.setState(\"hoveredValue\", null);\n }\n },\n [isDisabled, isReadOnly, store, propsRef]\n );\n\n const onMouseMove = React.useCallback(\n (event: React.MouseEvent<ItemElement>) => {\n propsRef.current.onMouseMove?.(event);\n if (event.defaultPrevented) return;\n\n if (!isDisabled && !isReadOnly && step < 1) {\n const rect = event.currentTarget.getBoundingClientRect();\n const mouseX = event.clientX - rect.left;\n const isLeftHalf = mouseX < rect.width / 2;\n\n let hoverValue = itemValue;\n if (context.dir === \"rtl\") {\n hoverValue = !isLeftHalf ? itemValue - step : itemValue;\n } else {\n hoverValue = isLeftHalf ? itemValue - step : itemValue;\n }\n\n store.setState(\"hoveredValue\", hoverValue);\n }\n },\n [isDisabled, isReadOnly, step, itemValue, store, context.dir, propsRef]\n );\n\n const dataState: DataState = isFilled ? \"full\" : isPartiallyFilled ? \"partial\" : \"empty\";\n\n const ItemPrimitive = asChild ? Slot : \"button\";\n\n return (\n <ItemPrimitive\n role=\"radio\"\n type=\"button\"\n id={itemId}\n aria-label={context.itemLabel(itemValue, context.max)}\n aria-checked={isChecked}\n aria-posinset={itemValue}\n aria-setsize={context.max}\n data-disabled={isDisabled ? \"\" : undefined}\n data-readonly={isReadOnly ? \"\" : undefined}\n data-state={dataState}\n data-hovered={isHovered ? \"\" : undefined}\n data-slot=\"rating-item\"\n disabled={isDisabled}\n tabIndex={isTabStop ? 0 : -1}\n {...itemProps}\n ref={composedRef}\n style={{\n ...itemProps.style,\n ...(isPartiallyFilled && {\n \"--partial-fill\": `url(#${getPartialFillGradientId(context.rootId, step)})`\n })\n }}\n className={cn(\n // Viền focus cùng khuôn với `Checkbox`/`RadioGroup` của gói.\n \"inline-flex items-center justify-center rounded transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50\",\n \"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg]:transition-colors [&_svg]:duration-200 data-[state=empty]:[&_svg]:fill-transparent data-[state=full]:[&_svg]:fill-current data-[state=partial]:[&_svg]:fill-(--partial-fill) [&_svg:not([class*='size-'])]:size-full\",\n context.size === \"sm\" ? \"size-4\" : context.size === \"lg\" ? \"size-6\" : \"size-5\",\n className\n )}\n onClick={onClick}\n onFocus={onFocus}\n onKeyDown={onKeyDown}\n onMouseDown={onMouseDown}\n onMouseEnter={onMouseEnter}\n onMouseMove={onMouseMove}\n onMouseLeave={onMouseLeave}\n >\n {typeof children === \"function\" ? children(dataState) : (children ?? <Star aria-hidden=\"true\" />)}\n </ItemPrimitive>\n );\n}\n\nexport { Rating, RatingItem, type RatingItemProps, type RatingProps, useStore as useRating };\n"],"mappings":"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","names":["value","instanceId","tabStopId","id","items","ref"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/components/ui/rating.tsx"],"sourcesContent":["\"use client\";\n\n/*\n * Port từ Dice UI (https://diceui.com/docs/components/rating) — MIT, © 2024 Sadman Sakib.\n * Giữ nguyên cấu trúc thượng nguồn để còn đối chiếu ngược được; mọi chỗ đổi đều có chú thích.\n */\n\nimport { useDirection } from \"@radix-ui/react-direction\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { Star } from \"lucide-react\";\nimport * as React from \"react\";\nimport { useComitorMessages } from \"../../i18n/locale-provider.js\";\nimport { cn } from \"../../lib/cn.js\";\nimport { useComposedRefs } from \"../../lib/compose-refs.js\";\nimport { useAsRef } from \"../../lib/use-as-ref.js\";\nimport { useIsomorphicLayoutEffect } from \"../../lib/use-isomorphic-layout-effect.js\";\nimport { useLazyRef } from \"../../lib/use-lazy-ref.js\";\nimport { VisuallyHiddenInput } from \"../../lib/visually-hidden-input.js\";\n\ntype Direction = \"ltr\" | \"rtl\";\ntype Orientation = \"horizontal\" | \"vertical\";\ntype ActivationMode = \"automatic\" | \"manual\";\ntype Size = \"default\" | \"sm\" | \"lg\";\ntype Step = 0.5 | 1;\ntype DataState = \"full\" | \"partial\" | \"empty\";\ntype FocusIntent = \"first\" | \"last\" | \"prev\" | \"next\";\n\ntype RootElement = React.ComponentRef<typeof Rating>;\ntype ItemElement = React.ComponentRef<typeof RatingItem>;\n\nconst ROOT_NAME = \"Rating\";\nconst ITEM_NAME = \"RatingItem\";\n\nconst ENTRY_FOCUS = \"ratingFocusGroup.onEntryFocus\";\nconst EVENT_OPTIONS = { bubbles: false, cancelable: true };\n\nfunction getItemId(id: string, value: number) {\n return `${id}-item-${value}`;\n}\n\nfunction getPartialFillGradientId(id: string, step: Step) {\n return `partial-fill-gradient-${id}-${step}`;\n}\n\nconst MAP_KEY_TO_FOCUS_INTENT: Record<string, FocusIntent> = {\n ArrowLeft: \"prev\",\n ArrowUp: \"prev\",\n ArrowRight: \"next\",\n ArrowDown: \"next\",\n Home: \"first\",\n End: \"last\"\n};\n\nfunction getDirectionAwareKey(key: string, dir?: Direction) {\n if (dir !== \"rtl\") return key;\n return key === \"ArrowLeft\" ? \"ArrowRight\" : key === \"ArrowRight\" ? \"ArrowLeft\" : key;\n}\n\nfunction getFocusIntent(event: React.KeyboardEvent<ItemElement>, dir?: Direction, orientation?: Orientation) {\n const key = getDirectionAwareKey(event.key, dir);\n if (orientation === \"horizontal\" && [\"ArrowUp\", \"ArrowDown\"].includes(key)) return undefined;\n if (orientation === \"vertical\" && [\"ArrowLeft\", \"ArrowRight\"].includes(key)) return undefined;\n return MAP_KEY_TO_FOCUS_INTENT[key];\n}\n\nfunction focusFirst(candidates: React.RefObject<ItemElement | null>[], preventScroll = false) {\n const PREVIOUSLY_FOCUSED_ELEMENT = document.activeElement;\n for (const candidateRef of candidates) {\n const candidate = candidateRef.current;\n if (!candidate) continue;\n if (candidate === PREVIOUSLY_FOCUSED_ELEMENT) return;\n candidate.focus({ preventScroll });\n if (document.activeElement !== PREVIOUSLY_FOCUSED_ELEMENT) return;\n }\n}\n\ninterface StoreState {\n value: number;\n hoveredValue: number | null;\n}\n\ninterface Store {\n subscribe: (callback: () => void) => () => void;\n getState: () => StoreState;\n setState: <K extends keyof StoreState>(key: K, value: StoreState[K]) => void;\n notify: () => void;\n}\n\nconst StoreContext = React.createContext<Store | null>(null);\n\nfunction useStoreContext(consumerName: string) {\n const context = React.useContext(StoreContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` phải nằm bên trong \\`${ROOT_NAME}\\``);\n }\n return context;\n}\n\nfunction useStore<T>(selector: (state: StoreState) => T, ogStore?: Store | null): T {\n const contextStore = React.useContext(StoreContext);\n\n const store = ogStore ?? contextStore;\n\n if (!store) {\n throw new Error(`\\`useRating\\` phải được gọi bên trong \\`${ROOT_NAME}\\``);\n }\n\n const getSnapshot = React.useCallback(() => selector(store.getState()), [store, selector]);\n\n return React.useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);\n}\n\ninterface ItemData {\n id: string;\n ref: React.RefObject<ItemElement | null>;\n value: number;\n disabled: boolean;\n}\n\ninterface RatingContextValue {\n rootId: string;\n dir: Direction;\n orientation: Orientation;\n activationMode: ActivationMode;\n size: Size;\n max: number;\n step: Step;\n clearable: boolean;\n disabled: boolean;\n readOnly: boolean;\n itemLabel: (value: number, max: number) => string;\n getAutoIndex: (instanceId: string) => number;\n}\n\nconst RatingContext = React.createContext<RatingContextValue | null>(null);\n\nfunction useRatingContext(consumerName: string) {\n const context = React.useContext(RatingContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` phải nằm bên trong \\`${ROOT_NAME}\\``);\n }\n return context;\n}\n\ninterface FocusContextValue {\n tabStopId: string | null;\n onItemFocus: (tabStopId: string) => void;\n onItemShiftTab: () => void;\n onFocusableItemAdd: () => void;\n onFocusableItemRemove: () => void;\n onItemRegister: (item: ItemData) => void;\n onItemUnregister: (id: string) => void;\n getItems: () => ItemData[];\n}\n\nconst FocusContext = React.createContext<FocusContextValue | null>(null);\n\nfunction useFocusContext(consumerName: string) {\n const context = React.useContext(FocusContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` phải nằm bên trong \\`FocusProvider\\``);\n }\n return context;\n}\n\ninterface RatingProps extends React.ComponentProps<\"div\"> {\n value?: number;\n defaultValue?: number;\n onValueChange?: (value: number) => void;\n onHover?: (value: number | null) => void;\n max?: number;\n activationMode?: ActivationMode;\n dir?: Direction;\n orientation?: Orientation;\n size?: Size;\n asChild?: boolean;\n step?: Step;\n clearable?: boolean;\n disabled?: boolean;\n readOnly?: boolean;\n required?: boolean;\n name?: string;\n /**\n * Tên của TỪNG ngôi sao cho trình đọc màn hình — mặc định khoá `itemLabel` của namespace\n * `rating` (`ComitorLocaleProvider`).\n *\n * ⚠ Thêm so với thượng nguồn. Ở bản gốc mỗi `RatingItem` là một `role=\"radio\"` chỉ chứa một\n * `<svg>`, tức một nút KHÔNG TÊN: trình đọc màn hình đọc \"nút radio, 3 trên 5\" mà không nói 3\n * CÁI GÌ. Là HÀM chứ không phải chuỗi mẫu vì có số chèn vào giữa câu (README §5).\n *\n * Nhóm vẫn cần tên riêng: truyền `aria-label` (vd \"Mức hài lòng\") cho chính `Rating`.\n */\n itemLabel?: (value: number, max: number) => string;\n}\n\nfunction Rating(props: RatingProps) {\n const {\n value: valueProp,\n defaultValue = 0,\n onValueChange,\n onHover,\n onFocus: onFocusProp,\n onMouseDown: onMouseDownProp,\n dir: dirProp,\n orientation = \"horizontal\",\n activationMode = \"automatic\",\n size = \"default\",\n max = 5,\n step = 1,\n clearable = false,\n asChild,\n disabled = false,\n readOnly = false,\n required = false,\n itemLabel: itemLabelProp,\n className,\n children,\n id,\n name,\n ref,\n ...rootProps\n } = props;\n\n const t = useComitorMessages(\"rating\");\n const itemLabel = itemLabelProp ?? t.itemLabel;\n const dir = useDirection(dirProp);\n const instanceId = React.useId();\n const rootId = id ?? instanceId;\n\n const listenersRef = useLazyRef(() => new Set<() => void>());\n const stateRef = useLazyRef<StoreState>(() => ({\n value: valueProp ?? defaultValue,\n hoveredValue: null\n }));\n\n const propsRef = useAsRef({\n onValueChange,\n onHover,\n onFocus: onFocusProp,\n onMouseDown: onMouseDownProp,\n step\n });\n\n const store = React.useMemo<Store>(() => {\n return {\n subscribe: (cb) => {\n listenersRef.current.add(cb);\n return () => listenersRef.current.delete(cb);\n },\n getState: () => stateRef.current,\n setState: (key, value) => {\n if (Object.is(stateRef.current[key], value)) return;\n\n if (key === \"value\" && typeof value === \"number\") {\n stateRef.current.value = value;\n propsRef.current.onValueChange?.(value);\n } else if (key === \"hoveredValue\") {\n stateRef.current.hoveredValue = value as number | null;\n propsRef.current.onHover?.(value as number | null);\n } else {\n stateRef.current[key] = value;\n }\n\n store.notify();\n },\n notify: () => {\n for (const cb of listenersRef.current) {\n cb();\n }\n }\n };\n }, [listenersRef, stateRef, propsRef]);\n\n useIsomorphicLayoutEffect(() => {\n if (valueProp !== undefined) {\n store.setState(\"value\", valueProp);\n }\n }, [valueProp]);\n\n const value = useStore((state) => state.value, store);\n\n const [formTrigger, setFormTrigger] = React.useState<RootElement | null>(null);\n const composedRef = useComposedRefs(ref, (node) => setFormTrigger(node));\n const isFormControl = formTrigger ? !!formTrigger.closest(\"form\") : true;\n\n const [tabStopId, setTabStopId] = React.useState<string | null>(null);\n const [isTabbingBackOut, setIsTabbingBackOut] = React.useState(false);\n const [focusableItemCount, setFocusableItemCount] = React.useState(0);\n const isClickFocusRef = React.useRef(false);\n const itemsRef = React.useRef<Map<string, ItemData>>(new Map());\n\n const autoIndexMapRef = React.useRef(new Map<string, number>());\n const nextAutoIndexRef = React.useRef(0);\n\n const getAutoIndex = React.useCallback((instanceId: string) => {\n const existingIndex = autoIndexMapRef.current.get(instanceId);\n if (existingIndex !== undefined) {\n return existingIndex;\n }\n\n const newIndex = nextAutoIndexRef.current++;\n autoIndexMapRef.current.set(instanceId, newIndex);\n return newIndex;\n }, []);\n\n const onItemFocus = React.useCallback((tabStopId: string) => {\n setTabStopId(tabStopId);\n }, []);\n\n const onItemShiftTab = React.useCallback(() => {\n setIsTabbingBackOut(true);\n }, []);\n\n const onFocusableItemAdd = React.useCallback(() => {\n setFocusableItemCount((prevCount) => prevCount + 1);\n }, []);\n\n const onFocusableItemRemove = React.useCallback(() => {\n setFocusableItemCount((prevCount) => prevCount - 1);\n }, []);\n\n const onItemRegister = React.useCallback((item: ItemData) => {\n itemsRef.current.set(item.id, item);\n }, []);\n\n const onItemUnregister = React.useCallback((id: string) => {\n itemsRef.current.delete(id);\n }, []);\n\n const getItems = React.useCallback(() => {\n return Array.from(itemsRef.current.values())\n .filter((item) => item.ref.current)\n .sort((a, b) => {\n const elementA = a.ref.current;\n const elementB = b.ref.current;\n if (!elementA || !elementB) return 0;\n const position = elementA.compareDocumentPosition(elementB);\n if (position & Node.DOCUMENT_POSITION_FOLLOWING) {\n return -1;\n }\n if (position & Node.DOCUMENT_POSITION_PRECEDING) {\n return 1;\n }\n return 0;\n });\n }, []);\n\n const onBlur = React.useCallback(\n (event: React.FocusEvent<HTMLDivElement>) => {\n rootProps.onBlur?.(event);\n if (event.defaultPrevented) return;\n\n setIsTabbingBackOut(false);\n },\n [rootProps.onBlur]\n );\n\n const onFocus = React.useCallback(\n (event: React.FocusEvent<HTMLDivElement>) => {\n propsRef.current.onFocus?.(event);\n if (event.defaultPrevented) return;\n\n const isKeyboardFocus = !isClickFocusRef.current;\n if (event.target === event.currentTarget && isKeyboardFocus && !isTabbingBackOut) {\n const entryFocusEvent = new CustomEvent(ENTRY_FOCUS, EVENT_OPTIONS);\n event.currentTarget.dispatchEvent(entryFocusEvent);\n\n if (!entryFocusEvent.defaultPrevented) {\n const items = Array.from(itemsRef.current.values()).filter((item) => !item.disabled);\n // Nửa sao: tìm mục đại diện cho giá trị đang chọn bằng giá trị TRẦN (3,5 → mục 4).\n const selectedItem =\n propsRef.current.step < 1\n ? items.find((item) => item.value === Math.ceil(value))\n : items.find((item) => item.value === value);\n const currentItem = items.find((item) => item.id === tabStopId);\n\n const candidateItems = [selectedItem, currentItem, ...items].filter(Boolean) as ItemData[];\n const candidateRefs = candidateItems.map((item) => item.ref);\n focusFirst(candidateRefs, false);\n }\n }\n isClickFocusRef.current = false;\n },\n [propsRef, isTabbingBackOut, value, tabStopId]\n );\n\n const onMouseDown = React.useCallback(\n (event: React.MouseEvent<HTMLDivElement>) => {\n propsRef.current.onMouseDown?.(event);\n\n if (event.defaultPrevented) return;\n\n isClickFocusRef.current = true;\n },\n [propsRef]\n );\n\n const contextValue = React.useMemo<RatingContextValue>(\n () => ({\n rootId,\n dir,\n orientation,\n activationMode,\n disabled,\n readOnly,\n size,\n max,\n step,\n clearable,\n itemLabel,\n getAutoIndex\n }),\n [rootId, dir, orientation, activationMode, disabled, readOnly, size, max, step, clearable, itemLabel, getAutoIndex]\n );\n\n const focusContextValue = React.useMemo<FocusContextValue>(\n () => ({\n tabStopId,\n onItemFocus,\n onItemShiftTab,\n onFocusableItemAdd,\n onFocusableItemRemove,\n onItemRegister,\n onItemUnregister,\n getItems\n }),\n [\n tabStopId,\n onItemFocus,\n onItemShiftTab,\n onFocusableItemAdd,\n onFocusableItemRemove,\n onItemRegister,\n onItemUnregister,\n getItems\n ]\n );\n\n const RootPrimitive = asChild ? Slot : \"div\";\n\n /*\n * ⚠ Khác thượng nguồn: gradient của NỬA SAO phải nằm BÊN TRONG phần tử gốc. Trong SVG,\n * `stopColor=\"currentColor\"` lấy `color` tính ở chỗ gradient được KHAI, không phải ở chỗ nó được\n * dùng — bản gốc đặt `<svg>` này làm anh em của phần tử gốc, nên nửa sao ăn màu chữ của trang\n * (đo trên trình duyệt: #0E1116 ở light, #F7F7F8 ở dark) thay vì màu sao `text-primary-ink`.\n * Với `asChild` thì Slot không nhận thêm con được, nên gradient đành đứng ngoài như bản gốc —\n * khi đó app phải tự đặt màu chữ cho phần tử BAO NGOÀI.\n */\n const partialFillGradient = (\n <svg width=\"0\" height=\"0\" aria-hidden=\"true\" focusable=\"false\" style={{ position: \"absolute\" }}>\n <defs>\n <linearGradient id={getPartialFillGradientId(rootId, step)}>\n {dir === \"rtl\" ? (\n <>\n <stop offset=\"50%\" stopColor=\"transparent\" />\n <stop offset=\"50%\" stopColor=\"currentColor\" />\n </>\n ) : (\n <>\n <stop offset=\"50%\" stopColor=\"currentColor\" />\n <stop offset=\"50%\" stopColor=\"transparent\" />\n </>\n )}\n </linearGradient>\n </defs>\n </svg>\n );\n\n return (\n <StoreContext.Provider value={store}>\n <RatingContext.Provider value={contextValue}>\n <FocusContext.Provider value={focusContextValue}>\n <RootPrimitive\n id={rootId}\n role=\"radiogroup\"\n aria-orientation={orientation}\n aria-disabled={disabled || undefined}\n aria-readonly={readOnly || undefined}\n aria-required={required || undefined}\n data-disabled={disabled ? \"\" : undefined}\n data-readonly={readOnly ? \"\" : undefined}\n data-orientation={orientation}\n data-slot=\"rating\"\n dir={dir}\n tabIndex={isTabbingBackOut || focusableItemCount === 0 ? -1 : 0}\n {...rootProps}\n ref={composedRef}\n className={cn(\n /*\n * Ngôi sao là thành phần PHI VĂN BẢN mang nghĩa (nó LÀ giá trị) → màu đi qua bậc\n * `-ink` của tone, không phải token tô nền. Bản gốc `text-primary` đọc thẳng màu\n * TÔ NỀN; ở bảng mặc định hai token trùng nhau (`--primary-ink: var(--primary)`),\n * nhưng bảng tương phản cao kéo `--primary-ink` xuống bậc tối — và chỉ `-ink` mới\n * được đổi theo. Hợp đồng §1 của docs/hop-dong-mau.md.\n */\n \"flex gap-1 text-primary-ink outline-none\",\n orientation === \"horizontal\" ? \"flex-row items-center\" : \"flex-col items-start\",\n className\n )}\n onBlur={onBlur}\n onFocus={onFocus}\n onMouseDown={onMouseDown}\n >\n {asChild ? (\n children\n ) : (\n <>\n {children}\n {partialFillGradient}\n </>\n )}\n </RootPrimitive>\n {asChild ? partialFillGradient : null}\n {isFormControl && (\n <VisuallyHiddenInput\n type=\"hidden\"\n control={formTrigger}\n name={name}\n value={value}\n disabled={disabled}\n readOnly={readOnly}\n required={required}\n />\n )}\n </FocusContext.Provider>\n </RatingContext.Provider>\n </StoreContext.Provider>\n );\n}\n\ninterface RatingItemProps extends Omit<React.ComponentProps<\"button\">, \"children\"> {\n index?: number;\n asChild?: boolean;\n children?: React.ReactNode | ((dataState: DataState) => React.ReactNode);\n}\n\nfunction RatingItem(props: RatingItemProps) {\n const {\n index,\n asChild,\n onClick: onClickProp,\n onFocus: onFocusProp,\n onKeyDown: onKeyDownProp,\n onMouseDown: onMouseDownProp,\n onMouseEnter: onMouseEnterProp,\n onMouseMove: onMouseMoveProp,\n onMouseLeave: onMouseLeaveProp,\n disabled,\n className,\n children,\n ref,\n ...itemProps\n } = props;\n\n const itemRef = React.useRef<ItemElement>(null);\n const composedRef = useComposedRefs(ref, itemRef);\n\n const context = useRatingContext(ITEM_NAME);\n\n const instanceId = React.useId();\n\n const actualIndex = React.useMemo(() => {\n if (index !== undefined) {\n return index;\n }\n\n return context.getAutoIndex(instanceId);\n }, [index, context, instanceId]);\n\n const itemValue = actualIndex + 1;\n const store = useStoreContext(ITEM_NAME);\n const focusContext = useFocusContext(ITEM_NAME);\n const value = useStore((state) => state.value);\n const hoveredValue = useStore((state) => state.hoveredValue);\n const clearable = context.clearable;\n const step = context.step;\n const activationMode = context.activationMode;\n\n const itemId = getItemId(context.rootId, itemValue);\n const isDisabled = context.disabled || disabled;\n const isReadOnly = context.readOnly;\n const isTabStop = focusContext.tabStopId === itemId;\n\n const displayValue = hoveredValue ?? value;\n const isFilled = displayValue >= itemValue;\n const isPartiallyFilled = step < 1 && displayValue >= itemValue - step && displayValue < itemValue;\n const isHovered = hoveredValue !== null && hoveredValue < itemValue;\n /*\n * ⚠ Khác thượng nguồn: bản gốc đặt `aria-checked={isFilled}`, tức chọn 4 sao thì BỐN nút radio\n * cùng báo \"đã chọn\" — một radiogroup có nhiều radio được chọn là trạng thái không tồn tại với\n * trình đọc màn hình. Ở đây chỉ đúng MỘT mục được chọn: mục chứa giá trị (3,5 → mục 4), và đọc\n * theo `value` thật chứ không theo sao đang được rê chuột (`displayValue`).\n */\n const isChecked = value > 0 && Math.ceil(value) === itemValue;\n\n const isMouseClickRef = React.useRef(false);\n\n const propsRef = useAsRef({\n onClick: onClickProp,\n onFocus: onFocusProp,\n onKeyDown: onKeyDownProp,\n onMouseDown: onMouseDownProp,\n onMouseEnter: onMouseEnterProp,\n onMouseMove: onMouseMoveProp,\n onMouseLeave: onMouseLeaveProp\n });\n\n useIsomorphicLayoutEffect(() => {\n focusContext.onItemRegister({\n id: itemId,\n ref: itemRef,\n value: itemValue,\n disabled: !!isDisabled\n });\n\n if (!isDisabled) {\n focusContext.onFocusableItemAdd();\n }\n\n return () => {\n focusContext.onItemUnregister(itemId);\n if (!isDisabled) {\n focusContext.onFocusableItemRemove();\n }\n };\n }, [focusContext, itemId, itemValue, isDisabled]);\n\n const onClick = React.useCallback(\n (event: React.MouseEvent<ItemElement>) => {\n propsRef.current.onClick?.(event);\n if (event.defaultPrevented) return;\n\n if (!isDisabled && !isReadOnly) {\n let newValue = itemValue;\n\n if (step < 1) {\n const rect = event.currentTarget.getBoundingClientRect();\n const clickX = event.clientX - rect.left;\n const isLeftHalf = clickX < rect.width / 2;\n\n if (context.dir === \"rtl\") {\n if (!isLeftHalf) {\n newValue = itemValue - step;\n }\n } else {\n if (isLeftHalf) {\n newValue = itemValue - step;\n }\n }\n }\n\n if (clearable && value === newValue) {\n newValue = 0;\n }\n\n store.setState(\"value\", newValue);\n }\n },\n [isDisabled, isReadOnly, clearable, step, value, itemValue, store, context.dir, propsRef]\n );\n\n const onFocus = React.useCallback(\n (event: React.FocusEvent<ItemElement>) => {\n propsRef.current.onFocus?.(event);\n if (event.defaultPrevented) return;\n\n focusContext.onItemFocus(itemId);\n\n const isKeyboardFocus = !isMouseClickRef.current;\n\n /*\n * ⚠ Khác thượng nguồn (3.0.1): chỉ CHỌN khi focus tới từ một mục khác của CÙNG nhóm — tức\n * người dùng đang đi bằng phím mũi tên. Bản gốc chọn ở mọi lần focus bằng bàn phím, kể cả lúc\n * Tab ĐI NGANG QUA: nhóm chưa chấm thành 1 sao chỉ vì người dùng Tab qua một biểu mẫu, còn\n * với `clearable` thì Tab vào một nhóm đã chấm lại XOÁ điểm (focus rơi đúng mục đang chọn,\n * và chọn lại mục đang chọn là xoá). Radio group gốc của trình duyệt và mẫu WAI-ARIA đều thế\n * này: Tab vào nhóm chỉ đưa focus tới, mũi tên mới chọn.\n * Tab vào nhóm thì focus đi từ `Rating` gốc (có `tabIndex`) sang một mục, nên `relatedTarget`\n * là chính gốc — không phải một mục khác.\n */\n const previous = event.relatedTarget;\n const cameFromSibling =\n previous instanceof HTMLElement &&\n previous !== event.currentTarget &&\n previous.getAttribute(\"data-slot\") === \"rating-item\" &&\n previous.closest('[data-slot=\"rating\"]') === event.currentTarget.closest('[data-slot=\"rating\"]');\n\n if (!isDisabled && !isReadOnly && activationMode !== \"manual\" && isKeyboardFocus && cameFromSibling) {\n // Nửa sao: nếu giá trị đang là nửa-bậc THUỘC mục này (3,5 khi mục là 4) thì đừng đổi nó.\n const isHalfStepValue = step < 1 && value === itemValue - step;\n\n if (!isHalfStepValue) {\n const newValue = clearable && value === itemValue ? 0 : itemValue;\n store.setState(\"value\", newValue);\n }\n }\n\n isMouseClickRef.current = false;\n },\n [focusContext, itemId, activationMode, isDisabled, isReadOnly, clearable, value, itemValue, step, store, propsRef]\n );\n\n const onKeyDown = React.useCallback(\n (event: React.KeyboardEvent<ItemElement>) => {\n propsRef.current.onKeyDown?.(event);\n if (event.defaultPrevented) return;\n\n if ((event.key === \"Enter\" || event.key === \" \") && activationMode === \"manual\") {\n event.preventDefault();\n if (!isDisabled && !isReadOnly && itemRef.current) {\n itemRef.current.click();\n }\n return;\n }\n\n if (event.key === \"Tab\" && event.shiftKey) {\n focusContext.onItemShiftTab();\n return;\n }\n\n if (event.target !== event.currentTarget) return;\n\n const focusIntent = getFocusIntent(event, context.dir, context.orientation);\n\n if (focusIntent !== undefined) {\n if (event.metaKey || event.ctrlKey || event.altKey || event.shiftKey) return;\n event.preventDefault();\n\n // Nửa sao: mũi tên tăng/giảm đúng một `step` thay vì nhảy sang mục kế.\n if (step < 1 && (focusIntent === \"prev\" || focusIntent === \"next\")) {\n if (!isDisabled && !isReadOnly) {\n let newValue = value;\n\n if (focusIntent === \"next\") {\n newValue = Math.min(value + step, context.max);\n } else {\n newValue = Math.max(value - step, 0);\n }\n\n store.setState(\"value\", newValue);\n\n // Đưa focus tới mục đại diện cho giá trị mới.\n const items = focusContext.getItems().filter((item) => !item.disabled);\n const targetItem = items.find((item) => item.value === Math.ceil(newValue));\n if (targetItem?.ref.current) {\n queueMicrotask(() => targetItem.ref.current?.focus());\n }\n }\n return;\n }\n\n // Bậc nguyên, hoặc Home/End: điều hướng như thường.\n const items = focusContext.getItems().filter((item) => !item.disabled);\n let candidateRefs = items.map((item) => item.ref);\n\n if (focusIntent === \"last\") {\n candidateRefs.reverse();\n } else if (focusIntent === \"prev\" || focusIntent === \"next\") {\n if (focusIntent === \"prev\") candidateRefs.reverse();\n const currentIndex = candidateRefs.findIndex((ref) => ref.current === event.currentTarget);\n candidateRefs = candidateRefs.slice(currentIndex + 1);\n }\n\n queueMicrotask(() => focusFirst(candidateRefs));\n }\n },\n [\n focusContext,\n context.dir,\n context.orientation,\n activationMode,\n isDisabled,\n isReadOnly,\n step,\n value,\n context.max,\n store,\n propsRef\n ]\n );\n\n const onMouseDown = React.useCallback(\n (event: React.MouseEvent<ItemElement>) => {\n propsRef.current.onMouseDown?.(event);\n if (event.defaultPrevented) return;\n\n isMouseClickRef.current = true;\n\n if (isDisabled) {\n event.preventDefault();\n } else {\n focusContext.onItemFocus(itemId);\n }\n },\n [focusContext, itemId, isDisabled, propsRef]\n );\n\n const onMouseEnter = React.useCallback(\n (event: React.MouseEvent<ItemElement>) => {\n propsRef.current.onMouseEnter?.(event);\n if (event.defaultPrevented) return;\n\n if (!isDisabled && !isReadOnly) {\n let hoverValue = itemValue;\n\n if (step < 1) {\n const rect = event.currentTarget.getBoundingClientRect();\n const mouseX = event.clientX - rect.left;\n const isLeftHalf = mouseX < rect.width / 2;\n\n if (context.dir === \"rtl\") {\n if (!isLeftHalf) {\n hoverValue = itemValue - step;\n }\n } else {\n if (isLeftHalf) {\n hoverValue = itemValue - step;\n }\n }\n }\n\n store.setState(\"hoveredValue\", hoverValue);\n }\n },\n [isDisabled, isReadOnly, step, itemValue, store, context.dir, propsRef]\n );\n\n const onMouseLeave = React.useCallback(\n (event: React.MouseEvent<ItemElement>) => {\n propsRef.current.onMouseLeave?.(event);\n if (event.defaultPrevented) return;\n\n if (!isDisabled && !isReadOnly) {\n store.setState(\"hoveredValue\", null);\n }\n },\n [isDisabled, isReadOnly, store, propsRef]\n );\n\n const onMouseMove = React.useCallback(\n (event: React.MouseEvent<ItemElement>) => {\n propsRef.current.onMouseMove?.(event);\n if (event.defaultPrevented) return;\n\n if (!isDisabled && !isReadOnly && step < 1) {\n const rect = event.currentTarget.getBoundingClientRect();\n const mouseX = event.clientX - rect.left;\n const isLeftHalf = mouseX < rect.width / 2;\n\n let hoverValue = itemValue;\n if (context.dir === \"rtl\") {\n hoverValue = !isLeftHalf ? itemValue - step : itemValue;\n } else {\n hoverValue = isLeftHalf ? itemValue - step : itemValue;\n }\n\n store.setState(\"hoveredValue\", hoverValue);\n }\n },\n [isDisabled, isReadOnly, step, itemValue, store, context.dir, propsRef]\n );\n\n const dataState: DataState = isFilled ? \"full\" : isPartiallyFilled ? \"partial\" : \"empty\";\n\n const ItemPrimitive = asChild ? Slot : \"button\";\n\n return (\n <ItemPrimitive\n role=\"radio\"\n type=\"button\"\n id={itemId}\n /*\n * ⚠ Khác thượng nguồn (3.0.1): mục ĐANG CHỌN của một giá trị lẻ (4,5 → mục 5) mang tên theo giá\n * trị thật. Trước đó trình đọc màn hình nghe \"5 stars, checked\" cho điểm trung bình 4,5 — mục\n * được chọn là mục trần của giá trị, còn tên thì đọc số thứ tự của mục.\n */\n aria-label={context.itemLabel(isChecked && value !== itemValue ? value : itemValue, context.max)}\n aria-checked={isChecked}\n aria-posinset={itemValue}\n aria-setsize={context.max}\n data-disabled={isDisabled ? \"\" : undefined}\n data-readonly={isReadOnly ? \"\" : undefined}\n data-state={dataState}\n data-hovered={isHovered ? \"\" : undefined}\n data-slot=\"rating-item\"\n disabled={isDisabled}\n tabIndex={isTabStop ? 0 : -1}\n {...itemProps}\n ref={composedRef}\n style={{\n ...itemProps.style,\n ...(isPartiallyFilled && {\n \"--partial-fill\": `url(#${getPartialFillGradientId(context.rootId, step)})`\n })\n }}\n className={cn(\n // Viền focus cùng khuôn với `Checkbox`/`RadioGroup` của gói.\n \"inline-flex items-center justify-center rounded transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50\",\n \"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg]:transition-colors [&_svg]:duration-200 data-[state=empty]:[&_svg]:fill-transparent data-[state=full]:[&_svg]:fill-current data-[state=partial]:[&_svg]:fill-(--partial-fill) [&_svg:not([class*='size-'])]:size-full\",\n context.size === \"sm\" ? \"size-4\" : context.size === \"lg\" ? \"size-6\" : \"size-5\",\n className\n )}\n onClick={onClick}\n onFocus={onFocus}\n onKeyDown={onKeyDown}\n onMouseDown={onMouseDown}\n onMouseEnter={onMouseEnter}\n onMouseMove={onMouseMove}\n onMouseLeave={onMouseLeave}\n >\n {typeof children === \"function\" ? children(dataState) : (children ?? <Star aria-hidden=\"true\" />)}\n </ItemPrimitive>\n );\n}\n\nexport { Rating, RatingItem, type RatingItemProps, type RatingProps, useStore as useRating };\n"],"mappings":"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","names":["value","instanceId","tabStopId","id","items","ref"]}
|
|
@@ -3,6 +3,7 @@ import { jsx } from "react/jsx-runtime";
|
|
|
3
3
|
import { useDirection } from "@radix-ui/react-direction";
|
|
4
4
|
import { Slot } from "@radix-ui/react-slot";
|
|
5
5
|
import * as React from "react";
|
|
6
|
+
import { useComitorMessages } from "../../i18n/locale-provider.js";
|
|
6
7
|
import { cn } from "../../lib/cn.js";
|
|
7
8
|
import { useComposedRefs } from "../../lib/compose-refs.js";
|
|
8
9
|
import { useAsRef } from "../../lib/use-as-ref.js";
|
|
@@ -227,11 +228,12 @@ function ScrollSpy(props) {
|
|
|
227
228
|
function ScrollSpyNav(props) {
|
|
228
229
|
const { asChild, className, ...navProps } = props;
|
|
229
230
|
const { dir, orientation } = useScrollSpyContext(NAV_NAME);
|
|
231
|
+
const t = useComitorMessages("scrollSpy");
|
|
230
232
|
const NavPrimitive = asChild ? Slot : "nav";
|
|
231
233
|
return /* @__PURE__ */ jsx(
|
|
232
234
|
NavPrimitive,
|
|
233
235
|
{
|
|
234
|
-
"aria-label":
|
|
236
|
+
"aria-label": t.navLabel,
|
|
235
237
|
"data-orientation": orientation,
|
|
236
238
|
"data-slot": "scroll-spy-nav",
|
|
237
239
|
dir,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/ui/scroll-spy.tsx"],"sourcesContent":["\"use client\";\n\n/*\n * Port từ Dice UI (https://diceui.com/docs/components/scroll-spy) — MIT, © 2024 Sadman Sakib.\n * Giữ nguyên cấu trúc thượng nguồn để còn đối chiếu ngược được; mọi chỗ đổi đều có chú thích.\n *\n * Dùng ở Comitor: trang cài đặt dài và trang chi tiết bản ghi (các mục của một liên hệ CRM) có\n * mục lục điều hướng trong trang.\n *\n * ⚠ `orientation` giữ NGHĨA của thượng nguồn — nó nói về hướng xếp của KHUNG, không phải của danh\n * sách link: `horizontal` (mặc định) = mục lục đứng bên cạnh nội dung, link xếp DỌC; `vertical` =\n * mục lục nằm trên nội dung, link xếp NGANG.\n */\n\nimport { useDirection } from \"@radix-ui/react-direction\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\nimport { useComposedRefs } from \"../../lib/compose-refs.js\";\nimport { useAsRef } from \"../../lib/use-as-ref.js\";\nimport { useIsomorphicLayoutEffect } from \"../../lib/use-isomorphic-layout-effect.js\";\nimport { useLazyRef } from \"../../lib/use-lazy-ref.js\";\n\nconst ROOT_NAME = \"ScrollSpy\";\nconst NAV_NAME = \"ScrollSpyNav\";\nconst LINK_NAME = \"ScrollSpyLink\";\nconst VIEWPORT_NAME = \"ScrollSpyViewport\";\nconst SECTION_NAME = \"ScrollSpySection\";\n\ntype Direction = \"ltr\" | \"rtl\";\ntype Orientation = \"horizontal\" | \"vertical\";\n\ntype LinkElement = React.ComponentRef<typeof ScrollSpyLink>;\ntype SectionElement = React.ComponentRef<typeof ScrollSpySection>;\n\nfunction getDefaultScrollBehavior(): ScrollBehavior {\n if (typeof window === \"undefined\") return \"smooth\";\n return window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches ? \"auto\" : \"smooth\";\n}\n\ninterface StoreState {\n value: string;\n}\n\ninterface Store {\n subscribe: (callback: () => void) => () => void;\n getState: () => StoreState;\n setState: <K extends keyof StoreState>(key: K, value: StoreState[K]) => void;\n notify: () => void;\n}\n\nconst StoreContext = React.createContext<Store | null>(null);\n\nfunction useStore<T>(selector: (state: StoreState) => T, ogStore?: Store | null): T {\n const contextStore = React.useContext(StoreContext);\n\n const store = ogStore ?? contextStore;\n\n if (!store) {\n throw new Error(`\\`useStore\\` phải được gọi bên trong \\`${ROOT_NAME}\\``);\n }\n\n const getSnapshot = React.useCallback(() => selector(store.getState()), [store, selector]);\n\n return React.useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);\n}\n\ninterface ScrollSpyContextValue {\n offset: number;\n scrollBehavior: ScrollBehavior | undefined;\n dir: Direction;\n orientation: Orientation;\n scrollContainer: HTMLElement | null;\n isScrollingRef: React.RefObject<boolean>;\n onSectionRegister: (id: string, element: SectionElement) => void;\n onSectionUnregister: (id: string) => void;\n onScrollToSection: (sectionId: string) => void;\n}\n\nconst ScrollSpyContext = React.createContext<ScrollSpyContextValue | null>(null);\n\nfunction useScrollSpyContext(consumerName: string) {\n const context = React.useContext(ScrollSpyContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` phải nằm bên trong \\`${ROOT_NAME}\\``);\n }\n return context;\n}\n\ninterface ScrollSpyProps extends React.ComponentProps<\"div\"> {\n value?: string;\n defaultValue?: string;\n onValueChange?: (value: string) => void;\n rootMargin?: string;\n threshold?: number | number[];\n offset?: number;\n /**\n * Kiểu cuộn khi bấm link. Không truyền thì `\"smooth\"`, hoặc `\"auto\"` nếu người dùng bật giảm\n * chuyển động (`prefers-reduced-motion`).\n */\n scrollBehavior?: ScrollBehavior;\n scrollContainer?: HTMLElement | null;\n dir?: Direction;\n orientation?: Orientation;\n asChild?: boolean;\n}\n\nfunction ScrollSpy(props: ScrollSpyProps) {\n const {\n value,\n defaultValue,\n onValueChange,\n rootMargin,\n threshold = 0.1,\n offset = 0,\n /*\n * ⚠ Khác thượng nguồn: bản gốc tính mặc định NGAY TRONG render (`scrollBehavior =\n * getDefaultScrollBehavior()` → `window.matchMedia` mỗi lượt render). Ở đây để `undefined` và\n * chỉ phân giải lúc cuộn thật — không chạm `window` khi render, và đọc đúng thiết lập giảm\n * chuyển động TẠI THỜI ĐIỂM bấm chứ không phải lúc render gần nhất.\n */\n scrollBehavior,\n scrollContainer = null,\n dir: dirProp,\n orientation = \"horizontal\",\n asChild,\n className,\n ...rootProps\n } = props;\n\n const dir = useDirection(dirProp);\n\n const stateRef = useLazyRef<StoreState>(() => ({\n value: value ?? defaultValue ?? \"\"\n }));\n const listenersRef = useLazyRef(() => new Set<() => void>());\n const onValueChangeRef = useAsRef(onValueChange);\n\n const store = React.useMemo<Store>(() => {\n return {\n subscribe: (cb) => {\n listenersRef.current.add(cb);\n return () => listenersRef.current.delete(cb);\n },\n getState: () => {\n return stateRef.current;\n },\n setState: (key, value) => {\n if (Object.is(stateRef.current[key], value)) return;\n\n stateRef.current[key] = value;\n\n if (key === \"value\" && value) {\n onValueChangeRef.current?.(value);\n }\n\n store.notify();\n },\n notify: () => {\n for (const cb of listenersRef.current) {\n cb();\n }\n }\n };\n }, [listenersRef, stateRef, onValueChangeRef]);\n\n const sectionMapRef = React.useRef(new Map<string, Element>());\n const isScrollingRef = React.useRef(false);\n const rafIdRef = React.useRef<number | null>(null);\n const isMountedRef = React.useRef(false);\n const scrollTimeoutRef = React.useRef<number | null>(null);\n\n const onSectionRegister = React.useCallback((id: string, element: SectionElement) => {\n sectionMapRef.current.set(id, element);\n }, []);\n\n const onSectionUnregister = React.useCallback((id: string) => {\n sectionMapRef.current.delete(id);\n }, []);\n\n const onScrollToSection = React.useCallback(\n (sectionId: string) => {\n /*\n * ⚠ Khác thượng nguồn: `CSS.escape` quanh id. Bản gốc ghép thẳng `#${sectionId}` vào\n * `querySelector`, nên id bắt đầu bằng chữ số (\"2024-hop-dong\") hay chứa ký tự đặc biệt\n * (id của `useId()` như \":r1:\") làm nó THROW `SyntaxError` ngay khi bấm link.\n */\n const section = scrollContainer\n ? scrollContainer.querySelector(`#${CSS.escape(sectionId)}`)\n : document.getElementById(sectionId);\n\n if (!section) {\n store.setState(\"value\", sectionId);\n return;\n }\n\n // Bật cờ để observer không ghi đè giá trị trong lúc đang cuộn theo lệnh.\n isScrollingRef.current = true;\n store.setState(\"value\", sectionId);\n\n const behavior = scrollBehavior ?? getDefaultScrollBehavior();\n\n if (scrollContainer) {\n const containerRect = scrollContainer.getBoundingClientRect();\n const sectionRect = section.getBoundingClientRect();\n const scrollTop = scrollContainer.scrollTop;\n const offsetPosition = sectionRect.top - containerRect.top + scrollTop - offset;\n\n scrollContainer.scrollTo({\n top: offsetPosition,\n behavior\n });\n } else {\n const sectionPosition = section.getBoundingClientRect().top;\n const offsetPosition = sectionPosition + window.scrollY - offset;\n\n window.scrollTo({\n top: offsetPosition,\n behavior\n });\n }\n\n if (scrollTimeoutRef.current !== null) {\n clearTimeout(scrollTimeoutRef.current);\n }\n\n scrollTimeoutRef.current = window.setTimeout(() => {\n isScrollingRef.current = false;\n }, 500);\n },\n [scrollContainer, offset, scrollBehavior, store]\n );\n\n /*\n * ⚠ Khác thượng nguồn: chỉ cuộn theo lệnh khi `value` đổi TỪ BÊN NGOÀI. Bản gốc cuộn mỗi khi\n * effect chạy lại sau lần đầu, với hai hệ quả đã đo trên trình duyệt:\n * · StrictMode chạy effect hai lần lúc mount → lần hai cuộn luôn CẢ TRANG tới mục (scrollY\n * 0 → ~5050) vì `scrollContainer` của app lúc đó còn `null`. Next dev bật StrictMode sẵn.\n * · Chế độ controlled tự kéo giật: observer đặt giá trị → `onValueChange` → app trả về prop\n * `value` → effect cuộn tới ĐẦU mục đó, ngay giữa lúc người dùng đang cuộn.\n * Luật mới: lúc mount không cuộn (đúng ý nhánh mount của bản gốc); về sau, `value` trùng với giá\n * trị store đang giữ nghĩa là nó chỉ là tiếng vọng của chính observer/link → không cuộn.\n * `defaultValue` đã vào store từ lúc khởi tạo (`stateRef`), không cần đồng bộ thêm.\n */\n const prevValuePropRef = React.useRef<string | undefined>(undefined);\n const onScrollToSectionRef = useAsRef(onScrollToSection);\n\n useIsomorphicLayoutEffect(() => {\n const previous = prevValuePropRef.current;\n prevValuePropRef.current = value;\n if (value === undefined || value === previous) return;\n\n if (previous === undefined || !isMountedRef.current) {\n isMountedRef.current = true;\n store.setState(\"value\", value);\n return;\n }\n\n if (value === store.getState().value) return;\n onScrollToSectionRef.current(value);\n }, [value, store, onScrollToSectionRef]);\n\n useIsomorphicLayoutEffect(() => {\n const sectionMap = sectionMapRef.current;\n if (sectionMap.size === 0) return;\n\n const observerRootMargin = rootMargin ?? `${-offset}px 0px -70% 0px`;\n\n const observer = new IntersectionObserver(\n (entries) => {\n if (isScrollingRef.current) return;\n\n if (rafIdRef.current !== null) {\n cancelAnimationFrame(rafIdRef.current);\n }\n\n rafIdRef.current = requestAnimationFrame(() => {\n const intersecting = entries.filter((entry) => entry.isIntersecting);\n\n if (intersecting.length === 0) return;\n\n const topmost = intersecting.reduce((prev, curr) => {\n return curr.boundingClientRect.top < prev.boundingClientRect.top ? curr : prev;\n });\n\n const id = topmost.target.id;\n if (id && sectionMap.has(id)) {\n store.setState(\"value\", id);\n }\n });\n },\n {\n root: scrollContainer,\n rootMargin: observerRootMargin,\n threshold\n }\n );\n\n for (const element of sectionMap.values()) {\n observer.observe(element);\n }\n\n return () => {\n observer.disconnect();\n if (rafIdRef.current !== null) {\n cancelAnimationFrame(rafIdRef.current);\n }\n if (scrollTimeoutRef.current !== null) {\n clearTimeout(scrollTimeoutRef.current);\n }\n };\n }, [offset, rootMargin, threshold, scrollContainer]);\n\n const contextValue = React.useMemo<ScrollSpyContextValue>(\n () => ({\n dir,\n orientation,\n offset,\n scrollBehavior,\n scrollContainer,\n isScrollingRef,\n onSectionRegister,\n onSectionUnregister,\n onScrollToSection\n }),\n [\n dir,\n orientation,\n offset,\n scrollBehavior,\n scrollContainer,\n onSectionRegister,\n onSectionUnregister,\n onScrollToSection\n ]\n );\n\n const RootPrimitive = asChild ? Slot : \"div\";\n\n return (\n <StoreContext.Provider value={store}>\n <ScrollSpyContext.Provider value={contextValue}>\n <RootPrimitive\n data-orientation={orientation}\n data-slot=\"scroll-spy\"\n dir={dir}\n {...rootProps}\n className={cn(\"flex\", orientation === \"horizontal\" ? \"flex-row\" : \"flex-col\", className)}\n />\n </ScrollSpyContext.Provider>\n </StoreContext.Provider>\n );\n}\n\ninterface ScrollSpyNavProps extends React.ComponentProps<\"nav\"> {\n asChild?: boolean;\n}\n\nfunction ScrollSpyNav(props: ScrollSpyNavProps) {\n const { asChild, className, ...navProps } = props;\n\n const { dir, orientation } = useScrollSpyContext(NAV_NAME);\n\n const NavPrimitive = asChild ? Slot : \"nav\";\n\n return (\n <NavPrimitive\n /*\n * ⚠ Khác thượng nguồn: `<nav>` có tên mặc định. Trong khung app Comitor đã có ít nhất MỘT\n * landmark `navigation` (sidebar), nên một `<nav>` thứ hai không tên là hai landmark trùng\n * loại không phân biệt được trong danh sách landmark của trình đọc màn hình. Ghi đè bằng\n * chính prop `aria-label` (cùng khuôn `Breadcrumb`/`Pagination` của gói).\n */\n aria-label=\"Các mục trong trang\"\n data-orientation={orientation}\n data-slot=\"scroll-spy-nav\"\n dir={dir}\n {...navProps}\n className={cn(\"flex gap-2\", orientation === \"horizontal\" ? \"flex-col\" : \"flex-row\", className)}\n />\n );\n}\n\ninterface ScrollSpyLinkProps extends React.ComponentProps<\"a\"> {\n value: string;\n asChild?: boolean;\n}\n\nfunction ScrollSpyLink(props: ScrollSpyLinkProps) {\n const { value: linkValue, asChild, onClick, className, ...linkProps } = props;\n\n const { orientation, onScrollToSection } = useScrollSpyContext(LINK_NAME);\n const value = useStore((state) => state.value);\n const isActive = value === linkValue;\n\n const onLinkClick = React.useCallback(\n (event: React.MouseEvent<LinkElement>) => {\n event.preventDefault();\n onClick?.(event);\n onScrollToSection(linkValue);\n },\n [linkValue, onClick, onScrollToSection]\n );\n\n const LinkPrimitive = asChild ? Slot : \"a\";\n\n return (\n <LinkPrimitive\n /*\n * ⚠ Khác thượng nguồn: bản gốc chỉ đặt `data-state=\"active\"` — thuộc tính mà trình đọc màn\n * hình KHÔNG đọc, nên mục đang xem chỉ được báo bằng hình. `aria-current=\"location\"` là giá\n * trị ARIA dành cho \"vị trí hiện tại\" trong một ngữ cảnh — ở đây là mục đang xem của trang.\n * Không dùng `\"page\"`: link trỏ tới một mục TRONG trang, không phải tới một trang khác.\n */\n aria-current={isActive ? \"location\" : undefined}\n data-orientation={orientation}\n data-slot=\"scroll-spy-link\"\n data-state={isActive ? \"active\" : \"inactive\"}\n {...linkProps}\n href={asChild ? undefined : `#${linkValue}`}\n className={cn(\n \"px-3 py-1.5 text-sm font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground data-[state=active]:bg-accent data-[state=active]:text-foreground\",\n /*\n * ⚠ Khác thượng nguồn: thêm VẠCH ACTIVE. Bản gốc báo mục đang xem chỉ bằng `bg-accent` —\n * #FBF5DC trên nền trắng là 1,09:1 (dark: 1,17:1), không đủ làm chỉ báo (WCAG 1.4.11 đòi\n * 3:1 cho thành phần phi văn bản mang nghĩa) — cộng đổi màu chữ, mà màu không được là tín\n * hiệu DUY NHẤT (1.4.1). Vạch 2px `border-primary-ink` là đúng khuôn `Tabs` biến thể\n * `default` (tabs-variants.ts): bậc `-ink` vì nó đi lên nền TRANG, và bảng tương phản cao\n * chỉ đổi được nó qua token này. Đặt ở cạnh ĐẦU (dọc) hoặc cạnh DƯỚI (ngang) theo hướng\n * xếp link; `border-s`/`rounded-e` là thuộc tính logic nên đúng cả RTL.\n */\n \"border-transparent data-[state=active]:border-primary-ink\",\n orientation === \"horizontal\" ? \"rounded-e-md border-s-2\" : \"rounded-t-md border-b-2\",\n // Vòng focus cùng khuôn `Tabs` của gói (TABS_TRIGGER_BASE).\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n className\n )}\n onClick={onLinkClick}\n />\n );\n}\n\ninterface ScrollSpyViewportProps extends React.ComponentProps<\"div\"> {\n asChild?: boolean;\n}\n\nfunction ScrollSpyViewport(props: ScrollSpyViewportProps) {\n const { asChild, className, ...viewportProps } = props;\n\n const { dir, orientation } = useScrollSpyContext(VIEWPORT_NAME);\n\n const ViewportPrimitive = asChild ? Slot : \"div\";\n\n return (\n <ViewportPrimitive\n data-orientation={orientation}\n data-slot=\"scroll-spy-viewport\"\n dir={dir}\n {...viewportProps}\n className={cn(\"flex flex-1 flex-col gap-8\", className)}\n />\n );\n}\n\ninterface ScrollSpySectionProps extends React.ComponentProps<\"div\"> {\n value: string;\n asChild?: boolean;\n}\n\nfunction ScrollSpySection(props: ScrollSpySectionProps) {\n const { asChild, ref, value, ...sectionProps } = props;\n\n const { orientation, onSectionRegister, onSectionUnregister } = useScrollSpyContext(SECTION_NAME);\n const sectionRef = React.useRef<SectionElement>(null);\n const composedRef = useComposedRefs(ref, sectionRef);\n\n useIsomorphicLayoutEffect(() => {\n const element = sectionRef.current;\n if (!element || !value) return;\n\n onSectionRegister(value, element);\n\n return () => {\n onSectionUnregister(value);\n };\n }, [value, onSectionRegister, onSectionUnregister]);\n\n const SectionPrimitive = asChild ? Slot : \"div\";\n\n return (\n <SectionPrimitive\n data-orientation={orientation}\n data-slot=\"scroll-spy-section\"\n {...sectionProps}\n id={value}\n ref={composedRef}\n />\n );\n}\n\nexport {\n ScrollSpy,\n ScrollSpyLink,\n type ScrollSpyLinkProps,\n ScrollSpyNav,\n type ScrollSpyNavProps,\n type ScrollSpyProps,\n ScrollSpySection,\n type ScrollSpySectionProps,\n ScrollSpyViewport,\n type ScrollSpyViewportProps\n};\n"],"mappings":"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","names":["value"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/components/ui/scroll-spy.tsx"],"sourcesContent":["\"use client\";\n\n/*\n * Port từ Dice UI (https://diceui.com/docs/components/scroll-spy) — MIT, © 2024 Sadman Sakib.\n * Giữ nguyên cấu trúc thượng nguồn để còn đối chiếu ngược được; mọi chỗ đổi đều có chú thích.\n *\n * Dùng ở Comitor: trang cài đặt dài và trang chi tiết bản ghi (các mục của một liên hệ CRM) có\n * mục lục điều hướng trong trang.\n *\n * ⚠ `orientation` giữ NGHĨA của thượng nguồn — nó nói về hướng xếp của KHUNG, không phải của danh\n * sách link: `horizontal` (mặc định) = mục lục đứng bên cạnh nội dung, link xếp DỌC; `vertical` =\n * mục lục nằm trên nội dung, link xếp NGANG.\n */\n\nimport { useDirection } from \"@radix-ui/react-direction\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport * as React from \"react\";\nimport { useComitorMessages } from \"../../i18n/locale-provider.js\";\nimport { cn } from \"../../lib/cn.js\";\nimport { useComposedRefs } from \"../../lib/compose-refs.js\";\nimport { useAsRef } from \"../../lib/use-as-ref.js\";\nimport { useIsomorphicLayoutEffect } from \"../../lib/use-isomorphic-layout-effect.js\";\nimport { useLazyRef } from \"../../lib/use-lazy-ref.js\";\n\nconst ROOT_NAME = \"ScrollSpy\";\nconst NAV_NAME = \"ScrollSpyNav\";\nconst LINK_NAME = \"ScrollSpyLink\";\nconst VIEWPORT_NAME = \"ScrollSpyViewport\";\nconst SECTION_NAME = \"ScrollSpySection\";\n\ntype Direction = \"ltr\" | \"rtl\";\ntype Orientation = \"horizontal\" | \"vertical\";\n\ntype LinkElement = React.ComponentRef<typeof ScrollSpyLink>;\ntype SectionElement = React.ComponentRef<typeof ScrollSpySection>;\n\nfunction getDefaultScrollBehavior(): ScrollBehavior {\n if (typeof window === \"undefined\") return \"smooth\";\n return window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches ? \"auto\" : \"smooth\";\n}\n\ninterface StoreState {\n value: string;\n}\n\ninterface Store {\n subscribe: (callback: () => void) => () => void;\n getState: () => StoreState;\n setState: <K extends keyof StoreState>(key: K, value: StoreState[K]) => void;\n notify: () => void;\n}\n\nconst StoreContext = React.createContext<Store | null>(null);\n\nfunction useStore<T>(selector: (state: StoreState) => T, ogStore?: Store | null): T {\n const contextStore = React.useContext(StoreContext);\n\n const store = ogStore ?? contextStore;\n\n if (!store) {\n throw new Error(`\\`useStore\\` phải được gọi bên trong \\`${ROOT_NAME}\\``);\n }\n\n const getSnapshot = React.useCallback(() => selector(store.getState()), [store, selector]);\n\n return React.useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);\n}\n\ninterface ScrollSpyContextValue {\n offset: number;\n scrollBehavior: ScrollBehavior | undefined;\n dir: Direction;\n orientation: Orientation;\n scrollContainer: HTMLElement | null;\n isScrollingRef: React.RefObject<boolean>;\n onSectionRegister: (id: string, element: SectionElement) => void;\n onSectionUnregister: (id: string) => void;\n onScrollToSection: (sectionId: string) => void;\n}\n\nconst ScrollSpyContext = React.createContext<ScrollSpyContextValue | null>(null);\n\nfunction useScrollSpyContext(consumerName: string) {\n const context = React.useContext(ScrollSpyContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` phải nằm bên trong \\`${ROOT_NAME}\\``);\n }\n return context;\n}\n\ninterface ScrollSpyProps extends React.ComponentProps<\"div\"> {\n value?: string;\n defaultValue?: string;\n onValueChange?: (value: string) => void;\n rootMargin?: string;\n threshold?: number | number[];\n offset?: number;\n /**\n * Kiểu cuộn khi bấm link. Không truyền thì `\"smooth\"`, hoặc `\"auto\"` nếu người dùng bật giảm\n * chuyển động (`prefers-reduced-motion`).\n */\n scrollBehavior?: ScrollBehavior;\n scrollContainer?: HTMLElement | null;\n dir?: Direction;\n orientation?: Orientation;\n asChild?: boolean;\n}\n\nfunction ScrollSpy(props: ScrollSpyProps) {\n const {\n value,\n defaultValue,\n onValueChange,\n rootMargin,\n threshold = 0.1,\n offset = 0,\n /*\n * ⚠ Khác thượng nguồn: bản gốc tính mặc định NGAY TRONG render (`scrollBehavior =\n * getDefaultScrollBehavior()` → `window.matchMedia` mỗi lượt render). Ở đây để `undefined` và\n * chỉ phân giải lúc cuộn thật — không chạm `window` khi render, và đọc đúng thiết lập giảm\n * chuyển động TẠI THỜI ĐIỂM bấm chứ không phải lúc render gần nhất.\n */\n scrollBehavior,\n scrollContainer = null,\n dir: dirProp,\n orientation = \"horizontal\",\n asChild,\n className,\n ...rootProps\n } = props;\n\n const dir = useDirection(dirProp);\n\n const stateRef = useLazyRef<StoreState>(() => ({\n value: value ?? defaultValue ?? \"\"\n }));\n const listenersRef = useLazyRef(() => new Set<() => void>());\n const onValueChangeRef = useAsRef(onValueChange);\n\n const store = React.useMemo<Store>(() => {\n return {\n subscribe: (cb) => {\n listenersRef.current.add(cb);\n return () => listenersRef.current.delete(cb);\n },\n getState: () => {\n return stateRef.current;\n },\n setState: (key, value) => {\n if (Object.is(stateRef.current[key], value)) return;\n\n stateRef.current[key] = value;\n\n if (key === \"value\" && value) {\n onValueChangeRef.current?.(value);\n }\n\n store.notify();\n },\n notify: () => {\n for (const cb of listenersRef.current) {\n cb();\n }\n }\n };\n }, [listenersRef, stateRef, onValueChangeRef]);\n\n const sectionMapRef = React.useRef(new Map<string, Element>());\n const isScrollingRef = React.useRef(false);\n const rafIdRef = React.useRef<number | null>(null);\n const isMountedRef = React.useRef(false);\n const scrollTimeoutRef = React.useRef<number | null>(null);\n\n const onSectionRegister = React.useCallback((id: string, element: SectionElement) => {\n sectionMapRef.current.set(id, element);\n }, []);\n\n const onSectionUnregister = React.useCallback((id: string) => {\n sectionMapRef.current.delete(id);\n }, []);\n\n const onScrollToSection = React.useCallback(\n (sectionId: string) => {\n /*\n * ⚠ Khác thượng nguồn: `CSS.escape` quanh id. Bản gốc ghép thẳng `#${sectionId}` vào\n * `querySelector`, nên id bắt đầu bằng chữ số (\"2024-hop-dong\") hay chứa ký tự đặc biệt\n * (id của `useId()` như \":r1:\") làm nó THROW `SyntaxError` ngay khi bấm link.\n */\n const section = scrollContainer\n ? scrollContainer.querySelector(`#${CSS.escape(sectionId)}`)\n : document.getElementById(sectionId);\n\n if (!section) {\n store.setState(\"value\", sectionId);\n return;\n }\n\n // Bật cờ để observer không ghi đè giá trị trong lúc đang cuộn theo lệnh.\n isScrollingRef.current = true;\n store.setState(\"value\", sectionId);\n\n const behavior = scrollBehavior ?? getDefaultScrollBehavior();\n\n if (scrollContainer) {\n const containerRect = scrollContainer.getBoundingClientRect();\n const sectionRect = section.getBoundingClientRect();\n const scrollTop = scrollContainer.scrollTop;\n const offsetPosition = sectionRect.top - containerRect.top + scrollTop - offset;\n\n scrollContainer.scrollTo({\n top: offsetPosition,\n behavior\n });\n } else {\n const sectionPosition = section.getBoundingClientRect().top;\n const offsetPosition = sectionPosition + window.scrollY - offset;\n\n window.scrollTo({\n top: offsetPosition,\n behavior\n });\n }\n\n if (scrollTimeoutRef.current !== null) {\n clearTimeout(scrollTimeoutRef.current);\n }\n\n scrollTimeoutRef.current = window.setTimeout(() => {\n isScrollingRef.current = false;\n }, 500);\n },\n [scrollContainer, offset, scrollBehavior, store]\n );\n\n /*\n * ⚠ Khác thượng nguồn: chỉ cuộn theo lệnh khi `value` đổi TỪ BÊN NGOÀI. Bản gốc cuộn mỗi khi\n * effect chạy lại sau lần đầu, với hai hệ quả đã đo trên trình duyệt:\n * · StrictMode chạy effect hai lần lúc mount → lần hai cuộn luôn CẢ TRANG tới mục (scrollY\n * 0 → ~5050) vì `scrollContainer` của app lúc đó còn `null`. Next dev bật StrictMode sẵn.\n * · Chế độ controlled tự kéo giật: observer đặt giá trị → `onValueChange` → app trả về prop\n * `value` → effect cuộn tới ĐẦU mục đó, ngay giữa lúc người dùng đang cuộn.\n * Luật mới: lúc mount không cuộn (đúng ý nhánh mount của bản gốc); về sau, `value` trùng với giá\n * trị store đang giữ nghĩa là nó chỉ là tiếng vọng của chính observer/link → không cuộn.\n * `defaultValue` đã vào store từ lúc khởi tạo (`stateRef`), không cần đồng bộ thêm.\n */\n const prevValuePropRef = React.useRef<string | undefined>(undefined);\n const onScrollToSectionRef = useAsRef(onScrollToSection);\n\n useIsomorphicLayoutEffect(() => {\n const previous = prevValuePropRef.current;\n prevValuePropRef.current = value;\n if (value === undefined || value === previous) return;\n\n if (previous === undefined || !isMountedRef.current) {\n isMountedRef.current = true;\n store.setState(\"value\", value);\n return;\n }\n\n if (value === store.getState().value) return;\n onScrollToSectionRef.current(value);\n }, [value, store, onScrollToSectionRef]);\n\n useIsomorphicLayoutEffect(() => {\n const sectionMap = sectionMapRef.current;\n if (sectionMap.size === 0) return;\n\n const observerRootMargin = rootMargin ?? `${-offset}px 0px -70% 0px`;\n\n const observer = new IntersectionObserver(\n (entries) => {\n if (isScrollingRef.current) return;\n\n if (rafIdRef.current !== null) {\n cancelAnimationFrame(rafIdRef.current);\n }\n\n rafIdRef.current = requestAnimationFrame(() => {\n const intersecting = entries.filter((entry) => entry.isIntersecting);\n\n if (intersecting.length === 0) return;\n\n const topmost = intersecting.reduce((prev, curr) => {\n return curr.boundingClientRect.top < prev.boundingClientRect.top ? curr : prev;\n });\n\n const id = topmost.target.id;\n if (id && sectionMap.has(id)) {\n store.setState(\"value\", id);\n }\n });\n },\n {\n root: scrollContainer,\n rootMargin: observerRootMargin,\n threshold\n }\n );\n\n for (const element of sectionMap.values()) {\n observer.observe(element);\n }\n\n return () => {\n observer.disconnect();\n if (rafIdRef.current !== null) {\n cancelAnimationFrame(rafIdRef.current);\n }\n if (scrollTimeoutRef.current !== null) {\n clearTimeout(scrollTimeoutRef.current);\n }\n };\n }, [offset, rootMargin, threshold, scrollContainer]);\n\n const contextValue = React.useMemo<ScrollSpyContextValue>(\n () => ({\n dir,\n orientation,\n offset,\n scrollBehavior,\n scrollContainer,\n isScrollingRef,\n onSectionRegister,\n onSectionUnregister,\n onScrollToSection\n }),\n [\n dir,\n orientation,\n offset,\n scrollBehavior,\n scrollContainer,\n onSectionRegister,\n onSectionUnregister,\n onScrollToSection\n ]\n );\n\n const RootPrimitive = asChild ? Slot : \"div\";\n\n return (\n <StoreContext.Provider value={store}>\n <ScrollSpyContext.Provider value={contextValue}>\n <RootPrimitive\n data-orientation={orientation}\n data-slot=\"scroll-spy\"\n dir={dir}\n {...rootProps}\n className={cn(\"flex\", orientation === \"horizontal\" ? \"flex-row\" : \"flex-col\", className)}\n />\n </ScrollSpyContext.Provider>\n </StoreContext.Provider>\n );\n}\n\ninterface ScrollSpyNavProps extends React.ComponentProps<\"nav\"> {\n asChild?: boolean;\n}\n\nfunction ScrollSpyNav(props: ScrollSpyNavProps) {\n const { asChild, className, ...navProps } = props;\n\n const { dir, orientation } = useScrollSpyContext(NAV_NAME);\n const t = useComitorMessages(\"scrollSpy\");\n\n const NavPrimitive = asChild ? Slot : \"nav\";\n\n return (\n <NavPrimitive\n /*\n * ⚠ Khác thượng nguồn: `<nav>` có tên mặc định (khoá `navLabel` của namespace `scrollSpy`,\n * theo `ComitorLocaleProvider`). Trong khung app Comitor đã có ít nhất MỘT landmark\n * `navigation` (sidebar), nên một `<nav>` thứ hai không tên là hai landmark trùng loại không\n * phân biệt được trong danh sách landmark của trình đọc màn hình. Ghi đè bằng chính prop\n * `aria-label` (cùng khuôn `Breadcrumb`/`Pagination` của gói).\n */\n aria-label={t.navLabel}\n data-orientation={orientation}\n data-slot=\"scroll-spy-nav\"\n dir={dir}\n {...navProps}\n className={cn(\"flex gap-2\", orientation === \"horizontal\" ? \"flex-col\" : \"flex-row\", className)}\n />\n );\n}\n\ninterface ScrollSpyLinkProps extends React.ComponentProps<\"a\"> {\n value: string;\n asChild?: boolean;\n}\n\nfunction ScrollSpyLink(props: ScrollSpyLinkProps) {\n const { value: linkValue, asChild, onClick, className, ...linkProps } = props;\n\n const { orientation, onScrollToSection } = useScrollSpyContext(LINK_NAME);\n const value = useStore((state) => state.value);\n const isActive = value === linkValue;\n\n const onLinkClick = React.useCallback(\n (event: React.MouseEvent<LinkElement>) => {\n event.preventDefault();\n onClick?.(event);\n onScrollToSection(linkValue);\n },\n [linkValue, onClick, onScrollToSection]\n );\n\n const LinkPrimitive = asChild ? Slot : \"a\";\n\n return (\n <LinkPrimitive\n /*\n * ⚠ Khác thượng nguồn: bản gốc chỉ đặt `data-state=\"active\"` — thuộc tính mà trình đọc màn\n * hình KHÔNG đọc, nên mục đang xem chỉ được báo bằng hình. `aria-current=\"location\"` là giá\n * trị ARIA dành cho \"vị trí hiện tại\" trong một ngữ cảnh — ở đây là mục đang xem của trang.\n * Không dùng `\"page\"`: link trỏ tới một mục TRONG trang, không phải tới một trang khác.\n */\n aria-current={isActive ? \"location\" : undefined}\n data-orientation={orientation}\n data-slot=\"scroll-spy-link\"\n data-state={isActive ? \"active\" : \"inactive\"}\n {...linkProps}\n href={asChild ? undefined : `#${linkValue}`}\n className={cn(\n \"px-3 py-1.5 text-sm font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground data-[state=active]:bg-accent data-[state=active]:text-foreground\",\n /*\n * ⚠ Khác thượng nguồn: thêm VẠCH ACTIVE. Bản gốc báo mục đang xem chỉ bằng `bg-accent` —\n * #FBF5DC trên nền trắng là 1,09:1 (dark: 1,17:1), không đủ làm chỉ báo (WCAG 1.4.11 đòi\n * 3:1 cho thành phần phi văn bản mang nghĩa) — cộng đổi màu chữ, mà màu không được là tín\n * hiệu DUY NHẤT (1.4.1). Vạch 2px `border-primary-ink` là đúng khuôn `Tabs` biến thể\n * `default` (tabs-variants.ts): bậc `-ink` vì nó đi lên nền TRANG, và bảng tương phản cao\n * chỉ đổi được nó qua token này. Đặt ở cạnh ĐẦU (dọc) hoặc cạnh DƯỚI (ngang) theo hướng\n * xếp link; `border-s`/`rounded-e` là thuộc tính logic nên đúng cả RTL.\n */\n \"border-transparent data-[state=active]:border-primary-ink\",\n orientation === \"horizontal\" ? \"rounded-e-md border-s-2\" : \"rounded-t-md border-b-2\",\n // Vòng focus cùng khuôn `Tabs` của gói (TABS_TRIGGER_BASE).\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n className\n )}\n onClick={onLinkClick}\n />\n );\n}\n\ninterface ScrollSpyViewportProps extends React.ComponentProps<\"div\"> {\n asChild?: boolean;\n}\n\nfunction ScrollSpyViewport(props: ScrollSpyViewportProps) {\n const { asChild, className, ...viewportProps } = props;\n\n const { dir, orientation } = useScrollSpyContext(VIEWPORT_NAME);\n\n const ViewportPrimitive = asChild ? Slot : \"div\";\n\n return (\n <ViewportPrimitive\n data-orientation={orientation}\n data-slot=\"scroll-spy-viewport\"\n dir={dir}\n {...viewportProps}\n className={cn(\"flex flex-1 flex-col gap-8\", className)}\n />\n );\n}\n\ninterface ScrollSpySectionProps extends React.ComponentProps<\"div\"> {\n value: string;\n asChild?: boolean;\n}\n\nfunction ScrollSpySection(props: ScrollSpySectionProps) {\n const { asChild, ref, value, ...sectionProps } = props;\n\n const { orientation, onSectionRegister, onSectionUnregister } = useScrollSpyContext(SECTION_NAME);\n const sectionRef = React.useRef<SectionElement>(null);\n const composedRef = useComposedRefs(ref, sectionRef);\n\n useIsomorphicLayoutEffect(() => {\n const element = sectionRef.current;\n if (!element || !value) return;\n\n onSectionRegister(value, element);\n\n return () => {\n onSectionUnregister(value);\n };\n }, [value, onSectionRegister, onSectionUnregister]);\n\n const SectionPrimitive = asChild ? Slot : \"div\";\n\n return (\n <SectionPrimitive\n data-orientation={orientation}\n data-slot=\"scroll-spy-section\"\n {...sectionProps}\n id={value}\n ref={composedRef}\n />\n );\n}\n\nexport {\n ScrollSpy,\n ScrollSpyLink,\n type ScrollSpyLinkProps,\n ScrollSpyNav,\n type ScrollSpyNavProps,\n type ScrollSpyProps,\n ScrollSpySection,\n type ScrollSpySectionProps,\n ScrollSpyViewport,\n type ScrollSpyViewportProps\n};\n"],"mappings":"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","names":["value"]}
|
|
@@ -22,10 +22,10 @@ type SheetSide = "top" | "right" | "bottom" | "left";
|
|
|
22
22
|
type SheetContentProps = React.ComponentProps<typeof DialogPrimitive.Content> & {
|
|
23
23
|
/** Cạnh màn hình mà panel trượt ra. Mặc định "right". */
|
|
24
24
|
side?: SheetSide;
|
|
25
|
-
/** Nhãn cho trình đọc màn hình của nút đóng. Mặc định
|
|
25
|
+
/** Nhãn cho trình đọc màn hình của nút đóng. Mặc định: khoá `closeLabel` của namespace `sheet`. */
|
|
26
26
|
closeLabel?: string;
|
|
27
27
|
};
|
|
28
|
-
declare function SheetContent({ className, children, side, closeLabel, ...props }: SheetContentProps): React.JSX.Element;
|
|
28
|
+
declare function SheetContent({ className, children, side, closeLabel: closeLabelProp, ...props }: SheetContentProps): React.JSX.Element;
|
|
29
29
|
type SheetHeaderProps = React.ComponentProps<"div">;
|
|
30
30
|
declare function SheetHeader({ className, ...props }: SheetHeaderProps): React.JSX.Element;
|
|
31
31
|
type SheetFooterProps = React.ComponentProps<"div">;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as SheetPrimitive from "@radix-ui/react-dialog";
|
|
4
4
|
import { XIcon } from "lucide-react";
|
|
5
|
+
import { useComitorMessages } from "../../i18n/locale-provider.js";
|
|
5
6
|
import { cn } from "../../lib/cn.js";
|
|
6
7
|
function Sheet({ ...props }) {
|
|
7
8
|
return /* @__PURE__ */ jsx(SheetPrimitive.Root, { "data-slot": "sheet", ...props });
|
|
@@ -28,7 +29,15 @@ function SheetOverlay({ className, ...props }) {
|
|
|
28
29
|
}
|
|
29
30
|
);
|
|
30
31
|
}
|
|
31
|
-
function SheetContent({
|
|
32
|
+
function SheetContent({
|
|
33
|
+
className,
|
|
34
|
+
children,
|
|
35
|
+
side = "right",
|
|
36
|
+
closeLabel: closeLabelProp,
|
|
37
|
+
...props
|
|
38
|
+
}) {
|
|
39
|
+
const t = useComitorMessages("sheet");
|
|
40
|
+
const closeLabel = closeLabelProp ?? t.closeLabel;
|
|
32
41
|
return /* @__PURE__ */ jsxs(SheetPortal, { children: [
|
|
33
42
|
/* @__PURE__ */ jsx(SheetOverlay, {}),
|
|
34
43
|
/* @__PURE__ */ jsxs(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/ui/sheet.tsx"],"sourcesContent":["\"use client\";\n\nimport * as SheetPrimitive from \"@radix-ui/react-dialog\";\nimport { XIcon } from \"lucide-react\";\nimport type * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\n\n/**\n * Sheet — panel trượt từ cạnh màn hình (dựng trên Radix Dialog).\n *\n * Dùng cho menu di động, bộ lọc, panel chi tiết. Shell Tier 3 dùng `side=\"left\"`\n * cho MobileMenu. Với panel kéo-thả kiểu mobile (có tay cầm, kéo để đóng) thì\n * dùng `Drawer` (vaul) thay vì Sheet.\n */\n\nexport type SheetProps = React.ComponentProps<typeof SheetPrimitive.Root>;\n\nfunction Sheet({ ...props }: SheetProps) {\n return <SheetPrimitive.Root data-slot=\"sheet\" {...props} />;\n}\n\nexport type SheetTriggerProps = React.ComponentProps<typeof SheetPrimitive.Trigger>;\n\nfunction SheetTrigger({ ...props }: SheetTriggerProps) {\n return <SheetPrimitive.Trigger data-slot=\"sheet-trigger\" {...props} />;\n}\n\nexport type SheetCloseProps = React.ComponentProps<typeof SheetPrimitive.Close>;\n\nfunction SheetClose({ ...props }: SheetCloseProps) {\n return <SheetPrimitive.Close data-slot=\"sheet-close\" {...props} />;\n}\n\nexport type SheetPortalProps = React.ComponentProps<typeof SheetPrimitive.Portal>;\n\nfunction SheetPortal({ ...props }: SheetPortalProps) {\n return <SheetPrimitive.Portal data-slot=\"sheet-portal\" {...props} />;\n}\n\nexport type SheetOverlayProps = React.ComponentProps<typeof SheetPrimitive.Overlay>;\n\nfunction SheetOverlay({ className, ...props }: SheetOverlayProps) {\n return (\n <SheetPrimitive.Overlay\n data-slot=\"sheet-overlay\"\n className={cn(\n \"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-ink/60 dark:bg-ink/80 backdrop-blur-sm\",\n className\n )}\n {...props}\n />\n );\n}\n\nexport type SheetSide = \"top\" | \"right\" | \"bottom\" | \"left\";\n\nexport type SheetContentProps = React.ComponentProps<typeof SheetPrimitive.Content> & {\n /** Cạnh màn hình mà panel trượt ra. Mặc định \"right\". */\n side?: SheetSide;\n /** Nhãn cho trình đọc màn hình của nút đóng. Mặc định
|
|
1
|
+
{"version":3,"sources":["../../../src/components/ui/sheet.tsx"],"sourcesContent":["\"use client\";\n\nimport * as SheetPrimitive from \"@radix-ui/react-dialog\";\nimport { XIcon } from \"lucide-react\";\nimport type * as React from \"react\";\nimport { useComitorMessages } from \"../../i18n/locale-provider.js\";\nimport { cn } from \"../../lib/cn.js\";\n\n/**\n * Sheet — panel trượt từ cạnh màn hình (dựng trên Radix Dialog).\n *\n * Dùng cho menu di động, bộ lọc, panel chi tiết. Shell Tier 3 dùng `side=\"left\"`\n * cho MobileMenu. Với panel kéo-thả kiểu mobile (có tay cầm, kéo để đóng) thì\n * dùng `Drawer` (vaul) thay vì Sheet.\n */\n\nexport type SheetProps = React.ComponentProps<typeof SheetPrimitive.Root>;\n\nfunction Sheet({ ...props }: SheetProps) {\n return <SheetPrimitive.Root data-slot=\"sheet\" {...props} />;\n}\n\nexport type SheetTriggerProps = React.ComponentProps<typeof SheetPrimitive.Trigger>;\n\nfunction SheetTrigger({ ...props }: SheetTriggerProps) {\n return <SheetPrimitive.Trigger data-slot=\"sheet-trigger\" {...props} />;\n}\n\nexport type SheetCloseProps = React.ComponentProps<typeof SheetPrimitive.Close>;\n\nfunction SheetClose({ ...props }: SheetCloseProps) {\n return <SheetPrimitive.Close data-slot=\"sheet-close\" {...props} />;\n}\n\nexport type SheetPortalProps = React.ComponentProps<typeof SheetPrimitive.Portal>;\n\nfunction SheetPortal({ ...props }: SheetPortalProps) {\n return <SheetPrimitive.Portal data-slot=\"sheet-portal\" {...props} />;\n}\n\nexport type SheetOverlayProps = React.ComponentProps<typeof SheetPrimitive.Overlay>;\n\nfunction SheetOverlay({ className, ...props }: SheetOverlayProps) {\n return (\n <SheetPrimitive.Overlay\n data-slot=\"sheet-overlay\"\n className={cn(\n \"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-ink/60 dark:bg-ink/80 backdrop-blur-sm\",\n className\n )}\n {...props}\n />\n );\n}\n\nexport type SheetSide = \"top\" | \"right\" | \"bottom\" | \"left\";\n\nexport type SheetContentProps = React.ComponentProps<typeof SheetPrimitive.Content> & {\n /** Cạnh màn hình mà panel trượt ra. Mặc định \"right\". */\n side?: SheetSide;\n /** Nhãn cho trình đọc màn hình của nút đóng. Mặc định: khoá `closeLabel` của namespace `sheet`. */\n closeLabel?: string;\n};\n\nfunction SheetContent({\n className,\n children,\n side = \"right\",\n closeLabel: closeLabelProp,\n ...props\n}: SheetContentProps) {\n const t = useComitorMessages(\"sheet\");\n const closeLabel = closeLabelProp ?? t.closeLabel;\n return (\n <SheetPortal>\n <SheetOverlay />\n <SheetPrimitive.Content\n data-slot=\"sheet-content\"\n className={cn(\n /*\n * `overflow-y-auto`: cùng lỗi với `DialogContent` trước 1.3.2 — `position: fixed`,\n * `h-full`, mà không có `overflow` nào, nên nội dung dài hơn khung nhìn bị CẮT và\n * không cuộn được. `SheetFooter` mang `mt-auto` nên nó vẫn dính đáy khi nội dung ngắn.\n */\n \"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 overflow-y-auto shadow-overlay transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500\",\n side === \"right\" &&\n \"data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm\",\n side === \"left\" &&\n \"data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm\",\n side === \"top\" &&\n \"data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b\",\n side === \"bottom\" &&\n \"data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t\",\n className\n )}\n {...props}\n >\n {children}\n <SheetPrimitive.Close className=\"absolute top-4 right-4 rounded-md p-1 text-muted-foreground transition-colors duration-150 ease-out hover:text-foreground hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none\">\n <XIcon className=\"size-4\" />\n <span className=\"sr-only\">{closeLabel}</span>\n </SheetPrimitive.Close>\n </SheetPrimitive.Content>\n </SheetPortal>\n );\n}\n\nexport type SheetHeaderProps = React.ComponentProps<\"div\">;\n\nfunction SheetHeader({ className, ...props }: SheetHeaderProps) {\n return <div data-slot=\"sheet-header\" className={cn(\"flex flex-col gap-1.5 p-4\", className)} {...props} />;\n}\n\nexport type SheetFooterProps = React.ComponentProps<\"div\">;\n\nfunction SheetFooter({ className, ...props }: SheetFooterProps) {\n return <div data-slot=\"sheet-footer\" className={cn(\"mt-auto flex flex-col gap-2 p-4\", className)} {...props} />;\n}\n\nexport type SheetTitleProps = React.ComponentProps<typeof SheetPrimitive.Title>;\n\nfunction SheetTitle({ className, ...props }: SheetTitleProps) {\n return (\n <SheetPrimitive.Title\n data-slot=\"sheet-title\"\n className={cn(\"text-foreground font-semibold\", className)}\n {...props}\n />\n );\n}\n\nexport type SheetDescriptionProps = React.ComponentProps<typeof SheetPrimitive.Description>;\n\nfunction SheetDescription({ className, ...props }: SheetDescriptionProps) {\n return (\n <SheetPrimitive.Description\n data-slot=\"sheet-description\"\n className={cn(\"text-muted-foreground text-sm\", className)}\n {...props}\n />\n );\n}\n\nexport {\n Sheet,\n SheetClose,\n SheetContent,\n SheetDescription,\n SheetFooter,\n SheetHeader,\n SheetOverlay,\n SheetPortal,\n SheetTitle,\n SheetTrigger\n};\n"],"mappings":";AAmBS,cA+ED,YA/EC;AAjBT,YAAY,oBAAoB;AAChC,SAAS,aAAa;AAEtB,SAAS,0BAA0B;AACnC,SAAS,UAAU;AAYnB,SAAS,MAAM,EAAE,GAAG,MAAM,GAAe;AACvC,SAAO,oBAAC,eAAe,MAAf,EAAoB,aAAU,SAAS,GAAG,OAAO;AAC3D;AAIA,SAAS,aAAa,EAAE,GAAG,MAAM,GAAsB;AACrD,SAAO,oBAAC,eAAe,SAAf,EAAuB,aAAU,iBAAiB,GAAG,OAAO;AACtE;AAIA,SAAS,WAAW,EAAE,GAAG,MAAM,GAAoB;AACjD,SAAO,oBAAC,eAAe,OAAf,EAAqB,aAAU,eAAe,GAAG,OAAO;AAClE;AAIA,SAAS,YAAY,EAAE,GAAG,MAAM,GAAqB;AACnD,SAAO,oBAAC,eAAe,QAAf,EAAsB,aAAU,gBAAgB,GAAG,OAAO;AACpE;AAIA,SAAS,aAAa,EAAE,WAAW,GAAG,MAAM,GAAsB;AAChE,SACE;AAAA,IAAC,eAAe;AAAA,IAAf;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;AAWA,SAAS,aAAa;AAAA,EACpB;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,GAAG;AACL,GAAsB;AACpB,QAAM,IAAI,mBAAmB,OAAO;AACpC,QAAM,aAAa,kBAAkB,EAAE;AACvC,SACE,qBAAC,eACC;AAAA,wBAAC,gBAAa;AAAA,IACd;AAAA,MAAC,eAAe;AAAA,MAAf;AAAA,QACC,aAAU;AAAA,QACV,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,UAMT;AAAA,UACA,SAAS,WACP;AAAA,UACF,SAAS,UACP;AAAA,UACF,SAAS,SACP;AAAA,UACF,SAAS,YACP;AAAA,UACF;AAAA,QACF;AAAA,QACC,GAAG;AAAA,QAEH;AAAA;AAAA,UACD,qBAAC,eAAe,OAAf,EAAqB,WAAU,qTAC9B;AAAA,gCAAC,SAAM,WAAU,UAAS;AAAA,YAC1B,oBAAC,UAAK,WAAU,WAAW,sBAAW;AAAA,aACxC;AAAA;AAAA;AAAA,IACF;AAAA,KACF;AAEJ;AAIA,SAAS,YAAY,EAAE,WAAW,GAAG,MAAM,GAAqB;AAC9D,SAAO,oBAAC,SAAI,aAAU,gBAAe,WAAW,GAAG,6BAA6B,SAAS,GAAI,GAAG,OAAO;AACzG;AAIA,SAAS,YAAY,EAAE,WAAW,GAAG,MAAM,GAAqB;AAC9D,SAAO,oBAAC,SAAI,aAAU,gBAAe,WAAW,GAAG,mCAAmC,SAAS,GAAI,GAAG,OAAO;AAC/G;AAIA,SAAS,WAAW,EAAE,WAAW,GAAG,MAAM,GAAoB;AAC5D,SACE;AAAA,IAAC,eAAe;AAAA,IAAf;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,iCAAiC,SAAS;AAAA,MACvD,GAAG;AAAA;AAAA,EACN;AAEJ;AAIA,SAAS,iBAAiB,EAAE,WAAW,GAAG,MAAM,GAA0B;AACxE,SACE;AAAA,IAAC,eAAe;AAAA,IAAf;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,iCAAiC,SAAS;AAAA,MACvD,GAAG;AAAA;AAAA,EACN;AAEJ;","names":[]}
|
|
@@ -26,6 +26,6 @@ import * as React from 'react';
|
|
|
26
26
|
* vừa ≥3:1 với nhau vừa ≥3:1 với nền.
|
|
27
27
|
*/
|
|
28
28
|
type SliderProps = React.ComponentProps<typeof SliderPrimitive.Root>;
|
|
29
|
-
declare function Slider({ className, defaultValue, value, min, max, ...props }: SliderProps): React.JSX.Element;
|
|
29
|
+
declare function Slider({ className, defaultValue, value, min, max, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, ...props }: SliderProps): React.JSX.Element;
|
|
30
30
|
|
|
31
31
|
export { Slider, type SliderProps };
|
|
@@ -3,11 +3,22 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import * as SliderPrimitive from "@radix-ui/react-slider";
|
|
4
4
|
import * as React from "react";
|
|
5
5
|
import { cn } from "../../lib/cn.js";
|
|
6
|
-
function Slider({
|
|
6
|
+
function Slider({
|
|
7
|
+
className,
|
|
8
|
+
defaultValue,
|
|
9
|
+
value,
|
|
10
|
+
min = 0,
|
|
11
|
+
max = 100,
|
|
12
|
+
"aria-label": ariaLabel,
|
|
13
|
+
"aria-labelledby": ariaLabelledby,
|
|
14
|
+
...props
|
|
15
|
+
}) {
|
|
7
16
|
const _values = React.useMemo(
|
|
8
17
|
() => Array.isArray(value) ? value : Array.isArray(defaultValue) ? defaultValue : [min, max],
|
|
9
18
|
[value, defaultValue, min, max]
|
|
10
19
|
);
|
|
20
|
+
const singleThumb = _values.length === 1;
|
|
21
|
+
const thumbName = singleThumb ? { "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby } : {};
|
|
11
22
|
return /* @__PURE__ */ jsxs(
|
|
12
23
|
SliderPrimitive.Root,
|
|
13
24
|
{
|
|
@@ -21,6 +32,8 @@ function Slider({ className, defaultValue, value, min = 0, max = 100, ...props }
|
|
|
21
32
|
className
|
|
22
33
|
),
|
|
23
34
|
...props,
|
|
35
|
+
"aria-label": singleThumb ? void 0 : ariaLabel,
|
|
36
|
+
"aria-labelledby": singleThumb ? void 0 : ariaLabelledby,
|
|
24
37
|
children: [
|
|
25
38
|
/* @__PURE__ */ jsx(
|
|
26
39
|
SliderPrimitive.Track,
|
|
@@ -40,6 +53,7 @@ function Slider({ className, defaultValue, value, min = 0, max = 100, ...props }
|
|
|
40
53
|
SliderPrimitive.Thumb,
|
|
41
54
|
{
|
|
42
55
|
"data-slot": "slider-thumb",
|
|
56
|
+
...thumbName,
|
|
43
57
|
className: "border-control-on ring-ring/50 block size-4 shrink-0 rounded-full border bg-control-knob shadow-sm transition-colors duration-150 ease-out hover:ring-4 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50"
|
|
44
58
|
},
|
|
45
59
|
index
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/ui/slider.tsx"],"sourcesContent":["\"use client\";\n\nimport * as SliderPrimitive from \"@radix-ui/react-slider\";\nimport * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\n\n/**\n * Slider (Radix) — hỗ trợ 1 hoặc nhiều thumb (range) và cả hướng dọc.\n * Số thumb suy ra từ `value`/`defaultValue`; không truyền gì thì mặc định 2 thumb\n * ứng với [min, max].\n *\n * ─ MÀU: CÙNG BỘ TOKEN VỚI `Switch`/`Checkbox` ─\n *\n * Thanh trượt là thành phần phi văn bản (WCAG 1.4.11, ngưỡng 3:1). Bản trước hỏng cả ba phía\n * ở light: rãnh `bg-muted` chỉ **1,07:1** với nền trắng (không nhìn thấy cái slider), đoạn đã\n * chọn `bg-primary` **1,86:1** với nền và **1,73:1** với rãnh, núm `bg-white` **1,86:1** với\n * đoạn đã chọn — và `bg-white` cứng thì ở dark núm ăn 1,49:1 với đoạn gold sáng.\n *\n * Nay đúng bộ ba của hợp đồng màu (docs/hop-dong-mau.md §3–§4):\n *\n * rãnh `bg-control-edge` : 3,83:1 (light) · 4,94:1 (dark) với nền trang ✓\n * đã chọn `bg-control-on` : 6,51:1 (light) · 12,67:1 (dark) với nền trang ✓\n * núm `bg-control-knob` : 6,51 / 12,67:1 với đoạn đã chọn · 3,83 / 4,94:1 với rãnh ✓\n * viền núm `border-control-on` : 6,51:1 (light) · 12,67:1 (dark) với nền trang ✓\n *\n * ⚠ Đoạn ĐÃ CHỌN ⇄ rãnh chỉ 1,70:1 (light) / 2,57:1 (dark) — giống hệt cặp rãnh BẬT ⇄ rãnh TẮT\n * của `Switch`, và chấp nhận vì cùng lý do: VỊ TRÍ NÚM mới là thứ mang giá trị, còn hai màu kia\n * đều tự đạt ≥3:1 với nền trang và với núm. Với nền trang trắng thì không tồn tại cặp màu nào\n * vừa ≥3:1 với nhau vừa ≥3:1 với nền.\n */\n\nexport type SliderProps = React.ComponentProps<typeof SliderPrimitive.Root>;\n\
|
|
1
|
+
{"version":3,"sources":["../../../src/components/ui/slider.tsx"],"sourcesContent":["\"use client\";\n\nimport * as SliderPrimitive from \"@radix-ui/react-slider\";\nimport * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\n\n/**\n * Slider (Radix) — hỗ trợ 1 hoặc nhiều thumb (range) và cả hướng dọc.\n * Số thumb suy ra từ `value`/`defaultValue`; không truyền gì thì mặc định 2 thumb\n * ứng với [min, max].\n *\n * ─ MÀU: CÙNG BỘ TOKEN VỚI `Switch`/`Checkbox` ─\n *\n * Thanh trượt là thành phần phi văn bản (WCAG 1.4.11, ngưỡng 3:1). Bản trước hỏng cả ba phía\n * ở light: rãnh `bg-muted` chỉ **1,07:1** với nền trắng (không nhìn thấy cái slider), đoạn đã\n * chọn `bg-primary` **1,86:1** với nền và **1,73:1** với rãnh, núm `bg-white` **1,86:1** với\n * đoạn đã chọn — và `bg-white` cứng thì ở dark núm ăn 1,49:1 với đoạn gold sáng.\n *\n * Nay đúng bộ ba của hợp đồng màu (docs/hop-dong-mau.md §3–§4):\n *\n * rãnh `bg-control-edge` : 3,83:1 (light) · 4,94:1 (dark) với nền trang ✓\n * đã chọn `bg-control-on` : 6,51:1 (light) · 12,67:1 (dark) với nền trang ✓\n * núm `bg-control-knob` : 6,51 / 12,67:1 với đoạn đã chọn · 3,83 / 4,94:1 với rãnh ✓\n * viền núm `border-control-on` : 6,51:1 (light) · 12,67:1 (dark) với nền trang ✓\n *\n * ⚠ Đoạn ĐÃ CHỌN ⇄ rãnh chỉ 1,70:1 (light) / 2,57:1 (dark) — giống hệt cặp rãnh BẬT ⇄ rãnh TẮT\n * của `Switch`, và chấp nhận vì cùng lý do: VỊ TRÍ NÚM mới là thứ mang giá trị, còn hai màu kia\n * đều tự đạt ≥3:1 với nền trang và với núm. Với nền trang trắng thì không tồn tại cặp màu nào\n * vừa ≥3:1 với nhau vừa ≥3:1 với nền.\n */\n\nexport type SliderProps = React.ComponentProps<typeof SliderPrimitive.Root>;\n\n/*\n * ─ TÊN CHO TRÌNH ĐỌC MÀN HÌNH ĐI XUỐNG NÚM (3.0.1) ─\n *\n * Radix đặt `role=\"slider\"` trên từng `Thumb`, còn `Root` chỉ là một `<span>` không vai trò. Trước\n * `3.0.1`, `aria-label` / `aria-labelledby` truyền cho `Slider` rơi xuống `Root` — tức lên một phần\n * tử KHÔNG AI focus, và núm thật sự nhận bàn phím thì không có tên (axe: \"ARIA slider elements\n * must have an accessible name\"). Gặp khi dựng thanh zoom cho `Cropper`.\n *\n * Nay với slider MỘT núm, hai thuộc tính đó đi thẳng xuống núm. Slider NHIỀU núm (range) giữ\n * nguyên như cũ: Radix tự đặt tên từng núm (\"Minimum\" / \"Maximum\"), và chép cùng một tên xuống\n * cả hai núm sẽ xoá mất đúng phân biệt đó — range cần tên riêng thì đặt nhãn cho từng núm ở app.\n */\nfunction Slider({\n className,\n defaultValue,\n value,\n min = 0,\n max = 100,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledby,\n ...props\n}: SliderProps) {\n const _values = React.useMemo(\n () => (Array.isArray(value) ? value : Array.isArray(defaultValue) ? defaultValue : [min, max]),\n [value, defaultValue, min, max]\n );\n const singleThumb = _values.length === 1;\n /*\n * Rải CÓ ĐIỀU KIỆN, đừng truyền `aria-label={undefined}` xuống núm: `Thumb` của Radix rải props\n * của app SAU tên nó tự đặt, nên chỉ một khoá mang giá trị `undefined` cũng đủ xoá \"Minimum\" /\n * \"Maximum\" của slider range.\n */\n const thumbName = singleThumb ? { \"aria-label\": ariaLabel, \"aria-labelledby\": ariaLabelledby } : {};\n\n return (\n <SliderPrimitive.Root\n data-slot=\"slider\"\n defaultValue={defaultValue}\n value={value}\n min={min}\n max={max}\n className={cn(\n \"relative flex w-full touch-none items-center select-none data-[disabled]:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col\",\n className\n )}\n {...props}\n aria-label={singleThumb ? undefined : ariaLabel}\n aria-labelledby={singleThumb ? undefined : ariaLabelledby}\n >\n <SliderPrimitive.Track\n data-slot=\"slider-track\"\n className=\"bg-slider-track relative grow overflow-hidden rounded-full data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5\"\n >\n <SliderPrimitive.Range\n data-slot=\"slider-range\"\n className=\"bg-control-on absolute data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full\"\n />\n </SliderPrimitive.Track>\n {Array.from({ length: _values.length }, (_, index) => (\n <SliderPrimitive.Thumb\n data-slot=\"slider-thumb\"\n // biome-ignore lint/suspicious/noArrayIndexKey: thumb chỉ phân biệt bằng vị trí, không có id\n key={index}\n {...thumbName}\n className=\"border-control-on ring-ring/50 block size-4 shrink-0 rounded-full border bg-control-knob shadow-sm transition-colors duration-150 ease-out hover:ring-4 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50\"\n />\n ))}\n </SliderPrimitive.Root>\n );\n}\n\nexport { Slider };\n"],"mappings":";AAoEI,SAkBI,KAlBJ;AAlEJ,YAAY,qBAAqB;AACjC,YAAY,WAAW;AACvB,SAAS,UAAU;AAyCnB,SAAS,OAAO;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA,MAAM;AAAA,EACN,MAAM;AAAA,EACN,cAAc;AAAA,EACd,mBAAmB;AAAA,EACnB,GAAG;AACL,GAAgB;AACd,QAAM,UAAU,MAAM;AAAA,IACpB,MAAO,MAAM,QAAQ,KAAK,IAAI,QAAQ,MAAM,QAAQ,YAAY,IAAI,eAAe,CAAC,KAAK,GAAG;AAAA,IAC5F,CAAC,OAAO,cAAc,KAAK,GAAG;AAAA,EAChC;AACA,QAAM,cAAc,QAAQ,WAAW;AAMvC,QAAM,YAAY,cAAc,EAAE,cAAc,WAAW,mBAAmB,eAAe,IAAI,CAAC;AAElG,SACE;AAAA,IAAC,gBAAgB;AAAA,IAAhB;AAAA,MACC,aAAU;AAAA,MACV;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MACJ,cAAY,cAAc,SAAY;AAAA,MACtC,mBAAiB,cAAc,SAAY;AAAA,MAE3C;AAAA;AAAA,UAAC,gBAAgB;AAAA,UAAhB;AAAA,YACC,aAAU;AAAA,YACV,WAAU;AAAA,YAEV;AAAA,cAAC,gBAAgB;AAAA,cAAhB;AAAA,gBACC,aAAU;AAAA,gBACV,WAAU;AAAA;AAAA,YACZ;AAAA;AAAA,QACF;AAAA,QACC,MAAM,KAAK,EAAE,QAAQ,QAAQ,OAAO,GAAG,CAAC,GAAG,UAC1C;AAAA,UAAC,gBAAgB;AAAA,UAAhB;AAAA,YACC,aAAU;AAAA,YAGT,GAAG;AAAA,YACJ,WAAU;AAAA;AAAA,UAFL;AAAA,QAGP,CACD;AAAA;AAAA;AAAA,EACH;AAEJ;","names":[]}
|
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
3
|
import { useTheme } from "next-themes";
|
|
4
4
|
import { Toaster as Sonner } from "sonner";
|
|
5
|
+
import { useToastLanguageBridge } from "../../lib/toast-language.js";
|
|
5
6
|
const Toaster = ({ ...props }) => {
|
|
6
7
|
const { theme = "system" } = useTheme();
|
|
8
|
+
useToastLanguageBridge();
|
|
7
9
|
return /* @__PURE__ */ jsx(
|
|
8
10
|
Sonner,
|
|
9
11
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/ui/sonner.tsx"],"sourcesContent":["\"use client\";\n\nimport { useTheme } from \"next-themes\";\nimport type * as React from \"react\";\nimport { Toaster as Sonner, type ToasterProps } from \"sonner\";\n\n/**\n * Toaster — bọc `sonner` và nối màu vào token popover của Comitor.\n *\n * Phụ thuộc `next-themes` (peer OPTIONAL) để đọc theme hiện tại; app nào không\n * dùng next-themes thì đừng render component này — hoặc render `<Sonner>` trực tiếp.\n *\n * Đặt duy nhất MỘT `<Toaster />` ở gốc app; gọi `toast()` của `sonner` ở bất kỳ đâu.\n * Bản có icon theo trạng thái (success/warning/info) là composite `Toaster` ở Tier 2.\n */\n\n/**\n * Cửa DUY NHẤT của file này: `SonnerToaster` + `SonnerToasterProps`.\n *\n * Tên trần `Toaster`/`ToasterProps` cố ý KHÔNG xuất, dù chúng là tên tự nhiên ở đây: composite\n * `Toaster`/`ToasterProps` (`composite/toast.tsx`) đã chiếm đúng hai tên đó ở entry `.`. Nếu để\n * chúng xuất ra thì (a) `shell/index.ts` chỉ lấy bản `Sonner*` nên chúng là export CHẾT, và\n * (b) ai thêm `export * from \"./components/ui/sonner.js\"` vào `src/index.ts` sẽ vỡ build ngay,\n * còn thêm vào `shell/index.ts` thì im lặng dựng thêm một cửa `Toaster` thứ hai.\n * Giữ hai toaster là quyết định có chủ ý (docs/quyet-dinh-tich-hop.md §31) — nhưng \"hai toaster,\n * hai tên\" chỉ đúng khi tên trần không còn lối ra.\n */\nexport type SonnerToasterProps = ToasterProps;\n\nconst Toaster = ({ ...props }: ToasterProps) => {\n const { theme = \"system\" } = useTheme();\n\n return (\n <Sonner\n theme={theme as ToasterProps[\"theme\"]}\n className=\"toaster group\"\n style={\n {\n \"--normal-bg\": \"var(--popover)\",\n \"--normal-text\": \"var(--popover-foreground)\",\n \"--normal-border\": \"var(--border)\"\n } as React.CSSProperties\n }\n {...props}\n />\n );\n};\n\nexport { Toaster as SonnerToaster };\n"],"mappings":";
|
|
1
|
+
{"version":3,"sources":["../../../src/components/ui/sonner.tsx"],"sourcesContent":["\"use client\";\n\nimport { useTheme } from \"next-themes\";\nimport type * as React from \"react\";\nimport { Toaster as Sonner, type ToasterProps } from \"sonner\";\nimport { useToastLanguageBridge } from \"../../lib/toast-language.js\";\n\n/**\n * Toaster — bọc `sonner` và nối màu vào token popover của Comitor.\n *\n * Phụ thuộc `next-themes` (peer OPTIONAL) để đọc theme hiện tại; app nào không\n * dùng next-themes thì đừng render component này — hoặc render `<Sonner>` trực tiếp.\n *\n * Đặt duy nhất MỘT `<Toaster />` ở gốc app; gọi `toast()` của `sonner` ở bất kỳ đâu.\n * Bản có icon theo trạng thái (success/warning/info) là composite `Toaster` ở Tier 2.\n */\n\n/**\n * Cửa DUY NHẤT của file này: `SonnerToaster` + `SonnerToasterProps`.\n *\n * Tên trần `Toaster`/`ToasterProps` cố ý KHÔNG xuất, dù chúng là tên tự nhiên ở đây: composite\n * `Toaster`/`ToasterProps` (`composite/toast.tsx`) đã chiếm đúng hai tên đó ở entry `.`. Nếu để\n * chúng xuất ra thì (a) `shell/index.ts` chỉ lấy bản `Sonner*` nên chúng là export CHẾT, và\n * (b) ai thêm `export * from \"./components/ui/sonner.js\"` vào `src/index.ts` sẽ vỡ build ngay,\n * còn thêm vào `shell/index.ts` thì im lặng dựng thêm một cửa `Toaster` thứ hai.\n * Giữ hai toaster là quyết định có chủ ý (docs/quyet-dinh-tich-hop.md §31) — nhưng \"hai toaster,\n * hai tên\" chỉ đúng khi tên trần không còn lối ra.\n */\nexport type SonnerToasterProps = ToasterProps;\n\nconst Toaster = ({ ...props }: ToasterProps) => {\n const { theme = \"system\" } = useTheme();\n /*\n * Ghi ngôn ngữ của `ComitorLocaleProvider` cho `toastPromise` — như `Toaster` ở Tier 2. Thiếu dòng\n * này (tới `3.0.0`) thì toast \"đang chạy\" của app dùng shell luôn nói tiếng Anh, dù đã có provider.\n */\n useToastLanguageBridge();\n\n return (\n <Sonner\n theme={theme as ToasterProps[\"theme\"]}\n className=\"toaster group\"\n style={\n {\n \"--normal-bg\": \"var(--popover)\",\n \"--normal-text\": \"var(--popover-foreground)\",\n \"--normal-border\": \"var(--border)\"\n } as React.CSSProperties\n }\n {...props}\n />\n );\n};\n\nexport { Toaster as SonnerToaster };\n"],"mappings":";AAuCI;AArCJ,SAAS,gBAAgB;AAEzB,SAAS,WAAW,cAAiC;AACrD,SAAS,8BAA8B;AAyBvC,MAAM,UAAU,CAAC,EAAE,GAAG,MAAM,MAAoB;AAC9C,QAAM,EAAE,QAAQ,SAAS,IAAI,SAAS;AAKtC,yBAAuB;AAEvB,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,WAAU;AAAA,MACV,OACE;AAAA,QACE,eAAe;AAAA,QACf,iBAAiB;AAAA,QACjB,mBAAmB;AAAA,MACrB;AAAA,MAED,GAAG;AAAA;AAAA,EACN;AAEJ;","names":[]}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
import { jsx } from "react/jsx-runtime";
|
|
2
3
|
import { Loader2Icon } from "lucide-react";
|
|
4
|
+
import { useComitorMessages } from "../../i18n/locale-provider.js";
|
|
3
5
|
import { cn } from "../../lib/cn.js";
|
|
4
6
|
function Spinner({ className, ...props }) {
|
|
5
|
-
|
|
7
|
+
const t = useComitorMessages("spinner");
|
|
8
|
+
return /* @__PURE__ */ jsx(Loader2Icon, { role: "status", "aria-label": t.label, className: cn("size-4 animate-spin", className), ...props });
|
|
6
9
|
}
|
|
7
10
|
export {
|
|
8
11
|
Spinner
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/ui/spinner.tsx"],"sourcesContent":["
|
|
1
|
+
{"version":3,"sources":["../../../src/components/ui/spinner.tsx"],"sourcesContent":["\"use client\";\n\nimport { Loader2Icon } from \"lucide-react\";\nimport type * as React from \"react\";\nimport { useComitorMessages } from \"../../i18n/locale-provider.js\";\nimport { cn } from \"../../lib/cn.js\";\n\nexport type SpinnerProps = React.ComponentProps<\"svg\">;\n\n/*\n * `\"use client\"` từ `3.0.0`: tên cho trình đọc màn hình theo `ComitorLocaleProvider` (namespace\n * `spinner`), mà context chỉ đọc được ở client. Trước đó file này tĩnh và ghim cứng \"Đang tải\".\n * `aria-label` truyền vào vẫn thắng vì `{...props}` trải sau.\n */\nfunction Spinner({ className, ...props }: SpinnerProps) {\n const t = useComitorMessages(\"spinner\");\n return <Loader2Icon role=\"status\" aria-label={t.label} className={cn(\"size-4 animate-spin\", className)} {...props} />;\n}\n\nexport { Spinner };\n"],"mappings":";AAgBS;AAdT,SAAS,mBAAmB;AAE5B,SAAS,0BAA0B;AACnC,SAAS,UAAU;AASnB,SAAS,QAAQ,EAAE,WAAW,GAAG,MAAM,GAAiB;AACtD,QAAM,IAAI,mBAAmB,SAAS;AACtC,SAAO,oBAAC,eAAY,MAAK,UAAS,cAAY,EAAE,OAAO,WAAW,GAAG,uBAAuB,SAAS,GAAI,GAAG,OAAO;AACrH;","names":[]}
|