@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
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { NavLink } from "react-router-dom";
|
|
2
|
+
import { IoClose } from "react-icons/io5";
|
|
3
|
+
import { useUiStore } from "../../store/useUiStore";
|
|
4
|
+
import { navigation } from "../../config/navigation";
|
|
5
|
+
import { Moon, Sun } from "lucide-react";
|
|
6
|
+
import appConfig from "../../config/appConfig";
|
|
7
|
+
import { motion, AnimatePresence } from "framer-motion";
|
|
8
|
+
import useWindowSize from "../../hooks/useWindowSize";
|
|
9
|
+
import { useAuthStore } from "../../store/useAuthStore";
|
|
10
|
+
import { GoSidebarCollapse, GoSidebarExpand } from "react-icons/go";
|
|
11
|
+
import { CiLogout } from "react-icons/ci";
|
|
12
|
+
|
|
13
|
+
const Sidebar = () => {
|
|
14
|
+
const { open, close, collapsed, toggleCollapsed, theme, toggleTheme } =
|
|
15
|
+
useUiStore();
|
|
16
|
+
const { logout, user } = useAuthStore();
|
|
17
|
+
const { width } = useWindowSize();
|
|
18
|
+
const isMobile = width < 1024; // lg breakpoint
|
|
19
|
+
|
|
20
|
+
const showSidebar = !isMobile || open;
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<>
|
|
24
|
+
{/* Overlay with AnimatePresence */}
|
|
25
|
+
<AnimatePresence>
|
|
26
|
+
{isMobile && open && (
|
|
27
|
+
<motion.div
|
|
28
|
+
initial={{ opacity: 0 }}
|
|
29
|
+
animate={{ opacity: 1 }}
|
|
30
|
+
exit={{ opacity: 0 }}
|
|
31
|
+
transition={{ duration: 0.2 }}
|
|
32
|
+
className="fixed inset-0 bg-base-100/40 z-40 lg:hidden"
|
|
33
|
+
onClick={close}
|
|
34
|
+
/>
|
|
35
|
+
)}
|
|
36
|
+
</AnimatePresence>
|
|
37
|
+
|
|
38
|
+
{/* Sidebar with AnimatePresence */}
|
|
39
|
+
<AnimatePresence>
|
|
40
|
+
{showSidebar && (
|
|
41
|
+
<motion.aside
|
|
42
|
+
initial={isMobile ? { x: "-100%" } : false}
|
|
43
|
+
animate={{ x: 0 }}
|
|
44
|
+
exit={isMobile ? { x: "-100%" } : undefined}
|
|
45
|
+
transition={{ type: "tween", duration: 0.25, ease: "easeInOut" }}
|
|
46
|
+
className={`
|
|
47
|
+
fixed lg:static top-0 left-0 z-50
|
|
48
|
+
h-screen bg-base-200 border-r border-base-300
|
|
49
|
+
flex flex-col overflow-hidden
|
|
50
|
+
transition-[width] duration-300
|
|
51
|
+
${collapsed ? "w-18" : "w-[75%] md:w-[16%] md:max-w-60"}
|
|
52
|
+
`}
|
|
53
|
+
>
|
|
54
|
+
{/* Header */}
|
|
55
|
+
<div className="h-16 border-b border-base-300 flex items-center justify-between px-5">
|
|
56
|
+
<div className="flex items-center gap-3">
|
|
57
|
+
{appConfig.app.logo ? (
|
|
58
|
+
<img
|
|
59
|
+
src={appConfig.app.logo}
|
|
60
|
+
alt=""
|
|
61
|
+
className="w-8 h-8 rounded-lg"
|
|
62
|
+
/>
|
|
63
|
+
) : (
|
|
64
|
+
<div className="flex items-center">
|
|
65
|
+
<div className="bg-primary w-8 h-8 rounded-lg flex items-center justify-center text-primary-content font-bold">
|
|
66
|
+
{appConfig.app.name.charAt(0)}
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
)}
|
|
70
|
+
<h1
|
|
71
|
+
className={`text-xl font-black text-primary whitespace-nowrap overflow-hidden ${collapsed ? "hidden" : ""}`}
|
|
72
|
+
>
|
|
73
|
+
{appConfig.app.name}
|
|
74
|
+
</h1>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<button
|
|
78
|
+
className={`btn btn-sm btn-circle btn-ghost md:hidden ${collapsed ? "hidden" : ""}`}
|
|
79
|
+
onClick={close}
|
|
80
|
+
>
|
|
81
|
+
<IoClose size={20} />
|
|
82
|
+
</button>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
{/* Navigation */}
|
|
86
|
+
<nav
|
|
87
|
+
className="flex-1 overflow-y-auto p-2.5 space-y-1"
|
|
88
|
+
data-lenis-prevent
|
|
89
|
+
>
|
|
90
|
+
{navigation
|
|
91
|
+
.filter((item) => {
|
|
92
|
+
const orgRole = user?.orgRole;
|
|
93
|
+
const isUserAdmin =
|
|
94
|
+
user?.role === "ADMIN" ||
|
|
95
|
+
orgRole === "OWNER" ||
|
|
96
|
+
orgRole === "ADMIN";
|
|
97
|
+
|
|
98
|
+
// Check if item is hidden for this role
|
|
99
|
+
if (item.hiddenForRoles) {
|
|
100
|
+
if (
|
|
101
|
+
isUserAdmin &&
|
|
102
|
+
(item.hiddenForRoles.includes("OWNER") ||
|
|
103
|
+
item.hiddenForRoles.includes("ADMIN"))
|
|
104
|
+
) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
if (orgRole && item.hiddenForRoles.includes(orgRole)) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!item.allowedRoles) return true; // visible to all
|
|
113
|
+
// System-level ADMIN always gets full access
|
|
114
|
+
if (user?.role === "ADMIN") return true;
|
|
115
|
+
// If orgRole not yet resolved, show all items (fail open for admins)
|
|
116
|
+
if (!orgRole) return true;
|
|
117
|
+
return item.allowedRoles.includes(orgRole);
|
|
118
|
+
})
|
|
119
|
+
.map((item) => {
|
|
120
|
+
const Icon = item.icon;
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
<NavLink
|
|
124
|
+
key={item.path}
|
|
125
|
+
to={item.path}
|
|
126
|
+
onClick={isMobile ? close : undefined}
|
|
127
|
+
className={({ isActive }) =>
|
|
128
|
+
`flex items-center gap-3 rounded-box px-4 py-3 duration-0 h-12
|
|
129
|
+
${
|
|
130
|
+
isActive
|
|
131
|
+
? "bg-primary text-primary-content"
|
|
132
|
+
: "hover:bg-base-300"
|
|
133
|
+
} ${collapsed ? "justify-center px-0" : ""}`
|
|
134
|
+
}
|
|
135
|
+
title={collapsed ? item.title : ""}
|
|
136
|
+
>
|
|
137
|
+
<Icon
|
|
138
|
+
size={18}
|
|
139
|
+
className={`shrink-0 ${collapsed && "mr-auto"}`}
|
|
140
|
+
/>
|
|
141
|
+
|
|
142
|
+
{!collapsed && (
|
|
143
|
+
<span className="font-medium transition-all duration-0 whitespace-nowrap">
|
|
144
|
+
{item.title}
|
|
145
|
+
</span>
|
|
146
|
+
)}
|
|
147
|
+
</NavLink>
|
|
148
|
+
);
|
|
149
|
+
})}
|
|
150
|
+
</nav>
|
|
151
|
+
|
|
152
|
+
{/* Footer with Actions */}
|
|
153
|
+
<div
|
|
154
|
+
className={`p-3 border-t border-base-300/50 shrink-0 flex gap-2 ${
|
|
155
|
+
collapsed
|
|
156
|
+
? "flex-col items-center justify-center"
|
|
157
|
+
: "flex-row items-center justify-between"
|
|
158
|
+
}`}
|
|
159
|
+
>
|
|
160
|
+
<button
|
|
161
|
+
onClick={() => {
|
|
162
|
+
logout();
|
|
163
|
+
close();
|
|
164
|
+
}}
|
|
165
|
+
className={`btn btn-sm btn-circle btn-soft btn-error ${collapsed ? "hidden" : "flex"}`}
|
|
166
|
+
title="Logout"
|
|
167
|
+
>
|
|
168
|
+
<CiLogout size={18} />
|
|
169
|
+
</button>
|
|
170
|
+
|
|
171
|
+
<div
|
|
172
|
+
className={`flex items-center gap-2 ${collapsed ? "flex-col" : ""}`}
|
|
173
|
+
>
|
|
174
|
+
<button
|
|
175
|
+
onClick={toggleTheme}
|
|
176
|
+
className="flex items-center gap-3 hover:opacity-80 transition-opacity"
|
|
177
|
+
title="Toggle Theme"
|
|
178
|
+
>
|
|
179
|
+
<div
|
|
180
|
+
className={`btn btn-sm btn-circle btn-primary btn-soft pointer-events-none ${collapsed ? "hidden" : "flex"}`}
|
|
181
|
+
>
|
|
182
|
+
{theme === "dark" ? (
|
|
183
|
+
<Sun size={16} className="shrink-0" />
|
|
184
|
+
) : (
|
|
185
|
+
<Moon size={16} className="shrink-0" />
|
|
186
|
+
)}
|
|
187
|
+
</div>
|
|
188
|
+
{!collapsed && (
|
|
189
|
+
<span className="md:hidden font-medium text-sm text-base-content/80">
|
|
190
|
+
{theme === "dark" ? "Dark Mode" : "Light Mode"}
|
|
191
|
+
</span>
|
|
192
|
+
)}
|
|
193
|
+
</button>
|
|
194
|
+
|
|
195
|
+
<button
|
|
196
|
+
onClick={toggleCollapsed}
|
|
197
|
+
className="btn btn-sm btn-circle btn-outline border-none text-base-content/70 hover:text-base-content hover:bg-base-300/60 hidden md:flex"
|
|
198
|
+
title={collapsed ? "Expand Sidebar" : "Collapse Sidebar"}
|
|
199
|
+
>
|
|
200
|
+
{collapsed ? (
|
|
201
|
+
<GoSidebarExpand size={18} />
|
|
202
|
+
) : (
|
|
203
|
+
<GoSidebarCollapse size={18} />
|
|
204
|
+
)}
|
|
205
|
+
</button>
|
|
206
|
+
</div>
|
|
207
|
+
</div>
|
|
208
|
+
</motion.aside>
|
|
209
|
+
)}
|
|
210
|
+
</AnimatePresence>
|
|
211
|
+
</>
|
|
212
|
+
);
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
export default Sidebar;
|
|
@@ -1,36 +1,31 @@
|
|
|
1
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
1
|
import { FiMenu } from "react-icons/fi";
|
|
3
2
|
import { useUiStore } from "../../store/useUiStore";
|
|
3
|
+
|
|
4
4
|
import { Breadcrumbs } from "../common/Breadcrumbs";
|
|
5
|
+
|
|
5
6
|
const SidebarHeader = ({ title = "Dashboard", actions = null }) => {
|
|
6
7
|
const { toggleSidebar, breadcrumbs } = useUiStore();
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
/* @__PURE__ */ jsx("div", {
|
|
29
|
-
className: "flex items-center gap-2",
|
|
30
|
-
children: actions,
|
|
31
|
-
}),
|
|
32
|
-
],
|
|
33
|
-
});
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<header className="sticky top-0 z-30 h-16 bg-base-200 backdrop-blur border-b border-base-200 flex items-center justify-between px-4 lg:px-6">
|
|
11
|
+
<div className="flex items-center gap-3">
|
|
12
|
+
<button
|
|
13
|
+
onClick={toggleSidebar}
|
|
14
|
+
className="btn btn-square border-none bg-base-200 shadow-none text-base-content lg:hidden"
|
|
15
|
+
>
|
|
16
|
+
<FiMenu size={22} />
|
|
17
|
+
</button>
|
|
18
|
+
|
|
19
|
+
{breadcrumbs && breadcrumbs.length > 0 ? (
|
|
20
|
+
<Breadcrumbs items={breadcrumbs} />
|
|
21
|
+
) : (
|
|
22
|
+
<h1 className="text-xl font-bold">{title}</h1>
|
|
23
|
+
)}
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<div className="flex items-center gap-2">{actions}</div>
|
|
27
|
+
</header>
|
|
28
|
+
);
|
|
34
29
|
};
|
|
35
|
-
|
|
36
|
-
export
|
|
30
|
+
|
|
31
|
+
export default SidebarHeader;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
const appConfig = {
|
|
2
2
|
app: {
|
|
3
|
-
name: "
|
|
3
|
+
name: "My App",
|
|
4
4
|
logo: "./logo.png",
|
|
5
|
-
shortName: "
|
|
5
|
+
shortName: "App",
|
|
6
6
|
description: "A React application built with @imksh/create",
|
|
7
7
|
version: "1.0.0",
|
|
8
|
-
author: "
|
|
8
|
+
author: "",
|
|
9
9
|
},
|
|
10
10
|
|
|
11
11
|
api: {
|
|
@@ -2,8 +2,8 @@ import { useState, useEffect } from "react";
|
|
|
2
2
|
|
|
3
3
|
const useWindowSize = () => {
|
|
4
4
|
const [size, setSize] = useState({
|
|
5
|
-
width: window.innerWidth,
|
|
6
|
-
height: window.innerHeight,
|
|
5
|
+
width: typeof window !== "undefined" ? window.innerWidth : 1024,
|
|
6
|
+
height: typeof window !== "undefined" ? window.innerHeight : 768,
|
|
7
7
|
});
|
|
8
8
|
|
|
9
9
|
useEffect(() => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { Link } from "react-router-dom";
|
|
3
|
-
import { ChevronRight } from "lucide-react";
|
|
2
|
+
import { Link, useNavigate } from "react-router-dom";
|
|
3
|
+
import { ArrowLeft, ChevronRight } from "lucide-react";
|
|
4
4
|
|
|
5
5
|
export interface BreadcrumbItem {
|
|
6
6
|
label: React.ReactNode;
|
|
@@ -14,23 +14,44 @@ interface BreadcrumbsProps {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export const Breadcrumbs = ({ items }: BreadcrumbsProps) => {
|
|
17
|
+
const navigation = useNavigate();
|
|
17
18
|
return (
|
|
18
19
|
<div className="flex items-center gap-1.5 text-sm font-bold text-base-content/50 flex-wrap">
|
|
20
|
+
<button
|
|
21
|
+
className="btn btn-primary btn-soft btn-square btn-sm mr-1 hidden md:flex"
|
|
22
|
+
onClick={() => navigation(-1)}
|
|
23
|
+
>
|
|
24
|
+
<ArrowLeft size={18} />
|
|
25
|
+
</button>
|
|
19
26
|
{items.map((item, idx) => {
|
|
20
27
|
const isLast = idx === items.length - 1;
|
|
21
28
|
return (
|
|
22
29
|
<React.Fragment key={idx}>
|
|
23
30
|
{item.path && !isLast ? (
|
|
24
|
-
<Link
|
|
31
|
+
<Link
|
|
32
|
+
to={item.path}
|
|
33
|
+
onClick={item.onClick}
|
|
34
|
+
className="hover:text-primary transition-colors hidden sm:flex items-center gap-1"
|
|
35
|
+
>
|
|
25
36
|
{item.label}
|
|
26
37
|
</Link>
|
|
27
38
|
) : item.onClick && !isLast ? (
|
|
28
|
-
<button
|
|
39
|
+
<button
|
|
40
|
+
type="button"
|
|
41
|
+
onClick={item.onClick}
|
|
42
|
+
className="hover:text-primary transition-colors hidden sm:flex items-center gap-1"
|
|
43
|
+
>
|
|
29
44
|
{item.label}
|
|
30
45
|
</button>
|
|
31
46
|
) : (
|
|
32
47
|
<div className="flex flex-col">
|
|
33
|
-
<span
|
|
48
|
+
<span
|
|
49
|
+
className={
|
|
50
|
+
isLast
|
|
51
|
+
? "text-lg md:text-xl font-bold leading-tight text-base-content tracking-tight truncate max-w-[200px] md:max-w-none"
|
|
52
|
+
: ""
|
|
53
|
+
}
|
|
54
|
+
>
|
|
34
55
|
{item.label}
|
|
35
56
|
</span>
|
|
36
57
|
{item.subtitle && (
|
|
@@ -40,7 +61,12 @@ export const Breadcrumbs = ({ items }: BreadcrumbsProps) => {
|
|
|
40
61
|
)}
|
|
41
62
|
</div>
|
|
42
63
|
)}
|
|
43
|
-
{!isLast &&
|
|
64
|
+
{!isLast && (
|
|
65
|
+
<ChevronRight
|
|
66
|
+
size={14}
|
|
67
|
+
className="text-base-content/30 shrink-0 hidden sm:block"
|
|
68
|
+
/>
|
|
69
|
+
)}
|
|
44
70
|
</React.Fragment>
|
|
45
71
|
);
|
|
46
72
|
})}
|
|
@@ -14,78 +14,172 @@ export const Pagination: React.FC<PaginationProps> = ({
|
|
|
14
14
|
}) => {
|
|
15
15
|
if (totalPages <= 1) return null;
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
// Desktop pagination: 5 pages with outer bounds & ellipsis
|
|
18
|
+
const maxDesktopPages = 5;
|
|
19
|
+
let startDesktopPage = Math.max(
|
|
20
|
+
1,
|
|
21
|
+
currentPage - Math.floor(maxDesktopPages / 2),
|
|
22
|
+
);
|
|
23
|
+
let endDesktopPage = Math.min(
|
|
24
|
+
totalPages,
|
|
25
|
+
startDesktopPage + maxDesktopPages - 1,
|
|
26
|
+
);
|
|
27
|
+
if (endDesktopPage - startDesktopPage + 1 < maxDesktopPages) {
|
|
28
|
+
startDesktopPage = Math.max(1, endDesktopPage - maxDesktopPages + 1);
|
|
29
|
+
}
|
|
30
|
+
const desktopPages: number[] = [];
|
|
31
|
+
for (let i = startDesktopPage; i <= endDesktopPage; i++) {
|
|
32
|
+
desktopPages.push(i);
|
|
23
33
|
}
|
|
24
34
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
35
|
+
// Small screen pagination: exactly 3 page circles
|
|
36
|
+
const maxMobilePages = 3;
|
|
37
|
+
let startMobilePage = Math.max(
|
|
38
|
+
1,
|
|
39
|
+
currentPage - Math.floor(maxMobilePages / 2),
|
|
40
|
+
);
|
|
41
|
+
let endMobilePage = Math.min(
|
|
42
|
+
totalPages,
|
|
43
|
+
startMobilePage + maxMobilePages - 1,
|
|
44
|
+
);
|
|
45
|
+
if (endMobilePage - startMobilePage + 1 < maxMobilePages) {
|
|
46
|
+
startMobilePage = Math.max(1, endMobilePage - maxMobilePages + 1);
|
|
47
|
+
}
|
|
48
|
+
const mobilePages: number[] = [];
|
|
49
|
+
for (let i = startMobilePage; i <= endMobilePage; i++) {
|
|
50
|
+
mobilePages.push(i);
|
|
28
51
|
}
|
|
29
52
|
|
|
53
|
+
const baseBtnClass =
|
|
54
|
+
"relative inline-flex items-center justify-center min-w-8 h-8 sm:min-w-9 sm:h-9 px-1.5 sm:px-2 text-xs sm:text-sm font-medium transition-all duration-200 rounded-full bg-base-100 hover:bg-base-200 text-base-content/70 hover:text-base-content focus:z-10 focus:outline-none focus:ring-2 focus:ring-primary/50 shadow-sm";
|
|
55
|
+
const activeBtnClass =
|
|
56
|
+
"relative inline-flex items-center justify-center min-w-8 h-8 sm:min-w-9 sm:h-9 px-1.5 sm:px-2 text-xs sm:text-sm font-bold transition-all duration-200 rounded-full border border-primary bg-primary text-primary-content z-10 focus:outline-none focus:ring-2 focus:ring-primary/50 shadow-sm shadow-primary/20";
|
|
57
|
+
const disabledBtnClass =
|
|
58
|
+
"relative inline-flex items-center justify-center min-w-8 h-8 sm:min-w-9 sm:h-9 px-1.5 sm:px-2 text-xs sm:text-sm font-medium transition-all duration-200 rounded-full bg-base-100 text-base-content/30 cursor-not-allowed shadow-sm";
|
|
59
|
+
const ellipsisClass =
|
|
60
|
+
"relative inline-flex items-center justify-center min-w-8 h-8 sm:min-w-9 sm:h-9 px-1 sm:px-2 text-xs sm:text-sm font-medium text-base-content/40 cursor-default";
|
|
61
|
+
|
|
30
62
|
return (
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
63
|
+
<>
|
|
64
|
+
{/* Mobile / Small Screen: 3 page circles + ellipses + boundary pages */}
|
|
65
|
+
<div className="flex sm:hidden items-center justify-center gap-1">
|
|
66
|
+
<button
|
|
67
|
+
onClick={() => onPageChange(currentPage - 1)}
|
|
68
|
+
disabled={currentPage === 1}
|
|
69
|
+
className={currentPage === 1 ? disabledBtnClass : baseBtnClass}
|
|
70
|
+
aria-label="Previous Page"
|
|
71
|
+
>
|
|
72
|
+
<ChevronLeft className="w-4 h-4" />
|
|
73
|
+
</button>
|
|
74
|
+
|
|
75
|
+
{startMobilePage > 1 && (
|
|
76
|
+
<>
|
|
77
|
+
<button
|
|
78
|
+
onClick={() => onPageChange(1)}
|
|
79
|
+
className={baseBtnClass}
|
|
80
|
+
>
|
|
81
|
+
1
|
|
82
|
+
</button>
|
|
83
|
+
{startMobilePage > 2 && (
|
|
84
|
+
<span className={ellipsisClass}>...</span>
|
|
85
|
+
)}
|
|
86
|
+
</>
|
|
87
|
+
)}
|
|
88
|
+
|
|
89
|
+
{mobilePages.map((page) => (
|
|
43
90
|
<button
|
|
44
|
-
|
|
45
|
-
|
|
91
|
+
key={`mobile-${page}`}
|
|
92
|
+
onClick={() => onPageChange(page)}
|
|
93
|
+
className={currentPage === page ? activeBtnClass : baseBtnClass}
|
|
46
94
|
>
|
|
47
|
-
|
|
95
|
+
{page}
|
|
48
96
|
</button>
|
|
49
|
-
|
|
50
|
-
</>
|
|
51
|
-
)}
|
|
97
|
+
))}
|
|
52
98
|
|
|
53
|
-
|
|
99
|
+
{endMobilePage < totalPages && (
|
|
100
|
+
<>
|
|
101
|
+
{endMobilePage < totalPages - 1 && (
|
|
102
|
+
<span className={ellipsisClass}>...</span>
|
|
103
|
+
)}
|
|
104
|
+
<button
|
|
105
|
+
onClick={() => onPageChange(totalPages)}
|
|
106
|
+
className={baseBtnClass}
|
|
107
|
+
>
|
|
108
|
+
{totalPages}
|
|
109
|
+
</button>
|
|
110
|
+
</>
|
|
111
|
+
)}
|
|
112
|
+
|
|
113
|
+
<button
|
|
114
|
+
onClick={() => onPageChange(currentPage + 1)}
|
|
115
|
+
disabled={currentPage === totalPages}
|
|
116
|
+
className={currentPage === totalPages ? disabledBtnClass : baseBtnClass}
|
|
117
|
+
aria-label="Next Page"
|
|
118
|
+
>
|
|
119
|
+
<ChevronRight className="w-4 h-4" />
|
|
120
|
+
</button>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
{/* Desktop Screen: 5 page circles + ellipses + boundary pages */}
|
|
124
|
+
<div className="hidden sm:flex items-center justify-center gap-1.5">
|
|
54
125
|
<button
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
className={
|
|
58
|
-
|
|
59
|
-
? "bg-primary text-primary-content shadow-lg shadow-primary/30 scale-105"
|
|
60
|
-
: "text-base-content/70 hover:bg-base-content/10 hover:text-base-content"
|
|
61
|
-
}`}
|
|
126
|
+
onClick={() => onPageChange(currentPage - 1)}
|
|
127
|
+
disabled={currentPage === 1}
|
|
128
|
+
className={currentPage === 1 ? disabledBtnClass : baseBtnClass}
|
|
129
|
+
aria-label="Previous Page"
|
|
62
130
|
>
|
|
63
|
-
|
|
131
|
+
<ChevronLeft className="w-4 h-4" />
|
|
64
132
|
</button>
|
|
65
|
-
))}
|
|
66
133
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
134
|
+
{startDesktopPage > 1 && (
|
|
135
|
+
<>
|
|
136
|
+
<button
|
|
137
|
+
onClick={() => onPageChange(1)}
|
|
138
|
+
className={baseBtnClass}
|
|
139
|
+
>
|
|
140
|
+
1
|
|
141
|
+
</button>
|
|
142
|
+
{startDesktopPage > 2 && (
|
|
143
|
+
<span className={ellipsisClass}>...</span>
|
|
144
|
+
)}
|
|
145
|
+
</>
|
|
146
|
+
)}
|
|
147
|
+
|
|
148
|
+
{desktopPages.map((page) => (
|
|
72
149
|
<button
|
|
73
|
-
|
|
74
|
-
|
|
150
|
+
key={`desktop-${page}`}
|
|
151
|
+
onClick={() => onPageChange(page)}
|
|
152
|
+
className={currentPage === page ? activeBtnClass : baseBtnClass}
|
|
75
153
|
>
|
|
76
|
-
{
|
|
154
|
+
{page}
|
|
77
155
|
</button>
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
156
|
+
))}
|
|
157
|
+
|
|
158
|
+
{endDesktopPage < totalPages && (
|
|
159
|
+
<>
|
|
160
|
+
{endDesktopPage < totalPages - 1 && (
|
|
161
|
+
<span className={ellipsisClass}>...</span>
|
|
162
|
+
)}
|
|
163
|
+
<button
|
|
164
|
+
onClick={() => onPageChange(totalPages)}
|
|
165
|
+
className={baseBtnClass}
|
|
166
|
+
>
|
|
167
|
+
{totalPages}
|
|
168
|
+
</button>
|
|
169
|
+
</>
|
|
170
|
+
)}
|
|
171
|
+
|
|
172
|
+
<button
|
|
173
|
+
onClick={() => onPageChange(currentPage + 1)}
|
|
174
|
+
disabled={currentPage === totalPages}
|
|
175
|
+
className={currentPage === totalPages ? disabledBtnClass : baseBtnClass}
|
|
176
|
+
aria-label="Next Page"
|
|
177
|
+
>
|
|
178
|
+
<ChevronRight className="w-4 h-4" />
|
|
179
|
+
</button>
|
|
180
|
+
</div>
|
|
181
|
+
</>
|
|
90
182
|
);
|
|
91
183
|
};
|
|
184
|
+
|
|
185
|
+
export default Pagination;
|