@a4ui/core 0.37.1 → 0.38.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -7
- package/dist/Collapse-Cr5cDqAl.js +375 -0
- package/dist/charts/AreaChart.d.ts +38 -0
- package/dist/charts/BarChart.d.ts +17 -1
- package/dist/charts/PieChart.d.ts +29 -0
- package/dist/charts/RadialBarChart.d.ts +33 -0
- package/dist/charts/index.d.ts +5 -0
- package/dist/charts/shared.d.ts +111 -0
- package/dist/charts.js +1493 -780
- package/dist/commerce.js +625 -634
- package/dist/elements.css +1076 -8
- package/dist/elements.iife.js +1 -6
- package/dist/elements.js +7774 -9133
- package/dist/full.css +1076 -8
- package/dist/index.d.ts +30 -3
- package/dist/index.js +17502 -15126
- package/dist/layout/Aurora.d.ts +16 -0
- package/dist/layout/EffectsToggle.d.ts +1 -1
- package/dist/layout/SpaceBackground.d.ts +1 -1
- package/dist/motion-CTIBwMCA.js +104 -0
- package/dist/styles.css +110 -3
- package/dist/ui/AspectRatio.d.ts +19 -0
- package/dist/ui/Attachment.d.ts +70 -0
- package/dist/ui/ButtonGroup.d.ts +37 -0
- package/dist/ui/ChatMarker.d.ts +20 -0
- package/dist/ui/ChatThread.d.ts +32 -8
- package/dist/ui/ContactSection.d.ts +49 -0
- package/dist/ui/CtaBanner.d.ts +28 -0
- package/dist/ui/Drawer.d.ts +5 -2
- package/dist/ui/Faq.d.ts +29 -0
- package/dist/ui/FeatureGrid.d.ts +35 -0
- package/dist/ui/Field.d.ts +148 -0
- package/dist/ui/Hero.d.ts +48 -0
- package/dist/ui/InputGroup.d.ts +93 -0
- package/dist/ui/Item.d.ts +63 -0
- package/dist/ui/Label.d.ts +23 -0
- package/dist/ui/Menubar.d.ts +83 -0
- package/dist/ui/Message.d.ts +51 -5
- package/dist/ui/Modal.d.ts +8 -2
- package/dist/ui/NativeSelect.d.ts +26 -0
- package/dist/ui/NavigationMenu.d.ts +85 -0
- package/dist/ui/PromptComposer.d.ts +5 -0
- package/dist/ui/Prose.d.ts +21 -0
- package/dist/ui/Questionnaire.d.ts +71 -0
- package/dist/ui/ScrollArea.d.ts +30 -0
- package/dist/ui/Sidebar.d.ts +229 -0
- package/dist/ui/SiteFooter.d.ts +39 -0
- package/dist/ui/SiteNav.d.ts +35 -0
- package/dist/ui/TeamGrid.d.ts +33 -0
- package/dist/ui/Testimonials.d.ts +37 -0
- package/dist/ui/internal/sectionKit.d.ts +22 -0
- package/dist/ui/internal/socialIcons.d.ts +9 -0
- package/package.json +38 -26
- package/preset.d.ts +23 -0
- package/src/charts/AreaChart.tsx +206 -0
- package/src/charts/BarChart.tsx +241 -5
- package/src/charts/DonutChart.tsx +8 -11
- package/src/charts/LineChart.tsx +2 -3
- package/src/charts/PieChart.tsx +145 -0
- package/src/charts/RadialBarChart.tsx +141 -0
- package/src/charts/index.ts +14 -0
- package/src/charts/shared.tsx +304 -0
- package/src/index.ts +192 -3
- package/src/layout/Aurora.tsx +36 -7
- package/src/layout/sceneEffects.ts +22 -3
- package/src/ui/AlertDialog.tsx +6 -3
- package/src/ui/AspectRatio.tsx +35 -0
- package/src/ui/Attachment.tsx +189 -0
- package/src/ui/ButtonGroup.tsx +89 -0
- package/src/ui/ChatMarker.tsx +58 -0
- package/src/ui/ChatThread.tsx +115 -23
- package/src/ui/Command.tsx +6 -3
- package/src/ui/ContactSection.tsx +199 -0
- package/src/ui/CtaBanner.tsx +62 -0
- package/src/ui/DateField.tsx +2 -1
- package/src/ui/DateRangePicker.tsx +2 -1
- package/src/ui/Drawer.tsx +43 -7
- package/src/ui/Faq.tsx +57 -0
- package/src/ui/FeatureGrid.tsx +77 -0
- package/src/ui/Field.tsx +374 -0
- package/src/ui/Hero.tsx +146 -0
- package/src/ui/Image.tsx +6 -3
- package/src/ui/InputGroup.tsx +219 -0
- package/src/ui/Item.tsx +185 -0
- package/src/ui/Label.tsx +50 -0
- package/src/ui/Menubar.tsx +212 -0
- package/src/ui/Message.tsx +139 -13
- package/src/ui/Modal.tsx +47 -8
- package/src/ui/MultiSelect.tsx +2 -1
- package/src/ui/NativeSelect.tsx +66 -0
- package/src/ui/NavigationMenu.tsx +262 -0
- package/src/ui/PromptComposer.tsx +11 -0
- package/src/ui/Prose.tsx +63 -0
- package/src/ui/Questionnaire.tsx +331 -0
- package/src/ui/ScrollArea.tsx +70 -0
- package/src/ui/Sidebar.tsx +753 -0
- package/src/ui/SiteFooter.tsx +112 -0
- package/src/ui/SiteNav.tsx +146 -0
- package/src/ui/TeamGrid.tsx +81 -0
- package/src/ui/Testimonials.tsx +103 -0
- package/src/ui/TimeField.tsx +2 -1
- package/src/ui/internal/sectionKit.tsx +72 -0
- package/src/ui/internal/socialIcons.tsx +41 -0
- package/dist/NumberInput-Bnh2u7pp.js +0 -381
- package/dist/motion-jYMWmZqB.js +0 -119
|
@@ -0,0 +1,753 @@
|
|
|
1
|
+
// Composable app sidebar. `SidebarProvider` owns the open state (controlled or
|
|
2
|
+
// uncontrolled, Ctrl/Cmd+B toggles it); `Sidebar` is the glass panel that collapses
|
|
3
|
+
// off-canvas or down to an icon rail; below `md` it renders inside the Drawer.
|
|
4
|
+
// Menu parts follow the Item/ItemGroup composition style. Geometry is plain
|
|
5
|
+
// Tailwind + the `--sidebar-w` / `--sidebar-w-collapsed` tokens; the width change is
|
|
6
|
+
// a CSS transition (reduced motion is neutralised globally in tokens.css).
|
|
7
|
+
import { Tooltip as KTooltip } from '@kobalte/core/tooltip'
|
|
8
|
+
import { ChevronRight, PanelLeft } from 'lucide-solid'
|
|
9
|
+
import {
|
|
10
|
+
type Accessor,
|
|
11
|
+
type JSX,
|
|
12
|
+
type ParentProps,
|
|
13
|
+
Show,
|
|
14
|
+
createContext,
|
|
15
|
+
createEffect,
|
|
16
|
+
createMemo,
|
|
17
|
+
createSignal,
|
|
18
|
+
createUniqueId,
|
|
19
|
+
onCleanup,
|
|
20
|
+
onMount,
|
|
21
|
+
splitProps,
|
|
22
|
+
useContext,
|
|
23
|
+
} from 'solid-js'
|
|
24
|
+
|
|
25
|
+
import { cn } from '../lib/cn'
|
|
26
|
+
import { useMediaQuery } from '../lib/media'
|
|
27
|
+
import { Drawer } from './Drawer'
|
|
28
|
+
import { ScrollArea } from './ScrollArea'
|
|
29
|
+
|
|
30
|
+
export type SidebarSide = 'left' | 'right'
|
|
31
|
+
export type SidebarVariant = 'sidebar' | 'floating' | 'inset'
|
|
32
|
+
export type SidebarCollapsible = 'offcanvas' | 'icon' | 'none'
|
|
33
|
+
|
|
34
|
+
interface SidebarContextValue {
|
|
35
|
+
/** Desktop expanded state. */
|
|
36
|
+
open: Accessor<boolean>
|
|
37
|
+
setOpen: (open: boolean) => void
|
|
38
|
+
/** Flip the active state: the drawer on mobile, the desktop panel otherwise. */
|
|
39
|
+
toggle: () => void
|
|
40
|
+
/** True below the `md` breakpoint, where the sidebar renders inside a Drawer. */
|
|
41
|
+
isMobile: Accessor<boolean>
|
|
42
|
+
openMobile: Accessor<boolean>
|
|
43
|
+
setOpenMobile: (open: boolean) => void
|
|
44
|
+
/** @internal id of the sidebar panel, for `aria-controls`. */
|
|
45
|
+
panelId: string
|
|
46
|
+
/** @internal registered by `Sidebar` so `SidebarInset` / `SidebarRail` can adapt. */
|
|
47
|
+
config: Accessor<{ side: SidebarSide; variant: SidebarVariant; collapsible: SidebarCollapsible }>
|
|
48
|
+
setConfig: (c: { side: SidebarSide; variant: SidebarVariant; collapsible: SidebarCollapsible }) => void
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const SidebarContext = createContext<SidebarContextValue>()
|
|
52
|
+
|
|
53
|
+
/** State of the nearest {@link SidebarProvider}. Throws outside one. */
|
|
54
|
+
export function useSidebar(): Omit<SidebarContextValue, 'panelId' | 'config' | 'setConfig'> {
|
|
55
|
+
const ctx = useContext(SidebarContext)
|
|
56
|
+
if (!ctx) throw new Error('useSidebar must be used inside <SidebarProvider>')
|
|
57
|
+
return ctx
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function useSidebarInternal(): SidebarContextValue {
|
|
61
|
+
const ctx = useContext(SidebarContext)
|
|
62
|
+
if (!ctx) throw new Error('Sidebar parts must be used inside <SidebarProvider>')
|
|
63
|
+
return ctx
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** True when the icon rail is collapsed (labels hidden, tooltips on). */
|
|
67
|
+
const IconCollapsed = createContext<Accessor<boolean>>(() => false)
|
|
68
|
+
|
|
69
|
+
interface SidebarProviderProps extends ParentProps {
|
|
70
|
+
/** Controlled desktop open state. */
|
|
71
|
+
open?: boolean
|
|
72
|
+
/** Called with the next desktop open state (controlled or not). */
|
|
73
|
+
onOpenChange?: (open: boolean) => void
|
|
74
|
+
/** Initial state when uncontrolled. @default true */
|
|
75
|
+
defaultOpen?: boolean
|
|
76
|
+
/** Key toggled with Ctrl/Cmd; `false` disables the shortcut. @default 'b' */
|
|
77
|
+
shortcut?: string | false
|
|
78
|
+
class?: string
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Owns the sidebar state and wraps `Sidebar` + `SidebarInset` in a flex row.
|
|
83
|
+
* Give it a bounded height (the sidebar fills it). Ctrl/Cmd+B toggles the sidebar.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```tsx
|
|
87
|
+
* <SidebarProvider class="h-screen">
|
|
88
|
+
* <Sidebar collapsible="icon">…</Sidebar>
|
|
89
|
+
* <SidebarInset>…</SidebarInset>
|
|
90
|
+
* </SidebarProvider>
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
export function SidebarProvider(props: SidebarProviderProps): JSX.Element {
|
|
94
|
+
const isMobile = useMediaQuery('(max-width: 767px)')
|
|
95
|
+
const [internal, setInternal] = createSignal(props.defaultOpen ?? true)
|
|
96
|
+
const [openMobile, setOpenMobile] = createSignal(false)
|
|
97
|
+
const [config, setConfig] = createSignal<ReturnType<SidebarContextValue['config']>>({
|
|
98
|
+
side: 'left',
|
|
99
|
+
variant: 'sidebar',
|
|
100
|
+
collapsible: 'offcanvas',
|
|
101
|
+
})
|
|
102
|
+
const open = () => (props.open !== undefined ? props.open : internal())
|
|
103
|
+
const setOpen = (next: boolean) => {
|
|
104
|
+
if (props.open === undefined) setInternal(next)
|
|
105
|
+
props.onOpenChange?.(next)
|
|
106
|
+
}
|
|
107
|
+
const toggle = () => (isMobile() ? setOpenMobile(!openMobile()) : setOpen(!open()))
|
|
108
|
+
|
|
109
|
+
onMount(() => {
|
|
110
|
+
const onKey = (e: KeyboardEvent) => {
|
|
111
|
+
const key = props.shortcut === undefined ? 'b' : props.shortcut
|
|
112
|
+
if (key === false || e.defaultPrevented) return
|
|
113
|
+
if (e.key.toLowerCase() !== key.toLowerCase() || !(e.ctrlKey || e.metaKey) || e.altKey || e.shiftKey)
|
|
114
|
+
return
|
|
115
|
+
const t = e.target
|
|
116
|
+
// Leave Ctrl+B alone inside text fields / rich editors.
|
|
117
|
+
if (t instanceof HTMLElement && (t.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(t.tagName)))
|
|
118
|
+
return
|
|
119
|
+
e.preventDefault()
|
|
120
|
+
toggle()
|
|
121
|
+
}
|
|
122
|
+
window.addEventListener('keydown', onKey)
|
|
123
|
+
onCleanup(() => window.removeEventListener('keydown', onKey))
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
const value: SidebarContextValue = {
|
|
127
|
+
open,
|
|
128
|
+
setOpen,
|
|
129
|
+
toggle,
|
|
130
|
+
isMobile,
|
|
131
|
+
openMobile,
|
|
132
|
+
setOpenMobile,
|
|
133
|
+
panelId: `sidebar-${createUniqueId()}`,
|
|
134
|
+
config,
|
|
135
|
+
setConfig,
|
|
136
|
+
}
|
|
137
|
+
return (
|
|
138
|
+
<SidebarContext.Provider value={value}>
|
|
139
|
+
<div data-sidebar="provider" class={cn('flex h-full min-h-0 w-full', props.class)}>
|
|
140
|
+
{props.children}
|
|
141
|
+
</div>
|
|
142
|
+
</SidebarContext.Provider>
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
interface SidebarProps extends ParentProps {
|
|
147
|
+
/** Which edge it sits on. @default 'left' */
|
|
148
|
+
side?: SidebarSide
|
|
149
|
+
/** `sidebar` is flush, `floating` is a detached rounded panel, `inset` has no surface of its own (pair with `SidebarInset`). @default 'sidebar' */
|
|
150
|
+
variant?: SidebarVariant
|
|
151
|
+
/** `offcanvas` slides away, `icon` shrinks to an icon rail, `none` never collapses. @default 'offcanvas' */
|
|
152
|
+
collapsible?: SidebarCollapsible
|
|
153
|
+
/** Accessible name (and the drawer title on mobile). @default 'Navigation' */
|
|
154
|
+
label?: string
|
|
155
|
+
class?: string
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* The sidebar panel. Collapses per `collapsible`; below `md` it opens as a
|
|
160
|
+
* `Drawer` on the same `side` (toggled by `SidebarTrigger` / Ctrl+B).
|
|
161
|
+
*
|
|
162
|
+
* @example
|
|
163
|
+
* ```tsx
|
|
164
|
+
* <Sidebar collapsible="icon" variant="floating">
|
|
165
|
+
* <SidebarHeader>…</SidebarHeader>
|
|
166
|
+
* <SidebarContent>…</SidebarContent>
|
|
167
|
+
* </Sidebar>
|
|
168
|
+
* ```
|
|
169
|
+
*/
|
|
170
|
+
export function Sidebar(props: SidebarProps): JSX.Element {
|
|
171
|
+
const ctx = useSidebarInternal()
|
|
172
|
+
const side = () => props.side ?? 'left'
|
|
173
|
+
const variant = () => props.variant ?? 'sidebar'
|
|
174
|
+
const collapsible = () => props.collapsible ?? 'offcanvas'
|
|
175
|
+
createEffect(() => ctx.setConfig({ side: side(), variant: variant(), collapsible: collapsible() }))
|
|
176
|
+
|
|
177
|
+
const expanded = () => collapsible() === 'none' || ctx.open()
|
|
178
|
+
const iconCollapsed = () => !ctx.isMobile() && collapsible() === 'icon' && !expanded()
|
|
179
|
+
const offscreen = () => collapsible() === 'offcanvas' && !expanded()
|
|
180
|
+
const label = () => props.label ?? 'Navigation'
|
|
181
|
+
|
|
182
|
+
const surface = () => (
|
|
183
|
+
<div
|
|
184
|
+
data-sidebar="surface"
|
|
185
|
+
class={cn(
|
|
186
|
+
'relative flex h-full w-full min-w-0 flex-col',
|
|
187
|
+
variant() === 'sidebar' &&
|
|
188
|
+
cn('bg-glass', side() === 'left' ? 'border-r border-border' : 'border-l border-border'),
|
|
189
|
+
variant() === 'floating' && 'rounded-xl border border-border bg-glass shadow-lg',
|
|
190
|
+
props.class,
|
|
191
|
+
)}
|
|
192
|
+
>
|
|
193
|
+
{props.children}
|
|
194
|
+
</div>
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
return (
|
|
198
|
+
<IconCollapsed.Provider value={iconCollapsed}>
|
|
199
|
+
<Show
|
|
200
|
+
when={!ctx.isMobile()}
|
|
201
|
+
fallback={
|
|
202
|
+
<Drawer
|
|
203
|
+
open={ctx.openMobile()}
|
|
204
|
+
onOpenChange={ctx.setOpenMobile}
|
|
205
|
+
side={side()}
|
|
206
|
+
title={label()}
|
|
207
|
+
class="max-w-[var(--sidebar-w)] p-0"
|
|
208
|
+
>
|
|
209
|
+
<div id={ctx.panelId} data-sidebar="surface" class="flex min-h-0 flex-1 flex-col">
|
|
210
|
+
{props.children}
|
|
211
|
+
</div>
|
|
212
|
+
</Drawer>
|
|
213
|
+
}
|
|
214
|
+
>
|
|
215
|
+
<aside
|
|
216
|
+
id={ctx.panelId}
|
|
217
|
+
aria-label={label()}
|
|
218
|
+
data-sidebar="root"
|
|
219
|
+
data-state={expanded() ? 'expanded' : 'collapsed'}
|
|
220
|
+
data-side={side()}
|
|
221
|
+
data-variant={variant()}
|
|
222
|
+
data-collapsible={collapsible()}
|
|
223
|
+
data-icon-collapsed={iconCollapsed()}
|
|
224
|
+
class={cn(
|
|
225
|
+
'group/sidebar relative h-full shrink-0 transition-[width] duration-200 ease-out',
|
|
226
|
+
expanded()
|
|
227
|
+
? 'w-[var(--sidebar-w)]'
|
|
228
|
+
: collapsible() === 'icon'
|
|
229
|
+
? 'w-[var(--sidebar-w-collapsed)]'
|
|
230
|
+
: 'w-0',
|
|
231
|
+
)}
|
|
232
|
+
>
|
|
233
|
+
<div
|
|
234
|
+
class={cn(
|
|
235
|
+
'absolute inset-y-0 transition-[transform,visibility] duration-200 ease-out',
|
|
236
|
+
side() === 'left' ? 'left-0' : 'right-0',
|
|
237
|
+
variant() !== 'sidebar' && 'p-2',
|
|
238
|
+
collapsible() === 'icon' ? 'w-full' : 'w-[var(--sidebar-w)]',
|
|
239
|
+
offscreen() && cn('invisible', side() === 'left' ? '-translate-x-full' : 'translate-x-full'),
|
|
240
|
+
)}
|
|
241
|
+
>
|
|
242
|
+
{surface()}
|
|
243
|
+
</div>
|
|
244
|
+
</aside>
|
|
245
|
+
</Show>
|
|
246
|
+
</IconCollapsed.Provider>
|
|
247
|
+
)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
interface SidebarTriggerProps {
|
|
251
|
+
/** Accessible name. @default 'Toggle sidebar' */
|
|
252
|
+
label?: string
|
|
253
|
+
class?: string
|
|
254
|
+
/** Replaces the default panel icon. */
|
|
255
|
+
children?: JSX.Element
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Button that toggles the sidebar (the drawer on mobile). Exposes
|
|
260
|
+
* `aria-expanded` and `aria-controls` for the panel.
|
|
261
|
+
*/
|
|
262
|
+
export function SidebarTrigger(props: SidebarTriggerProps): JSX.Element {
|
|
263
|
+
const ctx = useSidebarInternal()
|
|
264
|
+
const expanded = () => (ctx.isMobile() ? ctx.openMobile() : ctx.open())
|
|
265
|
+
return (
|
|
266
|
+
<button
|
|
267
|
+
type="button"
|
|
268
|
+
data-sidebar="trigger"
|
|
269
|
+
aria-label={props.label ?? 'Toggle sidebar'}
|
|
270
|
+
aria-expanded={expanded()}
|
|
271
|
+
aria-controls={ctx.panelId}
|
|
272
|
+
onClick={() => ctx.toggle()}
|
|
273
|
+
class={cn(
|
|
274
|
+
'grid h-8 w-8 shrink-0 place-items-center rounded-lg text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
|
|
275
|
+
props.class,
|
|
276
|
+
)}
|
|
277
|
+
>
|
|
278
|
+
{props.children ?? <PanelLeft class="h-4 w-4" aria-hidden="true" />}
|
|
279
|
+
</button>
|
|
280
|
+
)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Thin hit area on the sidebar's inner edge that toggles it. Pointer-only
|
|
285
|
+
* affordance (`tabindex=-1`); keyboard users have `SidebarTrigger` and Ctrl+B.
|
|
286
|
+
* Place it as the last child of `Sidebar`.
|
|
287
|
+
*/
|
|
288
|
+
export function SidebarRail(props: { class?: string }): JSX.Element {
|
|
289
|
+
const ctx = useSidebarInternal()
|
|
290
|
+
const left = () => ctx.config().side === 'left'
|
|
291
|
+
return (
|
|
292
|
+
<button
|
|
293
|
+
type="button"
|
|
294
|
+
data-sidebar="rail"
|
|
295
|
+
tabIndex={-1}
|
|
296
|
+
aria-label="Toggle sidebar edge"
|
|
297
|
+
title="Toggle sidebar"
|
|
298
|
+
onClick={() => ctx.toggle()}
|
|
299
|
+
class={cn(
|
|
300
|
+
'absolute inset-y-0 z-10 hidden w-3 after:absolute after:inset-y-0 after:left-1/2 after:w-px after:bg-transparent after:transition-colors hover:after:bg-border md:block',
|
|
301
|
+
left() ? '-right-1.5 cursor-w-resize' : '-left-1.5 cursor-e-resize',
|
|
302
|
+
props.class,
|
|
303
|
+
)}
|
|
304
|
+
/>
|
|
305
|
+
)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Main content wrapper. With `variant="inset"` it becomes a rounded glass panel
|
|
310
|
+
* beside the sidebar; otherwise it is a plain scrolling region.
|
|
311
|
+
*
|
|
312
|
+
* @example
|
|
313
|
+
* ```tsx
|
|
314
|
+
* <SidebarInset><main>…</main></SidebarInset>
|
|
315
|
+
* ```
|
|
316
|
+
*/
|
|
317
|
+
export function SidebarInset(props: ParentProps<{ class?: string }>): JSX.Element {
|
|
318
|
+
const ctx = useSidebarInternal()
|
|
319
|
+
const inset = () => ctx.config().variant === 'inset'
|
|
320
|
+
return (
|
|
321
|
+
<div
|
|
322
|
+
data-sidebar="inset"
|
|
323
|
+
class={cn(
|
|
324
|
+
'relative min-w-0 flex-1 overflow-auto',
|
|
325
|
+
inset() &&
|
|
326
|
+
cn(
|
|
327
|
+
'm-2 rounded-xl border border-border bg-glass shadow-sm',
|
|
328
|
+
ctx.config().side === 'left' ? 'ml-0' : 'mr-0',
|
|
329
|
+
),
|
|
330
|
+
props.class,
|
|
331
|
+
)}
|
|
332
|
+
>
|
|
333
|
+
{props.children}
|
|
334
|
+
</div>
|
|
335
|
+
)
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/** Top slot (brand, switcher, search). */
|
|
339
|
+
export function SidebarHeader(props: ParentProps<{ class?: string }>): JSX.Element {
|
|
340
|
+
return (
|
|
341
|
+
<div data-sidebar="header" class={cn('flex flex-col gap-2 p-2', props.class)}>
|
|
342
|
+
{props.children}
|
|
343
|
+
</div>
|
|
344
|
+
)
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/** Bottom slot (account, settings). */
|
|
348
|
+
export function SidebarFooter(props: ParentProps<{ class?: string }>): JSX.Element {
|
|
349
|
+
return (
|
|
350
|
+
<div data-sidebar="footer" class={cn('flex flex-col gap-2 p-2', props.class)}>
|
|
351
|
+
{props.children}
|
|
352
|
+
</div>
|
|
353
|
+
)
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/** Scrolling middle region, built on `ScrollArea`. */
|
|
357
|
+
export function SidebarContent(props: ParentProps<{ class?: string }>): JSX.Element {
|
|
358
|
+
return (
|
|
359
|
+
<ScrollArea class={cn('flex min-h-0 flex-1 flex-col gap-2 p-2', props.class)}>
|
|
360
|
+
{props.children}
|
|
361
|
+
</ScrollArea>
|
|
362
|
+
)
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const GroupContext = createContext<{ id: string; register: () => void }>()
|
|
366
|
+
|
|
367
|
+
/** A labelled section of the sidebar. */
|
|
368
|
+
export function SidebarGroup(props: ParentProps<{ class?: string }>): JSX.Element {
|
|
369
|
+
const id = `sidebar-group-${createUniqueId()}`
|
|
370
|
+
const [hasLabel, setHasLabel] = createSignal(false)
|
|
371
|
+
return (
|
|
372
|
+
<GroupContext.Provider value={{ id, register: () => setHasLabel(true) }}>
|
|
373
|
+
<div
|
|
374
|
+
role="group"
|
|
375
|
+
data-sidebar="group"
|
|
376
|
+
aria-labelledby={hasLabel() ? id : undefined}
|
|
377
|
+
class={cn('relative flex w-full min-w-0 flex-col gap-1', props.class)}
|
|
378
|
+
>
|
|
379
|
+
{props.children}
|
|
380
|
+
</div>
|
|
381
|
+
</GroupContext.Provider>
|
|
382
|
+
)
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/** Small uppercase heading of a group; hidden when the rail is icon-collapsed. */
|
|
386
|
+
export function SidebarGroupLabel(props: ParentProps<{ class?: string }>): JSX.Element {
|
|
387
|
+
const group = useContext(GroupContext)
|
|
388
|
+
onMount(() => group?.register())
|
|
389
|
+
return (
|
|
390
|
+
<div
|
|
391
|
+
id={group?.id}
|
|
392
|
+
data-sidebar="group-label"
|
|
393
|
+
class={cn(
|
|
394
|
+
'px-2 pt-1 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground group-data-[icon-collapsed=true]/sidebar:hidden',
|
|
395
|
+
props.class,
|
|
396
|
+
)}
|
|
397
|
+
>
|
|
398
|
+
{props.children}
|
|
399
|
+
</div>
|
|
400
|
+
)
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
interface SidebarGroupActionProps extends ParentProps {
|
|
404
|
+
/** Required accessible name (the action is usually just an icon). */
|
|
405
|
+
label: string
|
|
406
|
+
onClick?: (e: MouseEvent) => void
|
|
407
|
+
class?: string
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/** Icon button aligned to the right of the group label. */
|
|
411
|
+
export function SidebarGroupAction(props: SidebarGroupActionProps): JSX.Element {
|
|
412
|
+
return (
|
|
413
|
+
<button
|
|
414
|
+
type="button"
|
|
415
|
+
data-sidebar="group-action"
|
|
416
|
+
aria-label={props.label}
|
|
417
|
+
onClick={(e) => props.onClick?.(e)}
|
|
418
|
+
class={cn(
|
|
419
|
+
'absolute right-1 top-0 grid h-6 w-6 place-items-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring group-data-[icon-collapsed=true]/sidebar:hidden [&>svg]:h-4 [&>svg]:w-4',
|
|
420
|
+
props.class,
|
|
421
|
+
)}
|
|
422
|
+
>
|
|
423
|
+
{props.children}
|
|
424
|
+
</button>
|
|
425
|
+
)
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/** Wrapper for a group's items. */
|
|
429
|
+
export function SidebarGroupContent(props: ParentProps<{ class?: string }>): JSX.Element {
|
|
430
|
+
return (
|
|
431
|
+
<div data-sidebar="group-content" class={cn('w-full text-sm', props.class)}>
|
|
432
|
+
{props.children}
|
|
433
|
+
</div>
|
|
434
|
+
)
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/** `<ul>` of menu items. */
|
|
438
|
+
export function SidebarMenu(props: ParentProps<{ class?: string }>): JSX.Element {
|
|
439
|
+
return (
|
|
440
|
+
<ul data-sidebar="menu" class={cn('flex w-full min-w-0 list-none flex-col gap-1', props.class)}>
|
|
441
|
+
{props.children}
|
|
442
|
+
</ul>
|
|
443
|
+
)
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/** `<li>` of a menu; anchors `SidebarMenuAction` and `SidebarMenuBadge`. */
|
|
447
|
+
export function SidebarMenuItem(props: ParentProps<{ class?: string }>): JSX.Element {
|
|
448
|
+
return (
|
|
449
|
+
<li data-sidebar="menu-item" class={cn('group/item relative', props.class)}>
|
|
450
|
+
{props.children}
|
|
451
|
+
</li>
|
|
452
|
+
)
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
type MenuButtonSize = 'sm' | 'default' | 'lg'
|
|
456
|
+
|
|
457
|
+
interface SidebarMenuButtonProps extends ParentProps {
|
|
458
|
+
/** Marks the current page: sets `aria-current="page"` and the highlight. */
|
|
459
|
+
active?: boolean
|
|
460
|
+
/** Renders a link. Otherwise a `<button>` (use `onClick`). */
|
|
461
|
+
href?: string
|
|
462
|
+
onClick?: (e: MouseEvent) => void
|
|
463
|
+
/** Shown as a tooltip (beside the icon) only while the rail is icon-collapsed. */
|
|
464
|
+
tooltip?: JSX.Element
|
|
465
|
+
size?: MenuButtonSize
|
|
466
|
+
class?: string
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const SIZE: Record<MenuButtonSize, string> = {
|
|
470
|
+
sm: 'h-7 text-xs',
|
|
471
|
+
default: 'h-9 text-sm',
|
|
472
|
+
lg: 'h-12 text-sm',
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Shared look of MenuButton, MenuSubTrigger. In the icon rail every direct text
|
|
476
|
+
// child is visually hidden (sr-only) so the accessible name survives, and the
|
|
477
|
+
// icon is centred.
|
|
478
|
+
const ITEM_BASE =
|
|
479
|
+
'flex w-full min-w-0 items-center gap-2.5 rounded-lg px-2.5 text-left font-medium text-foreground/80 outline-none transition-colors hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring aria-[current=page]:bg-muted aria-[current=page]:text-foreground group-has-[[data-sidebar=menu-action]]/item:pr-9 [&>svg]:h-[18px] [&>svg]:w-[18px] [&>svg]:shrink-0 [&>span]:truncate group-data-[icon-collapsed=true]/sidebar:justify-center group-data-[icon-collapsed=true]/sidebar:px-0 group-data-[icon-collapsed=true]/sidebar:[&>span]:sr-only'
|
|
480
|
+
|
|
481
|
+
function MenuTooltip(
|
|
482
|
+
props: ParentProps<{ tooltip?: JSX.Element; as: 'a' | 'button'; [key: string]: unknown }>,
|
|
483
|
+
) {
|
|
484
|
+
const iconCollapsed = useContext(IconCollapsed)
|
|
485
|
+
const ctx = useSidebarInternal()
|
|
486
|
+
const [local, rest] = splitProps(props, ['tooltip', 'as', 'children'])
|
|
487
|
+
return (
|
|
488
|
+
<KTooltip
|
|
489
|
+
placement={ctx.config().side === 'left' ? 'right' : 'left'}
|
|
490
|
+
gutter={10}
|
|
491
|
+
openDelay={150}
|
|
492
|
+
closeDelay={0}
|
|
493
|
+
disabled={!local.tooltip || !iconCollapsed()}
|
|
494
|
+
>
|
|
495
|
+
<KTooltip.Trigger as={local.as} {...rest}>
|
|
496
|
+
{local.children}
|
|
497
|
+
</KTooltip.Trigger>
|
|
498
|
+
<KTooltip.Portal>
|
|
499
|
+
<KTooltip.Content class="z-50 rounded-md border border-border bg-card px-2.5 py-1.5 text-xs text-card-foreground shadow-md">
|
|
500
|
+
{local.tooltip}
|
|
501
|
+
</KTooltip.Content>
|
|
502
|
+
</KTooltip.Portal>
|
|
503
|
+
</KTooltip>
|
|
504
|
+
)
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Main navigation row. Pass `href` for a link or `onClick` for an action;
|
|
509
|
+
* `active` sets `aria-current="page"`. Put the label in a `<span>` after the icon
|
|
510
|
+
* so it can collapse; with `collapsible="icon"` the `tooltip` appears on
|
|
511
|
+
* hover/focus and the span stays as the accessible name.
|
|
512
|
+
*
|
|
513
|
+
* @example
|
|
514
|
+
* ```tsx
|
|
515
|
+
* <SidebarMenuButton href="#/inbox" active tooltip="Inbox"><Inbox /><span>Inbox</span></SidebarMenuButton>
|
|
516
|
+
* ```
|
|
517
|
+
*/
|
|
518
|
+
export function SidebarMenuButton(props: SidebarMenuButtonProps): JSX.Element {
|
|
519
|
+
const cls = () => cn(ITEM_BASE, SIZE[props.size ?? 'default'], props.class)
|
|
520
|
+
return (
|
|
521
|
+
<Show
|
|
522
|
+
when={props.href !== undefined}
|
|
523
|
+
fallback={
|
|
524
|
+
<MenuTooltip
|
|
525
|
+
as="button"
|
|
526
|
+
type="button"
|
|
527
|
+
tooltip={props.tooltip}
|
|
528
|
+
data-sidebar="menu-button"
|
|
529
|
+
aria-current={props.active ? 'page' : undefined}
|
|
530
|
+
onClick={(e: MouseEvent) => props.onClick?.(e)}
|
|
531
|
+
class={cls()}
|
|
532
|
+
>
|
|
533
|
+
{props.children}
|
|
534
|
+
</MenuTooltip>
|
|
535
|
+
}
|
|
536
|
+
>
|
|
537
|
+
<MenuTooltip
|
|
538
|
+
as="a"
|
|
539
|
+
href={props.href}
|
|
540
|
+
tooltip={props.tooltip}
|
|
541
|
+
data-sidebar="menu-button"
|
|
542
|
+
aria-current={props.active ? 'page' : undefined}
|
|
543
|
+
onClick={(e: MouseEvent) => props.onClick?.(e)}
|
|
544
|
+
class={cls()}
|
|
545
|
+
>
|
|
546
|
+
{props.children}
|
|
547
|
+
</MenuTooltip>
|
|
548
|
+
</Show>
|
|
549
|
+
)
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/** Icon button to the right of a `SidebarMenuButton` (add, more…). Needs a `label`. */
|
|
553
|
+
export function SidebarMenuAction(
|
|
554
|
+
props: ParentProps<{
|
|
555
|
+
label: string
|
|
556
|
+
onClick?: (e: MouseEvent) => void
|
|
557
|
+
showOnHover?: boolean
|
|
558
|
+
class?: string
|
|
559
|
+
}>,
|
|
560
|
+
): JSX.Element {
|
|
561
|
+
return (
|
|
562
|
+
<button
|
|
563
|
+
type="button"
|
|
564
|
+
data-sidebar="menu-action"
|
|
565
|
+
aria-label={props.label}
|
|
566
|
+
onClick={(e) => props.onClick?.(e)}
|
|
567
|
+
class={cn(
|
|
568
|
+
'absolute right-1 top-1/2 grid h-6 w-6 -translate-y-1/2 place-items-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring group-data-[icon-collapsed=true]/sidebar:hidden [&>svg]:h-4 [&>svg]:w-4',
|
|
569
|
+
props.showOnHover &&
|
|
570
|
+
'opacity-0 focus-visible:opacity-100 group-focus-within/item:opacity-100 group-hover/item:opacity-100',
|
|
571
|
+
props.class,
|
|
572
|
+
)}
|
|
573
|
+
>
|
|
574
|
+
{props.children}
|
|
575
|
+
</button>
|
|
576
|
+
)
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/** Count / status pill at the right edge of a menu item. */
|
|
580
|
+
export function SidebarMenuBadge(props: ParentProps<{ class?: string }>): JSX.Element {
|
|
581
|
+
return (
|
|
582
|
+
<span
|
|
583
|
+
data-sidebar="menu-badge"
|
|
584
|
+
class={cn(
|
|
585
|
+
'pointer-events-none absolute right-2 top-1/2 -translate-y-1/2 select-none rounded-md bg-primary/15 px-1.5 text-[11px] font-semibold tabular-nums text-primary group-data-[icon-collapsed=true]/sidebar:hidden',
|
|
586
|
+
props.class,
|
|
587
|
+
)}
|
|
588
|
+
>
|
|
589
|
+
{props.children}
|
|
590
|
+
</span>
|
|
591
|
+
)
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
interface SidebarMenuSubProps extends ParentProps {
|
|
595
|
+
/** Text of the row that expands the submenu. */
|
|
596
|
+
label: string
|
|
597
|
+
/** Icon shown before the label. */
|
|
598
|
+
icon?: JSX.Element
|
|
599
|
+
/** Controlled expanded state. */
|
|
600
|
+
open?: boolean
|
|
601
|
+
onOpenChange?: (open: boolean) => void
|
|
602
|
+
/** Initial state when uncontrolled. @default false */
|
|
603
|
+
defaultOpen?: boolean
|
|
604
|
+
class?: string
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* Collapsible nested menu: a toggle row (`aria-expanded` / `aria-controls`) and an
|
|
609
|
+
* indented list of `SidebarMenuSubItem`. Use inside a `SidebarMenuItem`. In the icon
|
|
610
|
+
* rail the toggle expands the sidebar first, then the submenu.
|
|
611
|
+
*
|
|
612
|
+
* @example
|
|
613
|
+
* ```tsx
|
|
614
|
+
* <SidebarMenuItem>
|
|
615
|
+
* <SidebarMenuSub label="Projects" icon={<Folder />}>
|
|
616
|
+
* <SidebarMenuSubItem><SidebarMenuSubButton href="#/a">Orbit</SidebarMenuSubButton></SidebarMenuSubItem>
|
|
617
|
+
* </SidebarMenuSub>
|
|
618
|
+
* </SidebarMenuItem>
|
|
619
|
+
* ```
|
|
620
|
+
*/
|
|
621
|
+
export function SidebarMenuSub(props: SidebarMenuSubProps): JSX.Element {
|
|
622
|
+
const ctx = useSidebarInternal()
|
|
623
|
+
const iconCollapsed = useContext(IconCollapsed)
|
|
624
|
+
const id = `sidebar-sub-${createUniqueId()}`
|
|
625
|
+
const [internal, setInternal] = createSignal(props.defaultOpen ?? false)
|
|
626
|
+
const open = () => (props.open !== undefined ? props.open : internal())
|
|
627
|
+
const set = (next: boolean) => {
|
|
628
|
+
if (props.open === undefined) setInternal(next)
|
|
629
|
+
props.onOpenChange?.(next)
|
|
630
|
+
}
|
|
631
|
+
const shown = createMemo(() => open() && !iconCollapsed())
|
|
632
|
+
return (
|
|
633
|
+
<div data-sidebar="menu-sub" class={props.class}>
|
|
634
|
+
<MenuTooltip
|
|
635
|
+
as="button"
|
|
636
|
+
type="button"
|
|
637
|
+
tooltip={props.label}
|
|
638
|
+
data-sidebar="menu-sub-trigger"
|
|
639
|
+
aria-expanded={shown()}
|
|
640
|
+
aria-controls={id}
|
|
641
|
+
onClick={() => {
|
|
642
|
+
if (iconCollapsed()) {
|
|
643
|
+
ctx.setOpen(true)
|
|
644
|
+
set(true)
|
|
645
|
+
} else set(!open())
|
|
646
|
+
}}
|
|
647
|
+
class={cn(ITEM_BASE, SIZE.default)}
|
|
648
|
+
>
|
|
649
|
+
{props.icon}
|
|
650
|
+
<span>{props.label}</span>
|
|
651
|
+
<ChevronRight
|
|
652
|
+
aria-hidden="true"
|
|
653
|
+
class={cn(
|
|
654
|
+
'ml-auto h-4 w-4 shrink-0 transition-transform duration-200 group-data-[icon-collapsed=true]/sidebar:hidden',
|
|
655
|
+
shown() && 'rotate-90',
|
|
656
|
+
)}
|
|
657
|
+
/>
|
|
658
|
+
</MenuTooltip>
|
|
659
|
+
<div
|
|
660
|
+
id={id}
|
|
661
|
+
class={cn(
|
|
662
|
+
'grid transition-[grid-template-rows,visibility] duration-200 ease-out',
|
|
663
|
+
shown() ? 'grid-rows-[1fr]' : 'invisible grid-rows-[0fr]',
|
|
664
|
+
)}
|
|
665
|
+
>
|
|
666
|
+
<div class="min-h-0 overflow-hidden">
|
|
667
|
+
<ul class="ml-4 mt-1 flex list-none flex-col gap-0.5 border-l border-border py-0.5 pl-2.5">
|
|
668
|
+
{props.children}
|
|
669
|
+
</ul>
|
|
670
|
+
</div>
|
|
671
|
+
</div>
|
|
672
|
+
</div>
|
|
673
|
+
)
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/** `<li>` of a submenu. */
|
|
677
|
+
export function SidebarMenuSubItem(props: ParentProps<{ class?: string }>): JSX.Element {
|
|
678
|
+
return (
|
|
679
|
+
<li data-sidebar="menu-sub-item" class={cn('relative', props.class)}>
|
|
680
|
+
{props.children}
|
|
681
|
+
</li>
|
|
682
|
+
)
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
/** Link (`href`) or button row inside a submenu; `active` sets `aria-current="page"`. */
|
|
686
|
+
export function SidebarMenuSubButton(
|
|
687
|
+
props: ParentProps<{ active?: boolean; href?: string; onClick?: (e: MouseEvent) => void; class?: string }>,
|
|
688
|
+
): JSX.Element {
|
|
689
|
+
const cls = () =>
|
|
690
|
+
cn(
|
|
691
|
+
'flex h-8 w-full min-w-0 items-center gap-2 rounded-md px-2 text-left text-[13px] font-medium text-foreground/75 outline-none transition-colors hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring aria-[current=page]:bg-muted aria-[current=page]:text-foreground [&>span]:truncate',
|
|
692
|
+
props.class,
|
|
693
|
+
)
|
|
694
|
+
return (
|
|
695
|
+
<Show
|
|
696
|
+
when={props.href !== undefined}
|
|
697
|
+
fallback={
|
|
698
|
+
<button
|
|
699
|
+
type="button"
|
|
700
|
+
data-sidebar="menu-sub-button"
|
|
701
|
+
aria-current={props.active ? 'page' : undefined}
|
|
702
|
+
onClick={(e) => props.onClick?.(e)}
|
|
703
|
+
class={cls()}
|
|
704
|
+
>
|
|
705
|
+
{props.children}
|
|
706
|
+
</button>
|
|
707
|
+
}
|
|
708
|
+
>
|
|
709
|
+
<a
|
|
710
|
+
href={props.href}
|
|
711
|
+
data-sidebar="menu-sub-button"
|
|
712
|
+
aria-current={props.active ? 'page' : undefined}
|
|
713
|
+
onClick={(e) => props.onClick?.(e)}
|
|
714
|
+
class={cls()}
|
|
715
|
+
>
|
|
716
|
+
{props.children}
|
|
717
|
+
</a>
|
|
718
|
+
</Show>
|
|
719
|
+
)
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
/** Loading placeholder shaped like a menu row. Decorative (`aria-hidden`). */
|
|
723
|
+
export function SidebarMenuSkeleton(props: { showIcon?: boolean; class?: string }): JSX.Element {
|
|
724
|
+
// Stable per instance so rows do not jitter between renders.
|
|
725
|
+
const width = `${50 + Math.floor(Math.random() * 40)}%`
|
|
726
|
+
return (
|
|
727
|
+
<div
|
|
728
|
+
data-sidebar="menu-skeleton"
|
|
729
|
+
aria-hidden="true"
|
|
730
|
+
class={cn(
|
|
731
|
+
'flex h-9 items-center gap-2.5 rounded-lg px-2.5 group-data-[icon-collapsed=true]/sidebar:justify-center group-data-[icon-collapsed=true]/sidebar:px-0',
|
|
732
|
+
props.class,
|
|
733
|
+
)}
|
|
734
|
+
>
|
|
735
|
+
<Show when={props.showIcon}>
|
|
736
|
+
<div class="h-[18px] w-[18px] shrink-0 animate-pulse rounded-md bg-muted" />
|
|
737
|
+
</Show>
|
|
738
|
+
<div
|
|
739
|
+
class="h-3 flex-1 animate-pulse rounded bg-muted group-data-[icon-collapsed=true]/sidebar:hidden"
|
|
740
|
+
style={{ 'max-width': width }}
|
|
741
|
+
/>
|
|
742
|
+
</div>
|
|
743
|
+
)
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
export type {
|
|
747
|
+
SidebarProps,
|
|
748
|
+
SidebarProviderProps,
|
|
749
|
+
SidebarTriggerProps,
|
|
750
|
+
SidebarGroupActionProps,
|
|
751
|
+
SidebarMenuButtonProps,
|
|
752
|
+
SidebarMenuSubProps,
|
|
753
|
+
}
|