@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.
- package/package.json +1 -1
- package/src/utils/askTemplate.js +2 -1
- package/templates/layers/electron/base/electron/main.js +262 -0
- package/templates/layers/electron/base/electron/preload.js +37 -0
- package/templates/layers/electron/base/electron-builder.json5 +68 -0
- package/templates/layers/electron/javascript/_gitignore +38 -0
- package/templates/layers/electron/javascript/package.json +49 -0
- package/templates/layers/electron/javascript/src/components/layout/SidebarLayout.jsx +43 -0
- package/templates/layers/electron/javascript/src/components/ui/TitleBar.jsx +130 -0
- package/templates/layers/electron/javascript/src/config/appConfig.js +51 -0
- package/templates/layers/electron/javascript/src/hooks/useElectron.js +68 -0
- package/templates/layers/electron/javascript/src/main.jsx +17 -0
- package/templates/layers/electron/javascript/src/store/useUiStore.js +59 -0
- package/templates/layers/electron/javascript/vite.config.js +29 -0
- package/templates/layers/electron/typescript/_gitignore +38 -0
- package/templates/layers/electron/typescript/electron/main.ts +213 -0
- package/templates/layers/electron/typescript/electron/preload.ts +52 -0
- package/templates/layers/electron/typescript/package.json +52 -0
- package/templates/layers/electron/typescript/src/components/layout/SidebarLayout.tsx +42 -0
- package/templates/layers/electron/typescript/src/components/ui/TitleBar.tsx +120 -0
- package/templates/layers/electron/typescript/src/config/appConfig.ts +52 -0
- package/templates/layers/electron/typescript/src/hooks/useElectron.ts +68 -0
- package/templates/layers/electron/typescript/src/main.tsx +17 -0
- package/templates/layers/electron/typescript/src/store/useUiStore.ts +97 -0
- package/templates/layers/electron/typescript/src/vite-env.d.ts +13 -0
- package/templates/layers/electron/typescript/tsconfig.json +25 -0
- package/templates/layers/electron/typescript/tsconfig.node.json +11 -0
- package/templates/layers/electron/typescript/vite.config.ts +29 -0
- package/templates/layers/next/javascript/src/components/common/Breadcrumbs.jsx +44 -6
- package/templates/layers/next/javascript/src/components/common/Loading.jsx +1 -0
- package/templates/layers/next/javascript/src/components/common/LottieIcon.jsx +1 -0
- package/templates/layers/next/javascript/src/components/common/Pagination.jsx +170 -0
- package/templates/layers/next/javascript/src/components/common/SidebarModal.jsx +180 -0
- package/templates/layers/next/javascript/src/components/common/StatsCard.jsx +168 -0
- package/templates/layers/next/javascript/src/components/common/Table.jsx +191 -41
- package/templates/layers/next/javascript/src/components/common/Tabs.jsx +187 -0
- package/templates/layers/next/javascript/src/components/common/ToggleSwitch.jsx +4 -3
- package/templates/layers/next/javascript/src/components/layout/ProtectedRoute.jsx +2 -1
- package/templates/layers/next/javascript/src/components/layout/PublicLayout.jsx +3 -6
- package/templates/layers/next/javascript/src/components/layout/SidebarLayout.jsx +4 -17
- package/templates/layers/next/javascript/src/components/modals/ConfirmModal.jsx +4 -12
- package/templates/layers/next/javascript/src/components/modals/Modal.jsx +50 -19
- package/templates/layers/next/javascript/src/components/ui/Input.jsx +50 -0
- package/templates/layers/next/{typescript/src/components/ui/sidebar.tsx → javascript/src/components/ui/Sidebar.jsx} +65 -53
- package/templates/layers/next/javascript/src/components/ui/SidebarHeader.jsx +11 -6
- package/templates/layers/next/javascript/src/config/appConfig.js +4 -4
- package/templates/layers/next/javascript/src/hooks/useWindowSize.js +2 -3
- package/templates/layers/next/javascript/src/store/useUiStore.js +9 -4
- package/templates/layers/next/typescript/src/components/common/Breadcrumbs.tsx +57 -8
- package/templates/layers/next/typescript/src/components/common/Loading.tsx +1 -0
- package/templates/layers/next/typescript/src/components/common/Pagination.tsx +186 -0
- package/templates/layers/next/typescript/src/components/common/SidebarModal.tsx +200 -0
- package/templates/layers/next/typescript/src/components/common/StatsCard.tsx +232 -0
- package/templates/layers/next/typescript/src/components/common/Table.tsx +201 -35
- package/templates/layers/next/typescript/src/components/common/Tabs.tsx +190 -0
- package/templates/layers/next/typescript/src/components/common/ToggleSwitch.tsx +12 -8
- package/templates/layers/next/typescript/src/components/layout/PublicLayout.tsx +8 -6
- package/templates/layers/next/typescript/src/components/layout/SidebarLayout.tsx +8 -17
- package/templates/layers/next/typescript/src/components/modals/ConfirmModal.tsx +1 -0
- package/templates/layers/next/typescript/src/components/modals/Modal.tsx +67 -21
- package/templates/layers/next/typescript/src/components/ui/Button.tsx +1 -0
- package/templates/layers/next/typescript/src/components/ui/Input.tsx +40 -60
- package/templates/layers/next/{javascript/src/components/ui/sidebar.jsx → typescript/src/components/ui/Sidebar.tsx} +77 -44
- package/templates/layers/next/typescript/src/components/ui/SidebarHeader.tsx +16 -10
- package/templates/layers/next/typescript/src/config/appConfig.ts +4 -4
- package/templates/layers/next/typescript/src/hooks/useWindowSize.ts +2 -3
- package/templates/layers/next/typescript/src/store/useUiStore.ts +14 -4
- package/templates/layers/node/typescript/package-lock.json +2 -2
- package/templates/layers/node/typescript/package.json +1 -1
- package/templates/layers/prisma/typescript/prisma/schema.prisma +35 -10
- package/templates/layers/react/javascript/src/components/common/Breadcrumbs.jsx +62 -58
- package/templates/layers/react/javascript/src/components/common/Pagination.jsx +164 -76
- package/templates/layers/react/javascript/src/components/common/SidebarModal.jsx +179 -0
- package/templates/layers/react/javascript/src/components/common/StatsCard.jsx +167 -0
- package/templates/layers/react/javascript/src/components/common/Table.jsx +248 -132
- package/templates/layers/react/javascript/src/components/common/Tabs.jsx +164 -112
- package/templates/layers/react/javascript/src/components/common/ToggleSwitch.jsx +3 -3
- package/templates/layers/react/javascript/src/components/layout/SidebarLayout.jsx +4 -12
- package/templates/layers/react/javascript/src/components/modals/Modal.jsx +53 -18
- package/templates/layers/react/javascript/src/components/ui/Input.jsx +49 -0
- package/templates/layers/react/javascript/src/components/ui/Sidebar.jsx +215 -0
- package/templates/layers/react/javascript/src/components/ui/SidebarHeader.jsx +25 -30
- package/templates/layers/react/javascript/src/config/appConfig.js +3 -3
- package/templates/layers/react/javascript/src/hooks/useWindowSize.js +2 -2
- package/templates/layers/react/typescript/src/components/common/Breadcrumbs.tsx +32 -6
- package/templates/layers/react/typescript/src/components/common/Pagination.tsx +151 -57
- package/templates/layers/react/typescript/src/components/common/SidebarModal.tsx +199 -0
- package/templates/layers/react/typescript/src/components/common/StatsCard.tsx +231 -0
- package/templates/layers/react/typescript/src/components/common/Table.tsx +197 -34
- package/templates/layers/react/typescript/src/components/common/Tabs.tsx +56 -12
- package/templates/layers/react/typescript/src/components/common/ToggleSwitch.tsx +15 -3
- package/templates/layers/react/typescript/src/components/layout/SidebarLayout.tsx +3 -6
- package/templates/layers/react/typescript/src/components/modals/Modal.tsx +151 -0
- package/templates/layers/react/typescript/src/components/ui/Input.tsx +58 -0
- package/templates/layers/react/typescript/src/components/ui/Sidebar.tsx +116 -53
- package/templates/layers/react/typescript/src/components/ui/SidebarHeader.tsx +5 -4
- package/templates/layers/react/typescript/src/config/appConfig.ts +3 -3
- package/templates/layers/react/typescript/src/hooks/useWindowSize.ts +2 -3
- package/templates/layers/react-native/base/src/config/appConfig.ts +1 -1
- package/templates/presets/electron/preset.json +9 -0
- package/templates/presets/electron-ts/preset.json +9 -0
- package/templates/layers/react/javascript/src/components/ui/sidebar.jsx +0 -169
|
@@ -0,0 +1,120 @@
|
|
|
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
|
+
type Platform = "darwin" | "win32" | "linux" | "web";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* TitleBar — custom frameless window title bar for Electron.
|
|
10
|
+
*
|
|
11
|
+
* Renders a draggable title bar with:
|
|
12
|
+
* - App icon + name centered
|
|
13
|
+
* - Window controls (minimize / maximize / close) per platform:
|
|
14
|
+
* · macOS: right side (native traffic lights hidden via frame: false)
|
|
15
|
+
* · Windows/Linux: right side (Windows style)
|
|
16
|
+
*
|
|
17
|
+
* Only renders when running inside Electron (window.electronAPI present).
|
|
18
|
+
*/
|
|
19
|
+
const TitleBar: React.FC = () => {
|
|
20
|
+
const { windowMaximized, setWindowMaximized, minimizeWindow, maximizeWindow, closeWindow } =
|
|
21
|
+
useUiStore();
|
|
22
|
+
const [platform, setPlatform] = useState<Platform>("win32");
|
|
23
|
+
|
|
24
|
+
const isElectron = Boolean(window.electronAPI);
|
|
25
|
+
if (!isElectron) return null;
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
window.electronAPI.getPlatform().then((p) => setPlatform(p as Platform));
|
|
29
|
+
window.electronAPI.isMaximized().then(setWindowMaximized);
|
|
30
|
+
window.electronAPI.onMaximizeChange((isMax) => setWindowMaximized(isMax));
|
|
31
|
+
|
|
32
|
+
return () => {
|
|
33
|
+
window.electronAPI.removeAllListeners("window:maximized-change");
|
|
34
|
+
};
|
|
35
|
+
}, []);
|
|
36
|
+
|
|
37
|
+
const isMac = platform === "darwin";
|
|
38
|
+
|
|
39
|
+
const windowControls = (
|
|
40
|
+
<div className={`flex items-center gap-1.5 ${isMac ? "mr-3" : "ml-auto"}`}>
|
|
41
|
+
{/* Minimize */}
|
|
42
|
+
<button
|
|
43
|
+
id="titlebar-minimize"
|
|
44
|
+
onClick={minimizeWindow}
|
|
45
|
+
className="w-7 h-7 flex items-center justify-center rounded-full
|
|
46
|
+
text-base-content/50 hover:bg-warning/20 hover:text-warning
|
|
47
|
+
transition-all duration-150 group"
|
|
48
|
+
title="Minimize"
|
|
49
|
+
>
|
|
50
|
+
<Minus size={12} className="group-hover:scale-110 transition-transform" />
|
|
51
|
+
</button>
|
|
52
|
+
|
|
53
|
+
{/* Maximize / Restore */}
|
|
54
|
+
<button
|
|
55
|
+
id="titlebar-maximize"
|
|
56
|
+
onClick={maximizeWindow}
|
|
57
|
+
className="w-7 h-7 flex items-center justify-center rounded-full
|
|
58
|
+
text-base-content/50 hover:bg-success/20 hover:text-success
|
|
59
|
+
transition-all duration-150 group"
|
|
60
|
+
title={windowMaximized ? "Restore" : "Maximize"}
|
|
61
|
+
>
|
|
62
|
+
{windowMaximized ? (
|
|
63
|
+
<Minimize2 size={11} className="group-hover:scale-110 transition-transform" />
|
|
64
|
+
) : (
|
|
65
|
+
<Maximize2 size={11} className="group-hover:scale-110 transition-transform" />
|
|
66
|
+
)}
|
|
67
|
+
</button>
|
|
68
|
+
|
|
69
|
+
{/* Close */}
|
|
70
|
+
<button
|
|
71
|
+
id="titlebar-close"
|
|
72
|
+
onClick={closeWindow}
|
|
73
|
+
className="w-7 h-7 flex items-center justify-center rounded-full
|
|
74
|
+
text-base-content/50 hover:bg-error/20 hover:text-error
|
|
75
|
+
transition-all duration-150 group"
|
|
76
|
+
title="Close"
|
|
77
|
+
>
|
|
78
|
+
<X size={12} className="group-hover:scale-110 transition-transform" />
|
|
79
|
+
</button>
|
|
80
|
+
</div>
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<div
|
|
85
|
+
id="electron-titlebar"
|
|
86
|
+
className="h-10 shrink-0 flex items-center px-3
|
|
87
|
+
bg-base-200 border-b border-base-300
|
|
88
|
+
select-none"
|
|
89
|
+
style={{ WebkitAppRegion: "drag" } as React.CSSProperties}
|
|
90
|
+
>
|
|
91
|
+
{isMac && (
|
|
92
|
+
<div style={{ WebkitAppRegion: "no-drag" } as React.CSSProperties}>
|
|
93
|
+
{windowControls}
|
|
94
|
+
</div>
|
|
95
|
+
)}
|
|
96
|
+
|
|
97
|
+
{/* App name — centered */}
|
|
98
|
+
<div className="flex items-center gap-2 mx-auto">
|
|
99
|
+
{appConfig.app.logo ? (
|
|
100
|
+
<img src={appConfig.app.logo} alt="" className="w-4 h-4 rounded" />
|
|
101
|
+
) : (
|
|
102
|
+
<div className="w-4 h-4 rounded bg-primary flex items-center justify-center text-primary-content text-[8px] font-bold">
|
|
103
|
+
{appConfig.app.name.charAt(0)}
|
|
104
|
+
</div>
|
|
105
|
+
)}
|
|
106
|
+
<span className="text-xs font-semibold text-base-content/70">
|
|
107
|
+
{appConfig.app.name}
|
|
108
|
+
</span>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
{!isMac && (
|
|
112
|
+
<div style={{ WebkitAppRegion: "no-drag" } as React.CSSProperties}>
|
|
113
|
+
{windowControls}
|
|
114
|
+
</div>
|
|
115
|
+
)}
|
|
116
|
+
</div>
|
|
117
|
+
);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
export default TitleBar;
|
|
@@ -0,0 +1,52 @@
|
|
|
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 as string,
|
|
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,
|
|
48
|
+
},
|
|
49
|
+
} as const;
|
|
50
|
+
|
|
51
|
+
export type AppConfig = typeof appConfig;
|
|
52
|
+
export default appConfig;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
|
|
3
|
+
type Platform = "darwin" | "win32" | "linux" | "web";
|
|
4
|
+
|
|
5
|
+
interface UseElectronReturn {
|
|
6
|
+
isElectron: boolean;
|
|
7
|
+
minimize: () => void;
|
|
8
|
+
maximize: () => void;
|
|
9
|
+
close: () => void;
|
|
10
|
+
isMaximized: () => Promise<boolean>;
|
|
11
|
+
getVersion: () => Promise<string>;
|
|
12
|
+
getPlatform: () => Promise<Platform>;
|
|
13
|
+
openExternal: (url: string) => Promise<void>;
|
|
14
|
+
onMaximizeChange: (cb: (isMax: boolean) => void) => void;
|
|
15
|
+
onMenuAction: (cb: (data: { label: string }) => void) => void;
|
|
16
|
+
removeAllListeners: (channel: string) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* useElectron — safely access window.electronAPI in the renderer.
|
|
21
|
+
*
|
|
22
|
+
* Returns typed wrappers for all IPC calls. Every method is a no-op
|
|
23
|
+
* (or returns a resolved promise) when running outside of Electron.
|
|
24
|
+
*/
|
|
25
|
+
export function useElectron(): UseElectronReturn {
|
|
26
|
+
const isElectron = typeof window !== "undefined" && Boolean(window.electronAPI);
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
isElectron,
|
|
30
|
+
minimize: () => window.electronAPI?.minimize(),
|
|
31
|
+
maximize: () => window.electronAPI?.maximize(),
|
|
32
|
+
close: () => window.electronAPI?.close(),
|
|
33
|
+
isMaximized: () => window.electronAPI?.isMaximized() ?? Promise.resolve(false),
|
|
34
|
+
getVersion: () => window.electronAPI?.getVersion() ?? Promise.resolve("web"),
|
|
35
|
+
getPlatform: () => (window.electronAPI?.getPlatform() ?? Promise.resolve("web")) as Promise<Platform>,
|
|
36
|
+
openExternal: (url) => window.electronAPI?.openExternal(url) ?? Promise.resolve(),
|
|
37
|
+
onMaximizeChange: (cb) => window.electronAPI?.onMaximizeChange(cb),
|
|
38
|
+
onMenuAction: (cb) => window.electronAPI?.onMenuAction(cb),
|
|
39
|
+
removeAllListeners: (channel) => window.electronAPI?.removeAllListeners(channel),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
type ElectronEvent = "maximize" | "menu";
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* useElectronEvent — subscribe to an Electron IPC event in a React component.
|
|
47
|
+
* Automatically cleans up the listener on unmount.
|
|
48
|
+
*/
|
|
49
|
+
export function useElectronEvent(
|
|
50
|
+
event: ElectronEvent,
|
|
51
|
+
callback: (value: boolean | { label: string }) => void,
|
|
52
|
+
): void {
|
|
53
|
+
const electron = useElectron();
|
|
54
|
+
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (!electron.isElectron) return;
|
|
57
|
+
|
|
58
|
+
if (event === "maximize") {
|
|
59
|
+
electron.onMaximizeChange(callback as (v: boolean) => void);
|
|
60
|
+
return () => electron.removeAllListeners("window:maximized-change");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (event === "menu") {
|
|
64
|
+
electron.onMenuAction(callback as (d: { label: string }) => void);
|
|
65
|
+
return () => electron.removeAllListeners("menu:preferences");
|
|
66
|
+
}
|
|
67
|
+
}, [event, 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.js";
|
|
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,97 @@
|
|
|
1
|
+
import { create } from "zustand";
|
|
2
|
+
|
|
3
|
+
interface UiState {
|
|
4
|
+
// Sidebar
|
|
5
|
+
open: boolean;
|
|
6
|
+
collapsed: boolean;
|
|
7
|
+
isMobileMenuOpened: boolean;
|
|
8
|
+
|
|
9
|
+
// Modal
|
|
10
|
+
isModalOpen: boolean;
|
|
11
|
+
|
|
12
|
+
// Header
|
|
13
|
+
headerTitle: string;
|
|
14
|
+
headerActions: React.ReactNode | null;
|
|
15
|
+
breadcrumbs: React.ReactNode | null;
|
|
16
|
+
layoutClass: string;
|
|
17
|
+
|
|
18
|
+
// Theme
|
|
19
|
+
theme: string;
|
|
20
|
+
|
|
21
|
+
// Electron window
|
|
22
|
+
windowMaximized: boolean;
|
|
23
|
+
|
|
24
|
+
// Actions
|
|
25
|
+
setLayoutClass: (layoutClass: string) => void;
|
|
26
|
+
setHeaderTitle: (title: string) => void;
|
|
27
|
+
setHeaderActions: (actions: React.ReactNode | null) => void;
|
|
28
|
+
setBreadcrumbs: (breadcrumbs: React.ReactNode | null) => void;
|
|
29
|
+
toggleCollapsed: () => void;
|
|
30
|
+
setMobileMenuOpened: (open: boolean) => void;
|
|
31
|
+
toggleMobileMenuOpened: () => void;
|
|
32
|
+
toggleSidebar: () => void;
|
|
33
|
+
openSidebar: () => void;
|
|
34
|
+
close: () => void;
|
|
35
|
+
setModalOpen: (open: boolean) => void;
|
|
36
|
+
setTheme: (theme: string) => void;
|
|
37
|
+
toggleTheme: () => void;
|
|
38
|
+
|
|
39
|
+
// Electron window controls
|
|
40
|
+
setWindowMaximized: (value: boolean) => void;
|
|
41
|
+
minimizeWindow: () => void;
|
|
42
|
+
maximizeWindow: () => void;
|
|
43
|
+
closeWindow: () => void;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const useUiStore = create<UiState>((set, get) => ({
|
|
47
|
+
open: false,
|
|
48
|
+
isModalOpen: false,
|
|
49
|
+
collapsed: false,
|
|
50
|
+
isMobileMenuOpened: false,
|
|
51
|
+
headerTitle: "Dashboard",
|
|
52
|
+
headerActions: null,
|
|
53
|
+
breadcrumbs: null,
|
|
54
|
+
layoutClass: "",
|
|
55
|
+
|
|
56
|
+
// ── Layout helpers ──────────────────────────────────────────────────────────
|
|
57
|
+
setLayoutClass: (layoutClass) => set({ layoutClass }),
|
|
58
|
+
setHeaderTitle: (title) => set({ headerTitle: title }),
|
|
59
|
+
setHeaderActions: (actions) => set({ headerActions: actions }),
|
|
60
|
+
setBreadcrumbs: (breadcrumbs) => set({ breadcrumbs }),
|
|
61
|
+
|
|
62
|
+
// ── Sidebar ─────────────────────────────────────────────────────────────────
|
|
63
|
+
toggleCollapsed: () => set((state) => ({ collapsed: !state.collapsed })),
|
|
64
|
+
setMobileMenuOpened: (open) => set({ isMobileMenuOpened: open }),
|
|
65
|
+
toggleMobileMenuOpened: () =>
|
|
66
|
+
set((state) => ({ isMobileMenuOpened: !state.isMobileMenuOpened })),
|
|
67
|
+
toggleSidebar: () => set((state) => ({ open: !state.open })),
|
|
68
|
+
openSidebar: () => set({ open: true }),
|
|
69
|
+
close: () => set({ open: false }),
|
|
70
|
+
|
|
71
|
+
// ── Modal ───────────────────────────────────────────────────────────────────
|
|
72
|
+
setModalOpen: (open) => set({ isModalOpen: open }),
|
|
73
|
+
|
|
74
|
+
// ── Theme ───────────────────────────────────────────────────────────────────
|
|
75
|
+
theme: localStorage.getItem("theme") || "light",
|
|
76
|
+
setTheme: (theme) => {
|
|
77
|
+
document.documentElement.setAttribute("data-theme", theme);
|
|
78
|
+
localStorage.setItem("theme", theme);
|
|
79
|
+
set({ theme });
|
|
80
|
+
},
|
|
81
|
+
toggleTheme: () => {
|
|
82
|
+
const nextTheme = get().theme === "light" ? "dark" : "light";
|
|
83
|
+
document.documentElement.setAttribute("data-theme", nextTheme);
|
|
84
|
+
localStorage.setItem("theme", nextTheme);
|
|
85
|
+
set({ theme: nextTheme });
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
// ── Electron window state ───────────────────────────────────────────────────
|
|
89
|
+
windowMaximized: false,
|
|
90
|
+
setWindowMaximized: (value) => set({ windowMaximized: value }),
|
|
91
|
+
minimizeWindow: () => window.electronAPI?.minimize(),
|
|
92
|
+
maximizeWindow: () => window.electronAPI?.maximize(),
|
|
93
|
+
closeWindow: () => window.electronAPI?.close(),
|
|
94
|
+
}));
|
|
95
|
+
|
|
96
|
+
export { useUiStore };
|
|
97
|
+
export type { UiState };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
2
|
+
|
|
3
|
+
import type { ElectronAPI } from '../electron/preload';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Augment the Window interface to include the Electron context bridge API.
|
|
7
|
+
* This gives full type safety when accessing window.electronAPI in the renderer.
|
|
8
|
+
*/
|
|
9
|
+
declare global {
|
|
10
|
+
interface Window {
|
|
11
|
+
electronAPI: ElectronAPI;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
|
|
9
|
+
/* Bundler mode */
|
|
10
|
+
"moduleResolution": "bundler",
|
|
11
|
+
"allowImportingTsExtensions": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"noEmit": true,
|
|
15
|
+
"jsx": "react-jsx",
|
|
16
|
+
|
|
17
|
+
/* Linting */
|
|
18
|
+
"strict": true,
|
|
19
|
+
"noUnusedLocals": true,
|
|
20
|
+
"noUnusedParameters": true,
|
|
21
|
+
"noFallthroughCasesInSwitch": true
|
|
22
|
+
},
|
|
23
|
+
"include": ["src", "electron"],
|
|
24
|
+
"references": [{ "path": "./tsconfig.node.json" }]
|
|
25
|
+
}
|
|
@@ -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.ts',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
// Preload script — reload renderer on change
|
|
19
|
+
entry: 'electron/preload.ts',
|
|
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
|
+
});
|
|
@@ -1,25 +1,63 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import React from "react";
|
|
3
3
|
import Link from "next/link";
|
|
4
|
-
import {
|
|
4
|
+
import { useRouter } from "next/navigation";
|
|
5
|
+
import { ArrowLeft, ChevronRight } from "lucide-react";
|
|
5
6
|
|
|
6
7
|
export const Breadcrumbs = ({ items }) => {
|
|
8
|
+
const router = useRouter();
|
|
7
9
|
return (
|
|
8
10
|
<div className="flex items-center gap-1.5 text-sm font-bold text-base-content/50 flex-wrap">
|
|
11
|
+
<button
|
|
12
|
+
className="btn btn-primary btn-soft btn-square btn-sm mr-1 hidden md:flex"
|
|
13
|
+
onClick={() => router.back()}
|
|
14
|
+
>
|
|
15
|
+
<ArrowLeft size={18} />
|
|
16
|
+
</button>
|
|
9
17
|
{items.map((item, idx) => {
|
|
10
18
|
const isLast = idx === items.length - 1;
|
|
11
19
|
return (
|
|
12
20
|
<React.Fragment key={idx}>
|
|
13
21
|
{item.path && !isLast ? (
|
|
14
|
-
<Link
|
|
22
|
+
<Link
|
|
23
|
+
href={item.path}
|
|
24
|
+
onClick={item.onClick}
|
|
25
|
+
className="hover:text-primary transition-colors hidden sm:flex items-center gap-1"
|
|
26
|
+
>
|
|
15
27
|
{item.label}
|
|
16
28
|
</Link>
|
|
17
|
-
) : (
|
|
18
|
-
<
|
|
29
|
+
) : item.onClick && !isLast ? (
|
|
30
|
+
<button
|
|
31
|
+
type="button"
|
|
32
|
+
onClick={item.onClick}
|
|
33
|
+
className="hover:text-primary transition-colors hidden sm:flex items-center gap-1"
|
|
34
|
+
>
|
|
19
35
|
{item.label}
|
|
20
|
-
</
|
|
36
|
+
</button>
|
|
37
|
+
) : (
|
|
38
|
+
<div className="flex flex-col">
|
|
39
|
+
<span
|
|
40
|
+
className={
|
|
41
|
+
isLast
|
|
42
|
+
? "text-lg md:text-xl font-bold leading-tight text-base-content tracking-tight truncate max-w-[200px] md:max-w-none"
|
|
43
|
+
: ""
|
|
44
|
+
}
|
|
45
|
+
>
|
|
46
|
+
{item.label}
|
|
47
|
+
</span>
|
|
48
|
+
{item.subtitle && (
|
|
49
|
+
<span className="text-xs text-base-content/60 font-normal truncate">
|
|
50
|
+
{item.subtitle}
|
|
51
|
+
</span>
|
|
52
|
+
)}
|
|
53
|
+
</div>
|
|
54
|
+
)}
|
|
55
|
+
{!isLast && (
|
|
56
|
+
<ChevronRight
|
|
57
|
+
size={14}
|
|
58
|
+
className="text-base-content/30 shrink-0 hidden sm:block"
|
|
59
|
+
/>
|
|
21
60
|
)}
|
|
22
|
-
{!isLast && <ChevronRight size={14} className="text-base-content/30 shrink-0" />}
|
|
23
61
|
</React.Fragment>
|
|
24
62
|
);
|
|
25
63
|
})}
|