@evinvest/uikit 0.31.0 → 0.33.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/README.md +23 -12
- package/dist/components/{accordion.js → atomic/accordion.js} +5 -4
- package/dist/components/atomic/accordion.js.map +1 -0
- package/dist/components/{alert-dialog.js → atomic/alert-dialog.js} +9 -9
- package/dist/components/atomic/alert-dialog.js.map +1 -0
- package/dist/components/{alert.js → atomic/alert.js} +6 -6
- package/dist/components/atomic/alert.js.map +1 -0
- package/dist/components/{animated-number-columns.js → atomic/animated-number-columns.js} +1 -1
- package/dist/components/atomic/animated-number-columns.js.map +1 -0
- package/dist/components/{avatar.js → atomic/avatar.js} +2 -2
- package/dist/components/atomic/avatar.js.map +1 -0
- package/dist/components/atomic/badge.js +26 -0
- package/dist/components/atomic/badge.js.map +1 -0
- package/dist/components/{band.js → atomic/band.js} +2 -2
- package/dist/components/atomic/band.js.map +1 -0
- package/dist/components/{breadcrumb.js → atomic/breadcrumb.js} +10 -12
- package/dist/components/atomic/breadcrumb.js.map +1 -0
- package/dist/components/{button-group.js → atomic/button-group.js} +3 -3
- package/dist/components/atomic/button-group.js.map +1 -0
- package/dist/components/{button.js → atomic/button.js} +8 -7
- package/dist/components/atomic/button.js.map +1 -0
- package/dist/components/{calendar.js → atomic/calendar.js} +12 -27
- package/dist/components/atomic/calendar.js.map +1 -0
- package/dist/components/{card.js → atomic/card.js} +2 -2
- package/dist/components/atomic/card.js.map +1 -0
- package/dist/components/{checkbox.js → atomic/checkbox.js} +6 -5
- package/dist/components/atomic/checkbox.js.map +1 -0
- package/dist/components/{collapsible.js → atomic/collapsible.js} +1 -1
- package/dist/components/atomic/collapsible.js.map +1 -0
- package/dist/components/{command.js → atomic/command.js} +11 -26
- package/dist/components/atomic/command.js.map +1 -0
- package/dist/components/{container.js → atomic/container.js} +2 -2
- package/dist/components/atomic/container.js.map +1 -0
- package/dist/components/{context-menu.js → atomic/context-menu.js} +15 -22
- package/dist/components/atomic/context-menu.js.map +1 -0
- package/dist/components/{dialog.js → atomic/dialog.js} +13 -11
- package/dist/components/atomic/dialog.js.map +1 -0
- package/dist/components/{drawer.js → atomic/drawer.js} +8 -8
- package/dist/components/atomic/drawer.js.map +1 -0
- package/dist/components/{dropdown-menu.js → atomic/dropdown-menu.js} +16 -24
- package/dist/components/atomic/dropdown-menu.js.map +1 -0
- package/dist/components/{empty.js → atomic/empty.js} +2 -2
- package/dist/components/atomic/empty.js.map +1 -0
- package/dist/components/atomic/field-context.js.map +1 -0
- package/dist/components/{form-row.js → atomic/form-row.js} +2 -2
- package/dist/components/atomic/form-row.js.map +1 -0
- package/dist/components/{hover-card.js → atomic/hover-card.js} +8 -8
- package/dist/components/atomic/hover-card.js.map +1 -0
- package/dist/components/{info-tip.js → atomic/info-tip.js} +18 -19
- package/dist/components/atomic/info-tip.js.map +1 -0
- package/dist/components/{input-otp.js → atomic/input-otp.js} +5 -4
- package/dist/components/atomic/input-otp.js.map +1 -0
- package/dist/components/{input.js → atomic/input.js} +3 -3
- package/dist/components/atomic/input.js.map +1 -0
- package/dist/components/{kbd.js → atomic/kbd.js} +2 -2
- package/dist/components/atomic/kbd.js.map +1 -0
- package/dist/components/{key-value.js → atomic/key-value.js} +8 -7
- package/dist/components/atomic/key-value.js.map +1 -0
- package/dist/components/{label.js → atomic/label.js} +2 -2
- package/dist/components/atomic/label.js.map +1 -0
- package/dist/components/{menubar.js → atomic/menubar.js} +16 -27
- package/dist/components/atomic/menubar.js.map +1 -0
- package/dist/components/{native-select.js → atomic/native-select.js} +5 -5
- package/dist/components/atomic/native-select.js.map +1 -0
- package/dist/components/{navigation-menu.js → atomic/navigation-menu.js} +12 -20
- package/dist/components/atomic/navigation-menu.js.map +1 -0
- package/dist/components/{pagination.js → atomic/pagination.js} +19 -20
- package/dist/components/atomic/pagination.js.map +1 -0
- package/dist/components/{popover.js → atomic/popover.js} +10 -10
- package/dist/components/atomic/popover.js.map +1 -0
- package/dist/components/{progress.js → atomic/progress.js} +2 -2
- package/dist/components/atomic/progress.js.map +1 -0
- package/dist/components/{radio-group.js → atomic/radio-group.js} +8 -7
- package/dist/components/atomic/radio-group.js.map +1 -0
- package/dist/components/{scroll-area.js → atomic/scroll-area.js} +2 -2
- package/dist/components/atomic/scroll-area.js.map +1 -0
- package/dist/components/{section-descriptor.js → atomic/section-descriptor.js} +7 -10
- package/dist/components/atomic/section-descriptor.js.map +1 -0
- package/dist/components/{select-listbox.js → atomic/select-listbox.js} +3 -3
- package/dist/components/atomic/select-listbox.js.map +1 -0
- package/dist/components/{select.js → atomic/select.js} +15 -23
- package/dist/components/atomic/select.js.map +1 -0
- package/dist/components/{separator.js → atomic/separator.js} +2 -2
- package/dist/components/atomic/separator.js.map +1 -0
- package/dist/components/{sheet.js → atomic/sheet.js} +13 -11
- package/dist/components/atomic/sheet.js.map +1 -0
- package/dist/components/{sidebar.js → atomic/sidebar.js} +11 -30
- package/dist/components/atomic/sidebar.js.map +1 -0
- package/dist/components/{skeleton.js → atomic/skeleton.js} +2 -2
- package/dist/components/atomic/skeleton.js.map +1 -0
- package/dist/components/{slider.js → atomic/slider.js} +3 -3
- package/dist/components/atomic/slider.js.map +1 -0
- package/dist/components/{sonner.js → atomic/sonner.js} +22 -20
- package/dist/components/atomic/sonner.js.map +1 -0
- package/dist/components/atomic/spinner.js +26 -0
- package/dist/components/atomic/spinner.js.map +1 -0
- package/dist/components/{status-item.js → atomic/status-item.js} +2 -2
- package/dist/components/atomic/status-item.js.map +1 -0
- package/dist/components/{switch.js → atomic/switch.js} +4 -4
- package/dist/components/atomic/switch.js.map +1 -0
- package/dist/components/{table.js → atomic/table.js} +2 -2
- package/dist/components/atomic/table.js.map +1 -0
- package/dist/components/{tabs.js → atomic/tabs.js} +4 -4
- package/dist/components/atomic/tabs.js.map +1 -0
- package/dist/components/{textarea.js → atomic/textarea.js} +3 -3
- package/dist/components/atomic/textarea.js.map +1 -0
- package/dist/components/{toggle-group.js → atomic/toggle-group.js} +4 -4
- package/dist/components/atomic/toggle-group.js.map +1 -0
- package/dist/components/{toggle.js → atomic/toggle.js} +5 -8
- package/dist/components/atomic/toggle.js.map +1 -0
- package/dist/components/{tooltip.js → atomic/tooltip.js} +8 -8
- package/dist/components/atomic/tooltip.js.map +1 -0
- package/dist/components/{use-count-tween.js → atomic/use-count-tween.js} +1 -1
- package/dist/components/atomic/use-count-tween.js.map +1 -0
- package/dist/components/{animated-number.js → derived/animated-number.js} +5 -5
- package/dist/components/derived/animated-number.js.map +1 -0
- package/dist/components/{carousel.js → derived/carousel.js} +10 -10
- package/dist/components/derived/carousel.js.map +1 -0
- package/dist/components/{command-palette.js → derived/command-palette.js} +14 -13
- package/dist/components/derived/command-palette.js.map +1 -0
- package/dist/components/{confirm-dialog.js → derived/confirm-dialog.js} +15 -20
- package/dist/components/derived/confirm-dialog.js.map +1 -0
- package/dist/components/{date-time-picker.js → derived/date-time-picker.js} +18 -22
- package/dist/components/derived/date-time-picker.js.map +1 -0
- package/dist/components/{field.js → derived/field.js} +6 -6
- package/dist/components/derived/field.js.map +1 -0
- package/dist/components/{form.js → derived/form.js} +4 -5
- package/dist/components/derived/form.js.map +1 -0
- package/dist/components/{icon-button.js → derived/icon-button.js} +4 -4
- package/dist/components/derived/icon-button.js.map +1 -0
- package/dist/components/{input-group.js → derived/input-group.js} +5 -5
- package/dist/components/derived/input-group.js.map +1 -0
- package/dist/components/{item.js → derived/item.js} +4 -5
- package/dist/components/derived/item.js.map +1 -0
- package/dist/components/{popover-form.js → derived/popover-form.js} +9 -8
- package/dist/components/derived/popover-form.js.map +1 -0
- package/dist/components/{settings.js → derived/settings.js} +7 -7
- package/dist/components/derived/settings.js.map +1 -0
- package/dist/components/{account-list.js → shell/account-list.js} +5 -5
- package/dist/components/shell/account-list.js.map +1 -0
- package/dist/components/{account-menu-profile.js → shell/account-menu-profile.js} +7 -7
- package/dist/components/shell/account-menu-profile.js.map +1 -0
- package/dist/components/{account-menu.js → shell/account-menu.js} +4 -4
- package/dist/components/shell/account-menu.js.map +1 -0
- package/dist/components/{account-settings-dialog.js → shell/account-settings-dialog.js} +9 -8
- package/dist/components/shell/account-settings-dialog.js.map +1 -0
- package/dist/components/{app-shell.js → shell/app-shell.js} +1 -1
- package/dist/components/shell/app-shell.js.map +1 -0
- package/dist/components/{aside.js → shell/aside.js} +12 -10
- package/dist/components/shell/aside.js.map +1 -0
- package/dist/components/{auth-wall.js → shell/auth-wall.js} +4 -6
- package/dist/components/shell/auth-wall.js.map +1 -0
- package/dist/components/{bottom-tab-bar.js → shell/bottom-tab-bar.js} +4 -4
- package/dist/components/shell/bottom-tab-bar.js.map +1 -0
- package/dist/components/{chat-composer.js → shell/chat-composer.js} +16 -14
- package/dist/components/shell/chat-composer.js.map +1 -0
- package/dist/components/{chat-message.js → shell/chat-message.js} +4 -4
- package/dist/components/shell/chat-message.js.map +1 -0
- package/dist/components/{chat-panel.js → shell/chat-panel.js} +3 -3
- package/dist/components/shell/chat-panel.js.map +1 -0
- package/dist/components/shell/chat-stream-text.js.map +1 -0
- package/dist/components/{chat-typing.js → shell/chat-typing.js} +4 -3
- package/dist/components/shell/chat-typing.js.map +1 -0
- package/dist/components/{footer.js → shell/footer.js} +27 -25
- package/dist/components/shell/footer.js.map +1 -0
- package/dist/components/{header-controls.js → shell/header-controls.js} +25 -23
- package/dist/components/shell/header-controls.js.map +1 -0
- package/dist/components/shell/hotbar.js +158 -0
- package/dist/components/shell/hotbar.js.map +1 -0
- package/dist/components/{list-rows.js → shell/list-rows.js} +1 -1
- package/dist/components/shell/list-rows.js.map +1 -0
- package/dist/components/{logo.js → shell/logo.js} +4 -3
- package/dist/components/shell/logo.js.map +1 -0
- package/dist/components/{mobile-app-bar.js → shell/mobile-app-bar.js} +5 -5
- package/dist/components/shell/mobile-app-bar.js.map +1 -0
- package/dist/components/{nav-badge.js → shell/nav-badge.js} +1 -1
- package/dist/components/shell/nav-badge.js.map +1 -0
- package/dist/components/{nav-collapse.js → shell/nav-collapse.js} +8 -7
- package/dist/components/shell/nav-collapse.js.map +1 -0
- package/dist/components/shell/nav-link.js.map +1 -0
- package/dist/components/shell/nav-model.js.map +1 -0
- package/dist/components/{page-frame.js → shell/page-frame.js} +2 -2
- package/dist/components/shell/page-frame.js.map +1 -0
- package/dist/components/{resource-error.js → shell/resource-error.js} +5 -5
- package/dist/components/shell/resource-error.js.map +1 -0
- package/dist/components/{section-nav.js → shell/section-nav.js} +4 -4
- package/dist/components/shell/section-nav.js.map +1 -0
- package/dist/components/shell/settled.js.map +1 -0
- package/dist/components/shell/shell-icons.js +42 -0
- package/dist/components/shell/shell-icons.js.map +1 -0
- package/dist/components/{shell-nav.js → shell/shell-nav.js} +5 -5
- package/dist/components/shell/shell-nav.js.map +1 -0
- package/dist/components/{smart-header.js → shell/smart-header.js} +2 -2
- package/dist/components/shell/smart-header.js.map +1 -0
- package/dist/components/shell/status-screen.js +145 -0
- package/dist/components/shell/status-screen.js.map +1 -0
- package/dist/components/{system-banner.js → shell/system-banner.js} +8 -8
- package/dist/components/shell/system-banner.js.map +1 -0
- package/dist/components/{top-bar.js → shell/top-bar.js} +2 -2
- package/dist/components/shell/top-bar.js.map +1 -0
- package/dist/components/shell/use-shell-nav.js.map +1 -0
- package/dist/components/{workspace-tab.js → shell/workspace-tab.js} +6 -5
- package/dist/components/shell/workspace-tab.js.map +1 -0
- package/dist/generated/accent.js +10 -10
- package/dist/generated/accent.js.map +1 -1
- package/dist/generated/accordion.js +6 -2
- package/dist/generated/accordion.js.map +1 -1
- package/dist/generated/account-list.js +4 -4
- package/dist/generated/account-list.js.map +1 -1
- package/dist/generated/account-settings-dialog.js +6 -2
- package/dist/generated/account-settings-dialog.js.map +1 -1
- package/dist/generated/alert-dialog.js +1 -1
- package/dist/generated/alert-dialog.js.map +1 -1
- package/dist/generated/alert.js +3 -10
- package/dist/generated/alert.js.map +1 -1
- package/dist/generated/aside.js +7 -0
- package/dist/generated/aside.js.map +1 -1
- package/dist/generated/badge.js +2 -11
- package/dist/generated/badge.js.map +1 -1
- package/dist/generated/band.js +10 -10
- package/dist/generated/band.js.map +1 -1
- package/dist/generated/breadcrumb.js +1 -1
- package/dist/generated/breadcrumb.js.map +1 -1
- package/dist/generated/button-group.js +3 -3
- package/dist/generated/button-group.js.map +1 -1
- package/dist/generated/button.js +4 -20
- package/dist/generated/button.js.map +1 -1
- package/dist/generated/calendar.js +5 -5
- package/dist/generated/calendar.js.map +1 -1
- package/dist/generated/card.js +1 -1
- package/dist/generated/card.js.map +1 -1
- package/dist/generated/carousel.js +1 -1
- package/dist/generated/carousel.js.map +1 -1
- package/dist/generated/chat-composer.js +9 -2
- package/dist/generated/chat-composer.js.map +1 -1
- package/dist/generated/chat-message.js +25 -19
- package/dist/generated/chat-message.js.map +1 -1
- package/dist/generated/checkbox.js +1 -1
- package/dist/generated/checkbox.js.map +1 -1
- package/dist/generated/command-palette.js +10 -1
- package/dist/generated/command-palette.js.map +1 -1
- package/dist/generated/command.js +8 -6
- package/dist/generated/command.js.map +1 -1
- package/dist/generated/confirm-dialog.js +8 -8
- package/dist/generated/confirm-dialog.js.map +1 -1
- package/dist/generated/context-menu.js +7 -5
- package/dist/generated/context-menu.js.map +1 -1
- package/dist/generated/copy.js +97 -0
- package/dist/generated/copy.js.map +1 -0
- package/dist/generated/date-time-picker.js +10 -0
- package/dist/generated/date-time-picker.js.map +1 -1
- package/dist/generated/dialog.js +2 -2
- package/dist/generated/dialog.js.map +1 -1
- package/dist/generated/drawer.js +6 -6
- package/dist/generated/drawer.js.map +1 -1
- package/dist/generated/dropdown-menu.js +7 -5
- package/dist/generated/dropdown-menu.js.map +1 -1
- package/dist/generated/empty.js +5 -5
- package/dist/generated/empty.js.map +1 -1
- package/dist/generated/field.js +9 -7
- package/dist/generated/field.js.map +1 -1
- package/dist/generated/footer.js +45 -0
- package/dist/generated/footer.js.map +1 -0
- package/dist/generated/header-controls.js +5 -10
- package/dist/generated/header-controls.js.map +1 -1
- package/dist/generated/hover-card.js +1 -1
- package/dist/generated/hover-card.js.map +1 -1
- package/dist/generated/icon-button.js +4 -2
- package/dist/generated/icon-button.js.map +1 -1
- package/dist/generated/icons.js +36 -0
- package/dist/generated/icons.js.map +1 -0
- package/dist/generated/info-tip.js +5 -0
- package/dist/generated/info-tip.js.map +1 -0
- package/dist/generated/input-group.js +10 -10
- package/dist/generated/input-group.js.map +1 -1
- package/dist/generated/input-otp.js +2 -2
- package/dist/generated/input-otp.js.map +1 -1
- package/dist/generated/input.js +4 -4
- package/dist/generated/input.js.map +1 -1
- package/dist/generated/item.js +11 -11
- package/dist/generated/item.js.map +1 -1
- package/dist/generated/kbd.js +1 -1
- package/dist/generated/kbd.js.map +1 -1
- package/dist/generated/key-value.js +7 -1
- package/dist/generated/key-value.js.map +1 -1
- package/dist/generated/label.js +1 -1
- package/dist/generated/label.js.map +1 -1
- package/dist/generated/logo.js +9 -0
- package/dist/generated/logo.js.map +1 -0
- package/dist/generated/menu.js +7 -0
- package/dist/generated/menu.js.map +1 -0
- package/dist/generated/menubar.js +10 -8
- package/dist/generated/menubar.js.map +1 -1
- package/dist/generated/navigation-menu.js +11 -5
- package/dist/generated/navigation-menu.js.map +1 -1
- package/dist/generated/orientation.js +5 -0
- package/dist/generated/orientation.js.map +1 -0
- package/dist/generated/pagination.js +15 -0
- package/dist/generated/pagination.js.map +1 -0
- package/dist/generated/popover-form.js +5 -0
- package/dist/generated/popover-form.js.map +1 -1
- package/dist/generated/popover.js +1 -1
- package/dist/generated/popover.js.map +1 -1
- package/dist/generated/primitives.js +7 -0
- package/dist/generated/primitives.js.map +1 -0
- package/dist/generated/radio-group.js +5 -1
- package/dist/generated/radio-group.js.map +1 -1
- package/dist/generated/section-descriptor.js +7 -0
- package/dist/generated/section-descriptor.js.map +1 -0
- package/dist/generated/select.js +16 -10
- package/dist/generated/select.js.map +1 -1
- package/dist/generated/settings.js +2 -7
- package/dist/generated/settings.js.map +1 -1
- package/dist/generated/sheet.js +1 -1
- package/dist/generated/sheet.js.map +1 -1
- package/dist/generated/sidebar.js +24 -12
- package/dist/generated/sidebar.js.map +1 -1
- package/dist/generated/size.js +12 -0
- package/dist/generated/size.js.map +1 -0
- package/dist/generated/skeleton.js +1 -1
- package/dist/generated/skeleton.js.map +1 -1
- package/dist/generated/slider.js +2 -2
- package/dist/generated/slider.js.map +1 -1
- package/dist/generated/slot.js +5 -0
- package/dist/generated/slot.js.map +1 -0
- package/dist/generated/smart-header.js +2 -0
- package/dist/generated/smart-header.js.map +1 -1
- package/dist/generated/sonner.js +11 -13
- package/dist/generated/sonner.js.map +1 -1
- package/dist/generated/spinner.js +5 -0
- package/dist/generated/spinner.js.map +1 -0
- package/dist/generated/status-screen.js +27 -0
- package/dist/generated/status-screen.js.map +1 -0
- package/dist/generated/switch.js +2 -2
- package/dist/generated/switch.js.map +1 -1
- package/dist/generated/system-banner.js +6 -16
- package/dist/generated/system-banner.js.map +1 -1
- package/dist/generated/table.js +9 -9
- package/dist/generated/table.js.map +1 -1
- package/dist/generated/tabs.js +2 -2
- package/dist/generated/tabs.js.map +1 -1
- package/dist/generated/textarea.js +1 -1
- package/dist/generated/textarea.js.map +1 -1
- package/dist/generated/toggle-group.js +9 -0
- package/dist/generated/toggle-group.js.map +1 -0
- package/dist/generated/toggle.js +1 -9
- package/dist/generated/toggle.js.map +1 -1
- package/dist/generated/tone.js +12 -0
- package/dist/generated/tone.js.map +1 -0
- package/dist/generated/tooltip.js +1 -1
- package/dist/generated/tooltip.js.map +1 -1
- package/dist/generated/workspace-tab.js +3 -3
- package/dist/generated/workspace-tab.js.map +1 -1
- package/dist/icons/icon-shapes.js +10 -0
- package/dist/icons/icon-shapes.js.map +1 -0
- package/dist/{components → icons}/select-chevron.js +2 -1
- package/dist/icons/select-chevron.js.map +1 -0
- package/dist/icons/ui-icons.js +23 -0
- package/dist/icons/ui-icons.js.map +1 -0
- package/dist/index.d.ts +203 -155
- package/dist/index.js +97 -96
- package/dist/lib/cn.js +1 -0
- package/dist/lib/cn.js.map +1 -1
- package/dist/primitives/use-floating.js.map +1 -1
- package/dist/primitives/use-roving-focus.js.map +1 -1
- package/package.json +2 -2
- package/styles/ev.css +13 -76
- package/styles/theme.css +133 -66
- package/styles/tokens.css +157 -149
- package/dist/components/accordion.js.map +0 -1
- package/dist/components/account-list.js.map +0 -1
- package/dist/components/account-menu-profile.js.map +0 -1
- package/dist/components/account-menu.js.map +0 -1
- package/dist/components/account-settings-dialog.js.map +0 -1
- package/dist/components/alert-dialog.js.map +0 -1
- package/dist/components/alert.js.map +0 -1
- package/dist/components/animated-number-columns.js.map +0 -1
- package/dist/components/animated-number.js.map +0 -1
- package/dist/components/app-shell.js.map +0 -1
- package/dist/components/aside.js.map +0 -1
- package/dist/components/auth-wall.js.map +0 -1
- package/dist/components/avatar.js.map +0 -1
- package/dist/components/badge.js +0 -25
- package/dist/components/badge.js.map +0 -1
- package/dist/components/band.js.map +0 -1
- package/dist/components/bottom-tab-bar.js.map +0 -1
- package/dist/components/breadcrumb.js.map +0 -1
- package/dist/components/button-group.js.map +0 -1
- package/dist/components/button.js.map +0 -1
- package/dist/components/calendar.js.map +0 -1
- package/dist/components/card.js.map +0 -1
- package/dist/components/carousel.js.map +0 -1
- package/dist/components/chat-composer.js.map +0 -1
- package/dist/components/chat-message.js.map +0 -1
- package/dist/components/chat-panel.js.map +0 -1
- package/dist/components/chat-stream-text.js.map +0 -1
- package/dist/components/chat-typing.js.map +0 -1
- package/dist/components/checkbox.js.map +0 -1
- package/dist/components/collapsible.js.map +0 -1
- package/dist/components/command-palette.js.map +0 -1
- package/dist/components/command.js.map +0 -1
- package/dist/components/confirm-dialog.js.map +0 -1
- package/dist/components/container.js.map +0 -1
- package/dist/components/context-menu.js.map +0 -1
- package/dist/components/date-time-picker.js.map +0 -1
- package/dist/components/dialog.js.map +0 -1
- package/dist/components/drawer.js.map +0 -1
- package/dist/components/dropdown-menu.js.map +0 -1
- package/dist/components/empty.js.map +0 -1
- package/dist/components/field-context.js.map +0 -1
- package/dist/components/field.js.map +0 -1
- package/dist/components/footer.js.map +0 -1
- package/dist/components/form-row.js.map +0 -1
- package/dist/components/form.js.map +0 -1
- package/dist/components/header-controls.js.map +0 -1
- package/dist/components/hover-card.js.map +0 -1
- package/dist/components/icon-button.js.map +0 -1
- package/dist/components/info-tip.js.map +0 -1
- package/dist/components/input-group.js.map +0 -1
- package/dist/components/input-otp.js.map +0 -1
- package/dist/components/input.js.map +0 -1
- package/dist/components/item.js.map +0 -1
- package/dist/components/kbd.js.map +0 -1
- package/dist/components/key-value.js.map +0 -1
- package/dist/components/label.js.map +0 -1
- package/dist/components/list-rows.js.map +0 -1
- package/dist/components/logo.js.map +0 -1
- package/dist/components/menubar.js.map +0 -1
- package/dist/components/mobile-app-bar.js.map +0 -1
- package/dist/components/native-select.js.map +0 -1
- package/dist/components/nav-badge.js.map +0 -1
- package/dist/components/nav-collapse.js.map +0 -1
- package/dist/components/nav-link.js.map +0 -1
- package/dist/components/nav-model.js.map +0 -1
- package/dist/components/navigation-menu.js.map +0 -1
- package/dist/components/page-frame.js.map +0 -1
- package/dist/components/pagination.js.map +0 -1
- package/dist/components/popover-form.js.map +0 -1
- package/dist/components/popover.js.map +0 -1
- package/dist/components/progress.js.map +0 -1
- package/dist/components/radio-group.js.map +0 -1
- package/dist/components/resource-error.js.map +0 -1
- package/dist/components/scroll-area.js.map +0 -1
- package/dist/components/section-descriptor.js.map +0 -1
- package/dist/components/section-nav.js.map +0 -1
- package/dist/components/select-chevron.js.map +0 -1
- package/dist/components/select-listbox.js.map +0 -1
- package/dist/components/select.js.map +0 -1
- package/dist/components/separator.js.map +0 -1
- package/dist/components/settings.js.map +0 -1
- package/dist/components/settled.js.map +0 -1
- package/dist/components/sheet.js.map +0 -1
- package/dist/components/shell-icons.js +0 -57
- package/dist/components/shell-icons.js.map +0 -1
- package/dist/components/shell-nav.js.map +0 -1
- package/dist/components/sidebar.js.map +0 -1
- package/dist/components/skeleton.js.map +0 -1
- package/dist/components/slider.js.map +0 -1
- package/dist/components/smart-header.js.map +0 -1
- package/dist/components/sonner.js.map +0 -1
- package/dist/components/spinner.js +0 -23
- package/dist/components/spinner.js.map +0 -1
- package/dist/components/status-item.js.map +0 -1
- package/dist/components/status-screen.js +0 -145
- package/dist/components/status-screen.js.map +0 -1
- package/dist/components/switch.js.map +0 -1
- package/dist/components/system-banner.js.map +0 -1
- package/dist/components/table.js.map +0 -1
- package/dist/components/tabs.js.map +0 -1
- package/dist/components/textarea.js.map +0 -1
- package/dist/components/toggle-group.js.map +0 -1
- package/dist/components/toggle.js.map +0 -1
- package/dist/components/tooltip.js.map +0 -1
- package/dist/components/top-bar.js.map +0 -1
- package/dist/components/ui-icons.js +0 -77
- package/dist/components/ui-icons.js.map +0 -1
- package/dist/components/use-count-tween.js.map +0 -1
- package/dist/components/use-shell-nav.js.map +0 -1
- package/dist/components/workspace-tab.js.map +0 -1
- /package/dist/components/{field-context.js → atomic/field-context.js} +0 -0
- /package/dist/components/{chat-stream-text.js → shell/chat-stream-text.js} +0 -0
- /package/dist/components/{nav-link.js → shell/nav-link.js} +0 -0
- /package/dist/components/{nav-model.js → shell/nav-model.js} +0 -0
- /package/dist/components/{settled.js → shell/settled.js} +0 -0
- /package/dist/components/{use-shell-nav.js → shell/use-shell-nav.js} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/shell/footer.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { Container } from \"../atomic/container\";\nimport * as copy from \"../../generated/copy\";\nimport type { FooterLink, FooterLinkGroup, FooterOffice } from \"../../generated/footer\";\nimport { FOOTER_BRAND, FOOTER_COPYRIGHT, FOOTER_DESCRIPTION, FOOTER_GRID, FOOTER_HEADING, FOOTER_LEGAL_LINK, FOOTER_LEGAL_LINKS, FOOTER_LEGAL_SEPARATOR, FOOTER_LOCKUP, FOOTER_LOCKUP_TEXT, FOOTER_NAV_COLUMN, FOOTER_NAV_LINK, FOOTER_NAV_LIST, FOOTER_NEWSLETTER_BLURB, FOOTER_NEWSLETTER_COLUMN, FOOTER_OFFICES_LIST, FOOTER_OFFICE_NAME, FOOTER_ROOT, FOOTER_TAGLINE, FOOTER_VERSION, FOOTER_WIDE_COLUMN } from \"../../generated/footer\";\n\nexport type { FooterLink, FooterLinkGroup, FooterOffice };\n\nexport interface FooterProps {\n /** The whole lock-up; replaces the one `mark` + `brand` + `tagline` compose. */\n lockup?: React.ReactNode;\n /** The wordmark beside the mark, set in the display family. */\n brand?: string;\n /** The mark beside the brand name. Sized by the caller. */\n mark?: React.ReactNode;\n tagline?: string;\n description?: React.ReactNode;\n copyright?: React.ReactNode;\n nav?: readonly FooterLinkGroup[];\n offices?: readonly FooterOffice[];\n officesHeading?: string;\n legalLinks?: readonly FooterLink[];\n /** The newsletter form; the newsletter column renders only when present. */\n newsletter?: React.ReactNode;\n newsletterHeading?: string;\n newsletterBlurb?: string;\n version?: string;\n commitHref?: string;\n linkComponent?: React.ElementType;\n children?: React.ReactNode;\n}\n\nconst HEADING = FOOTER_HEADING;\n\n/**\n * A 12-col footer grid — brand 3 | sitemap groups 2 each | offices 3 |\n * newsletter 2 — with the legal links and copyright line. On mobile the sitemap\n * columns sit side by side. The kit brings no content of its own: every string,\n * the mark and the lock-up are the caller's, and each column renders only when\n * it has something in it. `children` render right after the `<footer>` tag —\n * the slot for app-side extras like a build-version easter egg.\n *\n * The lock-up is either composed from `mark` + `brand` + `tagline`, or handed\n * over whole as `lockup` when a brand's arrangement is not that one.\n *\n * `linkComponent` lets Next hosts pass `next/link`; everyone else gets `<a>`.\n */\nexport function Footer({\n lockup,\n brand,\n mark,\n tagline,\n description,\n copyright,\n nav = [],\n offices = [],\n officesHeading = copy.FOOTER_OFFICES,\n legalLinks = [],\n newsletter,\n newsletterHeading = copy.FOOTER_NEWSLETTER,\n newsletterBlurb = \"\",\n version,\n commitHref,\n linkComponent,\n children,\n}: FooterProps) {\n const L = linkComponent ?? \"a\";\n const composed = brand !== undefined || mark !== undefined;\n const hasBrandColumn = lockup !== undefined || composed || description !== undefined || legalLinks.length > 0;\n\n return (\n <footer data-slot=\"footer\" className={FOOTER_ROOT}>\n {children}\n <Container>\n <div className={FOOTER_GRID}>\n {hasBrandColumn && (\n <div className={FOOTER_WIDE_COLUMN}>\n {lockup !== undefined ? (\n <div data-slot=\"footer-lockup\" className=\"mb-6\">{lockup}</div>\n ) : (\n composed && <FooterLockup brand={brand} mark={mark} tagline={tagline} />\n )}\n {description !== undefined && (\n <p className={FOOTER_DESCRIPTION}>\n {description}\n </p>\n )}\n {legalLinks.length > 0 && (\n <div className={FOOTER_LEGAL_LINKS}>\n {legalLinks.map((link, i) => (\n <React.Fragment key={link.label}>\n {i > 0 && <span className={FOOTER_LEGAL_SEPARATOR}>|</span>}\n <L href={link.href} className={FOOTER_LEGAL_LINK}>\n {link.label}\n </L>\n </React.Fragment>\n ))}\n </div>\n )}\n </div>\n )}\n\n {nav.map((group) => (\n <nav key={group.heading} aria-label={`Footer ${group.heading} links`} className={FOOTER_NAV_COLUMN}>\n <h4 className={HEADING}>{group.heading}</h4>\n <ul className={FOOTER_NAV_LIST}>\n {group.links.map((link) => (\n <li key={link.href}>\n <L\n href={link.href}\n className={FOOTER_NAV_LINK}\n >\n {link.label}\n </L>\n </li>\n ))}\n </ul>\n </nav>\n ))}\n\n {offices.length > 0 && (\n <div className={FOOTER_WIDE_COLUMN}>\n <h4 className={HEADING}>{officesHeading}</h4>\n <ul className={FOOTER_OFFICES_LIST}>\n {offices.map((office) => (\n <li key={office.name}>\n <strong className={FOOTER_OFFICE_NAME}>\n {office.name}\n </strong>\n {office.address}\n </li>\n ))}\n </ul>\n </div>\n )}\n\n {newsletter && (\n <div className={FOOTER_NEWSLETTER_COLUMN}>\n <h4 className={HEADING}>{newsletterHeading}</h4>\n {/* What the source's <Tier tier=\"alt\"><Text> emits: alt body size\n + the info variant, then the mb-4 the caller adds. */}\n <p className={FOOTER_NEWSLETTER_BLURB}>\n {newsletterBlurb}\n </p>\n {newsletter}\n </div>\n )}\n </div>\n\n {(copyright !== undefined || version !== undefined) && (\n <div className={FOOTER_COPYRIGHT}>\n <p>\n {copyright}\n {version && (\n <>\n {copyright !== undefined && \" \"}\n <a href={commitHref} className={FOOTER_VERSION}>\n {version}\n </a>\n </>\n )}\n </p>\n </div>\n )}\n </Container>\n </footer>\n );\n}\n\nfunction FooterLockup({\n brand,\n mark,\n tagline,\n}: {\n brand: string | undefined;\n mark: React.ReactNode;\n tagline: string | undefined;\n}) {\n return (\n <div data-slot=\"footer-lockup\" className={FOOTER_LOCKUP}>\n {mark}\n {(brand !== undefined || tagline !== undefined) && (\n <div className={FOOTER_LOCKUP_TEXT}>\n {brand !== undefined && (\n <span className={FOOTER_BRAND}>{brand}</span>\n )}\n {tagline !== undefined && (\n <span className={FOOTER_TAGLINE}>\n {tagline}\n </span>\n )}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import { cn } from "
|
|
4
|
+
import { cn } from "../../lib/cn.js";
|
|
5
5
|
import {
|
|
6
6
|
HEADER_BALANCE_BASE,
|
|
7
7
|
HEADER_BALANCE_CHEVRON,
|
|
@@ -26,32 +26,33 @@ import {
|
|
|
26
26
|
NOTIFICATION_BELL_ITEM_TITLE_UNREAD,
|
|
27
27
|
NOTIFICATION_BELL_LIST,
|
|
28
28
|
NOTIFICATION_BELL_TRIGGER,
|
|
29
|
-
NOTIFICATION_DOT
|
|
30
|
-
|
|
31
|
-
} from "
|
|
32
|
-
import { AnimatedNumber } from "
|
|
33
|
-
import { IconButton } from "
|
|
34
|
-
import { Kbd } from "
|
|
35
|
-
import { Popover, PopoverContent, PopoverTrigger } from "
|
|
29
|
+
NOTIFICATION_DOT
|
|
30
|
+
} from "../../generated/header-controls.js";
|
|
31
|
+
import { toneClasses } from "../../generated/tone.js";
|
|
32
|
+
import { AnimatedNumber } from "../derived/animated-number.js";
|
|
33
|
+
import { IconButton } from "../derived/icon-button.js";
|
|
34
|
+
import { Kbd } from "../atomic/kbd.js";
|
|
35
|
+
import { Popover, PopoverContent, PopoverTrigger } from "../atomic/popover.js";
|
|
36
36
|
import { Settled } from "./settled.js";
|
|
37
|
-
import { Skeleton } from "
|
|
38
|
-
import { UiIcon } from "
|
|
37
|
+
import { Skeleton } from "../atomic/skeleton.js";
|
|
38
|
+
import { UiIcon } from "../../icons/ui-icons.js";
|
|
39
|
+
import * as copy from "../../generated/copy.js";
|
|
39
40
|
const USD = { style: "currency", currency: "USD" };
|
|
40
41
|
function HeaderBalance({
|
|
41
|
-
label =
|
|
42
|
+
label = copy.HEADER_BALANCE,
|
|
42
43
|
value,
|
|
43
44
|
amount,
|
|
44
45
|
format = USD,
|
|
45
46
|
locales,
|
|
46
47
|
loading = false,
|
|
47
48
|
icon,
|
|
48
|
-
tone = "
|
|
49
|
+
tone = "neutral",
|
|
49
50
|
onClick,
|
|
50
51
|
disabled,
|
|
51
52
|
className,
|
|
52
53
|
...props
|
|
53
54
|
}) {
|
|
54
|
-
const face = cn(HEADER_BALANCE_BASE, onClick && HEADER_BALANCE_INTERACTIVE, className);
|
|
55
|
+
const face = cn(HEADER_BALANCE_BASE, toneClasses[tone], onClick && HEADER_BALANCE_INTERACTIVE, className);
|
|
55
56
|
const body = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
56
57
|
icon,
|
|
57
58
|
/* @__PURE__ */ jsx("span", { className: HEADER_BALANCE_LABEL, children: label }),
|
|
@@ -61,7 +62,7 @@ function HeaderBalance({
|
|
|
61
62
|
loading,
|
|
62
63
|
skeleton: /* @__PURE__ */ jsx(Skeleton, { className: HEADER_BALANCE_SKELETON }),
|
|
63
64
|
className: HEADER_BALANCE_SETTLED,
|
|
64
|
-
children: /* @__PURE__ */ jsx("span", { className:
|
|
65
|
+
children: /* @__PURE__ */ jsx("span", { className: HEADER_BALANCE_FIGURE, children: amount === void 0 ? value : /* @__PURE__ */ jsx(AnimatedNumber, { value: amount, format, locales }) })
|
|
65
66
|
}
|
|
66
67
|
),
|
|
67
68
|
onClick && /* @__PURE__ */ jsx(UiIcon, { name: "chevron-down", className: HEADER_BALANCE_CHEVRON })
|
|
@@ -73,7 +74,7 @@ function HeaderBalance({
|
|
|
73
74
|
}
|
|
74
75
|
function NotificationBell({ notifications, onSelect, labels = {}, footer, className }) {
|
|
75
76
|
const unread = notifications.filter((n) => n.unread).length;
|
|
76
|
-
const name = labels.trigger ??
|
|
77
|
+
const name = labels.trigger ?? copy.NOTIFICATION_BELL_TRIGGER;
|
|
77
78
|
const spoken = unread > 0 ? `${name}, ${(labels.unread ?? ((n) => `${n} unread`))(unread)}` : name;
|
|
78
79
|
return /* @__PURE__ */ jsxs(Popover, { children: [
|
|
79
80
|
/* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(IconButton, { "aria-label": spoken, variant: "outline", className: cn(NOTIFICATION_BELL_TRIGGER, className), children: [
|
|
@@ -81,7 +82,7 @@ function NotificationBell({ notifications, onSelect, labels = {}, footer, classN
|
|
|
81
82
|
unread > 0 && /* @__PURE__ */ jsx("span", { "data-slot": "notification-dot", "aria-hidden": true, className: NOTIFICATION_DOT })
|
|
82
83
|
] }) }),
|
|
83
84
|
/* @__PURE__ */ jsxs(PopoverContent, { align: "end", className: NOTIFICATION_BELL_CONTENT, children: [
|
|
84
|
-
notifications.length === 0 ? /* @__PURE__ */ jsx("p", { className: NOTIFICATION_BELL_EMPTY, children: labels.empty ??
|
|
85
|
+
notifications.length === 0 ? /* @__PURE__ */ jsx("p", { className: NOTIFICATION_BELL_EMPTY, children: labels.empty ?? copy.NOTIFICATION_BELL_EMPTY }) : /* @__PURE__ */ jsx("ul", { className: NOTIFICATION_BELL_LIST, children: notifications.map((n) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
|
|
85
86
|
"button",
|
|
86
87
|
{
|
|
87
88
|
type: "button",
|
|
@@ -111,21 +112,21 @@ function NotificationBell({ notifications, onSelect, labels = {}, footer, classN
|
|
|
111
112
|
] });
|
|
112
113
|
}
|
|
113
114
|
const APPLE = /Mac|iPhone|iPad|iPod/;
|
|
114
|
-
function
|
|
115
|
-
const [
|
|
115
|
+
function useAppleKeys() {
|
|
116
|
+
const [apple, setApple] = React.useState(false);
|
|
116
117
|
React.useEffect(() => {
|
|
117
118
|
const nav = navigator;
|
|
118
|
-
|
|
119
|
+
setApple(APPLE.test(nav.userAgentData?.platform ?? nav.platform ?? ""));
|
|
119
120
|
}, []);
|
|
120
|
-
return
|
|
121
|
+
return apple;
|
|
121
122
|
}
|
|
122
123
|
function HeaderSearch({
|
|
123
|
-
placeholder =
|
|
124
|
+
placeholder = copy.HEADER_SEARCH,
|
|
124
125
|
shortcut,
|
|
125
126
|
className,
|
|
126
127
|
...props
|
|
127
128
|
}) {
|
|
128
|
-
const platformShortcut =
|
|
129
|
+
const platformShortcut = useAppleKeys() ? copy.COMMAND_K : copy.CTRL_K;
|
|
129
130
|
return /* @__PURE__ */ jsxs(
|
|
130
131
|
"button",
|
|
131
132
|
{
|
|
@@ -144,6 +145,7 @@ function HeaderSearch({
|
|
|
144
145
|
export {
|
|
145
146
|
HeaderBalance,
|
|
146
147
|
HeaderSearch,
|
|
147
|
-
NotificationBell
|
|
148
|
+
NotificationBell,
|
|
149
|
+
useAppleKeys
|
|
148
150
|
};
|
|
149
151
|
//# sourceMappingURL=header-controls.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/shell/header-controls.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../../lib/cn\";\nimport {\n HEADER_BALANCE_BASE,\n HEADER_BALANCE_CHEVRON,\n HEADER_BALANCE_FIGURE,\n HEADER_BALANCE_INTERACTIVE,\n HEADER_BALANCE_LABEL,\n HEADER_BALANCE_SETTLED,\n HEADER_BALANCE_SKELETON,\n HEADER_SEARCH_BASE,\n HEADER_SEARCH_PLACEHOLDER,\n NOTIFICATION_BELL_CONTENT,\n NOTIFICATION_BELL_EMPTY,\n NOTIFICATION_BELL_FOOTER,\n NOTIFICATION_BELL_ITEM,\n NOTIFICATION_BELL_ITEM_BODY,\n NOTIFICATION_BELL_ITEM_DESCRIPTION,\n NOTIFICATION_BELL_ITEM_DOT,\n NOTIFICATION_BELL_ITEM_DOT_READ,\n NOTIFICATION_BELL_ITEM_DOT_UNREAD,\n NOTIFICATION_BELL_ITEM_TIME,\n NOTIFICATION_BELL_ITEM_TITLE,\n NOTIFICATION_BELL_ITEM_TITLE_UNREAD,\n NOTIFICATION_BELL_LIST,\n NOTIFICATION_BELL_TRIGGER,\n NOTIFICATION_DOT,\n} from \"../../generated/header-controls\";\nimport { toneClasses, type Tone } from \"../../generated/tone\";\nimport { AnimatedNumber } from \"../derived/animated-number\";\nimport { IconButton } from \"../derived/icon-button\";\nimport { Kbd } from \"../atomic/kbd\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"../atomic/popover\";\nimport { Settled } from \"./settled\";\nimport { Skeleton } from \"../atomic/skeleton\";\nimport { UiIcon } from \"../../icons/ui-icons\";\nimport * as copy from \"../../generated/copy\";\n\nexport interface HeaderBalanceProps extends Omit<React.HTMLAttributes<HTMLElement>, \"children\"> {\n /** Only meaningful with `onClick`. */\n disabled?: boolean;\n label?: React.ReactNode;\n /** The figure, already formatted: \"$0.00\". Ignored when `amount` is given. */\n value?: React.ReactNode;\n /** The figure as a number: it rolls to each new amount, formatted by `format` and `locales`. */\n amount?: number;\n /** Formatting for `amount`. Default: USD currency. */\n format?: Intl.NumberFormatOptions;\n locales?: Intl.LocalesArgument;\n /** True until the balance is known; the figure then reveals in place of the skeleton. */\n loading?: boolean;\n /** A glyph before the label. */\n icon?: React.ReactNode;\n /** `warn` colours the figure as a low-funds signal. */\n tone?: Tone;\n}\n\nconst USD: Intl.NumberFormatOptions = { style: \"currency\", currency: \"USD\" };\n\n/** \"Balance: $0.00\" in the header; a button only when given `onClick`. */\nexport function HeaderBalance({\n label = copy.HEADER_BALANCE,\n value,\n amount,\n format = USD,\n locales,\n loading = false,\n icon,\n tone = \"neutral\",\n onClick,\n disabled,\n className,\n ...props\n}: HeaderBalanceProps) {\n const face = cn(HEADER_BALANCE_BASE, toneClasses[tone], onClick && HEADER_BALANCE_INTERACTIVE, className);\n const body = (\n <>\n {icon}\n <span className={HEADER_BALANCE_LABEL}>{label}</span>\n <Settled\n loading={loading}\n skeleton={<Skeleton className={HEADER_BALANCE_SKELETON} />}\n className={HEADER_BALANCE_SETTLED}\n >\n <span className={HEADER_BALANCE_FIGURE}>\n {amount === undefined ? value : <AnimatedNumber value={amount} format={format} locales={locales} />}\n </span>\n </Settled>\n {onClick && <UiIcon name=\"chevron-down\" className={HEADER_BALANCE_CHEVRON} />}\n </>\n );\n if (!onClick) {\n return (\n <div data-slot=\"header-balance\" className={face} {...props}>\n {body}\n </div>\n );\n }\n return (\n <button type=\"button\" data-slot=\"header-balance\" className={face} onClick={onClick} disabled={disabled} {...props}>\n {body}\n </button>\n );\n}\n\nexport interface HeaderNotification {\n id: string;\n title: React.ReactNode;\n description?: React.ReactNode;\n /** Already formatted: \"2m ago\". */\n time?: React.ReactNode;\n unread?: boolean;\n}\n\nexport interface NotificationBellLabels {\n /** The bell's name. Default `\"Notifications\"`. */\n trigger?: string;\n /** Spoken after the name when something is unread; gets the count. Default `\"{n} unread\"`. */\n unread?: (count: number) => string;\n /** Default `\"No notifications\"`. */\n empty?: string;\n}\n\nexport interface NotificationBellProps {\n notifications: readonly HeaderNotification[];\n onSelect?: (id: string) => void;\n labels?: NotificationBellLabels;\n /** Under the list: \"View all\". */\n footer?: React.ReactNode;\n className?: string;\n}\n\n/** The bell with an unread dot; the list opens in a popover. */\nexport function NotificationBell({ notifications, onSelect, labels = {}, footer, className }: NotificationBellProps) {\n const unread = notifications.filter((n) => n.unread).length;\n const name = labels.trigger ?? copy.NOTIFICATION_BELL_TRIGGER;\n const spoken = unread > 0 ? `${name}, ${(labels.unread ?? ((n) => `${n} unread`))(unread)}` : name;\n return (\n <Popover>\n <PopoverTrigger asChild>\n <IconButton aria-label={spoken} variant=\"outline\" className={cn(NOTIFICATION_BELL_TRIGGER, className)}>\n <UiIcon name=\"bell\" />\n {unread > 0 && (\n <span data-slot=\"notification-dot\" aria-hidden className={NOTIFICATION_DOT} />\n )}\n </IconButton>\n </PopoverTrigger>\n <PopoverContent align=\"end\" className={NOTIFICATION_BELL_CONTENT}>\n {notifications.length === 0 ? (\n <p className={NOTIFICATION_BELL_EMPTY}>{labels.empty ?? copy.NOTIFICATION_BELL_EMPTY}</p>\n ) : (\n <ul className={NOTIFICATION_BELL_LIST}>\n {notifications.map((n) => (\n <li key={n.id}>\n <button\n type=\"button\"\n onClick={() => onSelect?.(n.id)}\n className={NOTIFICATION_BELL_ITEM}\n >\n <span\n aria-hidden\n className={cn(\n NOTIFICATION_BELL_ITEM_DOT,\n n.unread ? NOTIFICATION_BELL_ITEM_DOT_UNREAD : NOTIFICATION_BELL_ITEM_DOT_READ,\n )}\n />\n <span className={NOTIFICATION_BELL_ITEM_BODY}>\n <span className={cn(NOTIFICATION_BELL_ITEM_TITLE, n.unread && NOTIFICATION_BELL_ITEM_TITLE_UNREAD)}>{n.title}</span>\n {n.description != null && <span className={NOTIFICATION_BELL_ITEM_DESCRIPTION}>{n.description}</span>}\n </span>\n {n.time != null && <span className={NOTIFICATION_BELL_ITEM_TIME}>{n.time}</span>}\n </button>\n </li>\n ))}\n </ul>\n )}\n {footer != null && <div className={NOTIFICATION_BELL_FOOTER}>{footer}</div>}\n </PopoverContent>\n </Popover>\n );\n}\n\nexport interface HeaderSearchProps extends React.ComponentProps<\"button\"> {\n placeholder?: string;\n /** The shortcut hint at the end. Default: `⌘K` on Apple platforms, `Ctrl K` elsewhere. */\n shortcut?: string;\n}\n\nconst APPLE = /Mac|iPhone|iPad|iPod/;\n\n/** Whether ⌘ is the modifier: read after mount, so the server render and the first client render agree. */\nexport function useAppleKeys(): boolean {\n const [apple, setApple] = React.useState(false);\n React.useEffect(() => {\n const nav = navigator as Navigator & { userAgentData?: { platform?: string } };\n setApple(APPLE.test(nav.userAgentData?.platform ?? nav.platform ?? \"\"));\n }, []);\n return apple;\n}\n\n/** A button dressed as the search field; it opens the palette rather than taking input. */\nexport function HeaderSearch({\n placeholder = copy.HEADER_SEARCH,\n shortcut,\n className,\n ...props\n}: HeaderSearchProps) {\n const platformShortcut = useAppleKeys() ? copy.COMMAND_K : copy.CTRL_K;\n return (\n <button\n type=\"button\"\n data-slot=\"header-search\"\n className={cn(HEADER_SEARCH_BASE, className)}\n {...props}\n >\n <UiIcon name=\"search\" />\n <span className={HEADER_SEARCH_PLACEHOLDER}>{placeholder}</span>\n <Kbd>{shortcut ?? platformShortcut}</Kbd>\n </button>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "../../lib/cn.js";
|
|
5
|
+
import { toneClasses } from "../../generated/tone.js";
|
|
6
|
+
import { iconShapes } from "../../icons/icon-shapes.js";
|
|
7
|
+
import { Button } from "../atomic/button.js";
|
|
8
|
+
import { Checkbox } from "../atomic/checkbox.js";
|
|
9
|
+
import { Kbd } from "../atomic/kbd.js";
|
|
10
|
+
import { Popover, PopoverContent, PopoverTrigger } from "../atomic/popover.js";
|
|
11
|
+
import { useAppleKeys } from "./header-controls.js";
|
|
12
|
+
import { TriangleAlertIcon } from "./shell-icons.js";
|
|
13
|
+
function parseShortcut(shortcut) {
|
|
14
|
+
const mod = shortcut.startsWith("mod+");
|
|
15
|
+
const key = mod ? shortcut.slice(4) : shortcut;
|
|
16
|
+
return { mod, key, code: /\d/.test(key) ? `Digit${key}` : `Key${key}` };
|
|
17
|
+
}
|
|
18
|
+
const STORAGE_KEY = "ev-hotbar";
|
|
19
|
+
const NO_OVERRIDES = {};
|
|
20
|
+
let snapshot = { raw: null, value: NO_OVERRIDES };
|
|
21
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
22
|
+
function readOverrides() {
|
|
23
|
+
const raw = localStorage.getItem(STORAGE_KEY);
|
|
24
|
+
if (raw === snapshot.raw) return snapshot.value;
|
|
25
|
+
const value = raw === null ? NO_OVERRIDES : JSON.parse(raw);
|
|
26
|
+
if (typeof value !== "object" || value === null || Array.isArray(value) || Object.values(value).some((v) => typeof v !== "boolean"))
|
|
27
|
+
throw new Error(`localStorage["${STORAGE_KEY}"] is not an {actionId: boolean} map: ${raw}`);
|
|
28
|
+
snapshot = { raw, value };
|
|
29
|
+
return snapshot.value;
|
|
30
|
+
}
|
|
31
|
+
function writeOverrides(next) {
|
|
32
|
+
if (Object.keys(next).length === 0) localStorage.removeItem(STORAGE_KEY);
|
|
33
|
+
else localStorage.setItem(STORAGE_KEY, JSON.stringify(next));
|
|
34
|
+
listeners.forEach((l) => l());
|
|
35
|
+
}
|
|
36
|
+
function subscribeOverrides(listener) {
|
|
37
|
+
const onStorage = (e) => {
|
|
38
|
+
if (e.key === STORAGE_KEY || e.key === null) listener();
|
|
39
|
+
};
|
|
40
|
+
listeners.add(listener);
|
|
41
|
+
window.addEventListener("storage", onStorage);
|
|
42
|
+
return () => {
|
|
43
|
+
listeners.delete(listener);
|
|
44
|
+
window.removeEventListener("storage", onStorage);
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const EDITABLE = "input, textarea, select, [contenteditable]:not([contenteditable='false'])";
|
|
48
|
+
function Hotbar({ actions, status, takeover, labels = {}, className, ...props }) {
|
|
49
|
+
const parsed = React.useMemo(() => {
|
|
50
|
+
const ids = /* @__PURE__ */ new Set();
|
|
51
|
+
const codes = /* @__PURE__ */ new Set();
|
|
52
|
+
return actions.map((action) => {
|
|
53
|
+
if (ids.has(action.id)) throw new Error(`Hotbar: two actions have the id "${action.id}"`);
|
|
54
|
+
ids.add(action.id);
|
|
55
|
+
if (action.shortcut === void 0) return { action, shortcut: void 0 };
|
|
56
|
+
const shortcut = parseShortcut(action.shortcut);
|
|
57
|
+
const chord = `${shortcut.mod}:${shortcut.code}`;
|
|
58
|
+
if (codes.has(chord)) throw new Error(`Hotbar: two actions are bound to ${action.shortcut}`);
|
|
59
|
+
codes.add(chord);
|
|
60
|
+
return { action, shortcut };
|
|
61
|
+
});
|
|
62
|
+
}, [actions]);
|
|
63
|
+
const latest = React.useRef(parsed);
|
|
64
|
+
React.useEffect(() => {
|
|
65
|
+
latest.current = parsed;
|
|
66
|
+
});
|
|
67
|
+
React.useEffect(() => {
|
|
68
|
+
const onKeyDown = (e) => {
|
|
69
|
+
if (e.defaultPrevented || e.altKey || e.shiftKey) return;
|
|
70
|
+
const mod = e.metaKey || e.ctrlKey;
|
|
71
|
+
const hit = latest.current.find(({ shortcut }) => shortcut?.code === e.code && shortcut.mod === mod);
|
|
72
|
+
if (hit === void 0) return;
|
|
73
|
+
if (!mod && e.target instanceof Element && e.target.closest(EDITABLE)) return;
|
|
74
|
+
e.preventDefault();
|
|
75
|
+
if (!e.repeat) hit.action.onSelect();
|
|
76
|
+
};
|
|
77
|
+
document.addEventListener("keydown", onKeyDown);
|
|
78
|
+
return () => document.removeEventListener("keydown", onKeyDown);
|
|
79
|
+
}, []);
|
|
80
|
+
const overrides = React.useSyncExternalStore(subscribeOverrides, readOverrides, () => NO_OVERRIDES);
|
|
81
|
+
const isPinned = (action) => overrides[action.id] ?? action.pinned ?? true;
|
|
82
|
+
const setPinned = (action, pinned) => {
|
|
83
|
+
const next = { ...overrides };
|
|
84
|
+
delete next[action.id];
|
|
85
|
+
if (pinned !== (action.pinned ?? true)) next[action.id] = pinned;
|
|
86
|
+
writeOverrides(next);
|
|
87
|
+
};
|
|
88
|
+
const apple = useAppleKeys();
|
|
89
|
+
const kbd = (s) => s.mod ? apple ? `\u2318${s.key}` : `Ctrl ${s.key}` : s.key;
|
|
90
|
+
const [customizing, setCustomizing] = React.useState(false);
|
|
91
|
+
return /* @__PURE__ */ jsx(
|
|
92
|
+
"section",
|
|
93
|
+
{
|
|
94
|
+
"data-slot": "hotbar",
|
|
95
|
+
"aria-label": labels.region ?? "Hotbar",
|
|
96
|
+
className: cn(
|
|
97
|
+
"@container border-t border-border-subtle bg-background",
|
|
98
|
+
takeover && [toneClasses[takeover.tone], "border-(color:--tone-edge) bg-[image:linear-gradient(var(--tone-wash),var(--tone-wash))]"],
|
|
99
|
+
className
|
|
100
|
+
),
|
|
101
|
+
...props,
|
|
102
|
+
children: /* @__PURE__ */ jsxs("div", { className: "flex h-(--control-h-touch) items-center gap-2 overflow-x-auto px-3 @5xl:h-(--control-h) @5xl:gap-1.5 @5xl:overflow-visible", children: [
|
|
103
|
+
takeover ? /* @__PURE__ */ jsxs("div", { "data-slot": "hotbar-takeover", role: "alert", className: "flex min-w-0 items-center gap-2 text-body-s font-medium text-(color:--tone-ink)", children: [
|
|
104
|
+
/* @__PURE__ */ jsx(TriangleAlertIcon, { className: "size-4 shrink-0" }),
|
|
105
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: takeover.message }),
|
|
106
|
+
takeover.action
|
|
107
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
108
|
+
parsed.filter(({ action }) => isPinned(action)).map(({ action, shortcut }) => /* @__PURE__ */ jsxs(
|
|
109
|
+
"button",
|
|
110
|
+
{
|
|
111
|
+
type: "button",
|
|
112
|
+
"data-slot": "hotbar-action",
|
|
113
|
+
"aria-pressed": action.pressed,
|
|
114
|
+
onClick: action.onSelect,
|
|
115
|
+
className: cn(ITEM, action.pressed && PRESSED),
|
|
116
|
+
children: [
|
|
117
|
+
/* @__PURE__ */ jsx(action.icon, { className: "size-4 shrink-0" }),
|
|
118
|
+
action.label,
|
|
119
|
+
shortcut && /* @__PURE__ */ jsx(Kbd, { className: "hidden @5xl:inline-flex", children: kbd(shortcut) })
|
|
120
|
+
]
|
|
121
|
+
},
|
|
122
|
+
action.id
|
|
123
|
+
)),
|
|
124
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "hidden h-4 w-px shrink-0 bg-border @5xl:block" }),
|
|
125
|
+
/* @__PURE__ */ jsxs(Popover, { open: customizing, onOpenChange: setCustomizing, children: [
|
|
126
|
+
/* @__PURE__ */ jsxs(PopoverTrigger, { type: "button", "data-slot": "hotbar-customize", className: cn(ITEM, "@5xl:w-(--control-h-2xs) @5xl:justify-center @5xl:px-0"), children: [
|
|
127
|
+
/* @__PURE__ */ jsx("svg", { className: "size-4 shrink-0", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: iconShapes("sliders-horizontal") }),
|
|
128
|
+
/* @__PURE__ */ jsx("span", { className: "@5xl:sr-only", children: labels.customize ?? "Customize" })
|
|
129
|
+
] }),
|
|
130
|
+
/* @__PURE__ */ jsxs(PopoverContent, { side: "top", align: "start", className: "w-80 p-0", children: [
|
|
131
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-0.5 px-3 pt-3 pb-2", children: [
|
|
132
|
+
/* @__PURE__ */ jsx("p", { className: "text-body-l font-medium", children: labels.title ?? "Customize hotbar" }),
|
|
133
|
+
/* @__PURE__ */ jsx("p", { className: "text-caption text-ink-soft", children: labels.description ?? "Pinned actions show in the bar and in the mobile chip row." })
|
|
134
|
+
] }),
|
|
135
|
+
/* @__PURE__ */ jsx("ul", { className: "flex flex-col px-1 pb-1", children: parsed.map(({ action, shortcut }) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("label", { className: "flex h-(--control-h-sm) cursor-pointer items-center gap-2.5 rounded-control px-2 text-body text-ink-mid hover:bg-hover", children: [
|
|
136
|
+
/* @__PURE__ */ jsx(Checkbox, { checked: isPinned(action), onCheckedChange: (pinned) => setPinned(action, pinned) }),
|
|
137
|
+
/* @__PURE__ */ jsx(action.icon, { className: "size-4 shrink-0" }),
|
|
138
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: action.label }),
|
|
139
|
+
shortcut && /* @__PURE__ */ jsx(Kbd, { children: kbd(shortcut) })
|
|
140
|
+
] }) }, action.id)) }),
|
|
141
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-t border-border px-3 py-2", children: [
|
|
142
|
+
/* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: () => writeOverrides(NO_OVERRIDES), children: labels.reset ?? "Reset to default" }),
|
|
143
|
+
/* @__PURE__ */ jsx(Button, { size: "sm", onClick: () => setCustomizing(false), children: labels.done ?? "Done" })
|
|
144
|
+
] })
|
|
145
|
+
] })
|
|
146
|
+
] })
|
|
147
|
+
] }),
|
|
148
|
+
status != null && /* @__PURE__ */ jsx("div", { "data-slot": "hotbar-status", className: "ml-auto hidden shrink-0 items-center @5xl:flex", children: status })
|
|
149
|
+
] })
|
|
150
|
+
}
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
const ITEM = "inline-flex h-(--control-h-xs) shrink-0 items-center gap-1.5 whitespace-nowrap rounded-chip border border-border bg-secondary pr-3 pl-2 text-body-s text-ink-mid transition-colors outline-none hover:text-ink focus-visible:ring-2 focus-visible:ring-ring @5xl:h-(--control-h-2xs) @5xl:rounded-control @5xl:border-transparent @5xl:bg-transparent @5xl:px-2 @5xl:hover:bg-hover";
|
|
154
|
+
const PRESSED = `${toneClasses.primary} border-(color:--tone-edge) bg-(color:--tone-fill) text-(color:--tone-ink) hover:text-(color:--tone-ink) @5xl:border-(color:--tone-edge) @5xl:bg-(color:--tone-fill)`;
|
|
155
|
+
export {
|
|
156
|
+
Hotbar
|
|
157
|
+
};
|
|
158
|
+
//# sourceMappingURL=hotbar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/shell/hotbar.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../../lib/cn\";\nimport { toneClasses } from \"../../generated/tone\";\nimport { iconShapes } from \"../../icons/icon-shapes\";\nimport { Button } from \"../atomic/button\";\nimport { Checkbox } from \"../atomic/checkbox\";\nimport { Kbd } from \"../atomic/kbd\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"../atomic/popover\";\nimport { useAppleKeys } from \"./header-controls\";\nimport { TriangleAlertIcon } from \"./shell-icons\";\n\ntype Letter = \"A\" | \"B\" | \"C\" | \"D\" | \"E\" | \"F\" | \"G\" | \"H\" | \"I\" | \"J\" | \"K\" | \"L\" | \"M\" | \"N\" | \"O\" | \"P\" | \"Q\" | \"R\" | \"S\" | \"T\" | \"U\" | \"V\" | \"W\" | \"X\" | \"Y\" | \"Z\";\ntype Digit = \"0\" | \"1\" | \"2\" | \"3\" | \"4\" | \"5\" | \"6\" | \"7\" | \"8\" | \"9\";\ntype HotbarKey = Letter | Digit;\n\nexport interface HotbarAction {\n id: string;\n label: string;\n icon: React.ComponentType<{ className?: string }>;\n /** `\"N\"` alone, or `\"mod+K\"` — ⌘ on Apple, Ctrl elsewhere. Shown in a `Kbd` and bound while the bar is mounted, pinned or not. */\n shortcut?: HotbarKey | `mod+${HotbarKey}`;\n onSelect: () => void;\n /** A toggle that is on — the agent connected, a map layer shown. The app owns the state. */\n pressed?: boolean;\n /** `false` lists it only under Customize until the user pins it. Default pinned. */\n pinned?: boolean;\n}\n\nexport interface HotbarLabels {\n /** Accessible name of the strip. Default `\"Hotbar\"`. */\n region?: string;\n /** The customize button. Default `\"Customize\"`. */\n customize?: string;\n /** Default `\"Customize hotbar\"`. */\n title?: string;\n /** Default `\"Pinned actions show in the bar and in the mobile chip row.\"`. */\n description?: string;\n /** Default `\"Reset to default\"`. */\n reset?: string;\n /** Default `\"Done\"`. */\n done?: string;\n}\n\nexport interface HotbarProps extends Omit<React.ComponentProps<\"section\">, \"children\"> {\n /** Every action the app offers, in bar order. */\n actions: readonly HotbarAction[];\n /** `StatusItem`s at the end of the wide strip. */\n status?: React.ReactNode;\n /** An urgent level: replaces the actions with its message until the app clears it. */\n takeover?: { tone: \"warn\" | \"error\"; message: React.ReactNode; action?: React.ReactNode };\n labels?: HotbarLabels;\n}\n\ninterface Shortcut {\n mod: boolean;\n code: string;\n key: HotbarKey;\n}\n\nfunction parseShortcut(shortcut: NonNullable<HotbarAction[\"shortcut\"]>): Shortcut {\n const mod = shortcut.startsWith(\"mod+\");\n const key = (mod ? shortcut.slice(4) : shortcut) as HotbarKey;\n return { mod, key, code: /\\d/.test(key) ? `Digit${key}` : `Key${key}` };\n}\n\nconst STORAGE_KEY = \"ev-hotbar\";\n/** The user's pins that differ from an action's default, by action id. */\ntype Overrides = Readonly<Record<string, boolean>>;\nconst NO_OVERRIDES: Overrides = {};\nlet snapshot: { raw: string | null; value: Overrides } = { raw: null, value: NO_OVERRIDES };\nconst listeners = new Set<() => void>();\n\nfunction readOverrides(): Overrides {\n const raw = localStorage.getItem(STORAGE_KEY);\n if (raw === snapshot.raw) return snapshot.value;\n const value: unknown = raw === null ? NO_OVERRIDES : JSON.parse(raw);\n if (typeof value !== \"object\" || value === null || Array.isArray(value) || Object.values(value).some(v => typeof v !== \"boolean\"))\n throw new Error(`localStorage[\"${STORAGE_KEY}\"] is not an {actionId: boolean} map: ${raw}`);\n snapshot = { raw, value: value as Overrides };\n return snapshot.value;\n}\n\nfunction writeOverrides(next: Overrides) {\n if (Object.keys(next).length === 0) localStorage.removeItem(STORAGE_KEY);\n else localStorage.setItem(STORAGE_KEY, JSON.stringify(next));\n listeners.forEach(l => l());\n}\n\nfunction subscribeOverrides(listener: () => void) {\n const onStorage = (e: StorageEvent) => {\n if (e.key === STORAGE_KEY || e.key === null) listener();\n };\n listeners.add(listener);\n window.addEventListener(\"storage\", onStorage);\n return () => {\n listeners.delete(listener);\n window.removeEventListener(\"storage\", onStorage);\n };\n}\n\nconst EDITABLE = \"input, textarea, select, [contenteditable]:not([contenteditable='false'])\";\n\n/**\n * The bottom strip of a terminal-style app: pinned actions with bound shortcuts,\n * a customize popover, live status at the end. Position-agnostic — the app docks\n * it. Below a 64rem strip it turns into a sideways-scrolling chip row with no\n * status. Which actions are pinned is the user's, kept in this browser.\n */\nexport function Hotbar({ actions, status, takeover, labels = {}, className, ...props }: HotbarProps) {\n const parsed = React.useMemo(() => {\n const ids = new Set<string>();\n const codes = new Set<string>();\n return actions.map(action => {\n if (ids.has(action.id)) throw new Error(`Hotbar: two actions have the id \"${action.id}\"`);\n ids.add(action.id);\n if (action.shortcut === undefined) return { action, shortcut: undefined };\n const shortcut = parseShortcut(action.shortcut);\n const chord = `${shortcut.mod}:${shortcut.code}`;\n if (codes.has(chord)) throw new Error(`Hotbar: two actions are bound to ${action.shortcut}`);\n codes.add(chord);\n return { action, shortcut };\n });\n }, [actions]);\n\n const latest = React.useRef(parsed);\n React.useEffect(() => {\n latest.current = parsed;\n });\n React.useEffect(() => {\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.defaultPrevented || e.altKey || e.shiftKey) return;\n const mod = e.metaKey || e.ctrlKey;\n const hit = latest.current.find(({ shortcut }) => shortcut?.code === e.code && shortcut.mod === mod);\n if (hit === undefined) return;\n if (!mod && e.target instanceof Element && e.target.closest(EDITABLE)) return;\n e.preventDefault();\n // A held key would fire the action once per repeat.\n if (!e.repeat) hit.action.onSelect();\n };\n document.addEventListener(\"keydown\", onKeyDown);\n return () => document.removeEventListener(\"keydown\", onKeyDown);\n }, []);\n\n const overrides = React.useSyncExternalStore(subscribeOverrides, readOverrides, () => NO_OVERRIDES);\n const isPinned = (action: HotbarAction) => overrides[action.id] ?? action.pinned ?? true;\n const setPinned = (action: HotbarAction, pinned: boolean) => {\n const next: Record<string, boolean> = { ...overrides };\n delete next[action.id];\n if (pinned !== (action.pinned ?? true)) next[action.id] = pinned;\n writeOverrides(next);\n };\n\n const apple = useAppleKeys();\n const kbd = (s: Shortcut) => (s.mod ? (apple ? `⌘${s.key}` : `Ctrl ${s.key}`) : s.key);\n const [customizing, setCustomizing] = React.useState(false);\n\n return (\n <section\n data-slot=\"hotbar\"\n aria-label={labels.region ?? \"Hotbar\"}\n className={cn(\n \"@container border-t border-border-subtle bg-background\",\n takeover && [toneClasses[takeover.tone], \"border-(color:--tone-edge) bg-[image:linear-gradient(var(--tone-wash),var(--tone-wash))]\"],\n className,\n )}\n {...props}\n >\n <div className=\"flex h-(--control-h-touch) items-center gap-2 overflow-x-auto px-3 @5xl:h-(--control-h) @5xl:gap-1.5 @5xl:overflow-visible\">\n {takeover ? (\n <div data-slot=\"hotbar-takeover\" role=\"alert\" className=\"flex min-w-0 items-center gap-2 text-body-s font-medium text-(color:--tone-ink)\">\n <TriangleAlertIcon className=\"size-4 shrink-0\" />\n <span className=\"truncate\">{takeover.message}</span>\n {takeover.action}\n </div>\n ) : (\n <>\n {parsed.filter(({ action }) => isPinned(action)).map(({ action, shortcut }) => (\n <button\n key={action.id}\n type=\"button\"\n data-slot=\"hotbar-action\"\n aria-pressed={action.pressed}\n onClick={action.onSelect}\n className={cn(ITEM, action.pressed && PRESSED)}\n >\n <action.icon className=\"size-4 shrink-0\" />\n {action.label}\n {shortcut && <Kbd className=\"hidden @5xl:inline-flex\">{kbd(shortcut)}</Kbd>}\n </button>\n ))}\n <span aria-hidden className=\"hidden h-4 w-px shrink-0 bg-border @5xl:block\" />\n <Popover open={customizing} onOpenChange={setCustomizing}>\n <PopoverTrigger type=\"button\" data-slot=\"hotbar-customize\" className={cn(ITEM, \"@5xl:w-(--control-h-2xs) @5xl:justify-center @5xl:px-0\")}>\n <svg className=\"size-4 shrink-0\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth={2} strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n {iconShapes(\"sliders-horizontal\")}\n </svg>\n <span className=\"@5xl:sr-only\">{labels.customize ?? \"Customize\"}</span>\n </PopoverTrigger>\n <PopoverContent side=\"top\" align=\"start\" className=\"w-80 p-0\">\n <div className=\"flex flex-col gap-0.5 px-3 pt-3 pb-2\">\n <p className=\"text-body-l font-medium\">{labels.title ?? \"Customize hotbar\"}</p>\n <p className=\"text-caption text-ink-soft\">\n {labels.description ?? \"Pinned actions show in the bar and in the mobile chip row.\"}\n </p>\n </div>\n <ul className=\"flex flex-col px-1 pb-1\">\n {parsed.map(({ action, shortcut }) => (\n <li key={action.id}>\n <label className=\"flex h-(--control-h-sm) cursor-pointer items-center gap-2.5 rounded-control px-2 text-body text-ink-mid hover:bg-hover\">\n <Checkbox checked={isPinned(action)} onCheckedChange={pinned => setPinned(action, pinned)} />\n <action.icon className=\"size-4 shrink-0\" />\n <span className=\"flex-1 truncate\">{action.label}</span>\n {shortcut && <Kbd>{kbd(shortcut)}</Kbd>}\n </label>\n </li>\n ))}\n </ul>\n <div className=\"flex items-center justify-between border-t border-border px-3 py-2\">\n <Button variant=\"ghost\" size=\"sm\" onClick={() => writeOverrides(NO_OVERRIDES)}>\n {labels.reset ?? \"Reset to default\"}\n </Button>\n <Button size=\"sm\" onClick={() => setCustomizing(false)}>\n {labels.done ?? \"Done\"}\n </Button>\n </div>\n </PopoverContent>\n </Popover>\n </>\n )}\n {status != null && (\n <div data-slot=\"hotbar-status\" className=\"ml-auto hidden shrink-0 items-center @5xl:flex\">\n {status}\n </div>\n )}\n </div>\n </section>\n );\n}\n\n// A chip below the container breakpoint, a ghost row item from it up.\nconst ITEM =\n \"inline-flex h-(--control-h-xs) shrink-0 items-center gap-1.5 whitespace-nowrap rounded-chip border border-border bg-secondary pr-3 pl-2 text-body-s text-ink-mid transition-colors outline-none hover:text-ink focus-visible:ring-2 focus-visible:ring-ring \" +\n \"@5xl:h-(--control-h-2xs) @5xl:rounded-control @5xl:border-transparent @5xl:bg-transparent @5xl:px-2 @5xl:hover:bg-hover\";\nconst PRESSED = `${toneClasses.primary} border-(color:--tone-edge) bg-(color:--tone-fill) text-(color:--tone-ink) hover:text-(color:--tone-ink) @5xl:border-(color:--tone-edge) @5xl:bg-(color:--tone-fill)`;\n"],"mappings":"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","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as React from "react";
|
|
3
|
-
import { cn } from "
|
|
3
|
+
import { cn } from "../../lib/cn.js";
|
|
4
4
|
const LIST_ROWS = "flex w-full min-w-0 flex-col divide-y divide-border";
|
|
5
5
|
const listRowsVariants = {
|
|
6
6
|
plain: "",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/shell/list-rows.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../../lib/cn\";\n\n// The phone form of a table: a list of label/value rows split by hairlines.\n// A table scrolled sideways on a phone hides the trailing columns — usually the\n// date and the action, the reason the list was opened. TS-only (README\n// \"Limitations\").\n\nexport type ListRowsVariant = \"plain\" | \"card\";\n\nconst LIST_ROWS = \"flex w-full min-w-0 flex-col divide-y divide-border\";\n\n// `plain` sits in a container that already pads (a padded card, a section);\n// `card` is edge to edge in a `TableCard`, so the rows pad themselves and the\n// hairlines run the card's full width. The inset travels as a custom property,\n// as the table's cell padding does, so a row's own `px-*` still wins.\nconst listRowsVariants: Record<ListRowsVariant, string> = {\n plain: \"\",\n card: \"[--ev-list-row-x:1rem]\",\n};\n\nexport interface ListRowsProps extends React.ComponentProps<\"ul\"> {\n variant?: ListRowsVariant;\n}\n\nexport function ListRows({ variant = \"plain\", className, ...props }: ListRowsProps) {\n return (\n <ul\n data-slot=\"list-rows\"\n data-variant={variant}\n className={cn(LIST_ROWS, listRowsVariants[variant], className)}\n {...props}\n />\n );\n}\n\nconst LIST_ROW = \"flex min-w-0 items-center justify-between gap-3 px-[var(--ev-list-row-x,0px)] py-3\";\n\n// `<li value>` is an ordinal for `<ol>`; in an unordered list of rows the name\n// is far more useful for the trailing figure.\nexport interface ListRowProps extends Omit<React.ComponentProps<\"li\">, \"value\"> {\n /** The row's title. Omit it (and `value`) to lay the row out yourself. */\n label?: React.ReactNode;\n /** A caption a step down under the label. */\n description?: React.ReactNode;\n /** Trailing value, right-aligned and truncated rather than pushing the row wide. */\n value?: React.ReactNode;\n}\n\n/**\n * One row: label (and description) leading, value trailing, then `children`\n * (a badge, an action). With neither `label` nor `value` the children are the\n * whole row.\n */\nexport function ListRow({ label, description, value, className, children, ...props }: ListRowProps) {\n return (\n <li\n data-slot=\"list-row\"\n className={cn(LIST_ROW, className)}\n {...props}\n >\n {(label != null || description != null) && (\n <ListRowLabel description={description}>{label}</ListRowLabel>\n )}\n {value != null && <ListRowValue>{value}</ListRowValue>}\n {children}\n </li>\n );\n}\n\nexport interface ListRowLabelProps extends Omit<React.ComponentProps<\"div\">, \"title\"> {\n description?: React.ReactNode;\n}\n\n/** The leading block on its own, for a row you compose by hand. */\nexport function ListRowLabel({ description, className, children, ...props }: ListRowLabelProps) {\n return (\n <div\n data-slot=\"list-row-label\"\n className={cn(\"flex min-w-0 flex-1 flex-col gap-0.5\", className)}\n {...props}\n >\n <span className=\"text-sm font-medium text-ink\">{children}</span>\n {description != null && (\n <span\n data-slot=\"list-row-description\"\n className=\"text-xs leading-snug text-ink-soft\"\n >\n {description}\n </span>\n )}\n </div>\n );\n}\n\n/**\n * The trailing value. It shares the label's type step, so the muted tone and\n * the lighter weight are what keep the label reading first; digits are tabular\n * so a figure in one row lines up with the next.\n */\nexport function ListRowValue({ className, ...props }: React.ComponentProps<\"span\">) {\n return (\n <span\n data-slot=\"list-row-value\"\n className={cn(\"min-w-0 truncate text-right text-sm tabular-nums text-ink-soft\", className)}\n {...props}\n />\n );\n}\n"],"mappings":"AA2BI,cA6BA,YA7BA;AA3BJ,YAAY,WAAW;AACvB,SAAS,UAAU;AASnB,MAAM,YAAY;AAMlB,MAAM,mBAAoD;AAAA,EACxD,OAAO;AAAA,EACP,MAAM;AACR;AAMO,SAAS,SAAS,EAAE,UAAU,SAAS,WAAW,GAAG,MAAM,GAAkB;AAClF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,gBAAc;AAAA,MACd,WAAW,GAAG,WAAW,iBAAiB,OAAO,GAAG,SAAS;AAAA,MAC5D,GAAG;AAAA;AAAA,EACN;AAEJ;AAEA,MAAM,WAAW;AAkBV,SAAS,QAAQ,EAAE,OAAO,aAAa,OAAO,WAAW,UAAU,GAAG,MAAM,GAAiB;AAClG,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,UAAU,SAAS;AAAA,MAChC,GAAG;AAAA,MAEF;AAAA,kBAAS,QAAQ,eAAe,SAChC,oBAAC,gBAAa,aAA2B,iBAAM;AAAA,QAEhD,SAAS,QAAQ,oBAAC,gBAAc,iBAAM;AAAA,QACtC;AAAA;AAAA;AAAA,EACH;AAEJ;AAOO,SAAS,aAAa,EAAE,aAAa,WAAW,UAAU,GAAG,MAAM,GAAsB;AAC9F,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,wCAAwC,SAAS;AAAA,MAC9D,GAAG;AAAA,MAEJ;AAAA,4BAAC,UAAK,WAAU,gCAAgC,UAAS;AAAA,QACxD,eAAe,QACd;AAAA,UAAC;AAAA;AAAA,YACC,aAAU;AAAA,YACV,WAAU;AAAA,YAET;AAAA;AAAA,QACH;AAAA;AAAA;AAAA,EAEJ;AAEJ;AAOO,SAAS,aAAa,EAAE,WAAW,GAAG,MAAM,GAAiC;AAClF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,kEAAkE,SAAS;AAAA,MACxF,GAAG;AAAA;AAAA,EACN;AAEJ;","names":[]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
import * as React from "react";
|
|
3
|
-
import { cn } from "
|
|
3
|
+
import { cn } from "../../lib/cn.js";
|
|
4
|
+
import { LOGO_BACKGROUND, LOGO_MARK, LOGO_MARK_INSET } from "../../generated/logo.js";
|
|
4
5
|
const maskStyle = {
|
|
5
6
|
backgroundColor: "currentColor",
|
|
6
7
|
maskImage: "var(--brand-mark)",
|
|
@@ -22,7 +23,7 @@ function Logo({ className, withBackground = false }) {
|
|
|
22
23
|
"data-slot": "logo",
|
|
23
24
|
"aria-hidden": true,
|
|
24
25
|
style: maskStyle,
|
|
25
|
-
className: cn(
|
|
26
|
+
className: cn(LOGO_MARK, withBackground ? LOGO_MARK_INSET : className)
|
|
26
27
|
}
|
|
27
28
|
);
|
|
28
29
|
if (!withBackground) return mark;
|
|
@@ -30,7 +31,7 @@ function Logo({ className, withBackground = false }) {
|
|
|
30
31
|
"span",
|
|
31
32
|
{
|
|
32
33
|
"data-slot": "logo-background",
|
|
33
|
-
className: cn(
|
|
34
|
+
className: cn(LOGO_BACKGROUND, className),
|
|
34
35
|
children: mark
|
|
35
36
|
}
|
|
36
37
|
);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/shell/logo.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../../lib/cn\";\nimport { LOGO_BACKGROUND, LOGO_MARK, LOGO_MARK_INSET } from \"../../generated/logo\";\n\n/**\n * The consumer's mark, painted as a CSS mask (`backgroundColor: currentColor` +\n * `maskImage`) so a monochrome lockup follows the surrounding text colour\n * instead of the artwork's baked-in fill. `withBackground` seats it on the brand\n * field, keeping a chip consistent with a favicon.\n *\n * The kit ships no artwork. The mark is a design token like every colour: the\n * consumer declares `--brand-mark` — a `url()` at its own asset — and\n * `--brand-aspect` in the same sheet it declares the palette in (see\n * `styles/tokens.css`). Undeclared, `maskImage` is invalid at computed-value time\n * and the span paints as a solid block: loud, and nobody else's logo.\n *\n * Decorative by design (`aria-hidden`): every real call site has the brand name\n * in adjacent text or on the wrapping link, and a mark announced twice is worse\n * than one announced once.\n */\nexport interface LogoProps {\n className?: string;\n withBackground?: boolean;\n}\n\nconst maskStyle: React.CSSProperties = {\n backgroundColor: \"currentColor\",\n maskImage: \"var(--brand-mark)\",\n WebkitMaskImage: \"var(--brand-mark)\",\n maskRepeat: \"no-repeat\",\n WebkitMaskRepeat: \"no-repeat\",\n maskPosition: \"center\",\n WebkitMaskPosition: \"center\",\n maskSize: \"contain\",\n WebkitMaskSize: \"contain\",\n // the ratio's fallback is what makes a missing mark *visible*: a masked span\n // has no intrinsic size, so `h-N w-auto` without it is zero-width\n aspectRatio: \"var(--brand-aspect, 1)\",\n};\n\nexport function Logo({ className, withBackground = false }: LogoProps) {\n const mark = (\n <span\n data-slot=\"logo\"\n aria-hidden\n style={maskStyle}\n className={cn(LOGO_MARK, withBackground ? LOGO_MARK_INSET : className)}\n />\n );\n\n if (!withBackground) return mark;\n\n return (\n <span\n data-slot=\"logo-background\"\n className={cn(LOGO_BACKGROUND, className)}\n >\n {mark}\n </span>\n );\n}\n"],"mappings":"AA0CI;AA1CJ,YAAY,WAAW;AACvB,SAAS,UAAU;AACnB,SAAS,iBAAiB,WAAW,uBAAuB;AAuB5D,MAAM,YAAiC;AAAA,EACrC,iBAAiB;AAAA,EACjB,WAAW;AAAA,EACX,iBAAiB;AAAA,EACjB,YAAY;AAAA,EACZ,kBAAkB;AAAA,EAClB,cAAc;AAAA,EACd,oBAAoB;AAAA,EACpB,UAAU;AAAA,EACV,gBAAgB;AAAA;AAAA;AAAA,EAGhB,aAAa;AACf;AAEO,SAAS,KAAK,EAAE,WAAW,iBAAiB,MAAM,GAAc;AACrE,QAAM,OACJ;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,eAAW;AAAA,MACX,OAAO;AAAA,MACP,WAAW,GAAG,WAAW,iBAAiB,kBAAkB,SAAS;AAAA;AAAA,EACvE;AAGF,MAAI,CAAC,eAAgB,QAAO;AAE5B,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,iBAAiB,SAAS;AAAA,MAEvC;AAAA;AAAA,EACH;AAEJ;","names":[]}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import { cn } from "
|
|
4
|
+
import { cn } from "../../lib/cn.js";
|
|
5
5
|
import { ChevronLeftIcon } from "./shell-icons.js";
|
|
6
|
-
const BACK = "-ml-1 flex size-
|
|
6
|
+
const BACK = "-ml-1 flex size-(--control-h-2xs) shrink-0 items-center justify-center rounded-control text-ink outline-none focus-visible:ring-2 focus-visible:ring-ring";
|
|
7
7
|
const HIDE_FROM = { md: "md:hidden", lg: "lg:hidden" };
|
|
8
8
|
function MobileAppBar({
|
|
9
9
|
title,
|
|
@@ -23,13 +23,13 @@ function MobileAppBar({
|
|
|
23
23
|
"data-slot": "mobile-app-bar",
|
|
24
24
|
"data-enter": "rise",
|
|
25
25
|
className: cn(
|
|
26
|
-
"sticky top-[var(--ev-shell-offset,0px)] z-30 flex items-center gap-2 border-b border-border bg-
|
|
26
|
+
"sticky top-[var(--ev-shell-offset,0px)] z-30 flex items-center gap-2 border-b border-border bg-card px-4 pb-3.5 pt-4",
|
|
27
27
|
hideFrom && HIDE_FROM[hideFrom],
|
|
28
28
|
className
|
|
29
29
|
),
|
|
30
30
|
...props,
|
|
31
31
|
children: [
|
|
32
|
-
back && ("href" in back ? /* @__PURE__ */ jsx(Link, { href: back.href, "aria-label": backLabel, className: BACK, children: /* @__PURE__ */ jsx(ChevronLeftIcon, { className: "size-
|
|
32
|
+
back && ("href" in back ? /* @__PURE__ */ jsx(Link, { href: back.href, "aria-label": backLabel, className: BACK, children: /* @__PURE__ */ jsx(ChevronLeftIcon, { className: "size-full" }) }) : /* @__PURE__ */ jsx("button", { type: "button", onClick: back.onClick, "aria-label": backLabel, className: BACK, children: /* @__PURE__ */ jsx(ChevronLeftIcon, { className: "size-full" }) })),
|
|
33
33
|
/* @__PURE__ */ jsx(
|
|
34
34
|
"h1",
|
|
35
35
|
{
|
|
@@ -40,7 +40,7 @@ function MobileAppBar({
|
|
|
40
40
|
children: title
|
|
41
41
|
}
|
|
42
42
|
),
|
|
43
|
-
back && right === void 0 ? /* @__PURE__ */ jsx("span", { className: "size-
|
|
43
|
+
back && right === void 0 ? /* @__PURE__ */ jsx("span", { className: "size-(--control-h-2xs) shrink-0", "aria-hidden": true }) : right
|
|
44
44
|
]
|
|
45
45
|
}
|
|
46
46
|
);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/shell/mobile-app-bar.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../../lib/cn\";\nimport type { ShellBreakpoint } from \"./app-shell\";\nimport { ChevronLeftIcon } from \"./shell-icons\";\n\nexport interface MobileAppBarLabels {\n /** Accessible name of the back control. Default `\"Back\"`. */\n back?: string;\n}\n\nexport type MobileAppBarBack = { href: string } | { onClick: () => void };\n\nexport interface MobileAppBarProps extends Omit<React.ComponentProps<\"header\">, \"title\"> {\n title: React.ReactNode;\n /** A pushed screen: a back link (`href`) or a back button (`onClick`); centres the title. */\n back?: MobileAppBarBack;\n /** Trailing action — Save, a menu. */\n right?: React.ReactNode;\n linkComponent?: React.ElementType;\n /** Hidden from this breakpoint up, where the page heading takes over. Default `lg`; `false` never hides. */\n hideFrom?: ShellBreakpoint | false;\n labels?: MobileAppBarLabels;\n}\n\n// A bare glyph has no border of its own, so it needs a rounded box for the ring to trace.\nconst BACK =\n \"-ml-1 flex size-(--control-h-2xs) shrink-0 items-center justify-center rounded-control text-ink outline-none focus-visible:ring-2 focus-visible:ring-ring\";\n\nconst HIDE_FROM = { md: \"md:hidden\", lg: \"lg:hidden\" } as const;\n\n/**\n * The bar that titles a screen on a phone. A root screen puts the title flush\n * left with an optional action; a pushed one (`back`) centres it between the\n * back control and a matching spacer. Sticks under `--ev-shell-offset` and\n * arrives with the screen (`data-enter=\"rise\"`), one step ahead of a\n * `PageFrame`'s sections.\n */\nexport function MobileAppBar({\n title,\n back,\n right,\n linkComponent,\n hideFrom = \"lg\",\n labels = {},\n className,\n ...props\n}: MobileAppBarProps) {\n const backLabel = labels.back ?? \"Back\";\n const Link = linkComponent ?? \"a\";\n return (\n <header\n data-slot=\"mobile-app-bar\"\n data-enter=\"rise\"\n className={cn(\n \"sticky top-[var(--ev-shell-offset,0px)] z-30 flex items-center gap-2 border-b border-border bg-card px-4 pb-3.5 pt-4\",\n hideFrom && HIDE_FROM[hideFrom],\n className,\n )}\n {...props}\n >\n {back &&\n (\"href\" in back ? (\n <Link href={back.href} aria-label={backLabel} className={BACK}>\n <ChevronLeftIcon className=\"size-full\" />\n </Link>\n ) : (\n <button type=\"button\" onClick={back.onClick} aria-label={backLabel} className={BACK}>\n <ChevronLeftIcon className=\"size-full\" />\n </button>\n ))}\n <h1\n className={cn(\n \"min-w-0 flex-1 truncate font-semibold text-ink\",\n back ? \"text-center text-base\" : \"text-lg\",\n )}\n >\n {title}\n </h1>\n {back && right === undefined ? <span className=\"size-(--control-h-2xs) shrink-0\" aria-hidden /> : right}\n </header>\n );\n}\n"],"mappings":";AAoDI,SAaQ,KAbR;AAlDJ,YAAY,WAAW;AACvB,SAAS,UAAU;AAEnB,SAAS,uBAAuB;AAsBhC,MAAM,OACJ;AAEF,MAAM,YAAY,EAAE,IAAI,aAAa,IAAI,YAAY;AAS9C,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX,SAAS,CAAC;AAAA,EACV;AAAA,EACA,GAAG;AACL,GAAsB;AACpB,QAAM,YAAY,OAAO,QAAQ;AACjC,QAAM,OAAO,iBAAiB;AAC9B,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,cAAW;AAAA,MACX,WAAW;AAAA,QACT;AAAA,QACA,YAAY,UAAU,QAAQ;AAAA,QAC9B;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEH;AAAA,iBACE,UAAU,OACT,oBAAC,QAAK,MAAM,KAAK,MAAM,cAAY,WAAW,WAAW,MACvD,8BAAC,mBAAgB,WAAU,aAAY,GACzC,IAEA,oBAAC,YAAO,MAAK,UAAS,SAAS,KAAK,SAAS,cAAY,WAAW,WAAW,MAC7E,8BAAC,mBAAgB,WAAU,aAAY,GACzC;AAAA,QAEJ;AAAA,UAAC;AAAA;AAAA,YACC,WAAW;AAAA,cACT;AAAA,cACA,OAAO,0BAA0B;AAAA,YACnC;AAAA,YAEC;AAAA;AAAA,QACH;AAAA,QACC,QAAQ,UAAU,SAAY,oBAAC,UAAK,WAAU,mCAAkC,eAAW,MAAC,IAAK;AAAA;AAAA;AAAA,EACpG;AAEJ;","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as React from "react";
|
|
3
|
-
import { cn } from "
|
|
3
|
+
import { cn } from "../../lib/cn.js";
|
|
4
4
|
const PILL = "rounded-full px-2 py-0.5 text-xs font-semibold tabular-nums";
|
|
5
5
|
const CORNER = "absolute -right-2.5 -top-1.5 flex h-4 min-w-4 items-center justify-center rounded-full bg-primary px-1 text-xs font-semibold leading-none tabular-nums text-on-primary";
|
|
6
6
|
const defaultLabel = (n) => `${n} new`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/shell/nav-badge.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../../lib/cn\";\n\nexport type NavBadgeVariant = \"pill\" | \"corner\";\n\nexport interface NavBadgeProps extends Omit<React.ComponentProps<\"span\">, \"children\"> {\n count: number;\n /** Above this the badge shows `{max}+`. */\n max?: number;\n /**\n * The spoken text, given the REAL count — the one figure the cap throws away.\n * Default: `\"{n} new\"`.\n */\n label?: (count: number) => string;\n /**\n * `pill` sits in a row after the label (the rail); `corner` is the solid dot on\n * an icon's top-right corner (the tab bar) — its parent must be `relative`.\n */\n variant?: NavBadgeVariant;\n /** A pill on the marked row: inverted so it reads on the marker's fill. */\n active?: boolean;\n}\n\nconst PILL = \"rounded-full px-2 py-0.5 text-xs font-semibold tabular-nums\";\nconst CORNER =\n \"absolute -right-2.5 -top-1.5 flex h-4 min-w-4 items-center justify-center rounded-full bg-primary px-1 text-xs font-semibold leading-none tabular-nums text-on-primary\";\n\nconst defaultLabel = (n: number) => `${n} new`;\n\n/**\n * A count on a nav row or tab, capped so a neglected inbox cannot widen the\n * rail. Draws nothing for `0` or less.\n *\n * The visible figure is `aria-hidden` and the spoken one rides in an `sr-only`\n * span rather than an `aria-label`: a label on a plain span is not part of the\n * name the browser computes for the link around it, while text is — so the row\n * reads \"Notifications, 150 new\".\n */\nexport function NavBadge({\n count,\n max = 99,\n label = defaultLabel,\n variant = \"pill\",\n active = false,\n className,\n ...props\n}: NavBadgeProps) {\n if (!(count > 0)) return null;\n return (\n <span\n data-slot=\"nav-badge\"\n data-variant={variant}\n className={cn(\n variant === \"corner\"\n ? CORNER\n : cn(PILL, active ? \"bg-background text-ink\" : \"bg-primary-ink/15 text-primary-ink\"),\n className,\n )}\n {...props}\n >\n <span aria-hidden>{count > max ? `${max}+` : count}</span>\n <span className=\"sr-only\">{label(count)}</span>\n </span>\n );\n}\n\nexport type NavDotTone = \"primary\" | \"positive\" | \"warn\" | \"error\";\n\nconst DOT_TONE: Record<NavDotTone, string> = {\n primary: \"bg-primary-ink\",\n positive: \"bg-positive\",\n warn: \"bg-accent-warn\",\n error: \"bg-accent-error\",\n};\n\nexport interface NavDotProps extends Omit<React.ComponentProps<\"span\">, \"children\"> {\n tone?: NavDotTone;\n /** Spoken text; without one the dot is decoration and hidden from AT. */\n label?: string;\n /** Pin to an icon's top-right corner (parent `relative`), as the tab bar does. */\n corner?: boolean;\n}\n\n/** A presence dot — \"something here\", no figure. */\nexport function NavDot({ tone = \"primary\", label, corner = false, className, ...props }: NavDotProps) {\n return (\n <span\n data-slot=\"nav-dot\"\n aria-hidden={label === undefined ? true : undefined}\n className={cn(\n \"inline-block size-2 shrink-0 rounded-full\",\n DOT_TONE[tone],\n corner && \"absolute -right-1 -top-0.5\",\n className,\n )}\n {...props}\n >\n {label !== undefined && <span className=\"sr-only\">{label}</span>}\n </span>\n );\n}\n"],"mappings":"AAiDI,SAWE,KAXF;AAjDJ,YAAY,WAAW;AACvB,SAAS,UAAU;AAsBnB,MAAM,OAAO;AACb,MAAM,SACJ;AAEF,MAAM,eAAe,CAAC,MAAc,GAAG,CAAC;AAWjC,SAAS,SAAS;AAAA,EACvB;AAAA,EACA,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,SAAS;AAAA,EACT;AAAA,EACA,GAAG;AACL,GAAkB;AAChB,MAAI,EAAE,QAAQ,GAAI,QAAO;AACzB,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,gBAAc;AAAA,MACd,WAAW;AAAA,QACT,YAAY,WACR,SACA,GAAG,MAAM,SAAS,2BAA2B,oCAAoC;AAAA,QACrF;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEJ;AAAA,4BAAC,UAAK,eAAW,MAAE,kBAAQ,MAAM,GAAG,GAAG,MAAM,OAAM;AAAA,QACnD,oBAAC,UAAK,WAAU,WAAW,gBAAM,KAAK,GAAE;AAAA;AAAA;AAAA,EAC1C;AAEJ;AAIA,MAAM,WAAuC;AAAA,EAC3C,SAAS;AAAA,EACT,UAAU;AAAA,EACV,MAAM;AAAA,EACN,OAAO;AACT;AAWO,SAAS,OAAO,EAAE,OAAO,WAAW,OAAO,SAAS,OAAO,WAAW,GAAG,MAAM,GAAgB;AACpG,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,eAAa,UAAU,SAAY,OAAO;AAAA,MAC1C,WAAW;AAAA,QACT;AAAA,QACA,SAAS,IAAI;AAAA,QACb,UAAU;AAAA,QACV;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEH,oBAAU,UAAa,oBAAC,UAAK,WAAU,WAAW,iBAAM;AAAA;AAAA,EAC3D;AAEJ;","names":[]}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import { cn } from "
|
|
5
|
-
import { useControllableState } from "
|
|
4
|
+
import { cn } from "../../lib/cn.js";
|
|
5
|
+
import { useControllableState } from "../../primitives/use-controllable-state.js";
|
|
6
|
+
import { iconShapes } from "../../icons/icon-shapes.js";
|
|
6
7
|
function useNavGroupOpen(group, containsCurrent) {
|
|
7
8
|
const [open, setOpen] = useControllableState({
|
|
8
9
|
value: group.open,
|
|
@@ -65,11 +66,11 @@ function Chevron({ open }) {
|
|
|
65
66
|
strokeLinecap: "round",
|
|
66
67
|
strokeLinejoin: "round",
|
|
67
68
|
className: cn("size-4 transition-transform", MOTION, open && "rotate-90"),
|
|
68
|
-
children:
|
|
69
|
+
children: iconShapes("chevron-right")
|
|
69
70
|
}
|
|
70
71
|
);
|
|
71
72
|
}
|
|
72
|
-
const ROW = "flex min-h-
|
|
73
|
+
const ROW = "flex min-h-(--control-h-md) items-center rounded-row text-body font-medium text-ink transition-colors motion-reduce:transition-none";
|
|
73
74
|
const FOCUS = "outline-none focus-visible:ring-2 focus-visible:ring-ring";
|
|
74
75
|
function NavGroupHeader({
|
|
75
76
|
label,
|
|
@@ -84,9 +85,9 @@ function NavGroupHeader({
|
|
|
84
85
|
className
|
|
85
86
|
}) {
|
|
86
87
|
const chevronId = React.useId();
|
|
87
|
-
const row = cn(ROW, open ? "bg-
|
|
88
|
+
const row = cn(ROW, open ? "bg-selected" : "hover:bg-hover", className);
|
|
88
89
|
const chip = cn(
|
|
89
|
-
"grid size-
|
|
90
|
+
"grid size-(--control-h-xs) shrink-0 place-items-center rounded-control text-ink-mid",
|
|
90
91
|
open && "bg-selected"
|
|
91
92
|
);
|
|
92
93
|
if (href === void 0) {
|
|
@@ -114,7 +115,7 @@ function NavGroupHeader({
|
|
|
114
115
|
id: labelId,
|
|
115
116
|
href,
|
|
116
117
|
"aria-current": current ? "page" : void 0,
|
|
117
|
-
className: cn("min-w-0 flex-1 truncate rounded-
|
|
118
|
+
className: cn("min-w-0 flex-1 truncate rounded-row py-2 pl-3", FOCUS),
|
|
118
119
|
children: label
|
|
119
120
|
}
|
|
120
121
|
),
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/shell/nav-collapse.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../../lib/cn\";\nimport { useControllableState } from \"../../primitives/use-controllable-state\";\nimport { iconShapes } from \"../../icons/icon-shapes\";\n\n/** What a nav group takes to become a section that opens and closes. */\nexport interface NavGroupCollapse {\n /**\n * Draws the group's `label` as a header with a chevron and lets the rows fold\n * away. Needs a `label`. Off by default so an existing rail keeps its static\n * eyebrow.\n */\n collapsible?: boolean;\n /** Open on mount. Default: open exactly when the group holds the current item. */\n defaultOpen?: boolean;\n /** Controlled open state; pair with `onOpenChange`. */\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n /** A landing page: the label navigates to it and only the chevron toggles. */\n href?: string;\n}\n\nexport interface NavCollapseLabels {\n /** Spoken before the group's name on a closed section's chevron. Default `\"Expand\"`. */\n expand?: string;\n /** Spoken before the group's name on an open section's chevron. Default `\"Collapse\"`. */\n collapse?: string;\n}\n\n/**\n * Open state of one collapsible group. A route change that lands inside a closed\n * group opens it; a user who closes the group holding the current row is not\n * fought, because only the move INTO the group opens it.\n */\nexport function useNavGroupOpen(\n group: NavGroupCollapse,\n containsCurrent: boolean,\n): readonly [open: boolean, setOpen: (next: boolean) => void] {\n const [open, setOpen] = useControllableState<boolean>({\n value: group.open,\n defaultValue: group.defaultOpen ?? containsCurrent,\n onChange: group.onOpenChange,\n });\n const hadCurrent = React.useRef(containsCurrent);\n React.useEffect(() => {\n if (containsCurrent && !hadCurrent.current && !open) setOpen(true);\n hadCurrent.current = containsCurrent;\n }, [containsCurrent, open, setOpen]);\n return [open, setOpen] as const;\n}\n\nconst MOTION =\n \"duration-(--ev-dur-fast) ease-(--ev-ease-in-out) motion-reduce:transition-none\";\n\n/**\n * The folding body. Height runs on a `0fr → 1fr` grid track (no measuring), the\n * content fades, and `visibility` flips at the END of the close so the hidden\n * rows leave the tab order without cutting the fade. `inert` is set at once so\n * a closing region cannot take focus or be read while it fades.\n */\nexport function NavCollapse({\n open,\n id,\n children,\n}: {\n open: boolean;\n id: string;\n children: React.ReactNode;\n}) {\n const ref = React.useRef<HTMLDivElement>(null);\n // `inert` as a React prop is a boolean in 19 and unknown in 18; the DOM\n // attribute works in both.\n React.useLayoutEffect(() => {\n ref.current?.toggleAttribute(\"inert\", !open);\n }, [open]);\n return (\n <div\n ref={ref}\n id={id}\n data-slot=\"nav-collapse\"\n data-state={open ? \"open\" : \"closed\"}\n className={cn(\n // minmax(0, …): a plain `1fr` column grows to the widest label and the rail overflows.\n \"grid grid-cols-[minmax(0,1fr)] transition-[grid-template-rows]\",\n MOTION,\n open ? \"grid-rows-[1fr]\" : \"grid-rows-[0fr]\",\n )}\n >\n {/* `overflow-y-clip` keeps the x axis visible so a focus ring on a row is not cut. */}\n <div\n className={cn(\n \"min-h-0 overflow-y-clip transition-[opacity,visibility]\",\n MOTION,\n open ? \"visible opacity-100\" : \"invisible opacity-0\",\n )}\n >\n {children}\n </div>\n </div>\n );\n}\n\nfunction Chevron({ open }: { open: boolean }) {\n return (\n <svg\n aria-hidden\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className={cn(\"size-4 transition-transform\", MOTION, open && \"rotate-90\")}\n >\n {iconShapes(\"chevron-right\")}\n </svg>\n );\n}\n\nconst ROW = \"flex min-h-(--control-h-md) items-center rounded-row text-body font-medium text-ink transition-colors motion-reduce:transition-none\";\nconst FOCUS = \"outline-none focus-visible:ring-2 focus-visible:ring-ring\";\n\nexport interface NavGroupHeaderProps {\n label: React.ReactNode;\n labelId: string;\n regionId: string;\n open: boolean;\n onToggle: () => void;\n href?: string | undefined;\n linkComponent?: React.ElementType | undefined;\n /** The landing page is the current page. */\n current?: boolean | undefined;\n labels?: NavCollapseLabels | undefined;\n className?: string | undefined;\n}\n\n/**\n * The section's header row. With an `href` the label is a link and the chevron\n * is its own button; without one the whole row is the button and the chevron is\n * a picture of its state.\n */\nexport function NavGroupHeader({\n label,\n labelId,\n regionId,\n open,\n onToggle,\n href,\n linkComponent,\n current,\n labels,\n className,\n}: NavGroupHeaderProps) {\n const chevronId = React.useId();\n const row = cn(ROW, open ? \"bg-selected\" : \"hover:bg-hover\", className);\n const chip = cn(\n \"grid size-(--control-h-xs) shrink-0 place-items-center rounded-control text-ink-mid\",\n open && \"bg-selected\",\n );\n if (href === undefined) {\n return (\n <button\n type=\"button\"\n data-slot=\"nav-group-header\"\n aria-expanded={open}\n aria-controls={regionId}\n onClick={onToggle}\n className={cn(row, FOCUS, \"w-full gap-2 py-1 pl-3 pr-1.5 text-left\")}\n >\n <span id={labelId} className=\"min-w-0 flex-1 truncate\">\n {label}\n </span>\n <span className={chip}>\n <Chevron open={open} />\n </span>\n </button>\n );\n }\n const Link: React.ElementType = linkComponent ?? \"a\";\n return (\n <div data-slot=\"nav-group-header\" className={cn(row, \"gap-1 pr-1.5\")}>\n <Link\n id={labelId}\n href={href}\n aria-current={current ? \"page\" : undefined}\n className={cn(\"min-w-0 flex-1 truncate rounded-row py-2 pl-3\", FOCUS)}\n >\n {label}\n </Link>\n {/* Named \"Expand Guides\": the button's own aria-label, then the label's text. */}\n <button\n type=\"button\"\n id={chevronId}\n aria-label={open ? (labels?.collapse ?? \"Collapse\") : (labels?.expand ?? \"Expand\")}\n aria-labelledby={`${chevronId} ${labelId}`}\n aria-expanded={open}\n aria-controls={regionId}\n onClick={onToggle}\n className={cn(chip, FOCUS, !open && \"hover:bg-hover\")}\n >\n <Chevron open={open} />\n </button>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/shell/nav-link.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { anchorTargetProps, isPlainLeftClick, type NavItem } from \"./nav-model\";\n\nexport interface NavLinkCallbacks {\n /** Hover or focus on an item — the moment to prefetch its route. */\n onItemIntent?: ((item: NavItem) => void) | undefined;\n /** A plain left click on an in-app item, after it is marked pending. */\n onNavigate?: ((item: NavItem) => void) | undefined;\n}\n\ninterface NavLinkProps extends NavLinkCallbacks {\n item: NavItem;\n current: boolean;\n pending: boolean;\n linkComponent: React.ElementType | undefined;\n markPending: (id: string) => void;\n className: string;\n title?: string | undefined;\n children: React.ReactNode;\n}\n\n/**\n * The anchor (or the disabled stand-in) under a rail row and a tab: picks the\n * element, wires the optimistic mark and the intent/navigate callbacks, and\n * carries `aria-current`, which is also what the rail's marker measures.\n */\nexport function NavLink({\n item,\n current,\n pending,\n linkComponent,\n markPending,\n onItemIntent,\n onNavigate,\n className,\n title,\n children,\n}: NavLinkProps) {\n if (item.disabled) {\n return (\n <span data-slot=\"nav-link\" aria-disabled=\"true\" title={title} className={className}>\n {children}\n </span>\n );\n }\n const Comp: React.ElementType = item.external ? \"a\" : (linkComponent ?? \"a\");\n const intent = onItemIntent && (() => onItemIntent(item));\n return (\n <Comp\n data-slot=\"nav-link\"\n data-pending={pending ? \"true\" : undefined}\n href={item.href}\n title={title}\n aria-current={current ? \"page\" : undefined}\n className={className}\n onPointerEnter={intent}\n onFocus={intent}\n onClick={(e: React.MouseEvent<HTMLAnchorElement>) => {\n if (item.external || !isPlainLeftClick(e)) return;\n markPending(item.id);\n onNavigate?.(item);\n }}\n {...anchorTargetProps(item)}\n >\n {children}\n </Comp>\n );\n}\n\n/** The row's tooltip: the label itself when it is plain text, so a truncated one stays readable. */\nexport function labelTitle(label: React.ReactNode): string | undefined {\n return typeof label === \"string\" ? label : undefined;\n}\n"],"mappings":";AA0CM;AAxCN,YAAY,WAAW;AACvB,SAAS,mBAAmB,wBAAsC;AAyB3D,SAAS,QAAQ;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAiB;AACf,MAAI,KAAK,UAAU;AACjB,WACE,oBAAC,UAAK,aAAU,YAAW,iBAAc,QAAO,OAAc,WAC3D,UACH;AAAA,EAEJ;AACA,QAAM,OAA0B,KAAK,WAAW,MAAO,iBAAiB;AACxE,QAAM,SAAS,iBAAiB,MAAM,aAAa,IAAI;AACvD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,gBAAc,UAAU,SAAS;AAAA,MACjC,MAAM,KAAK;AAAA,MACX;AAAA,MACA,gBAAc,UAAU,SAAS;AAAA,MACjC;AAAA,MACA,gBAAgB;AAAA,MAChB,SAAS;AAAA,MACT,SAAS,CAAC,MAA2C;AACnD,YAAI,KAAK,YAAY,CAAC,iBAAiB,CAAC,EAAG;AAC3C,oBAAY,KAAK,EAAE;AACnB,qBAAa,IAAI;AAAA,MACnB;AAAA,MACC,GAAG,kBAAkB,IAAI;AAAA,MAEzB;AAAA;AAAA,EACH;AAEJ;AAGO,SAAS,WAAW,OAA4C;AACrE,SAAO,OAAO,UAAU,WAAW,QAAQ;AAC7C;","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/shell/nav-model.ts"],"sourcesContent":["import type * as React from \"react\";\nimport type { NavGroupCollapse } from \"./nav-collapse\";\n\n/**\n * How a {@link NavItem} decides it is the current one.\n *\n * - `\"prefix\"` (default) — the path is the item's own or lies beneath it, on a\n * segment boundary: `/invest` claims `/invest/arb` but not `/investors`. An\n * item at `/` is exact, or it would claim every page.\n * - `\"exact\"` — the path is the item's own and nothing beneath it.\n * - a function — whatever it answers for the path.\n */\nexport type NavMatch = \"exact\" | \"prefix\" | ((pathname: string) => boolean);\n\n/** State a row is rendered in; what `trailing` and `renderItem` are told. */\nexport interface NavItemState {\n active: boolean;\n /** Marked by a click whose navigation has not landed yet. */\n pending: boolean;\n}\n\n/** One destination, shared by the rail ({@link ShellNav}) and the tab bar. */\nexport interface NavItem {\n /** Stable key; also what `onNavigate` / `onItemIntent` hand back. */\n id: string;\n href: string;\n label: React.ReactNode;\n icon?: React.ComponentType<{ className?: string }>;\n /** A count (`NavBadge`, capped at 99+, `0` draws nothing) or your own node. */\n badge?: number | React.ReactNode;\n /** Anything after the label — a status chip; a function is told the row's state. */\n trailing?: React.ReactNode | ((state: NavItemState) => React.ReactNode);\n /**\n * Leaves the app: rendered as a plain `<a>` (never `linkComponent`), never\n * current, never marked pending — a `mailto:`, a docs site.\n */\n external?: boolean;\n /** Passed to the anchor; `_blank` gets `rel=\"noopener noreferrer\"`. */\n target?: React.HTMLAttributeAnchorTarget;\n match?: NavMatch;\n /**\n * More paths this item claims, prefix-matched — the one tab standing in for\n * a whole group of rail rows (`[\"/profile\", \"/notifications\"]`).\n */\n also?: readonly string[];\n /** Rendered, not focusable, never current. */\n disabled?: boolean;\n}\n\n/** A titled run of items; the rail draws one marker per group. */\nexport interface NavGroup extends NavGroupCollapse {\n id: string;\n /** The eyebrow above the rows; also the list's accessible name. */\n label?: React.ReactNode;\n items: readonly NavItem[];\n}\n\nfunction pathOf(href: string): string {\n const cut = href.search(/[?#]/);\n const path = cut === -1 ? href : href.slice(0, cut);\n return path.length > 1 && path.endsWith(\"/\") ? path.slice(0, -1) : path;\n}\n\nfunction underPrefix(pathname: string, prefix: string): boolean {\n if (prefix === \"/\") return pathname === \"/\";\n return pathname === prefix || pathname.startsWith(`${prefix}/`);\n}\n\n// An exact hit outranks any prefix; among prefixes the longest wins. A function\n// match ranks as a prefix hit on the item's own href.\nconst EXACT = Number.MAX_SAFE_INTEGER;\n\n/**\n * How strongly `item` claims `pathname`, or `null` when it does not. Used to\n * pick ONE current item when several match — the rail and the tab bar each\n * draw a single marker, so two current rows is not a state they can show.\n */\nexport function navItemMatch(item: NavItem, pathname: string): number | null {\n if (item.external || item.disabled) return null;\n const path = pathOf(pathname);\n const own = pathOf(item.href);\n const match = item.match ?? \"prefix\";\n let score: number | null = null;\n if (typeof match === \"function\") score = match(path) ? own.length : null;\n else if (path === own) score = EXACT;\n else if (match === \"prefix\" && underPrefix(path, own)) score = own.length;\n for (const extra of item.also ?? []) {\n const p = pathOf(extra);\n if (underPrefix(path, p) && (score === null || p.length > score)) score = p.length;\n }\n return score;\n}\n\n/** Whether `item` claims `pathname` at all, regardless of its neighbours. */\nexport function isNavItemActive(item: NavItem, pathname: string): boolean {\n return navItemMatch(item, pathname) !== null;\n}\n\n/** The single current item for `pathname`: the strongest claim, ties to the first. */\nexport function resolveActiveNavItem(\n items: Iterable<NavItem>,\n pathname: string,\n): NavItem | undefined {\n let best: NavItem | undefined;\n let bestScore = -1;\n for (const item of items) {\n const score = navItemMatch(item, pathname);\n if (score !== null && score > bestScore) {\n best = item;\n bestScore = score;\n }\n }\n return best;\n}\n\n/**\n * A click the app should handle in place. A modified or non-primary click opens\n * the page elsewhere (a tab, a window, the context menu) and the router lets the\n * browser have it, so the current row stays current; `defaultPrevented` is the\n * same courtesy to anything upstream that claimed the click.\n */\nexport function isPlainLeftClick(e: React.MouseEvent): boolean {\n return !(e.defaultPrevented || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || e.button !== 0);\n}\n\n/** Props the rail and the tab bar hand a `linkComponent`; `next/link` takes all of them. */\nexport function anchorTargetProps(\n item: Pick<NavItem, \"target\">,\n): { target?: React.HTMLAttributeAnchorTarget; rel?: string } {\n if (item.target === undefined) return {};\n return item.target === \"_blank\"\n ? { target: item.target, rel: \"noopener noreferrer\" }\n : { target: item.target };\n}\n"],"mappings":"AAyDA,SAAS,OAAO,MAAsB;AACpC,QAAM,MAAM,KAAK,OAAO,MAAM;AAC9B,QAAM,OAAO,QAAQ,KAAK,OAAO,KAAK,MAAM,GAAG,GAAG;AAClD,SAAO,KAAK,SAAS,KAAK,KAAK,SAAS,GAAG,IAAI,KAAK,MAAM,GAAG,EAAE,IAAI;AACrE;AAEA,SAAS,YAAY,UAAkB,QAAyB;AAC9D,MAAI,WAAW,IAAK,QAAO,aAAa;AACxC,SAAO,aAAa,UAAU,SAAS,WAAW,GAAG,MAAM,GAAG;AAChE;AAIA,MAAM,QAAQ,OAAO;AAOd,SAAS,aAAa,MAAe,UAAiC;AAC3E,MAAI,KAAK,YAAY,KAAK,SAAU,QAAO;AAC3C,QAAM,OAAO,OAAO,QAAQ;AAC5B,QAAM,MAAM,OAAO,KAAK,IAAI;AAC5B,QAAM,QAAQ,KAAK,SAAS;AAC5B,MAAI,QAAuB;AAC3B,MAAI,OAAO,UAAU,WAAY,SAAQ,MAAM,IAAI,IAAI,IAAI,SAAS;AAAA,WAC3D,SAAS,IAAK,SAAQ;AAAA,WACtB,UAAU,YAAY,YAAY,MAAM,GAAG,EAAG,SAAQ,IAAI;AACnE,aAAW,SAAS,KAAK,QAAQ,CAAC,GAAG;AACnC,UAAM,IAAI,OAAO,KAAK;AACtB,QAAI,YAAY,MAAM,CAAC,MAAM,UAAU,QAAQ,EAAE,SAAS,OAAQ,SAAQ,EAAE;AAAA,EAC9E;AACA,SAAO;AACT;AAGO,SAAS,gBAAgB,MAAe,UAA2B;AACxE,SAAO,aAAa,MAAM,QAAQ,MAAM;AAC1C;AAGO,SAAS,qBACd,OACA,UACqB;AACrB,MAAI;AACJ,MAAI,YAAY;AAChB,aAAW,QAAQ,OAAO;AACxB,UAAM,QAAQ,aAAa,MAAM,QAAQ;AACzC,QAAI,UAAU,QAAQ,QAAQ,WAAW;AACvC,aAAO;AACP,kBAAY;AAAA,IACd;AAAA,EACF;AACA,SAAO;AACT;AAQO,SAAS,iBAAiB,GAA8B;AAC7D,SAAO,EAAE,EAAE,oBAAoB,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,WAAW;AAClG;AAGO,SAAS,kBACd,MAC4D;AAC5D,MAAI,KAAK,WAAW,OAAW,QAAO,CAAC;AACvC,SAAO,KAAK,WAAW,WACnB,EAAE,QAAQ,KAAK,QAAQ,KAAK,sBAAsB,IAClD,EAAE,QAAQ,KAAK,OAAO;AAC5B;","names":[]}
|