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.
- package/dist/index.d.mts +35 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +470 -0
- package/dist/index.mjs +440 -0
- package/package.json +57 -0
package/dist/index.d.mts
ADDED
|
@@ -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.d.ts
ADDED
|
@@ -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
|
+
}
|