@tomny-dev/uzi 0.1.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,715 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ AppShell: () => AppShell,
24
+ Button: () => Button,
25
+ Card: () => Card,
26
+ Dropdown: () => Dropdown,
27
+ Modal: () => Modal,
28
+ Pill: () => Pill,
29
+ SidebarNav: () => SidebarNav,
30
+ ToastProvider: () => ToastProvider,
31
+ cx: () => cx,
32
+ useToast: () => useToast
33
+ });
34
+ module.exports = __toCommonJS(index_exports);
35
+
36
+ // src/utils/cx.ts
37
+ function cx(...values) {
38
+ return values.filter(Boolean).join(" ");
39
+ }
40
+
41
+ // src/components/button/button.module.css
42
+ var button_default = {};
43
+
44
+ // src/components/button/Button.tsx
45
+ var import_jsx_runtime = require("react/jsx-runtime");
46
+ function Button({
47
+ as,
48
+ variant = "primary",
49
+ size = "md",
50
+ className,
51
+ children,
52
+ ...rest
53
+ }) {
54
+ const classes = cx(
55
+ button_default.button,
56
+ button_default[`variant-${variant}`],
57
+ button_default[`size-${size}`],
58
+ className
59
+ );
60
+ if (as === "a") {
61
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { className: classes, ...rest, children });
62
+ }
63
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
64
+ "button",
65
+ {
66
+ type: "button",
67
+ className: classes,
68
+ ...rest,
69
+ children
70
+ }
71
+ );
72
+ }
73
+
74
+ // src/components/card/card.module.css
75
+ var card_default = {};
76
+
77
+ // src/components/card/Card.tsx
78
+ var import_jsx_runtime2 = require("react/jsx-runtime");
79
+ function Card({
80
+ as,
81
+ tone = "default",
82
+ padding = "md",
83
+ interactive = false,
84
+ className,
85
+ children,
86
+ ...rest
87
+ }) {
88
+ const Component = as ?? "div";
89
+ const toneClass = tone !== "default" ? card_default[`tone-${tone}`] : null;
90
+ const classes = cx(
91
+ card_default.card,
92
+ toneClass,
93
+ card_default[`padding-${padding}`],
94
+ interactive && card_default.interactive,
95
+ className
96
+ );
97
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Component, { className: classes, ...rest, children });
98
+ }
99
+
100
+ // src/components/pill/pill.module.css
101
+ var pill_default = {};
102
+
103
+ // src/components/pill/Pill.tsx
104
+ var import_jsx_runtime3 = require("react/jsx-runtime");
105
+ function Pill({
106
+ as,
107
+ tone = "neutral",
108
+ size = "md",
109
+ icon,
110
+ className,
111
+ children,
112
+ ...rest
113
+ }) {
114
+ const Component = as ?? "span";
115
+ const classes = cx(pill_default.pill, pill_default[`tone-${tone}`], pill_default[`size-${size}`], className);
116
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Component, { className: classes, ...rest, children: [
117
+ icon ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: pill_default.icon, "aria-hidden": "true", children: icon }) : null,
118
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: pill_default.content, children })
119
+ ] });
120
+ }
121
+
122
+ // src/components/modal/Modal.tsx
123
+ var import_react = require("react");
124
+
125
+ // src/components/modal/modal.module.css
126
+ var modal_default = {};
127
+
128
+ // src/components/modal/Modal.tsx
129
+ var import_jsx_runtime4 = require("react/jsx-runtime");
130
+ function Modal({ open, onClose, title, subtitle, size = "md", children, footer }) {
131
+ const mouseDownOnBackdrop = (0, import_react.useRef)(false);
132
+ if (!open) return null;
133
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
134
+ "div",
135
+ {
136
+ className: modal_default.backdrop,
137
+ onMouseDown: (e) => {
138
+ mouseDownOnBackdrop.current = e.target === e.currentTarget;
139
+ },
140
+ onMouseUp: (e) => {
141
+ if (mouseDownOnBackdrop.current && e.target === e.currentTarget) onClose();
142
+ mouseDownOnBackdrop.current = false;
143
+ },
144
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: cx(modal_default.modal, modal_default[`size-${size}`]), children: [
145
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: modal_default.header, children: [
146
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: modal_default.titles, children: [
147
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: modal_default.title, children: title }),
148
+ subtitle && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: modal_default.subtitle, children: subtitle })
149
+ ] }),
150
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { className: modal_default.closeButton, onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
151
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
152
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
153
+ ] }) })
154
+ ] }),
155
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: modal_default.body, children }),
156
+ footer && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: modal_default.footer, children: footer })
157
+ ] })
158
+ }
159
+ );
160
+ }
161
+
162
+ // src/components/toast/ToastContext.tsx
163
+ var import_react2 = require("react");
164
+
165
+ // src/components/toast/toast.module.css
166
+ var toast_default = {};
167
+
168
+ // src/components/toast/ToastContext.tsx
169
+ var import_jsx_runtime5 = require("react/jsx-runtime");
170
+ var DEFAULT_CONFIG = {
171
+ position: "top-right",
172
+ maxToasts: 5,
173
+ defaultDuration: 4e3,
174
+ pauseOnHover: true,
175
+ pauseOnFocusLoss: true
176
+ };
177
+ var ToastContext = (0, import_react2.createContext)(void 0);
178
+ var toastIdCounter = 0;
179
+ var generateToastId = () => `toast-${++toastIdCounter}-${Date.now()}`;
180
+ function ToastProvider({
181
+ children,
182
+ config
183
+ }) {
184
+ const [toasts, setToasts] = (0, import_react2.useState)([]);
185
+ const [isPaused, setIsPaused] = (0, import_react2.useState)(false);
186
+ const merged = (0, import_react2.useMemo)(() => ({ ...DEFAULT_CONFIG, ...config }), [config]);
187
+ const push = (0, import_react2.useCallback)(
188
+ (message, options = {}) => {
189
+ const id = generateToastId();
190
+ setToasts((prev) => {
191
+ const next = [
192
+ ...prev,
193
+ {
194
+ id,
195
+ message,
196
+ type: options.type ?? "info",
197
+ duration: options.duration ?? (options.type === "error" ? 6e3 : merged.defaultDuration),
198
+ dismissible: options.dismissible ?? true,
199
+ action: options.action
200
+ }
201
+ ];
202
+ if (next.length > merged.maxToasts) next.shift();
203
+ return next;
204
+ });
205
+ return id;
206
+ },
207
+ [merged.defaultDuration, merged.maxToasts]
208
+ );
209
+ const success = (0, import_react2.useCallback)(
210
+ (message, options) => push(message, { ...options, type: "success" }),
211
+ [push]
212
+ );
213
+ const error = (0, import_react2.useCallback)(
214
+ (message, options) => push(message, { ...options, type: "error", duration: options?.duration ?? 6e3 }),
215
+ [push]
216
+ );
217
+ const warning = (0, import_react2.useCallback)(
218
+ (message, options) => push(message, { ...options, type: "warning" }),
219
+ [push]
220
+ );
221
+ const info = (0, import_react2.useCallback)(
222
+ (message, options) => push(message, { ...options, type: "info" }),
223
+ [push]
224
+ );
225
+ const dismiss = (0, import_react2.useCallback)((id) => {
226
+ setToasts((prev) => prev.filter((t) => t.id !== id));
227
+ }, []);
228
+ const dismissAll = (0, import_react2.useCallback)(() => setToasts([]), []);
229
+ (0, import_react2.useEffect)(() => {
230
+ if (!merged.pauseOnFocusLoss) return;
231
+ const handleVisibility = () => setIsPaused(document.visibilityState !== "visible");
232
+ document.addEventListener("visibilitychange", handleVisibility);
233
+ return () => document.removeEventListener("visibilitychange", handleVisibility);
234
+ }, [merged.pauseOnFocusLoss]);
235
+ const value = (0, import_react2.useMemo)(
236
+ () => ({ toasts, push, success, error, warning, info, dismiss, dismissAll }),
237
+ [toasts, push, success, error, warning, info, dismiss, dismissAll]
238
+ );
239
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(ToastContext.Provider, { value, children: [
240
+ children,
241
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
242
+ ToastContainer,
243
+ {
244
+ toasts,
245
+ position: merged.position,
246
+ pauseOnHover: merged.pauseOnHover,
247
+ isPaused,
248
+ onDismiss: dismiss,
249
+ onPauseChange: setIsPaused
250
+ }
251
+ )
252
+ ] });
253
+ }
254
+ function useToast() {
255
+ const ctx = (0, import_react2.useContext)(ToastContext);
256
+ if (!ctx) throw new Error("useToast must be used within a ToastProvider");
257
+ return ctx;
258
+ }
259
+ function ToastContainer({
260
+ toasts,
261
+ position,
262
+ pauseOnHover,
263
+ isPaused,
264
+ onDismiss,
265
+ onPauseChange
266
+ }) {
267
+ const posClass = (() => {
268
+ switch (position) {
269
+ case "top-left":
270
+ return toast_default.topLeft;
271
+ case "top-center":
272
+ return toast_default.topCenter;
273
+ case "bottom-right":
274
+ return toast_default.bottomRight;
275
+ case "bottom-left":
276
+ return toast_default.bottomLeft;
277
+ case "bottom-center":
278
+ return toast_default.bottomCenter;
279
+ case "top-right":
280
+ default:
281
+ return toast_default.topRight;
282
+ }
283
+ })();
284
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
285
+ "div",
286
+ {
287
+ className: cx(toast_default.stack, posClass),
288
+ role: "presentation",
289
+ onMouseEnter: () => pauseOnHover && onPauseChange(true),
290
+ onMouseLeave: () => pauseOnHover && onPauseChange(false),
291
+ children: toasts.map((toast) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ToastItem, { toast, isPaused, onDismiss }, toast.id))
292
+ }
293
+ );
294
+ }
295
+ function ToastItem({
296
+ toast,
297
+ isPaused,
298
+ onDismiss
299
+ }) {
300
+ const [exiting, setExiting] = (0, import_react2.useState)(false);
301
+ const timerRef = (0, import_react2.useRef)(null);
302
+ const startRef = (0, import_react2.useRef)(0);
303
+ const remainingRef = (0, import_react2.useRef)(toast.duration ?? 0);
304
+ const palette = getPalette(toast.type);
305
+ const styleVars = {
306
+ ["--toast-bg"]: palette.background,
307
+ ["--toast-border"]: palette.border,
308
+ ["--toast-text"]: palette.text,
309
+ ["--toast-button-bg"]: palette.buttonBg,
310
+ ["--toast-button-border"]: palette.buttonBorder,
311
+ ["--toast-action-bg"]: palette.actionBg,
312
+ ["--toast-action-border"]: palette.actionBorder
313
+ };
314
+ const stopTimer = () => {
315
+ if (timerRef.current) {
316
+ window.clearTimeout(timerRef.current);
317
+ timerRef.current = null;
318
+ }
319
+ };
320
+ const triggerDismiss = (0, import_react2.useCallback)(() => {
321
+ setExiting(true);
322
+ stopTimer();
323
+ window.setTimeout(() => onDismiss(toast.id), 160);
324
+ }, [onDismiss, toast.id]);
325
+ const schedule = (0, import_react2.useCallback)(
326
+ (delay) => {
327
+ if (!delay || delay <= 0) {
328
+ triggerDismiss();
329
+ return;
330
+ }
331
+ startRef.current = performance.now();
332
+ stopTimer();
333
+ timerRef.current = window.setTimeout(() => triggerDismiss(), delay);
334
+ },
335
+ [triggerDismiss]
336
+ );
337
+ (0, import_react2.useEffect)(() => {
338
+ if (!toast.duration || toast.duration <= 0) return void 0;
339
+ schedule(toast.duration);
340
+ return stopTimer;
341
+ }, [schedule, toast.duration]);
342
+ (0, import_react2.useEffect)(() => {
343
+ if (!toast.duration || toast.duration <= 0) return;
344
+ if (isPaused) {
345
+ const elapsed = performance.now() - startRef.current;
346
+ remainingRef.current = Math.max(0, remainingRef.current - elapsed);
347
+ stopTimer();
348
+ } else {
349
+ schedule(remainingRef.current);
350
+ }
351
+ }, [isPaused, schedule, toast.duration]);
352
+ const icon = getIcon(toast.type);
353
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: cx(toast_default.toast, exiting && toast_default.exit), style: styleVars, role: "status", "aria-live": "polite", children: [
354
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: toast_default.icon, "aria-hidden": true, children: icon }),
355
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: toast_default.body, children: [
356
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: toast_default.message, children: toast.message }),
357
+ toast.action && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: toast_default.actions, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
358
+ "button",
359
+ {
360
+ type: "button",
361
+ className: toast_default.actionButton,
362
+ onClick: () => {
363
+ toast.action?.onClick();
364
+ triggerDismiss();
365
+ },
366
+ children: toast.action.label
367
+ }
368
+ ) })
369
+ ] }),
370
+ toast.dismissible !== false && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
371
+ "button",
372
+ {
373
+ type: "button",
374
+ className: toast_default.closeButton,
375
+ onClick: triggerDismiss,
376
+ "aria-label": "Dismiss notification",
377
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
378
+ "path",
379
+ {
380
+ d: "M11 3L3 11M3 3l8 8",
381
+ stroke: "currentColor",
382
+ strokeWidth: "1.5",
383
+ strokeLinecap: "round"
384
+ }
385
+ ) })
386
+ }
387
+ )
388
+ ] });
389
+ }
390
+ function getPalette(type) {
391
+ switch (type) {
392
+ case "success":
393
+ return {
394
+ background: "#22c55e",
395
+ border: "#16a34a",
396
+ text: "#0b1224",
397
+ buttonBg: "rgba(0, 0, 0, 0.08)",
398
+ buttonBorder: "rgba(0, 0, 0, 0.2)",
399
+ actionBg: "rgba(0, 0, 0, 0.1)",
400
+ actionBorder: "rgba(0, 0, 0, 0.2)"
401
+ };
402
+ case "error":
403
+ return {
404
+ background: "#f87171",
405
+ border: "#ef4444",
406
+ text: "#0b1224",
407
+ buttonBg: "rgba(0, 0, 0, 0.08)",
408
+ buttonBorder: "rgba(0, 0, 0, 0.2)",
409
+ actionBg: "rgba(0, 0, 0, 0.1)",
410
+ actionBorder: "rgba(0, 0, 0, 0.2)"
411
+ };
412
+ case "warning":
413
+ return {
414
+ background: "#fbbf24",
415
+ border: "#f59e0b",
416
+ text: "#0b1224",
417
+ buttonBg: "rgba(0, 0, 0, 0.08)",
418
+ buttonBorder: "rgba(0, 0, 0, 0.2)",
419
+ actionBg: "rgba(0, 0, 0, 0.1)",
420
+ actionBorder: "rgba(0, 0, 0, 0.2)"
421
+ };
422
+ case "info":
423
+ default:
424
+ return {
425
+ background: "#38bdf8",
426
+ border: "#0ea5e9",
427
+ text: "#0b1224",
428
+ buttonBg: "rgba(0, 0, 0, 0.08)",
429
+ buttonBorder: "rgba(0, 0, 0, 0.2)",
430
+ actionBg: "rgba(0, 0, 0, 0.1)",
431
+ actionBorder: "rgba(0, 0, 0, 0.2)"
432
+ };
433
+ }
434
+ }
435
+ function getIcon(type) {
436
+ switch (type) {
437
+ case "success":
438
+ return "OK";
439
+ case "error":
440
+ return "X";
441
+ case "warning":
442
+ return "!";
443
+ case "info":
444
+ default:
445
+ return "i";
446
+ }
447
+ }
448
+
449
+ // src/components/dropdown/Dropdown.tsx
450
+ var import_react3 = require("react");
451
+
452
+ // src/components/dropdown/dropdown.module.css
453
+ var dropdown_default = {};
454
+
455
+ // src/components/dropdown/Dropdown.tsx
456
+ var import_jsx_runtime6 = require("react/jsx-runtime");
457
+ function Dropdown({
458
+ options,
459
+ value,
460
+ onChange,
461
+ placeholder = "All",
462
+ className,
463
+ allowClear = true,
464
+ "aria-labelledby": ariaLabelledBy,
465
+ "aria-label": ariaLabel
466
+ }) {
467
+ const [open, setOpen] = (0, import_react3.useState)(false);
468
+ const ref = (0, import_react3.useRef)(null);
469
+ const selected = options.find((o) => o.value === value);
470
+ const isActive = allowClear && value !== "";
471
+ (0, import_react3.useEffect)(() => {
472
+ function handleClickOutside(e) {
473
+ if (ref.current && !ref.current.contains(e.target)) {
474
+ setOpen(false);
475
+ }
476
+ }
477
+ document.addEventListener("mousedown", handleClickOutside);
478
+ return () => document.removeEventListener("mousedown", handleClickOutside);
479
+ }, []);
480
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: cx(dropdown_default.wrapper, className), ref, children: [
481
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
482
+ "button",
483
+ {
484
+ type: "button",
485
+ className: cx(dropdown_default.trigger, isActive && dropdown_default["trigger-active"]),
486
+ onClick: () => setOpen((o) => !o),
487
+ "aria-labelledby": ariaLabelledBy,
488
+ "aria-label": ariaLabel,
489
+ children: [
490
+ selected ? selected.label : placeholder,
491
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: cx(dropdown_default.chevron, open && dropdown_default["chevron-open"]), children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("svg", { viewBox: "0 0 10 10", fill: "none", xmlns: "http://www.w3.org/2000/svg", width: "10", height: "10", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M2 3.5L5 6.5L8 3.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) }) })
492
+ ]
493
+ }
494
+ ),
495
+ open && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: dropdown_default.menu, children: [
496
+ allowClear && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
497
+ "button",
498
+ {
499
+ type: "button",
500
+ className: cx(dropdown_default.option, value === "" && dropdown_default["option-selected"]),
501
+ onClick: () => {
502
+ onChange("");
503
+ setOpen(false);
504
+ },
505
+ children: placeholder
506
+ }
507
+ ),
508
+ options.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
509
+ "button",
510
+ {
511
+ type: "button",
512
+ className: cx(dropdown_default.option, value === opt.value && dropdown_default["option-selected"]),
513
+ onClick: () => {
514
+ onChange(opt.value);
515
+ setOpen(false);
516
+ },
517
+ children: opt.label
518
+ },
519
+ opt.value
520
+ ))
521
+ ] })
522
+ ] });
523
+ }
524
+
525
+ // src/components/app-shell/AppShell.tsx
526
+ var import_react4 = require("react");
527
+
528
+ // src/components/app-shell/app-shell.module.css
529
+ var app_shell_default = {};
530
+
531
+ // src/components/app-shell/AppShell.tsx
532
+ var import_jsx_runtime7 = require("react/jsx-runtime");
533
+ var DESKTOP_BREAKPOINT = 960;
534
+ function getIsDesktop() {
535
+ if (typeof window === "undefined") return false;
536
+ return window.innerWidth >= DESKTOP_BREAKPOINT;
537
+ }
538
+ function AppShell({
539
+ children,
540
+ sidebar,
541
+ brand,
542
+ brandHref,
543
+ topbarStart,
544
+ topbarEnd,
545
+ className,
546
+ sidebarClassName,
547
+ topbarClassName,
548
+ mainClassName,
549
+ sidebarWidth,
550
+ closeSidebarOnChangeKey,
551
+ hamburgerLabel = "Toggle navigation",
552
+ onSidebarToggle
553
+ }) {
554
+ const [isDesktop, setIsDesktop] = (0, import_react4.useState)(false);
555
+ const [sidebarOpen, setSidebarOpen] = (0, import_react4.useState)(false);
556
+ const prevIsDesktopRef = (0, import_react4.useRef)(false);
557
+ const closeKeyRef = (0, import_react4.useRef)(closeSidebarOnChangeKey);
558
+ const sidebarRef = (0, import_react4.useRef)(null);
559
+ const hamburgerRef = (0, import_react4.useRef)(null);
560
+ const mainRef = (0, import_react4.useRef)(null);
561
+ const sidebarId = (0, import_react4.useId)();
562
+ (0, import_react4.useEffect)(() => {
563
+ const desktop = getIsDesktop();
564
+ setIsDesktop(desktop);
565
+ setSidebarOpen(desktop);
566
+ prevIsDesktopRef.current = desktop;
567
+ const handleResize = () => {
568
+ const nowDesktop = getIsDesktop();
569
+ setIsDesktop(nowDesktop);
570
+ if (nowDesktop !== prevIsDesktopRef.current) {
571
+ setSidebarOpen(nowDesktop);
572
+ prevIsDesktopRef.current = nowDesktop;
573
+ }
574
+ };
575
+ window.addEventListener("resize", handleResize);
576
+ return () => window.removeEventListener("resize", handleResize);
577
+ }, []);
578
+ (0, import_react4.useEffect)(() => {
579
+ if (isDesktop || !sidebarOpen) return;
580
+ const mainElement = mainRef.current;
581
+ const closeSidebar = () => setSidebarOpen(false);
582
+ const onPointerDown = (e) => {
583
+ const target = e.target;
584
+ if (!target) return;
585
+ if (sidebarRef.current?.contains(target)) return;
586
+ if (hamburgerRef.current?.contains(target)) return;
587
+ closeSidebar();
588
+ };
589
+ const timeoutId = window.setTimeout(() => {
590
+ document.addEventListener("pointerdown", onPointerDown);
591
+ window.addEventListener("scroll", closeSidebar, { passive: true });
592
+ mainElement?.addEventListener("scroll", closeSidebar, { passive: true });
593
+ document.addEventListener("touchmove", closeSidebar, { passive: true });
594
+ }, 10);
595
+ return () => {
596
+ window.clearTimeout(timeoutId);
597
+ document.removeEventListener("pointerdown", onPointerDown);
598
+ window.removeEventListener("scroll", closeSidebar);
599
+ mainElement?.removeEventListener("scroll", closeSidebar);
600
+ document.removeEventListener("touchmove", closeSidebar);
601
+ };
602
+ }, [sidebarOpen, isDesktop]);
603
+ (0, import_react4.useEffect)(() => {
604
+ if (!isDesktop && closeKeyRef.current !== closeSidebarOnChangeKey) {
605
+ setSidebarOpen(false);
606
+ }
607
+ closeKeyRef.current = closeSidebarOnChangeKey;
608
+ }, [closeSidebarOnChangeKey, isDesktop]);
609
+ (0, import_react4.useEffect)(() => {
610
+ onSidebarToggle?.(sidebarOpen);
611
+ }, [sidebarOpen, onSidebarToggle]);
612
+ const toggleSidebar = () => setSidebarOpen((open) => !open);
613
+ const sidebarWidthValue = sidebarWidth === void 0 ? void 0 : typeof sidebarWidth === "number" ? `${sidebarWidth}px` : sidebarWidth;
614
+ const shellStyle = sidebarWidthValue ? { ["--app-shell-sidebar-width"]: sidebarWidthValue } : void 0;
615
+ const shellClasses = cx(
616
+ app_shell_default.shell,
617
+ sidebarOpen ? app_shell_default.sidebarOpen : app_shell_default.sidebarCollapsed,
618
+ className
619
+ );
620
+ const sidebarClasses = cx(app_shell_default.sidebar, sidebarOpen && app_shell_default.open, sidebarClassName);
621
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
622
+ "div",
623
+ {
624
+ className: shellClasses,
625
+ style: shellStyle,
626
+ "data-app-shell": true,
627
+ "data-desktop": isDesktop ? "true" : "false",
628
+ "data-sidebar-open": sidebarOpen ? "true" : "false",
629
+ children: [
630
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("header", { className: cx(app_shell_default.topbar, topbarClassName), children: [
631
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: app_shell_default.topbarLeft, children: [
632
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
633
+ "button",
634
+ {
635
+ ref: hamburgerRef,
636
+ type: "button",
637
+ className: app_shell_default.hamburger,
638
+ onClick: toggleSidebar,
639
+ "aria-label": hamburgerLabel,
640
+ "aria-expanded": sidebarOpen,
641
+ "aria-controls": sidebarId,
642
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("svg", { viewBox: "0 0 24 24", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M3 6h18M3 12h18M3 18h18", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round" }) })
643
+ }
644
+ ),
645
+ brand && (brandHref ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("a", { className: app_shell_default.brand, href: brandHref, children: brand }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: app_shell_default.brand, children: brand })),
646
+ topbarStart && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: app_shell_default.topbarStart, children: topbarStart })
647
+ ] }),
648
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: app_shell_default.topbarRight, children: topbarEnd })
649
+ ] }),
650
+ !isDesktop && sidebarOpen && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: app_shell_default.backdrop, onClick: () => setSidebarOpen(false), onTouchStart: () => setSidebarOpen(false), "aria-hidden": "true" }),
651
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("aside", { ref: sidebarRef, id: sidebarId, className: sidebarClasses, "aria-label": "Sidebar navigation", children: sidebar }),
652
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("main", { ref: mainRef, className: cx(app_shell_default.main, mainClassName), children })
653
+ ]
654
+ }
655
+ );
656
+ }
657
+
658
+ // src/components/sidebar-nav/sidebar-nav.module.css
659
+ var sidebar_nav_default = {};
660
+
661
+ // src/components/sidebar-nav/SidebarNav.tsx
662
+ var import_jsx_runtime8 = require("react/jsx-runtime");
663
+ var defaultIsActive = (item, path) => {
664
+ if (item.active !== void 0) return item.active;
665
+ if (!path) return false;
666
+ if (item.href === "/") return path === "/";
667
+ return path.startsWith(item.href);
668
+ };
669
+ function SidebarNav({
670
+ items,
671
+ currentPath,
672
+ getIsActive = defaultIsActive,
673
+ onItemClick,
674
+ className,
675
+ itemClassName
676
+ }) {
677
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("nav", { className: cx(sidebar_nav_default.nav, className), "aria-label": "Sidebar navigation", children: items.map((item) => {
678
+ const active = getIsActive(item, currentPath);
679
+ const rel = item.rel ?? (item.target === "_blank" ? "noreferrer" : void 0);
680
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
681
+ "a",
682
+ {
683
+ className: cx(sidebar_nav_default.item, active && sidebar_nav_default.active, itemClassName),
684
+ href: item.href,
685
+ target: item.target,
686
+ rel,
687
+ "aria-current": active ? "page" : void 0,
688
+ onClick: () => {
689
+ item.onClick?.();
690
+ onItemClick?.(item);
691
+ },
692
+ children: [
693
+ item.icon && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: sidebar_nav_default.icon, children: item.icon }),
694
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: sidebar_nav_default.label, children: item.label }),
695
+ item.badge && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: sidebar_nav_default.badge, children: item.badge })
696
+ ]
697
+ },
698
+ item.href
699
+ );
700
+ }) });
701
+ }
702
+ // Annotate the CommonJS export names for ESM import in node:
703
+ 0 && (module.exports = {
704
+ AppShell,
705
+ Button,
706
+ Card,
707
+ Dropdown,
708
+ Modal,
709
+ Pill,
710
+ SidebarNav,
711
+ ToastProvider,
712
+ cx,
713
+ useToast
714
+ });
715
+ //# sourceMappingURL=index.cjs.map