@leduchai/vmeta-ui-shell 0.1.4 → 0.1.6
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 +42 -12
- package/dist/index.d.cts +9 -2
- package/dist/index.d.ts +9 -2
- package/dist/index.js +41 -13
- package/dist/styles.css +5 -3
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -22,6 +22,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
22
22
|
var index_exports = {};
|
|
23
23
|
__export(index_exports, {
|
|
24
24
|
VmetaAppShell: () => VmetaAppShell,
|
|
25
|
+
VmetaBrandMark: () => VmetaBrandMark,
|
|
26
|
+
VmetaCollapseIcon: () => VmetaCollapseIcon,
|
|
25
27
|
VmetaSidebar: () => VmetaSidebar,
|
|
26
28
|
isMenuItemActive: () => isMenuItemActive
|
|
27
29
|
});
|
|
@@ -29,6 +31,25 @@ module.exports = __toCommonJS(index_exports);
|
|
|
29
31
|
var import_react = require("react");
|
|
30
32
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
31
33
|
var allowAllPermissions = () => true;
|
|
34
|
+
function VmetaBrandMark({ className }) {
|
|
35
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { className, viewBox: "0 0 64 64", role: "img", "aria-label": "VMETA", focusable: "false", children: [
|
|
36
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { fill: "currentColor", d: "M7 10h13l12 31 12-31h13L38 55H26L7 10Z" }),
|
|
37
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { fill: "currentColor", d: "m25 10 7 18 7-18h10L32 48 15 10h10Z", opacity: ".42" })
|
|
38
|
+
] });
|
|
39
|
+
}
|
|
40
|
+
function VmetaCollapseIcon({ collapsed }) {
|
|
41
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { viewBox: "0 0 24 24", width: "18", height: "18", "aria-hidden": "true", focusable: "false", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
42
|
+
"path",
|
|
43
|
+
{
|
|
44
|
+
d: collapsed ? "m9 5 7 7-7 7" : "m15 5-7 7 7 7",
|
|
45
|
+
fill: "none",
|
|
46
|
+
stroke: "currentColor",
|
|
47
|
+
strokeLinecap: "round",
|
|
48
|
+
strokeLinejoin: "round",
|
|
49
|
+
strokeWidth: "1.8"
|
|
50
|
+
}
|
|
51
|
+
) });
|
|
52
|
+
}
|
|
32
53
|
function isMenuItemActive(item, activePath) {
|
|
33
54
|
if (item.match === "exact") return activePath === item.href;
|
|
34
55
|
if (item.match === "prefix") return activePath === item.href || activePath.startsWith(`${item.href}/`);
|
|
@@ -60,6 +81,7 @@ function VmetaSidebar({
|
|
|
60
81
|
activePath,
|
|
61
82
|
collapsed = false,
|
|
62
83
|
storageKey = `vmeta:${app.appId}:menu-groups`,
|
|
84
|
+
idPrefix,
|
|
63
85
|
account,
|
|
64
86
|
footer,
|
|
65
87
|
showCollapse = false,
|
|
@@ -67,6 +89,8 @@ function VmetaSidebar({
|
|
|
67
89
|
onNavigate,
|
|
68
90
|
onCollapsedChange
|
|
69
91
|
}) {
|
|
92
|
+
const generatedId = (0, import_react.useId)().replace(/:/g, "");
|
|
93
|
+
const groupIdPrefix = idPrefix ?? `vmeta-menu-${app.appId}-${generatedId}`;
|
|
70
94
|
const visibleGroups = (0, import_react.useMemo)(
|
|
71
95
|
() => groups.map((group) => ({
|
|
72
96
|
...group,
|
|
@@ -111,13 +135,7 @@ function VmetaSidebar({
|
|
|
111
135
|
};
|
|
112
136
|
const menuItems = collapsed ? visibleGroups.flatMap((group) => group.children) : [];
|
|
113
137
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("aside", { className: `vmeta-sidebar ${collapsed ? "is-collapsed" : ""}`, "aria-label": `Dieu huong ${app.appName}`, children: [
|
|
114
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
115
|
-
app.logoSrc ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: app.logoSrc, alt: app.logoAlt ?? "", className: "vmeta-sidebar__logo" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "vmeta-sidebar__mark", "aria-hidden": "true", children: "V" }),
|
|
116
|
-
!collapsed && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "vmeta-sidebar__brand-copy", children: [
|
|
117
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: app.appName }),
|
|
118
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { children: app.appSubtitle })
|
|
119
|
-
] })
|
|
120
|
-
] }),
|
|
138
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { className: "vmeta-sidebar__brand", href: app.homeHref ?? "/", "aria-label": `${app.appName} - ${app.appSubtitle}`, children: app.logoSrc ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: app.logoSrc, alt: app.logoAlt ?? "", className: "vmeta-sidebar__logo" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(VmetaBrandMark, { className: "vmeta-sidebar__mark" }) }),
|
|
121
139
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("nav", { className: "vmeta-sidebar__nav", children: collapsed ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "vmeta-sidebar__collapsed-items", children: menuItems.map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(VmetaMenuButton, { item, active: isMenuItemActive(item, activePath), collapsed: true, onNavigate: navigate }, item.key)) }) : visibleGroups.map((group) => {
|
|
122
140
|
const open = openKeys.includes(group.key);
|
|
123
141
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "vmeta-menu-group", children: [
|
|
@@ -128,14 +146,14 @@ function VmetaSidebar({
|
|
|
128
146
|
className: "vmeta-menu-group__toggle",
|
|
129
147
|
onClick: () => toggleGroup(group.key),
|
|
130
148
|
"aria-expanded": open,
|
|
131
|
-
"aria-controls":
|
|
149
|
+
"aria-controls": `${groupIdPrefix}-${group.key}`,
|
|
132
150
|
children: [
|
|
133
151
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: group.label }),
|
|
134
152
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `vmeta-menu-group__chevron ${open ? "is-open" : ""}`, "aria-hidden": "true", children: "\u2304" })
|
|
135
153
|
]
|
|
136
154
|
}
|
|
137
155
|
),
|
|
138
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { id:
|
|
156
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { id: `${groupIdPrefix}-${group.key}`, className: `vmeta-menu-group__items ${open ? "is-open" : ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: group.children.map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(VmetaMenuButton, { item, active: isMenuItemActive(item, activePath), onNavigate: navigate }, item.key)) }) })
|
|
139
157
|
] }, group.key);
|
|
140
158
|
}) }),
|
|
141
159
|
footer && !collapsed && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "vmeta-sidebar__footer", children: footer }),
|
|
@@ -155,7 +173,7 @@ function VmetaSidebar({
|
|
|
155
173
|
onClick: () => onCollapsedChange?.(!collapsed),
|
|
156
174
|
"aria-label": collapsed ? "Mo rong thanh dieu huong" : "Thu gon thanh dieu huong",
|
|
157
175
|
title: collapsed ? "Mo rong menu" : "Thu gon menu",
|
|
158
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
176
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(VmetaCollapseIcon, { collapsed })
|
|
159
177
|
}
|
|
160
178
|
)
|
|
161
179
|
] });
|
|
@@ -196,15 +214,24 @@ function VmetaAppShell({
|
|
|
196
214
|
canAccess,
|
|
197
215
|
onNavigate
|
|
198
216
|
}) {
|
|
199
|
-
const [collapsed, setCollapsed] = (0, import_react.useState)(
|
|
217
|
+
const [collapsed, setCollapsed] = (0, import_react.useState)(false);
|
|
200
218
|
const [menuOpen, setMenuOpen] = (0, import_react.useState)(false);
|
|
219
|
+
(0, import_react.useEffect)(() => {
|
|
220
|
+
setCollapsed(readStoredBoolean(storageKey, false));
|
|
221
|
+
}, [storageKey]);
|
|
201
222
|
const updateCollapsed = (value) => {
|
|
202
223
|
setCollapsed(value);
|
|
203
224
|
writeStorage(storageKey, String(value));
|
|
204
225
|
};
|
|
205
226
|
const navigate = (item) => {
|
|
206
227
|
setMenuOpen(false);
|
|
207
|
-
onNavigate
|
|
228
|
+
if (onNavigate) {
|
|
229
|
+
onNavigate(item);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
if (typeof window === "undefined") return;
|
|
233
|
+
if (item.external) window.open(item.href, "_blank", "noopener,noreferrer");
|
|
234
|
+
else window.location.assign(item.href);
|
|
208
235
|
};
|
|
209
236
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("main", { className: `vmeta-app-shell ${collapsed ? "is-collapsed" : ""}`, children: [
|
|
210
237
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
@@ -250,6 +277,7 @@ function VmetaAppShell({
|
|
|
250
277
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "vmeta-app-shell__menu-button", onClick: () => setMenuOpen(true), "aria-label": "Mo menu dieu huong", children: "\u2630" }),
|
|
251
278
|
header.leading,
|
|
252
279
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "vmeta-app-shell__title", children: [
|
|
280
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "vmeta-app-shell__app-name", children: app.appName }),
|
|
253
281
|
header.eyebrow && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: header.eyebrow }),
|
|
254
282
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { children: header.title }),
|
|
255
283
|
header.description && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: header.description })
|
|
@@ -264,6 +292,8 @@ function VmetaAppShell({
|
|
|
264
292
|
// Annotate the CommonJS export names for ESM import in node:
|
|
265
293
|
0 && (module.exports = {
|
|
266
294
|
VmetaAppShell,
|
|
295
|
+
VmetaBrandMark,
|
|
296
|
+
VmetaCollapseIcon,
|
|
267
297
|
VmetaSidebar,
|
|
268
298
|
isMenuItemActive
|
|
269
299
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -41,6 +41,12 @@ type VmetaHeader = {
|
|
|
41
41
|
leading?: ReactNode;
|
|
42
42
|
};
|
|
43
43
|
type VmetaPermissionResolver = (permission: string | string[]) => boolean;
|
|
44
|
+
declare function VmetaBrandMark({ className }: {
|
|
45
|
+
className?: string;
|
|
46
|
+
}): react_jsx_runtime.JSX.Element;
|
|
47
|
+
declare function VmetaCollapseIcon({ collapsed }: {
|
|
48
|
+
collapsed: boolean;
|
|
49
|
+
}): react_jsx_runtime.JSX.Element;
|
|
44
50
|
declare function isMenuItemActive(item: VmetaMenuItem, activePath: string): boolean;
|
|
45
51
|
interface VmetaSidebarProps {
|
|
46
52
|
app: VmetaAppIdentity;
|
|
@@ -48,6 +54,7 @@ interface VmetaSidebarProps {
|
|
|
48
54
|
activePath: string;
|
|
49
55
|
collapsed?: boolean;
|
|
50
56
|
storageKey?: string;
|
|
57
|
+
idPrefix?: string;
|
|
51
58
|
account?: VmetaAccount;
|
|
52
59
|
footer?: ReactNode;
|
|
53
60
|
showCollapse?: boolean;
|
|
@@ -55,7 +62,7 @@ interface VmetaSidebarProps {
|
|
|
55
62
|
onNavigate?: (item: VmetaMenuItem) => void;
|
|
56
63
|
onCollapsedChange?: (collapsed: boolean) => void;
|
|
57
64
|
}
|
|
58
|
-
declare function VmetaSidebar({ app, groups, activePath, collapsed, storageKey, account, footer, showCollapse, canAccess, onNavigate, onCollapsedChange, }: VmetaSidebarProps): react_jsx_runtime.JSX.Element;
|
|
65
|
+
declare function VmetaSidebar({ app, groups, activePath, collapsed, storageKey, idPrefix, account, footer, showCollapse, canAccess, onNavigate, onCollapsedChange, }: VmetaSidebarProps): react_jsx_runtime.JSX.Element;
|
|
59
66
|
interface VmetaAppShellProps {
|
|
60
67
|
app: VmetaAppIdentity;
|
|
61
68
|
groups: VmetaMenuGroup[];
|
|
@@ -71,4 +78,4 @@ interface VmetaAppShellProps {
|
|
|
71
78
|
}
|
|
72
79
|
declare function VmetaAppShell({ app, groups, activePath, header, children, account, sidebarFooter, contentClassName, storageKey, canAccess, onNavigate, }: VmetaAppShellProps): react_jsx_runtime.JSX.Element;
|
|
73
80
|
|
|
74
|
-
export { type VmetaAccount, type VmetaAppIdentity, VmetaAppShell, type VmetaAppShellProps, type VmetaHeader, type VmetaMenuGroup, type VmetaMenuItem, type VmetaMenuMatch, type VmetaPermissionResolver, VmetaSidebar, type VmetaSidebarProps, isMenuItemActive };
|
|
81
|
+
export { type VmetaAccount, type VmetaAppIdentity, VmetaAppShell, type VmetaAppShellProps, VmetaBrandMark, VmetaCollapseIcon, type VmetaHeader, type VmetaMenuGroup, type VmetaMenuItem, type VmetaMenuMatch, type VmetaPermissionResolver, VmetaSidebar, type VmetaSidebarProps, isMenuItemActive };
|
package/dist/index.d.ts
CHANGED
|
@@ -41,6 +41,12 @@ type VmetaHeader = {
|
|
|
41
41
|
leading?: ReactNode;
|
|
42
42
|
};
|
|
43
43
|
type VmetaPermissionResolver = (permission: string | string[]) => boolean;
|
|
44
|
+
declare function VmetaBrandMark({ className }: {
|
|
45
|
+
className?: string;
|
|
46
|
+
}): react_jsx_runtime.JSX.Element;
|
|
47
|
+
declare function VmetaCollapseIcon({ collapsed }: {
|
|
48
|
+
collapsed: boolean;
|
|
49
|
+
}): react_jsx_runtime.JSX.Element;
|
|
44
50
|
declare function isMenuItemActive(item: VmetaMenuItem, activePath: string): boolean;
|
|
45
51
|
interface VmetaSidebarProps {
|
|
46
52
|
app: VmetaAppIdentity;
|
|
@@ -48,6 +54,7 @@ interface VmetaSidebarProps {
|
|
|
48
54
|
activePath: string;
|
|
49
55
|
collapsed?: boolean;
|
|
50
56
|
storageKey?: string;
|
|
57
|
+
idPrefix?: string;
|
|
51
58
|
account?: VmetaAccount;
|
|
52
59
|
footer?: ReactNode;
|
|
53
60
|
showCollapse?: boolean;
|
|
@@ -55,7 +62,7 @@ interface VmetaSidebarProps {
|
|
|
55
62
|
onNavigate?: (item: VmetaMenuItem) => void;
|
|
56
63
|
onCollapsedChange?: (collapsed: boolean) => void;
|
|
57
64
|
}
|
|
58
|
-
declare function VmetaSidebar({ app, groups, activePath, collapsed, storageKey, account, footer, showCollapse, canAccess, onNavigate, onCollapsedChange, }: VmetaSidebarProps): react_jsx_runtime.JSX.Element;
|
|
65
|
+
declare function VmetaSidebar({ app, groups, activePath, collapsed, storageKey, idPrefix, account, footer, showCollapse, canAccess, onNavigate, onCollapsedChange, }: VmetaSidebarProps): react_jsx_runtime.JSX.Element;
|
|
59
66
|
interface VmetaAppShellProps {
|
|
60
67
|
app: VmetaAppIdentity;
|
|
61
68
|
groups: VmetaMenuGroup[];
|
|
@@ -71,4 +78,4 @@ interface VmetaAppShellProps {
|
|
|
71
78
|
}
|
|
72
79
|
declare function VmetaAppShell({ app, groups, activePath, header, children, account, sidebarFooter, contentClassName, storageKey, canAccess, onNavigate, }: VmetaAppShellProps): react_jsx_runtime.JSX.Element;
|
|
73
80
|
|
|
74
|
-
export { type VmetaAccount, type VmetaAppIdentity, VmetaAppShell, type VmetaAppShellProps, type VmetaHeader, type VmetaMenuGroup, type VmetaMenuItem, type VmetaMenuMatch, type VmetaPermissionResolver, VmetaSidebar, type VmetaSidebarProps, isMenuItemActive };
|
|
81
|
+
export { type VmetaAccount, type VmetaAppIdentity, VmetaAppShell, type VmetaAppShellProps, VmetaBrandMark, VmetaCollapseIcon, type VmetaHeader, type VmetaMenuGroup, type VmetaMenuItem, type VmetaMenuMatch, type VmetaPermissionResolver, VmetaSidebar, type VmetaSidebarProps, isMenuItemActive };
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,28 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
// src/index.tsx
|
|
4
|
-
import { useEffect, useMemo, useState } from "react";
|
|
4
|
+
import { useEffect, useId, useMemo, useState } from "react";
|
|
5
5
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
6
|
var allowAllPermissions = () => true;
|
|
7
|
+
function VmetaBrandMark({ className }) {
|
|
8
|
+
return /* @__PURE__ */ jsxs("svg", { className, viewBox: "0 0 64 64", role: "img", "aria-label": "VMETA", focusable: "false", children: [
|
|
9
|
+
/* @__PURE__ */ jsx("path", { fill: "currentColor", d: "M7 10h13l12 31 12-31h13L38 55H26L7 10Z" }),
|
|
10
|
+
/* @__PURE__ */ jsx("path", { fill: "currentColor", d: "m25 10 7 18 7-18h10L32 48 15 10h10Z", opacity: ".42" })
|
|
11
|
+
] });
|
|
12
|
+
}
|
|
13
|
+
function VmetaCollapseIcon({ collapsed }) {
|
|
14
|
+
return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", width: "18", height: "18", "aria-hidden": "true", focusable: "false", children: /* @__PURE__ */ jsx(
|
|
15
|
+
"path",
|
|
16
|
+
{
|
|
17
|
+
d: collapsed ? "m9 5 7 7-7 7" : "m15 5-7 7 7 7",
|
|
18
|
+
fill: "none",
|
|
19
|
+
stroke: "currentColor",
|
|
20
|
+
strokeLinecap: "round",
|
|
21
|
+
strokeLinejoin: "round",
|
|
22
|
+
strokeWidth: "1.8"
|
|
23
|
+
}
|
|
24
|
+
) });
|
|
25
|
+
}
|
|
7
26
|
function isMenuItemActive(item, activePath) {
|
|
8
27
|
if (item.match === "exact") return activePath === item.href;
|
|
9
28
|
if (item.match === "prefix") return activePath === item.href || activePath.startsWith(`${item.href}/`);
|
|
@@ -35,6 +54,7 @@ function VmetaSidebar({
|
|
|
35
54
|
activePath,
|
|
36
55
|
collapsed = false,
|
|
37
56
|
storageKey = `vmeta:${app.appId}:menu-groups`,
|
|
57
|
+
idPrefix,
|
|
38
58
|
account,
|
|
39
59
|
footer,
|
|
40
60
|
showCollapse = false,
|
|
@@ -42,6 +62,8 @@ function VmetaSidebar({
|
|
|
42
62
|
onNavigate,
|
|
43
63
|
onCollapsedChange
|
|
44
64
|
}) {
|
|
65
|
+
const generatedId = useId().replace(/:/g, "");
|
|
66
|
+
const groupIdPrefix = idPrefix ?? `vmeta-menu-${app.appId}-${generatedId}`;
|
|
45
67
|
const visibleGroups = useMemo(
|
|
46
68
|
() => groups.map((group) => ({
|
|
47
69
|
...group,
|
|
@@ -86,13 +108,7 @@ function VmetaSidebar({
|
|
|
86
108
|
};
|
|
87
109
|
const menuItems = collapsed ? visibleGroups.flatMap((group) => group.children) : [];
|
|
88
110
|
return /* @__PURE__ */ jsxs("aside", { className: `vmeta-sidebar ${collapsed ? "is-collapsed" : ""}`, "aria-label": `Dieu huong ${app.appName}`, children: [
|
|
89
|
-
/* @__PURE__ */
|
|
90
|
-
app.logoSrc ? /* @__PURE__ */ jsx("img", { src: app.logoSrc, alt: app.logoAlt ?? "", className: "vmeta-sidebar__logo" }) : /* @__PURE__ */ jsx("span", { className: "vmeta-sidebar__mark", "aria-hidden": "true", children: "V" }),
|
|
91
|
-
!collapsed && /* @__PURE__ */ jsxs("span", { className: "vmeta-sidebar__brand-copy", children: [
|
|
92
|
-
/* @__PURE__ */ jsx("strong", { children: app.appName }),
|
|
93
|
-
/* @__PURE__ */ jsx("small", { children: app.appSubtitle })
|
|
94
|
-
] })
|
|
95
|
-
] }),
|
|
111
|
+
/* @__PURE__ */ jsx("a", { className: "vmeta-sidebar__brand", href: app.homeHref ?? "/", "aria-label": `${app.appName} - ${app.appSubtitle}`, children: app.logoSrc ? /* @__PURE__ */ jsx("img", { src: app.logoSrc, alt: app.logoAlt ?? "", className: "vmeta-sidebar__logo" }) : /* @__PURE__ */ jsx(VmetaBrandMark, { className: "vmeta-sidebar__mark" }) }),
|
|
96
112
|
/* @__PURE__ */ jsx("nav", { className: "vmeta-sidebar__nav", children: collapsed ? /* @__PURE__ */ jsx("div", { className: "vmeta-sidebar__collapsed-items", children: menuItems.map((item) => /* @__PURE__ */ jsx(VmetaMenuButton, { item, active: isMenuItemActive(item, activePath), collapsed: true, onNavigate: navigate }, item.key)) }) : visibleGroups.map((group) => {
|
|
97
113
|
const open = openKeys.includes(group.key);
|
|
98
114
|
return /* @__PURE__ */ jsxs("section", { className: "vmeta-menu-group", children: [
|
|
@@ -103,14 +119,14 @@ function VmetaSidebar({
|
|
|
103
119
|
className: "vmeta-menu-group__toggle",
|
|
104
120
|
onClick: () => toggleGroup(group.key),
|
|
105
121
|
"aria-expanded": open,
|
|
106
|
-
"aria-controls":
|
|
122
|
+
"aria-controls": `${groupIdPrefix}-${group.key}`,
|
|
107
123
|
children: [
|
|
108
124
|
/* @__PURE__ */ jsx("span", { children: group.label }),
|
|
109
125
|
/* @__PURE__ */ jsx("span", { className: `vmeta-menu-group__chevron ${open ? "is-open" : ""}`, "aria-hidden": "true", children: "\u2304" })
|
|
110
126
|
]
|
|
111
127
|
}
|
|
112
128
|
),
|
|
113
|
-
/* @__PURE__ */ jsx("div", { id:
|
|
129
|
+
/* @__PURE__ */ jsx("div", { id: `${groupIdPrefix}-${group.key}`, className: `vmeta-menu-group__items ${open ? "is-open" : ""}`, children: /* @__PURE__ */ jsx("div", { children: group.children.map((item) => /* @__PURE__ */ jsx(VmetaMenuButton, { item, active: isMenuItemActive(item, activePath), onNavigate: navigate }, item.key)) }) })
|
|
114
130
|
] }, group.key);
|
|
115
131
|
}) }),
|
|
116
132
|
footer && !collapsed && /* @__PURE__ */ jsx("div", { className: "vmeta-sidebar__footer", children: footer }),
|
|
@@ -130,7 +146,7 @@ function VmetaSidebar({
|
|
|
130
146
|
onClick: () => onCollapsedChange?.(!collapsed),
|
|
131
147
|
"aria-label": collapsed ? "Mo rong thanh dieu huong" : "Thu gon thanh dieu huong",
|
|
132
148
|
title: collapsed ? "Mo rong menu" : "Thu gon menu",
|
|
133
|
-
children: /* @__PURE__ */ jsx(
|
|
149
|
+
children: /* @__PURE__ */ jsx(VmetaCollapseIcon, { collapsed })
|
|
134
150
|
}
|
|
135
151
|
)
|
|
136
152
|
] });
|
|
@@ -171,15 +187,24 @@ function VmetaAppShell({
|
|
|
171
187
|
canAccess,
|
|
172
188
|
onNavigate
|
|
173
189
|
}) {
|
|
174
|
-
const [collapsed, setCollapsed] = useState(
|
|
190
|
+
const [collapsed, setCollapsed] = useState(false);
|
|
175
191
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
192
|
+
useEffect(() => {
|
|
193
|
+
setCollapsed(readStoredBoolean(storageKey, false));
|
|
194
|
+
}, [storageKey]);
|
|
176
195
|
const updateCollapsed = (value) => {
|
|
177
196
|
setCollapsed(value);
|
|
178
197
|
writeStorage(storageKey, String(value));
|
|
179
198
|
};
|
|
180
199
|
const navigate = (item) => {
|
|
181
200
|
setMenuOpen(false);
|
|
182
|
-
onNavigate
|
|
201
|
+
if (onNavigate) {
|
|
202
|
+
onNavigate(item);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (typeof window === "undefined") return;
|
|
206
|
+
if (item.external) window.open(item.href, "_blank", "noopener,noreferrer");
|
|
207
|
+
else window.location.assign(item.href);
|
|
183
208
|
};
|
|
184
209
|
return /* @__PURE__ */ jsxs("main", { className: `vmeta-app-shell ${collapsed ? "is-collapsed" : ""}`, children: [
|
|
185
210
|
/* @__PURE__ */ jsx(
|
|
@@ -225,6 +250,7 @@ function VmetaAppShell({
|
|
|
225
250
|
/* @__PURE__ */ jsx("button", { type: "button", className: "vmeta-app-shell__menu-button", onClick: () => setMenuOpen(true), "aria-label": "Mo menu dieu huong", children: "\u2630" }),
|
|
226
251
|
header.leading,
|
|
227
252
|
/* @__PURE__ */ jsxs("div", { className: "vmeta-app-shell__title", children: [
|
|
253
|
+
/* @__PURE__ */ jsx("p", { className: "vmeta-app-shell__app-name", children: app.appName }),
|
|
228
254
|
header.eyebrow && /* @__PURE__ */ jsx("p", { children: header.eyebrow }),
|
|
229
255
|
/* @__PURE__ */ jsx("h1", { children: header.title }),
|
|
230
256
|
header.description && /* @__PURE__ */ jsx("span", { children: header.description })
|
|
@@ -238,6 +264,8 @@ function VmetaAppShell({
|
|
|
238
264
|
}
|
|
239
265
|
export {
|
|
240
266
|
VmetaAppShell,
|
|
267
|
+
VmetaBrandMark,
|
|
268
|
+
VmetaCollapseIcon,
|
|
241
269
|
VmetaSidebar,
|
|
242
270
|
isMenuItemActive
|
|
243
271
|
};
|
package/dist/styles.css
CHANGED
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
.vmeta-app-shell__header { position: sticky; top: 0; z-index: var(--z-header, 40); min-height: 82px; display: flex; align-items: center; justify-content: space-between; gap: 20px; padding: 14px var(--page-gutter-desktop, 28px); border-bottom: 1px solid var(--color-border); background: color-mix(in srgb, var(--color-surface) 94%, transparent); box-shadow: var(--shadow-sm); backdrop-filter: blur(16px); }
|
|
8
8
|
.vmeta-app-shell__header-copy { min-width: 0; display: flex; align-items: center; gap: 12px; }
|
|
9
9
|
.vmeta-app-shell__title { min-width: 0; }
|
|
10
|
+
.vmeta-app-shell__app-name { margin: 0 0 2px; color: var(--color-primary); font-size: 10px; font-weight: 800; letter-spacing: .12em; line-height: 1.2; text-transform: uppercase; }
|
|
10
11
|
.vmeta-app-shell__title p { margin: 0; color: var(--color-primary); font-size: 10px; font-weight: 750; letter-spacing: .13em; text-transform: uppercase; }
|
|
12
|
+
.vmeta-app-shell__title .vmeta-app-shell__app-name { margin-bottom: 2px; font-size: 9px; font-weight: 800; }
|
|
11
13
|
.vmeta-app-shell__title h1 { margin: 3px 0 0; color: var(--color-ink); font-size: clamp(20px, 2.5vw, 26px); line-height: 1.2; letter-spacing: -.035em; }
|
|
12
14
|
.vmeta-app-shell__title span { display: block; max-width: 740px; margin-top: 4px; overflow: hidden; color: var(--color-muted); font-size: 12px; text-overflow: ellipsis; white-space: nowrap; }
|
|
13
15
|
.vmeta-app-shell__header-actions { flex: 0 0 auto; display: flex; align-items: center; gap: 8px; }
|
|
@@ -15,10 +17,10 @@
|
|
|
15
17
|
.vmeta-app-shell__content { min-width: 0; padding: var(--page-gutter-tablet, 20px) var(--page-gutter-desktop, 28px) 48px; }
|
|
16
18
|
.vmeta-app-shell__overlay { display: none; }
|
|
17
19
|
.vmeta-sidebar { height: 100%; min-height: 0; display: flex; flex-direction: column; overflow: hidden; border-right: 1px solid var(--color-border); color: var(--color-ink); background: var(--color-surface); box-shadow: var(--shadow-md); }
|
|
18
|
-
.vmeta-sidebar__brand { min-height: 82px; display: flex; align-items: center;
|
|
20
|
+
.vmeta-sidebar__brand { min-height: 82px; display: flex; align-items: center; justify-content: center; margin: 10px; padding: 10px 12px; border: 1px solid var(--color-border); border-radius: 20px; color: inherit; text-decoration: none; background: var(--color-surface); box-shadow: 0 5px 18px rgb(7 19 13 / 5%); transition: border-color var(--motion-fast), box-shadow var(--motion-fast), transform var(--motion-fast); }
|
|
19
21
|
.vmeta-sidebar__brand:hover { border-color: color-mix(in srgb, var(--color-primary) 25%, var(--color-border)); box-shadow: var(--shadow-sm); transform: translateY(-1px); }
|
|
20
22
|
.vmeta-sidebar__logo { width: 42px; height: 42px; flex: 0 0 42px; object-fit: contain; }
|
|
21
|
-
.vmeta-sidebar__mark { width: 42px; height: 42px; flex: 0 0 42px; display:
|
|
23
|
+
.vmeta-sidebar__mark { width: 42px; height: 42px; flex: 0 0 42px; display: block; color: var(--color-primary); }
|
|
22
24
|
.vmeta-sidebar__brand-copy { min-width: 0; line-height: 1.15; }
|
|
23
25
|
.vmeta-sidebar__brand-copy strong, .vmeta-sidebar__brand-copy small { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
24
26
|
.vmeta-sidebar__brand-copy strong { color: var(--color-primary); font-size: 16px; font-weight: 800; letter-spacing: -.025em; }
|
|
@@ -54,7 +56,7 @@
|
|
|
54
56
|
.vmeta-sidebar__account-actions { flex: 0 0 auto; }
|
|
55
57
|
.vmeta-sidebar__collapse { min-height: 46px; display: grid; place-items: center; border: 0; border-top: 1px solid var(--color-border); color: var(--color-muted); background: transparent; font-size: 22px; }
|
|
56
58
|
.vmeta-sidebar__collapse:hover { color: var(--color-primary); background: var(--color-primary-soft); }
|
|
57
|
-
.vmeta-sidebar.is-collapsed .vmeta-sidebar__brand {
|
|
59
|
+
.vmeta-sidebar.is-collapsed .vmeta-sidebar__brand { min-height: 60px; margin: 9px; padding: 8px; }
|
|
58
60
|
.vmeta-sidebar.is-collapsed .vmeta-sidebar__logo, .vmeta-sidebar.is-collapsed .vmeta-sidebar__mark { width: 36px; height: 36px; flex-basis: 36px; }
|
|
59
61
|
.vmeta-sidebar.is-collapsed .vmeta-sidebar__nav { padding-inline: 9px; }
|
|
60
62
|
.vmeta-sidebar__collapsed-items { display: grid; gap: 4px; }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leduchai/vmeta-ui-shell",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Application shell, sidebar, menu va header dung chung cua VMETA.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"build": "tsup src/index.tsx --format esm,cjs --dts --clean --external react --external react-dom && node ../../scripts/copy-file.mjs src/styles.css dist/styles.css"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@leduchai/vmeta-ui-tokens": "0.1.
|
|
24
|
+
"@leduchai/vmeta-ui-tokens": "0.1.6"
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|
|
27
27
|
"react": ">=18 <20",
|