@comitor/ui 2.0.0 → 3.0.0
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/LICENSE +35 -0
- package/README.md +67 -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 +4 -2
- package/dist/components/composite/toast.js +9 -1
- package/dist/components/composite/toast.js.map +1 -1
- package/dist/components/ui/action-bar-constants.d.ts +35 -0
- package/dist/components/ui/action-bar-constants.js +6 -0
- package/dist/components/ui/action-bar-constants.js.map +1 -0
- package/dist/components/ui/action-bar.d.ts +68 -0
- package/dist/components/ui/action-bar.js +522 -0
- package/dist/components/ui/action-bar.js.map +1 -0
- package/dist/components/ui/alert.d.ts +1 -1
- package/dist/components/ui/badge-overflow.d.ts +30 -0
- package/dist/components/ui/badge-overflow.js +181 -0
- package/dist/components/ui/badge-overflow.js.map +1 -0
- package/dist/components/ui/badge.d.ts +1 -1
- package/dist/components/ui/banner.d.ts +80 -0
- package/dist/components/ui/banner.js +520 -0
- package/dist/components/ui/banner.js.map +1 -0
- 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.d.ts +50 -0
- package/dist/components/ui/circular-progress.js +266 -0
- package/dist/components/ui/circular-progress.js.map +1 -0
- 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 +39 -0
- package/dist/components/ui/cropper-constants.js +6 -0
- package/dist/components/ui/cropper-constants.js.map +1 -0
- package/dist/components/ui/cropper.d.ts +107 -0
- package/dist/components/ui/cropper.js +1269 -0
- package/dist/components/ui/cropper.js.map +1 -0
- 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/drawer.d.ts +1 -1
- package/dist/components/ui/drawer.js +2 -2
- package/dist/components/ui/drawer.js.map +1 -1
- package/dist/components/ui/item.d.ts +2 -2
- package/dist/components/ui/kanban-constants.d.ts +67 -0
- package/dist/components/ui/kanban-constants.js +6 -0
- package/dist/components/ui/kanban-constants.js.map +1 -0
- package/dist/components/ui/kanban.d.ts +72 -0
- package/dist/components/ui/kanban.js +787 -0
- package/dist/components/ui/kanban.js.map +1 -0
- package/dist/components/ui/key-value-constants.d.ts +37 -0
- package/dist/components/ui/key-value-constants.js +6 -0
- package/dist/components/ui/key-value-constants.js.map +1 -0
- package/dist/components/ui/key-value.d.ts +96 -0
- package/dist/components/ui/key-value.js +641 -0
- package/dist/components/ui/key-value.js.map +1 -0
- package/dist/components/ui/mask-input-constants.d.ts +98 -0
- package/dist/components/ui/mask-input-constants.js +600 -0
- package/dist/components/ui/mask-input-constants.js.map +1 -0
- package/dist/components/ui/mask-input.d.ts +39 -0
- package/dist/components/ui/mask-input.js +560 -0
- package/dist/components/ui/mask-input.js.map +1 -0
- package/dist/components/ui/mention.d.ts +32 -0
- package/dist/components/ui/mention.js +126 -0
- package/dist/components/ui/mention.js.map +1 -0
- 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 +65 -0
- package/dist/components/ui/phone-input-constants.js +6 -0
- package/dist/components/ui/phone-input-constants.js.map +1 -0
- package/dist/components/ui/phone-input.d.ts +92 -0
- package/dist/components/ui/phone-input.js +742 -0
- package/dist/components/ui/phone-input.js.map +1 -0
- package/dist/components/ui/progress.d.ts +2 -2
- package/dist/components/ui/rating.d.ts +57 -0
- package/dist/components/ui/rating.js +648 -0
- package/dist/components/ui/rating.js.map +1 -0
- package/dist/components/ui/responsive-dialog.d.ts +35 -0
- package/dist/components/ui/responsive-dialog.js +193 -0
- package/dist/components/ui/responsive-dialog.js.map +1 -0
- package/dist/components/ui/scroll-spy.d.ts +42 -0
- package/dist/components/ui/scroll-spy.js +336 -0
- package/dist/components/ui/scroll-spy.js.map +1 -0
- 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/spinner.js +4 -1
- package/dist/components/ui/spinner.js.map +1 -1
- package/dist/components/ui/stepper-constants.d.ts +34 -0
- package/dist/components/ui/stepper-constants.js +6 -0
- package/dist/components/ui/stepper-constants.js.map +1 -0
- package/dist/components/ui/stepper.d.ts +81 -0
- package/dist/components/ui/stepper.js +902 -0
- package/dist/components/ui/stepper.js.map +1 -0
- package/dist/components/ui/tags-input.d.ts +42 -0
- package/dist/components/ui/tags-input.js +129 -0
- package/dist/components/ui/tags-input.js.map +1 -0
- package/dist/components/ui/time-picker-constants.d.ts +51 -0
- package/dist/components/ui/time-picker-constants.js +6 -0
- package/dist/components/ui/time-picker-constants.js.map +1 -0
- package/dist/components/ui/time-picker.d.ts +120 -0
- package/dist/components/ui/time-picker.js +1618 -0
- package/dist/components/ui/time-picker.js.map +1 -0
- package/dist/components/ui/timeline.d.ts +32 -0
- package/dist/components/ui/timeline.js +559 -0
- package/dist/components/ui/timeline.js.map +1 -0
- package/dist/components/ui/toggle.d.ts +1 -1
- package/dist/components/ui/tour-constants.d.ts +44 -0
- package/dist/components/ui/tour-constants.js +6 -0
- package/dist/components/ui/tour-constants.js.map +1 -0
- package/dist/components/ui/tour.d.ts +125 -0
- package/dist/components/ui/tour.js +1207 -0
- package/dist/components/ui/tour.js.map +1 -0
- 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-cell-wrapper.js +1 -1
- package/dist/data-grid/data-grid-cell-wrapper.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-context-menu.js +1 -1
- package/dist/data-grid/data-grid-context-menu.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-paste-dialog.js +1 -1
- package/dist/data-grid/data-grid-paste-dialog.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-row.js +1 -1
- package/dist/data-grid/data-grid-row.js.map +1 -1
- package/dist/data-grid/data-grid-search.js +1 -1
- package/dist/data-grid/data-grid-search.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 +3 -4
- 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 +17 -20
- 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 +4 -4
- package/dist/data-grid/use-data-grid-undo-redo.js.map +1 -1
- package/dist/data-grid/use-data-grid.d.ts +21 -6
- package/dist/data-grid/use-data-grid.js +11 -9
- 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 +109 -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 +11 -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 +17 -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 +15 -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 +63 -0
- package/dist/index.js +66 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/compose-refs.js.map +1 -0
- package/dist/lib/use-as-ref.js.map +1 -0
- package/dist/lib/use-is-mobile.d.ts +10 -0
- package/dist/lib/use-is-mobile.js +21 -0
- package/dist/lib/use-is-mobile.js.map +1 -0
- package/dist/lib/use-isomorphic-layout-effect.js.map +1 -0
- package/dist/lib/use-lazy-ref.js.map +1 -0
- package/dist/lib/visually-hidden-input.d.ts +12 -0
- package/dist/lib/visually-hidden-input.js +105 -0
- package/dist/lib/visually-hidden-input.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/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 +8 -3
- package/styles.css +19 -0
- package/dist/data-grid/compose-refs.js.map +0 -1
- package/dist/data-grid/use-as-ref.js.map +0 -1
- package/dist/data-grid/use-isomorphic-layout-effect.js.map +0 -1
- package/dist/data-grid/use-lazy-ref.js.map +0 -1
- /package/dist/{data-grid → lib}/compose-refs.d.ts +0 -0
- /package/dist/{data-grid → lib}/compose-refs.js +0 -0
- /package/dist/{data-grid → lib}/use-as-ref.d.ts +0 -0
- /package/dist/{data-grid → lib}/use-as-ref.js +0 -0
- /package/dist/{data-grid → lib}/use-isomorphic-layout-effect.d.ts +0 -0
- /package/dist/{data-grid → lib}/use-isomorphic-layout-effect.js +0 -0
- /package/dist/{data-grid → lib}/use-lazy-ref.d.ts +0 -0
- /package/dist/{data-grid → lib}/use-lazy-ref.js +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/shell/app-header.tsx"],"sourcesContent":["\"use client\";\n\nimport { Menu, PanelLeft, Search } from \"lucide-react\";\nimport type { ReactNode } from \"react\";\n// `useIsMac` nhập thẳng từ Tier 2: 0.2.0 chỉ còn MỘT cửa xuất cho nó (entry `.`),\n// `/shell` không re-export nữa.\nimport { useIsMac } from \"../components/composite/keyboard-hint.js\";\nimport { Kbd } from \"../components/ui/kbd.js\";\nimport { Tooltip, TooltipContent, TooltipTrigger } from \"../components/ui/tooltip.js\";\nimport { cn } from \"../lib/cn.js\";\nimport { AppLauncher } from \"./app-launcher.js\";\nimport { NotificationsButton } from \"./notifications-button.js\";\nimport { useShell } from \"./shell-context.js\";\nimport { ThemeToggle } from \"./theme-toggle.js\";\nimport { UserMenu, type UserMenuProps } from \"./user-menu.js\";\n\nexport interface AppHeaderProps {\n className?: string;\n /** Tiêu đề trang — dùng khi không truyền `breadcrumb`. */\n title?: ReactNode;\n /** Slot breadcrumb (composite `PageHeader` của app) — ưu tiên hơn `title`. */\n breadcrumb?: ReactNode;\n /** Slot ngoài cùng bên trái (vd `<ComitorLockup size=\"xs\" variant=\"auto\" />`). */\n logo?: ReactNode;\n\n /** Thay ô tìm kiếm dựng sẵn bằng UI riêng của app. */\n searchSlot?: ReactNode;\n showSearch?: boolean;\n /** Nút thu gọn sidebar ở header (layout sidebar-first thường để ở sidebar). */\n showSidebarToggle?: boolean;\n\n /** Slot thông báo (app tự render Popover + panel). Có slot thì `notificationCount` bị bỏ qua. */\n notificationsSlot?: ReactNode;\n notificationCount?: number;\n onNotificationsClick?: () => void;\n\n /** Slot phụ bên phải, đứng trước AppLauncher. */\n extraSlot?: ReactNode;\n\n showAppLauncher?: boolean;\n showThemeToggle?: boolean;\n showUserMenu?: boolean;\n /** Chuyển tiếp props cho `UserMenu` (profileHref, settingsHref, items…). */\n userMenuProps?: UserMenuProps;\n}\n\n/**\n * Header cao 56px (`--header-height`), nền `bg-background/80 backdrop-blur`.\n * Comitor là hệ sáng — **không** dùng header luôn-tối.\n *\n * BA VÙNG, và ô tìm nằm ở vùng GIỮA — đúng bố cục AppHeader ở mục Patterns của comitor-ds:\n *\n * trái hamburger (mobile) / nút thu gọn sidebar + logo + breadcrumb hoặc tiêu đề\n * giữa ô tìm (Cmd/Ctrl+K), bo tròn hết cỡ\n * phải AppLauncher · thông báo · ThemeToggle · UserMenu\n *\n * Hai vùng ngoài đều `flex-1`, vùng giữa `shrink-0` — đó là điều kiện để vùng giữa nằm CHÍNH GIỮA\n * header bất kể breadcrumb bên trái dài ngắn thế nào. Nếu để ô tìm trong cụm phải (bản trước) thì\n * nó trôi theo số lượng nút bên phải; nếu căn giữa bằng `absolute left-1/2` thì nó đè lên\n * breadcrumb dài. Cách này không mắc cả hai.\n */\nexport function AppHeader({\n className,\n title,\n breadcrumb,\n logo,\n searchSlot,\n showSearch = true,\n showSidebarToggle = false,\n notificationsSlot,\n notificationCount,\n onNotificationsClick,\n extraSlot,\n showAppLauncher = true,\n showThemeToggle = true,\n showUserMenu = true,\n userMenuProps\n}: AppHeaderProps) {\n const { setMobileMenuOpen, setCommandPaletteOpen, toggleSidebar, sidebarCollapsed, labels, user } = useShell();\n const isMac = useIsMac();\n const modKey = isMac ? \"⌘\" : \"Ctrl\";\n\n return (\n <header\n className={cn(\n \"sticky top-0 z-40 flex h-header shrink-0 items-center gap-2 px-3 sm:px-4\",\n \"border-b border-border bg-background/80 backdrop-blur-sm\",\n className\n )}\n >\n {/* ── VÙNG TRÁI ─────────────────────────────────────────────────────── */}\n <div className=\"flex min-w-0 flex-1 items-center gap-2\">\n {/* Mở menu off-canvas — chỉ màn hình hẹp */}\n <button\n type=\"button\"\n onClick={() => setMobileMenuOpen(true)}\n aria-label={labels.openMenu}\n className={cn(\n \"-ml-1 flex size-9 shrink-0 items-center justify-center rounded-md text-muted-foreground md:hidden\",\n \"transition-colors duration-150 ease-out hover:bg-muted hover:text-foreground\"\n )}\n >\n <Menu className=\"size-5\" />\n </button>\n\n {showSidebarToggle ? (\n <Tooltip>\n <TooltipTrigger asChild>\n <button\n type=\"button\"\n onClick={toggleSidebar}\n aria-label={sidebarCollapsed ? labels.expandSidebar : labels.collapseSidebar}\n className={cn(\n \"hidden size-9 shrink-0 items-center justify-center rounded-md text-muted-foreground md:flex\",\n \"transition-colors duration-150 ease-out hover:bg-muted hover:text-foreground\"\n )}\n >\n <PanelLeft className=\"size-4\" />\n </button>\n </TooltipTrigger>\n <TooltipContent side=\"bottom\">\n {sidebarCollapsed ? labels.expandSidebar : labels.collapseSidebar} · {modKey} + B\n </TooltipContent>\n </Tooltip>\n ) : null}\n\n {logo ? <div className=\"shrink-0\">{logo}</div> : null}\n\n {/* Breadcrumb thắng title khi có cả hai */}\n <div className=\"flex min-w-0 items-center\">\n {breadcrumb ?? (title ? <h1 className=\"truncate text-sm font-semibold text-foreground\">{title}</h1> : null)}\n </div>\n </div>\n\n {/*\n ── VÙNG GIỮA: ô tìm dạng PILL, và chỉ nó ────────────────────────────\n Từ `lg` trở lên vùng này giữ ô tìm 256px và nằm chính giữa header. Dưới `lg` ô pill bị\n `hidden` nên vùng này RỖNG và co về 0 — lúc đó hai vùng `flex-1` hai bên chia đôi phần\n còn lại, và nút icon tìm kiếm (nằm ở vùng PHẢI) được căn phải cùng các icon khác.\n ⚠ Nút icon KHÔNG được để ở đây: nó sẽ bị căn giữa header, tức một icon lẻ trôi giữa\n khoảng trống trong khi mọi icon khác nằm sát phải.\n */}\n <div className=\"flex shrink-0 items-center justify-center\">\n {showSearch\n ? (searchSlot ?? (\n <button\n type=\"button\"\n onClick={() => setCommandPaletteOpen(true)}\n className={cn(\n // `rounded-full` + `w-64`: đúng ô tìm của AppHeader ở mục Patterns\n // (`<Input className=\"rounded-full w-64\" />`). Pill cần nhiều đệm ngang hơn ô\n // vuông góc nên `px-3` thay vì `px-2.5`.\n //\n // ⚠ `lg:` chứ KHÔNG phải `sm:`. Ô tìm nằm ở vùng GIỮA và `shrink-0`, nên nó\n // ăn 256px cố định của vùng nội dung. Dải nguy hiểm là md→lg: từ 768px thanh\n // bên đã hiện (chiếm 256px) mà vùng nội dung mới 512px — trừ ô tìm thì hai\n // bên chỉ còn 104px mỗi bên, không đủ cho cụm nút (~190px). Đo thật ở 768px\n // trước khi vá: ba nút header tụt còn 16px bề ngang.\n // Từ 1024px vùng nội dung là 768px → mỗi bên 232px, vừa đủ. Dưới ngưỡng đó\n // dùng nút icon (36px) ngay bên dưới; phím ⌘K vẫn mở bảng lệnh như thường.\n \"hidden h-8 w-64 items-center gap-2 rounded-full border border-input bg-muted/40 px-3 lg:flex\",\n \"text-sm text-muted-foreground transition-colors duration-150 ease-out\",\n // Viền control lúc hover phải ĐẬM HƠN lúc nghỉ, không nhạt đi. `border-gold-300`\n // (#E8B824) chỉ được 1,86:1 với `--background` và 1,73:1 với `--muted` (chính là\n // nền mà `hover:bg-muted` đổi sang) ở light — ô tìm gần như MẤT VIỀN đúng lúc rê\n // chuột vào, dưới xa mức 3:1 WCAG 1.4.11 đòi cho viền control. `gold-300` lại là\n // bậc thang màu thô, không đổi theo theme, nên ở dark cùng giá trị đó thành\n // 10,19:1 — mỗi theme một hành vi.\n // `--control-edge-strong` là bậc viền control lúc hover, đo đúng cho vai trò này:\n // 10,55:1 / 9,86:1 (light, trên `--background` / `--muted`) · 11,65:1 / 9,93:1\n // (dark). Lúc nghỉ vẫn `border-input` = 3,83:1 (light) · 4,94:1 (dark) ✓.\n \"hover:border-control-edge-strong hover:bg-muted\"\n )}\n >\n <Search className=\"size-3.5 shrink-0\" />\n <span className=\"flex-1 truncate text-left\">{labels.searchPlaceholder}</span>\n <Kbd>{modKey}K</Kbd>\n </button>\n ))\n : null}\n </div>\n\n {/* ── VÙNG PHẢI ─────────────────────────────────────────────────────── */}\n {/* `shrink-0`: cụm nút KHÔNG được co. Thiếu nó thì flexbox bóp chúng thay vì bóp ô tìm —\n ở 768px (thanh bên vừa hiện mà vùng nội dung mới 512px) chúng tụt còn 16px bề ngang. */}\n <div className=\"flex shrink-0 flex-1 items-center justify-end gap-1\">\n {extraSlot}\n\n {/*\n Dưới `lg`: ô tìm thu thành icon và đứng ở ĐÂY, cùng hàng với các icon khác bên phải —\n không phải ở vùng giữa. `searchSlot` của app thì không có bản icon nên nó ở lại vùng\n giữa và tự lo phần thu hẹp.\n */}\n {showSearch && !searchSlot ? (\n <button\n type=\"button\"\n onClick={() => setCommandPaletteOpen(true)}\n aria-label={labels.search}\n className={cn(\n \"flex size-9 items-center justify-center rounded-md text-muted-foreground lg:hidden\",\n \"transition-colors duration-150 ease-out hover:bg-muted hover:text-foreground\"\n )}\n >\n <Search className=\"size-4\" />\n </button>\n ) : null}\n\n {/*\n `hidden md:flex`: nút danh sách ứng dụng chỉ ở desktop. Dưới `md`, `MobileMenu` đã\n render `<AppLauncher inline variant=\"list\">` ngay trong menu hamburger — để cả hai là\n hai lối vào cho cùng một danh sách, và trên màn hình hẹp thì mỗi ô 36px của header đều\n đáng giá. Cùng mốc `md` với nút đổi giao diện ngay dưới.\n ⚠ App nào tắt `showMobileMenu`/`showAppLauncher` của `MobileMenu` thì phải xét lại đây,\n nếu không màn hình hẹp sẽ mất hẳn lối vào danh sách ứng dụng.\n */}\n {showAppLauncher ? <AppLauncher className=\"hidden md:flex\" /> : null}\n\n {notificationsSlot ??\n (notificationCount === undefined ? null : (\n <NotificationsButton\n count={notificationCount}\n label={labels.notifications}\n onClick={onNotificationsClick}\n />\n ))}\n\n {/*\n `hidden md:flex`: nút đổi giao diện chỉ ở desktop. Dưới `md` lối vào là submenu \"Giao\n diện\" trong `UserMenu` (mặc định `showThemeSubmenu=\"mobile\"`) — hai bên dùng CHUNG mốc\n `md` nên ở mọi bề rộng có đúng MỘT lối vào, không trùng và không hụt.\n ⚠ Đổi mốc ở một bên thì phải đổi ở bên kia, nếu không sẽ có dải bề rộng mất hẳn nút đổi\n giao diện.\n */}\n {showThemeToggle ? <ThemeToggle labels={labels} className=\"hidden md:flex\" /> : null}\n\n {showUserMenu && user ? (\n <>\n <span className=\"mx-1 hidden h-5 w-px bg-border sm:block\" aria-hidden=\"true\" />\n <UserMenu {...userMenuProps} />\n </>\n ) : null}\n </div>\n </header>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/shell/app-header.tsx"],"sourcesContent":["\"use client\";\n\nimport { Menu, PanelLeft, Search } from \"lucide-react\";\nimport type { ReactNode } from \"react\";\n// `useIsMac` nhập thẳng từ Tier 2: 0.2.0 chỉ còn MỘT cửa xuất cho nó (entry `.`),\n// `/shell` không re-export nữa.\nimport { useIsMac } from \"../components/composite/keyboard-hint.js\";\nimport { Kbd } from \"../components/ui/kbd.js\";\nimport { Tooltip, TooltipContent, TooltipTrigger } from \"../components/ui/tooltip.js\";\nimport { cn } from \"../lib/cn.js\";\nimport { AppLauncher } from \"./app-launcher.js\";\nimport { NotificationsButton } from \"./notifications-button.js\";\nimport { useShell } from \"./shell-context.js\";\nimport { ThemeToggle } from \"./theme-toggle.js\";\nimport { UserMenu, type UserMenuProps } from \"./user-menu.js\";\n\nexport interface AppHeaderProps {\n className?: string;\n /** Tiêu đề trang — dùng khi không truyền `breadcrumb`. */\n title?: ReactNode;\n /** Slot breadcrumb (composite `PageHeader` của app) — ưu tiên hơn `title`. */\n breadcrumb?: ReactNode;\n /** Slot ngoài cùng bên trái (vd `<ComitorLockup size=\"xs\" variant=\"auto\" />`). */\n logo?: ReactNode;\n\n /** Thay ô tìm kiếm dựng sẵn bằng UI riêng của app. */\n searchSlot?: ReactNode;\n showSearch?: boolean;\n /** Nút thu gọn sidebar ở header (layout sidebar-first thường để ở sidebar). */\n showSidebarToggle?: boolean;\n\n /** Slot thông báo (app tự render Popover + panel). Có slot thì `notificationCount` bị bỏ qua. */\n notificationsSlot?: ReactNode;\n notificationCount?: number;\n onNotificationsClick?: () => void;\n\n /** Slot phụ bên phải, đứng trước AppLauncher. */\n extraSlot?: ReactNode;\n\n showAppLauncher?: boolean;\n showThemeToggle?: boolean;\n showUserMenu?: boolean;\n /** Chuyển tiếp props cho `UserMenu` (profileHref, settingsHref, items…). */\n userMenuProps?: UserMenuProps;\n}\n\n/**\n * Header cao 56px (`--header-height`), nền `bg-background/80 backdrop-blur`.\n * Comitor là hệ sáng — **không** dùng header luôn-tối.\n *\n * BA VÙNG, và ô tìm nằm ở vùng GIỮA — đúng bố cục AppHeader ở mục Patterns của comitor-ds:\n *\n * trái hamburger (mobile) / nút thu gọn sidebar + logo + breadcrumb hoặc tiêu đề\n * giữa ô tìm (Cmd/Ctrl+K), bo tròn hết cỡ\n * phải AppLauncher · thông báo · ThemeToggle · UserMenu\n *\n * Hai vùng ngoài đều `flex-1`, vùng giữa `shrink-0` — đó là điều kiện để vùng giữa nằm CHÍNH GIỮA\n * header bất kể breadcrumb bên trái dài ngắn thế nào. Nếu để ô tìm trong cụm phải (bản trước) thì\n * nó trôi theo số lượng nút bên phải; nếu căn giữa bằng `absolute left-1/2` thì nó đè lên\n * breadcrumb dài. Cách này không mắc cả hai.\n */\nexport function AppHeader({\n className,\n title,\n breadcrumb,\n logo,\n searchSlot,\n showSearch = true,\n showSidebarToggle = false,\n notificationsSlot,\n notificationCount,\n onNotificationsClick,\n extraSlot,\n showAppLauncher = true,\n showThemeToggle = true,\n showUserMenu = true,\n userMenuProps\n}: AppHeaderProps) {\n const { setMobileMenuOpen, setCommandPaletteOpen, toggleSidebar, sidebarCollapsed, labels, user } = useShell();\n const isMac = useIsMac();\n const modKey = isMac ? \"⌘\" : \"Ctrl\";\n\n return (\n <header\n className={cn(\n \"sticky top-0 z-40 flex h-header shrink-0 items-center gap-2 px-3 sm:px-4\",\n \"border-b border-border bg-background/80 backdrop-blur-sm\",\n className\n )}\n >\n {/* ── VÙNG TRÁI ─────────────────────────────────────────────────────── */}\n <div className=\"flex min-w-0 flex-1 items-center gap-2\">\n {/* Mở menu off-canvas — chỉ màn hình hẹp */}\n <button\n type=\"button\"\n onClick={() => setMobileMenuOpen(true)}\n aria-label={labels.openMenu}\n className={cn(\n \"-ml-1 flex size-9 shrink-0 items-center justify-center rounded-md text-muted-foreground md:hidden\",\n \"transition-colors duration-150 ease-out hover:bg-muted hover:text-foreground\"\n )}\n >\n <Menu className=\"size-5\" />\n </button>\n\n {showSidebarToggle ? (\n <Tooltip>\n <TooltipTrigger asChild>\n <button\n type=\"button\"\n onClick={toggleSidebar}\n aria-label={sidebarCollapsed ? labels.expandSidebar : labels.collapseSidebar}\n className={cn(\n \"hidden size-9 shrink-0 items-center justify-center rounded-md text-muted-foreground md:flex\",\n \"transition-colors duration-150 ease-out hover:bg-muted hover:text-foreground\"\n )}\n >\n <PanelLeft className=\"size-4\" />\n </button>\n </TooltipTrigger>\n <TooltipContent side=\"bottom\">\n {sidebarCollapsed ? labels.expandSidebar : labels.collapseSidebar} · {modKey} + B\n </TooltipContent>\n </Tooltip>\n ) : null}\n\n {logo ? <div className=\"shrink-0\">{logo}</div> : null}\n\n {/* Breadcrumb thắng title khi có cả hai */}\n <div className=\"flex min-w-0 items-center\">\n {breadcrumb ?? (title ? <h1 className=\"truncate text-sm font-semibold text-foreground\">{title}</h1> : null)}\n </div>\n </div>\n\n {/*\n ── VÙNG GIỮA: ô tìm dạng PILL, và chỉ nó ────────────────────────────\n Từ `lg` trở lên vùng này giữ ô tìm 256px và nằm chính giữa header. Dưới `lg` ô pill bị\n `hidden` nên vùng này RỖNG và co về 0 — lúc đó hai vùng `flex-1` hai bên chia đôi phần\n còn lại, và nút icon tìm kiếm (nằm ở vùng PHẢI) được căn phải cùng các icon khác.\n ⚠ Nút icon KHÔNG được để ở đây: nó sẽ bị căn giữa header, tức một icon lẻ trôi giữa\n khoảng trống trong khi mọi icon khác nằm sát phải.\n */}\n <div className=\"flex shrink-0 items-center justify-center\">\n {showSearch\n ? (searchSlot ?? (\n <button\n type=\"button\"\n onClick={() => setCommandPaletteOpen(true)}\n className={cn(\n // `rounded-full` + `w-64`: đúng ô tìm của AppHeader ở mục Patterns\n // (`<Input className=\"rounded-full w-64\" />`). Pill cần nhiều đệm ngang hơn ô\n // vuông góc nên `px-3` thay vì `px-2.5`.\n //\n // ⚠ `lg:` chứ KHÔNG phải `sm:`. Ô tìm nằm ở vùng GIỮA và `shrink-0`, nên nó\n // ăn 256px cố định của vùng nội dung. Dải nguy hiểm là md→lg: từ 768px thanh\n // bên đã hiện (chiếm 256px) mà vùng nội dung mới 512px — trừ ô tìm thì hai\n // bên chỉ còn 104px mỗi bên, không đủ cho cụm nút (~190px). Đo thật ở 768px\n // trước khi vá: ba nút header tụt còn 16px bề ngang.\n // Từ 1024px vùng nội dung là 768px → mỗi bên 232px, vừa đủ. Dưới ngưỡng đó\n // dùng nút icon (36px) ngay bên dưới; phím ⌘K vẫn mở bảng lệnh như thường.\n \"hidden h-8 w-64 items-center gap-2 rounded-full border border-input bg-muted/40 px-3 lg:flex\",\n \"text-sm text-muted-foreground transition-colors duration-150 ease-out\",\n // Viền control lúc hover phải ĐẬM HƠN lúc nghỉ, không nhạt đi. `border-gold-300`\n // (#E8B824) chỉ được 1,86:1 với `--background` và 1,73:1 với `--muted` (chính là\n // nền mà `hover:bg-muted` đổi sang) ở light — ô tìm gần như MẤT VIỀN đúng lúc rê\n // chuột vào, dưới xa mức 3:1 WCAG 1.4.11 đòi cho viền control. `gold-300` lại là\n // bậc thang màu thô, không đổi theo theme, nên ở dark cùng giá trị đó thành\n // 10,19:1 — mỗi theme một hành vi.\n // `--control-edge-strong` là bậc viền control lúc hover, đo đúng cho vai trò này:\n // 10,55:1 / 9,86:1 (light, trên `--background` / `--muted`) · 11,65:1 / 9,93:1\n // (dark). Lúc nghỉ vẫn `border-input` = 3,83:1 (light) · 4,94:1 (dark) ✓.\n \"hover:border-control-edge-strong hover:bg-muted\"\n )}\n >\n <Search className=\"size-3.5 shrink-0\" />\n <span className=\"flex-1 truncate text-left\">{labels.searchPlaceholder}</span>\n <Kbd>{modKey}K</Kbd>\n </button>\n ))\n : null}\n </div>\n\n {/* ── VÙNG PHẢI ─────────────────────────────────────────────────────── */}\n {/* `shrink-0`: cụm nút KHÔNG được co. Thiếu nó thì flexbox bóp chúng thay vì bóp ô tìm —\n ở 768px (thanh bên vừa hiện mà vùng nội dung mới 512px) chúng tụt còn 16px bề ngang. */}\n <div className=\"flex shrink-0 flex-1 items-center justify-end gap-1\">\n {extraSlot}\n\n {/*\n Dưới `lg`: ô tìm thu thành icon và đứng ở ĐÂY, cùng hàng với các icon khác bên phải —\n không phải ở vùng giữa. `searchSlot` của app thì không có bản icon nên nó ở lại vùng\n giữa và tự lo phần thu hẹp.\n */}\n {showSearch && !searchSlot ? (\n <button\n type=\"button\"\n onClick={() => setCommandPaletteOpen(true)}\n aria-label={labels.search}\n className={cn(\n \"flex size-9 items-center justify-center rounded-md text-muted-foreground lg:hidden\",\n \"transition-colors duration-150 ease-out hover:bg-muted hover:text-foreground\"\n )}\n >\n <Search className=\"size-4\" />\n </button>\n ) : null}\n\n {/*\n `hidden md:flex`: nút danh sách ứng dụng chỉ ở desktop. Dưới `md`, `MobileMenu` đã\n render `<AppLauncher inline variant=\"list\">` ngay trong menu hamburger — để cả hai là\n hai lối vào cho cùng một danh sách, và trên màn hình hẹp thì mỗi ô 36px của header đều\n đáng giá. Cùng mốc `md` với nút đổi giao diện ngay dưới.\n ⚠ App nào tắt `showMobileMenu`/`showAppLauncher` của `MobileMenu` thì phải xét lại đây,\n nếu không màn hình hẹp sẽ mất hẳn lối vào danh sách ứng dụng.\n */}\n {showAppLauncher ? <AppLauncher className=\"hidden md:flex\" /> : null}\n\n {notificationsSlot ??\n (notificationCount === undefined ? null : (\n <NotificationsButton\n count={notificationCount}\n label={labels.notifications}\n onClick={onNotificationsClick}\n />\n ))}\n\n {/*\n `hidden md:flex`: nút đổi giao diện chỉ ở desktop. Dưới `md` lối vào là submenu đổi giao\n diện trong `UserMenu` (mặc định `showThemeSubmenu=\"mobile\"`) — hai bên dùng CHUNG mốc\n `md` nên ở mọi bề rộng có đúng MỘT lối vào, không trùng và không hụt.\n ⚠ Đổi mốc ở một bên thì phải đổi ở bên kia, nếu không sẽ có dải bề rộng mất hẳn nút đổi\n giao diện.\n */}\n {showThemeToggle ? <ThemeToggle labels={labels} className=\"hidden md:flex\" /> : null}\n\n {showUserMenu && user ? (\n <>\n <span className=\"mx-1 hidden h-5 w-px bg-border sm:block\" aria-hidden=\"true\" />\n <UserMenu {...userMenuProps} />\n </>\n ) : null}\n </div>\n </header>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -29,6 +29,23 @@ interface AppShellProps extends ShellProviderProps {
|
|
|
29
29
|
* đọc/ghi trạng thái qua `useShell().commandPaletteOpen`.
|
|
30
30
|
*/
|
|
31
31
|
commandPaletteSlot?: ReactNode;
|
|
32
|
+
/**
|
|
33
|
+
* Ngôn ngữ của cả cây shell (mã BCP 47: `"vi"`, `"en"`, `"en-GB"`…) — TIỆN ÍCH, không phải chỗ
|
|
34
|
+
* khuyên dùng.
|
|
35
|
+
*
|
|
36
|
+
* Chỗ đúng là `<ComitorLocaleProvider locale={…}>` ở ROOT layout: nó phủ cả những trang KHÔNG
|
|
37
|
+
* nằm trong shell (đăng nhập, trang lỗi, trang công khai) và mọi component của gói đọc theo nó.
|
|
38
|
+
* Prop này chỉ dành cho app không có root layout riêng để đặt provider.
|
|
39
|
+
*
|
|
40
|
+
* Có truyền → `AppShell` bọc cây của nó trong `ComitorLocaleProvider locale={locale}`. Bỏ trống →
|
|
41
|
+
* không bọc gì, mọi thứ đọc provider gần nhất bên ngoài (không có thì tiếng Anh).
|
|
42
|
+
*
|
|
43
|
+
* ⚠ Provider dựng ở đây là provider MỚI: nó KHÔNG kế thừa `messages` (ghi đè chữ) hay
|
|
44
|
+
* `dateFnsLocale` của provider bên ngoài. Cần những thứ đó thì đặt provider ở root layout.
|
|
45
|
+
* ⚠ Đổi qua lại giữa có và không truyền prop này đổi hình dạng cây — cả shell dựng lại từ đầu
|
|
46
|
+
* (mất trạng thái). Giữ nó luôn có hoặc luôn vắng.
|
|
47
|
+
*/
|
|
48
|
+
locale?: string;
|
|
32
49
|
className?: string;
|
|
33
50
|
contentClassName?: string;
|
|
34
51
|
}
|
|
@@ -36,11 +53,12 @@ interface AppShellProps extends ShellProviderProps {
|
|
|
36
53
|
* Khung ứng dụng lắp sẵn: `ShellProvider` + `Sidebar` + `AppHeader` + `<main>` + `MobileMenu`.
|
|
37
54
|
*
|
|
38
55
|
* ⚠ Bọc app bằng `<ThemeProvider>` ở **root layout**, NGOÀI `<AppShell>` — next-themes
|
|
39
|
-
* cần nằm trên cùng để tránh nháy theme lúc hydrate.
|
|
56
|
+
* cần nằm trên cùng để tránh nháy theme lúc hydrate. `<ComitorLocaleProvider>` cũng nên ở đó
|
|
57
|
+
* (ngôn ngữ của cả gói, kể cả trang ngoài shell); prop `locale` chỉ là lối tắt.
|
|
40
58
|
*
|
|
41
59
|
* Có `"use client"`: mọi callback (`onLogout`, `onWorkspaceChange`…) là hàm nên nơi
|
|
42
60
|
* render `<AppShell>` phải là client component.
|
|
43
61
|
*/
|
|
44
|
-
declare function AppShell({ layout, showSidebar, headerProps, sidebarProps, mobileMenuProps, commandPaletteSlot, className, contentClassName, children, ...providerProps }: AppShellProps): React.JSX.Element;
|
|
62
|
+
declare function AppShell({ layout, showSidebar, headerProps, sidebarProps, mobileMenuProps, commandPaletteSlot, locale, className, contentClassName, children, ...providerProps }: AppShellProps): React.JSX.Element;
|
|
45
63
|
|
|
46
64
|
export { AppShell, type AppShellLayout, type AppShellProps };
|
package/dist/shell/app-shell.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { ComitorLocaleProvider } from "../i18n/locale-provider.js";
|
|
3
4
|
import { cn } from "../lib/cn.js";
|
|
4
5
|
import { AppHeader } from "./app-header.js";
|
|
5
6
|
import { MobileMenu } from "./mobile-menu.js";
|
|
@@ -14,12 +15,13 @@ function AppShell({
|
|
|
14
15
|
sidebarProps,
|
|
15
16
|
mobileMenuProps,
|
|
16
17
|
commandPaletteSlot,
|
|
18
|
+
locale,
|
|
17
19
|
className,
|
|
18
20
|
contentClassName,
|
|
19
21
|
children,
|
|
20
22
|
...providerProps
|
|
21
23
|
}) {
|
|
22
|
-
|
|
24
|
+
const shell = /* @__PURE__ */ jsx(ShellProvider, { ...providerProps, children: /* @__PURE__ */ jsx(
|
|
23
25
|
AppShellFrame,
|
|
24
26
|
{
|
|
25
27
|
layout,
|
|
@@ -33,6 +35,7 @@ function AppShell({
|
|
|
33
35
|
children
|
|
34
36
|
}
|
|
35
37
|
) });
|
|
38
|
+
return locale === void 0 ? shell : /* @__PURE__ */ jsx(ComitorLocaleProvider, { locale, children: shell });
|
|
36
39
|
}
|
|
37
40
|
function AppShellFrame({
|
|
38
41
|
layout = "sidebar-first",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/shell/app-shell.tsx"],"sourcesContent":["\"use client\";\n\nimport type { ReactNode } from \"react\";\nimport { cn } from \"../lib/cn.js\";\nimport { AppHeader, type AppHeaderProps } from \"./app-header.js\";\nimport { MobileMenu, type MobileMenuProps } from \"./mobile-menu.js\";\nimport { ShellProvider, type ShellProviderProps, useShell } from \"./shell-context.js\";\nimport { Sidebar, type SidebarProps } from \"./sidebar.js\";\nimport { appAccentStyle } from \"./utils.js\";\nimport { WorkspaceSwitcher } from \"./workspace-switcher.js\";\n\nexport type AppShellLayout = \"sidebar-first\" | \"header-first\";\n\nexport interface AppShellProps extends ShellProviderProps {\n /**\n * \"sidebar-first\" (mặc định) — sidebar cao hết màn hình, header nằm bên phải nó\n * (kiểu Linear/Lark: WorkspaceSwitcher ở đỉnh sidebar).\n * \"header-first\" — header full width trên cùng, sidebar nằm dưới\n * (WorkspaceSwitcher chuyển lên header).\n */\n layout?: AppShellLayout;\n /** Ẩn sidebar hoàn toàn (app một khu, chỉ có header). */\n showSidebar?: boolean;\n headerProps?: AppHeaderProps;\n sidebarProps?: SidebarProps;\n mobileMenuProps?: MobileMenuProps;\n /**\n * Slot Command Palette — app tự render (dữ liệu tìm kiếm là của app),\n * đọc/ghi trạng thái qua `useShell().commandPaletteOpen`.\n */\n commandPaletteSlot?: ReactNode;\n className?: string;\n contentClassName?: string;\n}\n\n/**\n * Khung ứng dụng lắp sẵn: `ShellProvider` + `Sidebar` + `AppHeader` + `<main>` + `MobileMenu`.\n *\n * ⚠ Bọc app bằng `<ThemeProvider>` ở **root layout**, NGOÀI `<AppShell>` — next-themes\n * cần nằm trên cùng để tránh nháy theme lúc hydrate.\n *\n * Có `\"use client\"`: mọi callback (`onLogout`, `onWorkspaceChange`…) là hàm nên nơi\n * render `<AppShell>` phải là client component.\n */\nexport function AppShell({\n layout = \"sidebar-first\",\n showSidebar = true,\n headerProps,\n sidebarProps,\n mobileMenuProps,\n commandPaletteSlot,\n className,\n contentClassName,\n children,\n ...providerProps\n}: AppShellProps) {\n
|
|
1
|
+
{"version":3,"sources":["../../src/shell/app-shell.tsx"],"sourcesContent":["\"use client\";\n\nimport type { ReactNode } from \"react\";\nimport { ComitorLocaleProvider } from \"../i18n/locale-provider.js\";\nimport { cn } from \"../lib/cn.js\";\nimport { AppHeader, type AppHeaderProps } from \"./app-header.js\";\nimport { MobileMenu, type MobileMenuProps } from \"./mobile-menu.js\";\nimport { ShellProvider, type ShellProviderProps, useShell } from \"./shell-context.js\";\nimport { Sidebar, type SidebarProps } from \"./sidebar.js\";\nimport { appAccentStyle } from \"./utils.js\";\nimport { WorkspaceSwitcher } from \"./workspace-switcher.js\";\n\nexport type AppShellLayout = \"sidebar-first\" | \"header-first\";\n\nexport interface AppShellProps extends ShellProviderProps {\n /**\n * \"sidebar-first\" (mặc định) — sidebar cao hết màn hình, header nằm bên phải nó\n * (kiểu Linear/Lark: WorkspaceSwitcher ở đỉnh sidebar).\n * \"header-first\" — header full width trên cùng, sidebar nằm dưới\n * (WorkspaceSwitcher chuyển lên header).\n */\n layout?: AppShellLayout;\n /** Ẩn sidebar hoàn toàn (app một khu, chỉ có header). */\n showSidebar?: boolean;\n headerProps?: AppHeaderProps;\n sidebarProps?: SidebarProps;\n mobileMenuProps?: MobileMenuProps;\n /**\n * Slot Command Palette — app tự render (dữ liệu tìm kiếm là của app),\n * đọc/ghi trạng thái qua `useShell().commandPaletteOpen`.\n */\n commandPaletteSlot?: ReactNode;\n /**\n * Ngôn ngữ của cả cây shell (mã BCP 47: `\"vi\"`, `\"en\"`, `\"en-GB\"`…) — TIỆN ÍCH, không phải chỗ\n * khuyên dùng.\n *\n * Chỗ đúng là `<ComitorLocaleProvider locale={…}>` ở ROOT layout: nó phủ cả những trang KHÔNG\n * nằm trong shell (đăng nhập, trang lỗi, trang công khai) và mọi component của gói đọc theo nó.\n * Prop này chỉ dành cho app không có root layout riêng để đặt provider.\n *\n * Có truyền → `AppShell` bọc cây của nó trong `ComitorLocaleProvider locale={locale}`. Bỏ trống →\n * không bọc gì, mọi thứ đọc provider gần nhất bên ngoài (không có thì tiếng Anh).\n *\n * ⚠ Provider dựng ở đây là provider MỚI: nó KHÔNG kế thừa `messages` (ghi đè chữ) hay\n * `dateFnsLocale` của provider bên ngoài. Cần những thứ đó thì đặt provider ở root layout.\n * ⚠ Đổi qua lại giữa có và không truyền prop này đổi hình dạng cây — cả shell dựng lại từ đầu\n * (mất trạng thái). Giữ nó luôn có hoặc luôn vắng.\n */\n locale?: string;\n className?: string;\n contentClassName?: string;\n}\n\n/**\n * Khung ứng dụng lắp sẵn: `ShellProvider` + `Sidebar` + `AppHeader` + `<main>` + `MobileMenu`.\n *\n * ⚠ Bọc app bằng `<ThemeProvider>` ở **root layout**, NGOÀI `<AppShell>` — next-themes\n * cần nằm trên cùng để tránh nháy theme lúc hydrate. `<ComitorLocaleProvider>` cũng nên ở đó\n * (ngôn ngữ của cả gói, kể cả trang ngoài shell); prop `locale` chỉ là lối tắt.\n *\n * Có `\"use client\"`: mọi callback (`onLogout`, `onWorkspaceChange`…) là hàm nên nơi\n * render `<AppShell>` phải là client component.\n */\nexport function AppShell({\n layout = \"sidebar-first\",\n showSidebar = true,\n headerProps,\n sidebarProps,\n mobileMenuProps,\n commandPaletteSlot,\n locale,\n className,\n contentClassName,\n children,\n ...providerProps\n}: AppShellProps) {\n const shell = (\n <ShellProvider {...providerProps}>\n <AppShellFrame\n layout={layout}\n showSidebar={showSidebar}\n headerProps={headerProps}\n sidebarProps={sidebarProps}\n mobileMenuProps={mobileMenuProps}\n commandPaletteSlot={commandPaletteSlot}\n className={className}\n contentClassName={contentClassName}\n >\n {children}\n </AppShellFrame>\n </ShellProvider>\n );\n\n // Provider ở NGOÀI `ShellProvider`: chính `ShellProvider` đọc namespace `shell` để dựng `labels`.\n return locale === undefined ? shell : <ComitorLocaleProvider locale={locale}>{shell}</ComitorLocaleProvider>;\n}\n\ntype AppShellFrameProps = Pick<\n AppShellProps,\n | \"layout\"\n | \"showSidebar\"\n | \"headerProps\"\n | \"sidebarProps\"\n | \"mobileMenuProps\"\n | \"commandPaletteSlot\"\n | \"className\"\n | \"contentClassName\"\n> & { children: ReactNode };\n\n/**\n * Phần khung thật sự — tách khỏi `AppShell` vì cần `useShell()` (phải ở TRONG provider)\n * để gán `--app-accent` của app đang mở cho cả cây.\n */\nfunction AppShellFrame({\n layout = \"sidebar-first\",\n showSidebar = true,\n headerProps,\n sidebarProps,\n mobileMenuProps,\n commandPaletteSlot,\n className,\n contentClassName,\n children\n}: AppShellFrameProps) {\n const { currentApp } = useShell();\n const headerFirst = layout === \"header-first\";\n\n // Ở header-first, bộ chuyển workspace chuyển lên header — nếu để cả hai chỗ\n // thì người dùng thấy hai nút giống hệt nhau.\n // Spread TRƯỚC rồi mới đặt giá trị mặc định, nếu không `{...headerProps}` sẽ ghi đè\n // bằng `undefined` và giá trị mặc định tính ở trên thành công cốc.\n const header = (\n <AppHeader\n {...headerProps}\n showSidebarToggle={headerProps?.showSidebarToggle ?? headerFirst}\n logo={headerProps?.logo ?? (headerFirst ? <WorkspaceSwitcher className=\"w-56 shrink-0\" /> : undefined)}\n />\n );\n\n const sidebar = showSidebar ? (\n <Sidebar\n {...sidebarProps}\n showWorkspaceSwitcher={sidebarProps?.showWorkspaceSwitcher ?? !headerFirst}\n showCollapseButton={sidebarProps?.showCollapseButton ?? !headerFirst}\n />\n ) : null;\n\n /*\n * ─ VÌ SAO <main> PHẢI CÓ `relative` ─\n *\n * `overflow-y-auto` KHÔNG cắt phần tử `position: absolute` nếu chính nó không được định vị:\n * containing block của những phần tử đó nhảy lên tới `body`/ICB, nên chúng nằm ngoài tầm cắt\n * của `main` và **kéo dài vùng cuộn của cả TÀI LIỆU**. Khung shell là `h-dvh overflow-hidden`\n * nên phần kéo dài đó không có gì để vẽ: người dùng cuộn xuống và thấy một khoảng TRẮNG lớn\n * dưới đáy trang.\n *\n * Không phải lỗi lý thuyết — hai nguồn sinh ra nó có mặt trong chính gói này:\n * • `sr-only` của Tailwind là `position: absolute` không có `top/left`, nên nó đứng ở vị trí\n * tĩnh của mình. Trong `TablePagination`, bốn nhãn ẩn (\"Trang đầu/trước/sau/cuối\") nằm\n * trong `<Button>` — mà `Button` không `relative`.\n * • Radix để `<input>`/`<select>` gốc ẩn bằng `position: absolute` (RadioGroup, Select).\n *\n * Đo trên trang `/cong-viec` trước khi vá: `documentElement.scrollHeight` = 1531 trong khi\n * `clientHeight` = 900 → 631px trắng. Trang `/bieu-mau`: 1792 / 900 → 892px. Trang ngắn hoặc\n * trang có các phần tử đó nằm TRÊN nếp gấp thì không lộ ra — đó là lý do lỗi trông như chỉ dính\n * vài trang.\n *\n * `relative` đưa containing block về đúng `main`, và `overflow-y-auto` cắt được như mong đợi.\n * ⚠ Đừng đổi sang `contain: paint`: nó cũng chữa được nhưng kéo theo containment về sơn/bố cục\n * mà không ai cần ở đây.\n */\n const main = (\n <main className={cn(\"relative flex-1 overflow-y-auto bg-background\", contentClassName)}>{children}</main>\n );\n\n return (\n <div\n style={appAccentStyle(currentApp)}\n className={cn(\n \"flex h-dvh w-full overflow-hidden bg-background\",\n headerFirst ? \"flex-col\" : \"flex-row\",\n className\n )}\n >\n {headerFirst ? (\n <>\n {header}\n <div className=\"flex min-h-0 flex-1\">\n {sidebar}\n {main}\n </div>\n </>\n ) : (\n <>\n {sidebar}\n <div className=\"flex min-w-0 flex-1 flex-col\">\n {header}\n {main}\n </div>\n </>\n )}\n\n <MobileMenu {...mobileMenuProps} />\n {commandPaletteSlot}\n </div>\n );\n}\n"],"mappings":";AA8EM,SA2GE,UA3GF,KA6GI,YA7GJ;AA3EN,SAAS,6BAA6B;AACtC,SAAS,UAAU;AACnB,SAAS,iBAAsC;AAC/C,SAAS,kBAAwC;AACjD,SAAS,eAAwC,gBAAgB;AACjE,SAAS,eAAkC;AAC3C,SAAS,sBAAsB;AAC/B,SAAS,yBAAyB;AAqD3B,SAAS,SAAS;AAAA,EACvB,SAAS;AAAA,EACT,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAkB;AAChB,QAAM,QACJ,oBAAC,iBAAe,GAAG,eACjB;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MAEC;AAAA;AAAA,EACH,GACF;AAIF,SAAO,WAAW,SAAY,QAAQ,oBAAC,yBAAsB,QAAiB,iBAAM;AACtF;AAkBA,SAAS,cAAc;AAAA,EACrB,SAAS;AAAA,EACT,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAuB;AACrB,QAAM,EAAE,WAAW,IAAI,SAAS;AAChC,QAAM,cAAc,WAAW;AAM/B,QAAM,SACJ;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACJ,mBAAmB,aAAa,qBAAqB;AAAA,MACrD,MAAM,aAAa,SAAS,cAAc,oBAAC,qBAAkB,WAAU,iBAAgB,IAAK;AAAA;AAAA,EAC9F;AAGF,QAAM,UAAU,cACd;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACJ,uBAAuB,cAAc,yBAAyB,CAAC;AAAA,MAC/D,oBAAoB,cAAc,sBAAsB,CAAC;AAAA;AAAA,EAC3D,IACE;AA0BJ,QAAM,OACJ,oBAAC,UAAK,WAAW,GAAG,iDAAiD,gBAAgB,GAAI,UAAS;AAGpG,SACE;AAAA,IAAC;AAAA;AAAA,MACC,OAAO,eAAe,UAAU;AAAA,MAChC,WAAW;AAAA,QACT;AAAA,QACA,cAAc,aAAa;AAAA,QAC3B;AAAA,MACF;AAAA,MAEC;AAAA,sBACC,iCACG;AAAA;AAAA,UACD,qBAAC,SAAI,WAAU,uBACZ;AAAA;AAAA,YACA;AAAA,aACH;AAAA,WACF,IAEA,iCACG;AAAA;AAAA,UACD,qBAAC,SAAI,WAAU,gCACZ;AAAA;AAAA,YACA;AAAA,aACH;AAAA,WACF;AAAA,QAGF,oBAAC,cAAY,GAAG,iBAAiB;AAAA,QAChC;AAAA;AAAA;AAAA,EACH;AAEJ;","names":[]}
|
|
@@ -17,13 +17,21 @@ declare const CONTRAST_MODES: readonly ContrastMode[];
|
|
|
17
17
|
declare const CONTRAST_ATTRIBUTE = "data-contrast";
|
|
18
18
|
/** Khoá localStorage mặc định — cùng lối đặt tên với `comitor-theme` của ThemeProvider. */
|
|
19
19
|
declare const CONTRAST_STORAGE_KEY = "comitor-contrast";
|
|
20
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* Nhãn của riêng ContrastToggle — không nằm trong `ShellLabels` vì nút này dùng được ngoài shell.
|
|
22
|
+
* Theo `ComitorLocaleProvider` (namespace `contrast`); override từng khoá qua prop `labels`.
|
|
23
|
+
*/
|
|
21
24
|
interface ContrastToggleLabels {
|
|
22
25
|
/** Nhãn chính, đứng cạnh nút gạt. */
|
|
23
26
|
label: string;
|
|
24
27
|
/** Dòng mô tả bên dưới nhãn. */
|
|
25
28
|
description: string;
|
|
26
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Nhãn TIẾNG ANH mặc định (từ `3.0.0`; trước đó là tiếng Việt). Bảng tiếng Việt:
|
|
32
|
+
* `comitorMessagesVi.contrast`. `ContrastToggle` không đọc thẳng hằng này — nó đọc bảng của
|
|
33
|
+
* `ComitorLocaleProvider`; hằng ở đây để app trải ra khi tự dựng nhãn.
|
|
34
|
+
*/
|
|
27
35
|
declare const DEFAULT_CONTRAST_TOGGLE_LABELS: ContrastToggleLabels;
|
|
28
36
|
declare const isContrastMode: (value: unknown) => value is ContrastMode;
|
|
29
37
|
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
+
import { contrastEn } from "../i18n/namespaces/contrast.js";
|
|
1
2
|
const CONTRAST_MODES = ["normal", "high"];
|
|
2
3
|
const CONTRAST_ATTRIBUTE = "data-contrast";
|
|
3
4
|
const CONTRAST_STORAGE_KEY = "comitor-contrast";
|
|
4
|
-
const DEFAULT_CONTRAST_TOGGLE_LABELS = {
|
|
5
|
-
label: "T\u01B0\u01A1ng ph\u1EA3n cao",
|
|
6
|
-
description: "\u0110\u1EADm m\xE0u vi\u1EC1n, c\xF4ng t\u1EAFc v\xE0 ch\u1EEF ph\u1EE5. H\u1EEFu \xEDch tr\xEAn m\xE0n h\xECnh \u0111\u1ED9 s\xE1ng th\u1EA5p ho\u1EB7c khi d\xF9ng ngo\xE0i tr\u1EDDi."
|
|
7
|
-
};
|
|
5
|
+
const DEFAULT_CONTRAST_TOGGLE_LABELS = { ...contrastEn };
|
|
8
6
|
const isContrastMode = (value) => typeof value === "string" && CONTRAST_MODES.includes(value);
|
|
9
7
|
export {
|
|
10
8
|
CONTRAST_ATTRIBUTE,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/shell/contrast-constants.ts"],"sourcesContent":["/**\n * Hằng và kiểu của trục TƯƠNG PHẢN — module THUẦN, cố ý KHÔNG có `\"use client\"`.\n *\n * Ranh giới RSC cắt theo FILE: mọi export của một module `\"use client\"` khi đi tới Server\n * Component đều thành client-reference proxy, nên `CONTRAST_STORAGE_KEY` sẽ ra `undefined` mà\n * KHÔNG throw và KHÔNG warning (xem CHANGELOG 0.2.2 §2). Root layout là Server Component và nó\n * cần đúng khoá này để render `<ContrastScript />`, nên hằng phải sống ở đây.\n *\n * ⚠ Barrel `shell/index.ts` phải xuất thẳng từ file này, KHÔNG vòng qua contrast-provider.tsx —\n * đi qua một file client là ranh giới biến hằng lại thành proxy (cổng `verify:directives` phép\n * kiểm 3 canh đúng chuyện này).\n */\n\n/** Hai bảng màu của hệ sinh thái. `normal` = comitor-ds (bản duyệt), `high` = bảng đã đo WCAG. */\nexport type ContrastMode = \"normal\" | \"high\";\n\nexport const CONTRAST_MODES: readonly ContrastMode[] = [\"normal\", \"high\"];\n\n/** Thuộc tính trên `<html>` mà styles.css nhìn vào: `[data-contrast=\"high\"]`. */\nexport const CONTRAST_ATTRIBUTE = \"data-contrast\";\n\n/** Khoá localStorage mặc định — cùng lối đặt tên với `comitor-theme` của ThemeProvider. */\nexport const CONTRAST_STORAGE_KEY = \"comitor-contrast\";\n\n
|
|
1
|
+
{"version":3,"sources":["../../src/shell/contrast-constants.ts"],"sourcesContent":["/**\n * Hằng và kiểu của trục TƯƠNG PHẢN — module THUẦN, cố ý KHÔNG có `\"use client\"`.\n *\n * Ranh giới RSC cắt theo FILE: mọi export của một module `\"use client\"` khi đi tới Server\n * Component đều thành client-reference proxy, nên `CONTRAST_STORAGE_KEY` sẽ ra `undefined` mà\n * KHÔNG throw và KHÔNG warning (xem CHANGELOG 0.2.2 §2). Root layout là Server Component và nó\n * cần đúng khoá này để render `<ContrastScript />`, nên hằng phải sống ở đây.\n *\n * ⚠ Barrel `shell/index.ts` phải xuất thẳng từ file này, KHÔNG vòng qua contrast-provider.tsx —\n * đi qua một file client là ranh giới biến hằng lại thành proxy (cổng `verify:directives` phép\n * kiểm 3 canh đúng chuyện này).\n */\n\nimport { contrastEn } from \"../i18n/namespaces/contrast.js\";\n\n/** Hai bảng màu của hệ sinh thái. `normal` = comitor-ds (bản duyệt), `high` = bảng đã đo WCAG. */\nexport type ContrastMode = \"normal\" | \"high\";\n\nexport const CONTRAST_MODES: readonly ContrastMode[] = [\"normal\", \"high\"];\n\n/** Thuộc tính trên `<html>` mà styles.css nhìn vào: `[data-contrast=\"high\"]`. */\nexport const CONTRAST_ATTRIBUTE = \"data-contrast\";\n\n/** Khoá localStorage mặc định — cùng lối đặt tên với `comitor-theme` của ThemeProvider. */\nexport const CONTRAST_STORAGE_KEY = \"comitor-contrast\";\n\n/**\n * Nhãn của riêng ContrastToggle — không nằm trong `ShellLabels` vì nút này dùng được ngoài shell.\n * Theo `ComitorLocaleProvider` (namespace `contrast`); override từng khoá qua prop `labels`.\n */\nexport interface ContrastToggleLabels {\n /** Nhãn chính, đứng cạnh nút gạt. */\n label: string;\n /** Dòng mô tả bên dưới nhãn. */\n description: string;\n}\n\n/**\n * Nhãn TIẾNG ANH mặc định (từ `3.0.0`; trước đó là tiếng Việt). Bảng tiếng Việt:\n * `comitorMessagesVi.contrast`. `ContrastToggle` không đọc thẳng hằng này — nó đọc bảng của\n * `ComitorLocaleProvider`; hằng ở đây để app trải ra khi tự dựng nhãn.\n */\nexport const DEFAULT_CONTRAST_TOGGLE_LABELS: ContrastToggleLabels = { ...contrastEn };\n\nexport const isContrastMode = (value: unknown): value is ContrastMode =>\n typeof value === \"string\" && (CONTRAST_MODES as readonly string[]).includes(value);\n"],"mappings":"AAaA,SAAS,kBAAkB;AAKpB,MAAM,iBAA0C,CAAC,UAAU,MAAM;AAGjE,MAAM,qBAAqB;AAG3B,MAAM,uBAAuB;AAkB7B,MAAM,iCAAuD,EAAE,GAAG,WAAW;AAE7E,MAAM,iBAAiB,CAAC,UAC7B,OAAO,UAAU,YAAa,eAAqC,SAAS,KAAK;","names":[]}
|
|
@@ -3,7 +3,10 @@ import { ContrastToggleLabels, ContrastMode } from './contrast-constants.js';
|
|
|
3
3
|
|
|
4
4
|
interface ContrastToggleProps {
|
|
5
5
|
className?: string;
|
|
6
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* Override chuỗi hiển thị. Không truyền thì theo namespace `contrast` của
|
|
8
|
+
* `ComitorLocaleProvider` (không có provider → tiếng Anh).
|
|
9
|
+
*/
|
|
7
10
|
labels?: Partial<ContrastToggleLabels>;
|
|
8
11
|
/** Gọi sau khi người dùng đổi — để app hiện toast hoặc ghi log. */
|
|
9
12
|
onContrastChange?: (mode: ContrastMode) => void;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
3
|
import { Switch } from "../components/ui/switch.js";
|
|
4
|
+
import { useComitorLabels } from "../i18n/locale-provider.js";
|
|
4
5
|
import { cn } from "../lib/cn.js";
|
|
5
|
-
import { DEFAULT_CONTRAST_TOGGLE_LABELS } from "./contrast-constants.js";
|
|
6
6
|
import { useContrast } from "./contrast-provider.js";
|
|
7
7
|
function ContrastToggle({ className, labels: labelsProp, onContrastChange }) {
|
|
8
8
|
const { contrast, setContrast, mounted } = useContrast();
|
|
9
|
-
const labels =
|
|
9
|
+
const labels = useComitorLabels("contrast", labelsProp);
|
|
10
10
|
return /* @__PURE__ */ jsx(
|
|
11
11
|
Switch,
|
|
12
12
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/shell/contrast-toggle.tsx"],"sourcesContent":["\"use client\";\n\nimport { Switch } from \"../components/ui/switch.js\";\nimport { cn } from \"../lib/cn.js\";\nimport {
|
|
1
|
+
{"version":3,"sources":["../../src/shell/contrast-toggle.tsx"],"sourcesContent":["\"use client\";\n\nimport { Switch } from \"../components/ui/switch.js\";\nimport { useComitorLabels } from \"../i18n/locale-provider.js\";\nimport { cn } from \"../lib/cn.js\";\nimport type { ContrastMode, ContrastToggleLabels } from \"./contrast-constants.js\";\nimport { useContrast } from \"./contrast-provider.js\";\n\nexport interface ContrastToggleProps {\n className?: string;\n /**\n * Override chuỗi hiển thị. Không truyền thì theo namespace `contrast` của\n * `ComitorLocaleProvider` (không có provider → tiếng Anh).\n */\n labels?: Partial<ContrastToggleLabels>;\n /** Gọi sau khi người dùng đổi — để app hiện toast hoặc ghi log. */\n onContrastChange?: (mode: ContrastMode) => void;\n}\n\n/**\n * Nút gạt chọn BẢNG MÀU (bản duyệt comitor-ds ⇄ bản tương phản cao). Trục này độc lập với\n * sáng/tối: hai bảng màu đều có bản light và bản dark riêng.\n *\n * Đặt ở trang cài đặt của app, cạnh `<ThemeToggle>`. Phải nằm trong `<ContrastProvider>`.\n *\n * Chống lệch hydration bằng cờ `mounted` của provider — cùng lối với `ThemeToggle`: lựa chọn thật\n * chỉ đọc được ở client (localStorage), render thẳng trạng thái nút ngay lần đầu sẽ lệch với HTML\n * do server sinh ra. Trước khi mount thì nút hiện ở trạng thái mặc định và bị khoá, để layout\n * không nhảy và người dùng không bấm trúng một trạng thái sắp bị thay.\n */\nexport function ContrastToggle({ className, labels: labelsProp, onContrastChange }: ContrastToggleProps) {\n const { contrast, setContrast, mounted } = useContrast();\n const labels: ContrastToggleLabels = useComitorLabels(\"contrast\", labelsProp);\n\n return (\n <Switch\n className={cn(className)}\n checked={contrast === \"high\"}\n disabled={!mounted}\n label={labels.label}\n description={labels.description}\n onCheckedChange={(checked) => {\n const next: ContrastMode = checked ? \"high\" : \"normal\";\n setContrast(next);\n onContrastChange?.(next);\n }}\n />\n );\n}\n"],"mappings":";AAmCI;AAjCJ,SAAS,cAAc;AACvB,SAAS,wBAAwB;AACjC,SAAS,UAAU;AAEnB,SAAS,mBAAmB;AAwBrB,SAAS,eAAe,EAAE,WAAW,QAAQ,YAAY,iBAAiB,GAAwB;AACvG,QAAM,EAAE,UAAU,aAAa,QAAQ,IAAI,YAAY;AACvD,QAAM,SAA+B,iBAAiB,YAAY,UAAU;AAE5E,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,GAAG,SAAS;AAAA,MACvB,SAAS,aAAa;AAAA,MACtB,UAAU,CAAC;AAAA,MACX,OAAO,OAAO;AAAA,MACd,aAAa,OAAO;AAAA,MACpB,iBAAiB,CAAC,YAAY;AAC5B,cAAM,OAAqB,UAAU,SAAS;AAC9C,oBAAY,IAAI;AAChB,2BAAmB,IAAI;AAAA,MACzB;AAAA;AAAA,EACF;AAEJ;","names":[]}
|
|
@@ -23,7 +23,8 @@ declare function isDensityMode(value: unknown): value is DensityMode;
|
|
|
23
23
|
*
|
|
24
24
|
* Giá trị là `sm`/`md`/`lg` chứ không phải `small`/`medium`/`large`: chúng đi thẳng vào
|
|
25
25
|
* `[data-font-size="…"]` trong CSS và vào localStorage, tức là chuỗi MÁY — theo luật tên trường,
|
|
26
|
-
* không theo luật nhãn. Nhãn
|
|
26
|
+
* không theo luật nhãn. Nhãn hiển thị của từng mức nằm ở namespace `displayAxis` (theo
|
|
27
|
+
* `ComitorLocaleProvider`) và prop `optionLabels` của `FontSizeControl`.
|
|
27
28
|
*/
|
|
28
29
|
declare const FONT_SIZE_MODES: readonly ["sm", "md", "lg"];
|
|
29
30
|
type FontSizeMode = (typeof FONT_SIZE_MODES)[number];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/shell/display-axis-constants.ts"],"sourcesContent":["/**\n * Hằng của HAI trục hiển thị thêm: MẬT ĐỘ và CỠ CHỮ.\n *\n * ⚠ File này KHÔNG có `\"use client\"`, và đó là điều kiện bắt buộc chứ không phải tuỳ chọn.\n * Root layout là Server Component nhưng vẫn cần đọc tên thuộc tính / khoá lưu (vd để render sẵn\n * giá trị lên `<html>`). Mọi export của một module `\"use client\"` khi đi tới Server Component sẽ\n * biến thành client-reference proxy và đọc ra `undefined` — KHÔNG ném lỗi, không cảnh báo. Đúng\n * cái bẫy mà `contrast-constants.ts` được tách ra để tránh (CHANGELOG 0.2.2 §2), và cổng\n * `verify:directives` canh đúng chuyện này.\n *\n * Vì vậy: hằng ở đây, provider ở file khác, và `shell/index.ts` re-export THẲNG từ file này\n * chứ không đi vòng qua provider.\n */\n\n/* ────────────────────────────────────────────────────────────────────────────\n Trục 3 — MẬT ĐỘ (bố cục gọn)\n ──────────────────────────────────────────────────────────────────────────── */\n\n/** `comfortable` là mặc định và KHÔNG ghi thuộc tính ra DOM — xem ghi chú ở cuối file. */\nexport const DENSITY_MODES = [\"comfortable\", \"compact\"] as const;\nexport type DensityMode = (typeof DENSITY_MODES)[number];\n\nexport const DENSITY_ATTRIBUTE = \"data-density\";\nexport const DENSITY_STORAGE_KEY = \"comitor-density\";\nexport const DEFAULT_DENSITY: DensityMode = \"comfortable\";\n\nexport function isDensityMode(value: unknown): value is DensityMode {\n return typeof value === \"string\" && (DENSITY_MODES as readonly string[]).includes(value);\n}\n\n/* ────────────────────────────────────────────────────────────────────────────\n Trục 4 — CỠ CHỮ\n ──────────────────────────────────────────────────────────────────────────── */\n\n/**\n * `md` là mặc định và KHÔNG ghi thuộc tính ra DOM.\n *\n * Giá trị là `sm`/`md`/`lg` chứ không phải `small`/`medium`/`large`: chúng đi thẳng vào\n * `[data-font-size=\"…\"]` trong CSS và vào localStorage, tức là chuỗi MÁY — theo luật tên trường,\n * không theo luật nhãn. Nhãn
|
|
1
|
+
{"version":3,"sources":["../../src/shell/display-axis-constants.ts"],"sourcesContent":["/**\n * Hằng của HAI trục hiển thị thêm: MẬT ĐỘ và CỠ CHỮ.\n *\n * ⚠ File này KHÔNG có `\"use client\"`, và đó là điều kiện bắt buộc chứ không phải tuỳ chọn.\n * Root layout là Server Component nhưng vẫn cần đọc tên thuộc tính / khoá lưu (vd để render sẵn\n * giá trị lên `<html>`). Mọi export của một module `\"use client\"` khi đi tới Server Component sẽ\n * biến thành client-reference proxy và đọc ra `undefined` — KHÔNG ném lỗi, không cảnh báo. Đúng\n * cái bẫy mà `contrast-constants.ts` được tách ra để tránh (CHANGELOG 0.2.2 §2), và cổng\n * `verify:directives` canh đúng chuyện này.\n *\n * Vì vậy: hằng ở đây, provider ở file khác, và `shell/index.ts` re-export THẲNG từ file này\n * chứ không đi vòng qua provider.\n */\n\n/* ────────────────────────────────────────────────────────────────────────────\n Trục 3 — MẬT ĐỘ (bố cục gọn)\n ──────────────────────────────────────────────────────────────────────────── */\n\n/** `comfortable` là mặc định và KHÔNG ghi thuộc tính ra DOM — xem ghi chú ở cuối file. */\nexport const DENSITY_MODES = [\"comfortable\", \"compact\"] as const;\nexport type DensityMode = (typeof DENSITY_MODES)[number];\n\nexport const DENSITY_ATTRIBUTE = \"data-density\";\nexport const DENSITY_STORAGE_KEY = \"comitor-density\";\nexport const DEFAULT_DENSITY: DensityMode = \"comfortable\";\n\nexport function isDensityMode(value: unknown): value is DensityMode {\n return typeof value === \"string\" && (DENSITY_MODES as readonly string[]).includes(value);\n}\n\n/* ────────────────────────────────────────────────────────────────────────────\n Trục 4 — CỠ CHỮ\n ──────────────────────────────────────────────────────────────────────────── */\n\n/**\n * `md` là mặc định và KHÔNG ghi thuộc tính ra DOM.\n *\n * Giá trị là `sm`/`md`/`lg` chứ không phải `small`/`medium`/`large`: chúng đi thẳng vào\n * `[data-font-size=\"…\"]` trong CSS và vào localStorage, tức là chuỗi MÁY — theo luật tên trường,\n * không theo luật nhãn. Nhãn hiển thị của từng mức nằm ở namespace `displayAxis` (theo\n * `ComitorLocaleProvider`) và prop `optionLabels` của `FontSizeControl`.\n */\nexport const FONT_SIZE_MODES = [\"sm\", \"md\", \"lg\"] as const;\nexport type FontSizeMode = (typeof FONT_SIZE_MODES)[number];\n\nexport const FONT_SIZE_ATTRIBUTE = \"data-font-size\";\nexport const FONT_SIZE_STORAGE_KEY = \"comitor-font-size\";\nexport const DEFAULT_FONT_SIZE: FontSizeMode = \"md\";\n\nexport function isFontSizeMode(value: unknown): value is FontSizeMode {\n return typeof value === \"string\" && (FONT_SIZE_MODES as readonly string[]).includes(value);\n}\n\n/**\n * ─ VÌ SAO GIÁ TRỊ MẶC ĐỊNH KHÔNG GHI THUỘC TÍNH ─\n *\n * Cả hai trục đều GỠ thuộc tính khi ở mức mặc định thay vì ghi `data-density=\"comfortable\"`.\n * Cùng lối với `ContrastProvider`, và ở trục cỡ chữ thì nó còn là chuyện TRỢ NĂNG:\n *\n * Mức `md` không được phép là `font-size: 100%` viết ra DOM, càng không được là `16px`. Người\n * dùng có thể đã tăng cỡ chữ mặc định của trình duyệt; khai một cỡ tuyệt đối ở `<html>` là XOÁ\n * lựa chọn đó — tức tự tay vi phạm WCAG 1.4.4 ngay bên trong một tính năng trợ năng. Không ghi gì\n * ở mức mặc định thì cỡ gốc của trình duyệt được giữ nguyên, còn `sm`/`lg` là PHẦN TRĂM của chính\n * cỡ đó (87,5% / 112,5%) nên vẫn tôn trọng nó.\n *\n * ⚠ Không cổng nào bắt được lỗi này: `check-contrast.mjs` chỉ có ngưỡng 4,5 và 3, không có logic\n * cỡ chữ nào cả.\n */\n"],"mappings":"AAmBO,MAAM,gBAAgB,CAAC,eAAe,SAAS;AAG/C,MAAM,oBAAoB;AAC1B,MAAM,sBAAsB;AAC5B,MAAM,kBAA+B;AAErC,SAAS,cAAc,OAAsC;AAClE,SAAO,OAAO,UAAU,YAAa,cAAoC,SAAS,KAAK;AACzF;AAcO,MAAM,kBAAkB,CAAC,MAAM,MAAM,IAAI;AAGzC,MAAM,sBAAsB;AAC5B,MAAM,wBAAwB;AAC9B,MAAM,oBAAkC;AAExC,SAAS,eAAe,OAAuC;AACpE,SAAO,OAAO,UAAU,YAAa,gBAAsC,SAAS,KAAK;AAC3F;","names":[]}
|
|
@@ -13,9 +13,15 @@ import { DensityMode, FontSizeMode } from './display-axis-constants.js';
|
|
|
13
13
|
*
|
|
14
14
|
* (DOM thật thì không nháy: script chống nháy của provider đã đặt thuộc tính lên `<html>` trước
|
|
15
15
|
* lần sơn đầu tiên. Chỉ CONTROL là phải chờ.)
|
|
16
|
+
*
|
|
17
|
+
* ─ CHỮ ─
|
|
18
|
+
* Mọi chuỗi mặc định theo namespace `displayAxis` của `ComitorLocaleProvider` (không có provider
|
|
19
|
+
* → tiếng Anh). Prop `label`/`description`/`optionLabels` truyền vào vẫn thắng.
|
|
16
20
|
*/
|
|
17
21
|
interface DensityToggleProps {
|
|
22
|
+
/** Nhãn nút gạt (khoá `densityLabel` của namespace `displayAxis`). */
|
|
18
23
|
label?: string;
|
|
24
|
+
/** Dòng mô tả dưới nhãn (khoá `densityDescription`). */
|
|
19
25
|
description?: React.ReactNode;
|
|
20
26
|
className?: string;
|
|
21
27
|
/**
|
|
@@ -40,11 +46,14 @@ interface DensityToggleProps {
|
|
|
40
46
|
* thanh "Có thay đổi chưa lưu" sáng lên cho một thay đổi đã có hiệu lực, và nút "Hoàn tác" trong
|
|
41
47
|
* toast sẽ lùi bản nháp mà KHÔNG lùi được trục — giao diện tự mâu thuẫn với chính nó.
|
|
42
48
|
*/
|
|
43
|
-
declare function DensityToggle({ label, description, className, onDensityChange }: DensityToggleProps): React.JSX.Element;
|
|
49
|
+
declare function DensityToggle({ label: labelProp, description: descriptionProp, className, onDensityChange }: DensityToggleProps): React.JSX.Element;
|
|
44
50
|
interface FontSizeControlProps {
|
|
45
|
-
/** Nhãn của cả nhóm, đọc lên cho trình đọc màn hình. */
|
|
51
|
+
/** Nhãn của cả nhóm, đọc lên cho trình đọc màn hình (khoá `fontSizeLabel` của `displayAxis`). */
|
|
46
52
|
label?: string;
|
|
47
|
-
/**
|
|
53
|
+
/**
|
|
54
|
+
* Nhãn từng mức — THỨ TỰ theo `FONT_SIZE_MODES` (`sm`, `md`, `lg`). Không truyền thì lấy ba khoá
|
|
55
|
+
* `fontSizeSm`/`fontSizeMd`/`fontSizeLg` của namespace `displayAxis`.
|
|
56
|
+
*/
|
|
48
57
|
optionLabels?: Record<FontSizeMode, string>;
|
|
49
58
|
className?: string;
|
|
50
59
|
/** Gọi SAU khi người dùng đổi — xem giải thích ở `DensityToggleProps.onDensityChange`. */
|
|
@@ -58,9 +67,9 @@ interface FontSizeControlProps {
|
|
|
58
67
|
* phím tới được bằng Tab) thay vì roving focus của Radix ToggleGroup — nhóm chỉ có ba mục nên
|
|
59
68
|
* roving focus không đáng cái giá thêm một phụ thuộc.
|
|
60
69
|
*
|
|
61
|
-
* Cỡ chữ của CHÍNH các nút này cố định `text-sm`: nếu chúng phóng theo lựa chọn thì bấm
|
|
62
|
-
* làm cả hàng nút nhảy chỗ ngay dưới ngón tay.
|
|
70
|
+
* Cỡ chữ của CHÍNH các nút này cố định `text-sm`: nếu chúng phóng theo lựa chọn thì bấm mức `lg`
|
|
71
|
+
* sẽ làm cả hàng nút nhảy chỗ ngay dưới ngón tay.
|
|
63
72
|
*/
|
|
64
|
-
declare function FontSizeControl({ label, optionLabels, className, onFontSizeChange }: FontSizeControlProps): React.JSX.Element;
|
|
73
|
+
declare function FontSizeControl({ label: labelProp, optionLabels: optionLabelsProp, className, onFontSizeChange }: FontSizeControlProps): React.JSX.Element;
|
|
65
74
|
|
|
66
75
|
export { DensityToggle, type DensityToggleProps, FontSizeControl, type FontSizeControlProps };
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
3
|
import { Switch } from "../components/ui/switch.js";
|
|
4
|
+
import { useComitorMessages } from "../i18n/locale-provider.js";
|
|
4
5
|
import { cn } from "../lib/cn.js";
|
|
5
6
|
import { FONT_SIZE_MODES } from "./display-axis-constants.js";
|
|
6
7
|
import { useDensity, useFontSize } from "./display-axis-provider.js";
|
|
7
8
|
function DensityToggle({
|
|
8
|
-
label
|
|
9
|
-
description
|
|
9
|
+
label: labelProp,
|
|
10
|
+
description: descriptionProp,
|
|
10
11
|
className,
|
|
11
12
|
onDensityChange
|
|
12
13
|
}) {
|
|
14
|
+
const t = useComitorMessages("displayAxis");
|
|
15
|
+
const label = labelProp ?? t.densityLabel;
|
|
16
|
+
const description = descriptionProp ?? t.densityDescription;
|
|
13
17
|
const { density, setDensity, mounted } = useDensity();
|
|
14
18
|
return /* @__PURE__ */ jsx(
|
|
15
19
|
Switch,
|
|
@@ -27,17 +31,15 @@ function DensityToggle({
|
|
|
27
31
|
}
|
|
28
32
|
);
|
|
29
33
|
}
|
|
30
|
-
const DEFAULT_FONT_SIZE_LABELS = {
|
|
31
|
-
sm: "Nh\u1ECF",
|
|
32
|
-
md: "V\u1EEBa",
|
|
33
|
-
lg: "L\u1EDBn"
|
|
34
|
-
};
|
|
35
34
|
function FontSizeControl({
|
|
36
|
-
label
|
|
37
|
-
optionLabels
|
|
35
|
+
label: labelProp,
|
|
36
|
+
optionLabels: optionLabelsProp,
|
|
38
37
|
className,
|
|
39
38
|
onFontSizeChange
|
|
40
39
|
}) {
|
|
40
|
+
const t = useComitorMessages("displayAxis");
|
|
41
|
+
const label = labelProp ?? t.fontSizeLabel;
|
|
42
|
+
const optionLabels = optionLabelsProp ?? { sm: t.fontSizeSm, md: t.fontSizeMd, lg: t.fontSizeLg };
|
|
41
43
|
const { fontSize, setFontSize, mounted } = useFontSize();
|
|
42
44
|
return /* @__PURE__ */ jsx(
|
|
43
45
|
"div",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/shell/display-axis-controls.tsx"],"sourcesContent":["\"use client\";\n\nimport type * as React from \"react\";\nimport { Switch } from \"../components/ui/switch.js\";\nimport { cn } from \"../lib/cn.js\";\nimport { type DensityMode, FONT_SIZE_MODES, type FontSizeMode } from \"./display-axis-constants.js\";\nimport { useDensity, useFontSize } from \"./display-axis-provider.js\";\n\n/**\n * Hai control của trục MẬT ĐỘ và trục CỠ CHỮ.\n *\n * ─ VÌ SAO CẢ HAI ĐỀU CÓ `disabled={!mounted}` ─\n * Giá trị thật nằm ở localStorage, mà server không đọc được. Render lần đầu ở client PHẢI khớp\n * HTML server sinh ra, nếu không React báo lệch hydration — nên provider giữ giá trị mặc định cho\n * tới khi effect đầu chạy. Trong khoảnh khắc đó control hiện mặc định chứ không hiện lựa chọn\n * thật, và nếu người dùng kịp bấm thì họ ghi đè lên chính lựa chọn của mình. Khoá cho tới khi\n * `mounted` là cách `ContrastToggle` đã dùng.\n *\n * (DOM thật thì không nháy: script chống nháy của provider đã đặt thuộc tính lên `<html>` trước\n * lần sơn đầu tiên. Chỉ CONTROL là phải chờ.)\n */\n\nexport interface DensityToggleProps {\n label?: string;\n description?: React.ReactNode;\n className?: string;\n /**\n * Gọi SAU khi người dùng đổi — cùng khuôn với `onContrastChange` của `ContrastToggle`.\n *\n * ⚠ Đây là callback THÔNG BÁO, không phải `onChange` của một component controlled. Giá trị CÓ\n * HIỆU LỰC vẫn nằm ở `localStorage` của gói và vẫn do provider quản; prop này chỉ cho app biết\n * \"vừa có thay đổi\" để nó ghi một MẶC ĐỊNH theo tài khoản ở nơi khác (Comitor.Account), hiện một\n * toast, hoặc ghi log.\n *\n * Vì sao KHÔNG làm controlled: trục hiển thị phải có hiệu lực trước lượt sơn đầu tiên, và điều đó\n * chỉ làm được bằng script chống nháy đọc `localStorage` — một giá trị đi từ server xuống prop\n * luôn tới SAU lượt sơn ấy. Xem `display-axis-provider.tsx`.\n */\n onDensityChange?: (mode: DensityMode) => void;\n}\n\n/**\n * Nút gạt \"bố cục gọn\".\n *\n * ⚠ Đây KHÔNG phải một trường của biểu mẫu cài đặt. Nó có hiệu lực NGAY và tự lưu, giống\n * `ThemeToggle`/`ContrastToggle` — đừng đưa nó vào luồng nháp/\"Lưu thay đổi\". Trộn hai kiểu thì\n * thanh \"Có thay đổi chưa lưu\" sáng lên cho một thay đổi đã có hiệu lực, và nút \"Hoàn tác\" trong\n * toast sẽ lùi bản nháp mà KHÔNG lùi được trục — giao diện tự mâu thuẫn với chính nó.\n */\nexport function DensityToggle({\n label
|
|
1
|
+
{"version":3,"sources":["../../src/shell/display-axis-controls.tsx"],"sourcesContent":["\"use client\";\n\nimport type * as React from \"react\";\nimport { Switch } from \"../components/ui/switch.js\";\nimport { useComitorMessages } from \"../i18n/locale-provider.js\";\nimport { cn } from \"../lib/cn.js\";\nimport { type DensityMode, FONT_SIZE_MODES, type FontSizeMode } from \"./display-axis-constants.js\";\nimport { useDensity, useFontSize } from \"./display-axis-provider.js\";\n\n/**\n * Hai control của trục MẬT ĐỘ và trục CỠ CHỮ.\n *\n * ─ VÌ SAO CẢ HAI ĐỀU CÓ `disabled={!mounted}` ─\n * Giá trị thật nằm ở localStorage, mà server không đọc được. Render lần đầu ở client PHẢI khớp\n * HTML server sinh ra, nếu không React báo lệch hydration — nên provider giữ giá trị mặc định cho\n * tới khi effect đầu chạy. Trong khoảnh khắc đó control hiện mặc định chứ không hiện lựa chọn\n * thật, và nếu người dùng kịp bấm thì họ ghi đè lên chính lựa chọn của mình. Khoá cho tới khi\n * `mounted` là cách `ContrastToggle` đã dùng.\n *\n * (DOM thật thì không nháy: script chống nháy của provider đã đặt thuộc tính lên `<html>` trước\n * lần sơn đầu tiên. Chỉ CONTROL là phải chờ.)\n *\n * ─ CHỮ ─\n * Mọi chuỗi mặc định theo namespace `displayAxis` của `ComitorLocaleProvider` (không có provider\n * → tiếng Anh). Prop `label`/`description`/`optionLabels` truyền vào vẫn thắng.\n */\n\nexport interface DensityToggleProps {\n /** Nhãn nút gạt (khoá `densityLabel` của namespace `displayAxis`). */\n label?: string;\n /** Dòng mô tả dưới nhãn (khoá `densityDescription`). */\n description?: React.ReactNode;\n className?: string;\n /**\n * Gọi SAU khi người dùng đổi — cùng khuôn với `onContrastChange` của `ContrastToggle`.\n *\n * ⚠ Đây là callback THÔNG BÁO, không phải `onChange` của một component controlled. Giá trị CÓ\n * HIỆU LỰC vẫn nằm ở `localStorage` của gói và vẫn do provider quản; prop này chỉ cho app biết\n * \"vừa có thay đổi\" để nó ghi một MẶC ĐỊNH theo tài khoản ở nơi khác (Comitor.Account), hiện một\n * toast, hoặc ghi log.\n *\n * Vì sao KHÔNG làm controlled: trục hiển thị phải có hiệu lực trước lượt sơn đầu tiên, và điều đó\n * chỉ làm được bằng script chống nháy đọc `localStorage` — một giá trị đi từ server xuống prop\n * luôn tới SAU lượt sơn ấy. Xem `display-axis-provider.tsx`.\n */\n onDensityChange?: (mode: DensityMode) => void;\n}\n\n/**\n * Nút gạt \"bố cục gọn\".\n *\n * ⚠ Đây KHÔNG phải một trường của biểu mẫu cài đặt. Nó có hiệu lực NGAY và tự lưu, giống\n * `ThemeToggle`/`ContrastToggle` — đừng đưa nó vào luồng nháp/\"Lưu thay đổi\". Trộn hai kiểu thì\n * thanh \"Có thay đổi chưa lưu\" sáng lên cho một thay đổi đã có hiệu lực, và nút \"Hoàn tác\" trong\n * toast sẽ lùi bản nháp mà KHÔNG lùi được trục — giao diện tự mâu thuẫn với chính nó.\n */\nexport function DensityToggle({\n label: labelProp,\n description: descriptionProp,\n className,\n onDensityChange\n}: DensityToggleProps) {\n const t = useComitorMessages(\"displayAxis\");\n const label = labelProp ?? t.densityLabel;\n const description = descriptionProp ?? t.densityDescription;\n const { density, setDensity, mounted } = useDensity();\n\n return (\n <Switch\n className={className}\n checked={density === \"compact\"}\n onCheckedChange={(checked) => {\n const next: DensityMode = checked ? \"compact\" : \"comfortable\";\n setDensity(next);\n onDensityChange?.(next);\n }}\n disabled={!mounted}\n label={label}\n description={description}\n />\n );\n}\n\nexport interface FontSizeControlProps {\n /** Nhãn của cả nhóm, đọc lên cho trình đọc màn hình (khoá `fontSizeLabel` của `displayAxis`). */\n label?: string;\n /**\n * Nhãn từng mức — THỨ TỰ theo `FONT_SIZE_MODES` (`sm`, `md`, `lg`). Không truyền thì lấy ba khoá\n * `fontSizeSm`/`fontSizeMd`/`fontSizeLg` của namespace `displayAxis`.\n */\n optionLabels?: Record<FontSizeMode, string>;\n className?: string;\n /** Gọi SAU khi người dùng đổi — xem giải thích ở `DensityToggleProps.onDensityChange`. */\n onFontSizeChange?: (mode: FontSizeMode) => void;\n}\n\n/**\n * Chọn cỡ chữ — ba mức, một hàng nút.\n *\n * `role=\"radiogroup\"` + `aria-checked` chứ không phải ba nút thường: đây là chọn MỘT trong ba, và\n * trình đọc màn hình cần nghe \"2 trên 3\" chứ không nghe ba nút rời rạc. Dùng `<button>` thật (bàn\n * phím tới được bằng Tab) thay vì roving focus của Radix ToggleGroup — nhóm chỉ có ba mục nên\n * roving focus không đáng cái giá thêm một phụ thuộc.\n *\n * Cỡ chữ của CHÍNH các nút này cố định `text-sm`: nếu chúng phóng theo lựa chọn thì bấm mức `lg`\n * sẽ làm cả hàng nút nhảy chỗ ngay dưới ngón tay.\n */\nexport function FontSizeControl({\n label: labelProp,\n optionLabels: optionLabelsProp,\n className,\n onFontSizeChange\n}: FontSizeControlProps) {\n const t = useComitorMessages(\"displayAxis\");\n const label = labelProp ?? t.fontSizeLabel;\n const optionLabels = optionLabelsProp ?? { sm: t.fontSizeSm, md: t.fontSizeMd, lg: t.fontSizeLg };\n const { fontSize, setFontSize, mounted } = useFontSize();\n\n return (\n <div\n role=\"radiogroup\"\n aria-label={label}\n className={cn(\"inline-flex items-center gap-1 rounded-md border border-input p-1 text-sm\", className)}\n >\n {FONT_SIZE_MODES.map((mode) => {\n const selected = fontSize === mode;\n return (\n <button\n key={mode}\n type=\"button\"\n role=\"radio\"\n aria-checked={selected}\n disabled={!mounted}\n onClick={() => {\n setFontSize(mode);\n onFontSizeChange?.(mode);\n }}\n className={cn(\n \"rounded-sm px-3 py-1 text-sm transition-colors duration-150 ease-out\",\n \"focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-foreground\",\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n selected\n ? \"bg-control-on text-control-check font-medium\"\n : \"text-muted-foreground hover:bg-accent hover:text-foreground\"\n )}\n >\n {optionLabels[mode]}\n </button>\n );\n })}\n </div>\n );\n}\n"],"mappings":";AAoEI;AAjEJ,SAAS,cAAc;AACvB,SAAS,0BAA0B;AACnC,SAAS,UAAU;AACnB,SAA2B,uBAA0C;AACrE,SAAS,YAAY,mBAAmB;AAiDjC,SAAS,cAAc;AAAA,EAC5B,OAAO;AAAA,EACP,aAAa;AAAA,EACb;AAAA,EACA;AACF,GAAuB;AACrB,QAAM,IAAI,mBAAmB,aAAa;AAC1C,QAAM,QAAQ,aAAa,EAAE;AAC7B,QAAM,cAAc,mBAAmB,EAAE;AACzC,QAAM,EAAE,SAAS,YAAY,QAAQ,IAAI,WAAW;AAEpD,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,SAAS,YAAY;AAAA,MACrB,iBAAiB,CAAC,YAAY;AAC5B,cAAM,OAAoB,UAAU,YAAY;AAChD,mBAAW,IAAI;AACf,0BAAkB,IAAI;AAAA,MACxB;AAAA,MACA,UAAU,CAAC;AAAA,MACX;AAAA,MACA;AAAA;AAAA,EACF;AAEJ;AA0BO,SAAS,gBAAgB;AAAA,EAC9B,OAAO;AAAA,EACP,cAAc;AAAA,EACd;AAAA,EACA;AACF,GAAyB;AACvB,QAAM,IAAI,mBAAmB,aAAa;AAC1C,QAAM,QAAQ,aAAa,EAAE;AAC7B,QAAM,eAAe,oBAAoB,EAAE,IAAI,EAAE,YAAY,IAAI,EAAE,YAAY,IAAI,EAAE,WAAW;AAChG,QAAM,EAAE,UAAU,aAAa,QAAQ,IAAI,YAAY;AAEvD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,cAAY;AAAA,MACZ,WAAW,GAAG,6EAA6E,SAAS;AAAA,MAEnG,0BAAgB,IAAI,CAAC,SAAS;AAC7B,cAAM,WAAW,aAAa;AAC9B,eACE;AAAA,UAAC;AAAA;AAAA,YAEC,MAAK;AAAA,YACL,MAAK;AAAA,YACL,gBAAc;AAAA,YACd,UAAU,CAAC;AAAA,YACX,SAAS,MAAM;AACb,0BAAY,IAAI;AAChB,iCAAmB,IAAI;AAAA,YACzB;AAAA,YACA,WAAW;AAAA,cACT;AAAA,cACA;AAAA,cACA;AAAA,cACA,WACI,iDACA;AAAA,YACN;AAAA,YAEC,uBAAa,IAAI;AAAA;AAAA,UAlBb;AAAA,QAmBP;AAAA,MAEJ,CAAC;AAAA;AAAA,EACH;AAEJ;","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/shell/mobile-menu.tsx"],"sourcesContent":["\"use client\";\n\nimport type { ReactNode } from \"react\";\nimport { Separator } from \"../components/ui/separator.js\";\nimport { Sheet, SheetContent, SheetHeader, SheetTitle } from \"../components/ui/sheet.js\";\nimport { cn } from \"../lib/cn.js\";\nimport { AppLauncher } from \"./app-launcher.js\";\nimport { useShell } from \"./shell-context.js\";\nimport { SidebarGroup } from \"./sidebar-group.js\";\nimport { ThemeToggle } from \"./theme-toggle.js\";\nimport { UserMenu, type UserMenuProps } from \"./user-menu.js\";\nimport { appAccentStyle, flattenNavItems } from \"./utils.js\";\nimport { WorkspaceSwitcher } from \"./workspace-switcher.js\";\n\nexport interface MobileMenuProps {\n className?: string;\n /** Slot ngay dưới WorkspaceSwitcher. */\n header?: ReactNode;\n /** Slot đáy (phiên bản app, liên kết hỗ trợ…). */\n footer?: ReactNode;\n showAppLauncher?: boolean;\n showThemeToggle?: boolean;\n /** Chuyển tiếp props cho `UserMenu` ở đáy. */\n userMenuProps?: UserMenuProps;\n}\n\n/**\n * Menu off-canvas cho màn hình hẹp (`Sheet` trượt từ trái):\n * WorkspaceSwitcher (trục 1) → menu app HIỆN TẠI → danh sách app (trục 2) → user.\n *\n * ⚠ Thứ tự đó cố ý KHÁC thanh bên desktop. Trên desktop danh sách app nằm ở header nên nó không\n * chiếm chỗ của menu; ở đây cả hai nằm chung một vùng cuộn, và menu của app đang mở là thứ người\n * dùng mở menu để tìm. Đặt danh sách app lên trước thì một hệ sinh thái 8–10 sản phẩm đẩy toàn bộ\n * menu chính xuống dưới nếp gấp — phải cuộn mới thấy thứ mình cần.\n *\n * Tự đóng khi đổi route (`ShellProvider` lo) và khi bấm vào một link thật trong menu.\n */\nexport function MobileMenu({\n className,\n header,\n footer,\n showAppLauncher = true,\n showThemeToggle = true,\n userMenuProps\n}: MobileMenuProps) {\n const { mobileMenuOpen, setMobileMenuOpen, nav, currentApp, user, labels } = useShell();\n\n const allItems = flattenNavItems(nav);\n\n // Chỉ đóng khi bấm trúng LINK thật — `SidebarMenuItem` không gọi callback này\n // khi người dùng chỉ mở/đóng nhánh con, nên menu giữ nguyên cho họ đi tiếp.\n const closeMenu = () => setMobileMenuOpen(false);\n\n return (\n <Sheet open={mobileMenuOpen} onOpenChange={setMobileMenuOpen}>\n <SheetContent\n side=\"left\"\n aria-describedby={undefined}\n // `closeLabel` PHẢI truyền tay. Mặc định của `SheetContent` là \"
|
|
1
|
+
{"version":3,"sources":["../../src/shell/mobile-menu.tsx"],"sourcesContent":["\"use client\";\n\nimport type { ReactNode } from \"react\";\nimport { Separator } from \"../components/ui/separator.js\";\nimport { Sheet, SheetContent, SheetHeader, SheetTitle } from \"../components/ui/sheet.js\";\nimport { cn } from \"../lib/cn.js\";\nimport { AppLauncher } from \"./app-launcher.js\";\nimport { useShell } from \"./shell-context.js\";\nimport { SidebarGroup } from \"./sidebar-group.js\";\nimport { ThemeToggle } from \"./theme-toggle.js\";\nimport { UserMenu, type UserMenuProps } from \"./user-menu.js\";\nimport { appAccentStyle, flattenNavItems } from \"./utils.js\";\nimport { WorkspaceSwitcher } from \"./workspace-switcher.js\";\n\nexport interface MobileMenuProps {\n className?: string;\n /** Slot ngay dưới WorkspaceSwitcher. */\n header?: ReactNode;\n /** Slot đáy (phiên bản app, liên kết hỗ trợ…). */\n footer?: ReactNode;\n showAppLauncher?: boolean;\n showThemeToggle?: boolean;\n /** Chuyển tiếp props cho `UserMenu` ở đáy. */\n userMenuProps?: UserMenuProps;\n}\n\n/**\n * Menu off-canvas cho màn hình hẹp (`Sheet` trượt từ trái):\n * WorkspaceSwitcher (trục 1) → menu app HIỆN TẠI → danh sách app (trục 2) → user.\n *\n * ⚠ Thứ tự đó cố ý KHÁC thanh bên desktop. Trên desktop danh sách app nằm ở header nên nó không\n * chiếm chỗ của menu; ở đây cả hai nằm chung một vùng cuộn, và menu của app đang mở là thứ người\n * dùng mở menu để tìm. Đặt danh sách app lên trước thì một hệ sinh thái 8–10 sản phẩm đẩy toàn bộ\n * menu chính xuống dưới nếp gấp — phải cuộn mới thấy thứ mình cần.\n *\n * Tự đóng khi đổi route (`ShellProvider` lo) và khi bấm vào một link thật trong menu.\n */\nexport function MobileMenu({\n className,\n header,\n footer,\n showAppLauncher = true,\n showThemeToggle = true,\n userMenuProps\n}: MobileMenuProps) {\n const { mobileMenuOpen, setMobileMenuOpen, nav, currentApp, user, labels } = useShell();\n\n const allItems = flattenNavItems(nav);\n\n // Chỉ đóng khi bấm trúng LINK thật — `SidebarMenuItem` không gọi callback này\n // khi người dùng chỉ mở/đóng nhánh con, nên menu giữ nguyên cho họ đi tiếp.\n const closeMenu = () => setMobileMenuOpen(false);\n\n return (\n <Sheet open={mobileMenuOpen} onOpenChange={setMobileMenuOpen}>\n <SheetContent\n side=\"left\"\n aria-describedby={undefined}\n // `closeLabel` PHẢI truyền tay. Mặc định của `SheetContent` là nhãn \"đóng\" CHUNG của\n // primitive (namespace `sheet`), còn app đổi chữ của shell qua prop `labels` của shell.\n // Không nối hai đầu ở đây thì một `closeMenu` app đã đổi không bao giờ tới được cây trợ\n // năng — và họ không có chỗ nào sửa, vì `MobileMenu` không nhận props của `Sheet`.\n // Dùng `closeMenu` (\"Close menu\") chứ không phải một nhãn \"Close\" chung: trình đọc màn hình\n // đọc nút này TÁCH khỏi ngữ cảnh, nên nó phải nói rõ đóng cái gì — cùng lý do với\n // `collapseSidebar` trong types.ts.\n closeLabel={labels.closeMenu}\n style={appAccentStyle(currentApp)}\n className={cn(\"flex w-80 max-w-[85vw] flex-col gap-0 bg-sidebar p-0 md:hidden\", className)}\n >\n {/*\n `pr-12`: `SheetContent` đặt nút đóng ở `absolute top-4 right-4`, tức nó nằm ĐÈ lên nội\n dung header. Không chừa chỗ thì nó phủ lên mũi tên của `WorkspaceSwitcher` — bấm vào\n tưởng đổi workspace mà hoá ra đóng menu.\n */}\n <SheetHeader className=\"shrink-0 border-b border-sidebar-border p-2 pr-12\">\n <SheetTitle className=\"sr-only\">{labels.navigationLabel}</SheetTitle>\n <WorkspaceSwitcher align=\"start\" />\n </SheetHeader>\n\n {header ? <div className=\"shrink-0 p-2\">{header}</div> : null}\n\n <div className=\"flex-1 overflow-y-auto scrollbar-thin\">\n {/* Menu của app HIỆN TẠI đứng trước — xem lý do thứ tự ở JSDoc đầu file. */}\n <div className=\"flex flex-col gap-4 p-2\">\n {nav.map((group) => (\n <SidebarGroup key={group.id} group={group} siblings={allItems} onNavigate={closeMenu} />\n ))}\n </div>\n\n {showAppLauncher ? (\n <>\n <Separator className=\"bg-sidebar-border\" />\n <div className=\"p-2\">\n <AppLauncher inline variant=\"list\" onSelected={closeMenu} />\n </div>\n </>\n ) : null}\n </div>\n\n <div className=\"flex shrink-0 items-center gap-2 border-t border-sidebar-border p-2\">\n {user ? <UserMenu showName {...userMenuProps} className=\"min-w-0 flex-1\" /> : null}\n {showThemeToggle ? <ThemeToggle labels={labels} /> : null}\n </div>\n\n {footer ? <div className=\"shrink-0 border-t border-sidebar-border p-2\">{footer}</div> : null}\n </SheetContent>\n </Sheet>\n );\n}\n"],"mappings":";AA0EQ,SAgBI,UAfF,KADF;AAvER,SAAS,iBAAiB;AAC1B,SAAS,OAAO,cAAc,aAAa,kBAAkB;AAC7D,SAAS,UAAU;AACnB,SAAS,mBAAmB;AAC5B,SAAS,gBAAgB;AACzB,SAAS,oBAAoB;AAC7B,SAAS,mBAAmB;AAC5B,SAAS,gBAAoC;AAC7C,SAAS,gBAAgB,uBAAuB;AAChD,SAAS,yBAAyB;AAyB3B,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA,kBAAkB;AAAA,EAClB,kBAAkB;AAAA,EAClB;AACF,GAAoB;AAClB,QAAM,EAAE,gBAAgB,mBAAmB,KAAK,YAAY,MAAM,OAAO,IAAI,SAAS;AAEtF,QAAM,WAAW,gBAAgB,GAAG;AAIpC,QAAM,YAAY,MAAM,kBAAkB,KAAK;AAE/C,SACE,oBAAC,SAAM,MAAM,gBAAgB,cAAc,mBACzC;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,oBAAkB;AAAA,MAQlB,YAAY,OAAO;AAAA,MACnB,OAAO,eAAe,UAAU;AAAA,MAChC,WAAW,GAAG,kEAAkE,SAAS;AAAA,MAOzF;AAAA,6BAAC,eAAY,WAAU,qDACrB;AAAA,8BAAC,cAAW,WAAU,WAAW,iBAAO,iBAAgB;AAAA,UACxD,oBAAC,qBAAkB,OAAM,SAAQ;AAAA,WACnC;AAAA,QAEC,SAAS,oBAAC,SAAI,WAAU,gBAAgB,kBAAO,IAAS;AAAA,QAEzD,qBAAC,SAAI,WAAU,yCAEb;AAAA,8BAAC,SAAI,WAAU,2BACZ,cAAI,IAAI,CAAC,UACR,oBAAC,gBAA4B,OAAc,UAAU,UAAU,YAAY,aAAxD,MAAM,EAA6D,CACvF,GACH;AAAA,UAEC,kBACC,iCACE;AAAA,gCAAC,aAAU,WAAU,qBAAoB;AAAA,YACzC,oBAAC,SAAI,WAAU,OACb,8BAAC,eAAY,QAAM,MAAC,SAAQ,QAAO,YAAY,WAAW,GAC5D;AAAA,aACF,IACE;AAAA,WACN;AAAA,QAEA,qBAAC,SAAI,WAAU,uEACZ;AAAA,iBAAO,oBAAC,YAAS,UAAQ,MAAE,GAAG,eAAe,WAAU,kBAAiB,IAAK;AAAA,UAC7E,kBAAkB,oBAAC,eAAY,QAAgB,IAAK;AAAA,WACvD;AAAA,QAEC,SAAS,oBAAC,SAAI,WAAU,+CAA+C,kBAAO,IAAS;AAAA;AAAA;AAAA,EAC1F,GACF;AAEJ;","names":[]}
|
|
@@ -4,7 +4,7 @@ import { ComponentProps } from 'react';
|
|
|
4
4
|
interface NotificationsButtonProps extends Omit<ComponentProps<"button">, "children"> {
|
|
5
5
|
/** Số thông báo chưa đọc; 0 hoặc bỏ trống thì không hiện chấm số. */
|
|
6
6
|
count?: number;
|
|
7
|
-
/** Nhãn a11y
|
|
7
|
+
/** Nhãn a11y. Không truyền thì lấy khoá `notifications` của namespace `shell`. */
|
|
8
8
|
label?: string;
|
|
9
9
|
/** Vượt ngưỡng này thì hiển thị "n+" (mặc định 9). */
|
|
10
10
|
max?: number;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { Bell } from "lucide-react";
|
|
4
|
+
import { useComitorMessages } from "../i18n/locale-provider.js";
|
|
4
5
|
import { cn } from "../lib/cn.js";
|
|
5
|
-
import { DEFAULT_SHELL_LABELS } from "./types.js";
|
|
6
6
|
function NotificationsButton({ count = 0, label, max = 9, className, ...props }) {
|
|
7
|
-
const
|
|
7
|
+
const t = useComitorMessages("shell");
|
|
8
|
+
const text = label ?? t.notifications;
|
|
8
9
|
const hasUnread = count > 0;
|
|
9
10
|
return /* @__PURE__ */ jsxs(
|
|
10
11
|
"button",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/shell/notifications-button.tsx"],"sourcesContent":["\"use client\";\n\nimport { Bell } from \"lucide-react\";\nimport type { ComponentProps } from \"react\";\nimport {
|
|
1
|
+
{"version":3,"sources":["../../src/shell/notifications-button.tsx"],"sourcesContent":["\"use client\";\n\nimport { Bell } from \"lucide-react\";\nimport type { ComponentProps } from \"react\";\nimport { useComitorMessages } from \"../i18n/locale-provider.js\";\nimport { cn } from \"../lib/cn.js\";\n\nexport interface NotificationsButtonProps extends Omit<ComponentProps<\"button\">, \"children\"> {\n /** Số thông báo chưa đọc; 0 hoặc bỏ trống thì không hiện chấm số. */\n count?: number;\n /** Nhãn a11y. Không truyền thì lấy khoá `notifications` của namespace `shell`. */\n label?: string;\n /** Vượt ngưỡng này thì hiển thị \"n+\" (mặc định 9). */\n max?: number;\n}\n\n/**\n * Nút chuông + số chưa đọc. **Chỉ có cái nút** — nội dung panel thông báo là\n * app-specific: đóng sẵn một NotificationPanel vào package thì app nào cũng phải\n * uốn dữ liệu của mình cho vừa cái khuôn đó.\n *\n * App tự bọc trong Popover/Sheet của mình:\n * `<PopoverTrigger asChild><NotificationsButton count={3} /></PopoverTrigger>`\n */\nexport function NotificationsButton({ count = 0, label, max = 9, className, ...props }: NotificationsButtonProps) {\n const t = useComitorMessages(\"shell\");\n const text = label ?? t.notifications;\n const hasUnread = count > 0;\n\n return (\n <button\n type=\"button\"\n aria-label={hasUnread ? `${text} (${count})` : text}\n className={cn(\n \"relative flex size-9 items-center justify-center rounded-md text-muted-foreground\",\n \"transition-colors duration-150 ease-out hover:bg-muted hover:text-foreground\",\n className\n )}\n {...props}\n >\n <Bell className=\"size-4\" />\n {hasUnread ? (\n /*\n * Huy hiệu ĐẨY RA GÓC, không nằm trong lòng nút.\n *\n * `top-0 right-0` — huy hiệu nằm SÁT GÓC nút, không lấn vào trong và cũng không tràn ra\n * ngoài. Hai bản trước đều lệch, đo trên DOM thật (nút 36px, icon 16px căn giữa):\n *\n * right-1 top-1 che 39% cái chuông — thấy khối đỏ, không thấy chuông\n * -right-0.5 -top-0.5 che 6% — hết che, nhưng huy hiệu trôi ra ngoài nút, đọc như\n * một chấm rời không thuộc về cái chuông\n * top-0 right-0 che 14% — dính vào góc icon, vẫn thấy rõ hình chuông ← đang dùng\n *\n * KHÔNG dùng giá trị âm: các nút header cách nhau `gap-1` (4px), tràn ra ngoài là huy\n * hiệu tiến sát nút bên cạnh.\n *\n * `ring-2 ring-background` là nửa còn lại: viền cùng màu nền header cắt một đường sạch\n * giữa huy hiệu và icon, nên 14% chồng lấn kia đọc thành hai hình tách bạch chứ không\n * nhoè vào nhau. Cùng thủ pháp mà mẫu AppHeader ở mục Patterns dùng cho chấm thông báo\n * (`ring-2 ring-card`).\n */\n <span\n className={cn(\n \"absolute right-0 top-0 flex h-4 min-w-4 items-center justify-center rounded-full px-1\",\n \"ring-2 ring-background\",\n \"bg-destructive text-[10px] font-semibold leading-none text-destructive-foreground\"\n )}\n >\n {count > max ? `${max}+` : count}\n </span>\n ) : null}\n </button>\n );\n}\n"],"mappings":";AA8BI,SAUE,KAVF;AA5BJ,SAAS,YAAY;AAErB,SAAS,0BAA0B;AACnC,SAAS,UAAU;AAmBZ,SAAS,oBAAoB,EAAE,QAAQ,GAAG,OAAO,MAAM,GAAG,WAAW,GAAG,MAAM,GAA6B;AAChH,QAAM,IAAI,mBAAmB,OAAO;AACpC,QAAM,OAAO,SAAS,EAAE;AACxB,QAAM,YAAY,QAAQ;AAE1B,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,cAAY,YAAY,GAAG,IAAI,KAAK,KAAK,MAAM;AAAA,MAC/C,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEJ;AAAA,4BAAC,QAAK,WAAU,UAAS;AAAA,QACxB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,UAoBC;AAAA,YAAC;AAAA;AAAA,cACC,WAAW;AAAA,gBACT;AAAA,gBACA;AAAA,gBACA;AAAA,cACF;AAAA,cAEC,kBAAQ,MAAM,GAAG,GAAG,MAAM;AAAA;AAAA,UAC7B;AAAA,YACE;AAAA;AAAA;AAAA,EACN;AAEJ;","names":[]}
|
|
@@ -102,7 +102,10 @@ interface ShellProviderProps {
|
|
|
102
102
|
persistSidebarState?: boolean;
|
|
103
103
|
/** Tắt bộ phím tắt toàn cục. */
|
|
104
104
|
enableShortcuts?: boolean;
|
|
105
|
-
/**
|
|
105
|
+
/**
|
|
106
|
+
* Override từng chuỗi hiển thị. Không truyền thì theo namespace `shell` của
|
|
107
|
+
* `ComitorLocaleProvider` gần nhất (không có provider → tiếng Anh).
|
|
108
|
+
*/
|
|
106
109
|
labels?: Partial<ShellLabels>;
|
|
107
110
|
children: ReactNode;
|
|
108
111
|
}
|
|
@@ -4,10 +4,8 @@ import NextLinkModule from "next/link.js";
|
|
|
4
4
|
import { usePathname } from "next/navigation.js";
|
|
5
5
|
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react";
|
|
6
6
|
import { TooltipProvider } from "../components/ui/tooltip.js";
|
|
7
|
+
import { useComitorLabels } from "../i18n/locale-provider.js";
|
|
7
8
|
import { layout } from "../tokens.js";
|
|
8
|
-
import {
|
|
9
|
-
DEFAULT_SHELL_LABELS
|
|
10
|
-
} from "./types.js";
|
|
11
9
|
import { getEntitledApps, resolveCurrentApp, toNavGroups, toSearchParams } from "./utils.js";
|
|
12
10
|
const NextLink = NextLinkModule.default ?? NextLinkModule;
|
|
13
11
|
function DefaultShellLink({ href, children, ...props }) {
|
|
@@ -77,7 +75,7 @@ function ShellProvider({
|
|
|
77
75
|
return resolveCurrentApp(apps, pathname);
|
|
78
76
|
}, [apps, currentAppId, pathname]);
|
|
79
77
|
const navGroups = useMemo(() => toNavGroups(nav), [nav]);
|
|
80
|
-
const labels =
|
|
78
|
+
const labels = useComitorLabels("shell", labelsProp);
|
|
81
79
|
const [sidebarCollapsed, setSidebarCollapsed] = useState(defaultSidebarCollapsed);
|
|
82
80
|
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
|
83
81
|
const [commandPaletteOpen, setCommandPaletteOpen] = useState(false);
|