@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.
- package/CHANGELOG.md +62 -0
- package/index.css +3 -0
- package/package.json +59 -0
- package/src/_old/_table/index.ts +5 -0
- package/src/_old/_table/table-advanced.constants.tsx +24 -0
- package/src/_old/_table/table-advanced.tsx +311 -0
- package/src/_old/_table/table-advanced.types.ts +272 -0
- package/src/_old/_table/table.constants.ts +2 -0
- package/src/_old/_table/table.hook.tsx +115 -0
- package/src/_old/_table/table.lib.ts +85 -0
- package/src/_old/_table/table.tsx +916 -0
- package/src/_old/_table/table.types.ts +118 -0
- package/src/_old/_table/todo.md +11 -0
- package/src/_old/_upload/index.ts +9 -0
- package/src/_old/_upload/todo.md +38 -0
- package/src/_old/_upload/upload-advanced-chunks.tsx +1624 -0
- package/src/_old/_upload/upload-advanced.tsx +507 -0
- package/src/_old/_upload/upload-sonner.tsx +58 -0
- package/src/_old/_upload/upload.assets.tsx +239 -0
- package/src/_old/_upload/upload.constants.tsx +75 -0
- package/src/_old/_upload/upload.dto.ts +19 -0
- package/src/_old/_upload/upload.lib.tsx +630 -0
- package/src/_old/_upload/upload.tsx +491 -0
- package/src/_old/_upload/upload.types.ts +436 -0
- package/src/accordion/accordion.tsx +247 -0
- package/src/accordion/index.ts +1 -0
- package/src/alert/alert.constants.ts +17 -0
- package/src/alert/alert.tsx +52 -0
- package/src/alert/index.ts +2 -0
- package/src/alert-dialog/alert-dialog.tsx +107 -0
- package/src/alert-dialog/index.ts +1 -0
- package/src/aspect-ratio/aspect-ratio.tsx +33 -0
- package/src/aspect-ratio/index.ts +1 -0
- package/src/audio/audio-record.tsx +776 -0
- package/src/audio/audio-visualizer.tsx +377 -0
- package/src/audio/audio.libs.ts +5 -0
- package/src/audio/audio.types.ts +50 -0
- package/src/audio/index.ts +2 -0
- package/src/avatar/avatar.tsx +78 -0
- package/src/avatar/index.ts +1 -0
- package/src/badge/badge.constants.ts +38 -0
- package/src/badge/badge.tsx +19 -0
- package/src/badge/index.ts +2 -0
- package/src/breadcrumb/breadcrumb.tsx +119 -0
- package/src/breadcrumb/index.ts +1 -0
- package/src/button/button.constants.ts +44 -0
- package/src/button/button.tsx +79 -0
- package/src/button/button.types.ts +38 -0
- package/src/button/index.ts +3 -0
- package/src/button-group/button-group.constants.ts +26 -0
- package/src/button-group/button-group.tsx +65 -0
- package/src/button-group/index.ts +2 -0
- package/src/calendar/calendar.tsx +191 -0
- package/src/calendar/index.ts +1 -0
- package/src/card/card.tsx +81 -0
- package/src/card/index.ts +1 -0
- package/src/carousel/carousel.tsx +211 -0
- package/src/carousel/carousel.types.ts +23 -0
- package/src/carousel/index.ts +2 -0
- package/src/chart/chart.libs.ts +27 -0
- package/src/chart/chart.tsx +260 -0
- package/src/chart/chart.types.ts +38 -0
- package/src/chart/index.ts +3 -0
- package/src/checkbox/checkbox.tsx +144 -0
- package/src/checkbox/checkbox.types.ts +24 -0
- package/src/checkbox/index.ts +2 -0
- package/src/collapsible/collapsible.tsx +151 -0
- package/src/collapsible/index.ts +1 -0
- package/src/combobox/combobox.tsx +132 -0
- package/src/combobox/index.ts +1 -0
- package/src/command/command.tsx +192 -0
- package/src/command/command.types.ts +11 -0
- package/src/command/index.ts +2 -0
- package/src/context-menu/context-menu.tsx +178 -0
- package/src/context-menu/index.ts +1 -0
- package/src/dialog/dialog-responsive.tsx +137 -0
- package/src/dialog/dialog.tsx +97 -0
- package/src/dialog/index.ts +2 -0
- package/src/direction/direction.tsx +13 -0
- package/src/direction/index.ts +1 -0
- package/src/drawer/drawer.tsx +185 -0
- package/src/drawer/index.ts +1 -0
- package/src/dropdown-menu/dropdown-menu.tsx +181 -0
- package/src/dropdown-menu/index.ts +1 -0
- package/src/empty/empty.constants.ts +15 -0
- package/src/empty/empty.tsx +73 -0
- package/src/empty/index.ts +2 -0
- package/src/field/field.constants.ts +22 -0
- package/src/field/field.tsx +203 -0
- package/src/field/index.ts +2 -0
- package/src/hover-card/hover-card.tsx +79 -0
- package/src/hover-card/index.ts +1 -0
- package/src/input/index.ts +1 -0
- package/src/input/input.tsx +45 -0
- package/src/input-group/index.ts +1 -0
- package/src/input-group/input-group.tsx +170 -0
- package/src/input-otp/index.ts +1 -0
- package/src/input-otp/input-otp.tsx +66 -0
- package/src/item/index.ts +2 -0
- package/src/item/item.constants.ts +22 -0
- package/src/item/item.tsx +185 -0
- package/src/json-editor/index.ts +4 -0
- package/src/json-editor/json-editor.hooks.ts +21 -0
- package/src/json-editor/json-editor.libs.ts +34 -0
- package/src/json-editor/json-editor.tsx +425 -0
- package/src/json-editor/json-editor.types.ts +80 -0
- package/src/json-editor/json-editor.view.tsx +110 -0
- package/src/json-editor/json-text-area.tsx +7 -0
- package/src/kbd/index.ts +1 -0
- package/src/kbd/kbd.tsx +39 -0
- package/src/label/index.ts +1 -0
- package/src/label/label.tsx +28 -0
- package/src/menubar/index.ts +1 -0
- package/src/menubar/menubar.tsx +213 -0
- package/src/navigation-menu/index.ts +1 -0
- package/src/navigation-menu/navigation-menu.tsx +152 -0
- package/src/pagination/index.ts +2 -0
- package/src/pagination/pagination.tsx +191 -0
- package/src/pagination/pagination.types.ts +17 -0
- package/src/popover/index.ts +1 -0
- package/src/popover/popover.tsx +35 -0
- package/src/preview-panel/index.ts +3 -0
- package/src/preview-panel/preview-panel-dialog.tsx +99 -0
- package/src/preview-panel/preview-panel.tsx +389 -0
- package/src/preview-panel/preview-panel.types.ts +49 -0
- package/src/progress/index.ts +1 -0
- package/src/progress/progress.tsx +32 -0
- package/src/radio-group/index.ts +1 -0
- package/src/radio-group/radio-group.tsx +92 -0
- package/src/resizable/index.ts +1 -0
- package/src/resizable/resizable.tsx +52 -0
- package/src/scroll-area/index.ts +1 -0
- package/src/scroll-area/scroll-area.tsx +30 -0
- package/src/select/index.ts +1 -0
- package/src/select/select.tsx +138 -0
- package/src/separator/index.ts +1 -0
- package/src/separator/separator.tsx +28 -0
- package/src/sheet/index.ts +2 -0
- package/src/sheet/sheet.constants.tsx +20 -0
- package/src/sheet/sheet.tsx +92 -0
- package/src/sidebar/index.ts +4 -0
- package/src/sidebar/sidebar.constants.ts +30 -0
- package/src/sidebar/sidebar.hooks.ts +13 -0
- package/src/sidebar/sidebar.tsx +676 -0
- package/src/sidebar/sidebar.types.ts +28 -0
- package/src/skeleton/index.ts +1 -0
- package/src/skeleton/skeleton.tsx +22 -0
- package/src/slider/index.ts +1 -0
- package/src/slider/slider.tsx +57 -0
- package/src/sonner/index.ts +4 -0
- package/src/sonner/sonner.chunks.tsx +80 -0
- package/src/sonner/sonner.libs.ts +13 -0
- package/src/sonner/sonner.tsx +31 -0
- package/src/sonner/sonner.types.ts +9 -0
- package/src/switch/index.ts +1 -0
- package/src/switch/switch.tsx +63 -0
- package/src/table/index.ts +1 -0
- package/src/table/table.tsx +95 -0
- package/src/tabs/index.ts +1 -0
- package/src/tabs/tabs.tsx +151 -0
- package/src/textarea/index.ts +1 -0
- package/src/textarea/textarea.tsx +24 -0
- package/src/toggle/index.ts +2 -0
- package/src/toggle/toggle.constants.ts +22 -0
- package/src/toggle/toggle.tsx +24 -0
- package/src/toggle-group/index.ts +1 -0
- package/src/toggle-group/toggle-group.tsx +69 -0
- package/src/tooltip/index.ts +1 -0
- package/src/tooltip/tooltip.tsx +32 -0
- package/src/upload/index.ts +1 -0
- package/src/upload/upload.constants.tsx +19 -0
- package/src/upload/upload.libs.ts +97 -0
- package/src/upload/upload.tsx +340 -0
- package/src/upload/upload.types.ts +44 -0
- 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'
|