warqadui 0.0.3

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.
@@ -0,0 +1,35 @@
1
+ import React from 'react';
2
+
3
+ interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
4
+ variant?: "primary" | "secondary" | "outline" | "ghost" | "danger" | "warning";
5
+ size?: "sm" | "md" | "lg";
6
+ isLoading?: boolean;
7
+ icon?: React.ReactNode;
8
+ }
9
+ declare const Button: React.FC<ButtonProps>;
10
+
11
+ interface NavItem {
12
+ label: string;
13
+ icon?: React.ReactNode;
14
+ path?: string;
15
+ subItems?: NavItem[];
16
+ }
17
+ interface DashboardLayoutProps {
18
+ children: React.ReactNode;
19
+ navItems: NavItem[];
20
+ title?: string;
21
+ logo?: React.ReactNode;
22
+ userProfile?: React.ReactNode;
23
+ }
24
+ declare const DashboardLayout: React.FC<DashboardLayoutProps>;
25
+
26
+ declare const ThemeToggle: React.FC<{
27
+ className?: string;
28
+ }>;
29
+
30
+ declare const useTheme: () => {
31
+ isDarkMode: boolean;
32
+ toggleTheme: () => void;
33
+ };
34
+
35
+ export { Button, type ButtonProps, DashboardLayout, type NavItem, ThemeToggle, useTheme };
@@ -0,0 +1,35 @@
1
+ import React from 'react';
2
+
3
+ interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
4
+ variant?: "primary" | "secondary" | "outline" | "ghost" | "danger" | "warning";
5
+ size?: "sm" | "md" | "lg";
6
+ isLoading?: boolean;
7
+ icon?: React.ReactNode;
8
+ }
9
+ declare const Button: React.FC<ButtonProps>;
10
+
11
+ interface NavItem {
12
+ label: string;
13
+ icon?: React.ReactNode;
14
+ path?: string;
15
+ subItems?: NavItem[];
16
+ }
17
+ interface DashboardLayoutProps {
18
+ children: React.ReactNode;
19
+ navItems: NavItem[];
20
+ title?: string;
21
+ logo?: React.ReactNode;
22
+ userProfile?: React.ReactNode;
23
+ }
24
+ declare const DashboardLayout: React.FC<DashboardLayoutProps>;
25
+
26
+ declare const ThemeToggle: React.FC<{
27
+ className?: string;
28
+ }>;
29
+
30
+ declare const useTheme: () => {
31
+ isDarkMode: boolean;
32
+ toggleTheme: () => void;
33
+ };
34
+
35
+ export { Button, type ButtonProps, DashboardLayout, type NavItem, ThemeToggle, useTheme };
package/dist/index.js ADDED
@@ -0,0 +1,470 @@
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
+ Button: () => Button,
24
+ DashboardLayout: () => DashboardLayout,
25
+ ThemeToggle: () => ThemeToggle,
26
+ useTheme: () => useTheme
27
+ });
28
+ module.exports = __toCommonJS(index_exports);
29
+
30
+ // src/utils/cn.ts
31
+ var import_clsx = require("clsx");
32
+ var import_tailwind_merge = require("tailwind-merge");
33
+ function cn(...inputs) {
34
+ return (0, import_tailwind_merge.twMerge)((0, import_clsx.clsx)(inputs));
35
+ }
36
+
37
+ // src/components/Button.tsx
38
+ var import_jsx_runtime = require("react/jsx-runtime");
39
+ var Button = ({
40
+ children,
41
+ className,
42
+ variant = "primary",
43
+ size = "md",
44
+ isLoading,
45
+ icon,
46
+ ...props
47
+ }) => {
48
+ const variants = {
49
+ primary: "bg-blue-600 text-white hover:bg-blue-700 active:bg-blue-800 shadow-md focus:ring-blue-600",
50
+ secondary: "bg-slate-800 text-white hover:bg-slate-900 active:bg-slate-950 shadow-md focus:ring-slate-800",
51
+ outline: "border-2 border-slate-200 bg-transparent hover:bg-slate-50 active:bg-slate-100 focus:ring-slate-500",
52
+ ghost: "bg-transparent hover:bg-slate-100 active:bg-slate-200 focus:ring-slate-500",
53
+ danger: "bg-red-600 text-white hover:bg-red-700 active:bg-red-800 shadow-md focus:ring-red-600",
54
+ warning: "bg-amber-500 text-white hover:bg-amber-600 active:bg-amber-700 shadow-md focus:ring-amber-500"
55
+ };
56
+ const sizes = {
57
+ sm: "px-3 py-1.5 text-sm rounded-md gap-1.5",
58
+ md: "px-4 py-2 rounded-lg gap-2",
59
+ lg: "px-6 py-3 text-lg rounded-xl font-medium gap-2.5"
60
+ };
61
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
62
+ "button",
63
+ {
64
+ className: cn(
65
+ "inline-flex items-center justify-center transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed",
66
+ variants[variant],
67
+ sizes[size],
68
+ className
69
+ ),
70
+ disabled: isLoading || props.disabled,
71
+ ...props,
72
+ children: [
73
+ isLoading ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
74
+ "svg",
75
+ {
76
+ className: "animate-spin -ml-1 mr-2 h-4 w-4",
77
+ fill: "none",
78
+ viewBox: "0 0 24 24",
79
+ xmlns: "http://www.w3.org/2000/svg",
80
+ children: [
81
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
82
+ "circle",
83
+ {
84
+ className: "opacity-25",
85
+ cx: "12",
86
+ cy: "12",
87
+ r: "10",
88
+ stroke: "currentColor",
89
+ strokeWidth: "4"
90
+ }
91
+ ),
92
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
93
+ "path",
94
+ {
95
+ className: "opacity-75",
96
+ fill: "currentColor",
97
+ d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
98
+ }
99
+ )
100
+ ]
101
+ }
102
+ ) : icon && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "flex-shrink-0", children: icon }),
103
+ children
104
+ ]
105
+ }
106
+ );
107
+ };
108
+
109
+ // src/components/DashboardLayout.tsx
110
+ var import_react = require("react");
111
+ var import_react_router_dom = require("react-router-dom");
112
+ var import_lucide_react = require("lucide-react");
113
+ var import_framer_motion = require("framer-motion");
114
+ var import_jsx_runtime2 = require("react/jsx-runtime");
115
+ var useMediaQuery = (query) => {
116
+ const [matches, setMatches] = (0, import_react.useState)(() => {
117
+ if (typeof window !== "undefined") {
118
+ return window.matchMedia(query).matches;
119
+ }
120
+ return false;
121
+ });
122
+ (0, import_react.useEffect)(() => {
123
+ const media = window.matchMedia(query);
124
+ if (media.matches !== matches) {
125
+ setMatches(media.matches);
126
+ }
127
+ const listener = () => setMatches(media.matches);
128
+ media.addEventListener("change", listener);
129
+ return () => media.removeEventListener("change", listener);
130
+ }, [matches, query]);
131
+ return matches;
132
+ };
133
+ var isPathActive = (navPath, currentPath) => {
134
+ if (!navPath || !currentPath) return false;
135
+ return currentPath === navPath || navPath !== "/" && currentPath.startsWith(`${navPath}/`);
136
+ };
137
+ var SidebarItem = ({
138
+ item,
139
+ isActive,
140
+ isChildActive,
141
+ onClick,
142
+ isExpanded,
143
+ onToggleExpand,
144
+ isSidebarCollapsed
145
+ }) => {
146
+ const hasSubItems = item.subItems && item.subItems.length > 0;
147
+ const shouldHighlight = isActive || hasSubItems && isExpanded || isChildActive;
148
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mb-1 group relative", children: [
149
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
150
+ "button",
151
+ {
152
+ type: "button",
153
+ onClick: hasSubItems ? onToggleExpand : onClick,
154
+ title: isSidebarCollapsed ? item.label : void 0,
155
+ className: cn(
156
+ "w-full flex items-center py-2.5 rounded-lg text-sm font-medium transition-all duration-200",
157
+ isSidebarCollapsed ? "justify-center px-2" : "justify-between px-3",
158
+ shouldHighlight ? "bg-blue-50 text-blue-700 dark:bg-blue-900/20 dark:text-blue-400" : "text-gray-600 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200"
159
+ ),
160
+ children: [
161
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
162
+ "div",
163
+ {
164
+ className: cn(
165
+ "flex items-center gap-3 overflow-hidden",
166
+ isSidebarCollapsed ? "justify-center w-full" : ""
167
+ ),
168
+ children: [
169
+ item.icon && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
170
+ "span",
171
+ {
172
+ className: cn(
173
+ "flex-shrink-0 transition-colors",
174
+ shouldHighlight ? "text-blue-600 dark:text-blue-400" : "text-gray-400 group-hover:text-blue-500 dark:text-gray-500 dark:group-hover:text-blue-400"
175
+ ),
176
+ children: item.icon
177
+ }
178
+ ),
179
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_framer_motion.AnimatePresence, { children: !isSidebarCollapsed && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
180
+ import_framer_motion.motion.span,
181
+ {
182
+ initial: { opacity: 0, width: 0 },
183
+ animate: { opacity: 1, width: "auto" },
184
+ exit: { opacity: 0, width: 0 },
185
+ transition: { duration: 0.2 },
186
+ className: "truncate",
187
+ children: item.label
188
+ }
189
+ ) })
190
+ ]
191
+ }
192
+ ),
193
+ !isSidebarCollapsed && hasSubItems && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
194
+ "span",
195
+ {
196
+ className: cn(
197
+ "flex-shrink-0 ml-2 transition-transform duration-200",
198
+ shouldHighlight ? "text-blue-500" : "text-gray-400"
199
+ ),
200
+ children: isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.ChevronDown, { size: 16 }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.ChevronRight, { size: 16 })
201
+ }
202
+ )
203
+ ]
204
+ }
205
+ ),
206
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_framer_motion.AnimatePresence, { initial: false, children: !isSidebarCollapsed && hasSubItems && isExpanded && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
207
+ import_framer_motion.motion.div,
208
+ {
209
+ initial: { height: 0, opacity: 0 },
210
+ animate: { height: "auto", opacity: 1 },
211
+ exit: { height: 0, opacity: 0 },
212
+ transition: { duration: 0.2 },
213
+ className: "overflow-hidden",
214
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "relative ml-6 mt-1 space-y-1", children: [
215
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "absolute left-0 top-0 bottom-0 w-px bg-gray-200 dark:bg-gray-800" }),
216
+ item.subItems.map((subItem, index) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SubItem, { item: subItem }, index))
217
+ ] })
218
+ }
219
+ ) })
220
+ ] });
221
+ };
222
+ var SubItem = ({ item }) => {
223
+ const location = (0, import_react_router_dom.useLocation)();
224
+ const navigate = (0, import_react_router_dom.useNavigate)();
225
+ const isActive = isPathActive(item.path, location.pathname);
226
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
227
+ "button",
228
+ {
229
+ onClick: () => item.path && navigate(item.path),
230
+ className: cn(
231
+ "relative w-full flex items-center px-3 py-2 pl-6 rounded-md text-sm transition-colors duration-200",
232
+ isActive ? "bg-blue-100/50 text-blue-700 font-medium dark:bg-blue-900/30 dark:text-blue-400" : "text-gray-500 hover:bg-gray-50 hover:text-gray-900 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200"
233
+ ),
234
+ children: [
235
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "truncate", children: item.label }),
236
+ isActive && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "absolute left-0 top-1/2 -translate-y-1/2 w-1 h-4 bg-blue-500 rounded-r-full" })
237
+ ]
238
+ }
239
+ );
240
+ };
241
+ var DashboardLayout = ({
242
+ children,
243
+ navItems,
244
+ title = "Dashboard",
245
+ logo,
246
+ userProfile
247
+ }) => {
248
+ const isDesktop = useMediaQuery("(min-width: 1024px)");
249
+ const location = (0, import_react_router_dom.useLocation)();
250
+ const navigate = (0, import_react_router_dom.useNavigate)();
251
+ const [isSidebarOpen, setIsSidebarOpen] = (0, import_react.useState)(true);
252
+ const [expandedMenus, setExpandedMenus] = (0, import_react.useState)(
253
+ () => {
254
+ const initialState = {};
255
+ navItems.forEach((item) => {
256
+ if (item.subItems?.some(
257
+ (sub) => isPathActive(sub.path, location.pathname)
258
+ )) {
259
+ initialState[item.label] = true;
260
+ }
261
+ });
262
+ return initialState;
263
+ }
264
+ );
265
+ const prevPathRef = (0, import_react.useRef)(location.pathname);
266
+ (0, import_react.useEffect)(() => {
267
+ if (prevPathRef.current !== location.pathname) {
268
+ const activeItem = navItems.find(
269
+ (item) => item.subItems?.some((sub) => isPathActive(sub.path, location.pathname))
270
+ );
271
+ if (activeItem) {
272
+ setExpandedMenus((prev) => ({ ...prev, [activeItem.label]: true }));
273
+ }
274
+ prevPathRef.current = location.pathname;
275
+ }
276
+ }, [location.pathname, navItems]);
277
+ const isCollapsed = isDesktop && !isSidebarOpen;
278
+ (0, import_react.useEffect)(() => {
279
+ if (!isDesktop) {
280
+ setIsSidebarOpen(false);
281
+ } else {
282
+ setIsSidebarOpen(true);
283
+ }
284
+ }, [isDesktop]);
285
+ const toggleSidebar = () => setIsSidebarOpen(!isSidebarOpen);
286
+ const toggleMenu = (label) => {
287
+ if (isCollapsed) {
288
+ setIsSidebarOpen(true);
289
+ setTimeout(() => {
290
+ setExpandedMenus((prev) => {
291
+ const isCurrentlyOpen = !!prev[label];
292
+ return isCurrentlyOpen ? {} : { [label]: true };
293
+ });
294
+ }, 50);
295
+ return;
296
+ }
297
+ setExpandedMenus((prev) => {
298
+ const isCurrentlyOpen = !!prev[label];
299
+ return isCurrentlyOpen ? {} : { [label]: true };
300
+ });
301
+ };
302
+ const sidebarVariants = {
303
+ desktopOpen: {
304
+ width: "16rem",
305
+ transition: { duration: 0.3, type: "spring", bounce: 0, damping: 20 }
306
+ },
307
+ desktopClosed: {
308
+ width: "5rem",
309
+ // Mini Sidebar Width
310
+ transition: { duration: 0.3, type: "spring", bounce: 0, damping: 20 }
311
+ },
312
+ mobileOpen: {
313
+ x: 0,
314
+ transition: { duration: 0.3, type: "spring", bounce: 0, damping: 20 }
315
+ },
316
+ mobileClosed: {
317
+ x: "-100%",
318
+ transition: { duration: 0.3, type: "spring", bounce: 0, damping: 20 }
319
+ }
320
+ };
321
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "min-h-screen bg-gray-50 dark:bg-gray-900 text-slate-900 dark:text-slate-100 flex transition-colors duration-300 font-sans overflow-hidden", children: [
322
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_framer_motion.AnimatePresence, { children: !isDesktop && isSidebarOpen && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
323
+ import_framer_motion.motion.div,
324
+ {
325
+ initial: { opacity: 0 },
326
+ animate: { opacity: 0.5 },
327
+ exit: { opacity: 0 },
328
+ onClick: () => setIsSidebarOpen(false),
329
+ className: "fixed inset-0 bg-black/50 z-40 lg:hidden backdrop-blur-sm"
330
+ }
331
+ ) }),
332
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
333
+ import_framer_motion.motion.aside,
334
+ {
335
+ className: "fixed lg:relative inset-y-0 left-0 z-50 bg-white dark:bg-gray-900 border-r border-gray-200 dark:border-gray-800 flex flex-col h-screen",
336
+ initial: isDesktop ? "desktopOpen" : "mobileClosed",
337
+ animate: isDesktop ? isSidebarOpen ? "desktopOpen" : "desktopClosed" : isSidebarOpen ? "mobileOpen" : "mobileClosed",
338
+ variants: sidebarVariants,
339
+ children: [
340
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
341
+ "div",
342
+ {
343
+ className: cn(
344
+ "h-16 flex items-center border-b border-gray-100 dark:border-gray-800 flex-shrink-0 overflow-hidden transition-all duration-300",
345
+ isCollapsed ? "justify-center px-0" : "px-6"
346
+ ),
347
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex items-center gap-2 text-blue-600 dark:text-blue-400", children: isCollapsed ? logo : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
348
+ logo,
349
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-bold text-lg tracking-tight text-gray-900 dark:text-white", children: title })
350
+ ] }) })
351
+ }
352
+ ),
353
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex-1 p-4 overflow-y-auto overflow-x-hidden scrollbar-hide", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("nav", { className: "space-y-1", children: navItems.map((item) => {
354
+ const isActive = isPathActive(item.path, location.pathname);
355
+ const isChildActive = item.subItems?.some(
356
+ (sub) => isPathActive(sub.path, location.pathname)
357
+ );
358
+ const isExpanded = expandedMenus[item.label];
359
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
360
+ SidebarItem,
361
+ {
362
+ item,
363
+ isActive,
364
+ isChildActive: !!isChildActive,
365
+ isExpanded: !!isExpanded,
366
+ isSidebarCollapsed: !!isCollapsed,
367
+ onClick: () => {
368
+ if (item.path) navigate(item.path);
369
+ if (isCollapsed && !item.path) {
370
+ setIsSidebarOpen(true);
371
+ }
372
+ },
373
+ onToggleExpand: () => toggleMenu(item.label)
374
+ },
375
+ item.label
376
+ );
377
+ }) }) })
378
+ ]
379
+ }
380
+ ),
381
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex-1 flex flex-col min-w-0 h-screen overflow-hidden", children: [
382
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("header", { className: "sticky top-0 z-30 h-16 bg-white/80 dark:bg-gray-900/80 backdrop-blur-md border-b border-gray-200 dark:border-gray-800 px-4 sm:px-6 flex items-center justify-between flex-shrink-0", children: [
383
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
384
+ "button",
385
+ {
386
+ onClick: toggleSidebar,
387
+ className: "p-2 -ml-2 rounded-lg text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-800 transition-colors",
388
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.Menu, { size: 20 })
389
+ }
390
+ ) }),
391
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex items-center gap-2", children: userProfile && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
392
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "w-px h-6 mx-2 bg-gray-200 dark:bg-gray-800" }),
393
+ userProfile
394
+ ] }) })
395
+ ] }),
396
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("main", { className: "flex-1 p-2 md:p-4 overflow-y-auto", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "max-w-7xl mx-auto", children }) })
397
+ ] })
398
+ ] });
399
+ };
400
+
401
+ // src/components/ThemeToggle.tsx
402
+ var import_lucide_react2 = require("lucide-react");
403
+
404
+ // src/hooks/useTheme.ts
405
+ var import_react2 = require("react");
406
+ var useTheme = () => {
407
+ const [isDarkMode, setIsDarkMode] = (0, import_react2.useState)(() => {
408
+ if (typeof window !== "undefined") {
409
+ const savedTheme = localStorage.getItem("theme");
410
+ if (savedTheme) {
411
+ return savedTheme === "dark";
412
+ }
413
+ return window.matchMedia("(prefers-color-scheme: dark)").matches;
414
+ }
415
+ return false;
416
+ });
417
+ (0, import_react2.useEffect)(() => {
418
+ const root = window.document.documentElement;
419
+ if (isDarkMode) {
420
+ root.classList.add("dark");
421
+ localStorage.setItem("theme", "dark");
422
+ } else {
423
+ root.classList.remove("dark");
424
+ localStorage.setItem("theme", "light");
425
+ }
426
+ }, [isDarkMode]);
427
+ const toggleTheme = () => setIsDarkMode((prev) => !prev);
428
+ return { isDarkMode, toggleTheme };
429
+ };
430
+
431
+ // src/components/ThemeToggle.tsx
432
+ var import_jsx_runtime3 = require("react/jsx-runtime");
433
+ var ThemeToggle = ({
434
+ className
435
+ }) => {
436
+ const { isDarkMode, toggleTheme } = useTheme();
437
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
438
+ "button",
439
+ {
440
+ onClick: toggleTheme,
441
+ className: cn(
442
+ "p-2 rounded-lg transition-all duration-200",
443
+ "text-slate-500 hover:text-blue-600 hover:bg-slate-100",
444
+ "dark:text-yellow-400 dark:hover:text-yellow-300 dark:hover:bg-slate-800",
445
+ className
446
+ ),
447
+ "aria-label": "Toggle dark mode",
448
+ children: isDarkMode ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
449
+ import_lucide_react2.Sun,
450
+ {
451
+ size: 20,
452
+ className: "stroke-2 transition-transform duration-300 rotate-0 scale-100"
453
+ }
454
+ ) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
455
+ import_lucide_react2.Moon,
456
+ {
457
+ size: 20,
458
+ className: "stroke-2 transition-transform duration-300 rotate-0 scale-100"
459
+ }
460
+ )
461
+ }
462
+ );
463
+ };
464
+ // Annotate the CommonJS export names for ESM import in node:
465
+ 0 && (module.exports = {
466
+ Button,
467
+ DashboardLayout,
468
+ ThemeToggle,
469
+ useTheme
470
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,440 @@
1
+ // src/utils/cn.ts
2
+ import { clsx } from "clsx";
3
+ import { twMerge } from "tailwind-merge";
4
+ function cn(...inputs) {
5
+ return twMerge(clsx(inputs));
6
+ }
7
+
8
+ // src/components/Button.tsx
9
+ import { jsx, jsxs } from "react/jsx-runtime";
10
+ var Button = ({
11
+ children,
12
+ className,
13
+ variant = "primary",
14
+ size = "md",
15
+ isLoading,
16
+ icon,
17
+ ...props
18
+ }) => {
19
+ const variants = {
20
+ primary: "bg-blue-600 text-white hover:bg-blue-700 active:bg-blue-800 shadow-md focus:ring-blue-600",
21
+ secondary: "bg-slate-800 text-white hover:bg-slate-900 active:bg-slate-950 shadow-md focus:ring-slate-800",
22
+ outline: "border-2 border-slate-200 bg-transparent hover:bg-slate-50 active:bg-slate-100 focus:ring-slate-500",
23
+ ghost: "bg-transparent hover:bg-slate-100 active:bg-slate-200 focus:ring-slate-500",
24
+ danger: "bg-red-600 text-white hover:bg-red-700 active:bg-red-800 shadow-md focus:ring-red-600",
25
+ warning: "bg-amber-500 text-white hover:bg-amber-600 active:bg-amber-700 shadow-md focus:ring-amber-500"
26
+ };
27
+ const sizes = {
28
+ sm: "px-3 py-1.5 text-sm rounded-md gap-1.5",
29
+ md: "px-4 py-2 rounded-lg gap-2",
30
+ lg: "px-6 py-3 text-lg rounded-xl font-medium gap-2.5"
31
+ };
32
+ return /* @__PURE__ */ jsxs(
33
+ "button",
34
+ {
35
+ className: cn(
36
+ "inline-flex items-center justify-center transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed",
37
+ variants[variant],
38
+ sizes[size],
39
+ className
40
+ ),
41
+ disabled: isLoading || props.disabled,
42
+ ...props,
43
+ children: [
44
+ isLoading ? /* @__PURE__ */ jsxs(
45
+ "svg",
46
+ {
47
+ className: "animate-spin -ml-1 mr-2 h-4 w-4",
48
+ fill: "none",
49
+ viewBox: "0 0 24 24",
50
+ xmlns: "http://www.w3.org/2000/svg",
51
+ children: [
52
+ /* @__PURE__ */ jsx(
53
+ "circle",
54
+ {
55
+ className: "opacity-25",
56
+ cx: "12",
57
+ cy: "12",
58
+ r: "10",
59
+ stroke: "currentColor",
60
+ strokeWidth: "4"
61
+ }
62
+ ),
63
+ /* @__PURE__ */ jsx(
64
+ "path",
65
+ {
66
+ className: "opacity-75",
67
+ fill: "currentColor",
68
+ d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
69
+ }
70
+ )
71
+ ]
72
+ }
73
+ ) : icon && /* @__PURE__ */ jsx("span", { className: "flex-shrink-0", children: icon }),
74
+ children
75
+ ]
76
+ }
77
+ );
78
+ };
79
+
80
+ // src/components/DashboardLayout.tsx
81
+ import { useState, useEffect, useRef } from "react";
82
+ import { useLocation, useNavigate } from "react-router-dom";
83
+ import { Menu, ChevronDown, ChevronRight } from "lucide-react";
84
+ import { motion, AnimatePresence } from "framer-motion";
85
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
86
+ var useMediaQuery = (query) => {
87
+ const [matches, setMatches] = useState(() => {
88
+ if (typeof window !== "undefined") {
89
+ return window.matchMedia(query).matches;
90
+ }
91
+ return false;
92
+ });
93
+ useEffect(() => {
94
+ const media = window.matchMedia(query);
95
+ if (media.matches !== matches) {
96
+ setMatches(media.matches);
97
+ }
98
+ const listener = () => setMatches(media.matches);
99
+ media.addEventListener("change", listener);
100
+ return () => media.removeEventListener("change", listener);
101
+ }, [matches, query]);
102
+ return matches;
103
+ };
104
+ var isPathActive = (navPath, currentPath) => {
105
+ if (!navPath || !currentPath) return false;
106
+ return currentPath === navPath || navPath !== "/" && currentPath.startsWith(`${navPath}/`);
107
+ };
108
+ var SidebarItem = ({
109
+ item,
110
+ isActive,
111
+ isChildActive,
112
+ onClick,
113
+ isExpanded,
114
+ onToggleExpand,
115
+ isSidebarCollapsed
116
+ }) => {
117
+ const hasSubItems = item.subItems && item.subItems.length > 0;
118
+ const shouldHighlight = isActive || hasSubItems && isExpanded || isChildActive;
119
+ return /* @__PURE__ */ jsxs2("div", { className: "mb-1 group relative", children: [
120
+ /* @__PURE__ */ jsxs2(
121
+ "button",
122
+ {
123
+ type: "button",
124
+ onClick: hasSubItems ? onToggleExpand : onClick,
125
+ title: isSidebarCollapsed ? item.label : void 0,
126
+ className: cn(
127
+ "w-full flex items-center py-2.5 rounded-lg text-sm font-medium transition-all duration-200",
128
+ isSidebarCollapsed ? "justify-center px-2" : "justify-between px-3",
129
+ shouldHighlight ? "bg-blue-50 text-blue-700 dark:bg-blue-900/20 dark:text-blue-400" : "text-gray-600 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200"
130
+ ),
131
+ children: [
132
+ /* @__PURE__ */ jsxs2(
133
+ "div",
134
+ {
135
+ className: cn(
136
+ "flex items-center gap-3 overflow-hidden",
137
+ isSidebarCollapsed ? "justify-center w-full" : ""
138
+ ),
139
+ children: [
140
+ item.icon && /* @__PURE__ */ jsx2(
141
+ "span",
142
+ {
143
+ className: cn(
144
+ "flex-shrink-0 transition-colors",
145
+ shouldHighlight ? "text-blue-600 dark:text-blue-400" : "text-gray-400 group-hover:text-blue-500 dark:text-gray-500 dark:group-hover:text-blue-400"
146
+ ),
147
+ children: item.icon
148
+ }
149
+ ),
150
+ /* @__PURE__ */ jsx2(AnimatePresence, { children: !isSidebarCollapsed && /* @__PURE__ */ jsx2(
151
+ motion.span,
152
+ {
153
+ initial: { opacity: 0, width: 0 },
154
+ animate: { opacity: 1, width: "auto" },
155
+ exit: { opacity: 0, width: 0 },
156
+ transition: { duration: 0.2 },
157
+ className: "truncate",
158
+ children: item.label
159
+ }
160
+ ) })
161
+ ]
162
+ }
163
+ ),
164
+ !isSidebarCollapsed && hasSubItems && /* @__PURE__ */ jsx2(
165
+ "span",
166
+ {
167
+ className: cn(
168
+ "flex-shrink-0 ml-2 transition-transform duration-200",
169
+ shouldHighlight ? "text-blue-500" : "text-gray-400"
170
+ ),
171
+ children: isExpanded ? /* @__PURE__ */ jsx2(ChevronDown, { size: 16 }) : /* @__PURE__ */ jsx2(ChevronRight, { size: 16 })
172
+ }
173
+ )
174
+ ]
175
+ }
176
+ ),
177
+ /* @__PURE__ */ jsx2(AnimatePresence, { initial: false, children: !isSidebarCollapsed && hasSubItems && isExpanded && /* @__PURE__ */ jsx2(
178
+ motion.div,
179
+ {
180
+ initial: { height: 0, opacity: 0 },
181
+ animate: { height: "auto", opacity: 1 },
182
+ exit: { height: 0, opacity: 0 },
183
+ transition: { duration: 0.2 },
184
+ className: "overflow-hidden",
185
+ children: /* @__PURE__ */ jsxs2("div", { className: "relative ml-6 mt-1 space-y-1", children: [
186
+ /* @__PURE__ */ jsx2("div", { className: "absolute left-0 top-0 bottom-0 w-px bg-gray-200 dark:bg-gray-800" }),
187
+ item.subItems.map((subItem, index) => /* @__PURE__ */ jsx2(SubItem, { item: subItem }, index))
188
+ ] })
189
+ }
190
+ ) })
191
+ ] });
192
+ };
193
+ var SubItem = ({ item }) => {
194
+ const location = useLocation();
195
+ const navigate = useNavigate();
196
+ const isActive = isPathActive(item.path, location.pathname);
197
+ return /* @__PURE__ */ jsxs2(
198
+ "button",
199
+ {
200
+ onClick: () => item.path && navigate(item.path),
201
+ className: cn(
202
+ "relative w-full flex items-center px-3 py-2 pl-6 rounded-md text-sm transition-colors duration-200",
203
+ isActive ? "bg-blue-100/50 text-blue-700 font-medium dark:bg-blue-900/30 dark:text-blue-400" : "text-gray-500 hover:bg-gray-50 hover:text-gray-900 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200"
204
+ ),
205
+ children: [
206
+ /* @__PURE__ */ jsx2("span", { className: "truncate", children: item.label }),
207
+ isActive && /* @__PURE__ */ jsx2("div", { className: "absolute left-0 top-1/2 -translate-y-1/2 w-1 h-4 bg-blue-500 rounded-r-full" })
208
+ ]
209
+ }
210
+ );
211
+ };
212
+ var DashboardLayout = ({
213
+ children,
214
+ navItems,
215
+ title = "Dashboard",
216
+ logo,
217
+ userProfile
218
+ }) => {
219
+ const isDesktop = useMediaQuery("(min-width: 1024px)");
220
+ const location = useLocation();
221
+ const navigate = useNavigate();
222
+ const [isSidebarOpen, setIsSidebarOpen] = useState(true);
223
+ const [expandedMenus, setExpandedMenus] = useState(
224
+ () => {
225
+ const initialState = {};
226
+ navItems.forEach((item) => {
227
+ if (item.subItems?.some(
228
+ (sub) => isPathActive(sub.path, location.pathname)
229
+ )) {
230
+ initialState[item.label] = true;
231
+ }
232
+ });
233
+ return initialState;
234
+ }
235
+ );
236
+ const prevPathRef = useRef(location.pathname);
237
+ useEffect(() => {
238
+ if (prevPathRef.current !== location.pathname) {
239
+ const activeItem = navItems.find(
240
+ (item) => item.subItems?.some((sub) => isPathActive(sub.path, location.pathname))
241
+ );
242
+ if (activeItem) {
243
+ setExpandedMenus((prev) => ({ ...prev, [activeItem.label]: true }));
244
+ }
245
+ prevPathRef.current = location.pathname;
246
+ }
247
+ }, [location.pathname, navItems]);
248
+ const isCollapsed = isDesktop && !isSidebarOpen;
249
+ useEffect(() => {
250
+ if (!isDesktop) {
251
+ setIsSidebarOpen(false);
252
+ } else {
253
+ setIsSidebarOpen(true);
254
+ }
255
+ }, [isDesktop]);
256
+ const toggleSidebar = () => setIsSidebarOpen(!isSidebarOpen);
257
+ const toggleMenu = (label) => {
258
+ if (isCollapsed) {
259
+ setIsSidebarOpen(true);
260
+ setTimeout(() => {
261
+ setExpandedMenus((prev) => {
262
+ const isCurrentlyOpen = !!prev[label];
263
+ return isCurrentlyOpen ? {} : { [label]: true };
264
+ });
265
+ }, 50);
266
+ return;
267
+ }
268
+ setExpandedMenus((prev) => {
269
+ const isCurrentlyOpen = !!prev[label];
270
+ return isCurrentlyOpen ? {} : { [label]: true };
271
+ });
272
+ };
273
+ const sidebarVariants = {
274
+ desktopOpen: {
275
+ width: "16rem",
276
+ transition: { duration: 0.3, type: "spring", bounce: 0, damping: 20 }
277
+ },
278
+ desktopClosed: {
279
+ width: "5rem",
280
+ // Mini Sidebar Width
281
+ transition: { duration: 0.3, type: "spring", bounce: 0, damping: 20 }
282
+ },
283
+ mobileOpen: {
284
+ x: 0,
285
+ transition: { duration: 0.3, type: "spring", bounce: 0, damping: 20 }
286
+ },
287
+ mobileClosed: {
288
+ x: "-100%",
289
+ transition: { duration: 0.3, type: "spring", bounce: 0, damping: 20 }
290
+ }
291
+ };
292
+ return /* @__PURE__ */ jsxs2("div", { className: "min-h-screen bg-gray-50 dark:bg-gray-900 text-slate-900 dark:text-slate-100 flex transition-colors duration-300 font-sans overflow-hidden", children: [
293
+ /* @__PURE__ */ jsx2(AnimatePresence, { children: !isDesktop && isSidebarOpen && /* @__PURE__ */ jsx2(
294
+ motion.div,
295
+ {
296
+ initial: { opacity: 0 },
297
+ animate: { opacity: 0.5 },
298
+ exit: { opacity: 0 },
299
+ onClick: () => setIsSidebarOpen(false),
300
+ className: "fixed inset-0 bg-black/50 z-40 lg:hidden backdrop-blur-sm"
301
+ }
302
+ ) }),
303
+ /* @__PURE__ */ jsxs2(
304
+ motion.aside,
305
+ {
306
+ className: "fixed lg:relative inset-y-0 left-0 z-50 bg-white dark:bg-gray-900 border-r border-gray-200 dark:border-gray-800 flex flex-col h-screen",
307
+ initial: isDesktop ? "desktopOpen" : "mobileClosed",
308
+ animate: isDesktop ? isSidebarOpen ? "desktopOpen" : "desktopClosed" : isSidebarOpen ? "mobileOpen" : "mobileClosed",
309
+ variants: sidebarVariants,
310
+ children: [
311
+ /* @__PURE__ */ jsx2(
312
+ "div",
313
+ {
314
+ className: cn(
315
+ "h-16 flex items-center border-b border-gray-100 dark:border-gray-800 flex-shrink-0 overflow-hidden transition-all duration-300",
316
+ isCollapsed ? "justify-center px-0" : "px-6"
317
+ ),
318
+ children: /* @__PURE__ */ jsx2("div", { className: "flex items-center gap-2 text-blue-600 dark:text-blue-400", children: isCollapsed ? logo : /* @__PURE__ */ jsxs2(Fragment, { children: [
319
+ logo,
320
+ /* @__PURE__ */ jsx2("span", { className: "font-bold text-lg tracking-tight text-gray-900 dark:text-white", children: title })
321
+ ] }) })
322
+ }
323
+ ),
324
+ /* @__PURE__ */ jsx2("div", { className: "flex-1 p-4 overflow-y-auto overflow-x-hidden scrollbar-hide", children: /* @__PURE__ */ jsx2("nav", { className: "space-y-1", children: navItems.map((item) => {
325
+ const isActive = isPathActive(item.path, location.pathname);
326
+ const isChildActive = item.subItems?.some(
327
+ (sub) => isPathActive(sub.path, location.pathname)
328
+ );
329
+ const isExpanded = expandedMenus[item.label];
330
+ return /* @__PURE__ */ jsx2(
331
+ SidebarItem,
332
+ {
333
+ item,
334
+ isActive,
335
+ isChildActive: !!isChildActive,
336
+ isExpanded: !!isExpanded,
337
+ isSidebarCollapsed: !!isCollapsed,
338
+ onClick: () => {
339
+ if (item.path) navigate(item.path);
340
+ if (isCollapsed && !item.path) {
341
+ setIsSidebarOpen(true);
342
+ }
343
+ },
344
+ onToggleExpand: () => toggleMenu(item.label)
345
+ },
346
+ item.label
347
+ );
348
+ }) }) })
349
+ ]
350
+ }
351
+ ),
352
+ /* @__PURE__ */ jsxs2("div", { className: "flex-1 flex flex-col min-w-0 h-screen overflow-hidden", children: [
353
+ /* @__PURE__ */ jsxs2("header", { className: "sticky top-0 z-30 h-16 bg-white/80 dark:bg-gray-900/80 backdrop-blur-md border-b border-gray-200 dark:border-gray-800 px-4 sm:px-6 flex items-center justify-between flex-shrink-0", children: [
354
+ /* @__PURE__ */ jsx2("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ jsx2(
355
+ "button",
356
+ {
357
+ onClick: toggleSidebar,
358
+ className: "p-2 -ml-2 rounded-lg text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-800 transition-colors",
359
+ children: /* @__PURE__ */ jsx2(Menu, { size: 20 })
360
+ }
361
+ ) }),
362
+ /* @__PURE__ */ jsx2("div", { className: "flex items-center gap-2", children: userProfile && /* @__PURE__ */ jsxs2(Fragment, { children: [
363
+ /* @__PURE__ */ jsx2("div", { className: "w-px h-6 mx-2 bg-gray-200 dark:bg-gray-800" }),
364
+ userProfile
365
+ ] }) })
366
+ ] }),
367
+ /* @__PURE__ */ jsx2("main", { className: "flex-1 p-2 md:p-4 overflow-y-auto", children: /* @__PURE__ */ jsx2("div", { className: "max-w-7xl mx-auto", children }) })
368
+ ] })
369
+ ] });
370
+ };
371
+
372
+ // src/components/ThemeToggle.tsx
373
+ import { Sun, Moon } from "lucide-react";
374
+
375
+ // src/hooks/useTheme.ts
376
+ import { useState as useState2, useEffect as useEffect2 } from "react";
377
+ var useTheme = () => {
378
+ const [isDarkMode, setIsDarkMode] = useState2(() => {
379
+ if (typeof window !== "undefined") {
380
+ const savedTheme = localStorage.getItem("theme");
381
+ if (savedTheme) {
382
+ return savedTheme === "dark";
383
+ }
384
+ return window.matchMedia("(prefers-color-scheme: dark)").matches;
385
+ }
386
+ return false;
387
+ });
388
+ useEffect2(() => {
389
+ const root = window.document.documentElement;
390
+ if (isDarkMode) {
391
+ root.classList.add("dark");
392
+ localStorage.setItem("theme", "dark");
393
+ } else {
394
+ root.classList.remove("dark");
395
+ localStorage.setItem("theme", "light");
396
+ }
397
+ }, [isDarkMode]);
398
+ const toggleTheme = () => setIsDarkMode((prev) => !prev);
399
+ return { isDarkMode, toggleTheme };
400
+ };
401
+
402
+ // src/components/ThemeToggle.tsx
403
+ import { jsx as jsx3 } from "react/jsx-runtime";
404
+ var ThemeToggle = ({
405
+ className
406
+ }) => {
407
+ const { isDarkMode, toggleTheme } = useTheme();
408
+ return /* @__PURE__ */ jsx3(
409
+ "button",
410
+ {
411
+ onClick: toggleTheme,
412
+ className: cn(
413
+ "p-2 rounded-lg transition-all duration-200",
414
+ "text-slate-500 hover:text-blue-600 hover:bg-slate-100",
415
+ "dark:text-yellow-400 dark:hover:text-yellow-300 dark:hover:bg-slate-800",
416
+ className
417
+ ),
418
+ "aria-label": "Toggle dark mode",
419
+ children: isDarkMode ? /* @__PURE__ */ jsx3(
420
+ Sun,
421
+ {
422
+ size: 20,
423
+ className: "stroke-2 transition-transform duration-300 rotate-0 scale-100"
424
+ }
425
+ ) : /* @__PURE__ */ jsx3(
426
+ Moon,
427
+ {
428
+ size: 20,
429
+ className: "stroke-2 transition-transform duration-300 rotate-0 scale-100"
430
+ }
431
+ )
432
+ }
433
+ );
434
+ };
435
+ export {
436
+ Button,
437
+ DashboardLayout,
438
+ ThemeToggle,
439
+ useTheme
440
+ };
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "warqadui",
3
+ "version": "0.0.3",
4
+ "main": "./dist/index.js",
5
+ "module": "./dist/index.mjs",
6
+ "types": "./dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.mjs",
11
+ "require": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsup src/index.ts --format esm,cjs --dts",
19
+ "dev": "tsup src/index.ts --format esm,cjs --dts --watch",
20
+ "prepublish": "npm run build"
21
+ },
22
+ "peerDependencies": {
23
+ "antd": "^6.3.1",
24
+ "framer-motion": ">=10",
25
+ "react": ">=18",
26
+ "react-dom": ">=18",
27
+ "react-router-dom": ">=6",
28
+ "tailwindcss": ">=3.4.0"
29
+ },
30
+ "dependencies": {
31
+ "clsx": "^2.1.0",
32
+ "dayjs": "^1.11.10",
33
+ "lucide-react": "^0.344.0",
34
+ "moment": "^2.30.1",
35
+ "tailwind-merge": "^2.2.1",
36
+ "zod": "^3.22.4",
37
+ "zustand": "^4.5.0"
38
+ },
39
+ "devDependencies": {
40
+ "@types/react": "^18.2.55",
41
+ "@types/react-dom": "^18.2.19",
42
+ "@types/react-router-dom": "^5.3.3",
43
+ "antd": "^6.3.1",
44
+ "autoprefixer": "^10.4.17",
45
+ "clsx": "^2.1.0",
46
+ "dayjs": "^1.11.10",
47
+ "lucide-react": "^0.344.0",
48
+ "moment": "^2.30.1",
49
+ "postcss": "^8.4.35",
50
+ "tailwind-merge": "^2.2.1",
51
+ "tailwindcss": "^3.4.1",
52
+ "tsup": "^8.0.2",
53
+ "typescript": "^5.3.3",
54
+ "zod": "^3.22.4",
55
+ "zustand": "^4.5.0"
56
+ }
57
+ }