@imksh/create 1.0.28 → 1.0.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/package.json +1 -1
  2. package/src/utils/askTemplate.js +2 -1
  3. package/templates/layers/electron/base/electron/main.js +262 -0
  4. package/templates/layers/electron/base/electron/preload.js +37 -0
  5. package/templates/layers/electron/base/electron-builder.json5 +68 -0
  6. package/templates/layers/electron/javascript/_gitignore +38 -0
  7. package/templates/layers/electron/javascript/package.json +49 -0
  8. package/templates/layers/electron/javascript/src/components/layout/SidebarLayout.jsx +43 -0
  9. package/templates/layers/electron/javascript/src/components/ui/TitleBar.jsx +130 -0
  10. package/templates/layers/electron/javascript/src/config/appConfig.js +51 -0
  11. package/templates/layers/electron/javascript/src/hooks/useElectron.js +68 -0
  12. package/templates/layers/electron/javascript/src/main.jsx +17 -0
  13. package/templates/layers/electron/javascript/src/store/useUiStore.js +59 -0
  14. package/templates/layers/electron/javascript/vite.config.js +29 -0
  15. package/templates/layers/electron/typescript/_gitignore +38 -0
  16. package/templates/layers/electron/typescript/electron/main.ts +213 -0
  17. package/templates/layers/electron/typescript/electron/preload.ts +52 -0
  18. package/templates/layers/electron/typescript/package.json +52 -0
  19. package/templates/layers/electron/typescript/src/components/layout/SidebarLayout.tsx +42 -0
  20. package/templates/layers/electron/typescript/src/components/ui/TitleBar.tsx +120 -0
  21. package/templates/layers/electron/typescript/src/config/appConfig.ts +52 -0
  22. package/templates/layers/electron/typescript/src/hooks/useElectron.ts +68 -0
  23. package/templates/layers/electron/typescript/src/main.tsx +17 -0
  24. package/templates/layers/electron/typescript/src/store/useUiStore.ts +97 -0
  25. package/templates/layers/electron/typescript/src/vite-env.d.ts +13 -0
  26. package/templates/layers/electron/typescript/tsconfig.json +25 -0
  27. package/templates/layers/electron/typescript/tsconfig.node.json +11 -0
  28. package/templates/layers/electron/typescript/vite.config.ts +29 -0
  29. package/templates/layers/next/javascript/src/components/common/Breadcrumbs.jsx +44 -6
  30. package/templates/layers/next/javascript/src/components/common/Loading.jsx +1 -0
  31. package/templates/layers/next/javascript/src/components/common/LottieIcon.jsx +1 -0
  32. package/templates/layers/next/javascript/src/components/common/Pagination.jsx +170 -0
  33. package/templates/layers/next/javascript/src/components/common/SidebarModal.jsx +180 -0
  34. package/templates/layers/next/javascript/src/components/common/StatsCard.jsx +168 -0
  35. package/templates/layers/next/javascript/src/components/common/Table.jsx +191 -41
  36. package/templates/layers/next/javascript/src/components/common/Tabs.jsx +187 -0
  37. package/templates/layers/next/javascript/src/components/common/ToggleSwitch.jsx +4 -3
  38. package/templates/layers/next/javascript/src/components/layout/ProtectedRoute.jsx +2 -1
  39. package/templates/layers/next/javascript/src/components/layout/PublicLayout.jsx +3 -6
  40. package/templates/layers/next/javascript/src/components/layout/SidebarLayout.jsx +4 -17
  41. package/templates/layers/next/javascript/src/components/modals/ConfirmModal.jsx +4 -12
  42. package/templates/layers/next/javascript/src/components/modals/Modal.jsx +50 -19
  43. package/templates/layers/next/javascript/src/components/ui/Input.jsx +50 -0
  44. package/templates/layers/next/{typescript/src/components/ui/sidebar.tsx → javascript/src/components/ui/Sidebar.jsx} +65 -53
  45. package/templates/layers/next/javascript/src/components/ui/SidebarHeader.jsx +11 -6
  46. package/templates/layers/next/javascript/src/config/appConfig.js +4 -4
  47. package/templates/layers/next/javascript/src/hooks/useWindowSize.js +2 -3
  48. package/templates/layers/next/javascript/src/store/useUiStore.js +9 -4
  49. package/templates/layers/next/typescript/src/components/common/Breadcrumbs.tsx +57 -8
  50. package/templates/layers/next/typescript/src/components/common/Loading.tsx +1 -0
  51. package/templates/layers/next/typescript/src/components/common/Pagination.tsx +186 -0
  52. package/templates/layers/next/typescript/src/components/common/SidebarModal.tsx +200 -0
  53. package/templates/layers/next/typescript/src/components/common/StatsCard.tsx +232 -0
  54. package/templates/layers/next/typescript/src/components/common/Table.tsx +201 -35
  55. package/templates/layers/next/typescript/src/components/common/Tabs.tsx +190 -0
  56. package/templates/layers/next/typescript/src/components/common/ToggleSwitch.tsx +12 -8
  57. package/templates/layers/next/typescript/src/components/layout/PublicLayout.tsx +8 -6
  58. package/templates/layers/next/typescript/src/components/layout/SidebarLayout.tsx +8 -17
  59. package/templates/layers/next/typescript/src/components/modals/ConfirmModal.tsx +1 -0
  60. package/templates/layers/next/typescript/src/components/modals/Modal.tsx +67 -21
  61. package/templates/layers/next/typescript/src/components/ui/Button.tsx +1 -0
  62. package/templates/layers/next/typescript/src/components/ui/Input.tsx +40 -60
  63. package/templates/layers/next/{javascript/src/components/ui/sidebar.jsx → typescript/src/components/ui/Sidebar.tsx} +77 -44
  64. package/templates/layers/next/typescript/src/components/ui/SidebarHeader.tsx +16 -10
  65. package/templates/layers/next/typescript/src/config/appConfig.ts +4 -4
  66. package/templates/layers/next/typescript/src/hooks/useWindowSize.ts +2 -3
  67. package/templates/layers/next/typescript/src/store/useUiStore.ts +14 -4
  68. package/templates/layers/node/javascript/package.json +2 -1
  69. package/templates/layers/node/javascript/src/controllers/push.controller.js +35 -14
  70. package/templates/layers/node/javascript/src/models/pushSubscription.model.js +5 -0
  71. package/templates/layers/node/javascript/src/utils/pushHelper.js +59 -12
  72. package/templates/layers/node/typescript/package-lock.json +3795 -0
  73. package/templates/layers/node/typescript/package.json +3 -2
  74. package/templates/layers/node/typescript/src/controllers/push.controller.ts +35 -14
  75. package/templates/layers/node/typescript/src/models/pushSubscription.model.ts +6 -0
  76. package/templates/layers/node/typescript/src/services/auth.service.ts +3 -3
  77. package/templates/layers/node/typescript/src/utils/pushHelper.ts +66 -12
  78. package/templates/layers/node-prisma/javascript/package.json +6 -3
  79. package/templates/layers/node-prisma/javascript/prisma/schema.prisma +20 -9
  80. package/templates/layers/node-prisma/javascript/prisma.config.js +12 -0
  81. package/templates/layers/node-prisma/javascript/src/config/{db.js → prisma.js} +9 -1
  82. package/templates/layers/node-prisma/javascript/src/controllers/push.controller.js +58 -19
  83. package/templates/layers/node-prisma/javascript/src/utils/pushHelper.js +62 -14
  84. package/templates/layers/node-prisma/typescript/package-lock.json +4808 -0
  85. package/templates/layers/node-prisma/typescript/package.json +8 -4
  86. package/templates/layers/node-prisma/typescript/prisma/schema.prisma +20 -9
  87. package/templates/layers/node-prisma/typescript/prisma.config.ts +12 -0
  88. package/templates/layers/node-prisma/typescript/src/config/{db.ts → prisma.ts} +9 -1
  89. package/templates/layers/node-prisma/typescript/src/controllers/push.controller.ts +67 -22
  90. package/templates/layers/node-prisma/typescript/src/index.ts +1 -1
  91. package/templates/layers/node-prisma/typescript/src/middleware/auth.middleware.ts +15 -5
  92. package/templates/layers/node-prisma/typescript/src/services/auth.service.ts +134 -128
  93. package/templates/layers/node-prisma/typescript/src/utils/pushHelper.ts +69 -14
  94. package/templates/layers/prisma/typescript/prisma/schema.prisma +35 -10
  95. package/templates/layers/react/javascript/src/components/common/Breadcrumbs.jsx +62 -58
  96. package/templates/layers/react/javascript/src/components/common/Pagination.jsx +164 -76
  97. package/templates/layers/react/javascript/src/components/common/SidebarModal.jsx +179 -0
  98. package/templates/layers/react/javascript/src/components/common/StatsCard.jsx +167 -0
  99. package/templates/layers/react/javascript/src/components/common/Table.jsx +248 -132
  100. package/templates/layers/react/javascript/src/components/common/Tabs.jsx +164 -112
  101. package/templates/layers/react/javascript/src/components/common/ToggleSwitch.jsx +3 -3
  102. package/templates/layers/react/javascript/src/components/layout/SidebarLayout.jsx +4 -12
  103. package/templates/layers/react/javascript/src/components/modals/Modal.jsx +53 -18
  104. package/templates/layers/react/javascript/src/components/ui/Input.jsx +49 -0
  105. package/templates/layers/react/javascript/src/components/ui/Sidebar.jsx +215 -0
  106. package/templates/layers/react/javascript/src/components/ui/SidebarHeader.jsx +25 -30
  107. package/templates/layers/react/javascript/src/config/appConfig.js +3 -3
  108. package/templates/layers/react/javascript/src/hooks/useWindowSize.js +2 -2
  109. package/templates/layers/react/typescript/src/components/common/Breadcrumbs.tsx +32 -6
  110. package/templates/layers/react/typescript/src/components/common/Pagination.tsx +151 -57
  111. package/templates/layers/react/typescript/src/components/common/SidebarModal.tsx +199 -0
  112. package/templates/layers/react/typescript/src/components/common/StatsCard.tsx +231 -0
  113. package/templates/layers/react/typescript/src/components/common/Table.tsx +197 -34
  114. package/templates/layers/react/typescript/src/components/common/Tabs.tsx +56 -12
  115. package/templates/layers/react/typescript/src/components/common/ToggleSwitch.tsx +15 -3
  116. package/templates/layers/react/typescript/src/components/layout/SidebarLayout.tsx +3 -6
  117. package/templates/layers/react/typescript/src/components/modals/Modal.tsx +151 -0
  118. package/templates/layers/react/typescript/src/components/ui/Input.tsx +58 -0
  119. package/templates/layers/react/typescript/src/components/ui/Sidebar.tsx +116 -53
  120. package/templates/layers/react/typescript/src/components/ui/SidebarHeader.tsx +5 -4
  121. package/templates/layers/react/typescript/src/config/appConfig.ts +3 -3
  122. package/templates/layers/react/typescript/src/hooks/useWindowSize.ts +2 -3
  123. package/templates/layers/react-native/base/src/components/ui/BiometricLock.tsx +2 -1
  124. package/templates/layers/react-native/base/src/config/appConfig.ts +44 -0
  125. package/templates/presets/electron/preset.json +9 -0
  126. package/templates/presets/electron-ts/preset.json +9 -0
  127. package/templates/layers/react/javascript/src/components/ui/sidebar.jsx +0 -169
@@ -0,0 +1,51 @@
1
+ const appConfig = {
2
+ app: {
3
+ name: "Electron App",
4
+ logo: "./logo.png",
5
+ shortName: "App",
6
+ description: "An Electron application built with @imksh/create",
7
+ version: "1.0.0",
8
+ author: "imksh",
9
+ },
10
+
11
+ api: {
12
+ baseURL: import.meta.env.VITE_API_URL,
13
+ timeout: 30000,
14
+ },
15
+
16
+ routes: {
17
+ home: "/",
18
+ login: "/login",
19
+ profile: "/profile",
20
+ },
21
+
22
+ pagination: {
23
+ pageSize: 10,
24
+ },
25
+
26
+ github: {
27
+ repository: "",
28
+ },
29
+
30
+ social: {
31
+ website: "",
32
+ linkedin: "",
33
+ github: "",
34
+ twitter: "",
35
+ },
36
+
37
+ features: {
38
+ darkMode: true,
39
+ notifications: true,
40
+ sidebar: true,
41
+ electron: true,
42
+ customTitleBar: true,
43
+ },
44
+
45
+ electron: {
46
+ tray: true,
47
+ autoUpdater: false, // Set to true and configure publish in electron-builder.json5 to enable
48
+ },
49
+ };
50
+
51
+ export default appConfig;
@@ -0,0 +1,68 @@
1
+ import { useEffect } from "react";
2
+
3
+ /**
4
+ * useElectron — safely access window.electronAPI in the renderer.
5
+ *
6
+ * Returns typed wrappers for all IPC calls. Every method is a no-op
7
+ * (or returns a resolved promise) when running outside of Electron
8
+ * (e.g. during browser-based development or testing).
9
+ *
10
+ * @returns {{ isElectron: boolean } & ElectronAPI}
11
+ */
12
+ export function useElectron() {
13
+ const isElectron = typeof window !== "undefined" && Boolean(window.electronAPI);
14
+
15
+ return {
16
+ isElectron,
17
+
18
+ // Window controls
19
+ minimize: () => window.electronAPI?.minimize(),
20
+ maximize: () => window.electronAPI?.maximize(),
21
+ close: () => window.electronAPI?.close(),
22
+
23
+ // Window state
24
+ isMaximized: () => window.electronAPI?.isMaximized() ?? Promise.resolve(false),
25
+
26
+ // App info
27
+ getVersion: () => window.electronAPI?.getVersion() ?? Promise.resolve("web"),
28
+ getPlatform: () => window.electronAPI?.getPlatform() ?? Promise.resolve("web"),
29
+
30
+ // Shell
31
+ openExternal: (url) =>
32
+ window.electronAPI?.openExternal(url) ?? Promise.resolve(),
33
+
34
+ // Event subscription helpers
35
+ onMaximizeChange: (cb) => window.electronAPI?.onMaximizeChange(cb),
36
+ onMenuAction: (cb) => window.electronAPI?.onMenuAction(cb),
37
+
38
+ // Cleanup
39
+ removeAllListeners: (channel) =>
40
+ window.electronAPI?.removeAllListeners(channel),
41
+ };
42
+ }
43
+
44
+ /**
45
+ * useElectronEvent — subscribe to an Electron IPC event in a React component.
46
+ *
47
+ * Automatically cleans up the listener on unmount.
48
+ *
49
+ * @param {"maximize" | "menu"} event
50
+ * @param {Function} callback
51
+ */
52
+ export function useElectronEvent(event, callback) {
53
+ const electron = useElectron();
54
+
55
+ useEffect(() => {
56
+ if (!electron.isElectron) return;
57
+
58
+ if (event === "maximize") {
59
+ electron.onMaximizeChange(callback);
60
+ return () => electron.removeAllListeners("window:maximized-change");
61
+ }
62
+
63
+ if (event === "menu") {
64
+ electron.onMenuAction(callback);
65
+ return () => electron.removeAllListeners("menu:preferences");
66
+ }
67
+ }, [event, callback, electron.isElectron]);
68
+ }
@@ -0,0 +1,17 @@
1
+ import React from "react";
2
+ import ReactDOM from "react-dom/client";
3
+ import { HashRouter } from "react-router-dom";
4
+ import App from "./App.jsx";
5
+ import "./index.css";
6
+
7
+ // HashRouter is required for Electron because the renderer is served via
8
+ // the file:// protocol in production, which breaks BrowserRouter's history API.
9
+ // Hash-based URLs (#/path) work correctly in both dev and packaged builds.
10
+
11
+ ReactDOM.createRoot(document.getElementById("root")).render(
12
+ <React.StrictMode>
13
+ <HashRouter>
14
+ <App />
15
+ </HashRouter>
16
+ </React.StrictMode>,
17
+ );
@@ -0,0 +1,59 @@
1
+ import { create } from "zustand";
2
+
3
+ const useUiStore = create((set, get) => ({
4
+ open: false,
5
+ isModalOpen: false,
6
+ collapsed: false,
7
+ isMobileMenuOpened: false,
8
+ headerTitle: "Dashboard",
9
+ headerActions: null,
10
+ breadcrumbs: null,
11
+ layoutClass: "",
12
+
13
+ // ── Layout helpers ──────────────────────────────────────────────────────────
14
+ setLayoutClass: (layoutClass) => set({ layoutClass }),
15
+ setHeaderTitle: (title) => set({ headerTitle: title }),
16
+ setHeaderActions: (actions) => set({ headerActions: actions }),
17
+ setBreadcrumbs: (breadcrumbs) => set({ breadcrumbs }),
18
+
19
+ // ── Sidebar ─────────────────────────────────────────────────────────────────
20
+ toggleCollapsed: () => set((state) => ({ collapsed: !state.collapsed })),
21
+ setMobileMenuOpened: (open) => set({ isMobileMenuOpened: open }),
22
+ toggleMobileMenuOpened: () =>
23
+ set((state) => ({ isMobileMenuOpened: !state.isMobileMenuOpened })),
24
+ toggleSidebar: () => set((state) => ({ open: !state.open })),
25
+ openSidebar: () => set({ open: true }),
26
+ close: () => set({ open: false }),
27
+
28
+ // ── Modal ───────────────────────────────────────────────────────────────────
29
+ setModalOpen: (open) => set({ isModalOpen: open }),
30
+
31
+ // ── Theme ───────────────────────────────────────────────────────────────────
32
+ theme: localStorage.getItem("theme") || "light",
33
+ setTheme: (theme) => {
34
+ document.documentElement.setAttribute("data-theme", theme);
35
+ localStorage.setItem("theme", theme);
36
+ set({ theme });
37
+ },
38
+ toggleTheme: () => {
39
+ const nextTheme = get().theme === "light" ? "dark" : "light";
40
+ document.documentElement.setAttribute("data-theme", nextTheme);
41
+ localStorage.setItem("theme", nextTheme);
42
+ set({ theme: nextTheme });
43
+ },
44
+
45
+ // ── Electron window state ───────────────────────────────────────────────────
46
+ windowMaximized: false,
47
+ setWindowMaximized: (value) => set({ windowMaximized: value }),
48
+
49
+ /** Minimize the Electron window (no-op in browser) */
50
+ minimizeWindow: () => window.electronAPI?.minimize(),
51
+
52
+ /** Toggle maximize / restore the Electron window (no-op in browser) */
53
+ maximizeWindow: () => window.electronAPI?.maximize(),
54
+
55
+ /** Close the Electron window (no-op in browser) */
56
+ closeWindow: () => window.electronAPI?.close(),
57
+ }));
58
+
59
+ export { useUiStore };
@@ -0,0 +1,29 @@
1
+ import { defineConfig } from 'vite';
2
+ import react from '@vitejs/plugin-react';
3
+ import tailwindcss from '@tailwindcss/vite';
4
+ import electron from 'vite-plugin-electron';
5
+ import renderer from 'vite-plugin-electron-renderer';
6
+
7
+ // https://vitejs.dev/config/
8
+ export default defineConfig({
9
+ plugins: [
10
+ react(),
11
+ tailwindcss(),
12
+ electron([
13
+ {
14
+ // Main process entry
15
+ entry: 'electron/main.js',
16
+ },
17
+ {
18
+ // Preload script — reload renderer on change
19
+ entry: 'electron/preload.js',
20
+ onstart(options) {
21
+ options.reload();
22
+ },
23
+ },
24
+ ]),
25
+ renderer(),
26
+ ],
27
+ // Critical: ensures built assets resolve correctly via file:// in production
28
+ base: './',
29
+ });
@@ -0,0 +1,38 @@
1
+ # Logs
2
+ logs
3
+ *.log
4
+ npm-debug.log*
5
+ yarn-debug.log*
6
+ yarn-error.log*
7
+ pnpm-debug.log*
8
+ lerna-debug.log*
9
+
10
+ node_modules
11
+ dist
12
+ dist-ssr
13
+ dist-electron
14
+ *.local
15
+
16
+ # Electron build output
17
+ release/
18
+ out/
19
+
20
+ # Electron platform artifacts
21
+ *.dmg
22
+ *.exe
23
+ *.msi
24
+ *.AppImage
25
+ *.deb
26
+ *.snap
27
+ *.rpm
28
+
29
+ # Editor directories and files
30
+ .vscode/*
31
+ !.vscode/extensions.json
32
+ .idea
33
+ .DS_Store
34
+ *.suo
35
+ *.ntvs*
36
+ *.njsproj
37
+ *.sln
38
+ *.sw?
@@ -0,0 +1,213 @@
1
+ import {
2
+ app,
3
+ BrowserWindow,
4
+ Menu,
5
+ Tray,
6
+ shell,
7
+ ipcMain,
8
+ nativeImage,
9
+ MenuItemConstructorOptions,
10
+ } from 'electron';
11
+ import { fileURLToPath } from 'url';
12
+ import path from 'path';
13
+
14
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
15
+
16
+ // ─── Constants ────────────────────────────────────────────────────────────────
17
+ const isDev = process.env.NODE_ENV === 'development' || !app.isPackaged;
18
+ const VITE_DEV_SERVER_URL = process.env['VITE_DEV_SERVER_URL'];
19
+ const preloadPath = path.join(__dirname, 'preload.js');
20
+
21
+ let mainWindow: BrowserWindow | null = null;
22
+ let tray: Tray | null = null;
23
+
24
+ // ─── Window ───────────────────────────────────────────────────────────────────
25
+ function createWindow(): void {
26
+ mainWindow = new BrowserWindow({
27
+ width: 1280,
28
+ height: 800,
29
+ minWidth: 800,
30
+ minHeight: 600,
31
+ frame: false,
32
+ titleBarStyle: 'hidden',
33
+ trafficLightPosition: { x: 16, y: 16 },
34
+ backgroundColor: '#0f172a',
35
+ show: false,
36
+ icon: path.join(__dirname, '../public/icon.png'),
37
+ webPreferences: {
38
+ preload: preloadPath,
39
+ contextIsolation: true,
40
+ nodeIntegration: false,
41
+ sandbox: false,
42
+ },
43
+ });
44
+
45
+ mainWindow.once('ready-to-show', () => {
46
+ mainWindow!.show();
47
+ if (isDev) mainWindow!.webContents.openDevTools({ mode: 'detach' });
48
+ });
49
+
50
+ mainWindow.on('maximize', () => mainWindow?.webContents.send('window:maximized-change', true));
51
+ mainWindow.on('unmaximize', () => mainWindow?.webContents.send('window:maximized-change', false));
52
+
53
+ mainWindow.webContents.setWindowOpenHandler(({ url }) => {
54
+ shell.openExternal(url);
55
+ return { action: 'deny' };
56
+ });
57
+
58
+ if (isDev && VITE_DEV_SERVER_URL) {
59
+ mainWindow.loadURL(VITE_DEV_SERVER_URL);
60
+ } else {
61
+ mainWindow.loadFile(path.join(__dirname, '../dist/index.html'));
62
+ }
63
+ }
64
+
65
+ // ─── Native Application Menu ──────────────────────────────────────────────────
66
+ function buildMenu(): void {
67
+ const isMac = process.platform === 'darwin';
68
+
69
+ const template: MenuItemConstructorOptions[] = [
70
+ ...(isMac
71
+ ? [{
72
+ label: app.name,
73
+ submenu: [
74
+ { role: 'about' as const },
75
+ { type: 'separator' as const },
76
+ { label: 'Preferences…', accelerator: 'CmdOrCtrl+,', click: () => mainWindow?.webContents.send('menu:preferences') },
77
+ { type: 'separator' as const },
78
+ { role: 'services' as const },
79
+ { type: 'separator' as const },
80
+ { role: 'hide' as const },
81
+ { role: 'hideOthers' as const },
82
+ { role: 'unhide' as const },
83
+ { type: 'separator' as const },
84
+ { role: 'quit' as const },
85
+ ],
86
+ }]
87
+ : []),
88
+
89
+ {
90
+ label: 'File',
91
+ submenu: [
92
+ { label: 'New Window', accelerator: 'CmdOrCtrl+Shift+N', click: () => createWindow() },
93
+ { type: 'separator' },
94
+ isMac ? { role: 'close' as const } : { role: 'quit' as const },
95
+ ],
96
+ },
97
+
98
+ {
99
+ label: 'Edit',
100
+ submenu: [
101
+ { role: 'undo' as const },
102
+ { role: 'redo' as const },
103
+ { type: 'separator' as const },
104
+ { role: 'cut' as const },
105
+ { role: 'copy' as const },
106
+ { role: 'paste' as const },
107
+ ...(isMac
108
+ ? [{ role: 'pasteAndMatchStyle' as const }, { role: 'delete' as const }, { role: 'selectAll' as const }]
109
+ : [{ role: 'delete' as const }, { type: 'separator' as const }, { role: 'selectAll' as const }]),
110
+ ],
111
+ },
112
+
113
+ {
114
+ label: 'View',
115
+ submenu: [
116
+ { role: 'reload' as const },
117
+ { role: 'forceReload' as const },
118
+ { role: 'toggleDevTools' as const },
119
+ { type: 'separator' as const },
120
+ { role: 'resetZoom' as const },
121
+ { role: 'zoomIn' as const },
122
+ { role: 'zoomOut' as const },
123
+ { type: 'separator' as const },
124
+ { role: 'togglefullscreen' as const },
125
+ ],
126
+ },
127
+
128
+ {
129
+ label: 'Window',
130
+ submenu: [
131
+ { role: 'minimize' as const },
132
+ { role: 'zoom' as const },
133
+ ...(isMac
134
+ ? [{ type: 'separator' as const }, { role: 'front' as const }, { type: 'separator' as const }, { role: 'window' as const }]
135
+ : [{ role: 'close' as const }]),
136
+ ],
137
+ },
138
+
139
+ {
140
+ role: 'help' as const,
141
+ submenu: [
142
+ { label: 'Learn More', click: () => shell.openExternal('https://github.com/imksh/create') },
143
+ {
144
+ label: 'Toggle Developer Tools',
145
+ accelerator: isMac ? 'Alt+Cmd+I' : 'Alt+Shift+I',
146
+ click: () => mainWindow?.webContents.toggleDevTools(),
147
+ },
148
+ ],
149
+ },
150
+ ];
151
+
152
+ Menu.setApplicationMenu(Menu.buildFromTemplate(template));
153
+ }
154
+
155
+ // ─── System Tray ──────────────────────────────────────────────────────────────
156
+ function createTray(): void {
157
+ const iconPath = path.join(__dirname, '../public/icon.png');
158
+ const trayIcon = nativeImage.createFromPath(iconPath).resize({ width: 16, height: 16 });
159
+ tray = new Tray(trayIcon);
160
+
161
+ const contextMenu = Menu.buildFromTemplate([
162
+ { label: 'Show App', click: () => { mainWindow?.show(); mainWindow?.focus(); } },
163
+ { type: 'separator' },
164
+ { label: 'Quit', click: () => { (app as any).isQuiting = true; app.quit(); } },
165
+ ]);
166
+
167
+ tray.setToolTip(app.name);
168
+ tray.setContextMenu(contextMenu);
169
+ tray.on('double-click', () => { mainWindow?.show(); mainWindow?.focus(); });
170
+ }
171
+
172
+ // ─── IPC Handlers ─────────────────────────────────────────────────────────────
173
+ function registerIpcHandlers(): void {
174
+ ipcMain.on('window:minimize', () => mainWindow?.minimize());
175
+ ipcMain.on('window:maximize', () => {
176
+ mainWindow?.isMaximized() ? mainWindow.unmaximize() : mainWindow?.maximize();
177
+ });
178
+ ipcMain.on('window:close', () => mainWindow?.close());
179
+
180
+ ipcMain.handle('window:isMaximized', () => mainWindow?.isMaximized() ?? false);
181
+ ipcMain.handle('app:version', () => app.getVersion());
182
+ ipcMain.handle('app:platform', () => process.platform);
183
+
184
+ ipcMain.handle('shell:openExternal', (_event, url: string) => {
185
+ if (typeof url === 'string' && url.startsWith('http')) {
186
+ return shell.openExternal(url);
187
+ }
188
+ });
189
+ }
190
+
191
+ // ─── App Lifecycle ────────────────────────────────────────────────────────────
192
+ app.whenReady().then(() => {
193
+ if (process.platform === 'darwin') {
194
+ app.setAboutPanelOptions({
195
+ applicationName: app.name,
196
+ applicationVersion: app.getVersion(),
197
+ copyright: `© ${new Date().getFullYear()} imksh`,
198
+ });
199
+ }
200
+
201
+ registerIpcHandlers();
202
+ buildMenu();
203
+ createWindow();
204
+ createTray();
205
+
206
+ app.on('activate', () => {
207
+ if (BrowserWindow.getAllWindows().length === 0) createWindow();
208
+ });
209
+ });
210
+
211
+ app.on('window-all-closed', () => {
212
+ if (process.platform !== 'darwin') app.quit();
213
+ });
@@ -0,0 +1,52 @@
1
+ import { contextBridge, ipcRenderer } from 'electron';
2
+
3
+ /**
4
+ * ElectronAPI — typed interface exposed to the renderer via window.electronAPI.
5
+ * Declare this in vite-env.d.ts to get full type safety in the renderer.
6
+ */
7
+ export interface ElectronAPI {
8
+ // Window controls
9
+ minimize: () => void;
10
+ maximize: () => void;
11
+ close: () => void;
12
+
13
+ // Window state
14
+ isMaximized: () => Promise<boolean>;
15
+
16
+ // App info
17
+ getVersion: () => Promise<string>;
18
+ getPlatform: () => Promise<NodeJS.Platform>;
19
+
20
+ // Shell
21
+ openExternal: (url: string) => Promise<void>;
22
+
23
+ // Event subscriptions
24
+ onMaximizeChange: (callback: (isMaximized: boolean) => void) => void;
25
+ onMenuAction: (callback: (data: { label: string }) => void) => void;
26
+
27
+ // Cleanup
28
+ removeAllListeners: (channel: string) => void;
29
+ }
30
+
31
+ contextBridge.exposeInMainWorld('electronAPI', {
32
+ minimize: () => ipcRenderer.send('window:minimize'),
33
+ maximize: () => ipcRenderer.send('window:maximize'),
34
+ close: () => ipcRenderer.send('window:close'),
35
+
36
+ isMaximized: () => ipcRenderer.invoke('window:isMaximized'),
37
+
38
+ getVersion: () => ipcRenderer.invoke('app:version'),
39
+ getPlatform: () => ipcRenderer.invoke('app:platform'),
40
+
41
+ openExternal: (url: string) => ipcRenderer.invoke('shell:openExternal', url),
42
+
43
+ onMaximizeChange: (callback: (isMaximized: boolean) => void) => {
44
+ ipcRenderer.on('window:maximized-change', (_event, value: boolean) => callback(value));
45
+ },
46
+
47
+ onMenuAction: (callback: (data: { label: string }) => void) => {
48
+ ipcRenderer.on('menu:preferences', (_event, data) => callback({ label: 'preferences', ...data }));
49
+ },
50
+
51
+ removeAllListeners: (channel: string) => ipcRenderer.removeAllListeners(channel),
52
+ } satisfies ElectronAPI);
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "electron-app",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "description": "An Electron + Vite + React (TypeScript) application built with @imksh/create",
6
+ "author": "imksh",
7
+ "main": "dist-electron/main.js",
8
+ "scripts": {
9
+ "dev": "vite",
10
+ "build": "tsc && vite build && electron-builder",
11
+ "preview": "electron .",
12
+ "pack": "tsc && vite build && electron-builder --dir",
13
+ "dist": "tsc && vite build && electron-builder",
14
+ "dist:mac": "tsc && vite build && electron-builder --mac",
15
+ "dist:win": "tsc && vite build && electron-builder --win",
16
+ "dist:linux": "tsc && vite build && electron-builder --linux",
17
+ "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
18
+ },
19
+ "dependencies": {
20
+ "@tailwindcss/vite": "^4.3.2",
21
+ "axios": "^1.6.0",
22
+ "flyonui": "^2.4.1",
23
+ "framer-motion": "^11.0.0",
24
+ "lenis": "^1.1.3",
25
+ "lottie-react": "^2.4.0",
26
+ "lucide-react": "^0.292.0",
27
+ "react": "^18.2.0",
28
+ "react-dom": "^18.2.0",
29
+ "react-hot-toast": "^2.4.1",
30
+ "react-icons": "^4.11.0",
31
+ "react-router-dom": "^6.18.0",
32
+ "tailwindcss": "^4.3.2",
33
+ "zustand": "^4.5.2"
34
+ },
35
+ "devDependencies": {
36
+ "@types/react": "^18.2.37",
37
+ "@types/react-dom": "^18.2.15",
38
+ "@typescript-eslint/eslint-plugin": "^6.10.0",
39
+ "@typescript-eslint/parser": "^6.10.0",
40
+ "@vitejs/plugin-react": "^4.2.0",
41
+ "electron": "^31.0.0",
42
+ "electron-builder": "^24.13.0",
43
+ "eslint": "^8.53.0",
44
+ "eslint-plugin-react": "^7.33.2",
45
+ "eslint-plugin-react-hooks": "^4.6.0",
46
+ "eslint-plugin-react-refresh": "^0.4.4",
47
+ "typescript": "^5.2.2",
48
+ "vite": "^5.0.0",
49
+ "vite-plugin-electron": "^0.28.0",
50
+ "vite-plugin-electron-renderer": "^0.14.0"
51
+ }
52
+ }
@@ -0,0 +1,42 @@
1
+ import { useEffect } from "react";
2
+ import { Outlet, useLocation } from "react-router-dom";
3
+ import SidebarHeader from "../ui/SidebarHeader";
4
+ import Sidebar from "../ui/sidebar";
5
+ import TitleBar from "../ui/TitleBar";
6
+ import { useUiStore } from "../../store/useUiStore";
7
+ import appConfig from "../../config/appConfig";
8
+
9
+ const SidebarLayout: React.FC = () => {
10
+ const { headerTitle, headerActions, setHeaderTitle, setHeaderActions } =
11
+ useUiStore();
12
+ const { pathname } = useLocation();
13
+
14
+ useEffect(() => {
15
+ setHeaderTitle(appConfig.app.name);
16
+ setHeaderActions(null);
17
+ }, [pathname, setHeaderTitle, setHeaderActions]);
18
+
19
+ return (
20
+ <div className="flex flex-col h-screen overflow-hidden">
21
+ {/* Electron custom title bar — only renders inside Electron */}
22
+ <TitleBar />
23
+
24
+ <div className="flex flex-1 overflow-hidden">
25
+ <Sidebar />
26
+
27
+ <div className="flex flex-1 flex-col overflow-hidden">
28
+ <SidebarHeader title={headerTitle} actions={headerActions} />
29
+
30
+ <main
31
+ className="flex-1 h-full overflow-y-auto px-2 py-6 md:p-6 max-w-7xl"
32
+ data-lenis-prevent
33
+ >
34
+ <Outlet />
35
+ </main>
36
+ </div>
37
+ </div>
38
+ </div>
39
+ );
40
+ };
41
+
42
+ export default SidebarLayout;