@nikala-ui/core 0.10.1 → 0.11.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/package.json +1 -1
- package/registry/bubble.json +18 -0
- package/registry/button-group.json +20 -0
- package/registry/create-chat-scroll.json +13 -0
- package/registry/create-drop-zone.json +13 -0
- package/registry/create-pagination.json +13 -0
- package/registry/dropzone.json +21 -0
- package/registry/footer.json +18 -0
- package/registry/forgot-password-01.json +28 -0
- package/registry/hero-01.json +22 -0
- package/registry/index.json +331 -0
- package/registry/login-01.json +28 -0
- package/registry/marker.json +17 -0
- package/registry/marquee.json +17 -0
- package/registry/message.json +17 -0
- package/registry/navbar.json +19 -0
- package/registry/navigation-menu.json +22 -0
- package/registry/otp-verification-01.json +26 -0
- package/registry/pagination.json +22 -0
- package/registry/rating.json +19 -0
- package/registry/register-01.json +31 -0
- package/registry/review-card.json +23 -0
- package/registry/scroll-area.json +1 -1
- package/registry/sidebar.json +24 -0
- package/registry/spinner.json +1 -1
- package/registry/stat.json +19 -0
- package/registry/table.json +17 -0
- package/registry/timeline.json +18 -0
- package/registry/toggle-group.json +21 -0
- package/src/registry/blocks/forgot-password-01.tsx +141 -0
- package/src/registry/blocks/hero-01.tsx +28 -0
- package/src/registry/blocks/login-01.tsx +231 -0
- package/src/registry/blocks/otp-verification-01.tsx +170 -0
- package/src/registry/blocks/register-01.tsx +318 -0
- package/src/registry/components/ui/bubble.tsx +142 -0
- package/src/registry/components/ui/button-group.tsx +42 -0
- package/src/registry/components/ui/dropzone.tsx +189 -0
- package/src/registry/components/ui/footer.tsx +247 -0
- package/src/registry/components/ui/marker.tsx +102 -0
- package/src/registry/components/ui/marquee.tsx +118 -0
- package/src/registry/components/ui/message.tsx +168 -0
- package/src/registry/components/ui/navbar.tsx +368 -0
- package/src/registry/components/ui/navigation-menu.tsx +358 -0
- package/src/registry/components/ui/pagination.tsx +258 -0
- package/src/registry/components/ui/rating.tsx +185 -0
- package/src/registry/components/ui/review-card.tsx +195 -0
- package/src/registry/components/ui/scroll-area.tsx +2 -2
- package/src/registry/components/ui/sidebar.tsx +692 -0
- package/src/registry/components/ui/spinner.tsx +1 -1
- package/src/registry/components/ui/stat.tsx +245 -0
- package/src/registry/components/ui/table.tsx +160 -0
- package/src/registry/components/ui/timeline.tsx +350 -0
- package/src/registry/components/ui/toggle-group.tsx +203 -0
- package/src/registry/index.ts +3 -3
- package/src/registry/metadata.ts +141 -0
|
@@ -0,0 +1,692 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createContext,
|
|
3
|
+
createSignal,
|
|
4
|
+
createMemo,
|
|
5
|
+
createEffect,
|
|
6
|
+
useContext,
|
|
7
|
+
splitProps,
|
|
8
|
+
onMount,
|
|
9
|
+
onCleanup,
|
|
10
|
+
type Component,
|
|
11
|
+
type JSX,
|
|
12
|
+
type ParentComponent,
|
|
13
|
+
type Accessor,
|
|
14
|
+
Show,
|
|
15
|
+
} from "solid-js";
|
|
16
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
17
|
+
import { PanelLeft, PanelRight } from "lucide-solid";
|
|
18
|
+
import { Tooltip, TooltipTrigger, TooltipContent } from "./tooltip";
|
|
19
|
+
import { Skeleton } from "./skeleton";
|
|
20
|
+
import { Separator } from "./separator";
|
|
21
|
+
import { cn } from "@/lib/cn";
|
|
22
|
+
|
|
23
|
+
/* --- Constants --- */
|
|
24
|
+
const SIDEBAR_COOKIE_NAME = "nikala_sidebar_state";
|
|
25
|
+
const SIDEBAR_WIDTH = "17rem";
|
|
26
|
+
const SIDEBAR_WIDTH_ICON = "3.5rem";
|
|
27
|
+
const SIDEBAR_KEYBOARD_SHORTCUT = "b";
|
|
28
|
+
|
|
29
|
+
/* --- 1. Context & Types --- */
|
|
30
|
+
export type SidebarState = "expanded" | "collapsed";
|
|
31
|
+
export type SidebarSide = "left" | "right";
|
|
32
|
+
|
|
33
|
+
export interface SidebarContextValue {
|
|
34
|
+
state: Accessor<SidebarState>;
|
|
35
|
+
open: Accessor<boolean>;
|
|
36
|
+
setOpen: (open: boolean) => void;
|
|
37
|
+
openMobile: Accessor<boolean>;
|
|
38
|
+
setOpenMobile: (open: boolean) => void;
|
|
39
|
+
isMobile: Accessor<boolean>;
|
|
40
|
+
toggleSidebar: () => void;
|
|
41
|
+
side: Accessor<SidebarSide>;
|
|
42
|
+
setSide: (side: SidebarSide) => void;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const SidebarContext = createContext<SidebarContextValue>();
|
|
46
|
+
|
|
47
|
+
const fallbackSidebarContext: SidebarContextValue = {
|
|
48
|
+
state: () => "expanded",
|
|
49
|
+
open: () => true,
|
|
50
|
+
setOpen: () => {},
|
|
51
|
+
openMobile: () => false,
|
|
52
|
+
setOpenMobile: () => {},
|
|
53
|
+
isMobile: () => false,
|
|
54
|
+
toggleSidebar: () => {},
|
|
55
|
+
side: () => "left",
|
|
56
|
+
setSide: () => {},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export function useSidebar() {
|
|
60
|
+
const context = useContext(SidebarContext);
|
|
61
|
+
return context ?? fallbackSidebarContext;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* --- 2. SidebarProvider --- */
|
|
65
|
+
export interface SidebarProviderProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
|
66
|
+
defaultOpen?: boolean;
|
|
67
|
+
open?: Accessor<boolean>;
|
|
68
|
+
onOpenChange?: (open: boolean) => void;
|
|
69
|
+
side?: SidebarSide;
|
|
70
|
+
class?: string;
|
|
71
|
+
style?: JSX.CSSProperties;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export const SidebarProvider: ParentComponent<SidebarProviderProps> = (props) => {
|
|
75
|
+
const [local, rest] = splitProps(props, [
|
|
76
|
+
"defaultOpen",
|
|
77
|
+
"open",
|
|
78
|
+
"onOpenChange",
|
|
79
|
+
"side",
|
|
80
|
+
"class",
|
|
81
|
+
"style",
|
|
82
|
+
"children",
|
|
83
|
+
]);
|
|
84
|
+
|
|
85
|
+
const [internalOpen, setInternalOpen] = createSignal<boolean>(local.defaultOpen ?? true);
|
|
86
|
+
const [openMobile, setOpenMobile] = createSignal<boolean>(false);
|
|
87
|
+
const [isMobile, setIsMobile] = createSignal<boolean>(false);
|
|
88
|
+
const [side, setSide] = createSignal<SidebarSide>(local.side ?? "left");
|
|
89
|
+
|
|
90
|
+
createEffect(() => {
|
|
91
|
+
if (local.side) {
|
|
92
|
+
setSide(local.side);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const open = () => (local.open !== undefined ? local.open() : internalOpen());
|
|
97
|
+
|
|
98
|
+
const setOpen = (value: boolean) => {
|
|
99
|
+
if (local.open === undefined) {
|
|
100
|
+
setInternalOpen(value);
|
|
101
|
+
}
|
|
102
|
+
local.onOpenChange?.(value);
|
|
103
|
+
|
|
104
|
+
if (typeof document !== "undefined") {
|
|
105
|
+
document.cookie = `${SIDEBAR_COOKIE_NAME}=${value}; path=/; max-age=${60 * 60 * 24 * 7}`;
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const toggleSidebar = () => {
|
|
110
|
+
setOpen(!open());
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const state = createMemo<SidebarState>(() => (open() ? "expanded" : "collapsed"));
|
|
114
|
+
|
|
115
|
+
onMount(() => {
|
|
116
|
+
if (typeof window === "undefined") return;
|
|
117
|
+
|
|
118
|
+
const checkMobile = () => {
|
|
119
|
+
setIsMobile(window.innerWidth < 768);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
checkMobile();
|
|
123
|
+
window.addEventListener("resize", checkMobile, { passive: true });
|
|
124
|
+
|
|
125
|
+
// Keyboard shortcut (⌘B / Ctrl+B)
|
|
126
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
127
|
+
if (
|
|
128
|
+
(e.metaKey || e.ctrlKey) &&
|
|
129
|
+
e.key.toLowerCase() === SIDEBAR_KEYBOARD_SHORTCUT &&
|
|
130
|
+
!e.defaultPrevented
|
|
131
|
+
) {
|
|
132
|
+
e.preventDefault();
|
|
133
|
+
toggleSidebar();
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
138
|
+
|
|
139
|
+
onCleanup(() => {
|
|
140
|
+
window.removeEventListener("resize", checkMobile);
|
|
141
|
+
window.removeEventListener("keydown", handleKeyDown);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const contextValue: SidebarContextValue = {
|
|
146
|
+
state,
|
|
147
|
+
open,
|
|
148
|
+
setOpen,
|
|
149
|
+
openMobile,
|
|
150
|
+
setOpenMobile,
|
|
151
|
+
isMobile,
|
|
152
|
+
toggleSidebar,
|
|
153
|
+
side,
|
|
154
|
+
setSide,
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
<SidebarContext.Provider value={contextValue}>
|
|
159
|
+
<div
|
|
160
|
+
style={{
|
|
161
|
+
"--sidebar-width": SIDEBAR_WIDTH,
|
|
162
|
+
"--sidebar-width-icon": SIDEBAR_WIDTH_ICON,
|
|
163
|
+
...(typeof local.style === "object" ? local.style : {}),
|
|
164
|
+
} as JSX.CSSProperties}
|
|
165
|
+
class={cn(
|
|
166
|
+
"group/sidebar-wrapper relative flex w-full max-w-full",
|
|
167
|
+
local.class
|
|
168
|
+
)}
|
|
169
|
+
{...rest}
|
|
170
|
+
>
|
|
171
|
+
{local.children}
|
|
172
|
+
</div>
|
|
173
|
+
</SidebarContext.Provider>
|
|
174
|
+
);
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
/* --- 3. Sidebar Root --- */
|
|
178
|
+
export interface SidebarProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
|
179
|
+
side?: SidebarSide;
|
|
180
|
+
variant?: "sidebar" | "floating" | "inset";
|
|
181
|
+
collapsible?: "offcanvas" | "icon" | "none";
|
|
182
|
+
class?: string;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export const Sidebar: ParentComponent<SidebarProps> = (props) => {
|
|
186
|
+
const [local, rest] = splitProps(props, [
|
|
187
|
+
"side",
|
|
188
|
+
"variant",
|
|
189
|
+
"collapsible",
|
|
190
|
+
"class",
|
|
191
|
+
"children",
|
|
192
|
+
]);
|
|
193
|
+
|
|
194
|
+
const sidebar = useSidebar();
|
|
195
|
+
const side = () => local.side ?? sidebar.side();
|
|
196
|
+
const variant = () => local.variant ?? "sidebar";
|
|
197
|
+
const collapsible = () => local.collapsible ?? "icon";
|
|
198
|
+
|
|
199
|
+
createEffect(() => {
|
|
200
|
+
if (local.side) {
|
|
201
|
+
sidebar.setSide(local.side);
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
const isCollapsed = () => sidebar.state() === "collapsed" && collapsible() !== "none";
|
|
206
|
+
|
|
207
|
+
return (
|
|
208
|
+
<div
|
|
209
|
+
data-state={isCollapsed() ? "collapsed" : "expanded"}
|
|
210
|
+
data-collapsible={isCollapsed() ? collapsible() : ""}
|
|
211
|
+
data-variant={variant()}
|
|
212
|
+
data-side={side()}
|
|
213
|
+
class={cn(
|
|
214
|
+
"group relative flex flex-col bg-card text-card-foreground transition-all duration-200 ease-in-out shrink-0",
|
|
215
|
+
!isCollapsed()
|
|
216
|
+
? "w-[var(--sidebar-width,17rem)]"
|
|
217
|
+
: collapsible() === "icon"
|
|
218
|
+
? "w-[var(--sidebar-width-icon,3.5rem)]"
|
|
219
|
+
: "w-0 overflow-hidden opacity-0 border-none",
|
|
220
|
+
// Side borders
|
|
221
|
+
variant() === "sidebar" && (side() === "left" ? "border-r border-border" : "border-l border-border"),
|
|
222
|
+
// Floating variant
|
|
223
|
+
variant() === "floating" && "m-2 rounded-lg border border-border shadow-md",
|
|
224
|
+
// Inset variant
|
|
225
|
+
variant() === "inset" && "m-2 rounded-lg border border-border/80 bg-card shadow-2xs",
|
|
226
|
+
local.class
|
|
227
|
+
)}
|
|
228
|
+
{...rest}
|
|
229
|
+
>
|
|
230
|
+
<div
|
|
231
|
+
data-sidebar="sidebar"
|
|
232
|
+
class="flex h-full w-full flex-col overflow-hidden"
|
|
233
|
+
>
|
|
234
|
+
{local.children}
|
|
235
|
+
</div>
|
|
236
|
+
</div>
|
|
237
|
+
);
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
/* --- 4. SidebarTrigger --- */
|
|
241
|
+
export interface SidebarTriggerProps extends JSX.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
242
|
+
class?: string;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export const SidebarTrigger: Component<SidebarTriggerProps> = (props) => {
|
|
246
|
+
const [local, rest] = splitProps(props, ["class", "onClick"]);
|
|
247
|
+
const { toggleSidebar, side } = useSidebar();
|
|
248
|
+
|
|
249
|
+
return (
|
|
250
|
+
<button
|
|
251
|
+
type="button"
|
|
252
|
+
aria-label="Toggle Sidebar"
|
|
253
|
+
onClick={(e) => {
|
|
254
|
+
if (typeof local.onClick === "function") {
|
|
255
|
+
local.onClick(e);
|
|
256
|
+
}
|
|
257
|
+
toggleSidebar();
|
|
258
|
+
}}
|
|
259
|
+
class={cn(
|
|
260
|
+
"inline-flex size-7 items-center justify-center rounded-md border border-border/60 bg-background text-foreground hover:bg-muted focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring cursor-pointer transition-colors shadow-2xs",
|
|
261
|
+
local.class
|
|
262
|
+
)}
|
|
263
|
+
{...rest}
|
|
264
|
+
>
|
|
265
|
+
<Show when={side() === "right"} fallback={<PanelLeft class="size-4" />}>
|
|
266
|
+
<PanelRight class="size-4" />
|
|
267
|
+
</Show>
|
|
268
|
+
<span class="sr-only">Toggle Sidebar</span>
|
|
269
|
+
</button>
|
|
270
|
+
);
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
/* --- 5. SidebarRail --- */
|
|
274
|
+
export interface SidebarRailProps extends JSX.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
275
|
+
class?: string;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export const SidebarRail: Component<SidebarRailProps> = (props) => {
|
|
279
|
+
const [local, rest] = splitProps(props, ["class"]);
|
|
280
|
+
const { toggleSidebar } = useSidebar();
|
|
281
|
+
|
|
282
|
+
return (
|
|
283
|
+
<button
|
|
284
|
+
type="button"
|
|
285
|
+
data-sidebar="rail"
|
|
286
|
+
aria-label="Toggle Sidebar Rail"
|
|
287
|
+
tabIndex={-1}
|
|
288
|
+
onClick={toggleSidebar}
|
|
289
|
+
title="Toggle Sidebar"
|
|
290
|
+
class={cn(
|
|
291
|
+
"absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] hover:after:bg-border group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex cursor-w-resize",
|
|
292
|
+
local.class
|
|
293
|
+
)}
|
|
294
|
+
{...rest}
|
|
295
|
+
/>
|
|
296
|
+
);
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
/* --- 6. SidebarInset --- */
|
|
300
|
+
export interface SidebarInsetProps extends JSX.HTMLAttributes<HTMLElement> {
|
|
301
|
+
class?: string;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export const SidebarInset: ParentComponent<SidebarInsetProps> = (props) => {
|
|
305
|
+
const [local, rest] = splitProps(props, ["class", "children"]);
|
|
306
|
+
|
|
307
|
+
return (
|
|
308
|
+
<main
|
|
309
|
+
class={cn(
|
|
310
|
+
"relative flex min-h-0 flex-1 flex-col bg-background",
|
|
311
|
+
local.class
|
|
312
|
+
)}
|
|
313
|
+
{...rest}
|
|
314
|
+
>
|
|
315
|
+
{local.children}
|
|
316
|
+
</main>
|
|
317
|
+
);
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
/* --- 7. Sidebar Structural Sections --- */
|
|
321
|
+
export interface SidebarSectionProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
|
322
|
+
class?: string;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export const SidebarHeader: ParentComponent<SidebarSectionProps> = (props) => {
|
|
326
|
+
const [local, rest] = splitProps(props, ["class", "children"]);
|
|
327
|
+
|
|
328
|
+
return (
|
|
329
|
+
<div
|
|
330
|
+
data-sidebar="header"
|
|
331
|
+
class={cn("flex flex-col gap-2 p-3 border-b border-border/40 shrink-0 group-data-[collapsible=icon]:p-2 group-data-[collapsible=icon]:items-center", local.class)}
|
|
332
|
+
{...rest}
|
|
333
|
+
>
|
|
334
|
+
{local.children}
|
|
335
|
+
</div>
|
|
336
|
+
);
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
export const SidebarFooter: ParentComponent<SidebarSectionProps> = (props) => {
|
|
340
|
+
const [local, rest] = splitProps(props, ["class", "children"]);
|
|
341
|
+
|
|
342
|
+
return (
|
|
343
|
+
<div
|
|
344
|
+
data-sidebar="footer"
|
|
345
|
+
class={cn("flex flex-col gap-2 p-3 mt-auto border-t border-border/40 shrink-0 group-data-[collapsible=icon]:p-2 group-data-[collapsible=icon]:items-center", local.class)}
|
|
346
|
+
{...rest}
|
|
347
|
+
>
|
|
348
|
+
{local.children}
|
|
349
|
+
</div>
|
|
350
|
+
);
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
export const SidebarSeparator: Component<SidebarSectionProps> = (props) => {
|
|
354
|
+
const [local, rest] = splitProps(props, ["class"]);
|
|
355
|
+
|
|
356
|
+
return (
|
|
357
|
+
<Separator
|
|
358
|
+
data-sidebar="separator"
|
|
359
|
+
class={cn("mx-2 w-auto bg-border/50 my-1 group-data-[collapsible=icon]:mx-1", local.class)}
|
|
360
|
+
{...rest}
|
|
361
|
+
/>
|
|
362
|
+
);
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
export const SidebarContent: ParentComponent<SidebarSectionProps> = (props) => {
|
|
366
|
+
const [local, rest] = splitProps(props, ["class", "children"]);
|
|
367
|
+
|
|
368
|
+
return (
|
|
369
|
+
<div
|
|
370
|
+
data-sidebar="content"
|
|
371
|
+
class={cn(
|
|
372
|
+
"flex min-h-0 flex-1 flex-col gap-1 overflow-y-auto p-2 group-data-[collapsible=icon]:p-1 group-data-[collapsible=icon]:overflow-hidden group-data-[collapsible=icon]:items-center",
|
|
373
|
+
local.class
|
|
374
|
+
)}
|
|
375
|
+
{...rest}
|
|
376
|
+
>
|
|
377
|
+
{local.children}
|
|
378
|
+
</div>
|
|
379
|
+
);
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
/* --- 8. Sidebar Groups --- */
|
|
383
|
+
export const SidebarGroup: ParentComponent<SidebarSectionProps> = (props) => {
|
|
384
|
+
const [local, rest] = splitProps(props, ["class", "children"]);
|
|
385
|
+
|
|
386
|
+
return (
|
|
387
|
+
<div
|
|
388
|
+
data-sidebar="group"
|
|
389
|
+
class={cn("relative flex w-full min-w-0 flex-col p-1 group-data-[collapsible=icon]:p-0 group-data-[collapsible=icon]:items-center", local.class)}
|
|
390
|
+
{...rest}
|
|
391
|
+
>
|
|
392
|
+
{local.children}
|
|
393
|
+
</div>
|
|
394
|
+
);
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
export interface SidebarGroupLabelProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
|
398
|
+
class?: string;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export const SidebarGroupLabel: ParentComponent<SidebarGroupLabelProps> = (props) => {
|
|
402
|
+
const [local, rest] = splitProps(props, ["class", "children"]);
|
|
403
|
+
|
|
404
|
+
return (
|
|
405
|
+
<div
|
|
406
|
+
data-sidebar="group-label"
|
|
407
|
+
class={cn(
|
|
408
|
+
"flex h-7 shrink-0 items-center rounded-md px-2 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground outline-hidden transition-all duration-200 ease-linear group-data-[collapsible=icon]:hidden",
|
|
409
|
+
local.class
|
|
410
|
+
)}
|
|
411
|
+
{...rest}
|
|
412
|
+
>
|
|
413
|
+
{local.children}
|
|
414
|
+
</div>
|
|
415
|
+
);
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
export interface SidebarGroupActionProps extends JSX.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
419
|
+
class?: string;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
export const SidebarGroupAction: ParentComponent<SidebarGroupActionProps> = (props) => {
|
|
423
|
+
const [local, rest] = splitProps(props, ["class", "children"]);
|
|
424
|
+
|
|
425
|
+
return (
|
|
426
|
+
<button
|
|
427
|
+
type="button"
|
|
428
|
+
data-sidebar="group-action"
|
|
429
|
+
class={cn(
|
|
430
|
+
"absolute right-3 top-3 flex size-5 items-center justify-center rounded-md p-0 text-muted-foreground outline-hidden transition-transform hover:bg-muted hover:text-foreground cursor-pointer group-data-[collapsible=icon]:hidden",
|
|
431
|
+
local.class
|
|
432
|
+
)}
|
|
433
|
+
{...rest}
|
|
434
|
+
>
|
|
435
|
+
{local.children}
|
|
436
|
+
</button>
|
|
437
|
+
);
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
export const SidebarGroupContent: ParentComponent<SidebarSectionProps> = (props) => {
|
|
441
|
+
const [local, rest] = splitProps(props, ["class", "children"]);
|
|
442
|
+
|
|
443
|
+
return (
|
|
444
|
+
<div
|
|
445
|
+
data-sidebar="group-content"
|
|
446
|
+
class={cn("w-full text-sm", local.class)}
|
|
447
|
+
{...rest}
|
|
448
|
+
>
|
|
449
|
+
{local.children}
|
|
450
|
+
</div>
|
|
451
|
+
);
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
/* --- 9. Sidebar Menu --- */
|
|
455
|
+
export interface SidebarMenuProps extends JSX.HTMLAttributes<HTMLUListElement> {
|
|
456
|
+
class?: string;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
export const SidebarMenu: ParentComponent<SidebarMenuProps> = (props) => {
|
|
460
|
+
const [local, rest] = splitProps(props, ["class", "children"]);
|
|
461
|
+
|
|
462
|
+
return (
|
|
463
|
+
<ul
|
|
464
|
+
data-sidebar="menu"
|
|
465
|
+
class={cn("flex w-full min-w-0 flex-col gap-1 list-none p-0 m-0 group-data-[collapsible=icon]:items-center", local.class)}
|
|
466
|
+
{...rest}
|
|
467
|
+
>
|
|
468
|
+
{local.children}
|
|
469
|
+
</ul>
|
|
470
|
+
);
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
export interface SidebarMenuItemProps extends JSX.HTMLAttributes<HTMLLIElement> {
|
|
474
|
+
class?: string;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
export const SidebarMenuItem: ParentComponent<SidebarMenuItemProps> = (props) => {
|
|
478
|
+
const [local, rest] = splitProps(props, ["class", "children"]);
|
|
479
|
+
|
|
480
|
+
return (
|
|
481
|
+
<li
|
|
482
|
+
data-sidebar="menu-item"
|
|
483
|
+
class={cn("group/menu-item relative flex w-full justify-center", local.class)}
|
|
484
|
+
{...rest}
|
|
485
|
+
>
|
|
486
|
+
{local.children}
|
|
487
|
+
</li>
|
|
488
|
+
);
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
/* --- 10. SidebarMenuButton --- */
|
|
492
|
+
export const sidebarMenuButtonVariants = cva(
|
|
493
|
+
"peer/menu-button flex w-full items-center gap-2.5 overflow-hidden rounded-md p-2 text-left text-sm font-medium outline-hidden ring-sidebar-ring transition-colors hover:bg-muted hover:text-foreground focus-visible:ring-2 active:bg-muted disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-muted data-[active=true]:font-semibold data-[active=true]:text-foreground group-data-[collapsible=icon]:size-9 group-data-[collapsible=icon]:p-0 group-data-[collapsible=icon]:justify-center group-data-[collapsible=icon]:[&>span]:hidden cursor-pointer select-none",
|
|
494
|
+
{
|
|
495
|
+
variants: {
|
|
496
|
+
variant: {
|
|
497
|
+
default: "hover:bg-muted hover:text-foreground",
|
|
498
|
+
outline: "bg-background shadow-2xs hover:bg-muted hover:text-foreground border border-border",
|
|
499
|
+
},
|
|
500
|
+
size: {
|
|
501
|
+
default: "h-8 text-sm",
|
|
502
|
+
sm: "h-7 text-xs",
|
|
503
|
+
lg: "h-10 text-sm",
|
|
504
|
+
},
|
|
505
|
+
},
|
|
506
|
+
defaultVariants: {
|
|
507
|
+
variant: "default",
|
|
508
|
+
size: "default",
|
|
509
|
+
},
|
|
510
|
+
}
|
|
511
|
+
);
|
|
512
|
+
|
|
513
|
+
export interface SidebarMenuButtonProps
|
|
514
|
+
extends JSX.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
515
|
+
VariantProps<typeof sidebarMenuButtonVariants> {
|
|
516
|
+
isActive?: boolean;
|
|
517
|
+
tooltip?: string;
|
|
518
|
+
class?: string;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
export const SidebarMenuButton: ParentComponent<SidebarMenuButtonProps> = (props) => {
|
|
522
|
+
const [local, rest] = splitProps(props, [
|
|
523
|
+
"isActive",
|
|
524
|
+
"tooltip",
|
|
525
|
+
"variant",
|
|
526
|
+
"size",
|
|
527
|
+
"class",
|
|
528
|
+
"children",
|
|
529
|
+
]);
|
|
530
|
+
|
|
531
|
+
const sidebar = useSidebar();
|
|
532
|
+
const isCollapsed = () => sidebar.state() === "collapsed";
|
|
533
|
+
const tooltipPlacement = () => (sidebar.side() === "right" ? "left" : "right");
|
|
534
|
+
|
|
535
|
+
const buttonElement = () => (
|
|
536
|
+
<button
|
|
537
|
+
type="button"
|
|
538
|
+
data-sidebar="menu-button"
|
|
539
|
+
data-size={local.size}
|
|
540
|
+
data-active={local.isActive ? "true" : "false"}
|
|
541
|
+
class={cn(
|
|
542
|
+
sidebarMenuButtonVariants({ variant: local.variant, size: local.size }),
|
|
543
|
+
local.class
|
|
544
|
+
)}
|
|
545
|
+
{...rest}
|
|
546
|
+
>
|
|
547
|
+
{local.children}
|
|
548
|
+
</button>
|
|
549
|
+
);
|
|
550
|
+
|
|
551
|
+
return (
|
|
552
|
+
<Show
|
|
553
|
+
when={local.tooltip && isCollapsed() && !sidebar.isMobile()}
|
|
554
|
+
fallback={buttonElement()}
|
|
555
|
+
>
|
|
556
|
+
<Tooltip placement={tooltipPlacement()} gutter={8} openDelay={100}>
|
|
557
|
+
<TooltipTrigger as="div" class="w-full flex items-center justify-center">
|
|
558
|
+
{buttonElement()}
|
|
559
|
+
</TooltipTrigger>
|
|
560
|
+
<TooltipContent>
|
|
561
|
+
{local.tooltip}
|
|
562
|
+
</TooltipContent>
|
|
563
|
+
</Tooltip>
|
|
564
|
+
</Show>
|
|
565
|
+
);
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
/* --- 11. SidebarMenuAction & Badge --- */
|
|
569
|
+
export interface SidebarMenuActionProps extends JSX.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
570
|
+
showOnHover?: boolean;
|
|
571
|
+
class?: string;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
export const SidebarMenuAction: ParentComponent<SidebarMenuActionProps> = (props) => {
|
|
575
|
+
const [local, rest] = splitProps(props, ["showOnHover", "class", "children"]);
|
|
576
|
+
|
|
577
|
+
return (
|
|
578
|
+
<button
|
|
579
|
+
type="button"
|
|
580
|
+
data-sidebar="menu-action"
|
|
581
|
+
class={cn(
|
|
582
|
+
"absolute right-1 top-1.5 flex size-5 items-center justify-center rounded-md p-0 text-muted-foreground outline-hidden transition-transform hover:bg-muted hover:text-foreground cursor-pointer group-data-[collapsible=icon]:hidden",
|
|
583
|
+
local.showOnHover && "opacity-0 group-hover/menu-item:opacity-100 transition-opacity",
|
|
584
|
+
local.class
|
|
585
|
+
)}
|
|
586
|
+
{...rest}
|
|
587
|
+
>
|
|
588
|
+
{local.children}
|
|
589
|
+
</button>
|
|
590
|
+
);
|
|
591
|
+
};
|
|
592
|
+
|
|
593
|
+
export interface SidebarMenuBadgeProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
|
594
|
+
class?: string;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
export const SidebarMenuBadge: ParentComponent<SidebarMenuBadgeProps> = (props) => {
|
|
598
|
+
const [local, rest] = splitProps(props, ["class", "children"]);
|
|
599
|
+
|
|
600
|
+
return (
|
|
601
|
+
<div
|
|
602
|
+
data-sidebar="menu-badge"
|
|
603
|
+
class={cn(
|
|
604
|
+
"pointer-events-none absolute right-1.5 flex h-5 min-w-5 select-none items-center justify-center rounded-md px-1.5 text-[11px] font-medium tabular-nums bg-muted text-muted-foreground group-data-[collapsible=icon]:hidden",
|
|
605
|
+
local.class
|
|
606
|
+
)}
|
|
607
|
+
{...rest}
|
|
608
|
+
>
|
|
609
|
+
{local.children}
|
|
610
|
+
</div>
|
|
611
|
+
);
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
export interface SidebarMenuSkeletonProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
|
615
|
+
showIcon?: boolean;
|
|
616
|
+
class?: string;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
export const SidebarMenuSkeleton: Component<SidebarMenuSkeletonProps> = (props) => {
|
|
620
|
+
const [local, rest] = splitProps(props, ["showIcon", "class"]);
|
|
621
|
+
|
|
622
|
+
return (
|
|
623
|
+
<div
|
|
624
|
+
data-sidebar="menu-skeleton"
|
|
625
|
+
class={cn("flex h-8 items-center gap-2 rounded-md px-2 group-data-[collapsible=icon]:p-0 group-data-[collapsible=icon]:justify-center", local.class)}
|
|
626
|
+
{...rest}
|
|
627
|
+
>
|
|
628
|
+
<Show when={local.showIcon ?? true}>
|
|
629
|
+
<Skeleton class="size-4 rounded-md shrink-0" />
|
|
630
|
+
</Show>
|
|
631
|
+
<Skeleton class="h-4 flex-1 max-w-[80%] group-data-[collapsible=icon]:hidden" />
|
|
632
|
+
</div>
|
|
633
|
+
);
|
|
634
|
+
};
|
|
635
|
+
|
|
636
|
+
/* --- 12. Nested Submenus --- */
|
|
637
|
+
export interface SidebarMenuSubProps extends JSX.HTMLAttributes<HTMLUListElement> {
|
|
638
|
+
class?: string;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
export const SidebarMenuSub: ParentComponent<SidebarMenuSubProps> = (props) => {
|
|
642
|
+
const [local, rest] = splitProps(props, ["class", "children"]);
|
|
643
|
+
|
|
644
|
+
return (
|
|
645
|
+
<ul
|
|
646
|
+
data-sidebar="menu-sub"
|
|
647
|
+
class={cn(
|
|
648
|
+
"mx-3.5 flex min-w-0 flex-col gap-1 border-l border-border/60 px-2.5 py-0.5 list-none group-data-[collapsible=icon]:hidden",
|
|
649
|
+
local.class
|
|
650
|
+
)}
|
|
651
|
+
{...rest}
|
|
652
|
+
>
|
|
653
|
+
{local.children}
|
|
654
|
+
</ul>
|
|
655
|
+
);
|
|
656
|
+
};
|
|
657
|
+
|
|
658
|
+
export const SidebarMenuSubItem: ParentComponent<SidebarMenuItemProps> = (props) => {
|
|
659
|
+
const [local, rest] = splitProps(props, ["class", "children"]);
|
|
660
|
+
|
|
661
|
+
return (
|
|
662
|
+
<li class={cn("relative", local.class)} {...rest}>
|
|
663
|
+
{local.children}
|
|
664
|
+
</li>
|
|
665
|
+
);
|
|
666
|
+
};
|
|
667
|
+
|
|
668
|
+
export interface SidebarMenuSubButtonProps extends JSX.AnchorHTMLAttributes<HTMLAnchorElement> {
|
|
669
|
+
isActive?: boolean;
|
|
670
|
+
size?: "sm" | "md";
|
|
671
|
+
class?: string;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
export const SidebarMenuSubButton: ParentComponent<SidebarMenuSubButtonProps> = (props) => {
|
|
675
|
+
const [local, rest] = splitProps(props, ["isActive", "size", "class", "children"]);
|
|
676
|
+
|
|
677
|
+
return (
|
|
678
|
+
<a
|
|
679
|
+
data-sidebar="menu-sub-button"
|
|
680
|
+
data-size={local.size ?? "md"}
|
|
681
|
+
data-active={local.isActive ? "true" : "false"}
|
|
682
|
+
class={cn(
|
|
683
|
+
"flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-xs text-muted-foreground outline-hidden hover:bg-muted hover:text-foreground focus-visible:ring-1 focus-visible:ring-ring data-[active=true]:bg-muted data-[active=true]:font-medium data-[active=true]:text-foreground",
|
|
684
|
+
local.size === "sm" && "text-[11px]",
|
|
685
|
+
local.class
|
|
686
|
+
)}
|
|
687
|
+
{...rest}
|
|
688
|
+
>
|
|
689
|
+
{local.children}
|
|
690
|
+
</a>
|
|
691
|
+
);
|
|
692
|
+
};
|
|
@@ -3,7 +3,7 @@ import { cva, type VariantProps } from "class-variance-authority";
|
|
|
3
3
|
import { cn } from "@/lib/cn";
|
|
4
4
|
|
|
5
5
|
export const spinnerVariants = cva(
|
|
6
|
-
"inline-block animate-spin rounded-
|
|
6
|
+
"inline-block animate-spin rounded-lg border-2 border-current border-r-transparent text-primary",
|
|
7
7
|
{
|
|
8
8
|
variants: {
|
|
9
9
|
size: {
|