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.
- package/LICENSE +15 -0
- package/README.md +126 -0
- package/dist/accordion.js +130 -0
- package/dist/avatar.js +54 -0
- package/dist/calendar.js +2 -0
- package/dist/carousel.js +239 -0
- package/dist/chart.js +790 -0
- package/dist/checkbox-group.js +70 -0
- package/dist/checkbox.js +77 -0
- package/dist/chunks/bar-CVafh6C1.js +99 -0
- package/dist/chunks/calendar-q8jZ8Cxr.js +1923 -0
- package/dist/chunks/collection-DtRB63U4.js +111 -0
- package/dist/chunks/data-table-DpkWv4y4.js +1039 -0
- package/dist/chunks/menu-B45IyHHC.js +704 -0
- package/dist/chunks/native-BJdhd9XJ.js +20 -0
- package/dist/chunks/popup-C8Bb3l_g.js +459 -0
- package/dist/chunks/popup-surface-BDCgtVU0.js +18 -0
- package/dist/chunks/position-D6_i_SRn.js +434 -0
- package/dist/chunks/virtual-list-B7hjGkjk.js +413 -0
- package/dist/collapsible.js +106 -0
- package/dist/command.js +263 -0
- package/dist/context-menu.js +112 -0
- package/dist/data-table.js +5 -0
- package/dist/dialog.js +207 -0
- package/dist/direction.js +22 -0
- package/dist/drawer.js +139 -0
- package/dist/field.js +26 -0
- package/dist/index.js +38 -0
- package/dist/input-date.js +994 -0
- package/dist/input-group.js +52 -0
- package/dist/input-otp.js +179 -0
- package/dist/menu.js +2 -0
- package/dist/menubar.js +236 -0
- package/dist/message-scroller.js +446 -0
- package/dist/navigation-menu.js +330 -0
- package/dist/popover.js +307 -0
- package/dist/progress.js +39 -0
- package/dist/radio-group.js +107 -0
- package/dist/resizable.js +172 -0
- package/dist/select.js +961 -0
- package/dist/sidebar.js +343 -0
- package/dist/slider.js +259 -0
- package/dist/switch.js +53 -0
- package/dist/tabs.js +182 -0
- package/dist/toast.js +492 -0
- package/dist/toggle-group.js +111 -0
- package/dist/toggle.js +52 -0
- package/dist/toolbar.js +127 -0
- package/dist/tooltip.js +196 -0
- package/dist/utils.js +104 -0
- package/dist/virtual-list.js +2 -0
- package/package.json +250 -0
- package/src/accordion.tsx +261 -0
- package/src/availability.ts +261 -0
- package/src/avatar.tsx +99 -0
- package/src/bar.ts +156 -0
- package/src/calendar.tsx +1441 -0
- package/src/carousel.tsx +424 -0
- package/src/chart.tsx +1194 -0
- package/src/checkbox-group.tsx +132 -0
- package/src/checkbox.tsx +130 -0
- package/src/collapsible.tsx +188 -0
- package/src/collection.ts +154 -0
- package/src/command.tsx +511 -0
- package/src/context-menu.tsx +233 -0
- package/src/data-table-contract.ts +143 -0
- package/src/data-table-model.ts +760 -0
- package/src/data-table.tsx +475 -0
- package/src/dialog.tsx +393 -0
- package/src/direction.tsx +45 -0
- package/src/drawer.tsx +251 -0
- package/src/field.tsx +61 -0
- package/src/index.ts +37 -0
- package/src/input-date.tsx +1539 -0
- package/src/input-group.tsx +142 -0
- package/src/input-otp.tsx +324 -0
- package/src/menu-cluster.ts +124 -0
- package/src/menu.tsx +1095 -0
- package/src/menubar.tsx +459 -0
- package/src/message-scroller.tsx +732 -0
- package/src/native.ts +26 -0
- package/src/navigation-menu.tsx +578 -0
- package/src/popover.tsx +519 -0
- package/src/popup-surface.tsx +31 -0
- package/src/popup.ts +569 -0
- package/src/position.ts +523 -0
- package/src/progress.tsx +70 -0
- package/src/radio-group.tsx +186 -0
- package/src/resizable.tsx +310 -0
- package/src/segments.ts +922 -0
- package/src/select.tsx +1501 -0
- package/src/sidebar.tsx +683 -0
- package/src/slider.tsx +424 -0
- package/src/switch.tsx +104 -0
- package/src/tabs.tsx +314 -0
- package/src/toast.tsx +923 -0
- package/src/toggle-group.tsx +249 -0
- package/src/toggle.tsx +91 -0
- package/src/toolbar.tsx +212 -0
- package/src/tooltip.tsx +359 -0
- package/src/utils.ts +204 -0
- package/src/virtual-list.tsx +205 -0
- package/src/virtual.ts +385 -0
package/src/sidebar.tsx
ADDED
|
@@ -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
|
+
}
|