@imksh/create 1.0.28 → 1.0.31

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 (127) hide show
  1. package/package.json +1 -1
  2. package/src/utils/askTemplate.js +2 -1
  3. package/templates/layers/electron/base/electron/main.js +262 -0
  4. package/templates/layers/electron/base/electron/preload.js +37 -0
  5. package/templates/layers/electron/base/electron-builder.json5 +68 -0
  6. package/templates/layers/electron/javascript/_gitignore +38 -0
  7. package/templates/layers/electron/javascript/package.json +49 -0
  8. package/templates/layers/electron/javascript/src/components/layout/SidebarLayout.jsx +43 -0
  9. package/templates/layers/electron/javascript/src/components/ui/TitleBar.jsx +130 -0
  10. package/templates/layers/electron/javascript/src/config/appConfig.js +51 -0
  11. package/templates/layers/electron/javascript/src/hooks/useElectron.js +68 -0
  12. package/templates/layers/electron/javascript/src/main.jsx +17 -0
  13. package/templates/layers/electron/javascript/src/store/useUiStore.js +59 -0
  14. package/templates/layers/electron/javascript/vite.config.js +29 -0
  15. package/templates/layers/electron/typescript/_gitignore +38 -0
  16. package/templates/layers/electron/typescript/electron/main.ts +213 -0
  17. package/templates/layers/electron/typescript/electron/preload.ts +52 -0
  18. package/templates/layers/electron/typescript/package.json +52 -0
  19. package/templates/layers/electron/typescript/src/components/layout/SidebarLayout.tsx +42 -0
  20. package/templates/layers/electron/typescript/src/components/ui/TitleBar.tsx +120 -0
  21. package/templates/layers/electron/typescript/src/config/appConfig.ts +52 -0
  22. package/templates/layers/electron/typescript/src/hooks/useElectron.ts +68 -0
  23. package/templates/layers/electron/typescript/src/main.tsx +17 -0
  24. package/templates/layers/electron/typescript/src/store/useUiStore.ts +97 -0
  25. package/templates/layers/electron/typescript/src/vite-env.d.ts +13 -0
  26. package/templates/layers/electron/typescript/tsconfig.json +25 -0
  27. package/templates/layers/electron/typescript/tsconfig.node.json +11 -0
  28. package/templates/layers/electron/typescript/vite.config.ts +29 -0
  29. package/templates/layers/next/javascript/src/components/common/Breadcrumbs.jsx +44 -6
  30. package/templates/layers/next/javascript/src/components/common/Loading.jsx +1 -0
  31. package/templates/layers/next/javascript/src/components/common/LottieIcon.jsx +1 -0
  32. package/templates/layers/next/javascript/src/components/common/Pagination.jsx +170 -0
  33. package/templates/layers/next/javascript/src/components/common/SidebarModal.jsx +180 -0
  34. package/templates/layers/next/javascript/src/components/common/StatsCard.jsx +168 -0
  35. package/templates/layers/next/javascript/src/components/common/Table.jsx +191 -41
  36. package/templates/layers/next/javascript/src/components/common/Tabs.jsx +187 -0
  37. package/templates/layers/next/javascript/src/components/common/ToggleSwitch.jsx +4 -3
  38. package/templates/layers/next/javascript/src/components/layout/ProtectedRoute.jsx +2 -1
  39. package/templates/layers/next/javascript/src/components/layout/PublicLayout.jsx +3 -6
  40. package/templates/layers/next/javascript/src/components/layout/SidebarLayout.jsx +4 -17
  41. package/templates/layers/next/javascript/src/components/modals/ConfirmModal.jsx +4 -12
  42. package/templates/layers/next/javascript/src/components/modals/Modal.jsx +50 -19
  43. package/templates/layers/next/javascript/src/components/ui/Input.jsx +50 -0
  44. package/templates/layers/next/{typescript/src/components/ui/sidebar.tsx → javascript/src/components/ui/Sidebar.jsx} +65 -53
  45. package/templates/layers/next/javascript/src/components/ui/SidebarHeader.jsx +11 -6
  46. package/templates/layers/next/javascript/src/config/appConfig.js +4 -4
  47. package/templates/layers/next/javascript/src/hooks/useWindowSize.js +2 -3
  48. package/templates/layers/next/javascript/src/store/useUiStore.js +9 -4
  49. package/templates/layers/next/typescript/src/components/common/Breadcrumbs.tsx +57 -8
  50. package/templates/layers/next/typescript/src/components/common/Loading.tsx +1 -0
  51. package/templates/layers/next/typescript/src/components/common/Pagination.tsx +186 -0
  52. package/templates/layers/next/typescript/src/components/common/SidebarModal.tsx +200 -0
  53. package/templates/layers/next/typescript/src/components/common/StatsCard.tsx +232 -0
  54. package/templates/layers/next/typescript/src/components/common/Table.tsx +201 -35
  55. package/templates/layers/next/typescript/src/components/common/Tabs.tsx +190 -0
  56. package/templates/layers/next/typescript/src/components/common/ToggleSwitch.tsx +12 -8
  57. package/templates/layers/next/typescript/src/components/layout/PublicLayout.tsx +8 -6
  58. package/templates/layers/next/typescript/src/components/layout/SidebarLayout.tsx +8 -17
  59. package/templates/layers/next/typescript/src/components/modals/ConfirmModal.tsx +1 -0
  60. package/templates/layers/next/typescript/src/components/modals/Modal.tsx +67 -21
  61. package/templates/layers/next/typescript/src/components/ui/Button.tsx +1 -0
  62. package/templates/layers/next/typescript/src/components/ui/Input.tsx +40 -60
  63. package/templates/layers/next/{javascript/src/components/ui/sidebar.jsx → typescript/src/components/ui/Sidebar.tsx} +77 -44
  64. package/templates/layers/next/typescript/src/components/ui/SidebarHeader.tsx +16 -10
  65. package/templates/layers/next/typescript/src/config/appConfig.ts +4 -4
  66. package/templates/layers/next/typescript/src/hooks/useWindowSize.ts +2 -3
  67. package/templates/layers/next/typescript/src/store/useUiStore.ts +14 -4
  68. package/templates/layers/node/javascript/package.json +2 -1
  69. package/templates/layers/node/javascript/src/controllers/push.controller.js +35 -14
  70. package/templates/layers/node/javascript/src/models/pushSubscription.model.js +5 -0
  71. package/templates/layers/node/javascript/src/utils/pushHelper.js +59 -12
  72. package/templates/layers/node/typescript/package-lock.json +3795 -0
  73. package/templates/layers/node/typescript/package.json +3 -2
  74. package/templates/layers/node/typescript/src/controllers/push.controller.ts +35 -14
  75. package/templates/layers/node/typescript/src/models/pushSubscription.model.ts +6 -0
  76. package/templates/layers/node/typescript/src/services/auth.service.ts +3 -3
  77. package/templates/layers/node/typescript/src/utils/pushHelper.ts +66 -12
  78. package/templates/layers/node-prisma/javascript/package.json +6 -3
  79. package/templates/layers/node-prisma/javascript/prisma/schema.prisma +20 -9
  80. package/templates/layers/node-prisma/javascript/prisma.config.js +12 -0
  81. package/templates/layers/node-prisma/javascript/src/config/{db.js → prisma.js} +9 -1
  82. package/templates/layers/node-prisma/javascript/src/controllers/push.controller.js +58 -19
  83. package/templates/layers/node-prisma/javascript/src/utils/pushHelper.js +62 -14
  84. package/templates/layers/node-prisma/typescript/package-lock.json +4808 -0
  85. package/templates/layers/node-prisma/typescript/package.json +8 -4
  86. package/templates/layers/node-prisma/typescript/prisma/schema.prisma +20 -9
  87. package/templates/layers/node-prisma/typescript/prisma.config.ts +12 -0
  88. package/templates/layers/node-prisma/typescript/src/config/{db.ts → prisma.ts} +9 -1
  89. package/templates/layers/node-prisma/typescript/src/controllers/push.controller.ts +67 -22
  90. package/templates/layers/node-prisma/typescript/src/index.ts +1 -1
  91. package/templates/layers/node-prisma/typescript/src/middleware/auth.middleware.ts +15 -5
  92. package/templates/layers/node-prisma/typescript/src/services/auth.service.ts +134 -128
  93. package/templates/layers/node-prisma/typescript/src/utils/pushHelper.ts +69 -14
  94. package/templates/layers/prisma/typescript/prisma/schema.prisma +35 -10
  95. package/templates/layers/react/javascript/src/components/common/Breadcrumbs.jsx +62 -58
  96. package/templates/layers/react/javascript/src/components/common/Pagination.jsx +164 -76
  97. package/templates/layers/react/javascript/src/components/common/SidebarModal.jsx +179 -0
  98. package/templates/layers/react/javascript/src/components/common/StatsCard.jsx +167 -0
  99. package/templates/layers/react/javascript/src/components/common/Table.jsx +248 -132
  100. package/templates/layers/react/javascript/src/components/common/Tabs.jsx +164 -112
  101. package/templates/layers/react/javascript/src/components/common/ToggleSwitch.jsx +3 -3
  102. package/templates/layers/react/javascript/src/components/layout/SidebarLayout.jsx +4 -12
  103. package/templates/layers/react/javascript/src/components/modals/Modal.jsx +53 -18
  104. package/templates/layers/react/javascript/src/components/ui/Input.jsx +49 -0
  105. package/templates/layers/react/javascript/src/components/ui/Sidebar.jsx +215 -0
  106. package/templates/layers/react/javascript/src/components/ui/SidebarHeader.jsx +25 -30
  107. package/templates/layers/react/javascript/src/config/appConfig.js +3 -3
  108. package/templates/layers/react/javascript/src/hooks/useWindowSize.js +2 -2
  109. package/templates/layers/react/typescript/src/components/common/Breadcrumbs.tsx +32 -6
  110. package/templates/layers/react/typescript/src/components/common/Pagination.tsx +151 -57
  111. package/templates/layers/react/typescript/src/components/common/SidebarModal.tsx +199 -0
  112. package/templates/layers/react/typescript/src/components/common/StatsCard.tsx +231 -0
  113. package/templates/layers/react/typescript/src/components/common/Table.tsx +197 -34
  114. package/templates/layers/react/typescript/src/components/common/Tabs.tsx +56 -12
  115. package/templates/layers/react/typescript/src/components/common/ToggleSwitch.tsx +15 -3
  116. package/templates/layers/react/typescript/src/components/layout/SidebarLayout.tsx +3 -6
  117. package/templates/layers/react/typescript/src/components/modals/Modal.tsx +151 -0
  118. package/templates/layers/react/typescript/src/components/ui/Input.tsx +58 -0
  119. package/templates/layers/react/typescript/src/components/ui/Sidebar.tsx +116 -53
  120. package/templates/layers/react/typescript/src/components/ui/SidebarHeader.tsx +5 -4
  121. package/templates/layers/react/typescript/src/config/appConfig.ts +3 -3
  122. package/templates/layers/react/typescript/src/hooks/useWindowSize.ts +2 -3
  123. package/templates/layers/react-native/base/src/components/ui/BiometricLock.tsx +2 -1
  124. package/templates/layers/react-native/base/src/config/appConfig.ts +44 -0
  125. package/templates/presets/electron/preset.json +9 -0
  126. package/templates/presets/electron-ts/preset.json +9 -0
  127. package/templates/layers/react/javascript/src/components/ui/sidebar.jsx +0 -169
@@ -1,10 +1,12 @@
1
- import React from "react";
2
- import TableSkeleton from "../skeletons/common/TableSkeleton";
1
+ "use client";
2
+ import React, { useEffect, useRef } from "react";
3
+ import { Pagination } from "./Pagination";
3
4
 
4
5
  export interface TableColumn<T = any> {
5
- header: string;
6
+ header: React.ReactNode | string;
6
7
  accessor?: keyof T | string;
7
8
  render?: (value: any, row: T, index: number) => React.ReactNode;
9
+ headerClass?: string;
8
10
  }
9
11
 
10
12
  interface TableProps<T = any> {
@@ -16,6 +18,10 @@ interface TableProps<T = any> {
16
18
  currentPage?: number;
17
19
  totalPages?: number;
18
20
  onPageChange?: ((page: number) => void) | null;
21
+ wrapperClassName?: string;
22
+ renderCard?: (row: T, index: number) => React.ReactNode;
23
+ pagination?: boolean; // if true, uses load more/infinite scroll on mobile
24
+ header?: React.ReactNode; // custom header content to render above the table
19
25
  }
20
26
 
21
27
  const Table = <T extends Record<string, any>>({
@@ -27,43 +33,136 @@ const Table = <T extends Record<string, any>>({
27
33
  currentPage = 1,
28
34
  totalPages = 1,
29
35
  onPageChange = null,
36
+ wrapperClassName = "",
37
+ renderCard,
38
+ pagination = false,
39
+ header,
30
40
  }: TableProps<T>) => {
31
- if (loading) {
32
- return <TableSkeleton rows={5} columns={columns.length || 4} />;
33
- }
41
+ const observerTarget = useRef<HTMLDivElement>(null);
42
+
43
+ useEffect(() => {
44
+ if (!pagination || !onPageChange || currentPage >= totalPages) return;
45
+
46
+ const observer = new IntersectionObserver(
47
+ (entries) => {
48
+ if (entries[0].isIntersecting) {
49
+ onPageChange(currentPage + 1);
50
+ }
51
+ },
52
+ { threshold: 0.1 },
53
+ );
54
+
55
+ if (observerTarget.current) {
56
+ observer.observe(observerTarget.current);
57
+ }
58
+
59
+ return () => observer.disconnect();
60
+ }, [pagination, onPageChange, currentPage, totalPages]);
34
61
 
35
62
  return (
36
- <div className="flex flex-col w-full border border-base-300 rounded-xl overflow-hidden bg-base-100 shadow-sm">
37
- <div className="overflow-x-auto w-full">
38
- <table className="table table-hover w-full min-w-full">
39
- <thead className="bg-base-200/60 border-b border-base-300">
63
+ <div
64
+ className={`flex flex-col w-full h-full md:border md:border-base-300 rounded-xl overflow-hidden bg-base-100 ${wrapperClassName}`}
65
+ >
66
+ {/* Optional Custom Header Area */}
67
+ {header && (
68
+ <div className="shrink-0 bg-base-100 border-b border-base-300">
69
+ {header}
70
+ </div>
71
+ )}
72
+
73
+ {/* Desktop Table View (hidden on mobile if renderCard provided) */}
74
+ <div
75
+ className={`overflow-auto w-full flex-1 ${renderCard ? "hidden md:block" : "block"}`}
76
+ >
77
+ <table className="table table-hover w-full min-w-full relative border-collapse">
78
+ <thead className="bg-base-200 backdrop-blur sticky top-0 z-10 border-b border-base-300">
40
79
  <tr>
41
80
  {columns.map((col, index) => (
42
81
  <th
43
82
  key={(col.accessor as string) || index}
44
- className="px-6 py-4 text-left text-xs font-bold text-base-content/80 uppercase tracking-wider"
83
+ className={`px-6 py-4 text-left text-xs font-semibold text-base-content/70 uppercase tracking-wider bg-transparent border-none ${col.headerClass || ""}`}
45
84
  >
46
85
  {col.header}
47
86
  </th>
48
87
  ))}
49
88
  </tr>
50
89
  </thead>
51
- <tbody className="divide-y divide-base-300 bg-base-100">
52
- {data.length > 0 ? (
90
+ <tbody className="divide-y divide-base-200/60 bg-base-100">
91
+ {loading ? (
92
+ Array.from({ length: 5 }).map((_, rowIndex) => (
93
+ <tr key={`skeleton-${rowIndex}`} className="animate-pulse">
94
+ {columns.map((col, colIndex) => {
95
+ const isRight = col.headerClass?.includes("text-right");
96
+ const isFirst = colIndex === 0;
97
+ const isLast = colIndex === columns.length - 1;
98
+ const headerText =
99
+ typeof col.header === "string"
100
+ ? col.header.toLowerCase()
101
+ : "";
102
+
103
+ return (
104
+ <td
105
+ key={colIndex}
106
+ className={`px-6 py-4 whitespace-nowrap align-middle border-none ${col.headerClass || ""}`}
107
+ >
108
+ {isRight ||
109
+ (isLast && headerText.includes("action")) ? (
110
+ <div className="flex justify-end items-center gap-2">
111
+ <div className="w-7 h-7 rounded-lg bg-base-300/80"></div>
112
+ <div className="w-7 h-7 rounded-lg bg-base-300/80"></div>
113
+ </div>
114
+ ) : headerText.includes("assigned") ? (
115
+ <div className="h-6 w-28 rounded-md bg-base-200 border border-base-300/80"></div>
116
+ ) : isFirst ? (
117
+ <div className="space-y-1.5">
118
+ <div
119
+ className="h-4 bg-base-300/80 rounded"
120
+ style={{
121
+ width: `${65 + (rowIndex % 3) * 15}%`,
122
+ maxWidth: "140px",
123
+ }}
124
+ ></div>
125
+ <div
126
+ className="h-3 bg-base-200 rounded"
127
+ style={{
128
+ width: `${40 + (rowIndex % 2) * 15}%`,
129
+ maxWidth: "90px",
130
+ }}
131
+ ></div>
132
+ </div>
133
+ ) : (
134
+ <div
135
+ className={`h-4 bg-base-300/70 rounded ${isRight ? "ml-auto" : ""}`}
136
+ style={{
137
+ width: `${50 + ((rowIndex + colIndex) % 4) * 12}%`,
138
+ maxWidth: "160px",
139
+ }}
140
+ ></div>
141
+ )}
142
+ </td>
143
+ );
144
+ })}
145
+ </tr>
146
+ ))
147
+ ) : data.length > 0 ? (
53
148
  data.map((row, rowIndex) => (
54
149
  <tr
55
150
  key={row.id || rowIndex}
56
151
  onClick={() => onRowClick && onRowClick(row)}
57
- className={`transition-colors duration-150 ${
58
- onRowClick ? "cursor-pointer hover:bg-base-200" : ""
152
+ className={`transition-all duration-200 group ${
153
+ onRowClick
154
+ ? "cursor-pointer hover:bg-base-200/30"
155
+ : "hover:bg-base-200/10"
59
156
  }`}
60
157
  >
61
158
  {columns.map((col, colIndex) => {
62
- const value = col.accessor ? row[col.accessor as string] : undefined;
159
+ const value = col.accessor
160
+ ? row[col.accessor as string]
161
+ : undefined;
63
162
  return (
64
163
  <td
65
164
  key={(col.accessor as string) || colIndex}
66
- className="px-6 py-4 text-sm text-base-content/90 whitespace-nowrap align-middle"
165
+ className="px-6 py-4 text-[13px] text-base-content/80 whitespace-nowrap align-middle border-none"
67
166
  >
68
167
  {col.render ? col.render(value, row, rowIndex) : value}
69
168
  </td>
@@ -87,28 +186,95 @@ const Table = <T extends Record<string, any>>({
87
186
  </tbody>
88
187
  </table>
89
188
  </div>
90
- {totalPages > 1 && onPageChange && (
91
- <div className="border-t border-base-300 px-6 py-4 bg-base-200/30 flex items-center justify-between shrink-0">
189
+
190
+ {/* Mobile Card View (only visible if renderCard provided) */}
191
+ {renderCard && (
192
+ <div className="md:hidden flex flex-col divide-y divide-base-200 overflow-y-auto flex-1">
193
+ {loading ? (
194
+ Array.from({ length: 4 }).map((_, i) => (
195
+ <div
196
+ key={`card-skeleton-${i}`}
197
+ className="p-4 flex flex-col gap-3 animate-pulse"
198
+ >
199
+ <div className="flex justify-between items-start">
200
+ <div className="space-y-1.5 flex-1">
201
+ <div className="h-4.5 bg-base-300/80 rounded w-36"></div>
202
+ <div className="h-3 bg-base-200 rounded w-24"></div>
203
+ </div>
204
+ <div className="flex gap-2">
205
+ <div className="w-7 h-7 rounded-lg bg-base-300/80"></div>
206
+ <div className="w-7 h-7 rounded-lg bg-base-300/80"></div>
207
+ </div>
208
+ </div>
209
+ <div className="space-y-2">
210
+ <div className="h-3.5 bg-base-200 rounded w-48"></div>
211
+ <div className="h-3.5 bg-base-200 rounded w-32"></div>
212
+ </div>
213
+ <div className="flex items-center justify-between pt-2 border-t border-base-200">
214
+ <div className="h-3 bg-base-200 rounded w-20"></div>
215
+ <div className="h-6 bg-base-300/80 rounded-lg w-28"></div>
216
+ </div>
217
+ </div>
218
+ ))
219
+ ) : data.length > 0 ? (
220
+ <>
221
+ {data.map((row, index) => (
222
+ <div
223
+ key={row.id || index}
224
+ onClick={() => onRowClick && onRowClick(row)}
225
+ className={onRowClick ? "cursor-pointer" : ""}
226
+ >
227
+ {renderCard(row, index)}
228
+ </div>
229
+ ))}
230
+ {pagination && currentPage < totalPages && (
231
+ <div ref={observerTarget} className="p-6 flex justify-center">
232
+ <span className="loading loading-spinner loading-sm text-primary"></span>
233
+ </div>
234
+ )}
235
+ </>
236
+ ) : (
237
+ <div className="p-12 text-center text-sm text-base-content/60 flex flex-col items-center gap-2">
238
+ <span className="text-2xl">📁</span>
239
+ <span>{emptyMessage}</span>
240
+ </div>
241
+ )}
242
+ </div>
243
+ )}
244
+
245
+ {/* Desktop Pagination / Load More Footer */}
246
+ {pagination && onPageChange && totalPages > 1 && (
247
+ <div
248
+ className={`border-t border-base-300 px-6 py-4 bg-base-200/30 flex items-center shrink-0 ${renderCard ? "hidden md:flex justify-between" : "justify-between"}`}
249
+ >
92
250
  <div className="text-xs text-base-content/75 font-medium">
93
- Page <span className="font-bold">{currentPage}</span> of{" "}
94
- <span className="font-bold">{totalPages}</span>
251
+ Showing <span className="font-bold">{data.length}</span> items
95
252
  </div>
96
253
  <div className="flex gap-2">
97
- <button
98
- onClick={() => onPageChange(currentPage - 1)}
99
- disabled={currentPage <= 1}
100
- className="btn btn-sm btn-outline rounded-lg text-xs"
101
- >
102
- Previous
103
- </button>
104
- <button
105
- onClick={() => onPageChange(currentPage + 1)}
106
- disabled={currentPage >= totalPages}
107
- className="btn btn-sm btn-outline rounded-lg text-xs"
108
- >
109
- Next
110
- </button>
254
+ {currentPage < totalPages && (
255
+ <button
256
+ onClick={() => onPageChange(currentPage + 1)}
257
+ className="btn btn-sm btn-outline btn-primary rounded-lg text-xs"
258
+ >
259
+ Load More
260
+ </button>
261
+ )}
262
+ </div>
263
+ </div>
264
+ )}
265
+
266
+ {/* Standard Pagination (if pagination prop is not true) */}
267
+ {!pagination && onPageChange && totalPages > 0 && (
268
+ <div className="border-t border-base-300 px-3 sm:px-6 py-2.5 sm:py-4 bg-base-200/30 flex items-center justify-between shrink-0 mt-auto gap-2">
269
+ <div className="text-xs text-base-content/75 font-medium whitespace-nowrap">
270
+ Page <span className="font-bold">{currentPage}</span> of{" "}
271
+ <span className="font-bold">{totalPages}</span>
111
272
  </div>
273
+ <Pagination
274
+ currentPage={currentPage}
275
+ totalPages={totalPages}
276
+ onPageChange={onPageChange}
277
+ />
112
278
  </div>
113
279
  )}
114
280
  </div>
@@ -0,0 +1,190 @@
1
+ "use client";
2
+ import React, { ReactNode } from "react";
3
+ import { ChevronDown } from "lucide-react";
4
+ import { motion } from "framer-motion";
5
+
6
+ export interface TabItem {
7
+ id: string;
8
+ label: ReactNode;
9
+ icon?: ReactNode;
10
+ }
11
+
12
+ interface TabsProps {
13
+ tabs: TabItem[];
14
+ activeTab: string;
15
+ onChange: (id: string) => void;
16
+ variant?: "underline" | "box";
17
+ className?: string;
18
+ dropdown?: boolean;
19
+ disabled?: boolean;
20
+ size?: "xs" | "sm" | "base" | "lg" | "xl" | "2xl" | "3xl" | "4xl";
21
+ }
22
+
23
+ export const Tabs = ({
24
+ tabs,
25
+ activeTab,
26
+ onChange,
27
+ variant = "box",
28
+ className = "bg-base-200 w-fit",
29
+ dropdown = false,
30
+ disabled = false,
31
+ size = "base",
32
+ }: TabsProps) => {
33
+ const [isOpen, setIsOpen] = React.useState(false);
34
+
35
+ const getTabSizeClass = () => {
36
+ switch (size) {
37
+ case "xs": return "text-xs px-2.5 h-7 min-h-7";
38
+ case "sm": return "text-sm px-3 h-8 min-h-8";
39
+ case "base": return "text-base px-4 h-10 min-h-10";
40
+ case "lg": return "text-lg px-6 h-12 min-h-12";
41
+ case "xl": return "text-xl px-6 h-14 min-h-14";
42
+ case "2xl": return "text-2xl px-8 h-16 min-h-16";
43
+ default: return "text-base px-4 h-10 min-h-10";
44
+ }
45
+ };
46
+
47
+ const getBtnSizeClass = () => {
48
+ switch (size) {
49
+ case "xs": return "btn-xs text-xs";
50
+ case "sm": return "btn-sm text-sm";
51
+ case "base": return "text-base";
52
+ case "lg": return "btn-lg text-lg";
53
+ case "xl": return "btn-lg text-xl";
54
+ case "2xl": return "btn-lg text-2xl";
55
+ default: return "text-base";
56
+ }
57
+ };
58
+
59
+ const tabSizeClass = getTabSizeClass();
60
+ const btnSizeClass = getBtnSizeClass();
61
+
62
+ // Close dropdown if user clicks outside
63
+ React.useEffect(() => {
64
+ if (!dropdown || !isOpen) return;
65
+ const closeMenu = () => setIsOpen(false);
66
+ document.addEventListener("click", closeMenu);
67
+ return () => document.removeEventListener("click", closeMenu);
68
+ }, [dropdown, isOpen]);
69
+
70
+ const renderDropdown = () => {
71
+ if (!dropdown) return null;
72
+ const activeItem = tabs.find((t) => t.id === activeTab);
73
+ return (
74
+ <div
75
+ className="relative md:hidden w-full max-w-xs z-[50]"
76
+ onClick={(e) => e.stopPropagation()}
77
+ >
78
+ {/* Trigger Button */}
79
+ <button
80
+ type="button"
81
+ onClick={() => setIsOpen(!isOpen)}
82
+ disabled={disabled}
83
+ className={`btn ${btnSizeClass} bg-base-200 border-base-300 border flex w-full items-center justify-between px-3 text-base-content ${disabled ? "opacity-50 cursor-not-allowed" : ""}`}
84
+ >
85
+ <div className="flex items-center gap-1.5">
86
+ {activeItem?.icon}
87
+ <span className="font-semibold">{activeItem?.label}</span>
88
+ </div>
89
+ <ChevronDown
90
+ size={14}
91
+ className={`opacity-50 transition-transform ${isOpen ? "rotate-180" : ""}`}
92
+ />
93
+ </button>
94
+
95
+ {/* Dropdown Menu */}
96
+ {isOpen && (
97
+ <div className="absolute top-full left-0 right-0 mt-1 bg-base-100 border border-base-200 shadow rounded-lg py-1 animate-in fade-in slide-in-from-top-2">
98
+ {tabs.map((tab) => (
99
+ <button
100
+ key={tab.id}
101
+ onClick={() => {
102
+ onChange(tab.id);
103
+ setIsOpen(false);
104
+ }}
105
+ type="button"
106
+ className={`w-full flex items-center gap-2 px-3 py-2 transition-colors ${size === "base" ? "text-sm" : `text-${size}`} ${
107
+ activeTab === tab.id
108
+ ? "bg-primary/10 text-primary font-semibold"
109
+ : "hover:bg-base-200 text-base-content/80"
110
+ }`}
111
+ >
112
+ {tab.icon}
113
+ {tab.label}
114
+ </button>
115
+ ))}
116
+ </div>
117
+ )}
118
+ </div>
119
+ );
120
+ };
121
+
122
+ if (variant === "box") {
123
+ return (
124
+ <>
125
+ {renderDropdown()}
126
+ <div
127
+ className={`tabs tabs-boxed p-1 flex-nowrap overflow-x-auto hide-scrollbar shrink-0 rounded-lg ${dropdown ? "hidden md:flex" : "flex"} ${className}`}
128
+ >
129
+ {tabs.map((tab) => (
130
+ <button
131
+ type="button"
132
+ key={tab.id}
133
+ onClick={() => onChange(tab.id)}
134
+ disabled={disabled}
135
+ className={`tab ${tabSizeClass} whitespace-nowrap flex items-center rounded-lg gap-2 relative z-0 ${
136
+ activeTab === tab.id
137
+ ? "text-primary-content"
138
+ : "text-base-content/70 hover:text-base-content "
139
+ } ${disabled ? "opacity-50 cursor-not-allowed" : ""}`}
140
+ >
141
+ {activeTab === tab.id && (
142
+ <motion.div
143
+ layoutId="activeTabBox"
144
+ className="absolute inset-0 bg-primary rounded-lg -z-10"
145
+ transition={{ type: "spring", duration: 0, bounce: 0.2 }}
146
+ />
147
+ )}
148
+ {tab.icon}
149
+ {tab.label}
150
+ </button>
151
+ ))}
152
+ </div>
153
+ </>
154
+ );
155
+ }
156
+
157
+ // default: underline
158
+ return (
159
+ <>
160
+ {renderDropdown()}
161
+ <div
162
+ className={`tabs tabs-bordered ${dropdown ? "hidden md:flex" : "flex"} ${className}`}
163
+ >
164
+ {tabs.map((tab) => (
165
+ <button
166
+ type="button"
167
+ key={tab.id}
168
+ onClick={() => onChange(tab.id)}
169
+ disabled={disabled}
170
+ className={`tab ${tabSizeClass} whitespace-nowrap relative !border-transparent ${
171
+ activeTab === tab.id
172
+ ? "font-semibold text-primary"
173
+ : "text-base-content/70 hover:text-base-content"
174
+ } ${disabled ? "opacity-50 cursor-not-allowed" : ""}`}
175
+ >
176
+ {activeTab === tab.id && (
177
+ <motion.div
178
+ layoutId="activeTabUnderline"
179
+ className="absolute bottom-0 left-0 right-0 h-0.5 bg-primary"
180
+ transition={{ type: "spring", duration: 0.5, bounce: 0.2 }}
181
+ />
182
+ )}
183
+ {tab.icon && <span className="mr-2">{tab.icon}</span>}
184
+ {tab.label}
185
+ </button>
186
+ ))}
187
+ </div>
188
+ </>
189
+ );
190
+ };
@@ -1,19 +1,23 @@
1
1
  "use client";
2
2
  import React from "react";
3
- import { motion } from "framer-motion";
3
+ import { motion } from "motion/react";
4
4
 
5
- const ToggleSwitch = ({
5
+ interface ToggleSwitchProps {
6
+ checked: boolean;
7
+ onChange: (checked: boolean) => void;
8
+ label?: React.ReactNode;
9
+ className?:string
10
+ }
11
+
12
+ const ToggleSwitch: React.FC<ToggleSwitchProps> = ({
6
13
  checked,
7
14
  onChange,
8
15
  label,
9
- }: {
10
- checked: boolean;
11
- onChange: (checked: boolean) => void;
12
- label: string;
16
+ className
13
17
  }) => {
14
18
  return (
15
19
  <div
16
- className="flex items-center gap-3 cursor-pointer select-none w-fit group"
20
+ className={`flex items-center gap-3 cursor-pointer select-none w-fit group ${className}`}
17
21
  onClick={() => onChange(!checked)}
18
22
  >
19
23
  <span className="text-sm font-bold">{label}</span>
@@ -25,7 +29,7 @@ const ToggleSwitch = ({
25
29
  }`}
26
30
  >
27
31
  <motion.div
28
- className={`w-4 h-4 rounded-full shadow-sm ${
32
+ className={`w-4 h-4 rounded-full shadow ${
29
33
  checked ? "bg-white" : "bg-base-content/40"
30
34
  }`}
31
35
  layout
@@ -1,13 +1,15 @@
1
+ "use client";
2
+ import { ReactNode } from "react";
1
3
 
4
+ interface PublicLayoutProps {
5
+ children: ReactNode;
6
+ }
2
7
 
3
- import Header from "../ui/Header";
4
-
5
- const PublicLayout = ({ children }: { children: React.ReactNode }) => {
8
+ const PublicLayout = ({ children }: PublicLayoutProps) => {
6
9
  return (
7
- <>
8
- <Header />
10
+ <div className="min-h-screen bg-base-100 text-base-content">
9
11
  {children}
10
- </>
12
+ </div>
11
13
  );
12
14
  };
13
15
 
@@ -1,21 +1,15 @@
1
1
  "use client";
2
- import { useEffect } from "react";
3
- import { usePathname } from "next/navigation";
2
+ import { ReactNode } from "react";
4
3
  import SidebarHeader from "../ui/SidebarHeader";
5
- import Sidebar from "../ui/sidebar";
4
+ import Sidebar from "../ui/Sidebar";
6
5
  import { useUiStore } from "../../store/useUiStore";
7
- import appConfig from "../../config/appConfig";
8
6
 
9
- const SidebarLayout = ({ children }: { children: React.ReactNode }) => {
10
- const { headerTitle, headerActions, setHeaderTitle, setHeaderActions } =
11
- useUiStore();
12
- const pathname = usePathname();
7
+ interface SidebarLayoutProps {
8
+ children: ReactNode;
9
+ }
13
10
 
14
- // Reset header title and actions whenever the route shifts
15
- useEffect(() => {
16
- setHeaderTitle(appConfig.app.name);
17
- setHeaderActions(null);
18
- }, [pathname, setHeaderTitle, setHeaderActions]);
11
+ const SidebarLayout = ({ children }: SidebarLayoutProps) => {
12
+ const { headerTitle, headerActions } = useUiStore();
19
13
 
20
14
  return (
21
15
  <div className="flex h-screen overflow-hidden">
@@ -24,10 +18,7 @@ const SidebarLayout = ({ children }: { children: React.ReactNode }) => {
24
18
  <div className="flex flex-1 flex-col overflow-hidden">
25
19
  <SidebarHeader title={headerTitle} actions={headerActions} />
26
20
 
27
- <main
28
- className="flex-1 h-full overflow-y-auto px-2 py-6 md:p-6 max-w-7xl"
29
- data-lenis-prevent
30
- >
21
+ <main className="flex-1 h-full overflow-y-auto" data-lenis-prevent>
31
22
  {children}
32
23
  </main>
33
24
  </div>
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import { useEffect } from "react";
2
3
  import { createPortal } from "react-dom";
3
4
  import {