ajo-ui 0.1.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.
Files changed (103) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +126 -0
  3. package/dist/accordion.js +130 -0
  4. package/dist/avatar.js +54 -0
  5. package/dist/calendar.js +2 -0
  6. package/dist/carousel.js +239 -0
  7. package/dist/chart.js +790 -0
  8. package/dist/checkbox-group.js +70 -0
  9. package/dist/checkbox.js +77 -0
  10. package/dist/chunks/bar-CVafh6C1.js +99 -0
  11. package/dist/chunks/calendar-q8jZ8Cxr.js +1923 -0
  12. package/dist/chunks/collection-DtRB63U4.js +111 -0
  13. package/dist/chunks/data-table-DpkWv4y4.js +1039 -0
  14. package/dist/chunks/menu-B45IyHHC.js +704 -0
  15. package/dist/chunks/native-BJdhd9XJ.js +20 -0
  16. package/dist/chunks/popup-C8Bb3l_g.js +459 -0
  17. package/dist/chunks/popup-surface-BDCgtVU0.js +18 -0
  18. package/dist/chunks/position-D6_i_SRn.js +434 -0
  19. package/dist/chunks/virtual-list-B7hjGkjk.js +413 -0
  20. package/dist/collapsible.js +106 -0
  21. package/dist/command.js +263 -0
  22. package/dist/context-menu.js +112 -0
  23. package/dist/data-table.js +5 -0
  24. package/dist/dialog.js +207 -0
  25. package/dist/direction.js +22 -0
  26. package/dist/drawer.js +139 -0
  27. package/dist/field.js +26 -0
  28. package/dist/index.js +38 -0
  29. package/dist/input-date.js +994 -0
  30. package/dist/input-group.js +52 -0
  31. package/dist/input-otp.js +179 -0
  32. package/dist/menu.js +2 -0
  33. package/dist/menubar.js +236 -0
  34. package/dist/message-scroller.js +446 -0
  35. package/dist/navigation-menu.js +330 -0
  36. package/dist/popover.js +307 -0
  37. package/dist/progress.js +39 -0
  38. package/dist/radio-group.js +107 -0
  39. package/dist/resizable.js +172 -0
  40. package/dist/select.js +961 -0
  41. package/dist/sidebar.js +343 -0
  42. package/dist/slider.js +259 -0
  43. package/dist/switch.js +53 -0
  44. package/dist/tabs.js +182 -0
  45. package/dist/toast.js +492 -0
  46. package/dist/toggle-group.js +111 -0
  47. package/dist/toggle.js +52 -0
  48. package/dist/toolbar.js +127 -0
  49. package/dist/tooltip.js +196 -0
  50. package/dist/utils.js +104 -0
  51. package/dist/virtual-list.js +2 -0
  52. package/package.json +250 -0
  53. package/src/accordion.tsx +261 -0
  54. package/src/availability.ts +261 -0
  55. package/src/avatar.tsx +99 -0
  56. package/src/bar.ts +156 -0
  57. package/src/calendar.tsx +1441 -0
  58. package/src/carousel.tsx +424 -0
  59. package/src/chart.tsx +1194 -0
  60. package/src/checkbox-group.tsx +132 -0
  61. package/src/checkbox.tsx +130 -0
  62. package/src/collapsible.tsx +188 -0
  63. package/src/collection.ts +154 -0
  64. package/src/command.tsx +511 -0
  65. package/src/context-menu.tsx +233 -0
  66. package/src/data-table-contract.ts +143 -0
  67. package/src/data-table-model.ts +760 -0
  68. package/src/data-table.tsx +475 -0
  69. package/src/dialog.tsx +393 -0
  70. package/src/direction.tsx +45 -0
  71. package/src/drawer.tsx +251 -0
  72. package/src/field.tsx +61 -0
  73. package/src/index.ts +37 -0
  74. package/src/input-date.tsx +1539 -0
  75. package/src/input-group.tsx +142 -0
  76. package/src/input-otp.tsx +324 -0
  77. package/src/menu-cluster.ts +124 -0
  78. package/src/menu.tsx +1095 -0
  79. package/src/menubar.tsx +459 -0
  80. package/src/message-scroller.tsx +732 -0
  81. package/src/native.ts +26 -0
  82. package/src/navigation-menu.tsx +578 -0
  83. package/src/popover.tsx +519 -0
  84. package/src/popup-surface.tsx +31 -0
  85. package/src/popup.ts +569 -0
  86. package/src/position.ts +523 -0
  87. package/src/progress.tsx +70 -0
  88. package/src/radio-group.tsx +186 -0
  89. package/src/resizable.tsx +310 -0
  90. package/src/segments.ts +922 -0
  91. package/src/select.tsx +1501 -0
  92. package/src/sidebar.tsx +683 -0
  93. package/src/slider.tsx +424 -0
  94. package/src/switch.tsx +104 -0
  95. package/src/tabs.tsx +314 -0
  96. package/src/toast.tsx +923 -0
  97. package/src/toggle-group.tsx +249 -0
  98. package/src/toggle.tsx +91 -0
  99. package/src/toolbar.tsx +212 -0
  100. package/src/tooltip.tsx +359 -0
  101. package/src/utils.ts +204 -0
  102. package/src/virtual-list.tsx +205 -0
  103. package/src/virtual.ts +385 -0
@@ -0,0 +1,683 @@
1
+ import type { IntrinsicElements, Stateful, Stateless, WithChildren } from 'ajo'
2
+ import { browser, callHandler, controlled, dom, hotkey, media, statefulRootAttrs as rootAttrs } from 'ajo-cloves'
3
+ import { context } from 'ajo/context'
4
+ import { Drawer, DrawerContent } from './drawer'
5
+
6
+ /** Current expanded or collapsed presentation state. */
7
+ export type SidebarState = 'collapsed' | 'expanded'
8
+ /** Screen edge occupied by the sidebar. */
9
+ export type SidebarSide = 'left' | 'right'
10
+ /** Layout treatment applied to the sidebar shell. */
11
+ export type SidebarVariant = 'floating' | 'inset' | 'sidebar'
12
+ /** Behavior used when the desktop sidebar is collapsed. */
13
+ export type SidebarCollapsible = 'icon' | 'none' | 'offcanvas'
14
+
15
+ /** Props for shared desktop and mobile sidebar state. */
16
+ export type SidebarProviderArgs = WithChildren<IntrinsicElements['div'] & {
17
+ defaultOpen?: boolean
18
+ /** Controlled desktop open state. `null` and `undefined` both leave it uncontrolled. */
19
+ open?: boolean
20
+ /**
21
+ * Called with the next open state and the triggering event whenever either
22
+ * presentation changes: the desktop open state (trigger, rail, shortcut,
23
+ * `setOpen`) and the mobile drawer (`setOpenMobile`, drawer dismissal) both
24
+ * notify here. Only the desktop state is controllable through `open`; the
25
+ * mobile drawer state stays internal.
26
+ */
27
+ onOpenChange?: (open: boolean, event?: Event) => void
28
+ /**
29
+ * Persists the desktop open state; the default writes the `sidebar_state`
30
+ * cookie. Pass false to disable. The cookie is write-only today — reading it
31
+ * back into `defaultOpen` (kit SSR loader) is a recorded follow-up.
32
+ */
33
+ persist?: false | ((open: boolean) => void)
34
+ /** Keyboard shortcut that toggles the sidebar. Default `'mod+b'`; pass false to disable (e.g. for static `collapsible="none"` sidebars). */
35
+ shortcut?: string | false
36
+ /** Media query that switches to the mobile presentation. */
37
+ mobileQuery?: string
38
+ class?: string
39
+ style?: string
40
+ }>
41
+
42
+ /** Props for the responsive sidebar shell and its presentation. */
43
+ export type SidebarArgs = WithChildren<IntrinsicElements['aside'] & {
44
+ side?: SidebarSide
45
+ variant?: SidebarVariant
46
+ collapsible?: SidebarCollapsible
47
+ class?: string
48
+ mobileClass?: string
49
+ mobileContentClass?: string
50
+ }>
51
+
52
+ /** Props for a button that toggles the sidebar. */
53
+ export type SidebarTriggerArgs = WithChildren<IntrinsicElements['button'] & {
54
+ class?: string
55
+ }>
56
+
57
+ /** Props for the edge rail that toggles the desktop sidebar. */
58
+ export type SidebarRailArgs = IntrinsicElements['button'] & {
59
+ class?: string
60
+ }
61
+
62
+ /** Props for the main-content wrapper paired with an inset sidebar. */
63
+ export type SidebarInsetArgs = WithChildren<IntrinsicElements['main'] & {
64
+ class?: string
65
+ }>
66
+
67
+ /** Props for an input styled and identified as sidebar content. */
68
+ export type SidebarInputArgs = IntrinsicElements['input'] & {
69
+ class?: string
70
+ }
71
+
72
+ /** Props for a decorative separator between sidebar sections. */
73
+ export type SidebarSeparatorArgs = IntrinsicElements['div'] & {
74
+ class?: string
75
+ }
76
+
77
+ /** Shared props for structural sidebar section wrappers. */
78
+ export type SidebarSectionArgs = WithChildren<IntrinsicElements['div'] & {
79
+ class?: string
80
+ }>
81
+
82
+ /** Props for the sidebar header section. */
83
+ export type SidebarHeaderArgs = SidebarSectionArgs
84
+ /** Props for the sidebar footer section. */
85
+ export type SidebarFooterArgs = SidebarSectionArgs
86
+ /** Props for the sidebar's scrollable content section. */
87
+ export type SidebarContentArgs = SidebarSectionArgs
88
+ /** Props for a group of related sidebar controls. */
89
+ export type SidebarGroupArgs = SidebarSectionArgs
90
+ /** Props for the label that identifies a sidebar group. */
91
+ export type SidebarGroupLabelArgs = SidebarSectionArgs
92
+ /** Props for the content container inside a sidebar group. */
93
+ export type SidebarGroupContentArgs = SidebarSectionArgs
94
+
95
+ /** Props for an action button associated with a sidebar group. */
96
+ export type SidebarGroupActionArgs = WithChildren<IntrinsicElements['button'] & {
97
+ class?: string
98
+ }>
99
+
100
+ /** Props for a list of sidebar menu items. */
101
+ export type SidebarMenuArgs = WithChildren<IntrinsicElements['ul'] & {
102
+ class?: string
103
+ }>
104
+
105
+ /** Props for an item in the primary sidebar menu. */
106
+ export type SidebarMenuItemArgs = WithChildren<IntrinsicElements['li'] & {
107
+ class?: string
108
+ }>
109
+
110
+ type SidebarMenuButtonBaseArgs = WithChildren<{
111
+ isActive?: boolean
112
+ /** Native title hint shown while the sidebar is collapsed. */
113
+ tooltip?: string
114
+ size?: string
115
+ class?: string
116
+ }>
117
+
118
+ type SidebarMenuButtonAsButton = SidebarMenuButtonBaseArgs & IntrinsicElements['button'] & {
119
+ as?: 'button'
120
+ href?: undefined
121
+ }
122
+
123
+ type SidebarMenuButtonAsAnchor = SidebarMenuButtonBaseArgs & IntrinsicElements['a'] & {
124
+ as: 'a'
125
+ href?: string
126
+ }
127
+
128
+ /** Props for a button or anchor in the primary sidebar menu. */
129
+ export type SidebarMenuButtonArgs = SidebarMenuButtonAsAnchor | SidebarMenuButtonAsButton
130
+
131
+ /** Props for a secondary action attached to a sidebar menu item. */
132
+ export type SidebarMenuActionArgs = WithChildren<IntrinsicElements['button'] & {
133
+ class?: string
134
+ }>
135
+
136
+ /** Props for metadata displayed beside a sidebar menu item. */
137
+ export type SidebarMenuBadgeArgs = WithChildren<IntrinsicElements['div'] & {
138
+ class?: string
139
+ }>
140
+
141
+ /** Props for a loading placeholder shaped like a sidebar menu item. */
142
+ export type SidebarMenuSkeletonArgs = IntrinsicElements['div'] & {
143
+ showIcon?: boolean
144
+ width?: string
145
+ iconClass?: string
146
+ textClass?: string
147
+ class?: string
148
+ }
149
+
150
+ /** Props for a nested list of sidebar menu items. */
151
+ export type SidebarMenuSubArgs = WithChildren<IntrinsicElements['ul'] & {
152
+ class?: string
153
+ }>
154
+
155
+ /** Props for an item in a nested sidebar menu. */
156
+ export type SidebarMenuSubItemArgs = WithChildren<IntrinsicElements['li'] & {
157
+ class?: string
158
+ }>
159
+
160
+ /** Props for an anchor in a nested sidebar menu. */
161
+ export type SidebarMenuSubButtonArgs = WithChildren<IntrinsicElements['a'] & {
162
+ size?: string
163
+ isActive?: boolean
164
+ class?: string
165
+ }>
166
+
167
+ /** Responsive state and controls shared by Sidebar parts. */
168
+ export type SidebarContextValue = {
169
+ isMobile: boolean
170
+ open: boolean
171
+ openMobile: boolean
172
+ setOpen: (open: boolean, event?: Event) => void
173
+ setOpenMobile: (open: boolean, event?: Event) => void
174
+ state: SidebarState
175
+ toggleSidebar: (event?: Event) => void
176
+ }
177
+
178
+ /** Responsive state and controls provided by the nearest SidebarProvider. */
179
+ export const SidebarContext = context<SidebarContextValue | null>(null)
180
+
181
+ const cookie = 'sidebar_state'
182
+ const cookieMaxAge = 60 * 60 * 24 * 7
183
+ const defaultMobileQuery = '(max-width: 767px)'
184
+ const defaultShortcut = 'mod+b'
185
+
186
+ const vars = (style?: string) =>
187
+ ['--sidebar-width:16rem', '--sidebar-width-mobile:18rem', '--sidebar-width-icon:3rem', style]
188
+ .filter(Boolean)
189
+ .join(';')
190
+
191
+ const writeCookie = (open: boolean) => {
192
+ if (!browser()) return
193
+ document.cookie = `${cookie}=${open}; path=/; max-age=${cookieMaxAge}`
194
+ }
195
+
196
+ const sidebar = () => {
197
+ const value = SidebarContext()
198
+ if (!value) throw new Error('Sidebar controls must be used within a <SidebarProvider />.')
199
+ return value
200
+ }
201
+
202
+ const SidebarProviderRoot: Stateful<SidebarProviderArgs> = function* ({ defaultOpen = true, open }) {
203
+ let mobileQuery = defaultMobileQuery
204
+ let onOpenChange: SidebarProviderArgs['onOpenChange']
205
+ let persist: SidebarProviderArgs['persist']
206
+ let shortcut: SidebarProviderArgs['shortcut'] = defaultShortcut
207
+ const mobile = media(this, { query: () => mobileQuery })
208
+
209
+ const state = controlled<boolean>(this, {
210
+ fallback: Boolean(open ?? defaultOpen),
211
+ onChange: (next, event) => {
212
+ onOpenChange?.(next, event)
213
+ if (persist !== false) (persist ?? writeCookie)(next)
214
+ },
215
+ })
216
+
217
+ // The mobile drawer state is never controlled and never persisted, but it
218
+ // notifies through the same onOpenChange so consumers observe both presentations.
219
+ const mobileState = controlled<boolean>(this, {
220
+ fallback: false,
221
+ onChange: (next, event) => onOpenChange?.(next, event),
222
+ })
223
+
224
+ const setOpen = (next: boolean, event?: Event) => state.set(next, event)
225
+ const setOpenMobile = (next: boolean, event?: Event) => mobileState.set(next, event)
226
+ const toggleSidebar = (event?: Event) =>
227
+ mobile.matches ? setOpenMobile(!mobileState.value, event) : setOpen(!state.value, event)
228
+
229
+ hotkey(this, {
230
+ keys: () => shortcut || '',
231
+ active: () => shortcut !== false,
232
+ onPress: event => toggleSidebar(event),
233
+ })
234
+
235
+ for (const args of this) {
236
+ mobileQuery = args.mobileQuery ?? defaultMobileQuery
237
+ onOpenChange = args.onOpenChange
238
+ persist = args.persist
239
+ shortcut = args.shortcut ?? defaultShortcut
240
+ mobile.sync()
241
+
242
+ // Presentation stamp for themes: media queries cannot follow a custom
243
+ // mobileQuery, so presentation-conditional styling (e.g. hiding the
244
+ // rail inside the drawer) keys on this instead of a breakpoint.
245
+ if (dom(this)) this.setAttribute('data-mobile', String(mobile.matches))
246
+
247
+ // Leaving the mobile presentation drops the drawer, so an open drawer
248
+ // state must not survive it: re-narrowing would replay showModal() from
249
+ // a passive resize. The reset notifies onOpenChange like any other
250
+ // change, riding a microtask so the notification lands outside this
251
+ // render pass (consumers may re-render ancestors from it, which would
252
+ // re-enter this running generator).
253
+ if (!mobile.matches && mobileState.value) queueMicrotask(() => {
254
+ if (!this.signal.aborted && !mobile.matches && mobileState.value) mobileState.set(false)
255
+ })
256
+
257
+ // The sidebar contract treats null as uncontrolled; the clove binds on null.
258
+ const opened = state.sync(args.open != null ? Boolean(args.open) : undefined)
259
+
260
+ SidebarContext({
261
+ isMobile: mobile.matches,
262
+ open: opened,
263
+ openMobile: mobileState.value,
264
+ setOpen,
265
+ setOpenMobile,
266
+ state: opened ? 'expanded' : 'collapsed',
267
+ toggleSidebar,
268
+ })
269
+
270
+ yield <>{args.children}</>
271
+ }
272
+ }
273
+
274
+
275
+ /** Unstyled state provider for the sidebar component family. */
276
+ const SidebarProvider: Stateless<SidebarProviderArgs> = ({
277
+ children,
278
+ class: classes,
279
+ defaultOpen,
280
+ mobileQuery,
281
+ onOpenChange,
282
+ open,
283
+ persist,
284
+ shortcut,
285
+ style,
286
+ ...attrs
287
+ }) => (
288
+ <SidebarProviderRoot
289
+ {...rootAttrs(attrs)}
290
+ defaultOpen={defaultOpen}
291
+ mobileQuery={mobileQuery}
292
+ onOpenChange={onOpenChange}
293
+ open={open}
294
+ persist={persist}
295
+ shortcut={shortcut}
296
+ attr:class={classes}
297
+ attr:data-slot="sidebar-wrapper"
298
+ attr:style={vars(style)}
299
+ >
300
+ {children}
301
+ </SidebarProviderRoot>
302
+ )
303
+
304
+ /** Unstyled main sidebar panel. */
305
+ const Sidebar: Stateless<SidebarArgs> = ({
306
+ children,
307
+ class: classes,
308
+ collapsible = 'offcanvas',
309
+ mobileClass,
310
+ mobileContentClass,
311
+ side = 'left',
312
+ variant = 'sidebar',
313
+ ...attrs
314
+ }) => {
315
+ const ctx = SidebarContext()
316
+ const state = collapsible === 'none' ? 'expanded' : ctx?.state ?? 'expanded'
317
+ const collapsed = state === 'collapsed' ? collapsible : ''
318
+
319
+ if (ctx?.isMobile && collapsible !== 'none') {
320
+ return (
321
+ <Drawer
322
+ onOpenChange={(next, event) => ctx.setOpenMobile(next, event)}
323
+ open={ctx.openMobile}
324
+ side={side}
325
+ // Keeps the Drawer/Dialog wrapper divs out of the provider's flow so
326
+ // the closed drawer occupies no space (the old <dialog> was UA-hidden).
327
+ style="position:fixed"
328
+ >
329
+ <DrawerContent
330
+ {...attrs}
331
+ aria-label={attrs['aria-label'] ?? 'Sidebar'}
332
+ class={mobileClass ?? classes}
333
+ data-mobile="true"
334
+ data-variant={variant}
335
+ showCloseButton={false}
336
+ // DrawerContent pins data-slot="drawer-content" after its spread;
337
+ // reclaim the slot themes rely on. Ajo skips unchanged attr writes
338
+ // and re-runs refs each render, so the value survives updates.
339
+ ref={(element: HTMLDialogElement | null) => element?.setAttribute('data-slot', 'sidebar')}
340
+ >
341
+ <div class={mobileContentClass}>
342
+ {children}
343
+ </div>
344
+ </DrawerContent>
345
+ </Drawer>
346
+ )
347
+ }
348
+
349
+ return (
350
+ <aside
351
+ {...attrs}
352
+ aria-label={attrs['aria-label'] ?? 'Sidebar'}
353
+ class={classes}
354
+ data-collapsible={collapsed || undefined}
355
+ data-side={side}
356
+ data-slot="sidebar"
357
+ data-state={state}
358
+ data-variant={variant}
359
+ >
360
+ {children}
361
+ </aside>
362
+ )
363
+ }
364
+
365
+ /** Toggles the nearest SidebarProvider unless a caller-supplied click handler prevented default. */
366
+ const toggleClick = (ctx: SidebarContextValue, onClick: unknown) => (event: Event) => {
367
+ callHandler(onClick, event)
368
+ if (!event.defaultPrevented) ctx.toggleSidebar(event)
369
+ }
370
+
371
+ /** Unstyled button that toggles the current SidebarProvider. */
372
+ const SidebarTrigger: Stateless<SidebarTriggerArgs> = ({
373
+ 'aria-label': label = 'Toggle Sidebar',
374
+ children,
375
+ class: classes,
376
+ type = 'button',
377
+ 'set:onclick': onClick,
378
+ ...attrs
379
+ }) => {
380
+ const ctx = sidebar()
381
+
382
+ return (
383
+ <button
384
+ {...attrs}
385
+ aria-label={label}
386
+ class={classes}
387
+ data-sidebar="trigger"
388
+ data-slot="sidebar-trigger"
389
+ type={type}
390
+ set:onclick={toggleClick(ctx, onClick)}
391
+ >
392
+ {children}
393
+ </button>
394
+ )
395
+ }
396
+
397
+ /** Unstyled rail hit area used to collapse or expand a sidebar. */
398
+ const SidebarRail: Stateless<SidebarRailArgs> = ({
399
+ 'aria-label': label = 'Toggle Sidebar',
400
+ class: classes,
401
+ type = 'button',
402
+ 'set:onclick': onClick,
403
+ ...attrs
404
+ }) => {
405
+ const ctx = sidebar()
406
+
407
+ return (
408
+ <button
409
+ {...attrs}
410
+ aria-label={label}
411
+ class={classes}
412
+ data-sidebar="rail"
413
+ data-slot="sidebar-rail"
414
+ tabindex="-1"
415
+ title={label}
416
+ type={type}
417
+ set:onclick={toggleClick(ctx, onClick)}
418
+ />
419
+ )
420
+ }
421
+
422
+ /** Unstyled main content wrapper used with inset sidebars. */
423
+ const SidebarInset: Stateless<SidebarInsetArgs> = ({ children, class: classes, ...attrs }) => (
424
+ <main
425
+ {...attrs}
426
+ class={classes}
427
+ data-slot="sidebar-inset"
428
+ >
429
+ {children}
430
+ </main>
431
+ )
432
+
433
+ /** Unstyled input carrying the sidebar slot and data markers. */
434
+ const SidebarInput: Stateless<SidebarInputArgs> = ({ class: classes, ...attrs }) => (
435
+ <input {...attrs} class={classes} data-sidebar="input" data-slot="sidebar-input" />
436
+ )
437
+
438
+ /** Unstyled container for content at the top of a sidebar. */
439
+ const SidebarHeader: Stateless<SidebarSectionArgs> = ({ children, class: classes, ...attrs }) => (
440
+ <div {...attrs} class={classes} data-sidebar="header" data-slot="sidebar-header">
441
+ {children}
442
+ </div>
443
+ )
444
+
445
+ /** Unstyled container for content at the bottom of a sidebar. */
446
+ const SidebarFooter: Stateless<SidebarSectionArgs> = ({ children, class: classes, ...attrs }) => (
447
+ <div {...attrs} class={classes} data-sidebar="footer" data-slot="sidebar-footer">
448
+ {children}
449
+ </div>
450
+ )
451
+
452
+ /** Unstyled decorative separator between sidebar sections. */
453
+ const SidebarSeparator: Stateless<SidebarSeparatorArgs> = ({ class: classes, ...attrs }) => (
454
+ <div
455
+ {...attrs}
456
+ aria-hidden="true"
457
+ class={classes}
458
+ data-sidebar="separator"
459
+ data-slot="sidebar-separator"
460
+ role="none"
461
+ />
462
+ )
463
+
464
+ /** Unstyled container for the sidebar's primary content. */
465
+ const SidebarContent: Stateless<SidebarSectionArgs> = ({ children, class: classes, ...attrs }) => (
466
+ <div
467
+ {...attrs}
468
+ class={classes}
469
+ data-sidebar="content"
470
+ data-slot="sidebar-content"
471
+ >
472
+ {children}
473
+ </div>
474
+ )
475
+
476
+ /** Unstyled wrapper for a related group of sidebar controls. */
477
+ const SidebarGroup: Stateless<SidebarSectionArgs> = ({ children, class: classes, ...attrs }) => (
478
+ <div {...attrs} class={classes} data-sidebar="group" data-slot="sidebar-group">
479
+ {children}
480
+ </div>
481
+ )
482
+
483
+ /** Unstyled label for a sidebar group. */
484
+ const SidebarGroupLabel: Stateless<SidebarSectionArgs> = ({ children, class: classes, ...attrs }) => (
485
+ <div
486
+ {...attrs}
487
+ class={classes}
488
+ data-sidebar="group-label"
489
+ data-slot="sidebar-group-label"
490
+ >
491
+ {children}
492
+ </div>
493
+ )
494
+
495
+ /** Unstyled action button associated with a sidebar group. */
496
+ const SidebarGroupAction: Stateless<SidebarGroupActionArgs> = ({ children, class: classes, type = 'button', ...attrs }) => (
497
+ <button
498
+ {...attrs}
499
+ class={classes}
500
+ data-sidebar="group-action"
501
+ data-slot="sidebar-group-action"
502
+ type={type}
503
+ >
504
+ {children}
505
+ </button>
506
+ )
507
+
508
+ /** Unstyled content container inside a sidebar group. */
509
+ const SidebarGroupContent: Stateless<SidebarSectionArgs> = ({ children, class: classes, ...attrs }) => (
510
+ <div {...attrs} class={classes} data-sidebar="group-content" data-slot="sidebar-group-content">
511
+ {children}
512
+ </div>
513
+ )
514
+
515
+ /** Unstyled list for primary sidebar navigation items. */
516
+ const SidebarMenu: Stateless<SidebarMenuArgs> = ({ children, class: classes, ...attrs }) => (
517
+ <ul {...attrs} class={classes} data-sidebar="menu" data-slot="sidebar-menu">
518
+ {children}
519
+ </ul>
520
+ )
521
+
522
+ /** Unstyled item in the primary sidebar menu. */
523
+ const SidebarMenuItem: Stateless<SidebarMenuItemArgs> = ({ children, class: classes, ...attrs }) => (
524
+ <li {...attrs} class={classes} data-sidebar="menu-item" data-slot="sidebar-menu-item">
525
+ {children}
526
+ </li>
527
+ )
528
+
529
+ /** Unstyled primary sidebar menu control rendered as a button or anchor. */
530
+ const SidebarMenuButton: Stateless<SidebarMenuButtonArgs> = ({
531
+ as = 'button',
532
+ children,
533
+ class: classes,
534
+ isActive,
535
+ size = 'default',
536
+ tooltip,
537
+ ...attrs
538
+ }) => {
539
+ const ctx = SidebarContext()
540
+ const title = tooltip && ctx?.state === 'collapsed' ? tooltip : attrs.title
541
+
542
+ if (as === 'a') {
543
+ const anchor = attrs as IntrinsicElements['a']
544
+
545
+ return (
546
+ <a
547
+ {...anchor}
548
+ aria-current={isActive ? 'page' : undefined}
549
+ class={classes}
550
+ data-active={isActive ? 'true' : undefined}
551
+ data-sidebar="menu-button"
552
+ data-size={size}
553
+ data-slot="sidebar-menu-button"
554
+ title={title}
555
+ >
556
+ {children}
557
+ </a>
558
+ )
559
+ }
560
+
561
+ const { type = 'button', ...button } = attrs as IntrinsicElements['button']
562
+
563
+ return (
564
+ <button
565
+ {...button}
566
+ class={classes}
567
+ data-active={isActive ? 'true' : undefined}
568
+ data-sidebar="menu-button"
569
+ data-size={size}
570
+ data-slot="sidebar-menu-button"
571
+ title={title}
572
+ type={type}
573
+ >
574
+ {children}
575
+ </button>
576
+ )
577
+ }
578
+
579
+ /** Unstyled secondary action attached to a sidebar menu item. */
580
+ const SidebarMenuAction: Stateless<SidebarMenuActionArgs> = ({ children, class: classes, type = 'button', ...attrs }) => (
581
+ <button
582
+ {...attrs}
583
+ class={classes}
584
+ data-sidebar="menu-action"
585
+ data-slot="sidebar-menu-action"
586
+ type={type}
587
+ >
588
+ {children}
589
+ </button>
590
+ )
591
+
592
+ /** Unstyled metadata badge displayed beside a sidebar menu item. */
593
+ const SidebarMenuBadge: Stateless<SidebarMenuBadgeArgs> = ({ children, class: classes, ...attrs }) => (
594
+ <div
595
+ {...attrs}
596
+ class={classes}
597
+ data-sidebar="menu-badge"
598
+ data-slot="sidebar-menu-badge"
599
+ >
600
+ {children}
601
+ </div>
602
+ )
603
+
604
+ /** Unstyled loading placeholder for a sidebar menu item. */
605
+ const SidebarMenuSkeleton: Stateless<SidebarMenuSkeletonArgs> = ({
606
+ class: classes,
607
+ iconClass,
608
+ showIcon,
609
+ textClass,
610
+ width = '70%',
611
+ ...attrs
612
+ }) => (
613
+ <div {...attrs} class={classes} data-sidebar="menu-skeleton" data-slot="sidebar-menu-skeleton">
614
+ {showIcon ? <div class={iconClass} data-sidebar="menu-skeleton-icon" /> : null}
615
+ <div class={textClass} data-sidebar="menu-skeleton-text" style={`max-width:${width}`} />
616
+ </div>
617
+ )
618
+
619
+ /** Unstyled nested list within the sidebar menu. */
620
+ const SidebarMenuSub: Stateless<SidebarMenuSubArgs> = ({ children, class: classes, ...attrs }) => (
621
+ <ul
622
+ {...attrs}
623
+ class={classes}
624
+ data-sidebar="menu-sub"
625
+ data-slot="sidebar-menu-sub"
626
+ >
627
+ {children}
628
+ </ul>
629
+ )
630
+
631
+ /** Unstyled item in a nested sidebar menu. */
632
+ const SidebarMenuSubItem: Stateless<SidebarMenuSubItemArgs> = ({ children, class: classes, ...attrs }) => (
633
+ <li {...attrs} class={classes} data-sidebar="menu-sub-item" data-slot="sidebar-menu-sub-item">
634
+ {children}
635
+ </li>
636
+ )
637
+
638
+ /** Unstyled anchor for a nested sidebar menu item. */
639
+ const SidebarMenuSubButton: Stateless<SidebarMenuSubButtonArgs> = ({
640
+ children,
641
+ class: classes,
642
+ isActive,
643
+ size = 'md',
644
+ ...attrs
645
+ }) => (
646
+ <a
647
+ {...attrs}
648
+ aria-current={isActive ? 'page' : undefined}
649
+ class={classes}
650
+ data-active={isActive ? 'true' : undefined}
651
+ data-sidebar="menu-sub-button"
652
+ data-size={size}
653
+ data-slot="sidebar-menu-sub-button"
654
+ >
655
+ {children}
656
+ </a>
657
+ )
658
+
659
+ export {
660
+ Sidebar,
661
+ SidebarContent,
662
+ SidebarFooter,
663
+ SidebarGroup,
664
+ SidebarGroupAction,
665
+ SidebarGroupContent,
666
+ SidebarGroupLabel,
667
+ SidebarHeader,
668
+ SidebarInput,
669
+ SidebarInset,
670
+ SidebarMenu,
671
+ SidebarMenuAction,
672
+ SidebarMenuBadge,
673
+ SidebarMenuButton,
674
+ SidebarMenuItem,
675
+ SidebarMenuSkeleton,
676
+ SidebarMenuSub,
677
+ SidebarMenuSubButton,
678
+ SidebarMenuSubItem,
679
+ SidebarProvider,
680
+ SidebarRail,
681
+ SidebarSeparator,
682
+ SidebarTrigger,
683
+ }