@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.
- package/package.json +1 -1
- package/src/utils/askTemplate.js +2 -1
- package/templates/layers/electron/base/electron/main.js +262 -0
- package/templates/layers/electron/base/electron/preload.js +37 -0
- package/templates/layers/electron/base/electron-builder.json5 +68 -0
- package/templates/layers/electron/javascript/_gitignore +38 -0
- package/templates/layers/electron/javascript/package.json +49 -0
- package/templates/layers/electron/javascript/src/components/layout/SidebarLayout.jsx +43 -0
- package/templates/layers/electron/javascript/src/components/ui/TitleBar.jsx +130 -0
- package/templates/layers/electron/javascript/src/config/appConfig.js +51 -0
- package/templates/layers/electron/javascript/src/hooks/useElectron.js +68 -0
- package/templates/layers/electron/javascript/src/main.jsx +17 -0
- package/templates/layers/electron/javascript/src/store/useUiStore.js +59 -0
- package/templates/layers/electron/javascript/vite.config.js +29 -0
- package/templates/layers/electron/typescript/_gitignore +38 -0
- package/templates/layers/electron/typescript/electron/main.ts +213 -0
- package/templates/layers/electron/typescript/electron/preload.ts +52 -0
- package/templates/layers/electron/typescript/package.json +52 -0
- package/templates/layers/electron/typescript/src/components/layout/SidebarLayout.tsx +42 -0
- package/templates/layers/electron/typescript/src/components/ui/TitleBar.tsx +120 -0
- package/templates/layers/electron/typescript/src/config/appConfig.ts +52 -0
- package/templates/layers/electron/typescript/src/hooks/useElectron.ts +68 -0
- package/templates/layers/electron/typescript/src/main.tsx +17 -0
- package/templates/layers/electron/typescript/src/store/useUiStore.ts +97 -0
- package/templates/layers/electron/typescript/src/vite-env.d.ts +13 -0
- package/templates/layers/electron/typescript/tsconfig.json +25 -0
- package/templates/layers/electron/typescript/tsconfig.node.json +11 -0
- package/templates/layers/electron/typescript/vite.config.ts +29 -0
- package/templates/layers/next/javascript/src/components/common/Breadcrumbs.jsx +44 -6
- package/templates/layers/next/javascript/src/components/common/Loading.jsx +1 -0
- package/templates/layers/next/javascript/src/components/common/LottieIcon.jsx +1 -0
- package/templates/layers/next/javascript/src/components/common/Pagination.jsx +170 -0
- package/templates/layers/next/javascript/src/components/common/SidebarModal.jsx +180 -0
- package/templates/layers/next/javascript/src/components/common/StatsCard.jsx +168 -0
- package/templates/layers/next/javascript/src/components/common/Table.jsx +191 -41
- package/templates/layers/next/javascript/src/components/common/Tabs.jsx +187 -0
- package/templates/layers/next/javascript/src/components/common/ToggleSwitch.jsx +4 -3
- package/templates/layers/next/javascript/src/components/layout/ProtectedRoute.jsx +2 -1
- package/templates/layers/next/javascript/src/components/layout/PublicLayout.jsx +3 -6
- package/templates/layers/next/javascript/src/components/layout/SidebarLayout.jsx +4 -17
- package/templates/layers/next/javascript/src/components/modals/ConfirmModal.jsx +4 -12
- package/templates/layers/next/javascript/src/components/modals/Modal.jsx +50 -19
- package/templates/layers/next/javascript/src/components/ui/Input.jsx +50 -0
- package/templates/layers/next/{typescript/src/components/ui/sidebar.tsx → javascript/src/components/ui/Sidebar.jsx} +65 -53
- package/templates/layers/next/javascript/src/components/ui/SidebarHeader.jsx +11 -6
- package/templates/layers/next/javascript/src/config/appConfig.js +4 -4
- package/templates/layers/next/javascript/src/hooks/useWindowSize.js +2 -3
- package/templates/layers/next/javascript/src/store/useUiStore.js +9 -4
- package/templates/layers/next/typescript/src/components/common/Breadcrumbs.tsx +57 -8
- package/templates/layers/next/typescript/src/components/common/Loading.tsx +1 -0
- package/templates/layers/next/typescript/src/components/common/Pagination.tsx +186 -0
- package/templates/layers/next/typescript/src/components/common/SidebarModal.tsx +200 -0
- package/templates/layers/next/typescript/src/components/common/StatsCard.tsx +232 -0
- package/templates/layers/next/typescript/src/components/common/Table.tsx +201 -35
- package/templates/layers/next/typescript/src/components/common/Tabs.tsx +190 -0
- package/templates/layers/next/typescript/src/components/common/ToggleSwitch.tsx +12 -8
- package/templates/layers/next/typescript/src/components/layout/PublicLayout.tsx +8 -6
- package/templates/layers/next/typescript/src/components/layout/SidebarLayout.tsx +8 -17
- package/templates/layers/next/typescript/src/components/modals/ConfirmModal.tsx +1 -0
- package/templates/layers/next/typescript/src/components/modals/Modal.tsx +67 -21
- package/templates/layers/next/typescript/src/components/ui/Button.tsx +1 -0
- package/templates/layers/next/typescript/src/components/ui/Input.tsx +40 -60
- package/templates/layers/next/{javascript/src/components/ui/sidebar.jsx → typescript/src/components/ui/Sidebar.tsx} +77 -44
- package/templates/layers/next/typescript/src/components/ui/SidebarHeader.tsx +16 -10
- package/templates/layers/next/typescript/src/config/appConfig.ts +4 -4
- package/templates/layers/next/typescript/src/hooks/useWindowSize.ts +2 -3
- package/templates/layers/next/typescript/src/store/useUiStore.ts +14 -4
- package/templates/layers/node/javascript/package.json +2 -1
- package/templates/layers/node/javascript/src/controllers/push.controller.js +35 -14
- package/templates/layers/node/javascript/src/models/pushSubscription.model.js +5 -0
- package/templates/layers/node/javascript/src/utils/pushHelper.js +59 -12
- package/templates/layers/node/typescript/package-lock.json +3795 -0
- package/templates/layers/node/typescript/package.json +3 -2
- package/templates/layers/node/typescript/src/controllers/push.controller.ts +35 -14
- package/templates/layers/node/typescript/src/models/pushSubscription.model.ts +6 -0
- package/templates/layers/node/typescript/src/services/auth.service.ts +3 -3
- package/templates/layers/node/typescript/src/utils/pushHelper.ts +66 -12
- package/templates/layers/node-prisma/javascript/package.json +6 -3
- package/templates/layers/node-prisma/javascript/prisma/schema.prisma +20 -9
- package/templates/layers/node-prisma/javascript/prisma.config.js +12 -0
- package/templates/layers/node-prisma/javascript/src/config/{db.js → prisma.js} +9 -1
- package/templates/layers/node-prisma/javascript/src/controllers/push.controller.js +58 -19
- package/templates/layers/node-prisma/javascript/src/utils/pushHelper.js +62 -14
- package/templates/layers/node-prisma/typescript/package-lock.json +4808 -0
- package/templates/layers/node-prisma/typescript/package.json +8 -4
- package/templates/layers/node-prisma/typescript/prisma/schema.prisma +20 -9
- package/templates/layers/node-prisma/typescript/prisma.config.ts +12 -0
- package/templates/layers/node-prisma/typescript/src/config/{db.ts → prisma.ts} +9 -1
- package/templates/layers/node-prisma/typescript/src/controllers/push.controller.ts +67 -22
- package/templates/layers/node-prisma/typescript/src/index.ts +1 -1
- package/templates/layers/node-prisma/typescript/src/middleware/auth.middleware.ts +15 -5
- package/templates/layers/node-prisma/typescript/src/services/auth.service.ts +134 -128
- package/templates/layers/node-prisma/typescript/src/utils/pushHelper.ts +69 -14
- package/templates/layers/prisma/typescript/prisma/schema.prisma +35 -10
- package/templates/layers/react/javascript/src/components/common/Breadcrumbs.jsx +62 -58
- package/templates/layers/react/javascript/src/components/common/Pagination.jsx +164 -76
- package/templates/layers/react/javascript/src/components/common/SidebarModal.jsx +179 -0
- package/templates/layers/react/javascript/src/components/common/StatsCard.jsx +167 -0
- package/templates/layers/react/javascript/src/components/common/Table.jsx +248 -132
- package/templates/layers/react/javascript/src/components/common/Tabs.jsx +164 -112
- package/templates/layers/react/javascript/src/components/common/ToggleSwitch.jsx +3 -3
- package/templates/layers/react/javascript/src/components/layout/SidebarLayout.jsx +4 -12
- package/templates/layers/react/javascript/src/components/modals/Modal.jsx +53 -18
- package/templates/layers/react/javascript/src/components/ui/Input.jsx +49 -0
- package/templates/layers/react/javascript/src/components/ui/Sidebar.jsx +215 -0
- package/templates/layers/react/javascript/src/components/ui/SidebarHeader.jsx +25 -30
- package/templates/layers/react/javascript/src/config/appConfig.js +3 -3
- package/templates/layers/react/javascript/src/hooks/useWindowSize.js +2 -2
- package/templates/layers/react/typescript/src/components/common/Breadcrumbs.tsx +32 -6
- package/templates/layers/react/typescript/src/components/common/Pagination.tsx +151 -57
- package/templates/layers/react/typescript/src/components/common/SidebarModal.tsx +199 -0
- package/templates/layers/react/typescript/src/components/common/StatsCard.tsx +231 -0
- package/templates/layers/react/typescript/src/components/common/Table.tsx +197 -34
- package/templates/layers/react/typescript/src/components/common/Tabs.tsx +56 -12
- package/templates/layers/react/typescript/src/components/common/ToggleSwitch.tsx +15 -3
- package/templates/layers/react/typescript/src/components/layout/SidebarLayout.tsx +3 -6
- package/templates/layers/react/typescript/src/components/modals/Modal.tsx +151 -0
- package/templates/layers/react/typescript/src/components/ui/Input.tsx +58 -0
- package/templates/layers/react/typescript/src/components/ui/Sidebar.tsx +116 -53
- package/templates/layers/react/typescript/src/components/ui/SidebarHeader.tsx +5 -4
- package/templates/layers/react/typescript/src/config/appConfig.ts +3 -3
- package/templates/layers/react/typescript/src/hooks/useWindowSize.ts +2 -3
- package/templates/layers/react-native/base/src/components/ui/BiometricLock.tsx +2 -1
- package/templates/layers/react-native/base/src/config/appConfig.ts +44 -0
- package/templates/presets/electron/preset.json +9 -0
- package/templates/presets/electron-ts/preset.json +9 -0
- package/templates/layers/react/javascript/src/components/ui/sidebar.jsx +0 -169
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
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
|
-
|
|
32
|
-
|
|
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
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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=
|
|
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-
|
|
52
|
-
{
|
|
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-
|
|
58
|
-
onRowClick
|
|
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
|
|
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-
|
|
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
|
-
|
|
91
|
-
|
|
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
|
-
|
|
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
|
-
<
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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 "
|
|
3
|
+
import { motion } from "motion/react";
|
|
4
4
|
|
|
5
|
-
|
|
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=
|
|
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
|
|
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
|
-
|
|
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 {
|
|
3
|
-
import { usePathname } from "next/navigation";
|
|
2
|
+
import { ReactNode } from "react";
|
|
4
3
|
import SidebarHeader from "../ui/SidebarHeader";
|
|
5
|
-
import Sidebar from "../ui/
|
|
4
|
+
import Sidebar from "../ui/Sidebar";
|
|
6
5
|
import { useUiStore } from "../../store/useUiStore";
|
|
7
|
-
import appConfig from "../../config/appConfig";
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const pathname = usePathname();
|
|
7
|
+
interface SidebarLayoutProps {
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
}
|
|
13
10
|
|
|
14
|
-
|
|
15
|
-
|
|
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>
|