@adea-ai/ui 0.61.0 → 0.62.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 +44 -6
- package/dist/LICENSE +201 -0
- package/dist/NOTICE +50 -0
- package/dist/components/composites/account-menu/index.js +2 -0
- package/dist/components/composites/list-row/index.js +2 -0
- package/dist/components/composites/settings/index.js +2 -0
- package/dist/components/composites/stat/index.js +2 -0
- package/dist/components/composites/update-dialog/index.js +2 -0
- package/dist/components/composites/workspace-mark/index.js +2 -0
- package/dist/components/conversation/index.js +6 -0
- package/dist/components/layout/app-shell/index.js +2 -0
- package/dist/components/layout/page/index.js +2 -0
- package/dist/components/layout/panel/index.js +2 -0
- package/dist/components/layout/side-rail/index.js +3 -0
- package/dist/components/layout/sidebar-nav/index.js +2 -0
- package/dist/components/layout/status-bar/index.js +2 -0
- package/dist/components/layout/top-bar/index.js +2 -0
- package/dist/components/motion/index.js +2 -2
- package/dist/components/theme/index.js +5 -0
- package/dist/components/ui/accordion/index.js +2 -0
- package/dist/components/ui/alert/index.js +2 -0
- package/dist/components/ui/alert-dialog/index.js +2 -0
- package/dist/components/ui/aspect-ratio/index.js +2 -0
- package/dist/components/ui/avatar/index.js +2 -0
- package/dist/components/ui/badge/index.js +2 -0
- package/dist/components/ui/board/index.js +2 -0
- package/dist/components/ui/breadcrumb/index.js +2 -0
- package/dist/components/ui/button/index.js +2 -0
- package/dist/components/ui/button-group/index.js +2 -0
- package/dist/components/ui/calendar/index.js +2 -0
- package/dist/components/ui/card/index.js +2 -0
- package/dist/components/ui/carousel/index.js +2 -0
- package/dist/components/ui/chart/index.js +2 -0
- package/dist/components/ui/checkbox/index.js +2 -0
- package/dist/components/ui/code-block/index.js +2 -0
- package/dist/components/ui/collapsible/index.js +2 -0
- package/dist/components/ui/combobox/index.js +2 -0
- package/dist/components/ui/command/index.js +2 -0
- package/dist/components/ui/context-menu/index.js +2 -0
- package/dist/components/ui/detail-panel/index.js +2 -0
- package/dist/components/ui/dialog/index.js +2 -0
- package/dist/components/ui/diff-block/index.js +2 -0
- package/dist/components/ui/drawer/index.js +2 -0
- package/dist/components/ui/dropdown-menu/index.js +2 -0
- package/dist/components/ui/empty/index.js +2 -0
- package/dist/components/ui/entity-icon/index.js +2 -0
- package/dist/components/ui/field/index.js +2 -0
- package/dist/components/ui/hover-card/index.js +2 -0
- package/dist/components/ui/input/index.js +2 -0
- package/dist/components/ui/input-group/index.js +2 -0
- package/dist/components/ui/input-otp/index.js +2 -0
- package/dist/components/ui/item/index.js +2 -0
- package/dist/components/ui/kbd/index.js +2 -0
- package/dist/components/ui/label/index.js +2 -0
- package/dist/components/ui/menubar/index.js +2 -0
- package/dist/components/ui/modal-dialog/index.js +2 -0
- package/dist/components/ui/navigation-menu/index.js +2 -0
- package/dist/components/ui/pagination/index.js +2 -0
- package/dist/components/ui/popover/index.js +2 -0
- package/dist/components/ui/progress/index.js +2 -0
- package/dist/components/ui/radio-group/index.js +2 -0
- package/dist/components/ui/resizable/index.js +2 -0
- package/dist/components/ui/scroll-area/index.js +2 -0
- package/dist/components/ui/select/index.js +2 -0
- package/dist/components/ui/separator/index.js +2 -0
- package/dist/components/ui/sheet/index.js +2 -0
- package/dist/components/ui/skeleton/index.js +2 -0
- package/dist/components/ui/slider/index.js +2 -0
- package/dist/components/ui/spinner/index.js +2 -0
- package/dist/components/ui/status-chip/index.js +2 -0
- package/dist/components/ui/switch/index.js +2 -0
- package/dist/components/ui/table/index.js +2 -0
- package/dist/components/ui/tabs/index.js +2 -0
- package/dist/components/ui/textarea/index.js +2 -0
- package/dist/components/ui/toast/index.js +2 -0
- package/dist/components/ui/toggle/index.js +2 -0
- package/dist/components/ui/toggle-group/index.js +2 -0
- package/dist/components/ui/tooltip/index.js +2 -0
- package/dist/index.d.ts +2 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -3
- package/dist/r/accordion.json +3 -1
- package/dist/r/account-menu.json +2 -1
- package/dist/r/alert-dialog.json +3 -1
- package/dist/r/alert.json +3 -1
- package/dist/r/app-shell.json +3 -1
- package/dist/r/aspect-ratio.json +3 -1
- package/dist/r/avatar.json +3 -1
- package/dist/r/badge.json +3 -1
- package/dist/r/board.json +3 -1
- package/dist/r/breadcrumb.json +3 -1
- package/dist/r/button-group.json +2 -1
- package/dist/r/button.json +3 -1
- package/dist/r/calendar.json +2 -1
- package/dist/r/card.json +3 -1
- package/dist/r/carousel.json +2 -1
- package/dist/r/chart.json +3 -1
- package/dist/r/checkbox.json +3 -1
- package/dist/r/code-block.json +2 -1
- package/dist/r/collapsible.json +3 -1
- package/dist/r/combobox.json +3 -1
- package/dist/r/command.json +3 -1
- package/dist/r/context-menu.json +3 -1
- package/dist/r/conversation.json +2 -1
- package/dist/r/detail-panel.json +2 -1
- package/dist/r/dialog.json +3 -1
- package/dist/r/diff-block.json +3 -1
- package/dist/r/drawer.json +3 -1
- package/dist/r/dropdown-menu.json +3 -1
- package/dist/r/empty.json +3 -1
- package/dist/r/entity-icon.json +3 -1
- package/dist/r/field.json +3 -1
- package/dist/r/hover-card.json +3 -1
- package/dist/r/input-group.json +3 -1
- package/dist/r/input-otp.json +3 -1
- package/dist/r/input.json +3 -1
- package/dist/r/item.json +3 -1
- package/dist/r/kbd.json +3 -1
- package/dist/r/label.json +3 -1
- package/dist/r/lib.json +50 -0
- package/dist/r/list-row.json +3 -1
- package/dist/r/menubar.json +3 -1
- package/dist/r/modal-dialog.json +2 -1
- package/dist/r/motion.json +22 -0
- package/dist/r/navigation-menu.json +3 -1
- package/dist/r/page.json +3 -1
- package/dist/r/pagination.json +3 -1
- package/dist/r/panel.json +3 -1
- package/dist/r/popover.json +3 -1
- package/dist/r/progress.json +3 -1
- package/dist/r/radio-group.json +3 -1
- package/dist/r/registry.json +320 -73
- package/dist/r/resizable.json +3 -1
- package/dist/r/scroll-area.json +3 -1
- package/dist/r/select.json +3 -1
- package/dist/r/separator.json +3 -1
- package/dist/r/settings.json +3 -1
- package/dist/r/sheet.json +2 -1
- package/dist/r/side-rail.json +3 -1
- package/dist/r/sidebar-nav.json +3 -1
- package/dist/r/skeleton.json +3 -1
- package/dist/r/slider.json +3 -1
- package/dist/r/spinner.json +3 -1
- package/dist/r/src/components/composites/account-menu/account-menu.tsx +150 -0
- package/dist/r/src/components/composites/account-menu/index.ts +1 -0
- package/dist/r/src/components/composites/list-row/index.ts +1 -0
- package/dist/r/src/components/composites/list-row/list-row.tsx +116 -0
- package/dist/r/src/components/composites/settings/index.ts +1 -0
- package/dist/r/src/components/composites/settings/settings.tsx +142 -0
- package/dist/r/src/components/composites/stat/index.ts +9 -0
- package/dist/r/src/components/composites/stat/stat.tsx +155 -0
- package/dist/r/src/components/composites/update-dialog/index.ts +7 -0
- package/dist/r/src/components/composites/update-dialog/update-dialog.tsx +529 -0
- package/dist/r/src/components/composites/workspace-mark/index.ts +1 -0
- package/dist/r/src/components/composites/workspace-mark/workspace-mark.tsx +72 -0
- package/dist/r/src/components/conversation/conversation-avatar.tsx +60 -0
- package/dist/r/src/components/conversation/conversation-surface.tsx +107 -0
- package/dist/r/src/components/conversation/index.ts +25 -0
- package/dist/r/src/components/conversation/message-composer.tsx +239 -0
- package/dist/r/src/components/conversation/message-row.tsx +387 -0
- package/dist/r/src/components/conversation/thread-panel.tsx +141 -0
- package/dist/r/src/components/layout/app-shell/app-shell.tsx +92 -0
- package/dist/r/src/components/layout/app-shell/index.ts +1 -0
- package/dist/r/src/components/layout/page/index.ts +9 -0
- package/dist/r/src/components/layout/page/page.tsx +122 -0
- package/dist/r/src/components/layout/panel/index.ts +12 -0
- package/dist/r/src/components/layout/panel/panel.tsx +166 -0
- package/dist/r/src/components/layout/side-rail/index.ts +12 -0
- package/dist/r/src/components/layout/side-rail/side-rail-context.ts +42 -0
- package/dist/r/src/components/layout/side-rail/side-rail.tsx +405 -0
- package/dist/r/src/components/layout/sidebar-nav/index.ts +14 -0
- package/dist/r/src/components/layout/sidebar-nav/sidebar-nav.tsx +244 -0
- package/dist/r/src/components/layout/status-bar/index.ts +1 -0
- package/dist/r/src/components/layout/status-bar/status-bar.tsx +73 -0
- package/dist/r/src/components/layout/top-bar/index.ts +10 -0
- package/dist/r/src/components/layout/top-bar/top-bar.tsx +166 -0
- package/dist/r/src/components/motion/index.ts +2 -0
- package/dist/r/src/components/motion/presence.tsx +32 -0
- package/dist/r/src/components/theme/appearance-panel.tsx +179 -0
- package/dist/r/src/components/theme/index.ts +22 -0
- package/dist/r/src/components/theme/theme-picker.tsx +174 -0
- package/dist/r/src/components/theme/theme-provider.tsx +309 -0
- package/dist/r/src/components/theme/theme-toggle.tsx +64 -0
- package/dist/r/src/components/ui/accordion/accordion.tsx +66 -0
- package/dist/r/src/components/ui/accordion/index.ts +1 -0
- package/dist/r/src/components/ui/alert/alert.tsx +95 -0
- package/dist/r/src/components/ui/alert/index.ts +1 -0
- package/dist/r/src/components/ui/alert-dialog/alert-dialog.tsx +176 -0
- package/dist/r/src/components/ui/alert-dialog/index.ts +13 -0
- package/dist/r/src/components/ui/aspect-ratio/aspect-ratio.tsx +38 -0
- package/dist/r/src/components/ui/aspect-ratio/index.ts +1 -0
- package/dist/r/src/components/ui/avatar/avatar.tsx +114 -0
- package/dist/r/src/components/ui/avatar/index.ts +9 -0
- package/dist/r/src/components/ui/badge/badge.tsx +59 -0
- package/dist/r/src/components/ui/badge/index.ts +1 -0
- package/dist/r/src/components/ui/board/board.tsx +254 -0
- package/dist/r/src/components/ui/board/index.ts +10 -0
- package/dist/r/src/components/ui/breadcrumb/breadcrumb.tsx +117 -0
- package/dist/r/src/components/ui/breadcrumb/index.ts +9 -0
- package/dist/r/src/components/ui/button/button.tsx +89 -0
- package/dist/r/src/components/ui/button/index.ts +1 -0
- package/dist/r/src/components/ui/button-group/button-group.tsx +92 -0
- package/dist/r/src/components/ui/button-group/index.ts +6 -0
- package/dist/r/src/components/ui/calendar/calendar.tsx +277 -0
- package/dist/r/src/components/ui/calendar/index.ts +1 -0
- package/dist/r/src/components/ui/card/card.tsx +82 -0
- package/dist/r/src/components/ui/card/index.ts +10 -0
- package/dist/r/src/components/ui/carousel/carousel.tsx +209 -0
- package/dist/r/src/components/ui/carousel/index.ts +1 -0
- package/dist/r/src/components/ui/chart/chart.tsx +413 -0
- package/dist/r/src/components/ui/chart/index.ts +17 -0
- package/dist/r/src/components/ui/checkbox/checkbox.tsx +99 -0
- package/dist/r/src/components/ui/checkbox/index.ts +1 -0
- package/dist/r/src/components/ui/code-block/code-block.tsx +238 -0
- package/dist/r/src/components/ui/code-block/index.ts +1 -0
- package/dist/r/src/components/ui/collapsible/collapsible.tsx +54 -0
- package/dist/r/src/components/ui/collapsible/index.ts +1 -0
- package/dist/r/src/components/ui/combobox/combobox.tsx +142 -0
- package/dist/r/src/components/ui/combobox/index.ts +10 -0
- package/dist/r/src/components/ui/command/command.tsx +214 -0
- package/dist/r/src/components/ui/command/index.ts +12 -0
- package/dist/r/src/components/ui/context-menu/context-menu.tsx +163 -0
- package/dist/r/src/components/ui/context-menu/index.ts +15 -0
- package/dist/r/src/components/ui/detail-panel/detail-panel.tsx +161 -0
- package/dist/r/src/components/ui/detail-panel/index.ts +9 -0
- package/dist/r/src/components/ui/dialog/dialog.tsx +138 -0
- package/dist/r/src/components/ui/dialog/index.ts +14 -0
- package/dist/r/src/components/ui/diff-block/diff-block.tsx +286 -0
- package/dist/r/src/components/ui/diff-block/index.ts +11 -0
- package/dist/r/src/components/ui/drawer/drawer.tsx +144 -0
- package/dist/r/src/components/ui/drawer/index.ts +13 -0
- package/dist/r/src/components/ui/dropdown-menu/dropdown-menu.tsx +203 -0
- package/dist/r/src/components/ui/dropdown-menu/index.ts +18 -0
- package/dist/r/src/components/ui/empty/empty.tsx +104 -0
- package/dist/r/src/components/ui/empty/index.ts +10 -0
- package/dist/r/src/components/ui/entity-icon/entity-icon.tsx +154 -0
- package/dist/r/src/components/ui/entity-icon/index.ts +1 -0
- package/dist/r/src/components/ui/field/field.tsx +131 -0
- package/dist/r/src/components/ui/field/index.ts +11 -0
- package/dist/r/src/components/ui/hover-card/hover-card.tsx +48 -0
- package/dist/r/src/components/ui/hover-card/index.ts +1 -0
- package/dist/r/src/components/ui/input/index.ts +1 -0
- package/dist/r/src/components/ui/input/input.tsx +41 -0
- package/dist/r/src/components/ui/input-group/index.ts +8 -0
- package/dist/r/src/components/ui/input-group/input-group.tsx +111 -0
- package/dist/r/src/components/ui/input-otp/index.ts +1 -0
- package/dist/r/src/components/ui/input-otp/input-otp.tsx +108 -0
- package/dist/r/src/components/ui/item/index.ts +10 -0
- package/dist/r/src/components/ui/item/item.tsx +146 -0
- package/dist/r/src/components/ui/kbd/index.ts +1 -0
- package/dist/r/src/components/ui/kbd/kbd.tsx +42 -0
- package/dist/r/src/components/ui/label/index.ts +1 -0
- package/dist/r/src/components/ui/label/label.tsx +36 -0
- package/dist/r/src/components/ui/menubar/index.ts +16 -0
- package/dist/r/src/components/ui/menubar/menubar.tsx +190 -0
- package/dist/r/src/components/ui/modal-dialog/index.ts +1 -0
- package/dist/r/src/components/ui/modal-dialog/modal-dialog.tsx +95 -0
- package/dist/r/src/components/ui/navigation-menu/index.ts +13 -0
- package/dist/r/src/components/ui/navigation-menu/navigation-menu.tsx +229 -0
- package/dist/r/src/components/ui/pagination/index.ts +8 -0
- package/dist/r/src/components/ui/pagination/pagination.tsx +144 -0
- package/dist/r/src/components/ui/popover/index.ts +10 -0
- package/dist/r/src/components/ui/popover/popover.tsx +81 -0
- package/dist/r/src/components/ui/progress/index.ts +1 -0
- package/dist/r/src/components/ui/progress/progress.tsx +67 -0
- package/dist/r/src/components/ui/radio-group/index.ts +1 -0
- package/dist/r/src/components/ui/radio-group/radio-group.tsx +78 -0
- package/dist/r/src/components/ui/resizable/index.ts +6 -0
- package/dist/r/src/components/ui/resizable/resizable.tsx +74 -0
- package/dist/r/src/components/ui/scroll-area/index.ts +1 -0
- package/dist/r/src/components/ui/scroll-area/scroll-area.tsx +66 -0
- package/dist/r/src/components/ui/select/index.ts +11 -0
- package/dist/r/src/components/ui/select/select.tsx +188 -0
- package/dist/r/src/components/ui/separator/index.ts +1 -0
- package/dist/r/src/components/ui/separator/separator.tsx +40 -0
- package/dist/r/src/components/ui/sheet/index.ts +13 -0
- package/dist/r/src/components/ui/sheet/sheet.tsx +132 -0
- package/dist/r/src/components/ui/skeleton/index.ts +1 -0
- package/dist/r/src/components/ui/skeleton/skeleton.tsx +28 -0
- package/dist/r/src/components/ui/slider/index.ts +1 -0
- package/dist/r/src/components/ui/slider/slider.tsx +81 -0
- package/dist/r/src/components/ui/spinner/index.ts +1 -0
- package/dist/r/src/components/ui/spinner/spinner.tsx +58 -0
- package/dist/r/src/components/ui/status-chip/index.ts +8 -0
- package/dist/r/src/components/ui/status-chip/status-chip.tsx +142 -0
- package/dist/r/src/components/ui/switch/index.ts +1 -0
- package/dist/r/src/components/ui/switch/switch.tsx +100 -0
- package/dist/r/src/components/ui/table/index.ts +11 -0
- package/dist/r/src/components/ui/table/table.tsx +125 -0
- package/dist/r/src/components/ui/tabs/index.ts +8 -0
- package/dist/r/src/components/ui/tabs/tabs.tsx +77 -0
- package/dist/r/src/components/ui/textarea/index.ts +1 -0
- package/dist/r/src/components/ui/textarea/textarea.tsx +36 -0
- package/dist/r/src/components/ui/toast/index.ts +1 -0
- package/dist/r/src/components/ui/toast/toast.tsx +220 -0
- package/dist/r/src/components/ui/toggle/index.ts +1 -0
- package/dist/r/src/components/ui/toggle/toggle.tsx +60 -0
- package/dist/r/src/components/ui/toggle-group/index.ts +6 -0
- package/dist/r/src/components/ui/toggle-group/toggle-group.tsx +66 -0
- package/dist/r/src/components/ui/tooltip/index.ts +9 -0
- package/dist/r/src/components/ui/tooltip/tooltip.tsx +102 -0
- package/dist/r/src/lib/keyed-rows.ts +68 -0
- package/dist/r/src/lib/overlay.ts +70 -0
- package/dist/r/src/lib/themes.ts +490 -0
- package/dist/r/src/lib/tokens.ts +688 -0
- package/dist/r/src/lib/utils.ts +76 -0
- package/dist/r/src/lib/variants.ts +62 -0
- package/dist/r/src/lib/version-notes.ts +65 -0
- package/dist/r/src/styles/base.css +644 -0
- package/dist/r/src/styles/fonts.css +26 -0
- package/dist/r/src/styles/globals.css +18 -0
- package/dist/r/src/styles/theme.css +864 -0
- package/dist/r/stat.json +3 -1
- package/dist/r/status-bar.json +3 -1
- package/dist/r/status-chip.json +2 -1
- package/dist/r/switch.json +3 -1
- package/dist/r/table.json +3 -1
- package/dist/r/tabs.json +3 -1
- package/dist/r/textarea.json +3 -1
- package/dist/r/theming.json +42 -0
- package/dist/r/toast.json +3 -1
- package/dist/r/toggle-group.json +2 -1
- package/dist/r/toggle.json +3 -1
- package/dist/r/tooltip.json +3 -1
- package/dist/r/top-bar.json +3 -1
- package/dist/r/update-dialog.json +2 -1
- package/dist/r/workspace-mark.json +2 -1
- package/package.json +10 -9
- package/registry.json +320 -73
- package/src/index.ts +2 -4
- package/dist/components/ui/diff-block/diff-block.test.d.ts +0 -2
- package/dist/components/ui/diff-block/diff-block.test.d.ts.map +0 -1
- package/dist/lib/keyed-rows.test.d.ts +0 -2
- package/dist/lib/keyed-rows.test.d.ts.map +0 -1
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
import { Polymorphic, type PolymorphicProps } from '@kobalte/core/polymorphic'
|
|
2
|
+
import type { ComponentProps, JSX, ValidComponent } from 'solid-js'
|
|
3
|
+
import { Show, createMemo, createSignal, splitProps } from 'solid-js'
|
|
4
|
+
import { Portal } from 'solid-js/web'
|
|
5
|
+
import { cn } from '../../../lib/utils'
|
|
6
|
+
import { SideRailContext, createSideRailValue } from './side-rail-context'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* SideRail.
|
|
10
|
+
*
|
|
11
|
+
* The narrow, persistent column of top-level destinations at the window's leading
|
|
12
|
+
* edge: the app mark, the primary sections, and a footer for account and settings.
|
|
13
|
+
* It is the one piece of chrome that is always on screen, which is why it is a
|
|
14
|
+
* component rather than something each view arranges.
|
|
15
|
+
*
|
|
16
|
+
* Two forms, one component. Collapsed it is an icon column; expanded it carries
|
|
17
|
+
* labels. Both widths come from `--rail-width` (74px) and `--rail-width-expanded`
|
|
18
|
+
* (236px), so an app cannot pick its own and end up a few pixels out of step with
|
|
19
|
+
* the app beside it.
|
|
20
|
+
*
|
|
21
|
+
* The rail does not own its collapsed state; the app does. That state has to
|
|
22
|
+
* survive a reload and usually lives with the rest of the window preferences, and
|
|
23
|
+
* a rail that kept it privately could not be restored.
|
|
24
|
+
*/
|
|
25
|
+
export type SideRailProps<T extends ValidComponent = 'nav'> = PolymorphicProps<
|
|
26
|
+
T,
|
|
27
|
+
{
|
|
28
|
+
class?: string
|
|
29
|
+
collapsed?: boolean
|
|
30
|
+
'aria-label'?: string
|
|
31
|
+
}
|
|
32
|
+
>
|
|
33
|
+
|
|
34
|
+
export function SideRail<T extends ValidComponent = 'nav'>(props: SideRailProps<T>) {
|
|
35
|
+
const [local, rest] = splitProps(props as SideRailProps, [
|
|
36
|
+
'class',
|
|
37
|
+
'collapsed',
|
|
38
|
+
'aria-label',
|
|
39
|
+
'children',
|
|
40
|
+
])
|
|
41
|
+
|
|
42
|
+
const collapsed = () => local.collapsed ?? false
|
|
43
|
+
const value = createSideRailValue(
|
|
44
|
+
collapsed,
|
|
45
|
+
createMemo(() => (collapsed() ? 'var(--rail-width)' : 'var(--rail-width-expanded)'))
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<SideRailContext.Provider value={value}>
|
|
50
|
+
<Polymorphic
|
|
51
|
+
as="nav"
|
|
52
|
+
aria-label={local['aria-label'] ?? 'Primary'}
|
|
53
|
+
/* Rendered in both states, with a real value, so the descendant selectors
|
|
54
|
+
below can key on true *and* false — an absent attribute would leave the
|
|
55
|
+
expanded form matching neither. */
|
|
56
|
+
data-collapsed={collapsed() ? 'true' : 'false'}
|
|
57
|
+
class={cn(
|
|
58
|
+
'group/rail bg-sidebar text-sidebar-foreground flex h-full shrink-0 flex-col border-e border-sidebar-border',
|
|
59
|
+
'transition-[width] duration-150 ease-out',
|
|
60
|
+
{ 'w-rail': collapsed(), 'w-rail-expanded': !collapsed() },
|
|
61
|
+
local.class
|
|
62
|
+
)}
|
|
63
|
+
{...(rest as ComponentProps<'nav'>)}
|
|
64
|
+
>
|
|
65
|
+
{local.children}
|
|
66
|
+
</Polymorphic>
|
|
67
|
+
</SideRailContext.Provider>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** The header slot: the app mark, and the product name when there is room. */
|
|
72
|
+
export function SideRailHeader(props: ComponentProps<'div'>) {
|
|
73
|
+
const [local, rest] = splitProps(props, ['class'])
|
|
74
|
+
return (
|
|
75
|
+
<div
|
|
76
|
+
data-slot="side-rail-header"
|
|
77
|
+
class={cn(
|
|
78
|
+
'h-topbar flex shrink-0 items-center gap-2.5 px-3 group-data-[collapsed=true]/rail:justify-center group-data-[collapsed=true]/rail:px-0',
|
|
79
|
+
local.class
|
|
80
|
+
)}
|
|
81
|
+
{...rest}
|
|
82
|
+
/>
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** The scrolling middle. Keeps the header and the footer pinned. */
|
|
87
|
+
export function SideRailContent(props: ComponentProps<'div'>) {
|
|
88
|
+
const [local, rest] = splitProps(props, ['class'])
|
|
89
|
+
return (
|
|
90
|
+
<div
|
|
91
|
+
data-slot="side-rail-content"
|
|
92
|
+
class={cn(
|
|
93
|
+
'flex min-h-0 flex-1 flex-col gap-0.5 overflow-x-hidden overflow-y-auto px-2 py-2',
|
|
94
|
+
local.class
|
|
95
|
+
)}
|
|
96
|
+
{...rest}
|
|
97
|
+
/>
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** The pinned footer: account, settings, help. */
|
|
102
|
+
export function SideRailFooter(props: ComponentProps<'div'>) {
|
|
103
|
+
const [local, rest] = splitProps(props, ['class'])
|
|
104
|
+
return (
|
|
105
|
+
<div
|
|
106
|
+
data-slot="side-rail-footer"
|
|
107
|
+
class={cn(
|
|
108
|
+
'flex shrink-0 flex-col gap-0.5 border-t border-sidebar-border px-2 py-2',
|
|
109
|
+
local.class
|
|
110
|
+
)}
|
|
111
|
+
{...rest}
|
|
112
|
+
/>
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** A titled group of rail destinations. The title hides when collapsed. */
|
|
117
|
+
export function SideRailSection(props: ComponentProps<'div'> & { label?: string }) {
|
|
118
|
+
const [local, rest] = splitProps(props, ['class', 'label', 'children'])
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<div data-slot="side-rail-section" class={cn('flex flex-col gap-0.5', local.class)} {...rest}>
|
|
122
|
+
<Show when={local.label}>
|
|
123
|
+
<div class="text-sidebar-muted-foreground px-2 pt-2 pb-1 text-2xs font-medium tracking-wide uppercase group-data-[collapsed=true]/rail:hidden">
|
|
124
|
+
{local.label}
|
|
125
|
+
</div>
|
|
126
|
+
</Show>
|
|
127
|
+
{local.children}
|
|
128
|
+
</div>
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* The classes a rail row uses. Exported so a caller's own element — a router link,
|
|
134
|
+
* a custom button — can match the rail exactly instead of approximating it.
|
|
135
|
+
*/
|
|
136
|
+
export const sideRailItemClass = [
|
|
137
|
+
'group/rail-item flex h-rail-item min-w-0 w-full items-center gap-2.5 rounded-md px-3 text-sm font-medium',
|
|
138
|
+
'group-data-[collapsed=true]/rail:justify-center group-data-[collapsed=true]/rail:px-0',
|
|
139
|
+
'transition-[color,background-color,transform] ease-out outline-none select-none',
|
|
140
|
+
'focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-primary-subtle',
|
|
141
|
+
/*
|
|
142
|
+
* The donor's row scales up slightly under the pointer and down under a press.
|
|
143
|
+
* It is a CSS transform rather than a motion-library gesture because the two are
|
|
144
|
+
* the same thing here and the transform costs nothing at runtime: no wrapper
|
|
145
|
+
* element, no listener, and it works on whatever element a caller renders the row
|
|
146
|
+
* as — an anchor, a button, a router's own link.
|
|
147
|
+
*
|
|
148
|
+
* The collapsed rail opts out: a 1.02 scale on a 74px column pushes the row past
|
|
149
|
+
* the rail's own edge, which reads as a glitch rather than as feedback.
|
|
150
|
+
*/
|
|
151
|
+
'hover:scale-[1.02] active:scale-[0.97] group-data-[collapsed=true]/rail:hover:scale-100 group-data-[collapsed=true]/rail:active:scale-[0.98]',
|
|
152
|
+
'[&_svg]:size-4 [&_svg]:shrink-0',
|
|
153
|
+
].join(' ')
|
|
154
|
+
|
|
155
|
+
export const sideRailItemStateClass = {
|
|
156
|
+
active: 'bg-sidebar-accent text-sidebar-accent-foreground',
|
|
157
|
+
idle: 'text-sidebar-muted-foreground hover:bg-sidebar-accent/60 hover:text-sidebar-foreground',
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* A rail destination.
|
|
162
|
+
*
|
|
163
|
+
* ## The tooltip is the rail's hover affordance, not a generic bubble
|
|
164
|
+
*
|
|
165
|
+
* Collapsed, the row has no text, so the label has to come from somewhere. This
|
|
166
|
+
* ports the donor's treatment rather than using a standard tooltip, because the
|
|
167
|
+
* details are what make it read as part of the rail:
|
|
168
|
+
*
|
|
169
|
+
* - It is **flush with the row**: positioned at the row's own top and given the
|
|
170
|
+
* row's own height, so it continues the row outward instead of floating near
|
|
171
|
+
* it. A centred bubble with an arrow reads as a separate object.
|
|
172
|
+
* - It **repeats the icon**, which anchors it to the row it belongs to.
|
|
173
|
+
* - It carries the **keyboard chord**, which is the one piece of information a
|
|
174
|
+
* collapsed rail otherwise cannot show.
|
|
175
|
+
* - It opens on **hover and on focus**, so a keyboard user gets the same label a
|
|
176
|
+
* pointer user does.
|
|
177
|
+
* - It is `pointer-events-none` and `aria-hidden`: the row already carries the
|
|
178
|
+
* name (as a visually hidden span when collapsed), so this is decoration and
|
|
179
|
+
* must never be the accessible name.
|
|
180
|
+
*
|
|
181
|
+
* ## The chord on the row
|
|
182
|
+
*
|
|
183
|
+
* Expanded, the row shows its chord on hover and on focus — and the chord is
|
|
184
|
+
* declared once to assistive technology through `aria-keyshortcuts` rather than
|
|
185
|
+
* being read out as glyphs. The visible badge is `aria-hidden` for that reason.
|
|
186
|
+
*/
|
|
187
|
+
export type SideRailItemProps<T extends ValidComponent = 'a'> = PolymorphicProps<
|
|
188
|
+
T,
|
|
189
|
+
{
|
|
190
|
+
class?: string
|
|
191
|
+
/** Marks the item as the current destination. */
|
|
192
|
+
active?: boolean
|
|
193
|
+
/**
|
|
194
|
+
* The item's accessible name. Required, and always in the accessibility tree:
|
|
195
|
+
* collapsed it moves into the tooltip and a visually hidden span rather than
|
|
196
|
+
* being deleted.
|
|
197
|
+
*/
|
|
198
|
+
label: string
|
|
199
|
+
/** A count or a dot, rendered over the icon's corner. */
|
|
200
|
+
badge?: JSX.Element
|
|
201
|
+
/** An indicator pinned to the trailing edge in the expanded form. */
|
|
202
|
+
trailing?: JSX.Element
|
|
203
|
+
/** The chord as it should be drawn, e.g. `⌘1`. Shown on hover and focus. */
|
|
204
|
+
shortcut?: string
|
|
205
|
+
/**
|
|
206
|
+
* The same chord in the ARIA grammar, e.g. `Meta+1`. Declared to assistive
|
|
207
|
+
* technology instead of the display glyphs, which are not a keyboard shortcut
|
|
208
|
+
* a screen reader can parse. Supply it whenever `shortcut` is drawn.
|
|
209
|
+
*/
|
|
210
|
+
keyshortcuts?: string
|
|
211
|
+
}
|
|
212
|
+
>
|
|
213
|
+
|
|
214
|
+
export function SideRailItem<T extends ValidComponent = 'a'>(props: SideRailItemProps<T>) {
|
|
215
|
+
const [local, rest] = splitProps(props as SideRailItemProps, [
|
|
216
|
+
'as',
|
|
217
|
+
'class',
|
|
218
|
+
'active',
|
|
219
|
+
'label',
|
|
220
|
+
'badge',
|
|
221
|
+
'trailing',
|
|
222
|
+
'shortcut',
|
|
223
|
+
'keyshortcuts',
|
|
224
|
+
'children',
|
|
225
|
+
])
|
|
226
|
+
|
|
227
|
+
const [row, setRow] = createSignal<HTMLElement>()
|
|
228
|
+
const [tip, setTip] = createSignal<{ top: number; left: number; height: number } | null>(null)
|
|
229
|
+
|
|
230
|
+
const collapsed = () =>
|
|
231
|
+
row()?.closest('[data-collapsed]')?.getAttribute('data-collapsed') === 'true'
|
|
232
|
+
|
|
233
|
+
const showTip = () => {
|
|
234
|
+
const element = row()
|
|
235
|
+
if (!element || !collapsed()) return
|
|
236
|
+
const rect = element.getBoundingClientRect()
|
|
237
|
+
setTip({ top: rect.top, left: rect.right + 8, height: rect.height })
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const hideTip = () => setTip(null)
|
|
241
|
+
|
|
242
|
+
return (
|
|
243
|
+
<>
|
|
244
|
+
<Polymorphic
|
|
245
|
+
as={local.as ?? 'a'}
|
|
246
|
+
ref={setRow}
|
|
247
|
+
aria-current={local.active ? 'page' : undefined}
|
|
248
|
+
aria-keyshortcuts={local.keyshortcuts}
|
|
249
|
+
data-active={local.active ? '' : undefined}
|
|
250
|
+
class={cn(
|
|
251
|
+
sideRailItemClass,
|
|
252
|
+
local.active ? sideRailItemStateClass.active : sideRailItemStateClass.idle,
|
|
253
|
+
local.class
|
|
254
|
+
)}
|
|
255
|
+
onPointerEnter={showTip}
|
|
256
|
+
onPointerLeave={hideTip}
|
|
257
|
+
onFocus={showTip}
|
|
258
|
+
onBlur={hideTip}
|
|
259
|
+
{...(rest as Record<string, unknown>)}
|
|
260
|
+
>
|
|
261
|
+
<Show when={local.badge}>
|
|
262
|
+
<span class="relative flex size-4 shrink-0 items-center justify-center [&_svg]:size-4">
|
|
263
|
+
{local.children}
|
|
264
|
+
<span class="absolute -top-1 -end-1 flex items-center justify-center">
|
|
265
|
+
{local.badge}
|
|
266
|
+
</span>
|
|
267
|
+
</span>
|
|
268
|
+
</Show>
|
|
269
|
+
<Show when={!local.badge}>
|
|
270
|
+
<span class="flex size-4 shrink-0 items-center justify-center [&_svg]:size-4">
|
|
271
|
+
{local.children}
|
|
272
|
+
</span>
|
|
273
|
+
</Show>
|
|
274
|
+
|
|
275
|
+
<span class="min-w-0 flex-1 truncate group-data-[collapsed=true]/rail:sr-only">
|
|
276
|
+
{local.label}
|
|
277
|
+
</span>
|
|
278
|
+
|
|
279
|
+
<Show when={local.shortcut && local.trailing === undefined}>
|
|
280
|
+
<span
|
|
281
|
+
aria-hidden="true"
|
|
282
|
+
class="text-sidebar-muted-foreground shrink-0 font-mono text-2xs leading-none opacity-0 transition-opacity group-hover/rail-item:opacity-100 group-focus-visible/rail-item:opacity-100 group-data-[collapsed=true]/rail:hidden"
|
|
283
|
+
>
|
|
284
|
+
{local.shortcut}
|
|
285
|
+
</span>
|
|
286
|
+
</Show>
|
|
287
|
+
|
|
288
|
+
<Show when={local.trailing}>
|
|
289
|
+
<span class="ms-auto shrink-0">{local.trailing}</span>
|
|
290
|
+
</Show>
|
|
291
|
+
</Polymorphic>
|
|
292
|
+
|
|
293
|
+
<Show when={tip()}>
|
|
294
|
+
{(position) => (
|
|
295
|
+
<Portal>
|
|
296
|
+
<div
|
|
297
|
+
aria-hidden="true"
|
|
298
|
+
data-slot="side-rail-tip"
|
|
299
|
+
class={cn(
|
|
300
|
+
'bg-card text-card-foreground border-border pointer-events-none fixed z-(--z-tooltip)',
|
|
301
|
+
'flex items-center gap-2.5 rounded-md border ps-3 pe-3 text-sm font-medium whitespace-nowrap shadow-lg'
|
|
302
|
+
)}
|
|
303
|
+
style={{
|
|
304
|
+
top: `${position().top}px`,
|
|
305
|
+
left: `${position().left}px`,
|
|
306
|
+
height: `${position().height}px`,
|
|
307
|
+
}}
|
|
308
|
+
>
|
|
309
|
+
<span
|
|
310
|
+
class={cn('flex size-4 shrink-0 items-center justify-center [&_svg]:size-4', {
|
|
311
|
+
'text-primary': local.active,
|
|
312
|
+
})}
|
|
313
|
+
>
|
|
314
|
+
{local.children}
|
|
315
|
+
</span>
|
|
316
|
+
{local.label}
|
|
317
|
+
<Show when={local.shortcut}>
|
|
318
|
+
<span class="text-muted-foreground shrink-0 font-mono text-2xs leading-none">
|
|
319
|
+
{local.shortcut}
|
|
320
|
+
</span>
|
|
321
|
+
</Show>
|
|
322
|
+
</div>
|
|
323
|
+
</Portal>
|
|
324
|
+
)}
|
|
325
|
+
</Show>
|
|
326
|
+
</>
|
|
327
|
+
)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/** A non-navigating rail control: a collapse toggle, a "new" action. */
|
|
331
|
+
export function SideRailButton(
|
|
332
|
+
props: Omit<ComponentProps<'button'>, 'type'> & { label: string; shortcut?: string }
|
|
333
|
+
) {
|
|
334
|
+
const [local, rest] = splitProps(props, ['class', 'label', 'shortcut', 'children'])
|
|
335
|
+
const [row, setRow] = createSignal<HTMLElement>()
|
|
336
|
+
const [tip, setTip] = createSignal<{ top: number; left: number; height: number } | null>(null)
|
|
337
|
+
|
|
338
|
+
const collapsed = () =>
|
|
339
|
+
row()?.closest('[data-collapsed]')?.getAttribute('data-collapsed') === 'true'
|
|
340
|
+
|
|
341
|
+
const showTip = () => {
|
|
342
|
+
const element = row()
|
|
343
|
+
if (!element || !collapsed()) return
|
|
344
|
+
const rect = element.getBoundingClientRect()
|
|
345
|
+
setTip({ top: rect.top, left: rect.right + 8, height: rect.height })
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return (
|
|
349
|
+
<>
|
|
350
|
+
<button
|
|
351
|
+
type="button"
|
|
352
|
+
ref={setRow}
|
|
353
|
+
aria-label={local.label}
|
|
354
|
+
onPointerEnter={showTip}
|
|
355
|
+
onPointerLeave={() => setTip(null)}
|
|
356
|
+
onFocus={showTip}
|
|
357
|
+
onBlur={() => setTip(null)}
|
|
358
|
+
class={cn(
|
|
359
|
+
sideRailItemClass,
|
|
360
|
+
sideRailItemStateClass.idle,
|
|
361
|
+
'disabled:pointer-events-none disabled:opacity-50',
|
|
362
|
+
local.class
|
|
363
|
+
)}
|
|
364
|
+
{...rest}
|
|
365
|
+
>
|
|
366
|
+
<span class="flex size-4 shrink-0 items-center justify-center [&_svg]:size-4">
|
|
367
|
+
{local.children}
|
|
368
|
+
</span>
|
|
369
|
+
<span class="min-w-0 flex-1 truncate group-data-[collapsed=true]/rail:sr-only">
|
|
370
|
+
{local.label}
|
|
371
|
+
</span>
|
|
372
|
+
<Show when={local.shortcut}>
|
|
373
|
+
<span
|
|
374
|
+
aria-hidden="true"
|
|
375
|
+
class="text-sidebar-muted-foreground shrink-0 font-mono text-2xs leading-none opacity-0 transition-opacity group-hover/rail-item:opacity-100 group-focus-visible/rail-item:opacity-100 group-data-[collapsed=true]/rail:hidden"
|
|
376
|
+
>
|
|
377
|
+
{local.shortcut}
|
|
378
|
+
</span>
|
|
379
|
+
</Show>
|
|
380
|
+
</button>
|
|
381
|
+
|
|
382
|
+
<Show when={tip()}>
|
|
383
|
+
{(position) => (
|
|
384
|
+
<Portal>
|
|
385
|
+
<div
|
|
386
|
+
aria-hidden="true"
|
|
387
|
+
data-slot="side-rail-tip"
|
|
388
|
+
class="bg-card text-card-foreground border-border pointer-events-none fixed z-(--z-tooltip) flex items-center gap-2.5 rounded-md border ps-3 pe-3 text-sm font-medium whitespace-nowrap shadow-lg"
|
|
389
|
+
style={{
|
|
390
|
+
top: `${position().top}px`,
|
|
391
|
+
left: `${position().left}px`,
|
|
392
|
+
height: `${position().height}px`,
|
|
393
|
+
}}
|
|
394
|
+
>
|
|
395
|
+
<span class="flex size-4 shrink-0 items-center justify-center [&_svg]:size-4">
|
|
396
|
+
{local.children}
|
|
397
|
+
</span>
|
|
398
|
+
{local.label}
|
|
399
|
+
</div>
|
|
400
|
+
</Portal>
|
|
401
|
+
)}
|
|
402
|
+
</Show>
|
|
403
|
+
</>
|
|
404
|
+
)
|
|
405
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export {
|
|
2
|
+
SidebarNav,
|
|
3
|
+
SidebarNavButton,
|
|
4
|
+
SidebarNavContent,
|
|
5
|
+
SidebarNavFooter,
|
|
6
|
+
SidebarNavHeader,
|
|
7
|
+
SidebarNavItem,
|
|
8
|
+
SidebarNavSection,
|
|
9
|
+
SidebarNavTitle,
|
|
10
|
+
sidebarNavItemClass,
|
|
11
|
+
sidebarNavItemStateClass,
|
|
12
|
+
type SidebarNavItemProps,
|
|
13
|
+
type SidebarNavProps,
|
|
14
|
+
} from './sidebar-nav'
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import type { PolymorphicProps } from '@kobalte/core/polymorphic'
|
|
2
|
+
import { Polymorphic } from '@kobalte/core/polymorphic'
|
|
3
|
+
import { ChevronRight } from 'lucide-solid'
|
|
4
|
+
import type { ComponentProps, JSX, ValidComponent } from 'solid-js'
|
|
5
|
+
import { Show, createSignal, splitProps } from 'solid-js'
|
|
6
|
+
import { cn } from '../../../lib/utils'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* SidebarNav.
|
|
10
|
+
*
|
|
11
|
+
* The secondary navigation column: the list of things inside the destination
|
|
12
|
+
* the rail selected — projects, sessions, files, threads. It is a different
|
|
13
|
+
* object from the rail, not a wider version of it, which is why it is a separate
|
|
14
|
+
* component rather than the rail with labels on.
|
|
15
|
+
*
|
|
16
|
+
* Rows share one recipe with the rail's, so a row in a sidebar and a row in a
|
|
17
|
+
* rail read as the same kind of thing at the same density. That recipe is
|
|
18
|
+
* exactly KiroCrew's nav row: `text-sm font-medium`, `gap-2.5`, `px-3 py-2`,
|
|
19
|
+
* `rounded-md`, `transition-colors`.
|
|
20
|
+
*
|
|
21
|
+
* The width comes from `--sidebar-width`. Sections collapse by default only when
|
|
22
|
+
* the caller says so, because a section that starts collapsed and holds the
|
|
23
|
+
* user's own work is a hidden default.
|
|
24
|
+
*/
|
|
25
|
+
export type SidebarNavProps<T extends ValidComponent = 'nav'> = PolymorphicProps<
|
|
26
|
+
T,
|
|
27
|
+
{ class?: string; 'aria-label'?: string }
|
|
28
|
+
>
|
|
29
|
+
|
|
30
|
+
export function SidebarNav<T extends ValidComponent = 'nav'>(props: SidebarNavProps<T>) {
|
|
31
|
+
const [local, rest] = splitProps(props as SidebarNavProps, ['class', 'aria-label', 'children'])
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<Polymorphic
|
|
35
|
+
as="nav"
|
|
36
|
+
aria-label={local['aria-label'] ?? 'Section'}
|
|
37
|
+
class={cn(
|
|
38
|
+
'bg-sidebar text-sidebar-foreground flex h-full w-sidebar shrink-0 flex-col border-e border-sidebar-border',
|
|
39
|
+
local.class
|
|
40
|
+
)}
|
|
41
|
+
{...(rest as ComponentProps<'nav'>)}
|
|
42
|
+
>
|
|
43
|
+
{local.children}
|
|
44
|
+
</Polymorphic>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function SidebarNavHeader(props: ComponentProps<'div'>) {
|
|
49
|
+
const [local, rest] = splitProps(props, ['class'])
|
|
50
|
+
return (
|
|
51
|
+
<div
|
|
52
|
+
data-slot="sidebar-nav-header"
|
|
53
|
+
class={cn('flex h-topbar shrink-0 items-center justify-between gap-2 px-3', local.class)}
|
|
54
|
+
{...rest}
|
|
55
|
+
/>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function SidebarNavTitle(props: ComponentProps<'div'>) {
|
|
60
|
+
const [local, rest] = splitProps(props, ['class'])
|
|
61
|
+
return (
|
|
62
|
+
<div
|
|
63
|
+
data-slot="sidebar-nav-title"
|
|
64
|
+
class={cn('truncate text-sm font-semibold tracking-tight', local.class)}
|
|
65
|
+
{...rest}
|
|
66
|
+
/>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** The scrolling middle of the sidebar. */
|
|
71
|
+
export function SidebarNavContent(props: ComponentProps<'div'>) {
|
|
72
|
+
const [local, rest] = splitProps(props, ['class'])
|
|
73
|
+
return (
|
|
74
|
+
<div
|
|
75
|
+
data-slot="sidebar-nav-content"
|
|
76
|
+
class={cn('flex min-h-0 flex-1 flex-col gap-0.5 overflow-y-auto px-2 py-2', local.class)}
|
|
77
|
+
{...rest}
|
|
78
|
+
/>
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function SidebarNavFooter(props: ComponentProps<'div'>) {
|
|
83
|
+
const [local, rest] = splitProps(props, ['class'])
|
|
84
|
+
return (
|
|
85
|
+
<div
|
|
86
|
+
data-slot="sidebar-nav-footer"
|
|
87
|
+
class={cn('flex shrink-0 items-center gap-1 border-t border-sidebar-border p-2', local.class)}
|
|
88
|
+
{...rest}
|
|
89
|
+
/>
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* A titled, collapsible group. Collapsible because a list of projects
|
|
95
|
+
* inevitably outgrows the column, and the user needs to put the ones they are
|
|
96
|
+
* not using away *without* them disappearing into a menu.
|
|
97
|
+
*/
|
|
98
|
+
export function SidebarNavSection(
|
|
99
|
+
props: ComponentProps<'div'> & {
|
|
100
|
+
label: string
|
|
101
|
+
/** Render a disclosure control instead of a plain heading. */
|
|
102
|
+
collapsible?: boolean
|
|
103
|
+
defaultOpen?: boolean
|
|
104
|
+
/** Count shown beside the label. */
|
|
105
|
+
count?: number
|
|
106
|
+
/** A control at the trailing edge of the heading row. */
|
|
107
|
+
action?: JSX.Element
|
|
108
|
+
}
|
|
109
|
+
) {
|
|
110
|
+
const [local, rest] = splitProps(props, [
|
|
111
|
+
'class',
|
|
112
|
+
'label',
|
|
113
|
+
'collapsible',
|
|
114
|
+
'defaultOpen',
|
|
115
|
+
'count',
|
|
116
|
+
'action',
|
|
117
|
+
'children',
|
|
118
|
+
])
|
|
119
|
+
const [open, setOpen] = createSignal(local.defaultOpen ?? true)
|
|
120
|
+
|
|
121
|
+
const heading = (
|
|
122
|
+
<>
|
|
123
|
+
<Show when={local.collapsible}>
|
|
124
|
+
<ChevronRight
|
|
125
|
+
aria-hidden="true"
|
|
126
|
+
class={cn('size-3.5 shrink-0 text-muted-foreground transition-transform ease-out', {
|
|
127
|
+
'rotate-90': open(),
|
|
128
|
+
})}
|
|
129
|
+
/>
|
|
130
|
+
</Show>
|
|
131
|
+
<span class="min-w-0 flex-1 truncate">{local.label}</span>
|
|
132
|
+
<Show when={local.count !== undefined}>
|
|
133
|
+
<span class="shrink-0 tabular-nums text-muted-foreground">{local.count}</span>
|
|
134
|
+
</Show>
|
|
135
|
+
</>
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
return (
|
|
139
|
+
<div data-slot="sidebar-nav-section" class={cn('flex flex-col gap-0.5', local.class)} {...rest}>
|
|
140
|
+
<div class="group/section-header flex items-center gap-1 rounded-md px-2 py-1.5 hover:bg-sidebar-accent/60">
|
|
141
|
+
<Show
|
|
142
|
+
when={local.collapsible}
|
|
143
|
+
fallback={
|
|
144
|
+
<div class="flex min-w-0 flex-1 items-center gap-1.5 text-2xs font-medium tracking-wide text-sidebar-muted-foreground uppercase">
|
|
145
|
+
{heading}
|
|
146
|
+
</div>
|
|
147
|
+
}
|
|
148
|
+
>
|
|
149
|
+
<button
|
|
150
|
+
type="button"
|
|
151
|
+
aria-expanded={open()}
|
|
152
|
+
onClick={() => setOpen((value) => !value)}
|
|
153
|
+
class="flex min-w-0 flex-1 items-center gap-1.5 rounded-sm text-2xs font-medium tracking-wide text-sidebar-muted-foreground uppercase outline-none transition-colors ease-out hover:text-sidebar-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-primary-subtle"
|
|
154
|
+
>
|
|
155
|
+
{heading}
|
|
156
|
+
</button>
|
|
157
|
+
</Show>
|
|
158
|
+
<Show when={local.action}>
|
|
159
|
+
<span class="shrink-0 opacity-0 transition-opacity ease-out group-hover/section-header:opacity-100 focus-within:opacity-100">
|
|
160
|
+
{local.action}
|
|
161
|
+
</span>
|
|
162
|
+
</Show>
|
|
163
|
+
</div>
|
|
164
|
+
<Show when={!local.collapsible || open()}>
|
|
165
|
+
<div class="flex flex-col gap-0.5">{local.children}</div>
|
|
166
|
+
</Show>
|
|
167
|
+
</div>
|
|
168
|
+
)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export type SidebarNavItemProps<T extends ValidComponent = 'a'> = PolymorphicProps<
|
|
172
|
+
T,
|
|
173
|
+
{
|
|
174
|
+
class?: string
|
|
175
|
+
active?: boolean
|
|
176
|
+
/** Indent one level, for a child row such as a branch under a project. */
|
|
177
|
+
nested?: boolean
|
|
178
|
+
/** Trailing slot: a count, a status dot, a hover action. */
|
|
179
|
+
trailing?: JSX.Element
|
|
180
|
+
}
|
|
181
|
+
>
|
|
182
|
+
|
|
183
|
+
/** A sidebar row. Exported classes, so a caller's own element can match it. */
|
|
184
|
+
export const sidebarNavItemClass = [
|
|
185
|
+
'group/nav-item relative flex min-w-0 items-center gap-2.5 rounded-md px-3 py-2 text-sm font-medium',
|
|
186
|
+
'whitespace-nowrap transition-colors ease-out outline-none select-none',
|
|
187
|
+
'focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-primary-subtle',
|
|
188
|
+
'[&_svg]:size-4 [&_svg]:shrink-0',
|
|
189
|
+
].join(' ')
|
|
190
|
+
|
|
191
|
+
export const sidebarNavItemStateClass = {
|
|
192
|
+
active: 'bg-primary-subtle text-foreground',
|
|
193
|
+
idle: 'text-muted-foreground hover:bg-surface-hover hover:text-foreground',
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function SidebarNavItem<T extends ValidComponent = 'a'>(props: SidebarNavItemProps<T>) {
|
|
197
|
+
const [local, rest] = splitProps(props as SidebarNavItemProps, [
|
|
198
|
+
'class',
|
|
199
|
+
'active',
|
|
200
|
+
'nested',
|
|
201
|
+
'trailing',
|
|
202
|
+
'children',
|
|
203
|
+
])
|
|
204
|
+
|
|
205
|
+
return (
|
|
206
|
+
<Polymorphic
|
|
207
|
+
as="a"
|
|
208
|
+
aria-current={local.active ? 'page' : undefined}
|
|
209
|
+
data-active={local.active ? '' : undefined}
|
|
210
|
+
class={cn(
|
|
211
|
+
sidebarNavItemClass,
|
|
212
|
+
local.active ? sidebarNavItemStateClass.active : sidebarNavItemStateClass.idle,
|
|
213
|
+
{ 'ps-8': local.nested },
|
|
214
|
+
local.class
|
|
215
|
+
)}
|
|
216
|
+
{...(rest as ComponentProps<'a'>)}
|
|
217
|
+
>
|
|
218
|
+
{local.children}
|
|
219
|
+
<Show when={local.trailing}>
|
|
220
|
+
<span class="ms-auto flex shrink-0 items-center gap-1">{local.trailing}</span>
|
|
221
|
+
</Show>
|
|
222
|
+
</Polymorphic>
|
|
223
|
+
)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** A row that is a button rather than a destination — "New project". */
|
|
227
|
+
export function SidebarNavButton(props: ComponentProps<'button'>) {
|
|
228
|
+
const [local, rest] = splitProps(props, ['class', 'children'])
|
|
229
|
+
|
|
230
|
+
return (
|
|
231
|
+
<button
|
|
232
|
+
type="button"
|
|
233
|
+
class={cn(
|
|
234
|
+
sidebarNavItemClass,
|
|
235
|
+
sidebarNavItemStateClass.idle,
|
|
236
|
+
'w-full disabled:pointer-events-none disabled:opacity-50',
|
|
237
|
+
local.class
|
|
238
|
+
)}
|
|
239
|
+
{...rest}
|
|
240
|
+
>
|
|
241
|
+
{local.children}
|
|
242
|
+
</button>
|
|
243
|
+
)
|
|
244
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { StatusBar, StatusBarItem, StatusBarSpacer } from './status-bar'
|