@gentleduck/registry-ui 0.2.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.
Files changed (175) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/index.css +3 -0
  3. package/package.json +59 -0
  4. package/src/_old/_table/index.ts +5 -0
  5. package/src/_old/_table/table-advanced.constants.tsx +24 -0
  6. package/src/_old/_table/table-advanced.tsx +311 -0
  7. package/src/_old/_table/table-advanced.types.ts +272 -0
  8. package/src/_old/_table/table.constants.ts +2 -0
  9. package/src/_old/_table/table.hook.tsx +115 -0
  10. package/src/_old/_table/table.lib.ts +85 -0
  11. package/src/_old/_table/table.tsx +916 -0
  12. package/src/_old/_table/table.types.ts +118 -0
  13. package/src/_old/_table/todo.md +11 -0
  14. package/src/_old/_upload/index.ts +9 -0
  15. package/src/_old/_upload/todo.md +38 -0
  16. package/src/_old/_upload/upload-advanced-chunks.tsx +1624 -0
  17. package/src/_old/_upload/upload-advanced.tsx +507 -0
  18. package/src/_old/_upload/upload-sonner.tsx +58 -0
  19. package/src/_old/_upload/upload.assets.tsx +239 -0
  20. package/src/_old/_upload/upload.constants.tsx +75 -0
  21. package/src/_old/_upload/upload.dto.ts +19 -0
  22. package/src/_old/_upload/upload.lib.tsx +630 -0
  23. package/src/_old/_upload/upload.tsx +491 -0
  24. package/src/_old/_upload/upload.types.ts +436 -0
  25. package/src/accordion/accordion.tsx +247 -0
  26. package/src/accordion/index.ts +1 -0
  27. package/src/alert/alert.constants.ts +17 -0
  28. package/src/alert/alert.tsx +52 -0
  29. package/src/alert/index.ts +2 -0
  30. package/src/alert-dialog/alert-dialog.tsx +107 -0
  31. package/src/alert-dialog/index.ts +1 -0
  32. package/src/aspect-ratio/aspect-ratio.tsx +33 -0
  33. package/src/aspect-ratio/index.ts +1 -0
  34. package/src/audio/audio-record.tsx +776 -0
  35. package/src/audio/audio-visualizer.tsx +377 -0
  36. package/src/audio/audio.libs.ts +5 -0
  37. package/src/audio/audio.types.ts +50 -0
  38. package/src/audio/index.ts +2 -0
  39. package/src/avatar/avatar.tsx +78 -0
  40. package/src/avatar/index.ts +1 -0
  41. package/src/badge/badge.constants.ts +38 -0
  42. package/src/badge/badge.tsx +19 -0
  43. package/src/badge/index.ts +2 -0
  44. package/src/breadcrumb/breadcrumb.tsx +119 -0
  45. package/src/breadcrumb/index.ts +1 -0
  46. package/src/button/button.constants.ts +44 -0
  47. package/src/button/button.tsx +79 -0
  48. package/src/button/button.types.ts +38 -0
  49. package/src/button/index.ts +3 -0
  50. package/src/button-group/button-group.constants.ts +26 -0
  51. package/src/button-group/button-group.tsx +65 -0
  52. package/src/button-group/index.ts +2 -0
  53. package/src/calendar/calendar.tsx +191 -0
  54. package/src/calendar/index.ts +1 -0
  55. package/src/card/card.tsx +81 -0
  56. package/src/card/index.ts +1 -0
  57. package/src/carousel/carousel.tsx +211 -0
  58. package/src/carousel/carousel.types.ts +23 -0
  59. package/src/carousel/index.ts +2 -0
  60. package/src/chart/chart.libs.ts +27 -0
  61. package/src/chart/chart.tsx +260 -0
  62. package/src/chart/chart.types.ts +38 -0
  63. package/src/chart/index.ts +3 -0
  64. package/src/checkbox/checkbox.tsx +144 -0
  65. package/src/checkbox/checkbox.types.ts +24 -0
  66. package/src/checkbox/index.ts +2 -0
  67. package/src/collapsible/collapsible.tsx +151 -0
  68. package/src/collapsible/index.ts +1 -0
  69. package/src/combobox/combobox.tsx +132 -0
  70. package/src/combobox/index.ts +1 -0
  71. package/src/command/command.tsx +192 -0
  72. package/src/command/command.types.ts +11 -0
  73. package/src/command/index.ts +2 -0
  74. package/src/context-menu/context-menu.tsx +178 -0
  75. package/src/context-menu/index.ts +1 -0
  76. package/src/dialog/dialog-responsive.tsx +137 -0
  77. package/src/dialog/dialog.tsx +97 -0
  78. package/src/dialog/index.ts +2 -0
  79. package/src/direction/direction.tsx +13 -0
  80. package/src/direction/index.ts +1 -0
  81. package/src/drawer/drawer.tsx +185 -0
  82. package/src/drawer/index.ts +1 -0
  83. package/src/dropdown-menu/dropdown-menu.tsx +181 -0
  84. package/src/dropdown-menu/index.ts +1 -0
  85. package/src/empty/empty.constants.ts +15 -0
  86. package/src/empty/empty.tsx +73 -0
  87. package/src/empty/index.ts +2 -0
  88. package/src/field/field.constants.ts +22 -0
  89. package/src/field/field.tsx +203 -0
  90. package/src/field/index.ts +2 -0
  91. package/src/hover-card/hover-card.tsx +79 -0
  92. package/src/hover-card/index.ts +1 -0
  93. package/src/input/index.ts +1 -0
  94. package/src/input/input.tsx +45 -0
  95. package/src/input-group/index.ts +1 -0
  96. package/src/input-group/input-group.tsx +170 -0
  97. package/src/input-otp/index.ts +1 -0
  98. package/src/input-otp/input-otp.tsx +66 -0
  99. package/src/item/index.ts +2 -0
  100. package/src/item/item.constants.ts +22 -0
  101. package/src/item/item.tsx +185 -0
  102. package/src/json-editor/index.ts +4 -0
  103. package/src/json-editor/json-editor.hooks.ts +21 -0
  104. package/src/json-editor/json-editor.libs.ts +34 -0
  105. package/src/json-editor/json-editor.tsx +425 -0
  106. package/src/json-editor/json-editor.types.ts +80 -0
  107. package/src/json-editor/json-editor.view.tsx +110 -0
  108. package/src/json-editor/json-text-area.tsx +7 -0
  109. package/src/kbd/index.ts +1 -0
  110. package/src/kbd/kbd.tsx +39 -0
  111. package/src/label/index.ts +1 -0
  112. package/src/label/label.tsx +28 -0
  113. package/src/menubar/index.ts +1 -0
  114. package/src/menubar/menubar.tsx +213 -0
  115. package/src/navigation-menu/index.ts +1 -0
  116. package/src/navigation-menu/navigation-menu.tsx +152 -0
  117. package/src/pagination/index.ts +2 -0
  118. package/src/pagination/pagination.tsx +191 -0
  119. package/src/pagination/pagination.types.ts +17 -0
  120. package/src/popover/index.ts +1 -0
  121. package/src/popover/popover.tsx +35 -0
  122. package/src/preview-panel/index.ts +3 -0
  123. package/src/preview-panel/preview-panel-dialog.tsx +99 -0
  124. package/src/preview-panel/preview-panel.tsx +389 -0
  125. package/src/preview-panel/preview-panel.types.ts +49 -0
  126. package/src/progress/index.ts +1 -0
  127. package/src/progress/progress.tsx +32 -0
  128. package/src/radio-group/index.ts +1 -0
  129. package/src/radio-group/radio-group.tsx +92 -0
  130. package/src/resizable/index.ts +1 -0
  131. package/src/resizable/resizable.tsx +52 -0
  132. package/src/scroll-area/index.ts +1 -0
  133. package/src/scroll-area/scroll-area.tsx +30 -0
  134. package/src/select/index.ts +1 -0
  135. package/src/select/select.tsx +138 -0
  136. package/src/separator/index.ts +1 -0
  137. package/src/separator/separator.tsx +28 -0
  138. package/src/sheet/index.ts +2 -0
  139. package/src/sheet/sheet.constants.tsx +20 -0
  140. package/src/sheet/sheet.tsx +92 -0
  141. package/src/sidebar/index.ts +4 -0
  142. package/src/sidebar/sidebar.constants.ts +30 -0
  143. package/src/sidebar/sidebar.hooks.ts +13 -0
  144. package/src/sidebar/sidebar.tsx +676 -0
  145. package/src/sidebar/sidebar.types.ts +28 -0
  146. package/src/skeleton/index.ts +1 -0
  147. package/src/skeleton/skeleton.tsx +22 -0
  148. package/src/slider/index.ts +1 -0
  149. package/src/slider/slider.tsx +57 -0
  150. package/src/sonner/index.ts +4 -0
  151. package/src/sonner/sonner.chunks.tsx +80 -0
  152. package/src/sonner/sonner.libs.ts +13 -0
  153. package/src/sonner/sonner.tsx +31 -0
  154. package/src/sonner/sonner.types.ts +9 -0
  155. package/src/switch/index.ts +1 -0
  156. package/src/switch/switch.tsx +63 -0
  157. package/src/table/index.ts +1 -0
  158. package/src/table/table.tsx +95 -0
  159. package/src/tabs/index.ts +1 -0
  160. package/src/tabs/tabs.tsx +151 -0
  161. package/src/textarea/index.ts +1 -0
  162. package/src/textarea/textarea.tsx +24 -0
  163. package/src/toggle/index.ts +2 -0
  164. package/src/toggle/toggle.constants.ts +22 -0
  165. package/src/toggle/toggle.tsx +24 -0
  166. package/src/toggle-group/index.ts +1 -0
  167. package/src/toggle-group/toggle-group.tsx +69 -0
  168. package/src/tooltip/index.ts +1 -0
  169. package/src/tooltip/tooltip.tsx +32 -0
  170. package/src/upload/index.ts +1 -0
  171. package/src/upload/upload.constants.tsx +19 -0
  172. package/src/upload/upload.libs.ts +97 -0
  173. package/src/upload/upload.tsx +340 -0
  174. package/src/upload/upload.types.ts +44 -0
  175. package/tsconfig.json +25 -0
@@ -0,0 +1,676 @@
1
+ 'use client'
2
+
3
+ import { useIsMobile } from '@gentleduck/hooks/use-is-mobile'
4
+ import { cn } from '@gentleduck/libs/cn'
5
+ import { type Direction, useDirection } from '@gentleduck/primitives/direction'
6
+ import { Slot } from '@gentleduck/primitives/slot'
7
+ import type { VariantProps } from '@gentleduck/variants'
8
+ import { PanelLeftIcon } from 'lucide-react'
9
+ import * as React from 'react'
10
+ import { Button } from '../button'
11
+ import { Input } from '../input'
12
+ import { Separator } from '../separator'
13
+ import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from '../sheet'
14
+ import { Skeleton } from '../skeleton'
15
+ import { Tooltip, TooltipContent, TooltipTrigger } from '../tooltip'
16
+ import {
17
+ SIDEBAR_COOKIE_MAX_AGE,
18
+ SIDEBAR_COOKIE_NAME,
19
+ SIDEBAR_KEYBOARD_SHORTCUT,
20
+ SIDEBAR_WIDTH,
21
+ SIDEBAR_WIDTH_ICON,
22
+ SIDEBAR_WIDTH_MOBILE,
23
+ sidebarMenuButtonVariants,
24
+ } from './sidebar.constants'
25
+ import { SidebarContext, useSidebar } from './sidebar.hooks'
26
+ import type { SidebarContextProps, SidebarProps, SidebarProviderProps } from './sidebar.types'
27
+
28
+ function SidebarProvider({
29
+ defaultOpen = true,
30
+ open: openProp,
31
+ onOpenChange: setOpenProp,
32
+ className,
33
+ style,
34
+ dir,
35
+ children,
36
+ ...props
37
+ }: SidebarProviderProps) {
38
+ const isMobile = useIsMobile()
39
+ const [openMobile, setOpenMobile] = React.useState(false)
40
+ const direction = useDirection(dir as Direction)
41
+
42
+ // This is the internal state of the sidebar.
43
+ // We use openProp and setOpenProp for control from outside the component.
44
+ const [_open, _setOpen] = React.useState(defaultOpen)
45
+ const open = openProp ?? _open
46
+ const setOpen = React.useCallback(
47
+ (value: boolean | ((value: boolean) => boolean)) => {
48
+ const openState = typeof value === 'function' ? value(open) : value
49
+ if (setOpenProp) {
50
+ setOpenProp(openState)
51
+ } else {
52
+ _setOpen(openState)
53
+ }
54
+
55
+ // This sets the cookie to keep the sidebar state.
56
+ document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
57
+ },
58
+ [setOpenProp, open],
59
+ )
60
+
61
+ // Helper to toggle the sidebar.
62
+ const toggleSidebar = React.useCallback(() => {
63
+ return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open)
64
+ }, [isMobile, setOpen, setOpenMobile])
65
+
66
+ // Adds a keyboard shortcut to toggle the sidebar.
67
+ React.useEffect(() => {
68
+ const handleKeyDown = (event: KeyboardEvent) => {
69
+ if (event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey)) {
70
+ event.preventDefault()
71
+ toggleSidebar()
72
+ }
73
+ }
74
+
75
+ window.addEventListener('keydown', handleKeyDown)
76
+ return () => window.removeEventListener('keydown', handleKeyDown)
77
+ }, [toggleSidebar])
78
+
79
+ // We add a state so that we can do data-state="expanded" or "collapsed".
80
+ // This makes it easier to style the sidebar with Tailwind classes.
81
+ const state = open ? 'expanded' : 'collapsed'
82
+
83
+ const contextValue = React.useMemo<SidebarContextProps>(
84
+ () => ({
85
+ state,
86
+ open,
87
+ setOpen,
88
+ isMobile,
89
+ openMobile,
90
+ setOpenMobile,
91
+ toggleSidebar,
92
+ dir: direction,
93
+ }),
94
+ [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar, direction],
95
+ )
96
+
97
+ return (
98
+ <SidebarContext.Provider value={contextValue}>
99
+ <div
100
+ data-slot="sidebar-wrapper"
101
+ style={
102
+ {
103
+ '--sidebar-width': SIDEBAR_WIDTH,
104
+ '--sidebar-width-icon': SIDEBAR_WIDTH_ICON,
105
+ ...style,
106
+ } as React.CSSProperties
107
+ }
108
+ dir={direction}
109
+ className={cn('group/sidebar-wrapper flex min-h-svh w-full has-data-[variant=inset]:bg-sidebar', className)}
110
+ {...props}>
111
+ {children}
112
+ </div>
113
+ </SidebarContext.Provider>
114
+ )
115
+ }
116
+
117
+ function Sidebar({
118
+ side = 'left',
119
+ variant = 'sidebar',
120
+ collapsible = 'offcanvas',
121
+ className,
122
+ children,
123
+ dir,
124
+ mobileTitle = 'Sidebar',
125
+ mobileDescription = 'Displays the mobile sidebar.',
126
+ ...props
127
+ }: SidebarProps) {
128
+ const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
129
+ const direction = useDirection(dir as Direction)
130
+
131
+ if (collapsible === 'none') {
132
+ return (
133
+ <div
134
+ dir={direction}
135
+ data-slot="sidebar"
136
+ className={cn('flex h-full w-(--sidebar-width) flex-col bg-sidebar text-sidebar-foreground', className)}
137
+ {...props}>
138
+ {children}
139
+ </div>
140
+ )
141
+ }
142
+
143
+ if (isMobile) {
144
+ return (
145
+ <Sheet dir={direction} open={openMobile} onOpenChange={setOpenMobile} {...props}>
146
+ <SheetContent
147
+ dir={direction}
148
+ data-sidebar="sidebar"
149
+ data-slot="sidebar"
150
+ data-mobile="true"
151
+ className="w-[--sidebar-width] bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden"
152
+ style={
153
+ {
154
+ '--sidebar-width': SIDEBAR_WIDTH_MOBILE,
155
+ } as React.CSSProperties
156
+ }
157
+ side={side}>
158
+ <SheetHeader className="sr-only">
159
+ <SheetTitle>{mobileTitle}</SheetTitle>
160
+ <SheetDescription>{mobileDescription}</SheetDescription>
161
+ </SheetHeader>
162
+ <div className="flex h-full w-full flex-col">{children}</div>
163
+ </SheetContent>
164
+ </Sheet>
165
+ )
166
+ }
167
+
168
+ return (
169
+ <div
170
+ dir={direction}
171
+ className="group peer hidden text-sidebar-foreground md:block"
172
+ data-state={state}
173
+ data-collapsible={state === 'collapsed' ? collapsible : ''}
174
+ data-variant={variant}
175
+ data-side={side}
176
+ data-slot="sidebar">
177
+ {/* This is what handles the sidebar gap on desktop */}
178
+ <div
179
+ data-slot="sidebar-gap"
180
+ className={cn(
181
+ 'relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear',
182
+ 'group-data-[collapsible=offcanvas]:w-0',
183
+ variant === 'floating' || variant === 'inset'
184
+ ? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]'
185
+ : 'group-data-[collapsible=icon]:w-(--sidebar-width-icon)',
186
+ )}
187
+ />
188
+ <div
189
+ data-slot="sidebar-container"
190
+ data-side={side}
191
+ className={cn(
192
+ 'fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear data-[side=right]:right-0 data-[side=left]:left-0 data-[side=right]:group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)] data-[side=left]:group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)] md:flex',
193
+ // Adjust the padding for floating and inset variants.
194
+ variant === 'floating' || variant === 'inset'
195
+ ? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]'
196
+ : 'group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l',
197
+ className,
198
+ )}
199
+ {...props}>
200
+ <div
201
+ data-sidebar="sidebar"
202
+ data-slot="sidebar-inner"
203
+ className="flex size-full flex-col overflow-hidden bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:shadow-sm group-data-[variant=floating]:ring-1 group-data-[variant=floating]:ring-sidebar-border">
204
+ {children}
205
+ </div>
206
+ </div>
207
+ </div>
208
+ )
209
+ }
210
+
211
+ function SidebarTrigger({
212
+ className,
213
+ onClick,
214
+ text = 'Toggle Sidebar',
215
+ ...props
216
+ }: React.ComponentProps<typeof Button> & { text?: string }) {
217
+ const { toggleSidebar } = useSidebar()
218
+ const direction = useDirection()
219
+
220
+ return (
221
+ <Button
222
+ data-sidebar="trigger"
223
+ data-slot="sidebar-trigger"
224
+ variant="ghost"
225
+ size="icon-sm"
226
+ dir={direction}
227
+ className={cn(className)}
228
+ onClick={(event) => {
229
+ onClick?.(event)
230
+ toggleSidebar()
231
+ }}
232
+ {...props}>
233
+ <PanelLeftIcon aria-hidden="true" className="rtl:-scale-x-100" />
234
+ <span className="sr-only">{text}</span>
235
+ </Button>
236
+ )
237
+ }
238
+
239
+ function SidebarRail({
240
+ className,
241
+ text = 'Toggle Sidebar',
242
+ ...props
243
+ }: React.ComponentProps<'button'> & { text?: string }) {
244
+ const { toggleSidebar } = useSidebar()
245
+ const direction = useDirection()
246
+
247
+ return (
248
+ <button
249
+ data-sidebar="rail"
250
+ data-slot="sidebar-rail"
251
+ aria-label={text}
252
+ tabIndex={-1}
253
+ onClick={toggleSidebar}
254
+ dir={direction}
255
+ className={cn(
256
+ 'absolute inset-y-0 z-20 hidden w-4 transition-all ease-linear after:absolute after:inset-y-0 after:start-1/2 after:w-0.5 hover:after:bg-sidebar-border group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex ltr:-translate-x-1/2 rtl:-translate-x-1/2',
257
+ 'in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize',
258
+ '[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize',
259
+ 'group-data-[collapsible=offcanvas]:translate-x-0 hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:after:left-full',
260
+ '[[data-side=left][data-collapsible=offcanvas]_&]:-right-2',
261
+ '[[data-side=right][data-collapsible=offcanvas]_&]:-left-2',
262
+ className,
263
+ )}
264
+ {...props}
265
+ />
266
+ )
267
+ }
268
+
269
+ function SidebarInset({ className, ...props }: React.ComponentProps<'main'>) {
270
+ const direction = useDirection()
271
+
272
+ return (
273
+ <main
274
+ data-slot="sidebar-inset"
275
+ dir={direction}
276
+ className={cn(
277
+ 'relative flex w-full flex-1 flex-col bg-background md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ms-2 md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ms-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm',
278
+ className,
279
+ )}
280
+ {...props}
281
+ />
282
+ )
283
+ }
284
+
285
+ function SidebarInput({ className, ...props }: React.ComponentProps<typeof Input>) {
286
+ const direction = useDirection()
287
+
288
+ return (
289
+ <Input
290
+ data-slot="sidebar-input"
291
+ data-sidebar="input"
292
+ dir={direction}
293
+ className={cn('h-8 w-full bg-background shadow-none', className)}
294
+ {...props}
295
+ />
296
+ )
297
+ }
298
+
299
+ function SidebarHeader({ className, ...props }: React.ComponentProps<'div'>) {
300
+ const direction = useDirection()
301
+
302
+ return (
303
+ <div
304
+ data-slot="sidebar-header"
305
+ data-sidebar="header"
306
+ dir={direction}
307
+ className={cn('flex flex-col gap-2 p-2', className)}
308
+ {...props}
309
+ />
310
+ )
311
+ }
312
+
313
+ function SidebarFooter({ className, ...props }: React.ComponentProps<'div'>) {
314
+ const direction = useDirection()
315
+
316
+ return (
317
+ <div
318
+ data-slot="sidebar-footer"
319
+ data-sidebar="footer"
320
+ dir={direction}
321
+ className={cn('flex flex-col gap-2 p-2', className)}
322
+ {...props}
323
+ />
324
+ )
325
+ }
326
+
327
+ function SidebarSeparator({ className, ...props }: React.ComponentProps<typeof Separator>) {
328
+ const direction = useDirection()
329
+
330
+ return (
331
+ <Separator
332
+ data-slot="sidebar-separator"
333
+ data-sidebar="separator"
334
+ dir={direction}
335
+ className={cn('mx-2 w-auto bg-sidebar-border', className)}
336
+ {...props}
337
+ />
338
+ )
339
+ }
340
+
341
+ function SidebarContent({
342
+ className,
343
+ noScroll = true,
344
+ ...props
345
+ }: React.ComponentProps<'div'> & { noScroll?: boolean }) {
346
+ const direction = useDirection()
347
+
348
+ return (
349
+ <div
350
+ data-slot="sidebar-content"
351
+ data-sidebar="content"
352
+ dir={direction}
353
+ className={cn(
354
+ 'flex min-h-0 flex-1 flex-col gap-0 overflow-auto group-data-[collapsible=icon]:overflow-hidden',
355
+ noScroll && 'no-scrollbar',
356
+ className,
357
+ )}
358
+ {...props}
359
+ />
360
+ )
361
+ }
362
+
363
+ function SidebarGroup({ className, ...props }: React.ComponentProps<'div'>) {
364
+ const direction = useDirection()
365
+
366
+ return (
367
+ <div
368
+ data-slot="sidebar-group"
369
+ data-sidebar="group"
370
+ dir={direction}
371
+ className={cn('relative flex w-full min-w-0 flex-col p-2', className)}
372
+ {...props}
373
+ />
374
+ )
375
+ }
376
+
377
+ function SidebarGroupLabel({
378
+ className,
379
+ asChild = false,
380
+ ...props
381
+ }: React.ComponentProps<'div'> & { asChild?: boolean }) {
382
+ const Comp = asChild ? Slot : 'div'
383
+ const direction = useDirection()
384
+
385
+ return (
386
+ <Comp
387
+ data-slot="sidebar-group-label"
388
+ data-sidebar="group-label"
389
+ dir={direction}
390
+ className={cn(
391
+ 'flex h-8 shrink-0 items-center rounded-md px-2 font-medium text-sidebar-foreground/70 text-xs outline-hidden ring-sidebar-ring transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0 [&>svg]:size-4 [&>svg]:shrink-0',
392
+ className,
393
+ )}
394
+ {...props}
395
+ />
396
+ )
397
+ }
398
+
399
+ function SidebarGroupAction({
400
+ className,
401
+ asChild = false,
402
+ ...props
403
+ }: React.ComponentProps<'button'> & { asChild?: boolean }) {
404
+ const Comp = asChild ? Slot : 'button'
405
+ const direction = useDirection()
406
+
407
+ return (
408
+ <Comp
409
+ data-slot="sidebar-group-action"
410
+ data-sidebar="group-action"
411
+ dir={direction}
412
+ className={cn(
413
+ 'absolute end-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-hidden ring-sidebar-ring transition-transform after:absolute after:-inset-2 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 group-data-[collapsible=icon]:hidden md:after:hidden [&>svg]:size-4 [&>svg]:shrink-0',
414
+ className,
415
+ )}
416
+ {...props}
417
+ />
418
+ )
419
+ }
420
+
421
+ function SidebarGroupContent({ className, ...props }: React.ComponentProps<'div'>) {
422
+ const direction = useDirection()
423
+
424
+ return (
425
+ <div
426
+ data-slot="sidebar-group-content"
427
+ data-sidebar="group-content"
428
+ dir={direction}
429
+ className={cn('w-full text-sm', className)}
430
+ {...props}
431
+ />
432
+ )
433
+ }
434
+
435
+ function SidebarMenu({ className, ...props }: React.ComponentProps<'ul'>) {
436
+ const direction = useDirection()
437
+
438
+ return (
439
+ <ul
440
+ data-slot="sidebar-menu"
441
+ data-sidebar="menu"
442
+ dir={direction}
443
+ className={cn('flex w-full min-w-0 flex-col gap-0', className)}
444
+ {...props}
445
+ />
446
+ )
447
+ }
448
+
449
+ function SidebarMenuItem({ className, dir, ...props }: React.ComponentProps<'li'>) {
450
+ const direction = useDirection(dir as Direction)
451
+ return (
452
+ <li
453
+ data-slot="sidebar-menu-item"
454
+ dir={direction}
455
+ data-sidebar="menu-item"
456
+ className={cn('group/menu-item relative focus-within:z-10', className)}
457
+ {...props}
458
+ />
459
+ )
460
+ }
461
+
462
+ function SidebarMenuButton({
463
+ asChild = false,
464
+ isActive = false,
465
+ variant = 'default',
466
+ size = 'default',
467
+ tooltip,
468
+ dir,
469
+ className,
470
+ ...props
471
+ }: React.ComponentProps<'button'> & {
472
+ asChild?: boolean
473
+ isActive?: boolean
474
+ tooltip?: string | React.ComponentProps<typeof TooltipContent>
475
+ } & VariantProps<typeof sidebarMenuButtonVariants>) {
476
+ const Comp = asChild ? Slot : 'button'
477
+ const { isMobile, state } = useSidebar()
478
+ const direction = useDirection(dir as Direction)
479
+ const fallbackTooltipSide = direction === 'rtl' ? 'left' : 'right'
480
+
481
+ const button = (
482
+ <Comp
483
+ data-slot="sidebar-menu-button"
484
+ data-sidebar="menu-button"
485
+ data-size={size}
486
+ data-active={isActive || undefined}
487
+ className={cn(sidebarMenuButtonVariants({ variant, size }), className)}
488
+ {...props}
489
+ />
490
+ )
491
+
492
+ if (!tooltip) {
493
+ return button
494
+ }
495
+
496
+ const tooltipProps = typeof tooltip === 'string' ? { children: tooltip } : tooltip
497
+
498
+ return (
499
+ <Tooltip dir={direction}>
500
+ <TooltipTrigger asChild>{button}</TooltipTrigger>
501
+ <TooltipContent
502
+ side={fallbackTooltipSide}
503
+ align="center"
504
+ hidden={state !== 'collapsed' || isMobile}
505
+ {...tooltipProps}
506
+ />
507
+ </Tooltip>
508
+ )
509
+ }
510
+
511
+ function SidebarMenuAction({
512
+ className,
513
+ asChild = false,
514
+ showOnHover = false,
515
+ ...props
516
+ }: React.ComponentProps<'button'> & {
517
+ asChild?: boolean
518
+ showOnHover?: boolean
519
+ }) {
520
+ const Comp = asChild ? Slot : 'button'
521
+ const direction = useDirection()
522
+
523
+ return (
524
+ <Comp
525
+ data-slot="sidebar-menu-action"
526
+ data-sidebar="menu-action"
527
+ dir={direction}
528
+ className={cn(
529
+ 'absolute end-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-hidden ring-sidebar-ring transition-transform after:absolute after:-inset-2 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground group-data-[collapsible=icon]:hidden peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 md:after:hidden [&>svg]:size-4 [&>svg]:shrink-0',
530
+ showOnHover &&
531
+ 'group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 aria-expanded:opacity-100 peer-data-active/menu-button:text-sidebar-accent-foreground md:opacity-0',
532
+ className,
533
+ )}
534
+ {...props}
535
+ />
536
+ )
537
+ }
538
+
539
+ function SidebarMenuBadge({ className, ...props }: React.ComponentProps<'div'>) {
540
+ const direction = useDirection()
541
+
542
+ return (
543
+ <div
544
+ data-slot="sidebar-menu-badge"
545
+ data-sidebar="menu-badge"
546
+ dir={direction}
547
+ className={cn(
548
+ 'pointer-events-none absolute end-1 flex h-5 min-w-5 select-none items-center justify-center rounded-md px-1 font-medium text-sidebar-foreground text-xs tabular-nums peer-hover/menu-button:text-sidebar-accent-foreground group-data-[collapsible=icon]:hidden peer-data-[size=default]/menu-button:top-1.5 peer-data-[size=lg]/menu-button:top-2.5 peer-data-[size=sm]/menu-button:top-1 peer-data-active/menu-button:text-sidebar-accent-foreground',
549
+ className,
550
+ )}
551
+ {...props}
552
+ />
553
+ )
554
+ }
555
+
556
+ function SidebarMenuSkeleton({
557
+ className,
558
+ showIcon = false,
559
+ ...props
560
+ }: React.ComponentProps<'div'> & {
561
+ showIcon?: boolean
562
+ }) {
563
+ const direction = useDirection()
564
+
565
+ // Random width between 50 to 90%.
566
+ const [width] = React.useState(() => {
567
+ return `${Math.floor(Math.random() * 40) + 50}%`
568
+ })
569
+
570
+ return (
571
+ <div
572
+ data-slot="sidebar-menu-skeleton"
573
+ data-sidebar="menu-skeleton"
574
+ dir={direction}
575
+ className={cn('flex h-8 items-center gap-2 rounded-md px-2', className)}
576
+ {...props}>
577
+ {showIcon && <Skeleton className="size-4 rounded-md" data-sidebar="menu-skeleton-icon" />}
578
+ <Skeleton
579
+ className="h-4 max-w-(--skeleton-width) flex-1"
580
+ data-sidebar="menu-skeleton-text"
581
+ style={
582
+ {
583
+ '--skeleton-width': width,
584
+ } as React.CSSProperties
585
+ }
586
+ />
587
+ </div>
588
+ )
589
+ }
590
+
591
+ function SidebarMenuSub({ className, dir, ...props }: React.ComponentProps<'ul'>) {
592
+ const direction = useDirection(dir as Direction)
593
+
594
+ return (
595
+ <ul
596
+ data-slot="sidebar-menu-sub"
597
+ data-sidebar="menu-sub"
598
+ dir={direction}
599
+ className={cn(
600
+ 'mx-3.5 flex min-w-0 flex-col gap-1 border-sidebar-border border-s px-2.5 py-0.5 group-data-[collapsible=icon]:hidden ltr:translate-x-px rtl:-translate-x-px',
601
+ className,
602
+ )}
603
+ {...props}
604
+ />
605
+ )
606
+ }
607
+
608
+ function SidebarMenuSubItem({ className, dir, ...props }: React.ComponentProps<'li'>) {
609
+ const direction = useDirection(dir as Direction)
610
+ return (
611
+ <li
612
+ data-slot="sidebar-menu-sub-item"
613
+ data-sidebar="menu-sub-item"
614
+ dir={direction}
615
+ className={cn('group/menu-sub-item relative focus-within:z-10', className)}
616
+ {...props}
617
+ />
618
+ )
619
+ }
620
+
621
+ function SidebarMenuSubButton({
622
+ asChild = false,
623
+ size = 'md',
624
+ isActive = false,
625
+ className,
626
+ dir,
627
+ ...props
628
+ }: React.ComponentProps<'a'> & {
629
+ asChild?: boolean
630
+ size?: 'sm' | 'md'
631
+ isActive?: boolean
632
+ }) {
633
+ const Comp = asChild ? Slot : 'a'
634
+ const direction = useDirection(dir as Direction)
635
+
636
+ return (
637
+ <Comp
638
+ data-slot="sidebar-menu-sub-button"
639
+ data-sidebar="menu-sub-button"
640
+ data-size={size}
641
+ data-active={isActive || undefined}
642
+ dir={direction}
643
+ className={cn(
644
+ 'flex h-7 min-w-0 items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground outline-hidden ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-active:bg-sidebar-accent data-[size=md]:text-sm data-[size=sm]:text-xs data-active:text-sidebar-accent-foreground group-data-[collapsible=icon]:hidden ltr:-translate-x-px rtl:translate-x-px [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground',
645
+ className,
646
+ )}
647
+ {...props}
648
+ />
649
+ )
650
+ }
651
+
652
+ export {
653
+ Sidebar,
654
+ SidebarContent,
655
+ SidebarFooter,
656
+ SidebarGroup,
657
+ SidebarGroupAction,
658
+ SidebarGroupContent,
659
+ SidebarGroupLabel,
660
+ SidebarHeader,
661
+ SidebarInput,
662
+ SidebarInset,
663
+ SidebarMenu,
664
+ SidebarMenuAction,
665
+ SidebarMenuBadge,
666
+ SidebarMenuButton,
667
+ SidebarMenuItem,
668
+ SidebarMenuSkeleton,
669
+ SidebarMenuSub,
670
+ SidebarMenuSubButton,
671
+ SidebarMenuSubItem,
672
+ SidebarProvider,
673
+ SidebarRail,
674
+ SidebarSeparator,
675
+ SidebarTrigger,
676
+ }
@@ -0,0 +1,28 @@
1
+ import type { Direction } from '@gentleduck/primitives/direction'
2
+ import type { Dispatch, SetStateAction } from 'react'
3
+
4
+ export type SidebarDirection = Direction
5
+
6
+ export type SidebarContextProps = {
7
+ state: 'expanded' | 'collapsed'
8
+ open: boolean
9
+ setOpen: Dispatch<SetStateAction<boolean>>
10
+ openMobile: boolean
11
+ setOpenMobile: Dispatch<SetStateAction<boolean>>
12
+ isMobile: boolean
13
+ toggleSidebar: () => void
14
+ dir: SidebarDirection
15
+ }
16
+ export type SidebarProviderProps = React.ComponentProps<'div'> & {
17
+ defaultOpen?: boolean
18
+ open?: boolean
19
+ onOpenChange?: (open: boolean) => void
20
+ }
21
+
22
+ export type SidebarProps = React.ComponentProps<'div'> & {
23
+ side?: 'left' | 'right'
24
+ variant?: 'sidebar' | 'floating' | 'inset'
25
+ collapsible?: 'offcanvas' | 'icon' | 'none'
26
+ mobileTitle?: string
27
+ mobileDescription?: string
28
+ }
@@ -0,0 +1 @@
1
+ export * from './skeleton'