@eggspot/ui 0.0.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/eslint.config.js +4 -0
- package/package.json +66 -0
- package/postcss.config.mjs +1 -0
- package/src/components/Button.machine.tsx +50 -0
- package/src/components/Button.tsx +249 -0
- package/src/components/Button.variants.tsx +186 -0
- package/src/components/ButtonGroup.tsx +56 -0
- package/src/components/Calendar.tsx +275 -0
- package/src/components/Calendar.utils.tsx +22 -0
- package/src/components/Checkbox.tsx +199 -0
- package/src/components/ConfirmDialog.tsx +183 -0
- package/src/components/DashboardLayout/DashboardLayout.tsx +348 -0
- package/src/components/DashboardLayout/SidebarNav.tsx +509 -0
- package/src/components/DashboardLayout/index.ts +33 -0
- package/src/components/DataTable/DataTable.tsx +557 -0
- package/src/components/DataTable/DataTableColumnHeader.tsx +122 -0
- package/src/components/DataTable/DataTableDisplaySettings.tsx +265 -0
- package/src/components/DataTable/DataTableFloatingBar.tsx +44 -0
- package/src/components/DataTable/DataTablePagination.tsx +168 -0
- package/src/components/DataTable/DataTableStates.tsx +69 -0
- package/src/components/DataTable/DataTableToolbarContainer.tsx +47 -0
- package/src/components/DataTable/hooks/use-data-table-settings.ts +101 -0
- package/src/components/DataTable/index.ts +7 -0
- package/src/components/DataTable/types/data-table.ts +97 -0
- package/src/components/DatePicker.tsx +213 -0
- package/src/components/DatePicker.utils.tsx +38 -0
- package/src/components/Datefield.tsx +109 -0
- package/src/components/Datefield.utils.ts +10 -0
- package/src/components/Dialog.tsx +167 -0
- package/src/components/Field.tsx +49 -0
- package/src/components/Filter/Filter.store.tsx +122 -0
- package/src/components/Filter/Filter.tsx +11 -0
- package/src/components/Filter/Filter.types.ts +107 -0
- package/src/components/Filter/FilterBar.tsx +38 -0
- package/src/components/Filter/FilterBuilder.tsx +158 -0
- package/src/components/Filter/FilterField/DateModeRowValue.tsx +250 -0
- package/src/components/Filter/FilterField/FilterAsyncSelect.tsx +191 -0
- package/src/components/Filter/FilterField/FilterDateMode.tsx +241 -0
- package/src/components/Filter/FilterField/FilterDateRange.tsx +169 -0
- package/src/components/Filter/FilterField/FilterSelect.tsx +208 -0
- package/src/components/Filter/FilterField/FilterSingleDate.tsx +277 -0
- package/src/components/Filter/FilterField/OptionItem.tsx +112 -0
- package/src/components/Filter/FilterField/index.ts +6 -0
- package/src/components/Filter/FilterRow.tsx +527 -0
- package/src/components/Filter/index.ts +17 -0
- package/src/components/Form.tsx +195 -0
- package/src/components/Heading.tsx +41 -0
- package/src/components/Input.tsx +221 -0
- package/src/components/InputOTP.tsx +78 -0
- package/src/components/Label.tsx +65 -0
- package/src/components/Layout.tsx +129 -0
- package/src/components/ListBox.tsx +97 -0
- package/src/components/Menu.tsx +152 -0
- package/src/components/NativeSelect.tsx +77 -0
- package/src/components/NumberInput.tsx +114 -0
- package/src/components/Popover.tsx +44 -0
- package/src/components/Provider.tsx +22 -0
- package/src/components/RadioGroup.tsx +191 -0
- package/src/components/Resizable.tsx +71 -0
- package/src/components/ScrollArea.tsx +57 -0
- package/src/components/Select.tsx +626 -0
- package/src/components/Select.utils.tsx +64 -0
- package/src/components/Separator.tsx +25 -0
- package/src/components/Sheet.tsx +147 -0
- package/src/components/Sonner.tsx +96 -0
- package/src/components/Spinner.tsx +30 -0
- package/src/components/Switch.tsx +51 -0
- package/src/components/Text.tsx +35 -0
- package/src/components/Tooltip.tsx +58 -0
- package/src/consts/config.ts +2 -0
- package/src/hooks/.gitkeep +0 -0
- package/src/lib/utils.ts +10 -0
- package/tsconfig.json +11 -0
- package/tsconfig.lint.json +8 -0
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { Button } from "@eggspot/ui/components/Button"
|
|
5
|
+
import { SearchInput } from "@eggspot/ui/components/Input"
|
|
6
|
+
import { Tooltip, TooltipTrigger } from "@eggspot/ui/components/Tooltip"
|
|
7
|
+
import { cn } from "@eggspot/ui/lib/utils"
|
|
8
|
+
import { ChevronDown, ChevronLeft, ChevronRight, Search } from "lucide-react"
|
|
9
|
+
import { Focusable } from "react-aria-components"
|
|
10
|
+
import * as ReactDOM from "react-dom"
|
|
11
|
+
|
|
12
|
+
import { useDashboard } from "./DashboardLayout"
|
|
13
|
+
|
|
14
|
+
// Flyout panel for collapsed sidebar sections
|
|
15
|
+
interface SidebarFlyoutProps {
|
|
16
|
+
title: string
|
|
17
|
+
icon?: React.ReactNode
|
|
18
|
+
children: React.ReactNode
|
|
19
|
+
onClose: () => void
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function SidebarFlyout({ title, icon, children, onClose }: SidebarFlyoutProps) {
|
|
23
|
+
const { activePath, closeFlyout, sidebarRect } = useDashboard()
|
|
24
|
+
const flyoutRef = React.useRef<HTMLDivElement>(null)
|
|
25
|
+
const flyoutWidth = 220
|
|
26
|
+
|
|
27
|
+
const sidebarRight = sidebarRect?.right ?? 0
|
|
28
|
+
|
|
29
|
+
const prevActivePath = React.useRef(activePath)
|
|
30
|
+
React.useEffect(() => {
|
|
31
|
+
if (prevActivePath.current !== activePath) {
|
|
32
|
+
closeFlyout()
|
|
33
|
+
}
|
|
34
|
+
prevActivePath.current = activePath
|
|
35
|
+
}, [activePath, closeFlyout])
|
|
36
|
+
|
|
37
|
+
React.useEffect(() => {
|
|
38
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
39
|
+
if (e.key === "Escape") {
|
|
40
|
+
onClose()
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
document.addEventListener("keydown", handleKeyDown)
|
|
44
|
+
return () => document.removeEventListener("keydown", handleKeyDown)
|
|
45
|
+
}, [onClose])
|
|
46
|
+
|
|
47
|
+
React.useEffect(() => {
|
|
48
|
+
const originalOverflow = document.body.style.overflow
|
|
49
|
+
document.body.style.overflow = "hidden"
|
|
50
|
+
|
|
51
|
+
flyoutRef.current?.focus()
|
|
52
|
+
|
|
53
|
+
return () => {
|
|
54
|
+
document.body.style.overflow = originalOverflow
|
|
55
|
+
}
|
|
56
|
+
}, [])
|
|
57
|
+
|
|
58
|
+
const sidebarTop = sidebarRect?.top ?? 0
|
|
59
|
+
const sidebarHeight = sidebarRect?.height ?? 0
|
|
60
|
+
const maxAvailableHeight =
|
|
61
|
+
typeof window !== "undefined"
|
|
62
|
+
? window.innerHeight - sidebarTop
|
|
63
|
+
: sidebarHeight
|
|
64
|
+
const flyoutHeight = Math.min(sidebarHeight, maxAvailableHeight)
|
|
65
|
+
|
|
66
|
+
const flyoutContent = (
|
|
67
|
+
<>
|
|
68
|
+
<div
|
|
69
|
+
className="fixed inset-0 z-40"
|
|
70
|
+
style={{ left: sidebarRight + flyoutWidth }}
|
|
71
|
+
onClick={onClose}
|
|
72
|
+
aria-hidden="true"
|
|
73
|
+
/>
|
|
74
|
+
|
|
75
|
+
<div
|
|
76
|
+
ref={flyoutRef}
|
|
77
|
+
tabIndex={-1}
|
|
78
|
+
className={cn(
|
|
79
|
+
"fixed z-50 overflow-hidden",
|
|
80
|
+
"from-gray-2 to-gray-1 bg-linear-to-b",
|
|
81
|
+
"border-gray-6 border-r",
|
|
82
|
+
"rounded-r-2xl shadow-xl",
|
|
83
|
+
"animate-in slide-in-from-left-2 duration-200",
|
|
84
|
+
"focus:outline-none"
|
|
85
|
+
)}
|
|
86
|
+
style={{
|
|
87
|
+
left: sidebarRight + 1,
|
|
88
|
+
top: sidebarTop,
|
|
89
|
+
width: flyoutWidth,
|
|
90
|
+
height: flyoutHeight,
|
|
91
|
+
}}
|
|
92
|
+
>
|
|
93
|
+
<div className="flex h-full flex-col overflow-hidden p-4">
|
|
94
|
+
<div className="mb-4 flex shrink-0 items-center gap-3">
|
|
95
|
+
<span className="text-accent-11 [&>svg]:size-5">{icon}</span>
|
|
96
|
+
<h3 className="text-gray-12 text-sm font-semibold">{title}</h3>
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
<div className="flex-1 overflow-y-auto">
|
|
100
|
+
<div className="space-y-1.5">
|
|
101
|
+
{React.Children.map(children, (child) => {
|
|
102
|
+
if (React.isValidElement(child)) {
|
|
103
|
+
return React.cloneElement(
|
|
104
|
+
child as React.ReactElement<{
|
|
105
|
+
nested?: boolean
|
|
106
|
+
inFlyout?: boolean
|
|
107
|
+
}>,
|
|
108
|
+
{
|
|
109
|
+
nested: true,
|
|
110
|
+
inFlyout: true,
|
|
111
|
+
}
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
return child
|
|
115
|
+
})}
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
</>
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
if (typeof window === "undefined") return null
|
|
124
|
+
return ReactDOM.createPortal(flyoutContent, document.body)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface SidebarToggleProps {
|
|
128
|
+
className?: string
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function SidebarToggle({ className }: SidebarToggleProps) {
|
|
132
|
+
const { sidebarState, toggleSidebar } = useDashboard()
|
|
133
|
+
const isCollapsed = sidebarState === "collapsed"
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<Button
|
|
137
|
+
mode="icon"
|
|
138
|
+
aria-label={isCollapsed ? "Expand sidebar" : "Collapse sidebar"}
|
|
139
|
+
variant="ghost"
|
|
140
|
+
tooltip={isCollapsed ? "Expand sidebar" : "Collapse sidebar"}
|
|
141
|
+
tooltipPlacement="right"
|
|
142
|
+
onClick={toggleSidebar}
|
|
143
|
+
className={cn(
|
|
144
|
+
"rounded-2xl",
|
|
145
|
+
"text-gray-11 hover:bg-gray-4/50 hover:text-gray-12",
|
|
146
|
+
isCollapsed && "h-12 w-full",
|
|
147
|
+
className
|
|
148
|
+
)}
|
|
149
|
+
>
|
|
150
|
+
{isCollapsed ? (
|
|
151
|
+
<ChevronRight className="size-[18px]" />
|
|
152
|
+
) : (
|
|
153
|
+
<ChevronLeft className="size-[18px]" />
|
|
154
|
+
)}
|
|
155
|
+
</Button>
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Nav Section (collapsible group)
|
|
160
|
+
export interface SidebarNavSectionProps {
|
|
161
|
+
title: string
|
|
162
|
+
icon?: React.ReactNode
|
|
163
|
+
children: React.ReactNode
|
|
164
|
+
defaultOpen?: boolean
|
|
165
|
+
className?: string
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function SidebarNavSection({
|
|
169
|
+
title,
|
|
170
|
+
icon,
|
|
171
|
+
children,
|
|
172
|
+
defaultOpen = false,
|
|
173
|
+
className,
|
|
174
|
+
}: SidebarNavSectionProps) {
|
|
175
|
+
const {
|
|
176
|
+
sidebarState,
|
|
177
|
+
expandedGroups,
|
|
178
|
+
toggleGroup,
|
|
179
|
+
activePath,
|
|
180
|
+
flyoutGroup,
|
|
181
|
+
setFlyoutGroup,
|
|
182
|
+
} = useDashboard()
|
|
183
|
+
const isCollapsed = sidebarState === "collapsed"
|
|
184
|
+
|
|
185
|
+
const isOpen = expandedGroups.includes(title)
|
|
186
|
+
const isFlyoutOpen = flyoutGroup === title
|
|
187
|
+
|
|
188
|
+
const childrenArray = React.Children.toArray(children)
|
|
189
|
+
const hasActiveChild = childrenArray.some((child) => {
|
|
190
|
+
if (React.isValidElement<{ href?: string }>(child) && child.props.href) {
|
|
191
|
+
return activePath === child.props.href
|
|
192
|
+
}
|
|
193
|
+
return false
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
// Collapsed mode - show flyout on click
|
|
197
|
+
if (isCollapsed) {
|
|
198
|
+
return (
|
|
199
|
+
<>
|
|
200
|
+
<Button
|
|
201
|
+
mode="icon"
|
|
202
|
+
variant="ghost"
|
|
203
|
+
tooltip={isFlyoutOpen ? undefined : title}
|
|
204
|
+
tooltipPlacement="right"
|
|
205
|
+
onClick={() => setFlyoutGroup(isFlyoutOpen ? null : title)}
|
|
206
|
+
className={cn(
|
|
207
|
+
"relative flex h-12 w-full items-center justify-center rounded-2xl",
|
|
208
|
+
"text-gray-11 hover:bg-gray-4/50 hover:text-gray-12",
|
|
209
|
+
isFlyoutOpen && "bg-gray-4/50 text-gray-12",
|
|
210
|
+
hasActiveChild && "text-gray-12",
|
|
211
|
+
className
|
|
212
|
+
)}
|
|
213
|
+
>
|
|
214
|
+
{/* Active indicator */}
|
|
215
|
+
{hasActiveChild && (
|
|
216
|
+
<div className="bg-accent-9 absolute top-1/2 left-0 h-5 w-[3px] -translate-y-1/2 rounded-full" />
|
|
217
|
+
)}
|
|
218
|
+
<span
|
|
219
|
+
className={cn(
|
|
220
|
+
"[&>svg]:size-[18px]",
|
|
221
|
+
hasActiveChild && "text-accent-11"
|
|
222
|
+
)}
|
|
223
|
+
>
|
|
224
|
+
{icon}
|
|
225
|
+
</span>
|
|
226
|
+
</Button>
|
|
227
|
+
|
|
228
|
+
{/* Flyout panel */}
|
|
229
|
+
{isFlyoutOpen && (
|
|
230
|
+
<SidebarFlyout
|
|
231
|
+
title={title}
|
|
232
|
+
icon={icon}
|
|
233
|
+
onClose={() => setFlyoutGroup(null)}
|
|
234
|
+
>
|
|
235
|
+
{children}
|
|
236
|
+
</SidebarFlyout>
|
|
237
|
+
)}
|
|
238
|
+
</>
|
|
239
|
+
)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return (
|
|
243
|
+
<div className={cn("mb-1", className)}>
|
|
244
|
+
<button
|
|
245
|
+
onClick={() => toggleGroup(title)}
|
|
246
|
+
className={cn(
|
|
247
|
+
"group flex h-11 w-full min-w-0 items-center gap-3 rounded-2xl px-4",
|
|
248
|
+
"text-gray-11 text-sm transition-all duration-150",
|
|
249
|
+
"hover:bg-gray-4/50 hover:text-gray-12",
|
|
250
|
+
"focus-visible:ring-accent-9 focus:outline-none focus-visible:ring-2",
|
|
251
|
+
hasActiveChild && "text-gray-12"
|
|
252
|
+
)}
|
|
253
|
+
>
|
|
254
|
+
<span
|
|
255
|
+
className={cn(
|
|
256
|
+
"transition-colors",
|
|
257
|
+
hasActiveChild
|
|
258
|
+
? "text-accent-11"
|
|
259
|
+
: "text-gray-11 group-hover:text-gray-12"
|
|
260
|
+
)}
|
|
261
|
+
>
|
|
262
|
+
{icon}
|
|
263
|
+
</span>
|
|
264
|
+
<span className="min-w-0 flex-1 truncate text-left text-xs font-semibold tracking-wider uppercase">
|
|
265
|
+
{title}
|
|
266
|
+
</span>
|
|
267
|
+
<ChevronDown
|
|
268
|
+
className={cn(
|
|
269
|
+
"text-gray-11 group-hover:text-gray-12 size-4 transition-transform duration-200",
|
|
270
|
+
isOpen && "rotate-180"
|
|
271
|
+
)}
|
|
272
|
+
/>
|
|
273
|
+
</button>
|
|
274
|
+
|
|
275
|
+
{isOpen && (
|
|
276
|
+
<div className="animate-in slide-in-from-top-1 relative mt-2 ml-5 space-y-1 pl-4 duration-200">
|
|
277
|
+
{/* Guide line */}
|
|
278
|
+
<div className="bg-gray-6/50 absolute top-0 bottom-0 left-[4px] w-[2px] rounded-full" />
|
|
279
|
+
{children}
|
|
280
|
+
</div>
|
|
281
|
+
)}
|
|
282
|
+
</div>
|
|
283
|
+
)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Nav Group (static header, no collapse)
|
|
287
|
+
export interface SidebarNavGroupProps {
|
|
288
|
+
title: string
|
|
289
|
+
className?: string
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export function SidebarNavGroup({ title, className }: SidebarNavGroupProps) {
|
|
293
|
+
const { sidebarState } = useDashboard()
|
|
294
|
+
const isCollapsed = sidebarState === "collapsed"
|
|
295
|
+
|
|
296
|
+
if (isCollapsed) {
|
|
297
|
+
return <div className="bg-gray-6/30 mx-auto my-1.5 h-px w-8" />
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return (
|
|
301
|
+
<div
|
|
302
|
+
className={cn(
|
|
303
|
+
"text-gray-11 px-4 pt-4 pb-1 text-xs font-semibold tracking-wider uppercase",
|
|
304
|
+
className
|
|
305
|
+
)}
|
|
306
|
+
>
|
|
307
|
+
{title}
|
|
308
|
+
</div>
|
|
309
|
+
)
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Nav Item
|
|
313
|
+
export interface SidebarNavItemProps {
|
|
314
|
+
href?: string
|
|
315
|
+
icon?: React.ReactNode
|
|
316
|
+
children: React.ReactNode
|
|
317
|
+
isActive?: boolean
|
|
318
|
+
badge?: React.ReactNode
|
|
319
|
+
onClick?: () => void
|
|
320
|
+
className?: string
|
|
321
|
+
/** Whether this is a nested item inside a section */
|
|
322
|
+
nested?: boolean
|
|
323
|
+
/** Whether this item is rendered inside a flyout panel (forces expanded styling) */
|
|
324
|
+
inFlyout?: boolean
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export function SidebarNavItem({
|
|
328
|
+
href,
|
|
329
|
+
icon,
|
|
330
|
+
children,
|
|
331
|
+
isActive = false,
|
|
332
|
+
badge,
|
|
333
|
+
onClick,
|
|
334
|
+
className,
|
|
335
|
+
nested = false,
|
|
336
|
+
inFlyout = false,
|
|
337
|
+
}: SidebarNavItemProps) {
|
|
338
|
+
const { sidebarState, activePath, closeFlyout } = useDashboard()
|
|
339
|
+
const isCollapsed = sidebarState === "collapsed" && !inFlyout
|
|
340
|
+
|
|
341
|
+
const active = isActive || (href ? activePath === href : false)
|
|
342
|
+
|
|
343
|
+
const handleClick = React.useCallback(() => {
|
|
344
|
+
onClick?.()
|
|
345
|
+
if (inFlyout || href) {
|
|
346
|
+
closeFlyout()
|
|
347
|
+
}
|
|
348
|
+
}, [onClick, inFlyout, href, closeFlyout])
|
|
349
|
+
|
|
350
|
+
const baseStyles = cn(
|
|
351
|
+
"group relative flex min-w-0 items-center gap-3 rounded-2xl transition-all duration-150",
|
|
352
|
+
"focus-visible:ring-accent-9 focus:outline-none focus-visible:ring-2",
|
|
353
|
+
active
|
|
354
|
+
? "bg-accent-4/50 text-accent-11 font-medium"
|
|
355
|
+
: "text-gray-11/70 hover:bg-gray-4/50 hover:text-gray-12",
|
|
356
|
+
isCollapsed ? "h-12 w-full justify-center" : "h-11 px-4",
|
|
357
|
+
className
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
const content = (
|
|
361
|
+
<>
|
|
362
|
+
{/* Active indicator */}
|
|
363
|
+
{active && (
|
|
364
|
+
<div
|
|
365
|
+
className={cn(
|
|
366
|
+
"bg-accent-9 absolute rounded-full transition-all",
|
|
367
|
+
isCollapsed
|
|
368
|
+
? "bottom-1 left-1/2 h-1.5 w-1.5 -translate-x-1/2"
|
|
369
|
+
: "top-1/2 left-0 h-5 w-[3px] -translate-y-1/2"
|
|
370
|
+
)}
|
|
371
|
+
/>
|
|
372
|
+
)}
|
|
373
|
+
|
|
374
|
+
{icon && (
|
|
375
|
+
<span
|
|
376
|
+
className={cn(
|
|
377
|
+
"shrink-0 transition-colors duration-150",
|
|
378
|
+
isCollapsed || nested ? "[&>svg]:size-[18px]" : "[&>svg]:size-4",
|
|
379
|
+
active ? "text-accent-11" : "group-hover:text-gray-12"
|
|
380
|
+
)}
|
|
381
|
+
>
|
|
382
|
+
{icon}
|
|
383
|
+
</span>
|
|
384
|
+
)}
|
|
385
|
+
|
|
386
|
+
{!isCollapsed && (
|
|
387
|
+
<>
|
|
388
|
+
<span
|
|
389
|
+
className={cn(
|
|
390
|
+
"flex-1 truncate text-left text-xs uppercase",
|
|
391
|
+
nested && "text-xs capitalize"
|
|
392
|
+
)}
|
|
393
|
+
>
|
|
394
|
+
{children}
|
|
395
|
+
</span>
|
|
396
|
+
{badge}
|
|
397
|
+
</>
|
|
398
|
+
)}
|
|
399
|
+
</>
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
// Collapsed mode with tooltip
|
|
403
|
+
if (isCollapsed) {
|
|
404
|
+
const tooltipText = typeof children === "string" ? children : undefined
|
|
405
|
+
|
|
406
|
+
if (href) {
|
|
407
|
+
return (
|
|
408
|
+
<TooltipTrigger delay={0}>
|
|
409
|
+
<Focusable>
|
|
410
|
+
<a href={href} onClick={handleClick} className={baseStyles}>
|
|
411
|
+
{content}
|
|
412
|
+
</a>
|
|
413
|
+
</Focusable>
|
|
414
|
+
<Tooltip placement="right">{children}</Tooltip>
|
|
415
|
+
</TooltipTrigger>
|
|
416
|
+
)
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return (
|
|
420
|
+
<Button
|
|
421
|
+
variant="ghost"
|
|
422
|
+
tooltipPlacement="right"
|
|
423
|
+
tooltip={tooltipText}
|
|
424
|
+
onClick={handleClick}
|
|
425
|
+
className={cn(baseStyles, "w-full")}
|
|
426
|
+
>
|
|
427
|
+
{content}
|
|
428
|
+
</Button>
|
|
429
|
+
)
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Normal mode
|
|
433
|
+
if (href) {
|
|
434
|
+
return (
|
|
435
|
+
<a href={href} onClick={handleClick} className={baseStyles}>
|
|
436
|
+
{content}
|
|
437
|
+
</a>
|
|
438
|
+
)
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
return (
|
|
442
|
+
<button onClick={handleClick} className={cn(baseStyles, "w-full")}>
|
|
443
|
+
{content}
|
|
444
|
+
</button>
|
|
445
|
+
)
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Sidebar Divider
|
|
449
|
+
export function SidebarSeparator({ className }: { className?: string }) {
|
|
450
|
+
const { sidebarState } = useDashboard()
|
|
451
|
+
const isCollapsed = sidebarState === "collapsed"
|
|
452
|
+
|
|
453
|
+
return (
|
|
454
|
+
<div
|
|
455
|
+
className={cn(
|
|
456
|
+
"bg-gray-6/50",
|
|
457
|
+
isCollapsed ? "mx-auto my-1.5 h-px w-8" : "my-2 h-px",
|
|
458
|
+
className
|
|
459
|
+
)}
|
|
460
|
+
/>
|
|
461
|
+
)
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Search box for sidebar
|
|
465
|
+
export interface SidebarSearchProps {
|
|
466
|
+
placeholder?: string
|
|
467
|
+
shortcut?: string
|
|
468
|
+
onSearch?: (value: string) => void
|
|
469
|
+
className?: string
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
export function SidebarSearch({
|
|
473
|
+
placeholder = "Search",
|
|
474
|
+
shortcut = "⌘K",
|
|
475
|
+
onSearch,
|
|
476
|
+
className,
|
|
477
|
+
}: SidebarSearchProps) {
|
|
478
|
+
const { sidebarState } = useDashboard()
|
|
479
|
+
const isCollapsed = sidebarState === "collapsed"
|
|
480
|
+
|
|
481
|
+
if (isCollapsed) {
|
|
482
|
+
return (
|
|
483
|
+
<div className="px-3 py-2">
|
|
484
|
+
<Button
|
|
485
|
+
mode="icon"
|
|
486
|
+
variant="ghost"
|
|
487
|
+
tooltip={`Search (${shortcut})`}
|
|
488
|
+
className={cn(
|
|
489
|
+
"flex h-12 w-full items-center justify-center rounded-2xl",
|
|
490
|
+
"text-gray-11 hover:bg-gray-4/50 hover:text-gray-12",
|
|
491
|
+
className
|
|
492
|
+
)}
|
|
493
|
+
>
|
|
494
|
+
<Search className="size-5" />
|
|
495
|
+
</Button>
|
|
496
|
+
</div>
|
|
497
|
+
)
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
return (
|
|
501
|
+
<div className={cn("px-3 py-2", className)}>
|
|
502
|
+
<SearchInput
|
|
503
|
+
placeholder={placeholder}
|
|
504
|
+
onChange={(value) => onSearch?.(value)}
|
|
505
|
+
className="h-12 w-full rounded-2xl"
|
|
506
|
+
/>
|
|
507
|
+
</div>
|
|
508
|
+
)
|
|
509
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export {
|
|
2
|
+
DashboardContent,
|
|
3
|
+
DashboardHeader,
|
|
4
|
+
DashboardLayout,
|
|
5
|
+
DashboardMain,
|
|
6
|
+
Sidebar,
|
|
7
|
+
SidebarFooter,
|
|
8
|
+
SidebarHeader,
|
|
9
|
+
SidebarNav,
|
|
10
|
+
useDashboard,
|
|
11
|
+
type DashboardContentProps,
|
|
12
|
+
type DashboardHeaderProps,
|
|
13
|
+
type DashboardLayoutProps,
|
|
14
|
+
type DashboardMainProps,
|
|
15
|
+
type SidebarFooterProps,
|
|
16
|
+
type SidebarHeaderProps,
|
|
17
|
+
type SidebarNavProps,
|
|
18
|
+
type SidebarProps,
|
|
19
|
+
} from "./DashboardLayout"
|
|
20
|
+
|
|
21
|
+
export {
|
|
22
|
+
SidebarNavGroup,
|
|
23
|
+
SidebarNavItem,
|
|
24
|
+
SidebarNavSection,
|
|
25
|
+
SidebarSearch,
|
|
26
|
+
SidebarSeparator,
|
|
27
|
+
SidebarToggle,
|
|
28
|
+
type SidebarNavGroupProps,
|
|
29
|
+
type SidebarNavItemProps,
|
|
30
|
+
type SidebarNavSectionProps,
|
|
31
|
+
type SidebarSearchProps,
|
|
32
|
+
type SidebarToggleProps,
|
|
33
|
+
} from "./SidebarNav"
|