@imksh/create 1.0.30 → 1.0.32

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 (247) hide show
  1. package/README.md +2 -0
  2. package/package.json +1 -1
  3. package/src/commands/create.js +2 -0
  4. package/src/index.js +1 -1
  5. package/src/utils/askTemplate.js +2 -1
  6. package/templates/layers/electron/base/electron/main.js +262 -0
  7. package/templates/layers/electron/base/electron/preload.js +37 -0
  8. package/templates/layers/electron/base/electron-builder.json5 +68 -0
  9. package/templates/layers/electron/javascript/_gitignore +38 -0
  10. package/templates/layers/electron/javascript/package.json +49 -0
  11. package/templates/layers/electron/javascript/src/components/layout/SidebarLayout.jsx +43 -0
  12. package/templates/layers/electron/javascript/src/components/ui/TitleBar.jsx +130 -0
  13. package/templates/layers/electron/javascript/src/config/appConfig.js +51 -0
  14. package/templates/layers/electron/javascript/src/hooks/useElectron.js +68 -0
  15. package/templates/layers/electron/javascript/src/main.jsx +17 -0
  16. package/templates/layers/electron/javascript/src/store/useUiStore.js +59 -0
  17. package/templates/layers/electron/javascript/vite.config.js +29 -0
  18. package/templates/layers/electron/typescript/_gitignore +38 -0
  19. package/templates/layers/electron/typescript/electron/main.ts +213 -0
  20. package/templates/layers/electron/typescript/electron/preload.ts +52 -0
  21. package/templates/layers/electron/typescript/package.json +52 -0
  22. package/templates/layers/electron/typescript/src/components/layout/SidebarLayout.tsx +42 -0
  23. package/templates/layers/electron/typescript/src/components/ui/TitleBar.tsx +120 -0
  24. package/templates/layers/electron/typescript/src/config/appConfig.ts +52 -0
  25. package/templates/layers/electron/typescript/src/hooks/useElectron.ts +68 -0
  26. package/templates/layers/electron/typescript/src/main.tsx +17 -0
  27. package/templates/layers/electron/typescript/src/store/useUiStore.ts +97 -0
  28. package/templates/layers/electron/typescript/src/vite-env.d.ts +13 -0
  29. package/templates/layers/electron/typescript/tsconfig.json +25 -0
  30. package/templates/layers/electron/typescript/tsconfig.node.json +11 -0
  31. package/templates/layers/electron/typescript/vite.config.ts +29 -0
  32. package/templates/layers/next/javascript/package.json +8 -2
  33. package/templates/layers/next/javascript/src/components/common/Breadcrumbs.jsx +44 -6
  34. package/templates/layers/next/javascript/src/components/common/Loading.jsx +1 -0
  35. package/templates/layers/next/javascript/src/components/common/LottieIcon.jsx +1 -0
  36. package/templates/layers/next/javascript/src/components/common/Pagination.jsx +170 -0
  37. package/templates/layers/next/javascript/src/components/common/SidebarModal.jsx +180 -0
  38. package/templates/layers/next/javascript/src/components/common/StatsCard.jsx +168 -0
  39. package/templates/layers/next/javascript/src/components/common/Table.jsx +191 -41
  40. package/templates/layers/next/javascript/src/components/common/Tabs.jsx +187 -0
  41. package/templates/layers/next/javascript/src/components/common/ToggleSwitch.jsx +4 -3
  42. package/templates/layers/next/javascript/src/components/layout/ProtectedRoute.jsx +2 -1
  43. package/templates/layers/next/javascript/src/components/layout/PublicLayout.jsx +3 -6
  44. package/templates/layers/next/javascript/src/components/layout/SidebarLayout.jsx +4 -17
  45. package/templates/layers/next/javascript/src/components/modals/ConfirmModal.jsx +4 -12
  46. package/templates/layers/next/javascript/src/components/modals/Modal.jsx +50 -19
  47. package/templates/layers/next/javascript/src/components/ui/Input.jsx +50 -0
  48. package/templates/layers/next/{typescript/src/components/ui/sidebar.tsx → javascript/src/components/ui/Sidebar.jsx} +65 -53
  49. package/templates/layers/next/javascript/src/components/ui/SidebarHeader.jsx +11 -6
  50. package/templates/layers/next/javascript/src/config/appConfig.js +4 -4
  51. package/templates/layers/next/javascript/src/hooks/useWindowSize.js +2 -3
  52. package/templates/layers/next/javascript/src/store/useUiStore.js +9 -4
  53. package/templates/layers/next/typescript/.eslintrc.json +12 -0
  54. package/templates/layers/next/typescript/package.json +14 -3
  55. package/templates/layers/next/typescript/src/components/common/Breadcrumbs.tsx +57 -8
  56. package/templates/layers/next/typescript/src/components/common/Loading.tsx +2 -1
  57. package/templates/layers/next/typescript/src/components/common/LottieIcon.tsx +2 -2
  58. package/templates/layers/next/typescript/src/components/common/Pagination.tsx +186 -0
  59. package/templates/layers/next/typescript/src/components/common/SidebarModal.tsx +200 -0
  60. package/templates/layers/next/typescript/src/components/common/StatsCard.tsx +232 -0
  61. package/templates/layers/next/typescript/src/components/common/Table.tsx +205 -39
  62. package/templates/layers/next/typescript/src/components/common/Tabs.tsx +190 -0
  63. package/templates/layers/next/typescript/src/components/common/ToggleSwitch.tsx +12 -8
  64. package/templates/layers/next/typescript/src/components/layout/PublicLayout.tsx +8 -6
  65. package/templates/layers/next/typescript/src/components/layout/SidebarLayout.tsx +8 -17
  66. package/templates/layers/next/typescript/src/components/modals/ConfirmModal.tsx +3 -1
  67. package/templates/layers/next/typescript/src/components/modals/Modal.tsx +67 -21
  68. package/templates/layers/next/typescript/src/components/ui/Button.tsx +1 -0
  69. package/templates/layers/next/typescript/src/components/ui/Header.tsx +2 -2
  70. package/templates/layers/next/typescript/src/components/ui/Input.tsx +40 -60
  71. package/templates/layers/next/{javascript/src/components/ui/sidebar.jsx → typescript/src/components/ui/Sidebar.tsx} +77 -44
  72. package/templates/layers/next/typescript/src/components/ui/SidebarHeader.tsx +16 -10
  73. package/templates/layers/next/typescript/src/config/appConfig.ts +4 -4
  74. package/templates/layers/next/typescript/src/hooks/usePwaInstall.ts +10 -4
  75. package/templates/layers/next/typescript/src/hooks/useWindowSize.ts +2 -3
  76. package/templates/layers/next/typescript/src/services/authService.ts +1 -1
  77. package/templates/layers/next/typescript/src/store/useAuthStore.ts +24 -9
  78. package/templates/layers/next/typescript/src/store/useUiStore.ts +14 -4
  79. package/templates/layers/node/javascript/.env.example +3 -1
  80. package/templates/layers/node/javascript/package.json +12 -3
  81. package/templates/layers/node/javascript/src/app.js +2 -1
  82. package/templates/layers/node/javascript/src/controllers/auth.controller.js +106 -168
  83. package/templates/layers/node/javascript/src/controllers/push.controller.js +51 -58
  84. package/templates/layers/node/javascript/src/controllers/upload.controller.js +12 -19
  85. package/templates/layers/node/javascript/src/middleware/auth.middleware.js +5 -11
  86. package/templates/layers/node/javascript/src/middleware/errorHandler.js +6 -4
  87. package/templates/layers/node/javascript/src/routes/auth.route.js +2 -0
  88. package/templates/layers/node/javascript/src/services/auth.service.js +159 -164
  89. package/templates/layers/node/javascript/src/utils/ApiError.js +20 -0
  90. package/templates/layers/node/javascript/src/utils/SuccessResponse.js +15 -0
  91. package/templates/layers/node/javascript/src/utils/apiResponse.js +4 -0
  92. package/templates/layers/node/javascript/src/utils/asyncHandler.js +5 -0
  93. package/templates/layers/node/typescript/.env.example +3 -1
  94. package/templates/layers/node/typescript/.eslintrc.cjs +28 -0
  95. package/templates/layers/node/typescript/package-lock.json +2 -2
  96. package/templates/layers/node/typescript/package.json +17 -3
  97. package/templates/layers/node/typescript/src/app.ts +3 -2
  98. package/templates/layers/node/typescript/src/config/db.ts +3 -2
  99. package/templates/layers/node/typescript/src/config/socket.ts +5 -4
  100. package/templates/layers/node/typescript/src/controllers/auth.controller.ts +104 -132
  101. package/templates/layers/node/typescript/src/controllers/push.controller.ts +52 -59
  102. package/templates/layers/node/typescript/src/controllers/upload.controller.ts +12 -19
  103. package/templates/layers/node/typescript/src/middleware/auth.middleware.ts +13 -7
  104. package/templates/layers/node/typescript/src/middleware/errorHandler.ts +18 -5
  105. package/templates/layers/node/typescript/src/middleware/rateLimiter.ts +2 -2
  106. package/templates/layers/node/typescript/src/routes/auth.route.ts +2 -0
  107. package/templates/layers/node/typescript/src/services/auth.service.ts +86 -43
  108. package/templates/layers/node/typescript/src/types/express.d.ts +5 -3
  109. package/templates/layers/node/typescript/src/utils/ApiError.ts +25 -0
  110. package/templates/layers/node/typescript/src/utils/SuccessResponse.ts +17 -0
  111. package/templates/layers/node/typescript/src/utils/apiResponse.ts +4 -0
  112. package/templates/layers/node/typescript/src/utils/asyncHandler.ts +13 -0
  113. package/templates/layers/node/typescript/src/utils/cloudinaryUpload.ts +18 -7
  114. package/templates/layers/node/typescript/src/utils/expoNotification.ts +1 -1
  115. package/templates/layers/node/typescript/src/utils/genOtpToken.ts +7 -1
  116. package/templates/layers/node/typescript/src/utils/jwt.ts +8 -2
  117. package/templates/layers/node/typescript/src/utils/logger.ts +2 -1
  118. package/templates/layers/node/typescript/src/utils/pushHelper.ts +13 -9
  119. package/templates/layers/node-prisma/javascript/.env.example +3 -1
  120. package/templates/layers/node-prisma/javascript/package.json +11 -2
  121. package/templates/layers/node-prisma/javascript/src/app.js +2 -1
  122. package/templates/layers/node-prisma/javascript/src/controllers/auth.controller.js +90 -135
  123. package/templates/layers/node-prisma/javascript/src/controllers/push.controller.js +64 -76
  124. package/templates/layers/node-prisma/javascript/src/controllers/upload.controller.js +12 -19
  125. package/templates/layers/node-prisma/javascript/src/middleware/auth.middleware.js +5 -4
  126. package/templates/layers/node-prisma/javascript/src/middleware/errorHandler.js +6 -4
  127. package/templates/layers/node-prisma/javascript/src/routes/auth.route.js +2 -0
  128. package/templates/layers/node-prisma/javascript/src/services/auth.service.js +145 -122
  129. package/templates/layers/node-prisma/javascript/src/utils/ApiError.js +20 -0
  130. package/templates/layers/node-prisma/javascript/src/utils/SuccessResponse.js +15 -0
  131. package/templates/layers/node-prisma/javascript/src/utils/apiResponse.js +4 -0
  132. package/templates/layers/node-prisma/javascript/src/utils/asyncHandler.js +5 -0
  133. package/templates/layers/node-prisma/typescript/.env.example +3 -1
  134. package/templates/layers/node-prisma/typescript/.eslintrc.cjs +28 -0
  135. package/templates/layers/node-prisma/typescript/package.json +19 -5
  136. package/templates/layers/node-prisma/typescript/src/app.ts +3 -2
  137. package/templates/layers/node-prisma/typescript/src/config/prisma.ts +3 -2
  138. package/templates/layers/node-prisma/typescript/src/config/socket.ts +5 -4
  139. package/templates/layers/node-prisma/typescript/src/controllers/auth.controller.ts +89 -105
  140. package/templates/layers/node-prisma/typescript/src/controllers/push.controller.ts +67 -83
  141. package/templates/layers/node-prisma/typescript/src/controllers/upload.controller.ts +12 -19
  142. package/templates/layers/node-prisma/typescript/src/middleware/auth.middleware.ts +9 -13
  143. package/templates/layers/node-prisma/typescript/src/middleware/errorHandler.ts +18 -5
  144. package/templates/layers/node-prisma/typescript/src/middleware/rateLimiter.ts +2 -2
  145. package/templates/layers/node-prisma/typescript/src/routes/auth.route.ts +2 -0
  146. package/templates/layers/node-prisma/typescript/src/services/auth.service.ts +83 -31
  147. package/templates/layers/node-prisma/typescript/src/types/express.d.ts +5 -3
  148. package/templates/layers/node-prisma/typescript/src/utils/ApiError.ts +25 -0
  149. package/templates/layers/node-prisma/typescript/src/utils/SuccessResponse.ts +17 -0
  150. package/templates/layers/node-prisma/typescript/src/utils/apiResponse.ts +4 -0
  151. package/templates/layers/node-prisma/typescript/src/utils/asyncHandler.ts +13 -0
  152. package/templates/layers/node-prisma/typescript/src/utils/cloudinaryUpload.ts +18 -7
  153. package/templates/layers/node-prisma/typescript/src/utils/genOtpToken.ts +7 -1
  154. package/templates/layers/node-prisma/typescript/src/utils/jwt.ts +8 -2
  155. package/templates/layers/node-prisma/typescript/src/utils/logger.ts +2 -1
  156. package/templates/layers/node-prisma/typescript/src/utils/pushHelper.ts +10 -6
  157. package/templates/layers/prisma/javascript/package.json +8 -0
  158. package/templates/layers/prisma/typescript/package.json +14 -1
  159. package/templates/layers/prisma/typescript/prisma/schema.prisma +35 -10
  160. package/templates/layers/prisma/typescript/src/config/db.ts +3 -2
  161. package/templates/layers/react/javascript/package.json +6 -0
  162. package/templates/layers/react/javascript/src/components/common/Breadcrumbs.jsx +62 -58
  163. package/templates/layers/react/javascript/src/components/common/Pagination.jsx +164 -76
  164. package/templates/layers/react/javascript/src/components/common/SidebarModal.jsx +179 -0
  165. package/templates/layers/react/javascript/src/components/common/StatsCard.jsx +167 -0
  166. package/templates/layers/react/javascript/src/components/common/Table.jsx +248 -132
  167. package/templates/layers/react/javascript/src/components/common/Tabs.jsx +164 -112
  168. package/templates/layers/react/javascript/src/components/common/ToggleSwitch.jsx +3 -3
  169. package/templates/layers/react/javascript/src/components/layout/SidebarLayout.jsx +4 -12
  170. package/templates/layers/react/javascript/src/components/modals/Modal.jsx +53 -18
  171. package/templates/layers/react/javascript/src/components/ui/Input.jsx +49 -0
  172. package/templates/layers/react/javascript/src/components/ui/Sidebar.jsx +215 -0
  173. package/templates/layers/react/javascript/src/components/ui/SidebarHeader.jsx +25 -30
  174. package/templates/layers/react/javascript/src/config/appConfig.js +3 -3
  175. package/templates/layers/react/javascript/src/hooks/useWindowSize.js +2 -2
  176. package/templates/layers/react/typescript/.eslintrc.cjs +20 -0
  177. package/templates/layers/react/typescript/package.json +11 -2
  178. package/templates/layers/react/typescript/src/components/common/Breadcrumbs.tsx +32 -6
  179. package/templates/layers/react/typescript/src/components/common/Pagination.tsx +151 -57
  180. package/templates/layers/react/typescript/src/components/common/SidebarModal.tsx +199 -0
  181. package/templates/layers/react/typescript/src/components/common/StatsCard.tsx +231 -0
  182. package/templates/layers/react/typescript/src/components/common/Table.tsx +201 -38
  183. package/templates/layers/react/typescript/src/components/common/Tabs.tsx +56 -12
  184. package/templates/layers/react/typescript/src/components/common/ToggleSwitch.tsx +15 -3
  185. package/templates/layers/react/typescript/src/components/layout/SidebarLayout.tsx +3 -6
  186. package/templates/layers/react/typescript/src/components/modals/Modal.tsx +151 -0
  187. package/templates/layers/react/typescript/src/components/ui/Input.tsx +58 -0
  188. package/templates/layers/react/typescript/src/components/ui/Sidebar.tsx +116 -53
  189. package/templates/layers/react/typescript/src/components/ui/SidebarHeader.tsx +5 -4
  190. package/templates/layers/react/typescript/src/config/appConfig.ts +3 -3
  191. package/templates/layers/react/typescript/src/hooks/useWindowSize.ts +2 -3
  192. package/templates/layers/react/typescript/src/main.tsx +11 -11
  193. package/templates/layers/react/typescript/src/services/authService.ts +1 -1
  194. package/templates/layers/react/typescript/src/store/useAuthStore.ts +24 -9
  195. package/templates/layers/react-native/base/app.json +9 -22
  196. package/templates/layers/react-native/base/eslint.config.js +5 -1
  197. package/templates/layers/react-native/base/package.json +11 -1
  198. package/templates/layers/react-native/base/src/app/(auth)/edit-profile.tsx +7 -7
  199. package/templates/layers/react-native/base/src/app/(auth)/login.tsx +31 -7
  200. package/templates/layers/react-native/base/src/app/(tabs)/_layout.tsx +3 -3
  201. package/templates/layers/react-native/base/src/app/(tabs)/index.tsx +6 -5
  202. package/templates/layers/react-native/base/src/app/_layout.tsx +3 -3
  203. package/templates/layers/react-native/base/src/app/doc-viewer.tsx +3 -3
  204. package/templates/layers/react-native/base/src/components/common/ConfirmationToast.tsx +58 -40
  205. package/templates/layers/react-native/base/src/components/common/GoogleAuthButton.tsx +144 -0
  206. package/templates/layers/react-native/base/src/components/common/LocationLoader.tsx +64 -9
  207. package/templates/layers/react-native/base/src/components/common/Typography.tsx +54 -19
  208. package/templates/layers/react-native/base/src/components/ui/ClassicTabBar.tsx +15 -12
  209. package/templates/layers/react-native/base/src/components/ui/CustomTabBar.tsx +84 -90
  210. package/templates/layers/react-native/base/src/components/ui/Drawer.tsx +344 -0
  211. package/templates/layers/react-native/base/src/components/ui/Sidebar.tsx +4 -4
  212. package/templates/layers/react-native/base/src/components/ui/buttons/Button.tsx +29 -6
  213. package/templates/layers/react-native/base/src/components/ui/data-display/Accordion.tsx +2 -2
  214. package/templates/layers/react-native/base/src/components/ui/data-display/Badge.tsx +1 -2
  215. package/templates/layers/react-native/base/src/components/ui/data-display/Image.tsx +3 -3
  216. package/templates/layers/react-native/base/src/components/ui/data-display/SwipeableRow.tsx +28 -7
  217. package/templates/layers/react-native/base/src/components/ui/feedback/GestureWrapper.tsx +11 -4
  218. package/templates/layers/react-native/base/src/components/ui/inputs/DatePicker.tsx +3 -3
  219. package/templates/layers/react-native/base/src/components/ui/inputs/ImagePickerModal.tsx +45 -19
  220. package/templates/layers/react-native/base/src/components/ui/inputs/Input.tsx +28 -9
  221. package/templates/layers/react-native/base/src/components/ui/inputs/SearchablePicker.tsx +199 -0
  222. package/templates/layers/react-native/base/src/components/ui/inputs/Switch.tsx +7 -7
  223. package/templates/layers/react-native/base/src/components/ui/inputs/WebLocationPicker.tsx +146 -72
  224. package/templates/layers/react-native/base/src/config/api.ts +5 -5
  225. package/templates/layers/react-native/base/src/config/appConfig.ts +1 -1
  226. package/templates/layers/react-native/base/src/services/auth.service.ts +3 -1
  227. package/templates/layers/react-native/base/src/services/permission.service.ts +0 -2
  228. package/templates/layers/react-native/base/src/services/socket.service.ts +0 -7
  229. package/templates/layers/react-native/base/src/store/useAuthStore.ts +61 -16
  230. package/templates/layers/react-native/base/src/store/useNotificationStore.ts +36 -42
  231. package/templates/layers/react-native/base/src/types/images.d.ts +7 -5
  232. package/templates/layers/react-native/base/src/utils/fileDownload.ts +206 -0
  233. package/templates/layers/react-native/base/src/utils/storage.ts +1 -1
  234. package/templates/presets/electron/preset.json +9 -0
  235. package/templates/presets/electron-ts/preset.json +9 -0
  236. package/templates/presets/production-fullstack/overrides/client/package.json +9 -0
  237. package/templates/presets/production-fullstack/overrides/mobile/app.json +2 -15
  238. package/templates/presets/production-fullstack/overrides/mobile/package.json +11 -1
  239. package/templates/presets/production-fullstack/overrides/mobile/src/app/_layout.tsx +3 -3
  240. package/templates/presets/production-fullstack/overrides/mobile/src/components/ui/Drawer.tsx +344 -0
  241. package/templates/presets/production-fullstack/overrides/mobile/src/components/ui/inputs/DatePicker.tsx +3 -3
  242. package/templates/presets/production-fullstack/overrides/mobile/src/components/ui/inputs/ImagePickerModal.tsx +3 -3
  243. package/templates/presets/production-fullstack/overrides/server/package.json +11 -2
  244. package/templates/layers/react/javascript/src/components/ui/sidebar.jsx +0 -169
  245. package/templates/layers/react-native/base/src/components/ui/BottomModal.tsx +0 -176
  246. package/templates/presets/production-fullstack/overrides/mobile/src/components/ui/BottomModal.tsx +0 -159
  247. /package/templates/layers/react-native/base/assets/images/logo/{logoWithBg.png → icon.png} +0 -0
@@ -0,0 +1,199 @@
1
+ import React, { useEffect, ReactNode } from "react";
2
+ import { createPortal } from "react-dom";
3
+ import { motion, AnimatePresence } from "framer-motion";
4
+ import { IoClose } from "react-icons/io5";
5
+ import { useUiStore } from "../../store/useUiStore";
6
+
7
+ export type SidebarModalSide = "left" | "right";
8
+
9
+ export interface SidebarModalProps {
10
+ isOpen: boolean;
11
+ onClose: () => void;
12
+ side?: SidebarModalSide;
13
+ title?: ReactNode;
14
+ description?: ReactNode;
15
+ children: ReactNode;
16
+ width?: string;
17
+ showCloseButton?: boolean;
18
+ closeOnBackdrop?: boolean;
19
+ closeOnEsc?: boolean;
20
+ className?: string;
21
+ header?: ReactNode;
22
+ footer?: ReactNode;
23
+ bodyClassName?: string;
24
+ hasBackdrop?: boolean;
25
+ }
26
+
27
+ export const SidebarModal: React.FC<SidebarModalProps> = ({
28
+ isOpen,
29
+ onClose,
30
+ side = "right",
31
+ title,
32
+ description,
33
+ children,
34
+ width = "w-full sm:w-[420px]",
35
+ showCloseButton = true,
36
+ closeOnBackdrop = true,
37
+ closeOnEsc = true,
38
+ className = "",
39
+ header,
40
+ footer,
41
+ bodyClassName = "p-4 space-y-4",
42
+ hasBackdrop = true,
43
+ }) => {
44
+ const setModalOpen = useUiStore((state) => state.setModalOpen);
45
+
46
+ // Sync with global UI store modal state for scroll locking
47
+ useEffect(() => {
48
+ if (isOpen) {
49
+ setModalOpen(true);
50
+ return () => {
51
+ setModalOpen(false);
52
+ };
53
+ }
54
+ }, [isOpen, setModalOpen]);
55
+
56
+ // Handle ESC key
57
+ useEffect(() => {
58
+ if (!isOpen || !closeOnEsc) return;
59
+
60
+ const handleKeyDown = (e: KeyboardEvent) => {
61
+ if (e.key === "Escape") {
62
+ onClose();
63
+ }
64
+ };
65
+
66
+ window.addEventListener("keydown", handleKeyDown);
67
+ return () => window.removeEventListener("keydown", handleKeyDown);
68
+ }, [isOpen, closeOnEsc, onClose]);
69
+
70
+ const slideVariants = {
71
+ hidden: {
72
+ x: side === "right" ? "100%" : "-100%",
73
+ opacity: 0.7,
74
+ },
75
+ visible: {
76
+ x: 0,
77
+ opacity: 1,
78
+ transition: {
79
+ type: "spring",
80
+ damping: 30,
81
+ stiffness: 300,
82
+ mass: 0.8,
83
+ },
84
+ },
85
+ exit: {
86
+ x: side === "right" ? "100%" : "-100%",
87
+ opacity: 0.5,
88
+ transition: {
89
+ type: "tween",
90
+ duration: 0.22,
91
+ ease: "easeInOut",
92
+ },
93
+ },
94
+ };
95
+
96
+ const backdropVariants = {
97
+ hidden: { opacity: 0 },
98
+ visible: { opacity: 1, transition: { duration: 0.2 } },
99
+ exit: { opacity: 0, transition: { duration: 0.18 } },
100
+ };
101
+
102
+ const content = (
103
+ <AnimatePresence>
104
+ {isOpen && (
105
+ <div className="fixed inset-0 z-50 overflow-hidden">
106
+ {/* Backdrop */}
107
+ {hasBackdrop && (
108
+ <motion.div
109
+ variants={backdropVariants}
110
+ initial="hidden"
111
+ animate="visible"
112
+ exit="exit"
113
+ className="fixed inset-0 bg-black/40 backdrop-blur-xs transition-opacity"
114
+ onClick={() => {
115
+ if (closeOnBackdrop) onClose();
116
+ }}
117
+ />
118
+ )}
119
+
120
+ {/* Sliding Panel */}
121
+ <div
122
+ className={`fixed inset-y-0 ${
123
+ side === "right" ? "right-0" : "left-0"
124
+ } max-w-full flex`}
125
+ >
126
+ <motion.div
127
+ variants={slideVariants}
128
+ initial="hidden"
129
+ animate="visible"
130
+ exit="exit"
131
+ className={`
132
+ relative h-full flex flex-col bg-base-100 border-base-300 shadow-2xl overflow-hidden
133
+ ${side === "right" ? "border-l" : "border-r"}
134
+ ${width}
135
+ ${className}
136
+ `}
137
+ onClick={(e) => e.stopPropagation()}
138
+ >
139
+ {/* Optional Custom Header or Default Header */}
140
+ {header ? (
141
+ <div className="shrink-0">{header}</div>
142
+ ) : title ? (
143
+ <div className="flex items-center justify-between p-4 border-b border-base-300 bg-base-200/40 shrink-0">
144
+ <div className="flex-1 min-w-0 pr-2">
145
+ <h2 className="text-base font-bold text-base-content truncate">
146
+ {title}
147
+ </h2>
148
+ {description && (
149
+ <p className="text-xs text-base-content/60 truncate mt-0.5">
150
+ {description}
151
+ </p>
152
+ )}
153
+ </div>
154
+ {showCloseButton && (
155
+ <button
156
+ type="button"
157
+ className="btn btn-circle btn-sm btn-error btn-soft text-error/70 hover:text-error hover:bg-error/20 hover:shadow-sm hover:rotate-180 transition-transform duration-200"
158
+ onClick={onClose}
159
+ title="Close"
160
+ >
161
+ <IoClose size={18} />
162
+ </button>
163
+ )}
164
+ </div>
165
+ ) : showCloseButton ? (
166
+ <button
167
+ type="button"
168
+ className="btn btn-circle btn-sm btn-error btn-soft text-error/70 hover:text-error hover:bg-error/20 hover:shadow-sm hover:rotate-180 transition-transform duration-200"
169
+ onClick={onClose}
170
+ title="Close"
171
+ >
172
+ <IoClose size={18} />
173
+ </button>
174
+ ) : null}
175
+
176
+ {/* Scrollable Body */}
177
+ <div className={`flex-1 overflow-y-auto ${bodyClassName}`}>
178
+ {children}
179
+ </div>
180
+
181
+ {/* Optional Footer */}
182
+ {footer && (
183
+ <div className="p-4 border-t border-base-300 bg-base-200/30 shrink-0">
184
+ {footer}
185
+ </div>
186
+ )}
187
+ </motion.div>
188
+ </div>
189
+ </div>
190
+ )}
191
+ </AnimatePresence>
192
+ );
193
+
194
+ return typeof document !== "undefined"
195
+ ? createPortal(content, document.body)
196
+ : null;
197
+ };
198
+
199
+ export default SidebarModal;
@@ -0,0 +1,231 @@
1
+ import React, { ReactNode } from "react";
2
+
3
+ export type StatsCardColor =
4
+ | "blue"
5
+ | "emerald"
6
+ | "amber"
7
+ | "purple"
8
+ | "primary"
9
+ | "secondary"
10
+ | "success"
11
+ | "warning"
12
+ | "info"
13
+ | "error";
14
+
15
+ export interface StatsCardTrend {
16
+ value: string | number;
17
+ isPositive?: boolean;
18
+ label?: string;
19
+ }
20
+
21
+ export interface StatsCardBadge {
22
+ label: string | number;
23
+ variant?:
24
+ | "info"
25
+ | "success"
26
+ | "warning"
27
+ | "error"
28
+ | "neutral"
29
+ | "ghost"
30
+ | "secondary"
31
+ | "primary";
32
+ }
33
+
34
+ export interface StatsCardProps {
35
+ /** Main title or label of the stat card */
36
+ title: ReactNode;
37
+ /** Primary metric value (e.g. number, count, currency) */
38
+ value: ReactNode;
39
+ /** Secondary value displayed beside the main value (e.g. "75%", "Queue", "USD") */
40
+ subValue?: ReactNode;
41
+ /** Trend indicator with direction and value */
42
+ trend?: StatsCardTrend | ReactNode;
43
+ /** Badge displayed in the bottom footer row */
44
+ badge?: StatsCardBadge | ReactNode;
45
+ /** Descriptive subtext displayed in the footer row */
46
+ description?: ReactNode;
47
+ /** Icon displayed in the top-right corner */
48
+ icon?: ReactNode;
49
+ /** Color theme for icon container and active selection border */
50
+ color?: StatsCardColor;
51
+ /** Active selection state with highlighting ring */
52
+ active?: boolean;
53
+ /** Displays a loading spinner in place of value */
54
+ loading?: boolean;
55
+ /** Click handler for interactive selection / drill-down */
56
+ onClick?: () => void;
57
+ /** Custom additional container classes */
58
+ className?: string;
59
+ /** @deprecated use `subValue` instead */
60
+ percentage?: number | string;
61
+ }
62
+
63
+ const COLOR_STYLES: Record<
64
+ StatsCardColor,
65
+ {
66
+ icon: string;
67
+ activeBorder: string;
68
+ }
69
+ > = {
70
+ blue: {
71
+ icon: "bg-blue-500/10 text-blue-600 dark:text-blue-400",
72
+ activeBorder:
73
+ "border-blue-500/50 shadow-sm ring-1 ring-blue-500/20 bg-gradient-to-br from-base-100 to-blue-500/[0.03]",
74
+ },
75
+ emerald: {
76
+ icon: "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400",
77
+ activeBorder:
78
+ "border-emerald-500/50 shadow-sm ring-1 ring-emerald-500/20 bg-gradient-to-br from-base-100 to-emerald-500/[0.03]",
79
+ },
80
+ amber: {
81
+ icon: "bg-amber-500/10 text-amber-600 dark:text-amber-400",
82
+ activeBorder:
83
+ "border-amber-500/50 shadow-sm ring-1 ring-amber-500/20 bg-gradient-to-br from-base-100 to-amber-500/[0.03]",
84
+ },
85
+ purple: {
86
+ icon: "bg-purple-500/10 text-purple-600 dark:text-purple-400",
87
+ activeBorder:
88
+ "border-purple-500/50 shadow-sm ring-1 ring-purple-500/20 bg-gradient-to-br from-base-100 to-purple-500/[0.03]",
89
+ },
90
+ primary: {
91
+ icon: "bg-primary/10 text-primary",
92
+ activeBorder:
93
+ "border-primary/50 shadow-sm ring-1 ring-primary/20 bg-gradient-to-br from-base-100 to-primary/[0.03]",
94
+ },
95
+ secondary: {
96
+ icon: "bg-secondary/10 text-secondary",
97
+ activeBorder:
98
+ "border-secondary/50 shadow-sm ring-1 ring-secondary/20 bg-gradient-to-br from-base-100 to-secondary/[0.03]",
99
+ },
100
+ success: {
101
+ icon: "bg-success/10 text-success",
102
+ activeBorder:
103
+ "border-success/50 shadow-sm ring-1 ring-success/20 bg-gradient-to-br from-base-100 to-success/[0.03]",
104
+ },
105
+ warning: {
106
+ icon: "bg-warning/10 text-warning",
107
+ activeBorder:
108
+ "border-warning/50 shadow-sm ring-1 ring-warning/20 bg-gradient-to-br from-base-100 to-warning/[0.03]",
109
+ },
110
+ info: {
111
+ icon: "bg-info/10 text-info",
112
+ activeBorder:
113
+ "border-info/50 shadow-sm ring-1 ring-info/20 bg-gradient-to-br from-base-100 to-info/[0.03]",
114
+ },
115
+ error: {
116
+ icon: "bg-error/10 text-error",
117
+ activeBorder:
118
+ "border-error/50 shadow-sm ring-1 ring-error/20 bg-gradient-to-br from-base-100 to-error/[0.03]",
119
+ },
120
+ };
121
+
122
+ export const StatsCard: React.FC<StatsCardProps> = ({
123
+ title,
124
+ value,
125
+ subValue,
126
+ trend,
127
+ icon,
128
+ color = "primary",
129
+ percentage,
130
+ badge,
131
+ description,
132
+ active = false,
133
+ loading = false,
134
+ onClick,
135
+ className = "",
136
+ }) => {
137
+ const styles = COLOR_STYLES[color] || COLOR_STYLES.primary;
138
+
139
+ // Support both new `subValue` prop and deprecated `percentage` prop
140
+ const displaySubValue =
141
+ subValue !== undefined
142
+ ? subValue
143
+ : percentage !== undefined && percentage !== null
144
+ ? typeof percentage === "number"
145
+ ? `${percentage}%`
146
+ : percentage
147
+ : null;
148
+
149
+ const renderBadge = () => {
150
+ if (!badge) return null;
151
+ if (React.isValidElement(badge)) return badge;
152
+ const b = badge as StatsCardBadge;
153
+ return (
154
+ <span className={`badge badge-xs font-medium ${b.variant ? `badge-${b.variant}` : "badge-ghost"}`}>
155
+ {b.label}
156
+ </span>
157
+ );
158
+ };
159
+
160
+ const renderTrend = () => {
161
+ if (!trend) return null;
162
+ if (React.isValidElement(trend)) return trend;
163
+ const t = trend as StatsCardTrend;
164
+ const isPos = t.isPositive ?? true;
165
+ return (
166
+ <span
167
+ className={`inline-flex items-center gap-0.5 text-xs font-semibold ${
168
+ isPos ? "text-success" : "text-error"
169
+ }`}
170
+ >
171
+ <span>{isPos ? "↑" : "↓"}</span>
172
+ <span>{t.value}</span>
173
+ {t.label && <span className="opacity-70 font-normal ml-0.5">{t.label}</span>}
174
+ </span>
175
+ );
176
+ };
177
+
178
+ return (
179
+ <div
180
+ onClick={onClick}
181
+ className={`card bg-base-100 border transition-all duration-200 p-4 rounded-2xl ${
182
+ onClick ? "cursor-pointer hover:shadow-sm active:scale-[0.99]" : ""
183
+ } ${
184
+ active
185
+ ? styles.activeBorder
186
+ : "border-base-200 hover:border-base-300"
187
+ } ${className}`}
188
+ >
189
+ {/* Top row: Title and Icon */}
190
+ <div className="flex items-center justify-between gap-2">
191
+ <span className="text-xs font-semibold uppercase tracking-wider text-base-content/60 truncate">
192
+ {title}
193
+ </span>
194
+ {icon && (
195
+ <div
196
+ className={`w-9 h-9 rounded-xl flex items-center justify-center shrink-0 ${styles.icon}`}
197
+ >
198
+ {icon}
199
+ </div>
200
+ )}
201
+ </div>
202
+
203
+ {/* Middle row: Value and optional SubValue/Trend */}
204
+ <div className="mt-2 flex items-baseline gap-2 flex-wrap">
205
+ <span className="text-2xl md:text-3xl font-bold tracking-tight text-base-content">
206
+ {loading ? (
207
+ <span className="loading loading-spinner loading-sm"></span>
208
+ ) : (
209
+ value
210
+ )}
211
+ </span>
212
+ {displaySubValue !== null && !loading && (
213
+ <span className="text-xs font-semibold opacity-80">
214
+ {displaySubValue}
215
+ </span>
216
+ )}
217
+ {!loading && renderTrend()}
218
+ </div>
219
+
220
+ {/* Bottom row: Badge and description */}
221
+ {(badge || description) && (
222
+ <div className="mt-2 flex items-center gap-1.5 text-xs text-base-content/60 min-w-0">
223
+ {renderBadge()}
224
+ {description && <span className="truncate">{description}</span>}
225
+ </div>
226
+ )}
227
+ </div>
228
+ );
229
+ };
230
+
231
+ export default StatsCard;