@hilum/ui 0.1.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/LICENSE +21 -0
- package/README.md +42 -0
- package/dist/icons.d.ts +1 -0
- package/dist/icons.js +3 -0
- package/dist/icons.js.map +1 -0
- package/dist/index.d.ts +992 -0
- package/dist/index.js +4433 -0
- package/dist/index.js.map +1 -0
- package/dist/tokens.css +265 -0
- package/dist/tokens.d.ts +189 -0
- package/dist/tokens.js +193 -0
- package/dist/tokens.js.map +1 -0
- package/package.json +80 -0
- package/src/components/__tests__/data.test.tsx +180 -0
- package/src/components/__tests__/disclosure.test.tsx +198 -0
- package/src/components/__tests__/display.test.tsx +262 -0
- package/src/components/__tests__/feedback.test.tsx +138 -0
- package/src/components/__tests__/form.test.tsx +297 -0
- package/src/components/__tests__/layout.test.tsx +388 -0
- package/src/components/__tests__/overlay.test.tsx +90 -0
- package/src/components/__tests__/primitives.test.tsx +248 -0
- package/src/components/accordion.tsx +68 -0
- package/src/components/action-panel.tsx +112 -0
- package/src/components/activity-feed.tsx +69 -0
- package/src/components/alert-dialog.tsx +132 -0
- package/src/components/alert.tsx +44 -0
- package/src/components/aspect-ratio.tsx +19 -0
- package/src/components/avatar-stack.tsx +70 -0
- package/src/components/avatar.tsx +103 -0
- package/src/components/badge.tsx +33 -0
- package/src/components/breadcrumb.tsx +76 -0
- package/src/components/button-group.tsx +45 -0
- package/src/components/button.tsx +60 -0
- package/src/components/calendar.tsx +61 -0
- package/src/components/card-heading.tsx +60 -0
- package/src/components/card.tsx +120 -0
- package/src/components/carousel.tsx +222 -0
- package/src/components/chart.tsx +77 -0
- package/src/components/checkbox.tsx +35 -0
- package/src/components/collapsible.tsx +9 -0
- package/src/components/color-input.tsx +90 -0
- package/src/components/color-picker.tsx +111 -0
- package/src/components/combobox.tsx +207 -0
- package/src/components/command-palette.tsx +137 -0
- package/src/components/command.tsx +248 -0
- package/src/components/context-menu.tsx +201 -0
- package/src/components/data-table.tsx +151 -0
- package/src/components/date-picker.tsx +56 -0
- package/src/components/description-list.tsx +70 -0
- package/src/components/dialog.tsx +97 -0
- package/src/components/drawer.tsx +90 -0
- package/src/components/dropdown-menu.tsx +202 -0
- package/src/components/empty-state.tsx +43 -0
- package/src/components/field.tsx +45 -0
- package/src/components/grid-list.tsx +159 -0
- package/src/components/hover-card.tsx +34 -0
- package/src/components/input-group.tsx +134 -0
- package/src/components/input-number.tsx +142 -0
- package/src/components/input-otp.tsx +68 -0
- package/src/components/input.tsx +23 -0
- package/src/components/kbd.tsx +18 -0
- package/src/components/label.tsx +23 -0
- package/src/components/media-object.tsx +49 -0
- package/src/components/menubar.tsx +235 -0
- package/src/components/native-select.tsx +44 -0
- package/src/components/navigation-menu.tsx +158 -0
- package/src/components/notification.tsx +109 -0
- package/src/components/page-heading.tsx +125 -0
- package/src/components/pagination.tsx +89 -0
- package/src/components/popover.tsx +35 -0
- package/src/components/progress.tsx +23 -0
- package/src/components/property-row.tsx +56 -0
- package/src/components/radio-card.tsx +105 -0
- package/src/components/radio-group.tsx +38 -0
- package/src/components/resizable.tsx +74 -0
- package/src/components/scroll-area.tsx +42 -0
- package/src/components/section-heading.tsx +64 -0
- package/src/components/select.tsx +130 -0
- package/src/components/separator.tsx +25 -0
- package/src/components/sheet.tsx +117 -0
- package/src/components/sidebar.tsx +636 -0
- package/src/components/skeleton.tsx +14 -0
- package/src/components/slider.tsx +24 -0
- package/src/components/sonner.tsx +34 -0
- package/src/components/spinner.tsx +37 -0
- package/src/components/stacked-list.tsx +64 -0
- package/src/components/stat-card.tsx +54 -0
- package/src/components/steps.tsx +179 -0
- package/src/components/switch.tsx +33 -0
- package/src/components/table.tsx +101 -0
- package/src/components/tabs.tsx +55 -0
- package/src/components/textarea.tsx +21 -0
- package/src/components/toggle-group.tsx +49 -0
- package/src/components/toggle.tsx +66 -0
- package/src/components/tooltip.tsx +43 -0
- package/src/icons.ts +158 -0
- package/src/index.ts +84 -0
- package/src/lib/utils.ts +6 -0
- package/src/tokens/tokens.ts +194 -0
|
@@ -0,0 +1,636 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { PanelLeft } from "lucide-react";
|
|
5
|
+
import { Slot } from "radix-ui";
|
|
6
|
+
import { cn } from "../lib/utils";
|
|
7
|
+
import { Button } from "./button";
|
|
8
|
+
import { Separator } from "./separator";
|
|
9
|
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./tooltip";
|
|
10
|
+
|
|
11
|
+
/* ------------------------------------------------------------------ */
|
|
12
|
+
/* Constants */
|
|
13
|
+
/* ------------------------------------------------------------------ */
|
|
14
|
+
|
|
15
|
+
const SIDEBAR_COOKIE_NAME = "sidebar:state";
|
|
16
|
+
const SIDEBAR_WIDTH = "16rem";
|
|
17
|
+
const SIDEBAR_WIDTH_ICON = "3.5rem";
|
|
18
|
+
|
|
19
|
+
/* ------------------------------------------------------------------ */
|
|
20
|
+
/* Context */
|
|
21
|
+
/* ------------------------------------------------------------------ */
|
|
22
|
+
|
|
23
|
+
interface SidebarContextValue {
|
|
24
|
+
state: "expanded" | "collapsed";
|
|
25
|
+
open: boolean;
|
|
26
|
+
setOpen: (open: boolean) => void;
|
|
27
|
+
toggleSidebar: () => void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const SidebarContext = React.createContext<SidebarContextValue | null>(null);
|
|
31
|
+
|
|
32
|
+
function useSidebar() {
|
|
33
|
+
const ctx = React.useContext(SidebarContext);
|
|
34
|
+
if (!ctx) throw new Error("useSidebar must be used inside SidebarProvider");
|
|
35
|
+
return ctx;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* ------------------------------------------------------------------ */
|
|
39
|
+
/* SidebarProvider */
|
|
40
|
+
/* ------------------------------------------------------------------ */
|
|
41
|
+
|
|
42
|
+
interface SidebarProviderProps extends React.ComponentProps<"div"> {
|
|
43
|
+
defaultOpen?: boolean;
|
|
44
|
+
open?: boolean;
|
|
45
|
+
onOpenChange?: (open: boolean) => void;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function SidebarProvider({
|
|
49
|
+
defaultOpen = true,
|
|
50
|
+
open: openProp,
|
|
51
|
+
onOpenChange,
|
|
52
|
+
className,
|
|
53
|
+
style,
|
|
54
|
+
children,
|
|
55
|
+
...props
|
|
56
|
+
}: SidebarProviderProps) {
|
|
57
|
+
const [_open, _setOpen] = React.useState(defaultOpen);
|
|
58
|
+
|
|
59
|
+
const open = openProp !== undefined ? openProp : _open;
|
|
60
|
+
|
|
61
|
+
const setOpen = React.useCallback(
|
|
62
|
+
(value: boolean) => {
|
|
63
|
+
if (onOpenChange) {
|
|
64
|
+
onOpenChange(value);
|
|
65
|
+
} else {
|
|
66
|
+
_setOpen(value);
|
|
67
|
+
}
|
|
68
|
+
// Persist to cookie
|
|
69
|
+
document.cookie = `${SIDEBAR_COOKIE_NAME}=${value}; path=/; max-age=${60 * 60 * 24 * 7}`;
|
|
70
|
+
},
|
|
71
|
+
[onOpenChange],
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const toggleSidebar = React.useCallback(() => {
|
|
75
|
+
setOpen(!open);
|
|
76
|
+
}, [open, setOpen]);
|
|
77
|
+
|
|
78
|
+
const state = open ? "expanded" : "collapsed";
|
|
79
|
+
|
|
80
|
+
const contextValue = React.useMemo<SidebarContextValue>(
|
|
81
|
+
() => ({ state, open, setOpen, toggleSidebar }),
|
|
82
|
+
[state, open, setOpen, toggleSidebar],
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<SidebarContext.Provider value={contextValue}>
|
|
87
|
+
<TooltipProvider delayDuration={0}>
|
|
88
|
+
<div
|
|
89
|
+
data-sidebar="provider"
|
|
90
|
+
data-state={state}
|
|
91
|
+
className={cn("group/sidebar-wrapper flex min-h-svh w-full", className)}
|
|
92
|
+
style={
|
|
93
|
+
{
|
|
94
|
+
"--sidebar-width": SIDEBAR_WIDTH,
|
|
95
|
+
"--sidebar-width-icon": SIDEBAR_WIDTH_ICON,
|
|
96
|
+
...style,
|
|
97
|
+
} as React.CSSProperties
|
|
98
|
+
}
|
|
99
|
+
{...props}
|
|
100
|
+
>
|
|
101
|
+
{children}
|
|
102
|
+
</div>
|
|
103
|
+
</TooltipProvider>
|
|
104
|
+
</SidebarContext.Provider>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/* ------------------------------------------------------------------ */
|
|
109
|
+
/* Sidebar */
|
|
110
|
+
/* ------------------------------------------------------------------ */
|
|
111
|
+
|
|
112
|
+
interface SidebarProps extends React.ComponentProps<"aside"> {
|
|
113
|
+
side?: "left" | "right";
|
|
114
|
+
variant?: "sidebar" | "floating" | "inset";
|
|
115
|
+
collapsible?: "offcanvas" | "icon" | "none";
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function Sidebar({
|
|
119
|
+
side = "left",
|
|
120
|
+
variant = "sidebar",
|
|
121
|
+
collapsible = "icon",
|
|
122
|
+
className,
|
|
123
|
+
children,
|
|
124
|
+
...props
|
|
125
|
+
}: SidebarProps) {
|
|
126
|
+
const { state } = useSidebar();
|
|
127
|
+
|
|
128
|
+
if (collapsible === "none") {
|
|
129
|
+
return (
|
|
130
|
+
<aside
|
|
131
|
+
data-side={side}
|
|
132
|
+
data-variant={variant}
|
|
133
|
+
data-collapsible="none"
|
|
134
|
+
className={cn(
|
|
135
|
+
"flex flex-col h-svh bg-white border-r border-taupe-100 w-[--sidebar-width]",
|
|
136
|
+
side === "right" && "border-r-0 border-l",
|
|
137
|
+
className,
|
|
138
|
+
)}
|
|
139
|
+
{...props}
|
|
140
|
+
>
|
|
141
|
+
{children}
|
|
142
|
+
</aside>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (collapsible === "offcanvas") {
|
|
147
|
+
return (
|
|
148
|
+
<aside
|
|
149
|
+
data-state={state}
|
|
150
|
+
data-side={side}
|
|
151
|
+
data-variant={variant}
|
|
152
|
+
data-collapsible="offcanvas"
|
|
153
|
+
className={cn(
|
|
154
|
+
"group peer flex flex-col h-svh bg-white border-r border-taupe-100",
|
|
155
|
+
"transition-[width,transform] duration-200 ease-linear",
|
|
156
|
+
"data-[state=expanded]:w-[--sidebar-width] data-[state=collapsed]:w-0 data-[state=collapsed]:-translate-x-full",
|
|
157
|
+
side === "right" && "border-r-0 border-l data-[state=collapsed]:translate-x-full",
|
|
158
|
+
className,
|
|
159
|
+
)}
|
|
160
|
+
{...props}
|
|
161
|
+
>
|
|
162
|
+
{children}
|
|
163
|
+
</aside>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// collapsible === "icon" (default)
|
|
168
|
+
return (
|
|
169
|
+
<aside
|
|
170
|
+
data-state={state}
|
|
171
|
+
data-side={side}
|
|
172
|
+
data-variant={variant}
|
|
173
|
+
data-collapsible="icon"
|
|
174
|
+
className={cn(
|
|
175
|
+
"group peer hidden md:flex flex-col h-svh bg-white border-r border-taupe-100",
|
|
176
|
+
"transition-[width] duration-200 ease-linear overflow-hidden",
|
|
177
|
+
"data-[state=expanded]:w-[--sidebar-width] data-[state=collapsed]:w-[--sidebar-width-icon]",
|
|
178
|
+
side === "right" && "border-r-0 border-l",
|
|
179
|
+
variant === "floating" && "m-2 h-[calc(100svh-1rem)] rounded-xl border shadow-natural",
|
|
180
|
+
variant === "inset" && "border-r-0",
|
|
181
|
+
className,
|
|
182
|
+
)}
|
|
183
|
+
{...props}
|
|
184
|
+
>
|
|
185
|
+
{children}
|
|
186
|
+
</aside>
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/* ------------------------------------------------------------------ */
|
|
191
|
+
/* SidebarTrigger */
|
|
192
|
+
/* ------------------------------------------------------------------ */
|
|
193
|
+
|
|
194
|
+
function SidebarTrigger({ className, onClick, ...props }: React.ComponentProps<typeof Button>) {
|
|
195
|
+
const { toggleSidebar } = useSidebar();
|
|
196
|
+
|
|
197
|
+
return (
|
|
198
|
+
<Button
|
|
199
|
+
data-sidebar="trigger"
|
|
200
|
+
variant="ghost"
|
|
201
|
+
size="sm"
|
|
202
|
+
className={cn("h-7 w-7", className)}
|
|
203
|
+
onClick={(e) => {
|
|
204
|
+
onClick?.(e);
|
|
205
|
+
toggleSidebar();
|
|
206
|
+
}}
|
|
207
|
+
{...props}
|
|
208
|
+
>
|
|
209
|
+
<PanelLeft size={16} />
|
|
210
|
+
<span className="sr-only">Toggle sidebar</span>
|
|
211
|
+
</Button>
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/* ------------------------------------------------------------------ */
|
|
216
|
+
/* SidebarRail */
|
|
217
|
+
/* ------------------------------------------------------------------ */
|
|
218
|
+
|
|
219
|
+
function SidebarRail({ className, ...props }: React.ComponentProps<"button">) {
|
|
220
|
+
const { toggleSidebar } = useSidebar();
|
|
221
|
+
|
|
222
|
+
return (
|
|
223
|
+
<button
|
|
224
|
+
data-sidebar="rail"
|
|
225
|
+
aria-label="Toggle sidebar"
|
|
226
|
+
tabIndex={-1}
|
|
227
|
+
onClick={toggleSidebar}
|
|
228
|
+
title="Toggle sidebar"
|
|
229
|
+
className={cn(
|
|
230
|
+
"absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 cursor-col-resize transition-all ease-linear",
|
|
231
|
+
"after:absolute after:inset-y-0 after:left-1/2 after:w-[2px]",
|
|
232
|
+
"hover:after:bg-brand-primary/30",
|
|
233
|
+
"group-data-[side=left]:-right-4 group-data-[side=right]:left-0",
|
|
234
|
+
"sm:flex",
|
|
235
|
+
className,
|
|
236
|
+
)}
|
|
237
|
+
{...props}
|
|
238
|
+
/>
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/* ------------------------------------------------------------------ */
|
|
243
|
+
/* SidebarInset */
|
|
244
|
+
/* ------------------------------------------------------------------ */
|
|
245
|
+
|
|
246
|
+
function SidebarInset({ className, ...props }: React.ComponentProps<"main">) {
|
|
247
|
+
return (
|
|
248
|
+
<main
|
|
249
|
+
data-sidebar="inset"
|
|
250
|
+
className={cn("relative flex min-h-svh flex-1 flex-col bg-white overflow-hidden", className)}
|
|
251
|
+
{...props}
|
|
252
|
+
/>
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/* ------------------------------------------------------------------ */
|
|
257
|
+
/* Structure components */
|
|
258
|
+
/* ------------------------------------------------------------------ */
|
|
259
|
+
|
|
260
|
+
function SidebarHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
261
|
+
return (
|
|
262
|
+
<div
|
|
263
|
+
data-sidebar="header"
|
|
264
|
+
className={cn("flex flex-col gap-2 p-3 border-b border-taupe-100", className)}
|
|
265
|
+
{...props}
|
|
266
|
+
/>
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function SidebarContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
271
|
+
return (
|
|
272
|
+
<div
|
|
273
|
+
data-sidebar="content"
|
|
274
|
+
className={cn("flex min-h-0 flex-1 flex-col gap-2 overflow-auto p-3", className)}
|
|
275
|
+
{...props}
|
|
276
|
+
/>
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function SidebarFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
281
|
+
return (
|
|
282
|
+
<div
|
|
283
|
+
data-sidebar="footer"
|
|
284
|
+
className={cn("flex flex-col gap-2 p-3 border-t border-taupe-100", className)}
|
|
285
|
+
{...props}
|
|
286
|
+
/>
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function SidebarSeparator({ className, ...props }: React.ComponentProps<typeof Separator>) {
|
|
291
|
+
return (
|
|
292
|
+
<Separator
|
|
293
|
+
data-sidebar="separator"
|
|
294
|
+
className={cn("mx-2 my-1 bg-taupe-100", className)}
|
|
295
|
+
{...props}
|
|
296
|
+
/>
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/* ------------------------------------------------------------------ */
|
|
301
|
+
/* Group components */
|
|
302
|
+
/* ------------------------------------------------------------------ */
|
|
303
|
+
|
|
304
|
+
function SidebarGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
305
|
+
return (
|
|
306
|
+
<div
|
|
307
|
+
data-sidebar="group"
|
|
308
|
+
className={cn("relative flex w-full min-w-0 flex-col p-2", className)}
|
|
309
|
+
{...props}
|
|
310
|
+
/>
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function SidebarGroupLabel({
|
|
315
|
+
className,
|
|
316
|
+
asChild = false,
|
|
317
|
+
...props
|
|
318
|
+
}: React.ComponentProps<"div"> & { asChild?: boolean }) {
|
|
319
|
+
const Comp = asChild ? Slot.Root : "div";
|
|
320
|
+
|
|
321
|
+
return (
|
|
322
|
+
<Comp
|
|
323
|
+
data-sidebar="group-label"
|
|
324
|
+
className={cn(
|
|
325
|
+
"flex h-8 shrink-0 items-center rounded-md px-2 label text-taupe-400",
|
|
326
|
+
"outline-none ring-taupe-900 transition-[margin,opacity] duration-200 ease-linear",
|
|
327
|
+
"group-data-[state=collapsed]/sidebar-wrapper:opacity-0",
|
|
328
|
+
"group-data-[state=collapsed]/sidebar-wrapper:h-0",
|
|
329
|
+
"group-data-[state=collapsed]/sidebar-wrapper:m-0",
|
|
330
|
+
"group-data-[state=collapsed]/sidebar-wrapper:p-0",
|
|
331
|
+
"group-data-[state=collapsed]/sidebar-wrapper:overflow-hidden",
|
|
332
|
+
className,
|
|
333
|
+
)}
|
|
334
|
+
{...props}
|
|
335
|
+
/>
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function SidebarGroupContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
340
|
+
return (
|
|
341
|
+
<div data-sidebar="group-content" className={cn("w-full text-sm", className)} {...props} />
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function SidebarGroupAction({
|
|
346
|
+
className,
|
|
347
|
+
asChild = false,
|
|
348
|
+
...props
|
|
349
|
+
}: React.ComponentProps<"button"> & { asChild?: boolean }) {
|
|
350
|
+
const Comp = asChild ? Slot.Root : "button";
|
|
351
|
+
|
|
352
|
+
return (
|
|
353
|
+
<Comp
|
|
354
|
+
data-sidebar="group-action"
|
|
355
|
+
className={cn(
|
|
356
|
+
"absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center",
|
|
357
|
+
"rounded-md text-taupe-400 hover:bg-taupe-100 hover:text-taupe-900",
|
|
358
|
+
"outline-none focus-visible:ring-2 focus-visible:ring-brand-primary/30",
|
|
359
|
+
className,
|
|
360
|
+
)}
|
|
361
|
+
{...props}
|
|
362
|
+
/>
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/* ------------------------------------------------------------------ */
|
|
367
|
+
/* Menu components */
|
|
368
|
+
/* ------------------------------------------------------------------ */
|
|
369
|
+
|
|
370
|
+
function SidebarMenu({ className, ...props }: React.ComponentProps<"ul">) {
|
|
371
|
+
return (
|
|
372
|
+
<ul
|
|
373
|
+
data-sidebar="menu"
|
|
374
|
+
className={cn("flex w-full min-w-0 flex-col gap-1", className)}
|
|
375
|
+
{...props}
|
|
376
|
+
/>
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) {
|
|
381
|
+
return (
|
|
382
|
+
<li data-sidebar="menu-item" className={cn("group/menu-item relative", className)} {...props} />
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/* ------------------------------------------------------------------ */
|
|
387
|
+
/* SidebarMenuButton */
|
|
388
|
+
/* ------------------------------------------------------------------ */
|
|
389
|
+
|
|
390
|
+
interface SidebarMenuButtonProps extends React.ComponentProps<"button"> {
|
|
391
|
+
asChild?: boolean;
|
|
392
|
+
isActive?: boolean;
|
|
393
|
+
tooltip?: string;
|
|
394
|
+
size?: "default" | "sm" | "lg";
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function SidebarMenuButton({
|
|
398
|
+
asChild = false,
|
|
399
|
+
isActive = false,
|
|
400
|
+
tooltip,
|
|
401
|
+
size = "default",
|
|
402
|
+
className,
|
|
403
|
+
children,
|
|
404
|
+
...props
|
|
405
|
+
}: SidebarMenuButtonProps) {
|
|
406
|
+
const Comp = asChild ? (Slot.Root as React.ElementType) : "button";
|
|
407
|
+
|
|
408
|
+
const button = (
|
|
409
|
+
<Comp
|
|
410
|
+
data-sidebar="menu-button"
|
|
411
|
+
data-active={isActive}
|
|
412
|
+
data-size={size}
|
|
413
|
+
className={cn(
|
|
414
|
+
// Base
|
|
415
|
+
"peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 body text-left",
|
|
416
|
+
"outline-none ring-taupe-900",
|
|
417
|
+
"transition-[width,height,padding] duration-150",
|
|
418
|
+
"hover:bg-taupe-100 hover:text-taupe-900",
|
|
419
|
+
"focus-visible:ring-2 focus-visible:ring-brand-primary/30",
|
|
420
|
+
"active:bg-taupe-100",
|
|
421
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
422
|
+
"[&>svg]:size-4 [&>svg]:shrink-0",
|
|
423
|
+
// Size variants
|
|
424
|
+
size === "sm" && "h-7 text-xs",
|
|
425
|
+
size === "lg" && "h-10",
|
|
426
|
+
// Active state
|
|
427
|
+
isActive && [
|
|
428
|
+
"bg-brand-primary/10 text-brand-primary",
|
|
429
|
+
"hover:bg-brand-primary/15 hover:text-brand-primary",
|
|
430
|
+
],
|
|
431
|
+
// Collapsed state
|
|
432
|
+
"group-data-[state=collapsed]/sidebar-wrapper:justify-center",
|
|
433
|
+
"group-data-[state=collapsed]/sidebar-wrapper:size-9",
|
|
434
|
+
"group-data-[state=collapsed]/sidebar-wrapper:p-0",
|
|
435
|
+
className,
|
|
436
|
+
)}
|
|
437
|
+
{...props}
|
|
438
|
+
>
|
|
439
|
+
{children}
|
|
440
|
+
</Comp>
|
|
441
|
+
);
|
|
442
|
+
|
|
443
|
+
if (!tooltip) {
|
|
444
|
+
return button;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
return (
|
|
448
|
+
<Tooltip>
|
|
449
|
+
<TooltipTrigger asChild>{button}</TooltipTrigger>
|
|
450
|
+
<TooltipContent
|
|
451
|
+
side="right"
|
|
452
|
+
className={cn(
|
|
453
|
+
// Only visible when collapsed
|
|
454
|
+
"hidden group-data-[state=collapsed]/sidebar-wrapper:block",
|
|
455
|
+
)}
|
|
456
|
+
>
|
|
457
|
+
{tooltip}
|
|
458
|
+
</TooltipContent>
|
|
459
|
+
</Tooltip>
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/* ------------------------------------------------------------------ */
|
|
464
|
+
/* SidebarMenuAction */
|
|
465
|
+
/* ------------------------------------------------------------------ */
|
|
466
|
+
|
|
467
|
+
interface SidebarMenuActionProps extends React.ComponentProps<"button"> {
|
|
468
|
+
asChild?: boolean;
|
|
469
|
+
showOnHover?: boolean;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function SidebarMenuAction({
|
|
473
|
+
className,
|
|
474
|
+
asChild = false,
|
|
475
|
+
showOnHover = false,
|
|
476
|
+
...props
|
|
477
|
+
}: SidebarMenuActionProps) {
|
|
478
|
+
const Comp = asChild ? (Slot.Root as React.ElementType) : "button";
|
|
479
|
+
|
|
480
|
+
return (
|
|
481
|
+
<Comp
|
|
482
|
+
data-sidebar="menu-action"
|
|
483
|
+
className={cn(
|
|
484
|
+
"absolute right-1 top-1/2 -translate-y-1/2 flex aspect-square w-5 items-center justify-center",
|
|
485
|
+
"rounded-md text-taupe-400 hover:bg-taupe-100 hover:text-taupe-900",
|
|
486
|
+
"outline-none focus-visible:ring-2 focus-visible:ring-brand-primary/30",
|
|
487
|
+
showOnHover && [
|
|
488
|
+
"opacity-0 transition-opacity",
|
|
489
|
+
"group-hover/menu-item:opacity-100",
|
|
490
|
+
"peer-data-[active=true]/menu-button:opacity-100",
|
|
491
|
+
],
|
|
492
|
+
"group-data-[state=collapsed]/sidebar-wrapper:hidden",
|
|
493
|
+
className,
|
|
494
|
+
)}
|
|
495
|
+
{...props}
|
|
496
|
+
/>
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/* ------------------------------------------------------------------ */
|
|
501
|
+
/* SidebarMenuBadge */
|
|
502
|
+
/* ------------------------------------------------------------------ */
|
|
503
|
+
|
|
504
|
+
function SidebarMenuBadge({ className, ...props }: React.ComponentProps<"div">) {
|
|
505
|
+
return (
|
|
506
|
+
<div
|
|
507
|
+
data-sidebar="menu-badge"
|
|
508
|
+
className={cn(
|
|
509
|
+
"absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-full",
|
|
510
|
+
"caption font-medium bg-taupe-100 text-taupe-700 px-1",
|
|
511
|
+
"group-data-[state=collapsed]/sidebar-wrapper:hidden",
|
|
512
|
+
className,
|
|
513
|
+
)}
|
|
514
|
+
{...props}
|
|
515
|
+
/>
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/* ------------------------------------------------------------------ */
|
|
520
|
+
/* SidebarMenuSkeleton */
|
|
521
|
+
/* ------------------------------------------------------------------ */
|
|
522
|
+
|
|
523
|
+
function SidebarMenuSkeleton({
|
|
524
|
+
className,
|
|
525
|
+
showIcon = false,
|
|
526
|
+
...props
|
|
527
|
+
}: React.ComponentProps<"div"> & { showIcon?: boolean }) {
|
|
528
|
+
const width = React.useMemo(() => {
|
|
529
|
+
return `${Math.floor(Math.random() * 40) + 50}%`;
|
|
530
|
+
}, []);
|
|
531
|
+
|
|
532
|
+
return (
|
|
533
|
+
<div
|
|
534
|
+
data-sidebar="menu-skeleton"
|
|
535
|
+
className={cn("flex h-8 items-center gap-2 rounded-md px-2", className)}
|
|
536
|
+
{...props}
|
|
537
|
+
>
|
|
538
|
+
{showIcon && <div className="size-4 shrink-0 rounded-md bg-taupe-200 animate-pulse" />}
|
|
539
|
+
<div
|
|
540
|
+
className="h-4 max-w-[--skeleton-width] flex-1 rounded-md bg-taupe-200 animate-pulse"
|
|
541
|
+
style={{ "--skeleton-width": width } as React.CSSProperties}
|
|
542
|
+
/>
|
|
543
|
+
</div>
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/* ------------------------------------------------------------------ */
|
|
548
|
+
/* Sub menu */
|
|
549
|
+
/* ------------------------------------------------------------------ */
|
|
550
|
+
|
|
551
|
+
function SidebarMenuSub({ className, ...props }: React.ComponentProps<"ul">) {
|
|
552
|
+
return (
|
|
553
|
+
<ul
|
|
554
|
+
data-sidebar="menu-sub"
|
|
555
|
+
className={cn(
|
|
556
|
+
"mx-3.5 flex min-w-0 flex-col gap-1 border-l border-taupe-100 pl-3",
|
|
557
|
+
"group-data-[state=collapsed]/sidebar-wrapper:hidden",
|
|
558
|
+
className,
|
|
559
|
+
)}
|
|
560
|
+
{...props}
|
|
561
|
+
/>
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
function SidebarMenuSubItem({ className, ...props }: React.ComponentProps<"li">) {
|
|
566
|
+
return (
|
|
567
|
+
<li
|
|
568
|
+
data-sidebar="menu-sub-item"
|
|
569
|
+
className={cn("group/menu-sub-item relative", className)}
|
|
570
|
+
{...props}
|
|
571
|
+
/>
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
interface SidebarMenuSubButtonProps extends React.ComponentProps<"a"> {
|
|
576
|
+
asChild?: boolean;
|
|
577
|
+
isActive?: boolean;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function SidebarMenuSubButton({
|
|
581
|
+
asChild = false,
|
|
582
|
+
isActive = false,
|
|
583
|
+
className,
|
|
584
|
+
...props
|
|
585
|
+
}: SidebarMenuSubButtonProps) {
|
|
586
|
+
const Comp = asChild ? (Slot.Root as React.ElementType) : "a";
|
|
587
|
+
|
|
588
|
+
return (
|
|
589
|
+
<Comp
|
|
590
|
+
data-sidebar="menu-sub-button"
|
|
591
|
+
data-active={isActive}
|
|
592
|
+
className={cn(
|
|
593
|
+
"flex h-7 min-w-0 items-center gap-2 overflow-hidden rounded-md px-2",
|
|
594
|
+
"body text-taupe-500 outline-none",
|
|
595
|
+
"transition-colors duration-150",
|
|
596
|
+
"hover:text-taupe-900 hover:bg-taupe-100",
|
|
597
|
+
"focus-visible:ring-2 focus-visible:ring-brand-primary/30",
|
|
598
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
599
|
+
"[&>svg]:size-3.5 [&>svg]:shrink-0",
|
|
600
|
+
isActive && "text-brand-primary hover:text-brand-primary",
|
|
601
|
+
className,
|
|
602
|
+
)}
|
|
603
|
+
{...props}
|
|
604
|
+
/>
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/* ------------------------------------------------------------------ */
|
|
609
|
+
/* Exports */
|
|
610
|
+
/* ------------------------------------------------------------------ */
|
|
611
|
+
|
|
612
|
+
export {
|
|
613
|
+
SidebarProvider,
|
|
614
|
+
Sidebar,
|
|
615
|
+
SidebarContent,
|
|
616
|
+
SidebarFooter,
|
|
617
|
+
SidebarGroup,
|
|
618
|
+
SidebarGroupAction,
|
|
619
|
+
SidebarGroupContent,
|
|
620
|
+
SidebarGroupLabel,
|
|
621
|
+
SidebarHeader,
|
|
622
|
+
SidebarInset,
|
|
623
|
+
SidebarMenu,
|
|
624
|
+
SidebarMenuAction,
|
|
625
|
+
SidebarMenuBadge,
|
|
626
|
+
SidebarMenuButton,
|
|
627
|
+
SidebarMenuItem,
|
|
628
|
+
SidebarMenuSkeleton,
|
|
629
|
+
SidebarMenuSub,
|
|
630
|
+
SidebarMenuSubButton,
|
|
631
|
+
SidebarMenuSubItem,
|
|
632
|
+
SidebarRail,
|
|
633
|
+
SidebarSeparator,
|
|
634
|
+
SidebarTrigger,
|
|
635
|
+
useSidebar,
|
|
636
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cn } from "../lib/utils";
|
|
3
|
+
|
|
4
|
+
function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
|
|
5
|
+
return (
|
|
6
|
+
<div
|
|
7
|
+
data-slot="skeleton"
|
|
8
|
+
className={cn("animate-pulse rounded-md bg-taupe-100", className)}
|
|
9
|
+
{...props}
|
|
10
|
+
/>
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export { Skeleton };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Slider } from "radix-ui";
|
|
5
|
+
import { cn } from "../lib/utils";
|
|
6
|
+
|
|
7
|
+
const SliderRoot = React.forwardRef<
|
|
8
|
+
React.ComponentRef<typeof Slider.Root>,
|
|
9
|
+
React.ComponentPropsWithoutRef<typeof Slider.Root>
|
|
10
|
+
>(({ className, ...props }, ref) => (
|
|
11
|
+
<Slider.Root
|
|
12
|
+
ref={ref}
|
|
13
|
+
className={cn("relative flex w-full touch-none select-none items-center", className)}
|
|
14
|
+
{...props}
|
|
15
|
+
>
|
|
16
|
+
<Slider.Track className="relative h-1.5 w-full grow overflow-hidden rounded-full bg-taupe-100">
|
|
17
|
+
<Slider.Range className="absolute h-full bg-brand-primary" />
|
|
18
|
+
</Slider.Track>
|
|
19
|
+
<Slider.Thumb className="block size-4 rounded-full border-2 border-brand-primary bg-white shadow-natural transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-primary/30 disabled:pointer-events-none disabled:opacity-50" />
|
|
20
|
+
</Slider.Root>
|
|
21
|
+
));
|
|
22
|
+
SliderRoot.displayName = "Slider";
|
|
23
|
+
|
|
24
|
+
export { SliderRoot as Slider };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Toaster as SonnerToaster } from "sonner";
|
|
4
|
+
|
|
5
|
+
type ToasterProps = React.ComponentProps<typeof SonnerToaster>;
|
|
6
|
+
|
|
7
|
+
function Toaster({ ...props }: ToasterProps) {
|
|
8
|
+
return (
|
|
9
|
+
<SonnerToaster
|
|
10
|
+
theme="light"
|
|
11
|
+
className="toaster group"
|
|
12
|
+
toastOptions={{
|
|
13
|
+
classNames: {
|
|
14
|
+
toast:
|
|
15
|
+
"group toast font-sans text-sm group-[.toaster]:bg-white group-[.toaster]:text-taupe-900 group-[.toaster]:border group-[.toaster]:border-taupe-100 group-[.toaster]:shadow-elevated group-[.toaster]:rounded-xl",
|
|
16
|
+
description: "group-[.toast]:text-taupe-500 group-[.toast]:text-xs",
|
|
17
|
+
actionButton:
|
|
18
|
+
"group-[.toast]:bg-brand-primary group-[.toast]:text-white group-[.toast]:rounded-md group-[.toast]:text-xs group-[.toast]:font-medium",
|
|
19
|
+
cancelButton:
|
|
20
|
+
"group-[.toast]:bg-taupe-100 group-[.toast]:text-taupe-600 group-[.toast]:rounded-md group-[.toast]:text-xs group-[.toast]:font-medium",
|
|
21
|
+
success: "group-[.toaster]:border-brand-secondary/40 group-[.toaster]:text-taupe-900",
|
|
22
|
+
error:
|
|
23
|
+
"group-[.toaster]:bg-red-50 group-[.toaster]:text-red-700 group-[.toaster]:border-red-200",
|
|
24
|
+
warning:
|
|
25
|
+
"group-[.toaster]:bg-yellow-50 group-[.toaster]:text-yellow-800 group-[.toaster]:border-yellow-200",
|
|
26
|
+
info: "group-[.toaster]:border-taupe-200",
|
|
27
|
+
},
|
|
28
|
+
}}
|
|
29
|
+
{...props}
|
|
30
|
+
/>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export { Toaster };
|