gmoonc 0.0.3 → 0.0.4
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/dist/index.cjs +107 -17
- package/package.json +3 -3
- package/src/templates/shared/src/gmoonc/app/menu/defaultMenu.ts +1 -1
- package/src/templates/shared/src/gmoonc/config/defaultConfig.ts +1 -1
- package/src/templates/shared/src/gmoonc/layout/GMooncAppLayout.tsx +31 -28
- package/src/templates/shared/src/gmoonc/styles/theme.css +101 -0
- package/src/templates/shared/src/gmoonc/ui/menu.tsx +7 -3
- package/src/templates/shared/src/gmoonc/ui/shell.tsx +1 -1
package/dist/index.cjs
CHANGED
|
@@ -244,20 +244,29 @@ function patchEntryCss(entrypointPath, dryRun) {
|
|
|
244
244
|
return { success: true, backupPath: null };
|
|
245
245
|
}
|
|
246
246
|
const content = readFile(entrypointPath);
|
|
247
|
-
|
|
248
|
-
|
|
247
|
+
const hasThemeCss = content.includes("./gmoonc/styles/theme.css") || content.includes("gmoonc/styles/theme.css");
|
|
248
|
+
const hasGmooncCss = content.includes("./gmoonc/styles/gmoonc.css") || content.includes("gmoonc/styles/gmoonc.css");
|
|
249
|
+
if (hasThemeCss && hasGmooncCss) {
|
|
250
|
+
logSuccess("CSS imports already exist");
|
|
249
251
|
return { success: true, backupPath: null };
|
|
250
252
|
}
|
|
251
253
|
const importRegex = /^import\s+.*$/gm;
|
|
252
254
|
const imports = content.match(importRegex) || [];
|
|
253
255
|
let newContent = content;
|
|
256
|
+
const cssImports = [];
|
|
257
|
+
if (!hasThemeCss) {
|
|
258
|
+
cssImports.push('import "./gmoonc/styles/theme.css";');
|
|
259
|
+
}
|
|
260
|
+
if (!hasGmooncCss) {
|
|
261
|
+
cssImports.push('import "./gmoonc/styles/gmoonc.css";');
|
|
262
|
+
}
|
|
254
263
|
if (imports.length > 0) {
|
|
255
264
|
const lastImport = imports[imports.length - 1];
|
|
256
265
|
const lastImportIndex = content.lastIndexOf(lastImport);
|
|
257
266
|
const insertIndex = lastImportIndex + lastImport.length;
|
|
258
|
-
newContent = content.slice(0, insertIndex) +
|
|
267
|
+
newContent = content.slice(0, insertIndex) + "\n" + cssImports.join("\n") + content.slice(insertIndex);
|
|
259
268
|
} else {
|
|
260
|
-
newContent =
|
|
269
|
+
newContent = cssImports.join("\n") + "\n" + content;
|
|
261
270
|
}
|
|
262
271
|
const backupPath = writeFileSafe(entrypointPath, newContent);
|
|
263
272
|
if (backupPath) {
|
|
@@ -281,23 +290,58 @@ function extractRouteComponents(appContent) {
|
|
|
281
290
|
const lines = appContent.split("\n");
|
|
282
291
|
if (indexComponent) {
|
|
283
292
|
for (const line of lines) {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
293
|
+
const trimmed = line.trim();
|
|
294
|
+
if (trimmed.startsWith("import")) {
|
|
295
|
+
const defaultImportMatch = trimmed.match(/import\s+(\w+)\s+from\s+["']([^"']+)["']/);
|
|
296
|
+
if (defaultImportMatch && defaultImportMatch[1] === indexComponent) {
|
|
297
|
+
indexImport = trimmed;
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
const namedImportMatch = trimmed.match(/import\s+\{[^}]*\b(\w+)\b[^}]*\}\s+from\s+["']([^"']+)["']/);
|
|
301
|
+
if (namedImportMatch && namedImportMatch[1] === indexComponent) {
|
|
302
|
+
indexImport = trimmed;
|
|
303
|
+
break;
|
|
304
|
+
}
|
|
287
305
|
}
|
|
288
306
|
}
|
|
289
307
|
}
|
|
290
308
|
if (notFoundComponent) {
|
|
291
309
|
for (const line of lines) {
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
310
|
+
const trimmed = line.trim();
|
|
311
|
+
if (trimmed.startsWith("import")) {
|
|
312
|
+
const defaultImportMatch = trimmed.match(/import\s+(\w+)\s+from\s+["']([^"']+)["']/);
|
|
313
|
+
if (defaultImportMatch && defaultImportMatch[1] === notFoundComponent) {
|
|
314
|
+
notFoundImport = trimmed;
|
|
315
|
+
break;
|
|
316
|
+
}
|
|
317
|
+
const namedImportMatch = trimmed.match(/import\s+\{[^}]*\b(\w+)\b[^}]*\}\s+from\s+["']([^"']+)["']/);
|
|
318
|
+
if (namedImportMatch && namedImportMatch[1] === notFoundComponent) {
|
|
319
|
+
notFoundImport = trimmed;
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
295
322
|
}
|
|
296
323
|
}
|
|
297
324
|
}
|
|
298
325
|
return { indexImport, notFoundImport, indexComponent, notFoundComponent };
|
|
299
326
|
}
|
|
300
|
-
function
|
|
327
|
+
function convertImportToRelative(importLine, appRoutesPath, appPath) {
|
|
328
|
+
const pathMatch = importLine.match(/from\s+["']([^"']+)["']/);
|
|
329
|
+
if (!pathMatch) {
|
|
330
|
+
return importLine;
|
|
331
|
+
}
|
|
332
|
+
const originalPath = pathMatch[1];
|
|
333
|
+
if (originalPath.startsWith("./") || originalPath.startsWith("../")) {
|
|
334
|
+
const appDir = appPath.substring(0, appPath.lastIndexOf("/"));
|
|
335
|
+
const appRoutesDir = appRoutesPath.substring(0, appRoutesPath.lastIndexOf("/"));
|
|
336
|
+
const resolvedPath = (0, import_path5.join)(appDir, originalPath);
|
|
337
|
+
const relativePath = (0, import_path5.relative)(appRoutesDir, resolvedPath);
|
|
338
|
+
const normalizedPath = relativePath.replace(/\\/g, "/");
|
|
339
|
+
const finalPath = normalizedPath.startsWith(".") ? normalizedPath : "./" + normalizedPath;
|
|
340
|
+
return importLine.replace(pathMatch[1], finalPath);
|
|
341
|
+
}
|
|
342
|
+
return importLine;
|
|
343
|
+
}
|
|
344
|
+
function generateAppRoutes(consumerDir, basePath, indexImport, notFoundImport, indexComponent, notFoundComponent, appPath, dryRun) {
|
|
301
345
|
const appRoutesPath = (0, import_path5.join)(consumerDir, "src/gmoonc/router/AppRoutes.tsx");
|
|
302
346
|
if (dryRun) {
|
|
303
347
|
logInfo(`[DRY RUN] Would generate ${appRoutesPath}`);
|
|
@@ -313,19 +357,21 @@ function generateAppRoutes(consumerDir, basePath, indexImport, notFoundImport, i
|
|
|
313
357
|
routes.push(` { path: "*", element: <${notFoundComponent} /> }`);
|
|
314
358
|
}
|
|
315
359
|
const imports = [
|
|
316
|
-
"import { useRoutes } from 'react-router-dom';",
|
|
360
|
+
"import { useRoutes, type RouteObject } from 'react-router-dom';",
|
|
317
361
|
"import { createGmooncRoutes } from './createGmooncRoutes';"
|
|
318
362
|
];
|
|
319
363
|
if (indexImport) {
|
|
320
|
-
|
|
364
|
+
const convertedImport = convertImportToRelative(indexImport, appRoutesPath, appPath);
|
|
365
|
+
imports.push(convertedImport);
|
|
321
366
|
}
|
|
322
367
|
if (notFoundImport) {
|
|
323
|
-
|
|
368
|
+
const convertedImport = convertImportToRelative(notFoundImport, appRoutesPath, appPath);
|
|
369
|
+
imports.push(convertedImport);
|
|
324
370
|
}
|
|
325
371
|
const content = `${imports.join("\n")}
|
|
326
372
|
|
|
327
373
|
export function GMooncAppRoutes({ basePath = "${basePath}" }: { basePath?: string }) {
|
|
328
|
-
const allRoutes = [
|
|
374
|
+
const allRoutes: RouteObject[] = [
|
|
329
375
|
${routes.join(",\n")}
|
|
330
376
|
];
|
|
331
377
|
|
|
@@ -380,10 +426,54 @@ function patchBrowserRouter(consumerDir, basePath, dryRun) {
|
|
|
380
426
|
logInfo(`[DRY RUN] Would patch ${appPath}`);
|
|
381
427
|
return { success: true, backupPath: null };
|
|
382
428
|
}
|
|
383
|
-
generateAppRoutes(consumerDir, basePath, indexImport, notFoundImport, indexComponent, notFoundComponent, false);
|
|
429
|
+
generateAppRoutes(consumerDir, basePath, indexImport, notFoundImport, indexComponent, notFoundComponent, appPath, false);
|
|
384
430
|
const backupPath = writeFileSafe(appPath, "");
|
|
385
|
-
const importLine = `import { GMooncAppRoutes } from "./gmoonc/router/AppRoutes";`;
|
|
386
431
|
const lines = appContent.split("\n");
|
|
432
|
+
let hasBrowserRouterImport = false;
|
|
433
|
+
let reactRouterImportIndex = -1;
|
|
434
|
+
for (let i = 0; i < lines.length; i++) {
|
|
435
|
+
const line = lines[i].trim();
|
|
436
|
+
if (line.includes("from") && line.includes("react-router-dom")) {
|
|
437
|
+
reactRouterImportIndex = i;
|
|
438
|
+
if (line.includes("BrowserRouter")) {
|
|
439
|
+
hasBrowserRouterImport = true;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
if (!hasBrowserRouterImport) {
|
|
444
|
+
if (reactRouterImportIndex >= 0) {
|
|
445
|
+
const existingLine = lines[reactRouterImportIndex];
|
|
446
|
+
if (existingLine.includes("{") && existingLine.includes("}")) {
|
|
447
|
+
const updatedLine = existingLine.replace(/\{([^}]+)\}/, (match, imports) => {
|
|
448
|
+
const importList = imports.split(",").map((s) => s.trim());
|
|
449
|
+
if (!importList.includes("BrowserRouter")) {
|
|
450
|
+
importList.push("BrowserRouter");
|
|
451
|
+
}
|
|
452
|
+
return `{ ${importList.join(", ")} }`;
|
|
453
|
+
});
|
|
454
|
+
lines[reactRouterImportIndex] = updatedLine;
|
|
455
|
+
} else {
|
|
456
|
+
lines.splice(reactRouterImportIndex + 1, 0, `import { BrowserRouter } from "react-router-dom";`);
|
|
457
|
+
}
|
|
458
|
+
} else {
|
|
459
|
+
const importLine2 = `import { BrowserRouter } from "react-router-dom";`;
|
|
460
|
+
let lastImportIndex2 = -1;
|
|
461
|
+
for (let i = 0; i < lines.length; i++) {
|
|
462
|
+
const line = lines[i].trim();
|
|
463
|
+
if (line.startsWith("import ") || line.startsWith("import{") || line.startsWith("import ")) {
|
|
464
|
+
lastImportIndex2 = i;
|
|
465
|
+
} else if (line && lastImportIndex2 >= 0 && !line.startsWith("//") && !line.startsWith("/*")) {
|
|
466
|
+
break;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
if (lastImportIndex2 >= 0) {
|
|
470
|
+
lines.splice(lastImportIndex2 + 1, 0, importLine2);
|
|
471
|
+
} else {
|
|
472
|
+
lines.unshift(importLine2);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
const importLine = `import { GMooncAppRoutes } from "./gmoonc/router/AppRoutes";`;
|
|
387
477
|
let lastImportIndex = -1;
|
|
388
478
|
for (let i = 0; i < lines.length; i++) {
|
|
389
479
|
const line = lines[i].trim();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gmoonc",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "Goalmoon Ctrl (gmoonc): Complete dashboard installer for React projects",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://gmoonc.com",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
},
|
|
15
15
|
"type": "module",
|
|
16
16
|
"bin": {
|
|
17
|
-
"gmoonc": "./dist/
|
|
17
|
+
"gmoonc": "./dist/index.cjs"
|
|
18
18
|
},
|
|
19
19
|
"files": [
|
|
20
20
|
"dist",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
],
|
|
26
26
|
"scripts": {
|
|
27
27
|
"sync:templates": "node scripts/sync-templates.mjs",
|
|
28
|
-
"build": "tsup src/cli/index.ts --format cjs --no-splitting --out-dir dist && node -e \"const fs=require('fs');const
|
|
28
|
+
"build": "tsup src/cli/index.ts --format cjs --no-splitting --out-dir dist && node -e \"const fs=require('fs');const path=require('path');const distDir='dist';const files=fs.readdirSync(distDir);const indexFile=files.find(f=>f.startsWith('index')&&f.endsWith('.js'));if(indexFile){const oldPath=path.join(distDir,indexFile);const newPath=path.join(distDir,'index.cjs');fs.renameSync(oldPath,newPath);const content=fs.readFileSync(newPath,'utf8');if(!content.startsWith('#!/usr/bin/env node')){fs.writeFileSync(newPath,'#!/usr/bin/env node\\n'+content)}}\"",
|
|
29
29
|
"clean": "rimraf dist",
|
|
30
30
|
"prepublishOnly": "npm run clean && npm run build",
|
|
31
31
|
"postinstall": "node scripts/block-global-install.cjs"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useCallback } from 'react';
|
|
2
2
|
import { Outlet, useLocation, useNavigate, Link } from 'react-router-dom';
|
|
3
|
-
import { GmooncShell } from '../ui/
|
|
3
|
+
import { GmooncShell } from '../ui/shell';
|
|
4
4
|
import { defaultConfig } from '../config/defaultConfig';
|
|
5
5
|
import { GMooncSessionProvider, useGMooncSession } from '../session/GMooncSessionContext';
|
|
6
6
|
import { GMooncUserProfile } from '../components/GMooncUserProfile';
|
|
@@ -24,33 +24,36 @@ function GMooncAppLayoutInner() {
|
|
|
24
24
|
}, [navigate]);
|
|
25
25
|
|
|
26
26
|
return (
|
|
27
|
-
<
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
27
|
+
<div className="gmoonc-root">
|
|
28
|
+
<GmooncShell
|
|
29
|
+
config={defaultConfig}
|
|
30
|
+
roles={roles}
|
|
31
|
+
activePath={location.pathname}
|
|
32
|
+
onNavigate={(path) => navigate(path)}
|
|
33
|
+
headerRight={
|
|
34
|
+
<GMooncUserProfile
|
|
35
|
+
onAccount={handleAccount}
|
|
36
|
+
onAbout={handleAbout}
|
|
37
|
+
onLogoutRequest={handleLogout}
|
|
38
|
+
/>
|
|
39
|
+
}
|
|
40
|
+
renderLink={({ path, label, isActive, onClick, className, children }) => (
|
|
41
|
+
<Link
|
|
42
|
+
to={path}
|
|
43
|
+
onClick={onClick}
|
|
44
|
+
className={className}
|
|
45
|
+
style={{
|
|
46
|
+
textDecoration: isActive ? 'underline' : 'none',
|
|
47
|
+
color: 'inherit'
|
|
48
|
+
}}
|
|
49
|
+
>
|
|
50
|
+
{children || label}
|
|
51
|
+
</Link>
|
|
52
|
+
)}
|
|
53
|
+
>
|
|
54
|
+
<Outlet />
|
|
55
|
+
</GmooncShell>
|
|
56
|
+
</div>
|
|
54
57
|
);
|
|
55
58
|
}
|
|
56
59
|
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Goalmoon Ctrl (gmoonc) Theme Variables
|
|
3
|
+
*
|
|
4
|
+
* This file contains CSS custom properties (variables) that define the visual theme
|
|
5
|
+
* of the gmoonc dashboard. You can customize these values to match your brand.
|
|
6
|
+
*
|
|
7
|
+
* All variables are scoped under .gmoonc-root to avoid conflicts with your app.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
.gmoonc-root {
|
|
11
|
+
/* Colors - Background */
|
|
12
|
+
--gmoonc-color-bg: #293047;
|
|
13
|
+
--gmoonc-color-bg-light: #eaf0f5;
|
|
14
|
+
--gmoonc-color-bg-gradient-start: #eaf0f5;
|
|
15
|
+
--gmoonc-color-bg-gradient-end: #dbe2ea;
|
|
16
|
+
|
|
17
|
+
/* Colors - Surface */
|
|
18
|
+
--gmoonc-color-surface: #374161;
|
|
19
|
+
--gmoonc-color-surface-2: #3F4A6E;
|
|
20
|
+
--gmoonc-color-surface-3: #6374AD;
|
|
21
|
+
--gmoonc-color-surface-light: #f8f9fa;
|
|
22
|
+
--gmoonc-color-surface-white: #ffffff;
|
|
23
|
+
|
|
24
|
+
/* Colors - Text */
|
|
25
|
+
--gmoonc-color-text: #eaf0f5;
|
|
26
|
+
--gmoonc-color-text-primary: #374161;
|
|
27
|
+
--gmoonc-color-text-secondary: #6c757d;
|
|
28
|
+
--gmoonc-color-text-muted: #dbe2ea;
|
|
29
|
+
--gmoonc-color-text-white: #ffffff;
|
|
30
|
+
|
|
31
|
+
/* Colors - Primary */
|
|
32
|
+
--gmoonc-color-primary: #879FED;
|
|
33
|
+
--gmoonc-color-primary-2: #6374AD;
|
|
34
|
+
--gmoonc-color-primary-dark: #374161;
|
|
35
|
+
|
|
36
|
+
/* Colors - Accent */
|
|
37
|
+
--gmoonc-color-accent: #71b399;
|
|
38
|
+
|
|
39
|
+
/* Colors - Borders */
|
|
40
|
+
--gmoonc-color-border: #dbe2ea;
|
|
41
|
+
--gmoonc-color-border-light: #e9ecef;
|
|
42
|
+
--gmoonc-color-border-dark: #1d2436;
|
|
43
|
+
|
|
44
|
+
/* Colors - States */
|
|
45
|
+
--gmoonc-color-error: #dc2626;
|
|
46
|
+
--gmoonc-color-error-bg: #fef2f2;
|
|
47
|
+
--gmoonc-color-error-border: #fecaca;
|
|
48
|
+
--gmoonc-color-success: #166534;
|
|
49
|
+
--gmoonc-color-success-bg: #f0fdf4;
|
|
50
|
+
--gmoonc-color-success-border: #bbf7d0;
|
|
51
|
+
--gmoonc-color-disabled: #6c757d;
|
|
52
|
+
--gmoonc-color-disabled-bg: #f8f9fa;
|
|
53
|
+
|
|
54
|
+
/* Spacing */
|
|
55
|
+
--gmoonc-gap: 12px;
|
|
56
|
+
--gmoonc-gap-sm: 8px;
|
|
57
|
+
--gmoonc-gap-md: 16px;
|
|
58
|
+
--gmoonc-gap-lg: 20px;
|
|
59
|
+
--gmoonc-gap-xl: 30px;
|
|
60
|
+
|
|
61
|
+
/* Border Radius */
|
|
62
|
+
--gmoonc-radius: 12px;
|
|
63
|
+
--gmoonc-radius-sm: 8px;
|
|
64
|
+
--gmoonc-radius-md: 10px;
|
|
65
|
+
--gmoonc-radius-lg: 16px;
|
|
66
|
+
|
|
67
|
+
/* Typography */
|
|
68
|
+
--gmoonc-font-family: "Montserrat", system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
|
|
69
|
+
--gmoonc-font-weight-title: 700;
|
|
70
|
+
--gmoonc-font-weight-heading: 600;
|
|
71
|
+
--gmoonc-font-weight-body: 400;
|
|
72
|
+
--gmoonc-font-weight-medium: 500;
|
|
73
|
+
|
|
74
|
+
/* Font Sizes */
|
|
75
|
+
--gmoonc-font-size-xs: 12px;
|
|
76
|
+
--gmoonc-font-size-sm: 14px;
|
|
77
|
+
--gmoonc-font-size-base: 16px;
|
|
78
|
+
--gmoonc-font-size-lg: 18px;
|
|
79
|
+
--gmoonc-font-size-xl: 24px;
|
|
80
|
+
--gmoonc-font-size-2xl: 28px;
|
|
81
|
+
--gmoonc-font-size-3xl: 32px;
|
|
82
|
+
|
|
83
|
+
/* Shadows */
|
|
84
|
+
--gmoonc-shadow-sm: 0 4px 6px rgba(55, 65, 97, 0.1);
|
|
85
|
+
--gmoonc-shadow-md: 0 4px 20px rgba(55, 65, 97, 0.1);
|
|
86
|
+
--gmoonc-shadow-lg: 0 8px 32px rgba(55, 65, 97, 0.15);
|
|
87
|
+
--gmoonc-shadow-header: 0 4px 20px rgba(55, 65, 97, 0.3);
|
|
88
|
+
--gmoonc-shadow-menu-header: 0 8px 16px rgba(0, 0, 0, 0.22);
|
|
89
|
+
|
|
90
|
+
/* Transitions */
|
|
91
|
+
--gmoonc-transition: 0.3s ease;
|
|
92
|
+
--gmoonc-transition-fast: 0.2s ease;
|
|
93
|
+
|
|
94
|
+
/* Z-index */
|
|
95
|
+
--gmoonc-z-header: 1000;
|
|
96
|
+
--gmoonc-z-profile: 1001;
|
|
97
|
+
--gmoonc-z-dropdown: 1002;
|
|
98
|
+
--gmoonc-z-hamburger: 1002;
|
|
99
|
+
--gmoonc-z-backdrop: 1050;
|
|
100
|
+
--gmoonc-z-menu: 1100;
|
|
101
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useState, useCallback, useMemo, useEffect } from 'react';
|
|
2
2
|
import { createPortal } from 'react-dom';
|
|
3
|
-
import type { MenuItem as CoreMenuItem } from '
|
|
3
|
+
import type { MenuItem as CoreMenuItem } from '../core/types';
|
|
4
4
|
import './styles.css';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -36,6 +36,8 @@ export interface GmooncMenuProps {
|
|
|
36
36
|
label: string;
|
|
37
37
|
isActive: boolean;
|
|
38
38
|
onClick?: () => void;
|
|
39
|
+
className?: string;
|
|
40
|
+
children?: React.ReactNode;
|
|
39
41
|
}) => React.ReactNode;
|
|
40
42
|
/** Whether menu is open (for mobile/tablet) */
|
|
41
43
|
isOpen?: boolean;
|
|
@@ -392,7 +394,8 @@ export function GmooncMenu({
|
|
|
392
394
|
path: item.path,
|
|
393
395
|
label: item.label,
|
|
394
396
|
isActive: isItemActive,
|
|
395
|
-
onClick: handleItemClick
|
|
397
|
+
onClick: handleItemClick,
|
|
398
|
+
className: `gmoonc-menu-link ${hasSubmenu ? 'has-submenu' : ''} ${isSubmenuOpen ? 'submenu-open' : ''} ${isItemActive ? 'active' : ''}`
|
|
396
399
|
})
|
|
397
400
|
) : (
|
|
398
401
|
<button
|
|
@@ -439,7 +442,8 @@ export function GmooncMenu({
|
|
|
439
442
|
path: subItem.path,
|
|
440
443
|
label: subItem.label,
|
|
441
444
|
isActive: isSubActive,
|
|
442
|
-
onClick: handleSubItemClick
|
|
445
|
+
onClick: handleSubItemClick,
|
|
446
|
+
className: `gmoonc-submenu-link ${isSubActive ? 'active' : ''}`
|
|
443
447
|
}) : (
|
|
444
448
|
<button
|
|
445
449
|
type="button"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState, useEffect, useCallback } from 'react';
|
|
2
|
-
import type { GmooncConfig } from '
|
|
2
|
+
import type { GmooncConfig } from '../core/types';
|
|
3
3
|
import { GmooncHeader } from './header';
|
|
4
4
|
import { GmooncSidebar } from './sidebar';
|
|
5
5
|
import { GmooncMenu, type GmooncMenuItem } from './menu';
|