@nubitio/admin 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Johan Guerreros
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,55 @@
1
+ # @nubitio/admin
2
+
3
+ Admin shell layout for Nubit apps: responsive sidebar with nested menus, header with action slots, and screen-size utilities.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @nubitio/admin
9
+ ```
10
+
11
+ ## Peer dependencies
12
+
13
+ ```json
14
+ "react": "^19",
15
+ "react-dom": "^19",
16
+ "react-router-dom": "^6"
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ ```tsx
22
+ import { AdminShell } from '@nubitio/admin';
23
+ import '@nubitio/admin/style.css';
24
+
25
+ const menu = [
26
+ { text: 'Dashboard', icon: 'ph ph-house', path: '/' },
27
+ {
28
+ text: 'Catalog',
29
+ icon: 'ph ph-package',
30
+ items: [
31
+ { text: 'Products', path: '/products' },
32
+ { text: 'Categories', path: '/categories' },
33
+ ],
34
+ },
35
+ ];
36
+
37
+ export function App() {
38
+ return (
39
+ <AdminShell title="My Admin" menuItems={menu}>
40
+ {/* routed content */}
41
+ </AdminShell>
42
+ );
43
+ }
44
+ ```
45
+
46
+ ## Exports
47
+
48
+ - `AdminShell` — full layout: sidebar + header + content area
49
+ - `AdminHeader` — standalone header with action slots
50
+ - `AdminSidebarMenu` — standalone sidebar menu
51
+ - `useScreenSize` / `useScreenSizeClass` — responsive breakpoint helpers
52
+
53
+ ## License
54
+
55
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,390 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region \0rolldown/runtime.js
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
+ key = keys[i];
12
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
13
+ get: ((k) => from[k]).bind(null, key),
14
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
+ });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
20
+ value: mod,
21
+ enumerable: true
22
+ }) : target, mod));
23
+ //#endregion
24
+ let react = require("react");
25
+ react = __toESM(react, 1);
26
+ let react_router_dom = require("react-router-dom");
27
+ let _nubitio_ui = require("@nubitio/ui");
28
+ let react_jsx_runtime = require("react/jsx-runtime");
29
+ //#region packages/admin/AdminHeader.tsx
30
+ function ActionPopover({ action }) {
31
+ const { open, toggle, setOpen, containerRef } = (0, _nubitio_ui.useFloatingPanel)();
32
+ if (action.onClick) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
33
+ className: "nb-admin-messages",
34
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.IconButton, {
35
+ icon: action.icon,
36
+ label: action.label,
37
+ onClick: action.onClick
38
+ }), !!action.badge && action.badge > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.Badge, {
39
+ variant: "danger",
40
+ size: "sm",
41
+ pill: true,
42
+ "aria-label": `${action.badge} ${action.label}`,
43
+ children: action.badge > 99 ? "99+" : action.badge
44
+ })]
45
+ });
46
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
47
+ className: "nb-admin-header-popover",
48
+ ref: containerRef,
49
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
50
+ className: "nb-admin-messages",
51
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.IconButton, {
52
+ icon: action.icon,
53
+ label: action.label,
54
+ "aria-expanded": open,
55
+ onClick: toggle
56
+ }), !!action.badge && action.badge > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.Badge, {
57
+ variant: "danger",
58
+ size: "sm",
59
+ pill: true,
60
+ "aria-label": `${action.badge} ${action.label}`,
61
+ children: action.badge > 99 ? "99+" : action.badge
62
+ })]
63
+ }), open && action.renderPanel && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
64
+ className: "nb-admin-header-popover__panel",
65
+ role: "dialog",
66
+ "aria-label": action.label,
67
+ children: action.renderPanel({ close: () => setOpen(false) })
68
+ })]
69
+ });
70
+ }
71
+ function UserMenuPopover({ renderUserMenu }) {
72
+ const { open, toggle, setOpen, containerRef } = (0, _nubitio_ui.useFloatingPanel)();
73
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
74
+ className: "nb-admin-header-popover",
75
+ ref: containerRef,
76
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.IconButton, {
77
+ icon: "ph ph-user-circle",
78
+ label: "User menu",
79
+ "aria-expanded": open,
80
+ onClick: toggle
81
+ }), open && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
82
+ className: "nb-admin-header-popover__panel",
83
+ role: "dialog",
84
+ "aria-label": "User menu",
85
+ children: renderUserMenu({ close: () => setOpen(false) })
86
+ })]
87
+ });
88
+ }
89
+ const AdminHeader = ({ title, menuToggleEnabled, toggleMenu, className, actions = [], renderUserMenu, renderThemeSwitcher }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("header", {
90
+ className: ["nb-admin-header-component", className].filter(Boolean).join(" "),
91
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
92
+ className: "nb-admin-header-toolbar",
93
+ role: "toolbar",
94
+ "aria-label": "Main toolbar",
95
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
96
+ className: "nb-admin-header-toolbar__before",
97
+ children: [menuToggleEnabled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.IconButton, {
98
+ className: "nb-admin-menu-button",
99
+ icon: "ph ph-list",
100
+ label: "Toggle menu",
101
+ onClick: toggleMenu
102
+ }), title && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
103
+ className: "nb-admin-header-title",
104
+ children: title
105
+ })]
106
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
107
+ className: "nb-admin-header-toolbar__after",
108
+ children: [
109
+ renderThemeSwitcher?.(),
110
+ actions.map((action) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ActionPopover, { action }, action.id)),
111
+ renderUserMenu && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UserMenuPopover, { renderUserMenu })
112
+ ]
113
+ })]
114
+ })
115
+ });
116
+ //#endregion
117
+ //#region packages/admin/useScreenSize.ts
118
+ let handlers = [];
119
+ const xSmallMedia = window.matchMedia("(max-width: 575.98px)");
120
+ const smallMedia = window.matchMedia("(min-width: 576px) and (max-width: 991.98px)");
121
+ const mediumMedia = window.matchMedia("(min-width: 992px) and (max-width: 1199.98px)");
122
+ const largeMedia = window.matchMedia("(min-width: 1200px)");
123
+ let debounceTimer = null;
124
+ const notifyHandlers = () => {
125
+ if (debounceTimer !== null) clearTimeout(debounceTimer);
126
+ debounceTimer = setTimeout(() => {
127
+ handlers.forEach((handler) => handler());
128
+ }, 50);
129
+ };
130
+ [
131
+ xSmallMedia,
132
+ smallMedia,
133
+ mediumMedia,
134
+ largeMedia
135
+ ].forEach((media) => {
136
+ media.addEventListener("change", (e) => {
137
+ if (e.matches) notifyHandlers();
138
+ });
139
+ });
140
+ const subscribe = (handler) => handlers.push(handler);
141
+ const unsubscribe = (handler) => {
142
+ handlers = handlers.filter((item) => item !== handler);
143
+ };
144
+ function getScreenSize() {
145
+ return {
146
+ isXSmall: xSmallMedia.matches,
147
+ isSmall: smallMedia.matches,
148
+ isMedium: mediumMedia.matches,
149
+ isLarge: largeMedia.matches
150
+ };
151
+ }
152
+ const useScreenSize = () => {
153
+ const [screenSize, setScreenSize] = (0, react.useState)(getScreenSize());
154
+ const onSizeChanged = (0, react.useCallback)(() => setScreenSize(getScreenSize()), []);
155
+ (0, react.useEffect)(() => {
156
+ subscribe(onSizeChanged);
157
+ return () => unsubscribe(onSizeChanged);
158
+ }, [onSizeChanged]);
159
+ return screenSize;
160
+ };
161
+ const useScreenSizeClass = () => {
162
+ const { isLarge, isMedium, isSmall } = useScreenSize();
163
+ if (isLarge) return "screen-large";
164
+ if (isMedium) return "screen-medium";
165
+ if (isSmall) return "screen-small";
166
+ return "screen-x-small";
167
+ };
168
+ //#endregion
169
+ //#region packages/admin/AdminSidebarMenu.tsx
170
+ const normalizeRoute = (path) => {
171
+ if (!path) return "";
172
+ const normalized = path.startsWith("/") ? path : `/${path}`;
173
+ return normalized.length > 1 ? normalized.replace(/\/+$/, "") : normalized;
174
+ };
175
+ const getIconClassName = (icon) => {
176
+ if (!icon) return "";
177
+ return icon.includes(" ") ? icon : `ph ph-${icon}`;
178
+ };
179
+ const AdminSidebarMenu = ({ items, compactMode = false, selectedItemChanged, openMenu, footer }) => {
180
+ const [expandedItemKeys, setExpandedItemKeys] = (0, react.useState)(/* @__PURE__ */ new Set());
181
+ const { isLarge } = useScreenSize();
182
+ const location = (0, react_router_dom.useLocation)();
183
+ (0, react.useEffect)(() => {
184
+ setExpandedItemKeys(/* @__PURE__ */ new Set());
185
+ }, [isLarge, items]);
186
+ const activePath = (0, react.useMemo)(() => {
187
+ const flattenPaths = (menuItems) => menuItems.flatMap((item) => [normalizeRoute(item.path), ...item.items?.map((sub) => normalizeRoute(sub.path)) ?? []]).filter(Boolean);
188
+ const requestedPath = normalizeRoute(location.pathname);
189
+ const allPaths = flattenPaths(items);
190
+ return allPaths.find((path) => path === requestedPath) ?? allPaths.filter((path) => requestedPath.startsWith(path) && path !== "/").sort((a, b) => b.length - a.length)[0];
191
+ }, [location.pathname, items]);
192
+ const isSelected = (0, react.useCallback)((path) => normalizeRoute(path) === activePath, [activePath]);
193
+ const parentContainsSelected = (0, react.useCallback)((item) => item.items?.some((sub) => isSelected(sub.path)) ?? false, [isSelected]);
194
+ (0, react.useEffect)(() => {
195
+ const activeParent = items.find((item) => parentContainsSelected(item));
196
+ if (!activeParent || compactMode) return;
197
+ const activeParentKey = activeParent.path || activeParent.text;
198
+ setExpandedItemKeys((prev) => {
199
+ if (prev.has(activeParentKey)) return prev;
200
+ const next = new Set(prev);
201
+ next.add(activeParentKey);
202
+ return next;
203
+ });
204
+ }, [
205
+ compactMode,
206
+ items,
207
+ parentContainsSelected
208
+ ]);
209
+ const toggleExpanded = (0, react.useCallback)((key) => {
210
+ setExpandedItemKeys((prev) => {
211
+ const next = new Set(prev);
212
+ if (next.has(key)) next.delete(key);
213
+ else next.add(key);
214
+ return next;
215
+ });
216
+ }, []);
217
+ const handleSelect = (0, react.useCallback)((path, selected, event) => {
218
+ selectedItemChanged({
219
+ path,
220
+ selected,
221
+ event
222
+ });
223
+ }, [selectedItemChanged]);
224
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
225
+ className: `nb-admin-menu${compactMode ? " compact" : ""}`,
226
+ onPointerDown: openMenu,
227
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
228
+ className: "nb-admin-menu-container theme-dependent",
229
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("nav", {
230
+ className: "nb-admin-menu__nav",
231
+ "aria-label": "Main navigation",
232
+ children: items.map((item) => {
233
+ const selected = isSelected(item.path);
234
+ const containsSelected = parentContainsSelected(item);
235
+ const itemKey = item.path || item.text;
236
+ const hasChildren = (item.items?.length ?? 0) > 0;
237
+ const expanded = !compactMode && hasChildren && expandedItemKeys.has(itemKey);
238
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
239
+ className: `nb-admin-menu__section${containsSelected ? " has-selected-child" : ""}`,
240
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
241
+ className: `nb-admin-menu__item nb-admin-menu__item--parent${selected ? " is-selected" : ""}`,
242
+ type: "button",
243
+ "aria-current": selected ? "page" : void 0,
244
+ "aria-expanded": hasChildren ? expanded : void 0,
245
+ onClick: (event) => {
246
+ if (hasChildren) {
247
+ toggleExpanded(itemKey);
248
+ return;
249
+ }
250
+ handleSelect(item.path, selected, event);
251
+ },
252
+ children: [
253
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
254
+ className: `nb-admin-menu__icon ${getIconClassName(item.icon)}`,
255
+ "aria-hidden": "true"
256
+ }),
257
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
258
+ className: "nb-admin-menu__text",
259
+ children: item.text
260
+ }),
261
+ hasChildren && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
262
+ className: `nb-admin-menu__chevron${expanded ? " is-expanded" : ""}`,
263
+ "aria-hidden": "true"
264
+ })
265
+ ]
266
+ }), expanded && (item.items?.length ?? 0) > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
267
+ className: "nb-admin-menu__children",
268
+ children: item.items.map((subItem) => {
269
+ const childSelected = isSelected(subItem.path);
270
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
271
+ className: `nb-admin-menu__item nb-admin-menu__item--child${childSelected ? " is-selected" : ""}`,
272
+ type: "button",
273
+ "aria-current": childSelected ? "page" : void 0,
274
+ onClick: (event) => handleSelect(subItem.path, childSelected, event),
275
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
276
+ className: "nb-admin-menu__text",
277
+ children: subItem.text
278
+ })
279
+ }, subItem.path || subItem.text);
280
+ })
281
+ })]
282
+ }, itemKey);
283
+ })
284
+ })
285
+ }), footer && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("footer", {
286
+ className: "nb-admin-footer",
287
+ children: footer
288
+ })]
289
+ });
290
+ };
291
+ //#endregion
292
+ //#region packages/admin/AdminShell.tsx
293
+ const AdminShell = ({ title, menuItems, headerActions, renderUserMenu, renderThemeSwitcher, footer, children }) => {
294
+ const navigate = (0, react_router_dom.useNavigate)();
295
+ const { isXSmall, isLarge } = useScreenSize();
296
+ const [menuStatus, setMenuStatus] = (0, react.useState)(null);
297
+ const getDefaultMenuOpenState = (0, react.useCallback)(() => isLarge ? 2 : 1, [isLarge]);
298
+ const getMenuOpenState = (0, react.useCallback)((status) => status === null ? getDefaultMenuOpenState() : status, [getDefaultMenuOpenState]);
299
+ const getMenuStatus = (0, react.useCallback)((status) => status === getDefaultMenuOpenState() ? null : status, [getDefaultMenuOpenState]);
300
+ const changeMenuStatus = (0, react.useCallback)((reducerFn) => {
301
+ setMenuStatus((prev) => getMenuStatus(reducerFn(getMenuOpenState(prev)) ?? prev));
302
+ }, [getMenuOpenState, getMenuStatus]);
303
+ const toggleMenu = (0, react.useCallback)((event) => {
304
+ changeMenuStatus((prev) => prev === 1 ? 2 : 1);
305
+ event.stopPropagation();
306
+ }, [changeMenuStatus]);
307
+ const temporaryOpenMenu = (0, react.useCallback)(() => {
308
+ changeMenuStatus((prev) => prev === 1 ? 3 : null);
309
+ }, [changeMenuStatus]);
310
+ const closeMenuFromOverlay = (0, react.useCallback)(() => {
311
+ changeMenuStatus((prev) => prev !== 1 && !isLarge ? 1 : null);
312
+ }, [isLarge, changeMenuStatus]);
313
+ const onNavigationChanged = (0, react.useCallback)(({ path, event, selected }) => {
314
+ if (getMenuOpenState(menuStatus) === 1 || !path || selected) {
315
+ event?.preventDefault();
316
+ return;
317
+ }
318
+ navigate(path);
319
+ if (!isLarge || menuStatus === 3) {
320
+ setMenuStatus(getMenuStatus(1));
321
+ event?.stopPropagation();
322
+ }
323
+ }, [
324
+ navigate,
325
+ menuStatus,
326
+ isLarge,
327
+ getMenuOpenState,
328
+ getMenuStatus
329
+ ]);
330
+ (0, react.useEffect)(() => {
331
+ changeMenuStatus(() => menuStatus);
332
+ }, [
333
+ isLarge,
334
+ changeMenuStatus,
335
+ menuStatus
336
+ ]);
337
+ const menuOpenState = getMenuOpenState(menuStatus);
338
+ const isMenuOpen = menuOpenState !== 1;
339
+ const isCompact = menuOpenState === 1;
340
+ const bodyClassName = [
341
+ "nb-admin-shell__body nb-admin-layout-body",
342
+ isMenuOpen ? "is-open" : "is-closed",
343
+ isCompact ? "is-compact" : "",
344
+ isLarge ? "is-large" : "is-overlay",
345
+ isXSmall ? "is-xsmall" : ""
346
+ ].filter(Boolean).join(" ");
347
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
348
+ className: "nb-admin-shell",
349
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(AdminHeader, {
350
+ className: "nb-admin-layout-header",
351
+ menuToggleEnabled: true,
352
+ toggleMenu,
353
+ title,
354
+ actions: headerActions,
355
+ renderUserMenu,
356
+ renderThemeSwitcher
357
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
358
+ className: bodyClassName,
359
+ children: [
360
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("aside", {
361
+ className: "nb-admin-shell__panel",
362
+ "aria-label": "Main menu",
363
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AdminSidebarMenu, {
364
+ items: menuItems,
365
+ compactMode: isCompact,
366
+ selectedItemChanged: onNavigationChanged,
367
+ openMenu: temporaryOpenMenu,
368
+ footer
369
+ })
370
+ }),
371
+ !isLarge && isMenuOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
372
+ className: "nb-admin-shell__scrim",
373
+ type: "button",
374
+ "aria-label": "Close menu",
375
+ onClick: closeMenuFromOverlay
376
+ }),
377
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("main", {
378
+ className: "nb-admin-shell__content content",
379
+ children
380
+ })
381
+ ]
382
+ })]
383
+ });
384
+ };
385
+ //#endregion
386
+ exports.AdminHeader = AdminHeader;
387
+ exports.AdminShell = AdminShell;
388
+ exports.AdminSidebarMenu = AdminSidebarMenu;
389
+ exports.useScreenSize = useScreenSize;
390
+ exports.useScreenSizeClass = useScreenSizeClass;
@@ -0,0 +1,98 @@
1
+ import React from "react";
2
+
3
+ //#region packages/admin/AdminHeader.d.ts
4
+ interface AdminHeaderAction {
5
+ id: string;
6
+ icon: string;
7
+ label: string;
8
+ badge?: number;
9
+ /** When provided, clicking the button calls this instead of opening a panel. */
10
+ onClick?: () => void;
11
+ renderPanel?: (props: {
12
+ close: () => void;
13
+ }) => React.ReactNode;
14
+ }
15
+ interface AdminHeaderProps {
16
+ title?: string;
17
+ menuToggleEnabled?: boolean;
18
+ toggleMenu?: (e: React.MouseEvent<HTMLButtonElement>) => void;
19
+ className?: string;
20
+ actions?: AdminHeaderAction[];
21
+ renderUserMenu?: (props: {
22
+ close: () => void;
23
+ }) => React.ReactNode;
24
+ renderThemeSwitcher?: () => React.ReactNode;
25
+ }
26
+ declare const AdminHeader: ({
27
+ title,
28
+ menuToggleEnabled,
29
+ toggleMenu,
30
+ className,
31
+ actions,
32
+ renderUserMenu,
33
+ renderThemeSwitcher
34
+ }: AdminHeaderProps) => React.JSX.Element;
35
+ //#endregion
36
+ //#region packages/admin/AdminSidebarMenu.d.ts
37
+ interface AdminMenuSubItem {
38
+ text: string;
39
+ path: string;
40
+ }
41
+ interface AdminMenuItem {
42
+ text: string;
43
+ path?: string;
44
+ icon?: string;
45
+ items?: AdminMenuSubItem[];
46
+ }
47
+ interface AdminSidebarMenuSelectEvent {
48
+ path?: string;
49
+ selected: boolean;
50
+ event: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>;
51
+ }
52
+ interface AdminSidebarMenuProps {
53
+ items: AdminMenuItem[];
54
+ compactMode?: boolean;
55
+ selectedItemChanged: (e: AdminSidebarMenuSelectEvent) => void;
56
+ openMenu?: (e: React.PointerEvent) => void;
57
+ footer?: React.ReactNode;
58
+ }
59
+ declare const AdminSidebarMenu: ({
60
+ items,
61
+ compactMode,
62
+ selectedItemChanged,
63
+ openMenu,
64
+ footer
65
+ }: AdminSidebarMenuProps) => React.JSX.Element;
66
+ //#endregion
67
+ //#region packages/admin/AdminShell.d.ts
68
+ interface AdminShellProps {
69
+ title?: string;
70
+ menuItems: AdminMenuItem[];
71
+ headerActions?: AdminHeaderAction[];
72
+ renderUserMenu?: (props: {
73
+ close: () => void;
74
+ }) => React.ReactNode;
75
+ renderThemeSwitcher?: () => React.ReactNode;
76
+ footer?: React.ReactNode;
77
+ children: React.ReactNode;
78
+ }
79
+ declare const AdminShell: ({
80
+ title,
81
+ menuItems,
82
+ headerActions,
83
+ renderUserMenu,
84
+ renderThemeSwitcher,
85
+ footer,
86
+ children
87
+ }: AdminShellProps) => React.JSX.Element;
88
+ //#endregion
89
+ //#region packages/admin/useScreenSize.d.ts
90
+ declare const useScreenSize: () => {
91
+ isXSmall: boolean;
92
+ isSmall: boolean;
93
+ isMedium: boolean;
94
+ isLarge: boolean;
95
+ };
96
+ declare const useScreenSizeClass: () => "screen-large" | "screen-medium" | "screen-small" | "screen-x-small";
97
+ //#endregion
98
+ export { AdminHeader, type AdminHeaderAction, type AdminHeaderProps, type AdminMenuItem, type AdminMenuSubItem, AdminShell, type AdminShellProps, AdminSidebarMenu, type AdminSidebarMenuProps, type AdminSidebarMenuSelectEvent, useScreenSize, useScreenSizeClass };
@@ -0,0 +1,98 @@
1
+ import React from "react";
2
+
3
+ //#region packages/admin/AdminHeader.d.ts
4
+ interface AdminHeaderAction {
5
+ id: string;
6
+ icon: string;
7
+ label: string;
8
+ badge?: number;
9
+ /** When provided, clicking the button calls this instead of opening a panel. */
10
+ onClick?: () => void;
11
+ renderPanel?: (props: {
12
+ close: () => void;
13
+ }) => React.ReactNode;
14
+ }
15
+ interface AdminHeaderProps {
16
+ title?: string;
17
+ menuToggleEnabled?: boolean;
18
+ toggleMenu?: (e: React.MouseEvent<HTMLButtonElement>) => void;
19
+ className?: string;
20
+ actions?: AdminHeaderAction[];
21
+ renderUserMenu?: (props: {
22
+ close: () => void;
23
+ }) => React.ReactNode;
24
+ renderThemeSwitcher?: () => React.ReactNode;
25
+ }
26
+ declare const AdminHeader: ({
27
+ title,
28
+ menuToggleEnabled,
29
+ toggleMenu,
30
+ className,
31
+ actions,
32
+ renderUserMenu,
33
+ renderThemeSwitcher
34
+ }: AdminHeaderProps) => React.JSX.Element;
35
+ //#endregion
36
+ //#region packages/admin/AdminSidebarMenu.d.ts
37
+ interface AdminMenuSubItem {
38
+ text: string;
39
+ path: string;
40
+ }
41
+ interface AdminMenuItem {
42
+ text: string;
43
+ path?: string;
44
+ icon?: string;
45
+ items?: AdminMenuSubItem[];
46
+ }
47
+ interface AdminSidebarMenuSelectEvent {
48
+ path?: string;
49
+ selected: boolean;
50
+ event: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>;
51
+ }
52
+ interface AdminSidebarMenuProps {
53
+ items: AdminMenuItem[];
54
+ compactMode?: boolean;
55
+ selectedItemChanged: (e: AdminSidebarMenuSelectEvent) => void;
56
+ openMenu?: (e: React.PointerEvent) => void;
57
+ footer?: React.ReactNode;
58
+ }
59
+ declare const AdminSidebarMenu: ({
60
+ items,
61
+ compactMode,
62
+ selectedItemChanged,
63
+ openMenu,
64
+ footer
65
+ }: AdminSidebarMenuProps) => React.JSX.Element;
66
+ //#endregion
67
+ //#region packages/admin/AdminShell.d.ts
68
+ interface AdminShellProps {
69
+ title?: string;
70
+ menuItems: AdminMenuItem[];
71
+ headerActions?: AdminHeaderAction[];
72
+ renderUserMenu?: (props: {
73
+ close: () => void;
74
+ }) => React.ReactNode;
75
+ renderThemeSwitcher?: () => React.ReactNode;
76
+ footer?: React.ReactNode;
77
+ children: React.ReactNode;
78
+ }
79
+ declare const AdminShell: ({
80
+ title,
81
+ menuItems,
82
+ headerActions,
83
+ renderUserMenu,
84
+ renderThemeSwitcher,
85
+ footer,
86
+ children
87
+ }: AdminShellProps) => React.JSX.Element;
88
+ //#endregion
89
+ //#region packages/admin/useScreenSize.d.ts
90
+ declare const useScreenSize: () => {
91
+ isXSmall: boolean;
92
+ isSmall: boolean;
93
+ isMedium: boolean;
94
+ isLarge: boolean;
95
+ };
96
+ declare const useScreenSizeClass: () => "screen-large" | "screen-medium" | "screen-small" | "screen-x-small";
97
+ //#endregion
98
+ export { AdminHeader, type AdminHeaderAction, type AdminHeaderProps, type AdminMenuItem, type AdminMenuSubItem, AdminShell, type AdminShellProps, AdminSidebarMenu, type AdminSidebarMenuProps, type AdminSidebarMenuSelectEvent, useScreenSize, useScreenSizeClass };