@vendure/dashboard 3.3.4-master-202506181251 → 3.3.4-master-202506181256

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vendure/dashboard",
3
3
  "private": false,
4
- "version": "3.3.4-master-202506181251",
4
+ "version": "3.3.4-master-202506181256",
5
5
  "type": "module",
6
6
  "repository": {
7
7
  "type": "git",
@@ -86,8 +86,8 @@
86
86
  "@types/react-dom": "^19.0.4",
87
87
  "@types/react-grid-layout": "^1.3.5",
88
88
  "@uidotdev/usehooks": "^2.4.1",
89
- "@vendure/common": "^3.3.4-master-202506181251",
90
- "@vendure/core": "^3.3.4-master-202506181251",
89
+ "@vendure/common": "^3.3.4-master-202506181256",
90
+ "@vendure/core": "^3.3.4-master-202506181256",
91
91
  "@vitejs/plugin-react": "^4.3.4",
92
92
  "awesome-graphql-client": "^2.1.0",
93
93
  "class-variance-authority": "^0.7.1",
@@ -130,5 +130,5 @@
130
130
  "lightningcss-linux-arm64-musl": "^1.29.3",
131
131
  "lightningcss-linux-x64-musl": "^1.29.1"
132
132
  },
133
- "gitHead": "3f76be479246dfceb21000abc6951790ee280a73"
133
+ "gitHead": "2b51aa1b137d9ce0feea6ebb1ee2318d905672de"
134
134
  }
@@ -7,14 +7,15 @@ import {
7
7
  SidebarHeader,
8
8
  SidebarRail,
9
9
  } from '@/components/ui/sidebar.js';
10
- import { getNavMenuConfig } from '@/framework/nav-menu/nav-menu-extensions.js';
11
10
  import { useDashboardExtensions } from '@/framework/extension-api/use-dashboard-extensions.js';
11
+ import { getNavMenuConfig } from '@/framework/nav-menu/nav-menu-extensions.js';
12
12
  import * as React from 'react';
13
13
  import { ChannelSwitcher } from './channel-switcher.js';
14
14
 
15
15
  export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
16
16
  const { extensionsLoaded } = useDashboardExtensions();
17
17
  const { sections } = getNavMenuConfig();
18
+
18
19
  return (
19
20
  extensionsLoaded && (
20
21
  <Sidebar collapsible="icon" {...props}>
@@ -9,19 +9,48 @@ import {
9
9
  SidebarMenuSubButton,
10
10
  SidebarMenuSubItem,
11
11
  } from '@/components/ui/sidebar.js';
12
- import { NavMenuSection, NavMenuItem } from '@/framework/nav-menu/nav-menu-extensions.js';
13
- import { Link, rootRouteId, useLocation, useMatch } from '@tanstack/react-router';
12
+ import {
13
+ NavMenuItem,
14
+ NavMenuSection,
15
+ NavMenuSectionPlacement,
16
+ } from '@/framework/nav-menu/nav-menu-extensions.js';
17
+ import { Link, useLocation } from '@tanstack/react-router';
14
18
  import { ChevronRight } from 'lucide-react';
15
19
  import * as React from 'react';
16
20
 
21
+ // Utility to sort items & sections by the optional `order` prop (ascending) and then alphabetically by title
22
+ function sortByOrder<T extends { order?: number; title: string }>(a: T, b: T) {
23
+ const orderA = a.order ?? Number.MAX_SAFE_INTEGER;
24
+ const orderB = b.order ?? Number.MAX_SAFE_INTEGER;
25
+ if (orderA === orderB) {
26
+ return a.title.localeCompare(b.title);
27
+ }
28
+ return orderA - orderB;
29
+ }
30
+
17
31
  export function NavMain({ items }: { items: Array<NavMenuSection | NavMenuItem> }) {
18
32
  const location = useLocation();
19
33
  // State to track which bottom section is currently open
20
34
  const [openBottomSectionId, setOpenBottomSectionId] = React.useState<string | null>(null);
21
35
 
22
- // Split sections into top and bottom groups based on placement property
23
- const topSections = items.filter(item => item.placement === 'top');
24
- const bottomSections = items.filter(item => item.placement === 'bottom');
36
+ // Helper to build a sorted list of sections for a given placement, memoized for stability
37
+ const getSortedSections = React.useCallback(
38
+ (placement: NavMenuSectionPlacement) => {
39
+ return items
40
+ .filter(item => item.placement === placement)
41
+ .slice()
42
+ .sort(sortByOrder)
43
+ .map(section =>
44
+ 'items' in section
45
+ ? { ...section, items: section.items?.slice().sort(sortByOrder) }
46
+ : section,
47
+ );
48
+ },
49
+ [items],
50
+ );
51
+
52
+ const topSections = React.useMemo(() => getSortedSections('top'), [getSortedSections]);
53
+ const bottomSections = React.useMemo(() => getSortedSections('bottom'), [getSortedSections]);
25
54
 
26
55
  // Handle bottom section open/close
27
56
  const handleBottomSectionToggle = (sectionId: string, isOpen: boolean) => {
@@ -50,7 +79,7 @@ export function NavMain({ items }: { items: Array<NavMenuSection | NavMenuItem>
50
79
  return;
51
80
  }
52
81
  }
53
- }, [location.pathname]);
82
+ }, [location.pathname, bottomSections]);
54
83
 
55
84
  // Render a top navigation section
56
85
  const renderTopSection = (item: NavMenuSection | NavMenuItem) => {
@@ -23,6 +23,7 @@ export function registerDefaults() {
23
23
  placement: 'top',
24
24
  icon: LayoutDashboardIcon,
25
25
  url: '/',
26
+ order: 100,
26
27
  },
27
28
  {
28
29
  id: 'catalog',
@@ -30,6 +31,7 @@ export function registerDefaults() {
30
31
  icon: SquareTerminal,
31
32
  defaultOpen: true,
32
33
  placement: 'top',
34
+ order: 200,
33
35
  items: [
34
36
  {
35
37
  id: 'products',
@@ -64,6 +66,7 @@ export function registerDefaults() {
64
66
  icon: ShoppingCart,
65
67
  defaultOpen: true,
66
68
  placement: 'top',
69
+ order: 300,
67
70
  items: [
68
71
  {
69
72
  id: 'orders',
@@ -78,6 +81,7 @@ export function registerDefaults() {
78
81
  icon: Users,
79
82
  defaultOpen: false,
80
83
  placement: 'top',
84
+ order: 400,
81
85
  items: [
82
86
  {
83
87
  id: 'customers',
@@ -97,6 +101,7 @@ export function registerDefaults() {
97
101
  icon: Mail,
98
102
  defaultOpen: false,
99
103
  placement: 'top',
104
+ order: 500,
100
105
  items: [
101
106
  {
102
107
  id: 'promotions',
@@ -111,6 +116,7 @@ export function registerDefaults() {
111
116
  icon: Terminal,
112
117
  defaultOpen: false,
113
118
  placement: 'bottom',
119
+ order: 100,
114
120
  items: [
115
121
  {
116
122
  id: 'job-queue',
@@ -135,6 +141,7 @@ export function registerDefaults() {
135
141
  icon: Settings2,
136
142
  defaultOpen: false,
137
143
  placement: 'bottom',
144
+ order: 200,
138
145
  items: [
139
146
  {
140
147
  id: 'sellers',
@@ -3,7 +3,7 @@ import {
3
3
  registerDashboardActionBarItem,
4
4
  registerDashboardPageBlock,
5
5
  } from '../layout-engine/layout-extensions.js';
6
- import { addNavMenuItem, NavMenuItem } from '../nav-menu/nav-menu-extensions.js';
6
+ import { addNavMenuItem, addNavMenuSection, NavMenuItem } from '../nav-menu/nav-menu-extensions.js';
7
7
  import { registerRoute } from '../page/page-api.js';
8
8
  import { globalRegistry } from '../registry/global-registry.js';
9
9
 
@@ -34,6 +34,16 @@ export function executeDashboardExtensionCallbacks() {
34
34
  */
35
35
  export function defineDashboardExtension(extension: DashboardExtension) {
36
36
  globalRegistry.get('registerDashboardExtensionCallbacks').add(() => {
37
+ if (extension.navSections) {
38
+ for (const section of extension.navSections) {
39
+ addNavMenuSection({
40
+ ...section,
41
+ placement: 'top',
42
+ order: section.order ?? 999,
43
+ items: [],
44
+ });
45
+ }
46
+ }
37
47
  if (extension.routes) {
38
48
  for (const route of extension.routes) {
39
49
  if (route.navMenuItem) {
@@ -1,5 +1,6 @@
1
1
  import { PageContextValue } from '@/framework/layout-engine/page-provider.js';
2
2
  import { AnyRoute, RouteOptions } from '@tanstack/react-router';
3
+ import { LucideIcon } from 'lucide-react';
3
4
  import type React from 'react';
4
5
 
5
6
  import { DashboardAlertDefinition } from '../alert/types.js';
@@ -18,6 +19,13 @@ export interface ActionBarButtonState {
18
19
  visible: boolean;
19
20
  }
20
21
 
22
+ export interface DashboardNavSectionDefinition {
23
+ id: string;
24
+ title: string;
25
+ icon?: LucideIcon;
26
+ order?: number;
27
+ }
28
+
21
29
  /**
22
30
  * @description
23
31
  * **Status: Developer Preview**
@@ -103,6 +111,11 @@ export interface DashboardExtension {
103
111
  * Allows you to define custom routes such as list or detail views.
104
112
  */
105
113
  routes?: DashboardRouteDefinition[];
114
+ /**
115
+ * @description
116
+ * Allows you to define custom nav sections for the dashboard.
117
+ */
118
+ navSections?: DashboardNavSectionDefinition[];
106
119
  /**
107
120
  * @description
108
121
  * Allows you to define custom page blocks for any page in the dashboard.
@@ -9,6 +9,7 @@ interface NavMenuBaseItem {
9
9
  id: string;
10
10
  title: string;
11
11
  icon?: LucideIcon;
12
+ order?: number;
12
13
  placement?: NavMenuSectionPlacement;
13
14
  }
14
15
 
@@ -64,3 +65,9 @@ export function addNavMenuItem(item: NavMenuItem, sectionId: string) {
64
65
  }
65
66
  }
66
67
  }
68
+
69
+ export function addNavMenuSection(section: NavMenuSection) {
70
+ const navMenuConfig = getNavMenuConfig();
71
+ navMenuConfig.sections = [...navMenuConfig.sections];
72
+ navMenuConfig.sections.push(section);
73
+ }