@deenruv/admin-dashboard 1.0.17-dev.27 → 1.0.17-dev.29
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.
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const normalizePath = (path) => `/${path.split('?')[0].split('#')[0].split('/').filter(Boolean).join('/')}`;
|
|
2
|
+
export const buildMenuBreadcrumbs = ({ pathname, groups, links, extensions, fallbackLabels, }) => {
|
|
3
|
+
const normalizedPath = normalizePath(pathname);
|
|
4
|
+
const matchingLink = links.find((link) => normalizePath(link.href) === normalizedPath);
|
|
5
|
+
if (matchingLink?.permitted) {
|
|
6
|
+
const matchingGroup = groups.find((group) => group.id === matchingLink.groupId && group.permitted);
|
|
7
|
+
return [
|
|
8
|
+
...(extensions.permitted
|
|
9
|
+
? [{ label: extensions.label, href: normalizePath(extensions.href), current: false }]
|
|
10
|
+
: []),
|
|
11
|
+
...(matchingGroup ? [{ label: matchingGroup.label, current: false }] : []),
|
|
12
|
+
{ label: matchingLink.label, current: true },
|
|
13
|
+
];
|
|
14
|
+
}
|
|
15
|
+
const segments = normalizedPath.split('/').filter(Boolean);
|
|
16
|
+
const fallbackCrumbs = segments
|
|
17
|
+
.map((segment, index) => ({ label: fallbackLabels[index] ?? segment }))
|
|
18
|
+
.filter((crumb) => crumb.label !== '');
|
|
19
|
+
return fallbackCrumbs.map((crumb, index) => ({
|
|
20
|
+
...crumb,
|
|
21
|
+
current: index === fallbackCrumbs.length - 1,
|
|
22
|
+
}));
|
|
23
|
+
};
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import React, { useMemo } from 'react';
|
|
3
3
|
import { NavLink, useMatches, useNavigate } from 'react-router';
|
|
4
|
-
import { Breadcrumb, BreadcrumbItem, BreadcrumbList,
|
|
4
|
+
import { Breadcrumb, BreadcrumbItem, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, cn, dashToCamelCase, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, ScrollArea, Sheet, SheetContent, SheetDescription, SheetTitle, TooltipProvider, Routes, useGlobalSearch, usePluginStore, useServer, useSettings, useTranslation, } from '@deenruv/react-ui-devkit';
|
|
5
5
|
import { MenuIcon, Moon, PanelLeftClose, PanelLeftOpen, SearchIcon, Slash, Sun, SunMoon } from 'lucide-react';
|
|
6
|
-
import { canAccessAdminItem, useAdminAccess } from "../../access/index.js";
|
|
6
|
+
import { adminNavigationGroups, canAccessAdminItem, useAdminAccess } from "../../access/index.js";
|
|
7
7
|
import { ChannelSwitcher } from './ChannelSwitcher.js';
|
|
8
|
+
import { buildMenuBreadcrumbs } from './Breadcrumbs.js';
|
|
8
9
|
import { LanguagesDropdown } from './LanguagesDropdown.js';
|
|
9
10
|
import { Notifications } from './Notifications.js';
|
|
10
11
|
import { SidebarContent } from './SidebarContent.js';
|
|
@@ -13,11 +14,12 @@ import { TopbarOverflow } from './TopbarOverflow.js';
|
|
|
13
14
|
const removableCrumbs = ['draft', 'admin-ui'];
|
|
14
15
|
export const Menu = ({ children }) => {
|
|
15
16
|
const { t } = useTranslation('common');
|
|
17
|
+
const { t: pluginTranslation } = useTranslation();
|
|
16
18
|
const navigate = useNavigate();
|
|
17
19
|
const matches = useMatches();
|
|
18
20
|
const openGlobalSearch = useGlobalSearch((state) => state.open);
|
|
19
|
-
const { topNavigationComponents } = usePluginStore();
|
|
20
|
-
const { defaultRoute } = useAdminAccess();
|
|
21
|
+
const { navMenuData, topNavigationComponents } = usePluginStore();
|
|
22
|
+
const { defaultRoute, routes } = useAdminAccess();
|
|
21
23
|
const userPermissions = useServer((state) => state.userPermissions);
|
|
22
24
|
const { theme, setTheme } = useSettings();
|
|
23
25
|
const showChannelPicker = window.__DEENRUV_SETTINGS__.ui?.showChannelPicker !== false;
|
|
@@ -26,20 +28,51 @@ export const Menu = ({ children }) => {
|
|
|
26
28
|
const defaultRoutePath = defaultRoute?.path || Routes.dashboard;
|
|
27
29
|
const defaultRouteMenuKey = defaultRoute?.nav?.menuKey || defaultRoute?.search?.menuKey || 'dashboard';
|
|
28
30
|
const allowedTopNavigationComponents = topNavigationComponents?.filter((entry) => canAccessAdminItem({ item: entry.access, userPermissions }));
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
.
|
|
34
|
-
|
|
31
|
+
const pathname = matches.at(-1)?.pathname || '';
|
|
32
|
+
const crumbSegments = pathname.split('/').filter(Boolean);
|
|
33
|
+
const pluginT = (translation) => {
|
|
34
|
+
const [namespace = '', ...keyParts] = translation.split('.');
|
|
35
|
+
return pluginTranslation(keyParts.join('.'), { ns: namespace });
|
|
36
|
+
};
|
|
37
|
+
const crumbs = useMemo(() => {
|
|
38
|
+
const groups = [
|
|
39
|
+
...adminNavigationGroups.map((group) => ({
|
|
40
|
+
id: group.id,
|
|
41
|
+
label: t(`menuGroups.${group.labelKey}`),
|
|
42
|
+
permitted: true,
|
|
43
|
+
})),
|
|
44
|
+
...navMenuData.groups.map((group) => ({
|
|
45
|
+
id: group.id,
|
|
46
|
+
label: pluginT(group.labelId),
|
|
47
|
+
permitted: canAccessAdminItem({ item: group.access, userPermissions }),
|
|
48
|
+
})),
|
|
49
|
+
];
|
|
50
|
+
const links = navMenuData.links.map((link) => ({
|
|
51
|
+
href: link.href,
|
|
52
|
+
groupId: link.groupId,
|
|
53
|
+
label: pluginT(link.labelId),
|
|
54
|
+
permitted: canAccessAdminItem({ item: link.access, userPermissions }),
|
|
55
|
+
}));
|
|
56
|
+
const extensionsRoute = routes.find((route) => route.id === 'extensions');
|
|
57
|
+
return buildMenuBreadcrumbs({
|
|
58
|
+
pathname,
|
|
59
|
+
groups,
|
|
60
|
+
links,
|
|
61
|
+
extensions: {
|
|
62
|
+
label: t('menu.extensions'),
|
|
63
|
+
href: extensionsRoute?.path || '/admin-ui/extensions',
|
|
64
|
+
permitted: canAccessAdminItem({ item: extensionsRoute, userPermissions }),
|
|
65
|
+
},
|
|
66
|
+
fallbackLabels: crumbSegments.map((crumb) => removableCrumbs.includes(crumb) ? '' : t(`menu.${dashToCamelCase(crumb)}`, { defaultValue: crumb })),
|
|
67
|
+
});
|
|
68
|
+
}, [navMenuData.groups, navMenuData.links, pathname, pluginTranslation, routes, t, userPermissions]);
|
|
35
69
|
const navigateHome = () => navigate(defaultRoutePath, { viewTransition: true });
|
|
36
70
|
return (_jsx(TooltipProvider, { delayDuration: 100, children: _jsxs("div", { className: "flex h-screen w-full overflow-hidden bg-[var(--sidebar-canvas)]", children: [_jsx("aside", { "data-collapsed": isCollapsed, className: cn('hidden h-full shrink-0 border-r border-[var(--sidebar-hairline)] bg-[var(--sidebar-canvas)] transition-[width] duration-300 ease-out motion-reduce:transition-none md:block', isCollapsed ? 'w-12' : 'w-64'), "aria-label": t('mainNavigation'), children: _jsx(SidebarContent, { isCollapsed: isCollapsed, manuallyOpenGroupIds: manuallyOpenGroupIds, mode: "desktop", onExpand: () => setIsCollapsed(false), onOpenGroupIdsChange: setManuallyOpenGroupIds, onNavigate: () => undefined, onNavigateHome: navigateHome }) }), _jsx(Sheet, { open: isMobileOpen, onOpenChange: setIsMobileOpen, children: _jsxs(SheetContent, { side: "left", className: "w-72 max-w-[calc(100vw-1rem)] border-[var(--sidebar-hairline)] bg-[var(--sidebar-canvas)] p-0 sm:max-w-72 [&>button]:rounded-[4px] [&>button]:text-[var(--sidebar-secondary)] [&>button]:focus:ring-[var(--sidebar-focus)] [&>button]:data-[state=open]:bg-[var(--sidebar-hover)]", children: [_jsx(SheetTitle, { className: "sr-only", children: t('mainNavigation') }), _jsx(SheetDescription, { className: "sr-only", children: t('mobileNavigationDescription') }), _jsx(SidebarContent, { isCollapsed: false, manuallyOpenGroupIds: manuallyOpenGroupIds, mode: "mobile", onExpand: () => undefined, onOpenGroupIdsChange: setManuallyOpenGroupIds, onNavigate: () => setIsMobileOpen(false), onNavigateHome: () => {
|
|
37
71
|
navigateHome();
|
|
38
72
|
setIsMobileOpen(false);
|
|
39
73
|
} })] }) }), _jsxs("main", { className: "flex min-w-0 flex-1 flex-col bg-[var(--sidebar-canvas)]", children: [_jsxs("header", { className: "flex h-16 shrink-0 items-center gap-2 border-b border-[var(--sidebar-hairline)] bg-[var(--sidebar-canvas)] px-3 lg:h-[72px] lg:px-5", children: [_jsx(Button, { variant: "outline", size: "icon", className: "size-9 shrink-0 md:hidden", onClick: () => setIsMobileOpen(true), "aria-label": t('openSidebar'), title: t('openSidebar'), children: _jsx(MenuIcon, { className: "size-4" }) }), _jsx(Button, { variant: "outline", size: "icon", className: "hidden size-9 shrink-0 md:inline-flex", onClick: toggleDesktop, "aria-label": isCollapsed ? t('expandSidebar') : t('collapseSidebar'), title: `${isCollapsed ? t('expandSidebar') : t('collapseSidebar')} (${t('sidebarShortcut')})`, children: isCollapsed ? _jsx(PanelLeftOpen, { className: "size-4" }) : _jsx(PanelLeftClose, { className: "size-4" }) }), _jsx("div", { className: "hidden min-w-0 flex-col items-start justify-center sm:flex", children: _jsx(Breadcrumb, { children: _jsx(BreadcrumbList, { className: "flex-nowrap overflow-hidden", children: crumbs.length ? (crumbs.map((crumb, index) => {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
: `menu.${dashToCamelCase(defaultRouteMenuKey)}`) }) }) })) }) }) }), _jsxs("div", { className: "ml-auto flex min-w-0 items-center justify-end gap-1.5", children: [_jsx(TopbarOverflow, { components: allowedTopNavigationComponents || [], showChannelPicker: showChannelPicker, showLanguagePicker: showLanguagePicker }), allowedTopNavigationComponents?.length ? (_jsx("div", { className: "hidden items-center gap-2 lg:flex", children: allowedTopNavigationComponents.map(({ component: Component }, index) => (_jsx(Component, {}, index))) })) : null, showChannelPicker || showLanguagePicker ? (_jsxs("div", { className: "hidden items-center gap-1.5 lg:flex", children: [showLanguagePicker ? _jsx(LanguagesDropdown, {}) : null, showChannelPicker ? _jsx(ChannelSwitcher, { className: "min-w-44" }) : null] })) : null, _jsx(Button, { onClick: openGlobalSearch, variant: "outline", size: "icon", className: "relative size-9 shrink-0", "aria-label": t('openGlobalSearch'), title: t('openGlobalSearch'), children: _jsx(SearchIcon, { className: "size-4" }) }), _jsx(Notifications, {}), _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs(Button, { variant: "outline", size: "icon", className: "relative size-9 shrink-0", children: [theme === 'light' ? (_jsx(Sun, { className: "size-[1.2rem]" })) : theme === 'dark' ? (_jsx(Moon, { className: "size-[1.2rem]" })) : (_jsx(SunMoon, { className: "size-[1.2rem]" })), _jsx("span", { className: "sr-only", children: t('toggleTheme') })] }) }), _jsxs(DropdownMenuContent, { align: "end", children: [_jsx(DropdownMenuItem, { onClick: () => setTheme('light'), children: t('themeLight') }), _jsx(DropdownMenuItem, { onClick: () => setTheme('dark'), children: t('themeDark') }), _jsx(DropdownMenuItem, { onClick: () => setTheme('system'), children: t('themeSystem') })] })] })] })] }), _jsx(ScrollArea, { className: "relative min-h-0 flex-1 overflow-y-hidden bg-[var(--sidebar-canvas)]", children: children })] })] }) }));
|
|
74
|
+
return (_jsxs(React.Fragment, { children: [_jsx(BreadcrumbItem, { className: "min-w-0", children: crumb.current ? (_jsx(BreadcrumbPage, { className: "truncate text-sm font-semibold tracking-tight", children: crumb.label })) : crumb.href ? (_jsx(NavLink, { to: crumb.href, viewTransition: true, className: "truncate", children: _jsx("span", { className: "text-sm font-semibold tracking-tight text-foreground", children: crumb.label }) })) : (_jsx("span", { className: "truncate text-sm font-semibold tracking-tight text-muted-foreground", children: crumb.label })) }), index !== crumbs.length - 1 && (_jsx(BreadcrumbSeparator, { children: _jsx(Slash, { className: "text-muted-foreground" }) }))] }, `${crumb.label}-${index}`));
|
|
75
|
+
})) : (_jsx(BreadcrumbItem, { children: _jsx(BreadcrumbPage, { className: "text-lg font-semibold tracking-tight", children: t(defaultRouteMenuKey === 'dashboard'
|
|
76
|
+
? 'dashboard'
|
|
77
|
+
: `menu.${dashToCamelCase(defaultRouteMenuKey)}`) }) })) }) }) }), _jsxs("div", { className: "ml-auto flex min-w-0 items-center justify-end gap-1.5", children: [_jsx(TopbarOverflow, { components: allowedTopNavigationComponents || [], showChannelPicker: showChannelPicker, showLanguagePicker: showLanguagePicker }), allowedTopNavigationComponents?.length ? (_jsx("div", { className: "hidden items-center gap-2 lg:flex", children: allowedTopNavigationComponents.map(({ component: Component }, index) => (_jsx(Component, {}, index))) })) : null, showChannelPicker || showLanguagePicker ? (_jsxs("div", { className: "hidden items-center gap-1.5 lg:flex", children: [showLanguagePicker ? _jsx(LanguagesDropdown, {}) : null, showChannelPicker ? _jsx(ChannelSwitcher, { className: "min-w-44" }) : null] })) : null, _jsx(Button, { onClick: openGlobalSearch, variant: "outline", size: "icon", className: "relative size-9 shrink-0", "aria-label": t('openGlobalSearch'), title: t('openGlobalSearch'), children: _jsx(SearchIcon, { className: "size-4" }) }), _jsx(Notifications, {}), _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs(Button, { variant: "outline", size: "icon", className: "relative size-9 shrink-0", children: [theme === 'light' ? (_jsx(Sun, { className: "size-[1.2rem]" })) : theme === 'dark' ? (_jsx(Moon, { className: "size-[1.2rem]" })) : (_jsx(SunMoon, { className: "size-[1.2rem]" })), _jsx("span", { className: "sr-only", children: t('toggleTheme') })] }) }), _jsxs(DropdownMenuContent, { align: "end", children: [_jsx(DropdownMenuItem, { onClick: () => setTheme('light'), children: t('themeLight') }), _jsx(DropdownMenuItem, { onClick: () => setTheme('dark'), children: t('themeDark') }), _jsx(DropdownMenuItem, { onClick: () => setTheme('system'), children: t('themeSystem') })] })] })] })] }), _jsx(ScrollArea, { className: "relative min-h-0 flex-1 overflow-y-hidden bg-[var(--sidebar-canvas)]", children: children })] })] }) }));
|
|
45
78
|
};
|
|
@@ -972,13 +972,22 @@
|
|
|
972
972
|
"asset": {
|
|
973
973
|
"dialogTitle": "Select resource",
|
|
974
974
|
"dialogButton": "Select resource",
|
|
975
|
+
"pickerDescription": "Choose one resource. Your selection is applied only after confirmation.",
|
|
976
|
+
"searchLabel": "Search resources by filename",
|
|
975
977
|
"dialogSearchPlaceholder": "Searching for resources by name",
|
|
976
978
|
"tags": "Filter by tag",
|
|
979
|
+
"removeTag": "Remove {{value}} filter",
|
|
977
980
|
"perPageValue": "{{value}} per page",
|
|
978
981
|
"confirmButton": "Add resource",
|
|
979
982
|
"uploadButton": "Upload from disk",
|
|
980
983
|
"cancel": "Cancel",
|
|
981
984
|
"noImages": "No resources for a specific search",
|
|
985
|
+
"fetchError": "Resources could not be loaded. Try again.",
|
|
986
|
+
"loading": "Loading resources",
|
|
987
|
+
"selected": "Selected",
|
|
988
|
+
"resultRange": "{{from}}–{{to}} of {{total}}",
|
|
989
|
+
"paginationLabel": "Resource pages",
|
|
990
|
+
"actionsLabel": "Resource selection controls",
|
|
982
991
|
"description": "Uploading and managing resources that can be used in content"
|
|
983
992
|
},
|
|
984
993
|
"product": {
|
|
@@ -971,13 +971,22 @@
|
|
|
971
971
|
"asset": {
|
|
972
972
|
"dialogTitle": "Wybierz zasób",
|
|
973
973
|
"dialogButton": "Wybierz zasób",
|
|
974
|
+
"pickerDescription": "Wybierz jeden zasób. Wybór zostanie zastosowany dopiero po potwierdzeniu.",
|
|
975
|
+
"searchLabel": "Szukaj zasobów według nazwy pliku",
|
|
974
976
|
"dialogSearchPlaceholder": "Wyszukiwanie zasobów według nazwy",
|
|
975
977
|
"tags": "Filtruj według tagu",
|
|
978
|
+
"removeTag": "Usuń filtr {{value}}",
|
|
976
979
|
"perPageValue": "{{value}} na stronę",
|
|
977
980
|
"confirmButton": "Dodaj zasób",
|
|
978
981
|
"uploadButton": "Prześlij z dysku",
|
|
979
982
|
"cancel": "Anuluj",
|
|
980
983
|
"noImages": "Brak zasobów dla określonego wyszukiwania",
|
|
984
|
+
"fetchError": "Nie udało się załadować zasobów. Spróbuj ponownie.",
|
|
985
|
+
"loading": "Ładowanie zasobów",
|
|
986
|
+
"selected": "Wybrano",
|
|
987
|
+
"resultRange": "{{from}}–{{to}} z {{total}}",
|
|
988
|
+
"paginationLabel": "Strony zasobów",
|
|
989
|
+
"actionsLabel": "Elementy sterujące wyborem zasobu",
|
|
981
990
|
"description": "Przesyłanie i zarządzanie zasobami, które mogą być używane w treści"
|
|
982
991
|
},
|
|
983
992
|
"product": {
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const ADMIN_DASHBOARD_VERSION = '1.0.17-dev.
|
|
1
|
+
export const ADMIN_DASHBOARD_VERSION = '1.0.17-dev.29';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deenruv/admin-dashboard",
|
|
3
|
-
"version": "1.0.17-dev.
|
|
3
|
+
"version": "1.0.17-dev.29",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"style": "dist/index.css",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -80,10 +80,10 @@
|
|
|
80
80
|
"yup": "^1.7.1",
|
|
81
81
|
"zod": "^4.4.3",
|
|
82
82
|
"zustand": "^5.0.14",
|
|
83
|
-
"@deenruv/admin-dashboard": "1.0.17-dev.
|
|
84
|
-
"@deenruv/
|
|
85
|
-
"@deenruv/
|
|
86
|
-
"@deenruv/react-ui-devkit": "1.0.17-dev.
|
|
83
|
+
"@deenruv/admin-dashboard": "1.0.17-dev.29",
|
|
84
|
+
"@deenruv/admin-types": "1.0.17-dev.29",
|
|
85
|
+
"@deenruv/deenruv-examples-plugin": "1.0.17-dev.29",
|
|
86
|
+
"@deenruv/react-ui-devkit": "1.0.17-dev.29"
|
|
87
87
|
},
|
|
88
88
|
"devDependencies": {
|
|
89
89
|
"@tailwindcss/typography": "^0.5.20",
|