warqadui 0.0.8 → 0.0.10

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.
Files changed (166) hide show
  1. package/package.json +62 -10
  2. package/.vscode/settings.json +0 -3
  3. package/apps/dev-app/.env +0 -1
  4. package/apps/dev-app/errors.log +0 -0
  5. package/apps/dev-app/index.html +0 -12
  6. package/apps/dev-app/node_modules/.vite/deps/@tanstack_react-table.js +0 -3254
  7. package/apps/dev-app/node_modules/.vite/deps/@tanstack_react-table.js.map +0 -7
  8. package/apps/dev-app/node_modules/.vite/deps/_metadata.json +0 -166
  9. package/apps/dev-app/node_modules/.vite/deps/antd.js +0 -108982
  10. package/apps/dev-app/node_modules/.vite/deps/antd.js.map +0 -7
  11. package/apps/dev-app/node_modules/.vite/deps/axios.js +0 -2751
  12. package/apps/dev-app/node_modules/.vite/deps/axios.js.map +0 -7
  13. package/apps/dev-app/node_modules/.vite/deps/chunk-5OG7DCD7.js +0 -41
  14. package/apps/dev-app/node_modules/.vite/deps/chunk-5OG7DCD7.js.map +0 -7
  15. package/apps/dev-app/node_modules/.vite/deps/chunk-DC5AMYBS.js +0 -39
  16. package/apps/dev-app/node_modules/.vite/deps/chunk-DC5AMYBS.js.map +0 -7
  17. package/apps/dev-app/node_modules/.vite/deps/chunk-DKXRQMOD.js +0 -135
  18. package/apps/dev-app/node_modules/.vite/deps/chunk-DKXRQMOD.js.map +0 -7
  19. package/apps/dev-app/node_modules/.vite/deps/chunk-EL47BWQR.js +0 -37
  20. package/apps/dev-app/node_modules/.vite/deps/chunk-EL47BWQR.js.map +0 -7
  21. package/apps/dev-app/node_modules/.vite/deps/chunk-HHL3MHGV.js +0 -288
  22. package/apps/dev-app/node_modules/.vite/deps/chunk-HHL3MHGV.js.map +0 -7
  23. package/apps/dev-app/node_modules/.vite/deps/chunk-IGGUWUPT.js +0 -60
  24. package/apps/dev-app/node_modules/.vite/deps/chunk-IGGUWUPT.js.map +0 -7
  25. package/apps/dev-app/node_modules/.vite/deps/chunk-IGXZPJXT.js +0 -928
  26. package/apps/dev-app/node_modules/.vite/deps/chunk-IGXZPJXT.js.map +0 -7
  27. package/apps/dev-app/node_modules/.vite/deps/chunk-L2GCM37S.js +0 -21628
  28. package/apps/dev-app/node_modules/.vite/deps/chunk-L2GCM37S.js.map +0 -7
  29. package/apps/dev-app/node_modules/.vite/deps/chunk-M7DZDBHW.js +0 -14
  30. package/apps/dev-app/node_modules/.vite/deps/chunk-M7DZDBHW.js.map +0 -7
  31. package/apps/dev-app/node_modules/.vite/deps/chunk-S54SBVCU.js +0 -1906
  32. package/apps/dev-app/node_modules/.vite/deps/chunk-S54SBVCU.js.map +0 -7
  33. package/apps/dev-app/node_modules/.vite/deps/chunk-WFNHCR67.js +0 -21
  34. package/apps/dev-app/node_modules/.vite/deps/chunk-WFNHCR67.js.map +0 -7
  35. package/apps/dev-app/node_modules/.vite/deps/clsx.js +0 -10
  36. package/apps/dev-app/node_modules/.vite/deps/clsx.js.map +0 -7
  37. package/apps/dev-app/node_modules/.vite/deps/dayjs.js +0 -6
  38. package/apps/dev-app/node_modules/.vite/deps/dayjs.js.map +0 -7
  39. package/apps/dev-app/node_modules/.vite/deps/dayjs_plugin_customParseFormat.js +0 -6
  40. package/apps/dev-app/node_modules/.vite/deps/dayjs_plugin_customParseFormat.js.map +0 -7
  41. package/apps/dev-app/node_modules/.vite/deps/framer-motion.js +0 -12388
  42. package/apps/dev-app/node_modules/.vite/deps/framer-motion.js.map +0 -7
  43. package/apps/dev-app/node_modules/.vite/deps/html2canvas-pro.js +0 -9713
  44. package/apps/dev-app/node_modules/.vite/deps/html2canvas-pro.js.map +0 -7
  45. package/apps/dev-app/node_modules/.vite/deps/html2canvas.esm-IYRWPPEI.js +0 -7808
  46. package/apps/dev-app/node_modules/.vite/deps/html2canvas.esm-IYRWPPEI.js.map +0 -7
  47. package/apps/dev-app/node_modules/.vite/deps/index.es-3WTXOFZ2.js +0 -10392
  48. package/apps/dev-app/node_modules/.vite/deps/index.es-3WTXOFZ2.js.map +0 -7
  49. package/apps/dev-app/node_modules/.vite/deps/jspdf.js +0 -14806
  50. package/apps/dev-app/node_modules/.vite/deps/jspdf.js.map +0 -7
  51. package/apps/dev-app/node_modules/.vite/deps/lucide-react.js +0 -31586
  52. package/apps/dev-app/node_modules/.vite/deps/lucide-react.js.map +0 -7
  53. package/apps/dev-app/node_modules/.vite/deps/package.json +0 -3
  54. package/apps/dev-app/node_modules/.vite/deps/purify.es-JNLDEIMX.js +0 -1029
  55. package/apps/dev-app/node_modules/.vite/deps/purify.es-JNLDEIMX.js.map +0 -7
  56. package/apps/dev-app/node_modules/.vite/deps/react-dom.js +0 -7
  57. package/apps/dev-app/node_modules/.vite/deps/react-dom.js.map +0 -7
  58. package/apps/dev-app/node_modules/.vite/deps/react-dom_client.js +0 -8
  59. package/apps/dev-app/node_modules/.vite/deps/react-dom_client.js.map +0 -7
  60. package/apps/dev-app/node_modules/.vite/deps/react-hook-form.js +0 -2233
  61. package/apps/dev-app/node_modules/.vite/deps/react-hook-form.js.map +0 -7
  62. package/apps/dev-app/node_modules/.vite/deps/react-phone-number-input.js +0 -9307
  63. package/apps/dev-app/node_modules/.vite/deps/react-phone-number-input.js.map +0 -7
  64. package/apps/dev-app/node_modules/.vite/deps/react-router-dom.js +0 -14234
  65. package/apps/dev-app/node_modules/.vite/deps/react-router-dom.js.map +0 -7
  66. package/apps/dev-app/node_modules/.vite/deps/react.js +0 -6
  67. package/apps/dev-app/node_modules/.vite/deps/react.js.map +0 -7
  68. package/apps/dev-app/node_modules/.vite/deps/react_jsx-dev-runtime.js +0 -913
  69. package/apps/dev-app/node_modules/.vite/deps/react_jsx-dev-runtime.js.map +0 -7
  70. package/apps/dev-app/node_modules/.vite/deps/react_jsx-runtime.js +0 -7
  71. package/apps/dev-app/node_modules/.vite/deps/react_jsx-runtime.js.map +0 -7
  72. package/apps/dev-app/node_modules/.vite/deps/tailwind-merge.js +0 -2534
  73. package/apps/dev-app/node_modules/.vite/deps/tailwind-merge.js.map +0 -7
  74. package/apps/dev-app/node_modules/tailwindcss/LICENSE +0 -21
  75. package/apps/dev-app/node_modules/tailwindcss/README.md +0 -36
  76. package/apps/dev-app/node_modules/tailwindcss/dist/chunk-L5IEUH3R.mjs +0 -38
  77. package/apps/dev-app/node_modules/tailwindcss/dist/chunk-UWKE2Z6N.mjs +0 -1
  78. package/apps/dev-app/node_modules/tailwindcss/dist/chunk-X4GG3EDV.mjs +0 -1
  79. package/apps/dev-app/node_modules/tailwindcss/dist/colors-C__qRT83.d.ts +0 -347
  80. package/apps/dev-app/node_modules/tailwindcss/dist/colors.d.mts +0 -347
  81. package/apps/dev-app/node_modules/tailwindcss/dist/colors.d.ts +0 -5
  82. package/apps/dev-app/node_modules/tailwindcss/dist/colors.js +0 -1
  83. package/apps/dev-app/node_modules/tailwindcss/dist/colors.mjs +0 -1
  84. package/apps/dev-app/node_modules/tailwindcss/dist/default-theme.d.mts +0 -1199
  85. package/apps/dev-app/node_modules/tailwindcss/dist/default-theme.d.ts +0 -1199
  86. package/apps/dev-app/node_modules/tailwindcss/dist/default-theme.js +0 -1
  87. package/apps/dev-app/node_modules/tailwindcss/dist/default-theme.mjs +0 -1
  88. package/apps/dev-app/node_modules/tailwindcss/dist/flatten-color-palette.d.mts +0 -6
  89. package/apps/dev-app/node_modules/tailwindcss/dist/flatten-color-palette.d.ts +0 -6
  90. package/apps/dev-app/node_modules/tailwindcss/dist/flatten-color-palette.js +0 -3
  91. package/apps/dev-app/node_modules/tailwindcss/dist/flatten-color-palette.mjs +0 -1
  92. package/apps/dev-app/node_modules/tailwindcss/dist/lib.d.mts +0 -378
  93. package/apps/dev-app/node_modules/tailwindcss/dist/lib.d.ts +0 -3
  94. package/apps/dev-app/node_modules/tailwindcss/dist/lib.js +0 -38
  95. package/apps/dev-app/node_modules/tailwindcss/dist/lib.mjs +0 -1
  96. package/apps/dev-app/node_modules/tailwindcss/dist/plugin.d.mts +0 -11
  97. package/apps/dev-app/node_modules/tailwindcss/dist/plugin.d.ts +0 -134
  98. package/apps/dev-app/node_modules/tailwindcss/dist/plugin.js +0 -1
  99. package/apps/dev-app/node_modules/tailwindcss/dist/plugin.mjs +0 -1
  100. package/apps/dev-app/node_modules/tailwindcss/dist/resolve-config-B4yBzhca.d.ts +0 -29
  101. package/apps/dev-app/node_modules/tailwindcss/dist/resolve-config-QUZ9b-Gn.d.mts +0 -190
  102. package/apps/dev-app/node_modules/tailwindcss/dist/types-CJYAW1ql.d.mts +0 -128
  103. package/apps/dev-app/node_modules/tailwindcss/index.css +0 -944
  104. package/apps/dev-app/node_modules/tailwindcss/package.json +0 -89
  105. package/apps/dev-app/node_modules/tailwindcss/preflight.css +0 -393
  106. package/apps/dev-app/node_modules/tailwindcss/theme.css +0 -510
  107. package/apps/dev-app/node_modules/tailwindcss/utilities.css +0 -1
  108. package/apps/dev-app/package.json +0 -34
  109. package/apps/dev-app/src/App.tsx +0 -74
  110. package/apps/dev-app/src/index.css +0 -18
  111. package/apps/dev-app/src/main.tsx +0 -18
  112. package/apps/dev-app/src/pages/Buttons.tsx +0 -122
  113. package/apps/dev-app/src/pages/DataTable.tsx +0 -208
  114. package/apps/dev-app/src/pages/Fields.tsx +0 -342
  115. package/apps/dev-app/src/pages/Modals.tsx +0 -151
  116. package/apps/dev-app/src/pages/Spins.tsx +0 -161
  117. package/apps/dev-app/ts_errors.txt +0 -0
  118. package/apps/dev-app/tsconfig.json +0 -25
  119. package/apps/dev-app/tsconfig.node.json +0 -10
  120. package/apps/dev-app/vite.config.ts +0 -11
  121. package/packages/ui/log.txt +0 -0
  122. package/packages/ui/package.json +0 -70
  123. package/packages/ui/postcss.config.js +0 -6
  124. package/packages/ui/src/components/Button.tsx +0 -85
  125. package/packages/ui/src/components/Card.tsx +0 -97
  126. package/packages/ui/src/components/CodeBlock.tsx +0 -53
  127. package/packages/ui/src/components/DashboardLayout.tsx +0 -442
  128. package/packages/ui/src/components/Fields/Input.tsx +0 -191
  129. package/packages/ui/src/components/Fields/PhoneInput.tsx +0 -134
  130. package/packages/ui/src/components/Fields/date.tsx +0 -165
  131. package/packages/ui/src/components/Fields/index.tsx +0 -17
  132. package/packages/ui/src/components/Fields/searchApi.tsx +0 -479
  133. package/packages/ui/src/components/Fields/select.tsx +0 -131
  134. package/packages/ui/src/components/Fields/textArea.tsx +0 -121
  135. package/packages/ui/src/components/LoadingBox.tsx +0 -11
  136. package/packages/ui/src/components/PageHeader.tsx +0 -34
  137. package/packages/ui/src/components/ThemeToggle.tsx +0 -35
  138. package/packages/ui/src/components/modal/Modal.tsx +0 -81
  139. package/packages/ui/src/components/spins/ClassicSpin.tsx +0 -18
  140. package/packages/ui/src/components/spins/LoadingSpin.tsx +0 -45
  141. package/packages/ui/src/components/spins/OverlaySpin.tsx +0 -10
  142. package/packages/ui/src/components/spins/index.tsx +0 -13
  143. package/packages/ui/src/components/tables/DataTable.tsx +0 -261
  144. package/packages/ui/src/components/tables/index.ts +0 -1
  145. package/packages/ui/src/hooks/Fetches/useApis.tsx +0 -197
  146. package/packages/ui/src/hooks/ThemeContext.tsx +0 -56
  147. package/packages/ui/src/hooks/useModal.tsx +0 -38
  148. package/packages/ui/src/hooks/useTheme.ts +0 -34
  149. package/packages/ui/src/index.ts +0 -24
  150. package/packages/ui/src/providers/WarqadProvider.tsx +0 -69
  151. package/packages/ui/src/styles.css +0 -26
  152. package/packages/ui/src/utils/cn.ts +0 -6
  153. package/packages/ui/src/utils/pdf.ts +0 -171
  154. package/packages/ui/tailwind.config.js +0 -13
  155. package/packages/ui/tsconfig.json +0 -17
  156. package/packages/ui/warqad-ui-0.0.1.tgz +0 -0
  157. package/packages/ui/warqadui-0.0.3.tgz +0 -0
  158. package/warqad-ui-0.0.1.tgz +0 -0
  159. /package/{packages/ui/dist → dist}/index.d.mts +0 -0
  160. /package/{packages/ui/dist → dist}/index.d.ts +0 -0
  161. /package/{packages/ui/dist → dist}/index.js +0 -0
  162. /package/{packages/ui/dist → dist}/index.mjs +0 -0
  163. /package/{packages/ui/dist → dist}/styles.d.mts +0 -0
  164. /package/{packages/ui/dist → dist}/styles.d.ts +0 -0
  165. /package/{packages/ui/dist → dist}/styles.js +0 -0
  166. /package/{packages/ui/dist → dist}/styles.mjs +0 -0
@@ -1,442 +0,0 @@
1
- import React, { useState, useEffect, useRef } from "react";
2
- import { useLocation, useNavigate } from "react-router-dom";
3
- import { Menu, ChevronDown, ChevronRight } from "lucide-react";
4
- import { motion, AnimatePresence } from "framer-motion";
5
- import { cn } from "../utils/cn";
6
-
7
- // --- Types ---
8
- export interface NavItem {
9
- label: string;
10
- icon?: React.ReactNode;
11
- path?: string;
12
- subItems?: (NavItem | false | null | undefined)[];
13
- hidden?: boolean;
14
- disabled?: boolean;
15
- }
16
-
17
- export type NavItems = (NavItem | false | null | undefined)[];
18
-
19
- interface DashboardLayoutProps {
20
- children: React.ReactNode;
21
- navItems: NavItems;
22
- title?: string;
23
- logo?: React.ReactNode;
24
- userProfile?: React.ReactNode;
25
- fontFamily?: string;
26
- }
27
-
28
- // --- Hooks ---
29
- const useMediaQuery = (query: string) => {
30
- const [matches, setMatches] = useState(() => {
31
- if (typeof window !== "undefined") {
32
- return window.matchMedia(query).matches;
33
- }
34
- return false;
35
- });
36
-
37
- useEffect(() => {
38
- const media = window.matchMedia(query);
39
- if (media.matches !== matches) {
40
- setMatches(media.matches);
41
- }
42
- const listener = () => setMatches(media.matches);
43
- media.addEventListener("change", listener);
44
- return () => media.removeEventListener("change", listener);
45
- }, [matches, query]);
46
-
47
- return matches;
48
- };
49
-
50
- const isPathActive = (navPath?: string, currentPath?: string) => {
51
- if (!navPath || !currentPath) return false;
52
- return (
53
- currentPath === navPath ||
54
- (navPath !== "/" && currentPath.startsWith(`${navPath}/`))
55
- );
56
- };
57
-
58
- // --- Components ---
59
-
60
- // 1. Sidebar Item
61
- const SidebarItem = ({
62
- item,
63
- isActive,
64
- isChildActive,
65
- onClick,
66
- isExpanded,
67
- onToggleExpand,
68
- isSidebarCollapsed,
69
- }: {
70
- item: NavItem;
71
- isActive: boolean;
72
- isChildActive?: boolean;
73
- onClick: () => void;
74
- isExpanded: boolean;
75
- onToggleExpand: () => void;
76
- isSidebarCollapsed: boolean;
77
- }) => {
78
- const hasSubItems = item.subItems && item.subItems.length > 0;
79
-
80
- // Decide if the parent item itself should be highlighted
81
- const shouldHighlight =
82
- isActive || (hasSubItems && isExpanded) || isChildActive;
83
-
84
- return (
85
- <div className="mb-1 group relative ">
86
- <button
87
- type="button"
88
- onClick={
89
- item.disabled ? undefined : hasSubItems ? onToggleExpand : onClick
90
- }
91
- disabled={item.disabled}
92
- title={isSidebarCollapsed ? item.label : undefined}
93
- className={cn(
94
- "w-full flex items-center py-2.5 rounded-xl text-sm font-medium transition-all duration-200",
95
- isSidebarCollapsed ? "justify-center px-2" : "justify-between px-3",
96
- shouldHighlight && !item.disabled
97
- ? "bg-blue-50 text-blue-700 dark:bg-blue-900/20 dark:text-blue-400"
98
- : "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",
99
- item.disabled && "opacity-50 cursor-not-allowed grayscale-[0.5]",
100
- )}
101
- >
102
- <div
103
- className={cn(
104
- "flex items-center gap-3 overflow-hidden",
105
- isSidebarCollapsed ? "justify-center w-full" : "",
106
- )}
107
- >
108
- {item.icon && (
109
- <span
110
- className={cn(
111
- "flex-shrink-0 transition-colors [&_svg]:w-4 [&_svg]:h-4",
112
- shouldHighlight && !item.disabled
113
- ? "text-blue-600 dark:text-blue-400"
114
- : "text-gray-400 group-hover:text-blue-500 dark:text-gray-500 dark:group-hover:text-blue-400",
115
- item.disabled && "group-hover:text-gray-400",
116
- )}
117
- >
118
- {item.icon}
119
- </span>
120
- )}
121
-
122
- <AnimatePresence>
123
- {!isSidebarCollapsed && (
124
- <motion.span
125
- initial={{ opacity: 0, width: 0 }}
126
- animate={{ opacity: 1, width: "auto" }}
127
- exit={{ opacity: 0, width: 0 }}
128
- transition={{ duration: 0.2 }}
129
- className="truncate"
130
- >
131
- {item.label}
132
- </motion.span>
133
- )}
134
- </AnimatePresence>
135
- </div>
136
-
137
- {!isSidebarCollapsed && hasSubItems && (
138
- <span
139
- className={cn(
140
- "flex-shrink-0 ml-2 transition-transform duration-200",
141
- shouldHighlight ? "text-blue-500" : "text-gray-400",
142
- )}
143
- >
144
- {isExpanded ? (
145
- <ChevronDown size={16} />
146
- ) : (
147
- <ChevronRight size={16} />
148
- )}
149
- </span>
150
- )}
151
- </button>
152
-
153
- {/* Submenu */}
154
- <AnimatePresence initial={false}>
155
- {!isSidebarCollapsed && hasSubItems && isExpanded && !item.disabled && (
156
- <motion.div
157
- initial={{ height: 0, opacity: 0 }}
158
- animate={{ height: "auto", opacity: 1 }}
159
- exit={{ height: 0, opacity: 0 }}
160
- transition={{ duration: 0.2 }}
161
- className="overflow-hidden"
162
- >
163
- <div className="relative ml-6 mt-1 space-y-1">
164
- {/* Vertical Line for tree structure */}
165
- <div className="absolute left-0 top-0 bottom-0 w-px bg-gray-200 dark:bg-gray-800" />
166
-
167
- {item
168
- .subItems!.filter((sub): sub is NavItem => !!sub && !sub.hidden)
169
- .map((subItem, index) => (
170
- <SubItem
171
- key={index}
172
- item={subItem}
173
- parentDisabled={item.disabled}
174
- />
175
- ))}
176
- </div>
177
- </motion.div>
178
- )}
179
- </AnimatePresence>
180
- </div>
181
- );
182
- };
183
-
184
- // 2. Sub Item Helper
185
- const SubItem = ({
186
- item,
187
- parentDisabled,
188
- }: {
189
- item: NavItem;
190
- parentDisabled?: boolean;
191
- }) => {
192
- const location = useLocation();
193
- const navigate = useNavigate();
194
- const isActive = isPathActive(item.path, location.pathname);
195
- const isDisabled = item.disabled || parentDisabled;
196
-
197
- return (
198
- <button
199
- onClick={() => !isDisabled && item.path && navigate(item.path)}
200
- disabled={isDisabled}
201
- className={cn(
202
- "relative w-full flex items-center px-3 py-2 pl-6 rounded-lg text-sm transition-colors duration-200",
203
- isActive && !isDisabled
204
- ? "bg-blue-100/50 text-blue-700 font-medium dark:bg-blue-900/30 dark:text-blue-400"
205
- : "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",
206
- isDisabled && "opacity-50 cursor-not-allowed grayscale-[0.5]",
207
- )}
208
- >
209
- <span className="truncate">{item.label}</span>
210
- {isActive && !isDisabled && (
211
- <div className="absolute left-0 top-1/2 -translate-y-1/2 w-1 h-4 bg-blue-500 rounded-r-full" />
212
- )}
213
- </button>
214
- );
215
- };
216
-
217
- // --- Main Layout Component ---
218
- export const DashboardLayout: React.FC<DashboardLayoutProps> = ({
219
- children,
220
- navItems,
221
- title = "Dashboard",
222
- logo,
223
- userProfile,
224
- fontFamily = 'system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"', // Humanist System Stack
225
- }) => {
226
- const isDesktop = useMediaQuery("(min-width: 1024px)");
227
- const location = useLocation();
228
- const navigate = useNavigate();
229
- const [isSidebarOpen, setIsSidebarOpen] = useState(true);
230
- const [expandedMenus, setExpandedMenus] = useState<Record<string, boolean>>(
231
- () => {
232
- const initialState: Record<string, boolean> = {};
233
- navItems.forEach((item) => {
234
- if (
235
- item &&
236
- !item.hidden &&
237
- !item.disabled &&
238
- item.subItems?.some(
239
- (sub) =>
240
- sub && !sub.hidden && isPathActive(sub.path, location.pathname),
241
- )
242
- ) {
243
- initialState[item.label] = true;
244
- }
245
- });
246
- return initialState;
247
- },
248
- );
249
-
250
- const prevPathRef = useRef(location.pathname);
251
-
252
- useEffect(() => {
253
- if (prevPathRef.current !== location.pathname) {
254
- const activeItem = navItems.find(
255
- (item): item is NavItem =>
256
- !!item &&
257
- !item.hidden &&
258
- !item.disabled &&
259
- item.subItems?.some(
260
- (sub) =>
261
- sub && !sub.hidden && isPathActive(sub.path, location.pathname),
262
- ) === true,
263
- );
264
- if (activeItem) {
265
- setExpandedMenus((prev) => ({ ...prev, [activeItem.label]: true }));
266
- }
267
- prevPathRef.current = location.pathname;
268
- }
269
- }, [location.pathname, navItems]);
270
-
271
- const isCollapsed = isDesktop && !isSidebarOpen;
272
-
273
- // Auto-close sidebar on mobile route change
274
- useEffect(() => {
275
- if (!isDesktop) {
276
- setIsSidebarOpen(false);
277
- } else {
278
- setIsSidebarOpen(true);
279
- }
280
- }, [isDesktop]);
281
-
282
- const toggleSidebar = () => setIsSidebarOpen(!isSidebarOpen);
283
-
284
- // Accordion behavior: Close others when opening a new one
285
- const toggleMenu = (label: string) => {
286
- if (isCollapsed) {
287
- setIsSidebarOpen(true);
288
- setTimeout(() => {
289
- setExpandedMenus((prev) => {
290
- const isCurrentlyOpen = !!prev[label];
291
- return isCurrentlyOpen ? {} : { [label]: true };
292
- });
293
- }, 50);
294
- return;
295
- }
296
- setExpandedMenus((prev) => {
297
- const isCurrentlyOpen = !!prev[label];
298
- return isCurrentlyOpen ? {} : { [label]: true };
299
- });
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", // Mini Sidebar Width
309
- transition: { duration: 0.3, type: "spring", bounce: 0, damping: 20 },
310
- },
311
- mobileOpen: {
312
- x: 0,
313
- transition: { duration: 0.3, type: "spring", bounce: 0, damping: 20 },
314
- },
315
- mobileClosed: {
316
- x: "-100%",
317
- transition: { duration: 0.3, type: "spring", bounce: 0, damping: 20 },
318
- },
319
- };
320
-
321
- return (
322
- <div
323
- className="min-h-screen bg-gray-50 dark:bg-[#0B0F1A] text-slate-900 dark:text-slate-100 flex transition-colors duration-500 font-sans overflow-hidden"
324
- style={{ fontFamily }}
325
- >
326
- {/* Mobile Overlay */}
327
- <AnimatePresence>
328
- {!isDesktop && isSidebarOpen && (
329
- <motion.div
330
- initial={{ opacity: 0 }}
331
- animate={{ opacity: 0.5 }}
332
- exit={{ opacity: 0 }}
333
- onClick={() => setIsSidebarOpen(false)}
334
- className="fixed inset-0 bg-black/50 z-40 lg:hidden backdrop-blur-sm"
335
- />
336
- )}
337
- </AnimatePresence>
338
- {/* Sidebar */}
339
- <motion.aside
340
- 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"
341
- initial={isDesktop ? "desktopOpen" : "mobileClosed"}
342
- animate={
343
- isDesktop
344
- ? isSidebarOpen
345
- ? "desktopOpen"
346
- : "desktopClosed"
347
- : isSidebarOpen
348
- ? "mobileOpen"
349
- : "mobileClosed"
350
- }
351
- variants={sidebarVariants as any}
352
- >
353
- <div
354
- className={cn(
355
- "h-16 flex items-center border-b border-gray-100 dark:border-gray-800 flex-shrink-0 overflow-hidden transition-all duration-300",
356
- isCollapsed ? "justify-center px-0" : "px-6",
357
- )}
358
- >
359
- <div className="flex items-center gap-2 text-blue-600 dark:text-blue-400">
360
- {isCollapsed ? (
361
- logo
362
- ) : (
363
- <>
364
- {logo}
365
- <span className="font-bold text-lg tracking-tight text-gray-900 dark:text-white">
366
- {title}
367
- </span>
368
- </>
369
- )}
370
- </div>
371
- </div>
372
-
373
- <div className="flex-1 p-4 overflow-y-auto overflow-x-hidden scrollbar-hide">
374
- <nav className="space-y-1">
375
- {navItems
376
- .filter((item): item is NavItem => !!item && !item.hidden)
377
- .map((item) => {
378
- const isActive = isPathActive(item.path, location.pathname);
379
-
380
- const isChildActive = item.subItems?.some(
381
- (sub) =>
382
- sub &&
383
- !sub.hidden &&
384
- isPathActive(sub.path, location.pathname),
385
- );
386
-
387
- const isExpanded = expandedMenus[item.label];
388
-
389
- return (
390
- <SidebarItem
391
- key={item.label}
392
- item={item}
393
- isActive={isActive}
394
- isChildActive={!!isChildActive}
395
- isExpanded={!!isExpanded}
396
- isSidebarCollapsed={!!isCollapsed}
397
- onClick={() => {
398
- if (item.path) navigate(item.path);
399
- if (isCollapsed && !item.path) {
400
- setIsSidebarOpen(true);
401
- }
402
- }}
403
- onToggleExpand={() => toggleMenu(item.label)}
404
- />
405
- );
406
- })}
407
- </nav>
408
- </div>
409
- </motion.aside>
410
-
411
- {/* Main Content Wrapper */}
412
- <div className="flex-1 flex flex-col min-w-0 h-screen overflow-hidden">
413
- {/* Header */}
414
- <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">
415
- <div className="flex items-center gap-4">
416
- <button
417
- onClick={toggleSidebar}
418
- 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"
419
- >
420
- <Menu size={20} />
421
- </button>
422
- </div>
423
-
424
- {/* Right Actions */}
425
- <div className="flex items-center gap-2">
426
- {userProfile && (
427
- <>
428
- <div className="w-px h-6 mx-2 bg-gray-200 dark:bg-gray-800" />
429
- {userProfile}
430
- </>
431
- )}
432
- </div>
433
- </header>
434
-
435
- {/* Body */}
436
- <main className="flex-1 p-2 md:p-4 overflow-y-auto">
437
- <div className="max-w-7xl mx-auto">{children}</div>
438
- </main>
439
- </div>
440
- </div>
441
- );
442
- };
@@ -1,191 +0,0 @@
1
- import { forwardRef, useEffect, useState } from "react";
2
- import { Eye, EyeOff } from "lucide-react";
3
- import { useWarqadConfig } from "../../providers/WarqadProvider";
4
- import {
5
- Controller,
6
- type FieldValues,
7
- type Path,
8
- type UseFormReturn,
9
- } from "react-hook-form";
10
-
11
- export interface InputProps<T extends FieldValues> extends Omit<
12
- React.InputHTMLAttributes<HTMLInputElement>,
13
- "name" | "form"
14
- > {
15
- label: string;
16
- icon?: React.ReactNode;
17
- error?: string;
18
- containerClassName?: string;
19
- name?: Path<T>;
20
- form?: UseFormReturn<T>;
21
- type?: "text" | "number" | "email" | "password";
22
- }
23
-
24
- export const Input = forwardRef(
25
- <T extends FieldValues>(
26
- {
27
- label,
28
- icon,
29
- error,
30
- containerClassName = "",
31
- name,
32
- form,
33
- onFocus,
34
- onBlur,
35
- className = "",
36
- type,
37
- ...props
38
- }: InputProps<T>,
39
- ref: React.ForwardedRef<HTMLInputElement>,
40
- ) => {
41
- const [isFocused, setIsFocused] = useState(false);
42
- const [showPassword, setShowPassword] = useState(false);
43
- const { theme } = useWarqadConfig();
44
- const primaryColor = theme?.primaryColor;
45
-
46
- let message = error;
47
- if (form && name) {
48
- const {
49
- formState: { errors },
50
- } = form;
51
- const errorObj = name
52
- .split(".")
53
- .reduce((acc: any, current: string) => acc?.[current], errors);
54
- message = (errorObj as any)?.message;
55
- }
56
-
57
- useEffect(() => {
58
- if (form && name) {
59
- form.clearErrors("root");
60
- }
61
- }, [form?.watch(name as any)]);
62
-
63
- const renderInput = (
64
- inputProps: any,
65
- localType: string = type || "text",
66
- ) => (
67
- <div className={`space-y-2 group ${containerClassName}`}>
68
- <label
69
- htmlFor={props.id}
70
- className={`block capitalize text-xs font-medium transition-colors duration-200`}
71
- style={{ color: isFocused ? primaryColor : undefined }}
72
- >
73
- {label}
74
- {props.required && <span className="text-red-500 ml-1">*</span>}
75
- </label>
76
- <div className="relative">
77
- {icon && (
78
- <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
79
- <div
80
- className={`transition-colors duration-200`}
81
- style={{ color: isFocused ? primaryColor : "#9ca3af" }}
82
- >
83
- {icon}
84
- </div>
85
- </div>
86
- )}
87
- <input
88
- ref={ref}
89
- onFocus={(e) => {
90
- setIsFocused(true);
91
- onFocus?.(e);
92
- }}
93
- onBlur={(e) => {
94
- setIsFocused(false);
95
- onBlur?.(e);
96
- }}
97
- className={`block w-full ${
98
- icon ? "pl-10" : "pl-3"
99
- } pr-3 h-8 py-0 rounded-lg border-gray-200 dark:border-zinc-800 bg-gray-50 dark:bg-zinc-900/50 text-gray-900 dark:text-white placeholder-gray-400 focus:bg-white dark:focus:bg-zinc-900 outline-none transition-all duration-200 border text-sm ${className}`}
100
- style={{
101
- borderColor: isFocused ? primaryColor : undefined,
102
- boxShadow: isFocused
103
- ? `${primaryColor}33 0px 0px 0px 2px`
104
- : undefined,
105
- }}
106
- {...props}
107
- {...inputProps}
108
- type={
109
- localType === "password"
110
- ? showPassword
111
- ? "text"
112
- : "password"
113
- : localType
114
- }
115
- />
116
- {type === "password" && (
117
- <button
118
- type="button"
119
- onClick={() => setShowPassword(!showPassword)}
120
- className="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 transition-colors focus:outline-none cursor-pointer"
121
- tabIndex={-1}
122
- >
123
- {showPassword ? (
124
- <EyeOff className="h-5 w-5" />
125
- ) : (
126
- <Eye className="h-5 w-5" />
127
- )}
128
- </button>
129
- )}
130
- </div>
131
- {message && (
132
- <p className="text-sm text-red-600 dark:text-red-400">{message}</p>
133
- )}
134
- </div>
135
- );
136
-
137
- if (form && name && type === "number") {
138
- return (
139
- <Controller
140
- control={form.control}
141
- name={name}
142
- render={({ field: { onChange, value, ref: fieldRef, onBlur } }) =>
143
- renderInput(
144
- {
145
- ref: fieldRef,
146
- onBlur: () => {
147
- onBlur();
148
- },
149
- value: value
150
- ? String(value)
151
- .split(".")
152
- .map((part, index) =>
153
- index === 0
154
- ? part.replace(/\B(?=(\d{3})+(?!\d))/g, ",")
155
- : part,
156
- )
157
- .join(".")
158
- : "",
159
- onChange: (e: React.ChangeEvent<HTMLInputElement>) => {
160
- const rawValue = e.target.value.replace(/,/g, "");
161
- if (!/^\d*\.?\d*$/.test(rawValue)) return;
162
-
163
- if (rawValue === "") {
164
- onChange("");
165
- } else {
166
- const numValue = Number(rawValue);
167
- if (!isNaN(numValue) && String(numValue) === rawValue) {
168
- onChange(numValue);
169
- } else {
170
- onChange(rawValue);
171
- }
172
- }
173
- props.onChange?.(e);
174
- },
175
- },
176
- "text",
177
- )
178
- }
179
- />
180
- );
181
- }
182
-
183
- return renderInput(
184
- form && name
185
- ? form.register(name, type === "number" ? { valueAsNumber: true } : {})
186
- : {},
187
- );
188
- },
189
- ) as <T extends FieldValues>(
190
- props: InputProps<T> & { ref?: React.ForwardedRef<HTMLInputElement> },
191
- ) => React.ReactElement;