@imksh/create 1.0.30 → 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 (102) 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/typescript/package-lock.json +2 -2
  69. package/templates/layers/node/typescript/package.json +1 -1
  70. package/templates/layers/prisma/typescript/prisma/schema.prisma +35 -10
  71. package/templates/layers/react/javascript/src/components/common/Breadcrumbs.jsx +62 -58
  72. package/templates/layers/react/javascript/src/components/common/Pagination.jsx +164 -76
  73. package/templates/layers/react/javascript/src/components/common/SidebarModal.jsx +179 -0
  74. package/templates/layers/react/javascript/src/components/common/StatsCard.jsx +167 -0
  75. package/templates/layers/react/javascript/src/components/common/Table.jsx +248 -132
  76. package/templates/layers/react/javascript/src/components/common/Tabs.jsx +164 -112
  77. package/templates/layers/react/javascript/src/components/common/ToggleSwitch.jsx +3 -3
  78. package/templates/layers/react/javascript/src/components/layout/SidebarLayout.jsx +4 -12
  79. package/templates/layers/react/javascript/src/components/modals/Modal.jsx +53 -18
  80. package/templates/layers/react/javascript/src/components/ui/Input.jsx +49 -0
  81. package/templates/layers/react/javascript/src/components/ui/Sidebar.jsx +215 -0
  82. package/templates/layers/react/javascript/src/components/ui/SidebarHeader.jsx +25 -30
  83. package/templates/layers/react/javascript/src/config/appConfig.js +3 -3
  84. package/templates/layers/react/javascript/src/hooks/useWindowSize.js +2 -2
  85. package/templates/layers/react/typescript/src/components/common/Breadcrumbs.tsx +32 -6
  86. package/templates/layers/react/typescript/src/components/common/Pagination.tsx +151 -57
  87. package/templates/layers/react/typescript/src/components/common/SidebarModal.tsx +199 -0
  88. package/templates/layers/react/typescript/src/components/common/StatsCard.tsx +231 -0
  89. package/templates/layers/react/typescript/src/components/common/Table.tsx +197 -34
  90. package/templates/layers/react/typescript/src/components/common/Tabs.tsx +56 -12
  91. package/templates/layers/react/typescript/src/components/common/ToggleSwitch.tsx +15 -3
  92. package/templates/layers/react/typescript/src/components/layout/SidebarLayout.tsx +3 -6
  93. package/templates/layers/react/typescript/src/components/modals/Modal.tsx +151 -0
  94. package/templates/layers/react/typescript/src/components/ui/Input.tsx +58 -0
  95. package/templates/layers/react/typescript/src/components/ui/Sidebar.tsx +116 -53
  96. package/templates/layers/react/typescript/src/components/ui/SidebarHeader.tsx +5 -4
  97. package/templates/layers/react/typescript/src/config/appConfig.ts +3 -3
  98. package/templates/layers/react/typescript/src/hooks/useWindowSize.ts +2 -3
  99. package/templates/layers/react-native/base/src/config/appConfig.ts +1 -1
  100. package/templates/presets/electron/preset.json +9 -0
  101. package/templates/presets/electron-ts/preset.json +9 -0
  102. package/templates/layers/react/javascript/src/components/ui/sidebar.jsx +0 -169
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@imksh/create",
3
- "version": "1.0.30",
3
+ "version": "1.0.31",
4
4
  "description": "A production-ready create for scaffolding modern applications.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,6 +8,7 @@ export async function askTemplate() {
8
8
  message: 'Which framework would you like to use?',
9
9
  choices: [
10
10
  { name: 'React', value: 'react' },
11
+ { name: 'Electron', value: 'electron' },
11
12
  { name: 'Node.js', value: 'node' },
12
13
  { name: 'MERN Stack', value: 'mern' },
13
14
  { name: 'React Native', value: 'react-native' },
@@ -19,7 +20,7 @@ export async function askTemplate() {
19
20
  },
20
21
  ]);
21
22
 
22
- if (framework === 'react' || framework === 'next' || framework === 'mern') {
23
+ if (framework === 'react' || framework === 'next' || framework === 'mern' || framework === 'electron') {
23
24
  const { language } = await inquirer.prompt([
24
25
  {
25
26
  type: 'list',
@@ -0,0 +1,262 @@
1
+ import { app, BrowserWindow, Menu, Tray, shell, ipcMain, nativeImage } from 'electron';
2
+ import { fileURLToPath } from 'url';
3
+ import path from 'path';
4
+ import { createRequire } from 'module';
5
+
6
+ const require = createRequire(import.meta.url);
7
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
+
9
+ // ─── Constants ────────────────────────────────────────────────────────────────
10
+ const isDev = process.env.NODE_ENV === 'development' || !app.isPackaged;
11
+ const VITE_DEV_SERVER_URL = process.env['VITE_DEV_SERVER_URL'];
12
+ const preloadPath = path.join(__dirname, 'preload.js');
13
+
14
+ /** @type {BrowserWindow | null} */
15
+ let mainWindow = null;
16
+ /** @type {Tray | null} */
17
+ let tray = null;
18
+
19
+ // ─── Window ───────────────────────────────────────────────────────────────────
20
+ function createWindow() {
21
+ mainWindow = new BrowserWindow({
22
+ width: 1280,
23
+ height: 800,
24
+ minWidth: 800,
25
+ minHeight: 600,
26
+ frame: false, // Custom title bar in renderer
27
+ titleBarStyle: 'hidden',
28
+ trafficLightPosition: { x: 16, y: 16 }, // macOS traffic lights position
29
+ backgroundColor: '#0f172a',
30
+ show: false, // Don't show until ready-to-show
31
+ icon: path.join(__dirname, '../public/icon.png'),
32
+ webPreferences: {
33
+ preload: preloadPath,
34
+ contextIsolation: true,
35
+ nodeIntegration: false,
36
+ sandbox: false,
37
+ },
38
+ });
39
+
40
+ // Show window once ready to avoid white flash
41
+ mainWindow.once('ready-to-show', () => {
42
+ mainWindow.show();
43
+ if (isDev) mainWindow.webContents.openDevTools({ mode: 'detach' });
44
+ });
45
+
46
+ // Notify renderer when window maximize state changes
47
+ mainWindow.on('maximize', () => mainWindow?.webContents.send('window:maximized-change', true));
48
+ mainWindow.on('unmaximize', () => mainWindow?.webContents.send('window:maximized-change', false));
49
+
50
+ // Open external links in the default browser instead of Electron
51
+ mainWindow.webContents.setWindowOpenHandler(({ url }) => {
52
+ shell.openExternal(url);
53
+ return { action: 'deny' };
54
+ });
55
+
56
+ // Load app
57
+ if (isDev && VITE_DEV_SERVER_URL) {
58
+ mainWindow.loadURL(VITE_DEV_SERVER_URL);
59
+ } else {
60
+ mainWindow.loadFile(path.join(__dirname, '../dist/index.html'));
61
+ }
62
+ }
63
+
64
+ // ─── Native Application Menu ──────────────────────────────────────────────────
65
+ function buildMenu() {
66
+ const isMac = process.platform === 'darwin';
67
+
68
+ const template = [
69
+ // App menu (macOS only)
70
+ ...(isMac
71
+ ? [{
72
+ label: app.name,
73
+ submenu: [
74
+ { role: 'about' },
75
+ { type: 'separator' },
76
+ { label: 'Preferences…', accelerator: 'CmdOrCtrl+,', click: () => mainWindow?.webContents.send('menu:preferences') },
77
+ { type: 'separator' },
78
+ { role: 'services' },
79
+ { type: 'separator' },
80
+ { role: 'hide' },
81
+ { role: 'hideOthers' },
82
+ { role: 'unhide' },
83
+ { type: 'separator' },
84
+ { role: 'quit' },
85
+ ],
86
+ }]
87
+ : []),
88
+
89
+ // File
90
+ {
91
+ label: 'File',
92
+ submenu: [
93
+ {
94
+ label: 'New Window',
95
+ accelerator: 'CmdOrCtrl+Shift+N',
96
+ click: () => createWindow(),
97
+ },
98
+ { type: 'separator' },
99
+ isMac ? { role: 'close' } : { role: 'quit' },
100
+ ],
101
+ },
102
+
103
+ // Edit
104
+ {
105
+ label: 'Edit',
106
+ submenu: [
107
+ { role: 'undo' },
108
+ { role: 'redo' },
109
+ { type: 'separator' },
110
+ { role: 'cut' },
111
+ { role: 'copy' },
112
+ { role: 'paste' },
113
+ ...(isMac
114
+ ? [{ role: 'pasteAndMatchStyle' }, { role: 'delete' }, { role: 'selectAll' }]
115
+ : [{ role: 'delete' }, { type: 'separator' }, { role: 'selectAll' }]),
116
+ ],
117
+ },
118
+
119
+ // View
120
+ {
121
+ label: 'View',
122
+ submenu: [
123
+ { role: 'reload' },
124
+ { role: 'forceReload' },
125
+ { role: 'toggleDevTools' },
126
+ { type: 'separator' },
127
+ { role: 'resetZoom' },
128
+ { role: 'zoomIn' },
129
+ { role: 'zoomOut' },
130
+ { type: 'separator' },
131
+ { role: 'togglefullscreen' },
132
+ ],
133
+ },
134
+
135
+ // Window
136
+ {
137
+ label: 'Window',
138
+ submenu: [
139
+ { role: 'minimize' },
140
+ { role: 'zoom' },
141
+ ...(isMac
142
+ ? [{ type: 'separator' }, { role: 'front' }, { type: 'separator' }, { role: 'window' }]
143
+ : [{ role: 'close' }]),
144
+ ],
145
+ },
146
+
147
+ // Help
148
+ {
149
+ role: 'help',
150
+ submenu: [
151
+ {
152
+ label: 'Learn More',
153
+ click: () => shell.openExternal('https://github.com/imksh/create'),
154
+ },
155
+ {
156
+ label: 'Toggle Developer Tools',
157
+ accelerator: isMac ? 'Alt+Cmd+I' : 'Alt+Shift+I',
158
+ click: () => mainWindow?.webContents.toggleDevTools(),
159
+ },
160
+ ],
161
+ },
162
+ ];
163
+
164
+ const menu = Menu.buildFromTemplate(template);
165
+ Menu.setApplicationMenu(menu);
166
+ }
167
+
168
+ // ─── System Tray ──────────────────────────────────────────────────────────────
169
+ function createTray() {
170
+ // Use a simple nativeImage as placeholder — replace public/tray-icon.png with your 16x16 icon
171
+ const iconPath = path.join(__dirname, '../public/icon.png');
172
+ const trayIcon = nativeImage.createFromPath(iconPath).resize({ width: 16, height: 16 });
173
+ tray = new Tray(trayIcon);
174
+
175
+ const contextMenu = Menu.buildFromTemplate([
176
+ {
177
+ label: 'Show App',
178
+ click: () => {
179
+ mainWindow?.show();
180
+ mainWindow?.focus();
181
+ },
182
+ },
183
+ { type: 'separator' },
184
+ {
185
+ label: 'Quit',
186
+ click: () => {
187
+ app.isQuiting = true;
188
+ app.quit();
189
+ },
190
+ },
191
+ ]);
192
+
193
+ tray.setToolTip(app.name);
194
+ tray.setContextMenu(contextMenu);
195
+
196
+ // Double-click tray icon to show window
197
+ tray.on('double-click', () => {
198
+ mainWindow?.show();
199
+ mainWindow?.focus();
200
+ });
201
+ }
202
+
203
+ // ─── IPC Handlers ─────────────────────────────────────────────────────────────
204
+ function registerIpcHandlers() {
205
+ // Window controls
206
+ ipcMain.on('window:minimize', () => mainWindow?.minimize());
207
+ ipcMain.on('window:maximize', () => {
208
+ if (mainWindow?.isMaximized()) {
209
+ mainWindow.unmaximize();
210
+ } else {
211
+ mainWindow?.maximize();
212
+ }
213
+ });
214
+ ipcMain.on('window:close', () => mainWindow?.close());
215
+
216
+ // Window state queries
217
+ ipcMain.handle('window:isMaximized', () => mainWindow?.isMaximized() ?? false);
218
+
219
+ // App info
220
+ ipcMain.handle('app:version', () => app.getVersion());
221
+ ipcMain.handle('app:platform', () => process.platform);
222
+
223
+ // Shell
224
+ ipcMain.handle('shell:openExternal', (_event, url) => {
225
+ if (typeof url === 'string' && url.startsWith('http')) {
226
+ return shell.openExternal(url);
227
+ }
228
+ });
229
+ }
230
+
231
+ // ─── App Lifecycle ────────────────────────────────────────────────────────────
232
+ app.whenReady().then(() => {
233
+ // macOS About panel
234
+ if (process.platform === 'darwin') {
235
+ app.setAboutPanelOptions({
236
+ applicationName: app.name,
237
+ applicationVersion: app.getVersion(),
238
+ copyright: `© ${new Date().getFullYear()} imksh`,
239
+ });
240
+ }
241
+
242
+ registerIpcHandlers();
243
+ buildMenu();
244
+ createWindow();
245
+ createTray();
246
+
247
+ app.on('activate', () => {
248
+ // On macOS re-create window when dock icon is clicked and no windows are open
249
+ if (BrowserWindow.getAllWindows().length === 0) createWindow();
250
+ });
251
+ });
252
+
253
+ app.on('window-all-closed', () => {
254
+ if (process.platform !== 'darwin') {
255
+ app.quit();
256
+ }
257
+ });
258
+
259
+ // Minimize to tray on close (Windows/Linux)
260
+ app.on('before-quit', () => {
261
+ app.isQuiting = true;
262
+ });
@@ -0,0 +1,37 @@
1
+ import { contextBridge, ipcRenderer } from 'electron';
2
+
3
+ /**
4
+ * Expose a safe, typed API to the renderer via window.electronAPI.
5
+ * Never expose ipcRenderer directly — that would allow any renderer code
6
+ * to send arbitrary IPC messages.
7
+ */
8
+ contextBridge.exposeInMainWorld('electronAPI', {
9
+ // ── Window controls ────────────────────────────────────────────────────────
10
+ minimize: () => ipcRenderer.send('window:minimize'),
11
+ maximize: () => ipcRenderer.send('window:maximize'),
12
+ close: () => ipcRenderer.send('window:close'),
13
+
14
+ // ── Window state ───────────────────────────────────────────────────────────
15
+ isMaximized: () => ipcRenderer.invoke('window:isMaximized'),
16
+
17
+ // ── App info ───────────────────────────────────────────────────────────────
18
+ getVersion: () => ipcRenderer.invoke('app:version'),
19
+ getPlatform: () => ipcRenderer.invoke('app:platform'),
20
+
21
+ // ── Shell ──────────────────────────────────────────────────────────────────
22
+ openExternal: (url) => ipcRenderer.invoke('shell:openExternal', url),
23
+
24
+ // ── Event subscriptions ────────────────────────────────────────────────────
25
+ /** @param {(isMaximized: boolean) => void} callback */
26
+ onMaximizeChange: (callback) => {
27
+ ipcRenderer.on('window:maximized-change', (_event, value) => callback(value));
28
+ },
29
+
30
+ /** @param {(data: { label: string }) => void} callback */
31
+ onMenuAction: (callback) => {
32
+ ipcRenderer.on('menu:preferences', (_event, data) => callback({ label: 'preferences', ...data }));
33
+ },
34
+
35
+ // ── Cleanup ────────────────────────────────────────────────────────────────
36
+ removeAllListeners: (channel) => ipcRenderer.removeAllListeners(channel),
37
+ });
@@ -0,0 +1,68 @@
1
+ {
2
+ // Application ID — change this to your own reverse-domain identifier
3
+ appId: "com.imksh.electron-app",
4
+ productName: "Electron App",
5
+
6
+ // Directories
7
+ directories: {
8
+ output: "release",
9
+ buildResources: "public"
10
+ },
11
+
12
+ // Files to include in the app bundle
13
+ files: [
14
+ "dist/**/*",
15
+ "dist-electron/**/*",
16
+ "public/**/*"
17
+ ],
18
+
19
+ // Pack into an asar archive for faster startup
20
+ asar: true,
21
+
22
+ // macOS
23
+ mac: {
24
+ target: [{ target: "dmg", arch: ["x64", "arm64"] }],
25
+ icon: "public/icon.icns",
26
+ category: "public.app-category.productivity",
27
+ hardenedRuntime: true,
28
+ gatekeeperAssess: false,
29
+ entitlements: "build/entitlements.mac.plist",
30
+ entitlementsInherit: "build/entitlements.mac.plist"
31
+ },
32
+
33
+ dmg: {
34
+ contents: [
35
+ { x: 130, y: 220 },
36
+ { x: 410, y: 220, type: "link", path: "/Applications" }
37
+ ]
38
+ },
39
+
40
+ // Windows
41
+ win: {
42
+ target: [{ target: "nsis", arch: ["x64"] }],
43
+ icon: "public/icon.ico"
44
+ },
45
+
46
+ nsis: {
47
+ oneClick: false,
48
+ perMachine: false,
49
+ allowToChangeInstallationDirectory: true,
50
+ deleteAppDataOnUninstall: false,
51
+ createDesktopShortcut: true,
52
+ createStartMenuShortcut: true
53
+ },
54
+
55
+ // Linux
56
+ linux: {
57
+ target: ["AppImage", "deb", "snap"],
58
+ icon: "public/icon.png",
59
+ category: "Utility"
60
+ },
61
+
62
+ // Publish config (for auto-updates — fill in your GitHub repo)
63
+ // publish: {
64
+ // provider: "github",
65
+ // owner: "your-github-username",
66
+ // repo: "your-repo-name"
67
+ // }
68
+ }
@@ -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,49 @@
1
+ {
2
+ "name": "electron-app",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "description": "An Electron + Vite + React application built with @imksh/create",
6
+ "author": "imksh",
7
+ "main": "dist-electron/main.js",
8
+ "scripts": {
9
+ "dev": "vite",
10
+ "build": "vite build && electron-builder",
11
+ "preview": "electron .",
12
+ "pack": "vite build && electron-builder --dir",
13
+ "dist": "vite build && electron-builder",
14
+ "dist:mac": "vite build && electron-builder --mac",
15
+ "dist:win": "vite build && electron-builder --win",
16
+ "dist:linux": "vite build && electron-builder --linux",
17
+ "lint": "eslint . --ext js,jsx --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
+ "@vitejs/plugin-react": "^4.2.0",
39
+ "electron": "^31.0.0",
40
+ "electron-builder": "^24.13.0",
41
+ "eslint": "^8.53.0",
42
+ "eslint-plugin-react": "^7.33.2",
43
+ "eslint-plugin-react-hooks": "^4.6.0",
44
+ "eslint-plugin-react-refresh": "^0.4.4",
45
+ "vite": "^5.0.0",
46
+ "vite-plugin-electron": "^0.28.0",
47
+ "vite-plugin-electron-renderer": "^0.14.0"
48
+ }
49
+ }
@@ -0,0 +1,43 @@
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 = () => {
10
+ const { headerTitle, headerActions, setHeaderTitle, setHeaderActions } =
11
+ useUiStore();
12
+ const { pathname } = useLocation();
13
+
14
+ // Reset header title and actions whenever the route shifts
15
+ useEffect(() => {
16
+ setHeaderTitle(appConfig.app.name);
17
+ setHeaderActions(null);
18
+ }, [pathname, setHeaderTitle, setHeaderActions]);
19
+
20
+ return (
21
+ <div className="flex flex-col h-screen overflow-hidden">
22
+ {/* Electron custom title bar — only renders inside Electron */}
23
+ <TitleBar />
24
+
25
+ <div className="flex flex-1 overflow-hidden">
26
+ <Sidebar />
27
+
28
+ <div className="flex flex-1 flex-col overflow-hidden">
29
+ <SidebarHeader title={headerTitle} actions={headerActions} />
30
+
31
+ <main
32
+ className="flex-1 h-full overflow-y-auto px-2 py-6 md:p-6 max-w-7xl"
33
+ data-lenis-prevent
34
+ >
35
+ <Outlet />
36
+ </main>
37
+ </div>
38
+ </div>
39
+ </div>
40
+ );
41
+ };
42
+
43
+ export default SidebarLayout;
@@ -0,0 +1,130 @@
1
+ import React, { useEffect, useState } from "react";
2
+ import { Minus, Square, X, Maximize2, Minimize2 } from "lucide-react";
3
+ import { useUiStore } from "../../store/useUiStore";
4
+ import appConfig from "../../config/appConfig";
5
+
6
+ /**
7
+ * TitleBar — custom frameless window title bar for Electron.
8
+ *
9
+ * Renders a draggable title bar with:
10
+ * - App icon + name on the left
11
+ * - Window controls (minimize / maximize / close) positioned per platform:
12
+ * · macOS: right side (native traffic lights are hidden via frame: false)
13
+ * · Windows/Linux: right side (Windows style)
14
+ *
15
+ * Only renders when running inside Electron (window.electronAPI is present).
16
+ */
17
+ const TitleBar = () => {
18
+ const { windowMaximized, setWindowMaximized, minimizeWindow, maximizeWindow, closeWindow } =
19
+ useUiStore();
20
+ const [platform, setPlatform] = useState("win32");
21
+
22
+ const isElectron = Boolean(window.electronAPI);
23
+
24
+ // Don't render in browser
25
+ if (!isElectron) return null;
26
+
27
+ useEffect(() => {
28
+ // Get platform for conditional layout
29
+ window.electronAPI.getPlatform().then(setPlatform);
30
+
31
+ // Sync initial maximize state
32
+ window.electronAPI.isMaximized().then(setWindowMaximized);
33
+
34
+ // Listen for window state changes from main process
35
+ window.electronAPI.onMaximizeChange((isMax) => {
36
+ setWindowMaximized(isMax);
37
+ });
38
+
39
+ return () => {
40
+ window.electronAPI.removeAllListeners("window:maximized-change");
41
+ };
42
+ }, []);
43
+
44
+ const isMac = platform === "darwin";
45
+
46
+ const windowControls = (
47
+ <div className={`flex items-center gap-1.5 ${isMac ? "mr-3" : "ml-auto"}`}>
48
+ {/* Minimize */}
49
+ <button
50
+ id="titlebar-minimize"
51
+ onClick={minimizeWindow}
52
+ className="w-7 h-7 flex items-center justify-center rounded-full
53
+ text-base-content/50 hover:bg-warning/20 hover:text-warning
54
+ transition-all duration-150 group"
55
+ title="Minimize"
56
+ >
57
+ <Minus size={12} className="group-hover:scale-110 transition-transform" />
58
+ </button>
59
+
60
+ {/* Maximize / Restore */}
61
+ <button
62
+ id="titlebar-maximize"
63
+ onClick={maximizeWindow}
64
+ className="w-7 h-7 flex items-center justify-center rounded-full
65
+ text-base-content/50 hover:bg-success/20 hover:text-success
66
+ transition-all duration-150 group"
67
+ title={windowMaximized ? "Restore" : "Maximize"}
68
+ >
69
+ {windowMaximized ? (
70
+ <Minimize2 size={11} className="group-hover:scale-110 transition-transform" />
71
+ ) : (
72
+ <Maximize2 size={11} className="group-hover:scale-110 transition-transform" />
73
+ )}
74
+ </button>
75
+
76
+ {/* Close */}
77
+ <button
78
+ id="titlebar-close"
79
+ onClick={closeWindow}
80
+ className="w-7 h-7 flex items-center justify-center rounded-full
81
+ text-base-content/50 hover:bg-error/20 hover:text-error
82
+ transition-all duration-150 group"
83
+ title="Close"
84
+ >
85
+ <X size={12} className="group-hover:scale-110 transition-transform" />
86
+ </button>
87
+ </div>
88
+ );
89
+
90
+ return (
91
+ <div
92
+ id="electron-titlebar"
93
+ className="h-10 shrink-0 flex items-center px-3
94
+ bg-base-200 border-b border-base-300
95
+ select-none"
96
+ // The entire bar is draggable except the buttons (buttons set no-drag via CSS)
97
+ style={{ WebkitAppRegion: "drag" }}
98
+ >
99
+ {/* macOS: controls on left */}
100
+ {isMac && (
101
+ <div style={{ WebkitAppRegion: "no-drag" }}>
102
+ {windowControls}
103
+ </div>
104
+ )}
105
+
106
+ {/* App name — centered */}
107
+ <div className="flex items-center gap-2 mx-auto">
108
+ {appConfig.app.logo ? (
109
+ <img src={appConfig.app.logo} alt="" className="w-4 h-4 rounded" />
110
+ ) : (
111
+ <div className="w-4 h-4 rounded bg-primary flex items-center justify-center text-primary-content text-[8px] font-bold">
112
+ {appConfig.app.name.charAt(0)}
113
+ </div>
114
+ )}
115
+ <span className="text-xs font-semibold text-base-content/70">
116
+ {appConfig.app.name}
117
+ </span>
118
+ </div>
119
+
120
+ {/* Windows/Linux: controls on right */}
121
+ {!isMac && (
122
+ <div style={{ WebkitAppRegion: "no-drag" }}>
123
+ {windowControls}
124
+ </div>
125
+ )}
126
+ </div>
127
+ );
128
+ };
129
+
130
+ export default TitleBar;