@dimaan/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/dist/index.cjs ADDED
@@ -0,0 +1,1296 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var clsx = require('clsx');
5
+ var tailwindMerge = require('tailwind-merge');
6
+ var jsxRuntime = require('react/jsx-runtime');
7
+ var lucideReact = require('lucide-react');
8
+
9
+ // src/components/dashboard-layout/context.ts
10
+ var DashboardLayoutContext = react.createContext(null);
11
+ function useDashboardLayout() {
12
+ const ctx = react.useContext(DashboardLayoutContext);
13
+ if (!ctx) {
14
+ throw new Error("useDashboardLayout must be used within a <DashboardLayout> component.");
15
+ }
16
+ return ctx;
17
+ }
18
+ function cn(...inputs) {
19
+ return tailwindMerge.twMerge(clsx.clsx(inputs));
20
+ }
21
+ function DashboardContent({ className, children, ...props }) {
22
+ return /* @__PURE__ */ jsxRuntime.jsx(
23
+ "main",
24
+ {
25
+ className: cn(
26
+ "flex min-h-[calc(100vh-var(--header-height))] flex-1 flex-col gap-6 p-4 sm:p-6 lg:p-8",
27
+ className
28
+ ),
29
+ ...props,
30
+ children
31
+ }
32
+ );
33
+ }
34
+ function DashboardLayout({
35
+ defaultCollapsed = false,
36
+ collapsed: collapsedProp,
37
+ onCollapsedChange,
38
+ className,
39
+ children,
40
+ ...props
41
+ }) {
42
+ const [internalCollapsed, setInternalCollapsed] = react.useState(defaultCollapsed);
43
+ const [mobileOpen, setMobileOpenState] = react.useState(false);
44
+ const isControlled = collapsedProp !== void 0;
45
+ const collapsed = isControlled ? collapsedProp : internalCollapsed;
46
+ const setCollapsed = react.useCallback(
47
+ (next) => {
48
+ if (!isControlled) setInternalCollapsed(next);
49
+ onCollapsedChange?.(next);
50
+ },
51
+ [isControlled, onCollapsedChange]
52
+ );
53
+ const toggleCollapsed = react.useCallback(() => {
54
+ setCollapsed(!collapsed);
55
+ }, [collapsed, setCollapsed]);
56
+ const setMobileOpen = react.useCallback((next) => {
57
+ setMobileOpenState(next);
58
+ }, []);
59
+ const toggleMobileOpen = react.useCallback(() => {
60
+ setMobileOpenState((prev) => !prev);
61
+ }, []);
62
+ const value = react.useMemo(
63
+ () => ({
64
+ collapsed,
65
+ setCollapsed,
66
+ toggleCollapsed,
67
+ mobileOpen,
68
+ setMobileOpen,
69
+ toggleMobileOpen
70
+ }),
71
+ [collapsed, setCollapsed, toggleCollapsed, mobileOpen, setMobileOpen, toggleMobileOpen]
72
+ );
73
+ return /* @__PURE__ */ jsxRuntime.jsx(DashboardLayoutContext.Provider, { value, children: /* @__PURE__ */ jsxRuntime.jsx(
74
+ "div",
75
+ {
76
+ "data-collapsed": collapsed ? "true" : "false",
77
+ className: cn(
78
+ "relative flex min-h-screen w-full bg-background text-foreground font-sans antialiased",
79
+ className
80
+ ),
81
+ ...props,
82
+ children
83
+ }
84
+ ) });
85
+ }
86
+ function DashboardMain({ className, children, ...props }) {
87
+ const { collapsed } = useDashboardLayout();
88
+ return /* @__PURE__ */ jsxRuntime.jsx(
89
+ "div",
90
+ {
91
+ className: cn(
92
+ "flex min-h-screen flex-1 flex-col transition-[margin] duration-200 ease-out",
93
+ // On desktop, push the main column past the fixed sidebar using logical margin.
94
+ collapsed ? "lg:ms-[var(--sidebar-width-collapsed)]" : "lg:ms-[var(--sidebar-width)]",
95
+ className
96
+ ),
97
+ ...props,
98
+ children
99
+ }
100
+ );
101
+ }
102
+ function DashboardHeader({ className, children, ...props }) {
103
+ return /* @__PURE__ */ jsxRuntime.jsx(
104
+ "header",
105
+ {
106
+ className: cn(
107
+ "sticky top-0 z-20 flex h-[var(--header-height)] shrink-0 items-center gap-3 border-b border-header-border bg-header/80 px-4 text-header-foreground backdrop-blur-md sm:px-6",
108
+ className
109
+ ),
110
+ ...props,
111
+ children
112
+ }
113
+ );
114
+ }
115
+ function HeaderActions({ className, children, ...props }) {
116
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("ms-auto flex items-center gap-1", className), ...props, children });
117
+ }
118
+
119
+ // src/components/button/buttonVariants.ts
120
+ var buttonVariantClass = {
121
+ primary: "bg-primary text-primary-foreground shadow-sm hover:bg-primary/90 focus-visible:ring-primary/40",
122
+ secondary: "bg-muted text-foreground hover:bg-muted/80 focus-visible:ring-muted-foreground/30",
123
+ outline: "border border-input bg-background text-foreground hover:bg-accent hover:text-accent-foreground focus-visible:ring-ring/40",
124
+ ghost: "bg-transparent text-foreground hover:bg-accent hover:text-accent-foreground focus-visible:ring-ring/40",
125
+ destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90 focus-visible:ring-destructive/40",
126
+ success: "bg-success text-success-foreground shadow-sm hover:bg-success/90 focus-visible:ring-success/40",
127
+ warning: "bg-warning text-warning-foreground shadow-sm hover:bg-warning/90 focus-visible:ring-warning/40",
128
+ link: "text-primary underline-offset-4 hover:underline focus-visible:ring-primary/40 px-0 shadow-none"
129
+ };
130
+ var buttonSizeClass = {
131
+ sm: "h-8 gap-1.5 rounded-md px-3 text-sm",
132
+ md: "h-9 gap-2 rounded-md px-4 text-sm",
133
+ lg: "h-11 gap-2.5 rounded-md px-6 text-base",
134
+ icon: "h-9 w-9 shrink-0 rounded-md p-0",
135
+ "icon-sm": "h-8 w-8 shrink-0 rounded-md p-0"
136
+ };
137
+ var buttonBaseClass = "group/button relative inline-flex items-center justify-center font-medium select-none whitespace-nowrap outline-none transition-[background-color,color,box-shadow,opacity] focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0";
138
+ var Button = react.forwardRef(function Button2({
139
+ variant = "primary",
140
+ size = "md",
141
+ loading = false,
142
+ loadingText,
143
+ leadingIcon,
144
+ trailingIcon,
145
+ fullWidth = false,
146
+ asChild = false,
147
+ type,
148
+ disabled,
149
+ className,
150
+ children,
151
+ ...props
152
+ }, ref) {
153
+ const isDisabled = disabled || loading;
154
+ const composedClass = cn(
155
+ buttonBaseClass,
156
+ buttonVariantClass[variant],
157
+ buttonSizeClass[size],
158
+ fullWidth && "w-full",
159
+ className
160
+ );
161
+ const content = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
162
+ loading ? /* @__PURE__ */ jsxRuntime.jsx(Spinner, {}) : leadingIcon ? /* @__PURE__ */ jsxRuntime.jsx(Slot, { children: leadingIcon }) : null,
163
+ loading && loadingText !== void 0 ? loadingText : children,
164
+ !loading && trailingIcon ? /* @__PURE__ */ jsxRuntime.jsx(Slot, { children: trailingIcon }) : null
165
+ ] });
166
+ if (asChild) {
167
+ const child = react.Children.only(children);
168
+ if (!react.isValidElement(child)) {
169
+ throw new Error("Button: `asChild` requires a single valid React element as a child.");
170
+ }
171
+ const mergedClassName = cn(composedClass, child.props.className);
172
+ return react.cloneElement(child, {
173
+ ...child.props,
174
+ className: mergedClassName,
175
+ "aria-disabled": isDisabled ? true : void 0,
176
+ "data-loading": loading ? "true" : void 0,
177
+ children: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
178
+ loading ? /* @__PURE__ */ jsxRuntime.jsx(Spinner, {}) : leadingIcon ? /* @__PURE__ */ jsxRuntime.jsx(Slot, { children: leadingIcon }) : null,
179
+ loading && loadingText !== void 0 ? loadingText : child.props.children,
180
+ !loading && trailingIcon ? /* @__PURE__ */ jsxRuntime.jsx(Slot, { children: trailingIcon }) : null
181
+ ] })
182
+ });
183
+ }
184
+ return /* @__PURE__ */ jsxRuntime.jsx(
185
+ "button",
186
+ {
187
+ ref,
188
+ type: type ?? "button",
189
+ disabled: isDisabled,
190
+ "data-loading": loading ? "true" : void 0,
191
+ className: composedClass,
192
+ ...props,
193
+ children: content
194
+ }
195
+ );
196
+ });
197
+ function Slot({ children }) {
198
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", className: "inline-flex h-4 w-4 items-center justify-center", children });
199
+ }
200
+ function Spinner() {
201
+ return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { "aria-hidden": "true", className: "h-4 w-4 animate-spin", "data-testid": "button-spinner" });
202
+ }
203
+ function HeaderCollapseTrigger({
204
+ icon,
205
+ className,
206
+ onClick,
207
+ variant = "ghost",
208
+ size = "icon",
209
+ "aria-label": ariaLabel = "Toggle sidebar",
210
+ ...props
211
+ }) {
212
+ const { toggleCollapsed, collapsed } = useDashboardLayout();
213
+ return /* @__PURE__ */ jsxRuntime.jsx(
214
+ Button,
215
+ {
216
+ "aria-label": ariaLabel,
217
+ "aria-pressed": collapsed,
218
+ variant,
219
+ size,
220
+ onClick: (e) => {
221
+ toggleCollapsed();
222
+ onClick?.(e);
223
+ },
224
+ className: cn("hidden lg:inline-flex", className),
225
+ ...props,
226
+ children: icon ?? /* @__PURE__ */ jsxRuntime.jsx(ChevronIcon, { collapsed })
227
+ }
228
+ );
229
+ }
230
+ function ChevronIcon({ collapsed }) {
231
+ return /* @__PURE__ */ jsxRuntime.jsx(
232
+ lucideReact.ChevronLeft,
233
+ {
234
+ "aria-hidden": "true",
235
+ className: cn(
236
+ "h-[18px] w-[18px] transition-transform duration-200 rtl:-scale-x-100",
237
+ collapsed && "rotate-180"
238
+ )
239
+ }
240
+ );
241
+ }
242
+ function HeaderMobileTrigger({
243
+ icon,
244
+ className,
245
+ onClick,
246
+ variant = "ghost",
247
+ size = "icon",
248
+ "aria-label": ariaLabel = "Toggle navigation",
249
+ ...props
250
+ }) {
251
+ const { toggleMobileOpen, mobileOpen } = useDashboardLayout();
252
+ return /* @__PURE__ */ jsxRuntime.jsx(
253
+ Button,
254
+ {
255
+ "aria-label": ariaLabel,
256
+ "aria-expanded": mobileOpen,
257
+ variant,
258
+ size,
259
+ onClick: (e) => {
260
+ toggleMobileOpen();
261
+ onClick?.(e);
262
+ },
263
+ className: className ? `lg:hidden ${className}` : "lg:hidden",
264
+ ...props,
265
+ children: icon ?? /* @__PURE__ */ jsxRuntime.jsx(DefaultMenuIcon, {})
266
+ }
267
+ );
268
+ }
269
+ function DefaultMenuIcon() {
270
+ return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Menu, { "aria-hidden": "true", className: "h-[18px] w-[18px]" });
271
+ }
272
+ var HeaderSearch = react.forwardRef(
273
+ ({ icon, containerClassName, className, type = "search", ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsxs(
274
+ "div",
275
+ {
276
+ className: cn("relative hidden h-9 w-full max-w-sm items-center md:flex", containerClassName),
277
+ children: [
278
+ icon ? /* @__PURE__ */ jsxRuntime.jsx(
279
+ "span",
280
+ {
281
+ "aria-hidden": "true",
282
+ className: "pointer-events-none absolute start-3 flex h-4 w-4 items-center justify-center text-muted-foreground",
283
+ children: icon
284
+ }
285
+ ) : null,
286
+ /* @__PURE__ */ jsxRuntime.jsx(
287
+ "input",
288
+ {
289
+ ref,
290
+ type,
291
+ className: cn(
292
+ "h-9 w-full rounded-md border border-input bg-background text-sm transition-colors",
293
+ "placeholder:text-muted-foreground",
294
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
295
+ icon ? "ps-9 pe-3" : "px-3",
296
+ className
297
+ ),
298
+ ...props
299
+ }
300
+ )
301
+ ]
302
+ }
303
+ )
304
+ );
305
+ HeaderSearch.displayName = "HeaderSearch";
306
+ function HeaderTitle({ className, children, ...props }) {
307
+ return /* @__PURE__ */ jsxRuntime.jsx(
308
+ "div",
309
+ {
310
+ className: cn("flex min-w-0 flex-1 items-center gap-2 text-base font-semibold", className),
311
+ ...props,
312
+ children
313
+ }
314
+ );
315
+ }
316
+ function Sidebar({ className, children, ...props }) {
317
+ const { collapsed, mobileOpen, setMobileOpen } = useDashboardLayout();
318
+ react.useEffect(() => {
319
+ if (!mobileOpen) return;
320
+ const onKey = (e) => {
321
+ if (e.key === "Escape") setMobileOpen(false);
322
+ };
323
+ document.addEventListener("keydown", onKey);
324
+ return () => document.removeEventListener("keydown", onKey);
325
+ }, [mobileOpen, setMobileOpen]);
326
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
327
+ /* @__PURE__ */ jsxRuntime.jsx(
328
+ "div",
329
+ {
330
+ "aria-hidden": "true",
331
+ onClick: () => setMobileOpen(false),
332
+ className: cn(
333
+ "fixed inset-0 z-30 bg-foreground/40 backdrop-blur-sm transition-opacity duration-200 lg:hidden",
334
+ mobileOpen ? "opacity-100" : "pointer-events-none opacity-0"
335
+ )
336
+ }
337
+ ),
338
+ /* @__PURE__ */ jsxRuntime.jsx(
339
+ "aside",
340
+ {
341
+ "data-collapsed": collapsed ? "true" : "false",
342
+ "data-mobile-open": mobileOpen ? "true" : "false",
343
+ className: cn(
344
+ // Positioning
345
+ "fixed inset-y-0 start-0 z-40 flex flex-col",
346
+ // Surface
347
+ "bg-sidebar text-sidebar-foreground border-e border-sidebar-border",
348
+ // Sizing — width animates between full and collapsed
349
+ collapsed ? "w-[var(--sidebar-width-collapsed)]" : "w-[var(--sidebar-width)]",
350
+ // Motion
351
+ "transition-[transform,width] duration-200 ease-out",
352
+ // Mobile slide: hidden by default, visible when mobileOpen.
353
+ // Logical translate via rtl variant so it slides off the inline-start edge
354
+ // in both LTR and RTL.
355
+ mobileOpen ? "translate-x-0" : "-translate-x-full rtl:translate-x-full lg:translate-x-0 lg:rtl:translate-x-0",
356
+ className
357
+ ),
358
+ ...props,
359
+ children
360
+ }
361
+ )
362
+ ] });
363
+ }
364
+ function SidebarFooter({ className, children, ...props }) {
365
+ return /* @__PURE__ */ jsxRuntime.jsx(
366
+ "div",
367
+ {
368
+ className: cn(
369
+ "mt-auto flex shrink-0 items-center gap-2 border-t border-sidebar-border p-3",
370
+ className
371
+ ),
372
+ ...props,
373
+ children
374
+ }
375
+ );
376
+ }
377
+ function SidebarGroup({ label, className, children, ...props }) {
378
+ const { collapsed } = useDashboardLayout();
379
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex flex-col gap-1 py-2", className), ...props, children: [
380
+ label ? /* @__PURE__ */ jsxRuntime.jsx(
381
+ "div",
382
+ {
383
+ className: cn(
384
+ "px-3 pb-1 text-xs font-medium uppercase tracking-wider text-muted-foreground transition-opacity",
385
+ collapsed && "pointer-events-none h-0 overflow-hidden opacity-0"
386
+ ),
387
+ children: label
388
+ }
389
+ ) : null,
390
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-0.5", children })
391
+ ] });
392
+ }
393
+ function SidebarHeader({ className, children, ...props }) {
394
+ return /* @__PURE__ */ jsxRuntime.jsx(
395
+ "div",
396
+ {
397
+ className: cn(
398
+ "flex h-[var(--header-height)] shrink-0 items-center gap-2 border-b border-sidebar-border px-3",
399
+ className
400
+ ),
401
+ ...props,
402
+ children
403
+ }
404
+ );
405
+ }
406
+ function SidebarNav({ className, children, ...props }) {
407
+ return /* @__PURE__ */ jsxRuntime.jsx("nav", { className: cn("flex flex-1 flex-col gap-1 overflow-y-auto p-2", className), ...props, children });
408
+ }
409
+ function SidebarNavGroup({
410
+ icon,
411
+ label,
412
+ endSlot,
413
+ active = false,
414
+ defaultOpen = false,
415
+ open: openProp,
416
+ onOpenChange,
417
+ className,
418
+ children,
419
+ onClick,
420
+ ...props
421
+ }) {
422
+ const { collapsed } = useDashboardLayout();
423
+ const submenuId = react.useId();
424
+ const [internalOpen, setInternalOpen] = react.useState(defaultOpen);
425
+ const isControlled = openProp !== void 0;
426
+ const open = isControlled ? openProp : internalOpen;
427
+ const setOpen = react.useCallback(
428
+ (next) => {
429
+ if (!isControlled) setInternalOpen(next);
430
+ onOpenChange?.(next);
431
+ },
432
+ [isControlled, onOpenChange]
433
+ );
434
+ react.useEffect(() => {
435
+ if (collapsed && open) setOpen(false);
436
+ }, [collapsed, open, setOpen]);
437
+ const titleAttr = collapsed && typeof label === "string" ? label : props.title ?? void 0;
438
+ const showChildren = !collapsed;
439
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col", children: [
440
+ /* @__PURE__ */ jsxRuntime.jsxs(
441
+ "button",
442
+ {
443
+ type: "button",
444
+ "aria-expanded": showChildren ? open : void 0,
445
+ "aria-controls": showChildren ? submenuId : void 0,
446
+ "data-active": active ? "true" : void 0,
447
+ title: titleAttr,
448
+ onClick: (e) => {
449
+ if (showChildren) setOpen(!open);
450
+ onClick?.(e);
451
+ },
452
+ className: cn(
453
+ "group relative flex h-9 w-full items-center gap-3 rounded-md px-3 text-sm font-medium outline-none transition-colors",
454
+ "text-sidebar-foreground/80 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
455
+ "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-sidebar",
456
+ active && "bg-sidebar-accent text-sidebar-accent-foreground",
457
+ collapsed && "justify-center px-0",
458
+ className
459
+ ),
460
+ ...props,
461
+ children: [
462
+ icon ? /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", className: "flex h-5 w-5 shrink-0 items-center justify-center", children: icon }) : null,
463
+ /* @__PURE__ */ jsxRuntime.jsx(
464
+ "span",
465
+ {
466
+ className: cn(
467
+ "flex-1 truncate text-start transition-[opacity,width]",
468
+ collapsed && "pointer-events-none w-0 opacity-0"
469
+ ),
470
+ children: label
471
+ }
472
+ ),
473
+ endSlot && !collapsed ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex shrink-0 items-center", children: endSlot }) : null,
474
+ showChildren ? /* @__PURE__ */ jsxRuntime.jsx(ChevronCaret, { open }) : null
475
+ ]
476
+ }
477
+ ),
478
+ /* @__PURE__ */ jsxRuntime.jsx(
479
+ "div",
480
+ {
481
+ id: submenuId,
482
+ hidden: !showChildren || !open,
483
+ className: cn(
484
+ "grid transition-[grid-template-rows] duration-200 ease-out",
485
+ showChildren && open ? "grid-rows-[1fr]" : "grid-rows-[0fr]"
486
+ ),
487
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-0.5 ps-7 pt-1", children }) })
488
+ }
489
+ )
490
+ ] });
491
+ }
492
+ function ChevronCaret({ open }) {
493
+ return /* @__PURE__ */ jsxRuntime.jsx(
494
+ lucideReact.ChevronDown,
495
+ {
496
+ "aria-hidden": "true",
497
+ className: cn(
498
+ "h-3.5 w-3.5 shrink-0 text-muted-foreground transition-transform duration-200",
499
+ open && "rotate-180"
500
+ )
501
+ }
502
+ );
503
+ }
504
+ function SidebarNavItem({
505
+ icon,
506
+ active = false,
507
+ label,
508
+ endSlot,
509
+ className,
510
+ children,
511
+ render,
512
+ ...props
513
+ }) {
514
+ const { collapsed } = useDashboardLayout();
515
+ const labelContent = label ?? children;
516
+ const titleAttr = collapsed && typeof labelContent === "string" ? labelContent : props.title;
517
+ const inner = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
518
+ icon ? /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", className: "flex h-5 w-5 shrink-0 items-center justify-center", children: icon }) : null,
519
+ /* @__PURE__ */ jsxRuntime.jsx(
520
+ "span",
521
+ {
522
+ className: cn(
523
+ "flex-1 truncate text-start transition-[opacity,width]",
524
+ collapsed && "pointer-events-none w-0 opacity-0"
525
+ ),
526
+ children: labelContent
527
+ }
528
+ ),
529
+ endSlot && !collapsed ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ms-auto flex shrink-0 items-center", children: endSlot }) : null
530
+ ] });
531
+ const computedClass = cn(
532
+ "group relative flex h-9 items-center gap-3 rounded-md px-3 text-sm font-medium outline-none transition-colors",
533
+ "text-sidebar-foreground/80 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
534
+ "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-sidebar",
535
+ active && "bg-sidebar-accent text-sidebar-accent-foreground",
536
+ collapsed && "justify-center px-0",
537
+ className
538
+ );
539
+ if (render) {
540
+ return render({
541
+ className: computedClass,
542
+ children: inner,
543
+ title: titleAttr,
544
+ "aria-current": active ? "page" : void 0,
545
+ "data-active": active ? "true" : void 0
546
+ });
547
+ }
548
+ return /* @__PURE__ */ jsxRuntime.jsx(
549
+ "a",
550
+ {
551
+ "aria-current": active ? "page" : void 0,
552
+ "data-active": active ? "true" : void 0,
553
+ title: titleAttr,
554
+ className: computedClass,
555
+ ...props,
556
+ children: inner
557
+ }
558
+ );
559
+ }
560
+ function isSection(entry) {
561
+ return "items" in entry && !("label" in entry && "href" in entry) && Array.isArray(entry.items);
562
+ }
563
+ function isGroup(entry) {
564
+ return "items" in entry;
565
+ }
566
+ function normalize(nav) {
567
+ if (nav.length === 0) return [];
568
+ if (isSection(nav[0])) return nav;
569
+ return [{ key: "__root", items: nav }];
570
+ }
571
+ function renderItem(item) {
572
+ if (item.render) {
573
+ return /* @__PURE__ */ jsxRuntime.jsx(
574
+ SidebarNavItem,
575
+ {
576
+ icon: item.icon,
577
+ active: item.active,
578
+ endSlot: item.endSlot,
579
+ render: item.render,
580
+ children: item.label
581
+ },
582
+ item.key
583
+ );
584
+ }
585
+ return /* @__PURE__ */ jsxRuntime.jsx(
586
+ SidebarNavItem,
587
+ {
588
+ href: item.href,
589
+ icon: item.icon,
590
+ active: item.active,
591
+ endSlot: item.endSlot,
592
+ children: item.label
593
+ },
594
+ item.key
595
+ );
596
+ }
597
+ function AppShell({
598
+ brand,
599
+ nav,
600
+ title,
601
+ searchPlaceholder,
602
+ onSearch,
603
+ headerActions,
604
+ sidebarFooter,
605
+ defaultCollapsed,
606
+ collapsed,
607
+ onCollapsedChange,
608
+ children
609
+ }) {
610
+ const sections = normalize(nav);
611
+ return /* @__PURE__ */ jsxRuntime.jsxs(
612
+ DashboardLayout,
613
+ {
614
+ defaultCollapsed,
615
+ collapsed,
616
+ onCollapsedChange,
617
+ children: [
618
+ /* @__PURE__ */ jsxRuntime.jsxs(Sidebar, { children: [
619
+ (brand?.logo || brand?.name) && /* @__PURE__ */ jsxRuntime.jsxs(SidebarHeader, { children: [
620
+ brand.logo,
621
+ brand.name ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-sm font-semibold", children: brand.name }) : null
622
+ ] }),
623
+ /* @__PURE__ */ jsxRuntime.jsx(SidebarNav, { children: sections.map((section) => /* @__PURE__ */ jsxRuntime.jsx(SidebarGroup, { label: section.label, children: section.items.map(
624
+ (entry) => isGroup(entry) ? /* @__PURE__ */ jsxRuntime.jsx(
625
+ SidebarNavGroup,
626
+ {
627
+ label: entry.label,
628
+ icon: entry.icon,
629
+ active: entry.active,
630
+ defaultOpen: entry.defaultOpen ?? entry.items.some((i) => i.active),
631
+ children: entry.items.map(renderItem)
632
+ },
633
+ entry.key
634
+ ) : renderItem(entry)
635
+ ) }, section.key)) }),
636
+ sidebarFooter ? /* @__PURE__ */ jsxRuntime.jsx(SidebarFooter, { children: sidebarFooter }) : null
637
+ ] }),
638
+ /* @__PURE__ */ jsxRuntime.jsxs(DashboardMain, { children: [
639
+ /* @__PURE__ */ jsxRuntime.jsxs(DashboardHeader, { children: [
640
+ /* @__PURE__ */ jsxRuntime.jsx(HeaderMobileTrigger, {}),
641
+ /* @__PURE__ */ jsxRuntime.jsx(HeaderCollapseTrigger, {}),
642
+ title ? /* @__PURE__ */ jsxRuntime.jsx(HeaderTitle, { children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: title }) }) : null,
643
+ searchPlaceholder ? /* @__PURE__ */ jsxRuntime.jsx(
644
+ HeaderSearch,
645
+ {
646
+ placeholder: searchPlaceholder,
647
+ onChange: onSearch ? (e) => onSearch(e.target.value) : void 0
648
+ }
649
+ ) : null,
650
+ headerActions ? /* @__PURE__ */ jsxRuntime.jsx(HeaderActions, { children: headerActions }) : null
651
+ ] }),
652
+ /* @__PURE__ */ jsxRuntime.jsx(DashboardContent, { children })
653
+ ] })
654
+ ]
655
+ }
656
+ );
657
+ }
658
+ var sizeClass = {
659
+ sm: "h-7 w-7 text-xs",
660
+ md: "h-9 w-9 text-sm",
661
+ lg: "h-11 w-11 text-base"
662
+ };
663
+ function Avatar({ src, alt = "", fallback, size = "md", className, ...props }) {
664
+ const [errored, setErrored] = react.useState(false);
665
+ const showImage = Boolean(src) && !errored;
666
+ return /* @__PURE__ */ jsxRuntime.jsx(
667
+ "span",
668
+ {
669
+ className: cn(
670
+ "inline-flex shrink-0 items-center justify-center overflow-hidden rounded-full bg-muted text-muted-foreground font-medium select-none",
671
+ sizeClass[size],
672
+ className
673
+ ),
674
+ ...props,
675
+ children: showImage ? /* @__PURE__ */ jsxRuntime.jsx(
676
+ "img",
677
+ {
678
+ src,
679
+ alt,
680
+ onError: () => setErrored(true),
681
+ className: "h-full w-full object-cover"
682
+ }
683
+ ) : /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": !fallback, children: fallback ?? "?" })
684
+ }
685
+ );
686
+ }
687
+ var sizeClass2 = {
688
+ sm: "h-3.5 w-3.5",
689
+ md: "h-4 w-4"
690
+ };
691
+ var Checkbox = react.forwardRef(function Checkbox2({
692
+ checked,
693
+ defaultChecked,
694
+ indeterminate = false,
695
+ onCheckedChange,
696
+ onChange,
697
+ disabled,
698
+ size = "md",
699
+ className,
700
+ "aria-checked": ariaCheckedProp,
701
+ ...rest
702
+ }, forwardedRef) {
703
+ const inputRef = react.useRef(null);
704
+ react.useImperativeHandle(forwardedRef, () => inputRef.current, []);
705
+ react.useLayoutEffect(() => {
706
+ if (inputRef.current) {
707
+ inputRef.current.indeterminate = indeterminate;
708
+ }
709
+ }, [indeterminate]);
710
+ const ariaChecked = ariaCheckedProp ?? (indeterminate ? "mixed" : void 0);
711
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: cn("relative inline-flex shrink-0", sizeClass2[size], className), children: [
712
+ /* @__PURE__ */ jsxRuntime.jsx(
713
+ "input",
714
+ {
715
+ ref: inputRef,
716
+ type: "checkbox",
717
+ checked,
718
+ defaultChecked,
719
+ disabled,
720
+ "aria-checked": ariaChecked,
721
+ onChange: (event) => {
722
+ onChange?.(event);
723
+ onCheckedChange?.(event.currentTarget.checked);
724
+ },
725
+ className: cn(
726
+ "peer absolute inset-0 m-0 cursor-pointer appearance-none rounded-sm border border-input bg-background",
727
+ "transition-colors",
728
+ "checked:border-primary checked:bg-primary",
729
+ "indeterminate:border-primary indeterminate:bg-primary",
730
+ "hover:border-ring",
731
+ "disabled:cursor-not-allowed disabled:opacity-50",
732
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background"
733
+ ),
734
+ ...rest
735
+ }
736
+ ),
737
+ /* @__PURE__ */ jsxRuntime.jsx(
738
+ lucideReact.Check,
739
+ {
740
+ "aria-hidden": "true",
741
+ strokeWidth: 3,
742
+ className: "pointer-events-none absolute inset-0 m-auto h-3 w-3 text-primary-foreground opacity-0 peer-checked:opacity-100 peer-indeterminate:opacity-0"
743
+ }
744
+ ),
745
+ /* @__PURE__ */ jsxRuntime.jsx(
746
+ lucideReact.Minus,
747
+ {
748
+ "aria-hidden": "true",
749
+ strokeWidth: 3,
750
+ className: "pointer-events-none absolute inset-0 m-auto h-3 w-3 text-primary-foreground opacity-0 peer-indeterminate:opacity-100"
751
+ }
752
+ )
753
+ ] });
754
+ });
755
+ function readDocumentDirection() {
756
+ if (typeof document === "undefined") return "ltr";
757
+ const dir = document.documentElement.getAttribute("dir");
758
+ return dir === "rtl" ? "rtl" : "ltr";
759
+ }
760
+ function useDirection() {
761
+ const [dir, setDir] = react.useState(() => readDocumentDirection());
762
+ react.useEffect(() => {
763
+ setDir(readDocumentDirection());
764
+ const observer = new MutationObserver(() => {
765
+ setDir(readDocumentDirection());
766
+ });
767
+ observer.observe(document.documentElement, {
768
+ attributes: true,
769
+ attributeFilter: ["dir"]
770
+ });
771
+ return () => observer.disconnect();
772
+ }, []);
773
+ return dir;
774
+ }
775
+ function Pagination({
776
+ pageIndex,
777
+ pageSize,
778
+ pageCount,
779
+ totalRowCount,
780
+ pageSizeOptions,
781
+ onChange
782
+ }) {
783
+ const dir = useDirection();
784
+ const isRtl = dir === "rtl";
785
+ const isFirst = pageIndex <= 0;
786
+ const isLast = pageIndex >= pageCount - 1;
787
+ const goPrev = () => {
788
+ if (!isFirst) onChange({ pageIndex: pageIndex - 1, pageSize });
789
+ };
790
+ const goNext = () => {
791
+ if (!isLast) onChange({ pageIndex: pageIndex + 1, pageSize });
792
+ };
793
+ const start = totalRowCount === 0 ? 0 : pageIndex * pageSize + 1;
794
+ const end = Math.min(totalRowCount, (pageIndex + 1) * pageSize);
795
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3 text-sm text-muted-foreground", children: [
796
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex items-center gap-2", children: [
797
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Rows per page" }),
798
+ /* @__PURE__ */ jsxRuntime.jsx(
799
+ "select",
800
+ {
801
+ className: "h-8 rounded-md border border-input bg-background px-2 text-sm text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
802
+ value: pageSize,
803
+ onChange: (event) => {
804
+ const nextSize = Number(event.target.value);
805
+ onChange({ pageIndex: 0, pageSize: nextSize });
806
+ },
807
+ children: pageSizeOptions.map((option) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: option, children: option }, option))
808
+ }
809
+ )
810
+ ] }) }),
811
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
812
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { "aria-live": "polite", children: [
813
+ start,
814
+ "\u2013",
815
+ end,
816
+ " of ",
817
+ totalRowCount
818
+ ] }),
819
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
820
+ /* @__PURE__ */ jsxRuntime.jsx(
821
+ Button,
822
+ {
823
+ type: "button",
824
+ variant: "outline",
825
+ size: "sm",
826
+ disabled: isFirst,
827
+ onClick: goPrev,
828
+ "aria-label": "Previous page",
829
+ children: isRtl ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { "aria-hidden": "true", className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeft, { "aria-hidden": "true", className: "h-3.5 w-3.5" })
830
+ }
831
+ ),
832
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "px-1 text-foreground", children: [
833
+ pageIndex + 1,
834
+ " / ",
835
+ pageCount
836
+ ] }),
837
+ /* @__PURE__ */ jsxRuntime.jsx(
838
+ Button,
839
+ {
840
+ type: "button",
841
+ variant: "outline",
842
+ size: "sm",
843
+ disabled: isLast,
844
+ onClick: goNext,
845
+ "aria-label": "Next page",
846
+ children: isRtl ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeft, { "aria-hidden": "true", className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { "aria-hidden": "true", className: "h-3.5 w-3.5" })
847
+ }
848
+ )
849
+ ] })
850
+ ] })
851
+ ] });
852
+ }
853
+ function Toolbar({ count, onClear, renderLabel, clearLabel, children }) {
854
+ return /* @__PURE__ */ jsxRuntime.jsxs(
855
+ "div",
856
+ {
857
+ role: "toolbar",
858
+ "aria-label": "Bulk actions",
859
+ className: "flex flex-wrap items-center gap-3 rounded-md border border-border bg-muted/40 px-3 py-2 text-sm",
860
+ children: [
861
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-foreground", children: renderLabel ? renderLabel(count) : `${count} selected` }),
862
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ms-auto flex flex-wrap items-center gap-2", children: [
863
+ children,
864
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: onClear, children: clearLabel ?? "Clear" })
865
+ ] })
866
+ ]
867
+ }
868
+ );
869
+ }
870
+
871
+ // src/components/table/tableVariants.ts
872
+ var tableSizeClass = {
873
+ sm: {
874
+ row: "",
875
+ cell: "px-3 py-1.5 text-xs",
876
+ head: "px-3 py-2 text-xs font-medium"
877
+ },
878
+ md: {
879
+ row: "",
880
+ cell: "px-4 py-2.5 text-sm",
881
+ head: "px-4 py-2.5 text-xs font-medium uppercase tracking-wide"
882
+ },
883
+ lg: {
884
+ row: "",
885
+ cell: "px-5 py-3.5 text-sm",
886
+ head: "px-5 py-3 text-sm font-medium"
887
+ }
888
+ };
889
+ var tableBaseClass = "w-full caption-bottom border-collapse";
890
+ var selectedRowClass = "bg-muted/40";
891
+ var sortIconClass = "inline-flex h-3 w-3 shrink-0 items-center justify-center";
892
+ var alignClass = {
893
+ start: "text-start",
894
+ center: "text-center",
895
+ end: "text-end"
896
+ };
897
+ var EMPTY_SELECTION = /* @__PURE__ */ new Set();
898
+ var NO_SORT = { columnId: null, direction: "asc" };
899
+ function useTableState(props) {
900
+ const {
901
+ defaultSort,
902
+ sort: sortProp,
903
+ onSortChange,
904
+ defaultPagination,
905
+ pagination: paginationProp,
906
+ onPaginationChange,
907
+ pageSizeOptions,
908
+ defaultSelectedRowIds,
909
+ selectedRowIds: selectedRowIdsProp,
910
+ onSelectedRowIdsChange,
911
+ totalCount
912
+ } = props;
913
+ const [internalSort, setInternalSort] = react.useState(defaultSort ?? NO_SORT);
914
+ const isSortControlled = sortProp !== void 0;
915
+ const sort = isSortControlled ? sortProp : internalSort;
916
+ const setSort = react.useCallback(
917
+ (next) => {
918
+ if (!isSortControlled) setInternalSort(next);
919
+ onSortChange?.(next);
920
+ },
921
+ [isSortControlled, onSortChange]
922
+ );
923
+ const [internalPagination, setInternalPagination] = react.useState(
924
+ defaultPagination ?? { pageIndex: 0, pageSize: pageSizeOptions?.[0] ?? 10 }
925
+ );
926
+ const isPaginationControlled = paginationProp !== void 0;
927
+ const pagination = isPaginationControlled ? paginationProp : internalPagination;
928
+ const setPagination = react.useCallback(
929
+ (next) => {
930
+ if (!isPaginationControlled) setInternalPagination(next);
931
+ onPaginationChange?.(next);
932
+ },
933
+ [isPaginationControlled, onPaginationChange]
934
+ );
935
+ const [internalSelected, setInternalSelected] = react.useState(
936
+ defaultSelectedRowIds ?? EMPTY_SELECTION
937
+ );
938
+ const isSelectionControlled = selectedRowIdsProp !== void 0;
939
+ const selected = isSelectionControlled ? selectedRowIdsProp : internalSelected;
940
+ const setSelected = react.useCallback(
941
+ (next) => {
942
+ if (!isSelectionControlled) setInternalSelected(next);
943
+ onSelectedRowIdsChange?.(next);
944
+ },
945
+ [isSelectionControlled, onSelectedRowIdsChange]
946
+ );
947
+ return {
948
+ sort,
949
+ setSort,
950
+ pagination,
951
+ setPagination,
952
+ selected,
953
+ setSelected,
954
+ isServerSide: totalCount !== void 0
955
+ };
956
+ }
957
+ var DEFAULT_PAGE_SIZE_OPTIONS = [10, 25, 50];
958
+ function Table(props) {
959
+ const {
960
+ data,
961
+ columns,
962
+ getRowId,
963
+ enableRowSelection = false,
964
+ isRowSelectable,
965
+ bulkActions,
966
+ renderSelectionLabel,
967
+ clearSelectionLabel,
968
+ loading = false,
969
+ loadingRowCount,
970
+ emptyState,
971
+ size = "md",
972
+ className,
973
+ tableClassName,
974
+ maxHeight,
975
+ striped = false,
976
+ onRowClick,
977
+ tableRef,
978
+ pageSizeOptions = DEFAULT_PAGE_SIZE_OPTIONS,
979
+ showPagination,
980
+ caption
981
+ } = props;
982
+ const ariaLabel = props["aria-label"];
983
+ const ariaLabelledBy = props["aria-labelledby"];
984
+ const { sort, setSort, pagination, setPagination, selected, setSelected, isServerSide } = useTableState(props);
985
+ const sortedRows = react.useMemo(() => {
986
+ if (isServerSide || sort.columnId === null) return data;
987
+ const col = columns.find((c) => c.id === sort.columnId);
988
+ if (!col) return data;
989
+ const get = resolveSortGetter(col);
990
+ if (!get) return data;
991
+ const tagged = data.map((row, index) => ({ row, index }));
992
+ tagged.sort((a, b) => {
993
+ const cmp = compareValues(get(a.row), get(b.row));
994
+ if (cmp !== 0) return sort.direction === "asc" ? cmp : -cmp;
995
+ return a.index - b.index;
996
+ });
997
+ return tagged.map((entry) => entry.row);
998
+ }, [data, columns, sort, isServerSide]);
999
+ const pagedRows = react.useMemo(() => {
1000
+ if (isServerSide) return sortedRows;
1001
+ const start = pagination.pageIndex * pagination.pageSize;
1002
+ return sortedRows.slice(start, start + pagination.pageSize);
1003
+ }, [sortedRows, pagination, isServerSide]);
1004
+ const totalRowCount = isServerSide ? props.totalCount ?? 0 : data.length;
1005
+ const pageCount = Math.max(1, Math.ceil(totalRowCount / pagination.pageSize));
1006
+ const selectableRowIds = react.useMemo(() => {
1007
+ if (!enableRowSelection) return [];
1008
+ return pagedRows.map((row, index) => ({ row, index })).filter(({ row }) => isRowSelectable ? isRowSelectable(row) : true).map(({ row, index }) => getRowId(row, index));
1009
+ }, [pagedRows, enableRowSelection, isRowSelectable, getRowId]);
1010
+ const selectedOnPageCount = selectableRowIds.reduce(
1011
+ (acc, id) => selected.has(id) ? acc + 1 : acc,
1012
+ 0
1013
+ );
1014
+ const allOnPageSelected = selectableRowIds.length > 0 && selectedOnPageCount === selectableRowIds.length;
1015
+ const someOnPageSelected = selectedOnPageCount > 0 && !allOnPageSelected;
1016
+ const selectedRowsInData = react.useMemo(() => {
1017
+ if (selected.size === 0) return [];
1018
+ return data.filter((row, index) => selected.has(getRowId(row, index)));
1019
+ }, [data, selected, getRowId]);
1020
+ const toggleHeader = (next) => {
1021
+ const updated = new Set(selected);
1022
+ if (next) {
1023
+ for (const id of selectableRowIds) updated.add(id);
1024
+ } else {
1025
+ for (const id of selectableRowIds) updated.delete(id);
1026
+ }
1027
+ setSelected(updated);
1028
+ };
1029
+ const toggleRow = (id, next) => {
1030
+ const updated = new Set(selected);
1031
+ if (next) updated.add(id);
1032
+ else updated.delete(id);
1033
+ setSelected(updated);
1034
+ };
1035
+ const clearSelection = () => setSelected(/* @__PURE__ */ new Set());
1036
+ const handleSortClick = (columnId) => {
1037
+ setSort(nextSort(sort, columnId));
1038
+ };
1039
+ const totalColumnCount = columns.length + (enableRowSelection ? 1 : 0);
1040
+ const paginationVisible = showPagination ?? totalRowCount > pagination.pageSize;
1041
+ const sizeClasses = tableSizeClass[size];
1042
+ const showToolbar = enableRowSelection && bulkActions !== void 0 && selected.size > 0;
1043
+ const skeletonCount = loadingRowCount ?? pagination.pageSize;
1044
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex w-full flex-col gap-3", className), children: [
1045
+ showToolbar && /* @__PURE__ */ jsxRuntime.jsx(
1046
+ Toolbar,
1047
+ {
1048
+ count: selected.size,
1049
+ onClear: clearSelection,
1050
+ renderLabel: renderSelectionLabel,
1051
+ clearLabel: clearSelectionLabel,
1052
+ children: bulkActions(selectedRowsInData)
1053
+ }
1054
+ ),
1055
+ /* @__PURE__ */ jsxRuntime.jsx(
1056
+ "div",
1057
+ {
1058
+ className: cn(
1059
+ "overflow-x-auto rounded-md border border-border bg-background",
1060
+ maxHeight !== void 0 && "overflow-y-auto"
1061
+ ),
1062
+ style: maxHeight !== void 0 ? { maxHeight } : void 0,
1063
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
1064
+ "table",
1065
+ {
1066
+ ref: tableRef,
1067
+ "aria-label": ariaLabel,
1068
+ "aria-labelledby": ariaLabelledBy,
1069
+ "aria-rowcount": totalRowCount,
1070
+ className: cn(tableBaseClass, "text-sm text-foreground", tableClassName),
1071
+ children: [
1072
+ caption ? /* @__PURE__ */ jsxRuntime.jsx("caption", { className: "sr-only", children: caption }) : null,
1073
+ /* @__PURE__ */ jsxRuntime.jsx(
1074
+ "thead",
1075
+ {
1076
+ className: cn(
1077
+ "bg-muted/40 text-muted-foreground",
1078
+ maxHeight !== void 0 && "sticky top-0 z-10"
1079
+ ),
1080
+ children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
1081
+ enableRowSelection ? /* @__PURE__ */ jsxRuntime.jsx("th", { scope: "col", className: cn("w-10", sizeClasses.head), children: /* @__PURE__ */ jsxRuntime.jsx(
1082
+ Checkbox,
1083
+ {
1084
+ "aria-label": "Select all rows on this page",
1085
+ checked: allOnPageSelected,
1086
+ indeterminate: someOnPageSelected,
1087
+ disabled: selectableRowIds.length === 0,
1088
+ onCheckedChange: toggleHeader
1089
+ }
1090
+ ) }) : null,
1091
+ columns.map((column) => {
1092
+ const isSorted = sort.columnId === column.id;
1093
+ const ariaSort = isSorted ? sort.direction === "asc" ? "ascending" : "descending" : "none";
1094
+ return /* @__PURE__ */ jsxRuntime.jsx(
1095
+ "th",
1096
+ {
1097
+ scope: "col",
1098
+ "aria-sort": column.sortable ? ariaSort : void 0,
1099
+ className: cn(
1100
+ sizeClasses.head,
1101
+ alignClass[column.align ?? "start"],
1102
+ column.className
1103
+ ),
1104
+ children: column.sortable ? /* @__PURE__ */ jsxRuntime.jsxs(
1105
+ "button",
1106
+ {
1107
+ type: "button",
1108
+ onClick: () => handleSortClick(column.id),
1109
+ className: "inline-flex items-center gap-1.5 font-inherit uppercase tracking-inherit text-inherit hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background",
1110
+ "aria-label": sortAriaLabel(column, sort),
1111
+ children: [
1112
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: renderHeader(column.header) }),
1113
+ /* @__PURE__ */ jsxRuntime.jsx(
1114
+ SortIndicator,
1115
+ {
1116
+ active: isSorted,
1117
+ direction: isSorted ? sort.direction : null
1118
+ }
1119
+ )
1120
+ ]
1121
+ }
1122
+ ) : renderHeader(column.header)
1123
+ },
1124
+ column.id
1125
+ );
1126
+ })
1127
+ ] })
1128
+ }
1129
+ ),
1130
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: loading ? /* @__PURE__ */ jsxRuntime.jsx(
1131
+ SkeletonRows,
1132
+ {
1133
+ rowCount: skeletonCount,
1134
+ columnCount: totalColumnCount,
1135
+ cellClassName: sizeClasses.cell
1136
+ }
1137
+ ) : pagedRows.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("tr", { children: /* @__PURE__ */ jsxRuntime.jsx(
1138
+ "td",
1139
+ {
1140
+ colSpan: totalColumnCount,
1141
+ className: cn(sizeClasses.cell, "py-10 text-center text-muted-foreground"),
1142
+ children: emptyState ?? "No data"
1143
+ }
1144
+ ) }) : pagedRows.map((row, rowIndex) => {
1145
+ const id = getRowId(row, rowIndex);
1146
+ const isSelected = selected.has(id);
1147
+ const rowSelectable = isRowSelectable ? isRowSelectable(row) : true;
1148
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1149
+ "tr",
1150
+ {
1151
+ "data-selected": isSelected ? "true" : void 0,
1152
+ "aria-selected": enableRowSelection ? isSelected : void 0,
1153
+ className: cn(
1154
+ "border-t border-border transition-colors",
1155
+ "hover:bg-accent",
1156
+ striped && rowIndex % 2 === 1 && "bg-muted/20",
1157
+ isSelected && selectedRowClass,
1158
+ onRowClick && "cursor-pointer"
1159
+ ),
1160
+ onClick: onRowClick ? () => onRowClick(row, rowIndex) : void 0,
1161
+ children: [
1162
+ enableRowSelection ? /* @__PURE__ */ jsxRuntime.jsx("td", { className: cn(sizeClasses.cell, "w-10"), children: /* @__PURE__ */ jsxRuntime.jsx(
1163
+ Checkbox,
1164
+ {
1165
+ "aria-label": `Select row ${rowIndex + 1}`,
1166
+ checked: isSelected,
1167
+ disabled: !rowSelectable,
1168
+ onCheckedChange: (next) => toggleRow(id, next),
1169
+ onClick: stopRowClickPropagation
1170
+ }
1171
+ ) }) : null,
1172
+ columns.map((column) => /* @__PURE__ */ jsxRuntime.jsx(
1173
+ "td",
1174
+ {
1175
+ className: cn(
1176
+ sizeClasses.cell,
1177
+ alignClass[column.align ?? "start"],
1178
+ column.className
1179
+ ),
1180
+ children: renderCell(column, row, rowIndex)
1181
+ },
1182
+ column.id
1183
+ ))
1184
+ ]
1185
+ },
1186
+ id
1187
+ );
1188
+ }) })
1189
+ ]
1190
+ }
1191
+ )
1192
+ }
1193
+ ),
1194
+ paginationVisible ? /* @__PURE__ */ jsxRuntime.jsx(
1195
+ Pagination,
1196
+ {
1197
+ pageIndex: pagination.pageIndex,
1198
+ pageSize: pagination.pageSize,
1199
+ pageCount,
1200
+ totalRowCount,
1201
+ pageSizeOptions,
1202
+ onChange: setPagination
1203
+ }
1204
+ ) : null
1205
+ ] });
1206
+ }
1207
+ function renderHeader(header) {
1208
+ return typeof header === "function" ? header() : header;
1209
+ }
1210
+ function renderCell(column, row, rowIndex) {
1211
+ if (column.render) return column.render(row, rowIndex);
1212
+ if (column.accessor !== void 0) {
1213
+ const value = row[column.accessor];
1214
+ return value === null || value === void 0 ? "" : String(value);
1215
+ }
1216
+ return null;
1217
+ }
1218
+ function resolveSortGetter(column) {
1219
+ if (column.sortAccessor) return column.sortAccessor;
1220
+ if (column.accessor !== void 0) {
1221
+ const key = column.accessor;
1222
+ return (row) => row[key];
1223
+ }
1224
+ return null;
1225
+ }
1226
+ function compareValues(a, b) {
1227
+ if (a === b) return 0;
1228
+ if (a === null || a === void 0) return 1;
1229
+ if (b === null || b === void 0) return -1;
1230
+ if (a instanceof Date && b instanceof Date) return a.getTime() - b.getTime();
1231
+ if (typeof a === "number" && typeof b === "number") return a - b;
1232
+ if (typeof a === "bigint" && typeof b === "bigint") return a < b ? -1 : 1;
1233
+ if (typeof a === "boolean" && typeof b === "boolean") return a === b ? 0 : a ? 1 : -1;
1234
+ return String(a).localeCompare(String(b), void 0, { numeric: true, sensitivity: "base" });
1235
+ }
1236
+ function nextSort(current, columnId) {
1237
+ if (current.columnId !== columnId) return { columnId, direction: "asc" };
1238
+ if (current.direction === "asc") return { columnId, direction: "desc" };
1239
+ return { columnId: null, direction: "asc" };
1240
+ }
1241
+ function sortAriaLabel(column, sort) {
1242
+ const headerText = typeof column.header === "string" ? column.header : column.id;
1243
+ if (sort.columnId !== column.id) return `Sort by ${headerText}`;
1244
+ return sort.direction === "asc" ? `Sort by ${headerText}, currently ascending` : `Sort by ${headerText}, currently descending`;
1245
+ }
1246
+ function stopRowClickPropagation(event) {
1247
+ event.stopPropagation();
1248
+ }
1249
+ function SkeletonRows({ rowCount, columnCount, cellClassName }) {
1250
+ const rowKeys = Array.from({ length: Math.max(0, rowCount) }, (_, i) => `skeleton-row-${i}`);
1251
+ const colKeys = Array.from({ length: Math.max(1, columnCount) }, (_, i) => `skeleton-col-${i}`);
1252
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: rowKeys.map((rowKey) => /* @__PURE__ */ jsxRuntime.jsx("tr", { className: "border-t border-border", "data-testid": "table-skeleton-row", children: colKeys.map((colKey) => /* @__PURE__ */ jsxRuntime.jsx("td", { className: cellClassName, children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "block h-3 w-full animate-pulse rounded bg-muted" }) }, `${rowKey}-${colKey}`)) }, rowKey)) });
1253
+ }
1254
+ function SortIndicator({ active, direction }) {
1255
+ const className = cn(
1256
+ "h-3.5 w-3.5 shrink-0",
1257
+ active ? "text-foreground" : "text-muted-foreground"
1258
+ );
1259
+ if (!active) return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronsUpDown, { "aria-hidden": "true", className });
1260
+ return direction === "asc" ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronUp, { "aria-hidden": "true", className }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { "aria-hidden": "true", className });
1261
+ }
1262
+
1263
+ exports.AppShell = AppShell;
1264
+ exports.Avatar = Avatar;
1265
+ exports.Button = Button;
1266
+ exports.Checkbox = Checkbox;
1267
+ exports.DashboardContent = DashboardContent;
1268
+ exports.DashboardHeader = DashboardHeader;
1269
+ exports.DashboardLayout = DashboardLayout;
1270
+ exports.DashboardMain = DashboardMain;
1271
+ exports.HeaderActions = HeaderActions;
1272
+ exports.HeaderCollapseTrigger = HeaderCollapseTrigger;
1273
+ exports.HeaderMobileTrigger = HeaderMobileTrigger;
1274
+ exports.HeaderSearch = HeaderSearch;
1275
+ exports.HeaderTitle = HeaderTitle;
1276
+ exports.Sidebar = Sidebar;
1277
+ exports.SidebarFooter = SidebarFooter;
1278
+ exports.SidebarGroup = SidebarGroup;
1279
+ exports.SidebarHeader = SidebarHeader;
1280
+ exports.SidebarNav = SidebarNav;
1281
+ exports.SidebarNavGroup = SidebarNavGroup;
1282
+ exports.SidebarNavItem = SidebarNavItem;
1283
+ exports.Table = Table;
1284
+ exports.buttonBaseClass = buttonBaseClass;
1285
+ exports.buttonSizeClass = buttonSizeClass;
1286
+ exports.buttonVariantClass = buttonVariantClass;
1287
+ exports.cn = cn;
1288
+ exports.tableAlignClass = alignClass;
1289
+ exports.tableBaseClass = tableBaseClass;
1290
+ exports.tableSelectedRowClass = selectedRowClass;
1291
+ exports.tableSizeClass = tableSizeClass;
1292
+ exports.tableSortIconClass = sortIconClass;
1293
+ exports.useDashboardLayout = useDashboardLayout;
1294
+ exports.useDirection = useDirection;
1295
+ //# sourceMappingURL=index.cjs.map
1296
+ //# sourceMappingURL=index.cjs.map