@vendure/dashboard 3.4.2-master-202509081248 → 3.4.2-master-202509090229
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 +4 -4
- package/src/app/routes/_authenticated/_administrators/administrators_.$id.tsx +1 -1
- package/src/app/routes/_authenticated/_assets/assets.tsx +1 -0
- package/src/app/routes/_authenticated/_assets/assets_.$id.tsx +1 -1
- package/src/app/routes/_authenticated/_channels/channels_.$id.tsx +1 -1
- package/src/app/routes/_authenticated/_collections/collections_.$id.tsx +1 -1
- package/src/app/routes/_authenticated/_countries/countries_.$id.tsx +1 -1
- package/src/app/routes/_authenticated/_customer-groups/customer-groups_.$id.tsx +1 -1
- package/src/app/routes/_authenticated/_customers/customers_.$id.tsx +1 -1
- package/src/app/routes/_authenticated/_facets/facets_.$facetId.values_.$id.tsx +1 -1
- package/src/app/routes/_authenticated/_facets/facets_.$id.tsx +1 -1
- package/src/app/routes/_authenticated/_global-settings/global-settings.tsx +2 -2
- package/src/app/routes/_authenticated/_orders/orders_.$id.tsx +1 -1
- package/src/app/routes/_authenticated/_orders/orders_.$id_.modify.tsx +5 -1
- package/src/app/routes/_authenticated/_orders/orders_.draft.$id.tsx +1 -1
- package/src/app/routes/_authenticated/_payment-methods/payment-methods_.$id.tsx +1 -1
- package/src/app/routes/_authenticated/_product-variants/product-variants_.$id.tsx +2 -2
- package/src/app/routes/_authenticated/_products/products_.$id.tsx +1 -1
- package/src/app/routes/_authenticated/_products/products_.$id_.variants.tsx +2 -2
- package/src/app/routes/_authenticated/_promotions/promotions_.$id.tsx +1 -1
- package/src/app/routes/_authenticated/_roles/roles_.$id.tsx +1 -1
- package/src/app/routes/_authenticated/_sellers/sellers_.$id.tsx +1 -1
- package/src/app/routes/_authenticated/_shipping-methods/shipping-methods_.$id.tsx +1 -1
- package/src/app/routes/_authenticated/_stock-locations/stock-locations_.$id.tsx +1 -1
- package/src/app/routes/_authenticated/_system/healthchecks.tsx +1 -0
- package/src/app/routes/_authenticated/_tax-categories/tax-categories_.$id.tsx +1 -1
- package/src/app/routes/_authenticated/_tax-rates/tax-rates_.$id.tsx +1 -1
- package/src/app/routes/_authenticated/_zones/zones_.$id.tsx +1 -1
- package/src/lib/components/layout/generated-breadcrumbs.tsx +103 -43
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vendure/dashboard",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "3.4.2-master-
|
|
4
|
+
"version": "3.4.2-master-202509090229",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -100,8 +100,8 @@
|
|
|
100
100
|
"@types/react": "^19.0.10",
|
|
101
101
|
"@types/react-dom": "^19.0.4",
|
|
102
102
|
"@uidotdev/usehooks": "^2.4.1",
|
|
103
|
-
"@vendure/common": "^3.4.2-master-
|
|
104
|
-
"@vendure/core": "^3.4.2-master-
|
|
103
|
+
"@vendure/common": "^3.4.2-master-202509090229",
|
|
104
|
+
"@vendure/core": "^3.4.2-master-202509090229",
|
|
105
105
|
"@vitejs/plugin-react": "^4.3.4",
|
|
106
106
|
"acorn": "^8.11.3",
|
|
107
107
|
"acorn-walk": "^8.3.2",
|
|
@@ -152,5 +152,5 @@
|
|
|
152
152
|
"lightningcss-linux-arm64-musl": "^1.29.3",
|
|
153
153
|
"lightningcss-linux-x64-musl": "^1.29.1"
|
|
154
154
|
},
|
|
155
|
-
"gitHead": "
|
|
155
|
+
"gitHead": "05e55ae78c8486c39c8b58dfae290777e648398e"
|
|
156
156
|
}
|
|
@@ -36,7 +36,7 @@ export const Route = createFileRoute('/_authenticated/_administrators/administra
|
|
|
36
36
|
breadcrumb: (isNew, entity) => {
|
|
37
37
|
const name = `${entity?.firstName} ${entity?.lastName}`;
|
|
38
38
|
return [
|
|
39
|
-
{ path: '/administrators', label:
|
|
39
|
+
{ path: '/administrators', label: <Trans>Administrators</Trans> },
|
|
40
40
|
isNew ? <Trans>New administrator</Trans> : name,
|
|
41
41
|
];
|
|
42
42
|
},
|
|
@@ -6,6 +6,7 @@ import { DeleteAssetsBulkAction } from './components/asset-bulk-actions.js';
|
|
|
6
6
|
|
|
7
7
|
export const Route = createFileRoute('/_authenticated/_assets/assets')({
|
|
8
8
|
component: RouteComponent,
|
|
9
|
+
loader: () => ({ breadcrumb: () => <Trans>Assets</Trans> }),
|
|
9
10
|
});
|
|
10
11
|
|
|
11
12
|
function RouteComponent() {
|
|
@@ -35,7 +35,7 @@ export const Route = createFileRoute('/_authenticated/_assets/assets_/$id')({
|
|
|
35
35
|
queryDocument: assetDetailDocument,
|
|
36
36
|
breadcrumb(isNew, entity) {
|
|
37
37
|
return [
|
|
38
|
-
{ path: '/assets', label:
|
|
38
|
+
{ path: '/assets', label: <Trans>Assets</Trans> },
|
|
39
39
|
isNew ? <Trans>New asset</Trans> : (entity?.name ?? ''),
|
|
40
40
|
];
|
|
41
41
|
},
|
|
@@ -37,7 +37,7 @@ export const Route = createFileRoute('/_authenticated/_channels/channels_/$id')(
|
|
|
37
37
|
queryDocument: channelDetailDocument,
|
|
38
38
|
breadcrumb(isNew, entity) {
|
|
39
39
|
return [
|
|
40
|
-
{ path: '/channels', label:
|
|
40
|
+
{ path: '/channels', label: <Trans>Channels</Trans> },
|
|
41
41
|
isNew ? <Trans>New channel</Trans> : <ChannelCodeLabel code={entity?.code ?? ''} />,
|
|
42
42
|
];
|
|
43
43
|
},
|
|
@@ -41,7 +41,7 @@ export const Route = createFileRoute('/_authenticated/_collections/collections_/
|
|
|
41
41
|
pageId,
|
|
42
42
|
queryDocument: collectionDetailDocument,
|
|
43
43
|
breadcrumb: (isNew, entity) => [
|
|
44
|
-
{ path: '/collections', label:
|
|
44
|
+
{ path: '/collections', label: <Trans>Collections</Trans> },
|
|
45
45
|
isNew ? <Trans>New collection</Trans> : entity?.name,
|
|
46
46
|
],
|
|
47
47
|
}),
|
|
@@ -31,7 +31,7 @@ export const Route = createFileRoute('/_authenticated/_countries/countries_/$id'
|
|
|
31
31
|
pageId,
|
|
32
32
|
queryDocument: countryDetailDocument,
|
|
33
33
|
breadcrumb: (isNew, entity) => [
|
|
34
|
-
{ path: '/countries', label:
|
|
34
|
+
{ path: '/countries', label: <Trans>Countries</Trans> },
|
|
35
35
|
isNew ? <Trans>New country</Trans> : entity?.name,
|
|
36
36
|
],
|
|
37
37
|
}),
|
|
@@ -34,7 +34,7 @@ export const Route = createFileRoute('/_authenticated/_customer-groups/customer-
|
|
|
34
34
|
pageId,
|
|
35
35
|
queryDocument: customerGroupDetailDocument,
|
|
36
36
|
breadcrumb: (isNew, entity) => [
|
|
37
|
-
{ path: '/customer-groups', label:
|
|
37
|
+
{ path: '/customer-groups', label: <Trans>Customer Groups</Trans> },
|
|
38
38
|
isNew ? <Trans>New customer group</Trans> : entity?.name,
|
|
39
39
|
],
|
|
40
40
|
}),
|
|
@@ -55,7 +55,7 @@ export const Route = createFileRoute('/_authenticated/_customers/customers_/$id'
|
|
|
55
55
|
pageId,
|
|
56
56
|
queryDocument: customerDetailDocument,
|
|
57
57
|
breadcrumb: (isNew, entity) => [
|
|
58
|
-
{ path: '/customers', label:
|
|
58
|
+
{ path: '/customers', label: <Trans>Customers</Trans> },
|
|
59
59
|
isNew ? <Trans>New customer</Trans> : `${entity?.firstName} ${entity?.lastName}`,
|
|
60
60
|
],
|
|
61
61
|
}),
|
|
@@ -36,7 +36,7 @@ export const Route = createFileRoute('/_authenticated/_facets/facets_/$facetId/v
|
|
|
36
36
|
queryDocument: facetValueDetailDocument,
|
|
37
37
|
breadcrumb(isNew, entity) {
|
|
38
38
|
const facetName = entity?.facet.name ?? 'Facet Value';
|
|
39
|
-
const breadcrumb: PageBreadcrumb[] = [{ path: '/facets', label:
|
|
39
|
+
const breadcrumb: PageBreadcrumb[] = [{ path: '/facets', label: <Trans>Facets</Trans> }];
|
|
40
40
|
if (isNew) {
|
|
41
41
|
breadcrumb.push(<Trans>New facet value</Trans>);
|
|
42
42
|
} else if (entity) {
|
|
@@ -32,7 +32,7 @@ export const Route = createFileRoute('/_authenticated/_facets/facets_/$id')({
|
|
|
32
32
|
pageId,
|
|
33
33
|
queryDocument: facetDetailDocument,
|
|
34
34
|
breadcrumb(isNew, entity) {
|
|
35
|
-
return [{ path: '/facets', label:
|
|
35
|
+
return [{ path: '/facets', label: <Trans>Facets</Trans> }, isNew ? <Trans>New facet</Trans> : entity?.name];
|
|
36
36
|
},
|
|
37
37
|
}),
|
|
38
38
|
errorComponent: ({ error }) => <ErrorPage message={error.message} />,
|
|
@@ -38,7 +38,7 @@ export const Route = createFileRoute('/_authenticated/_global-settings/global-se
|
|
|
38
38
|
{},
|
|
39
39
|
);
|
|
40
40
|
return {
|
|
41
|
-
breadcrumb: [{ path: '/global-settings', label: <Trans>Global
|
|
41
|
+
breadcrumb: [{ path: '/global-settings', label: <Trans>Global Settings</Trans> }],
|
|
42
42
|
};
|
|
43
43
|
},
|
|
44
44
|
errorComponent: ({ error }) => <ErrorPage message={error.message} />,
|
|
@@ -88,7 +88,7 @@ function GlobalSettingsPage() {
|
|
|
88
88
|
return (
|
|
89
89
|
<Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
|
|
90
90
|
<PageTitle>
|
|
91
|
-
<Trans>Global
|
|
91
|
+
<Trans>Global Settings</Trans>
|
|
92
92
|
</PageTitle>
|
|
93
93
|
<PageActionBar>
|
|
94
94
|
<PageActionBarRight>
|
|
@@ -71,7 +71,7 @@ export const Route = createFileRoute('/_authenticated/_orders/orders_/$id')({
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
return {
|
|
74
|
-
breadcrumb: [{ path: '/orders', label:
|
|
74
|
+
breadcrumb: [{ path: '/orders', label: <Trans>Orders</Trans> }, result.order.code],
|
|
75
75
|
};
|
|
76
76
|
},
|
|
77
77
|
errorComponent: ({ error }) => <ErrorPage message={error.message} />,
|
|
@@ -62,7 +62,11 @@ export const Route = createFileRoute('/_authenticated/_orders/orders_/$id_/modif
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
return {
|
|
65
|
-
breadcrumb: [
|
|
65
|
+
breadcrumb: [
|
|
66
|
+
{ path: '/orders', label: <Trans>Orders</Trans> },
|
|
67
|
+
result.order.code,
|
|
68
|
+
{ label: <Trans>Modify</Trans> },
|
|
69
|
+
],
|
|
66
70
|
};
|
|
67
71
|
},
|
|
68
72
|
errorComponent: ({ error }) => <ErrorPage message={error.message} />,
|
|
@@ -68,7 +68,7 @@ export const Route = createFileRoute('/_authenticated/_orders/orders_/draft/$id'
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
return {
|
|
71
|
-
breadcrumb: [{ path: '/orders', label:
|
|
71
|
+
breadcrumb: [{ path: '/orders', label: <Trans>Orders</Trans> }, result.order.code],
|
|
72
72
|
};
|
|
73
73
|
},
|
|
74
74
|
errorComponent: ({ error }) => <ErrorPage message={error.message} />,
|
|
@@ -39,7 +39,7 @@ export const Route = createFileRoute('/_authenticated/_payment-methods/payment-m
|
|
|
39
39
|
queryDocument: paymentMethodDetailDocument,
|
|
40
40
|
breadcrumb(_isNew, entity) {
|
|
41
41
|
return [
|
|
42
|
-
{ path: '/payment-methods', label:
|
|
42
|
+
{ path: '/payment-methods', label: <Trans>Payment Methods</Trans> },
|
|
43
43
|
_isNew ? <Trans>New payment method</Trans> : entity?.name,
|
|
44
44
|
];
|
|
45
45
|
},
|
|
@@ -46,12 +46,12 @@ export const Route = createFileRoute('/_authenticated/_product-variants/product-
|
|
|
46
46
|
breadcrumb(_isNew, entity, location) {
|
|
47
47
|
if ((location.search as any).from === 'product') {
|
|
48
48
|
return [
|
|
49
|
-
{ path: '/product', label:
|
|
49
|
+
{ path: '/product', label: <Trans>Products</Trans> },
|
|
50
50
|
{ path: `/products/${entity?.product.id}`, label: entity?.product.name ?? '' },
|
|
51
51
|
entity?.name,
|
|
52
52
|
];
|
|
53
53
|
}
|
|
54
|
-
return [{ path: '/product-variants', label:
|
|
54
|
+
return [{ path: '/product-variants', label: <Trans>Product Variants</Trans> }, entity?.name];
|
|
55
55
|
},
|
|
56
56
|
}),
|
|
57
57
|
errorComponent: ({ error }) => <ErrorPage message={error.message} />,
|
|
@@ -40,7 +40,7 @@ export const Route = createFileRoute('/_authenticated/_products/products_/$id')(
|
|
|
40
40
|
queryDocument: productDetailDocument,
|
|
41
41
|
breadcrumb(isNew, entity) {
|
|
42
42
|
return [
|
|
43
|
-
{ path: '/products', label:
|
|
43
|
+
{ path: '/products', label: <Trans>Products</Trans> },
|
|
44
44
|
isNew ? <Trans>New product</Trans> : entity?.name,
|
|
45
45
|
];
|
|
46
46
|
},
|
|
@@ -51,9 +51,9 @@ export const Route = createFileRoute('/_authenticated/_products/products_/$id_/v
|
|
|
51
51
|
});
|
|
52
52
|
return {
|
|
53
53
|
breadcrumb: [
|
|
54
|
-
{ path: '/products', label:
|
|
54
|
+
{ path: '/products', label: <Trans>Products</Trans> },
|
|
55
55
|
{ path: `/products/${params.id}`, label: result.product?.name },
|
|
56
|
-
<Trans>Manage
|
|
56
|
+
<Trans>Manage Variants</Trans>,
|
|
57
57
|
],
|
|
58
58
|
};
|
|
59
59
|
},
|
|
@@ -40,7 +40,7 @@ export const Route = createFileRoute('/_authenticated/_promotions/promotions_/$i
|
|
|
40
40
|
queryDocument: promotionDetailDocument,
|
|
41
41
|
breadcrumb(isNew, entity) {
|
|
42
42
|
return [
|
|
43
|
-
{ path: '/promotions', label:
|
|
43
|
+
{ path: '/promotions', label: <Trans>Promotions</Trans> },
|
|
44
44
|
isNew ? <Trans>New promotion</Trans> : entity?.name,
|
|
45
45
|
];
|
|
46
46
|
},
|
|
@@ -31,7 +31,7 @@ export const Route = createFileRoute('/_authenticated/_roles/roles_/$id')({
|
|
|
31
31
|
queryDocument: roleDetailDocument,
|
|
32
32
|
breadcrumb(isNew, entity) {
|
|
33
33
|
return [
|
|
34
|
-
{ path: '/roles', label:
|
|
34
|
+
{ path: '/roles', label: <Trans>Roles</Trans> },
|
|
35
35
|
isNew ? <Trans>New role</Trans> : entity?.description,
|
|
36
36
|
];
|
|
37
37
|
},
|
|
@@ -28,7 +28,7 @@ export const Route = createFileRoute('/_authenticated/_sellers/sellers_/$id')({
|
|
|
28
28
|
pageId,
|
|
29
29
|
queryDocument: sellerDetailDocument,
|
|
30
30
|
breadcrumb: (isNew, entity) => [
|
|
31
|
-
{ path: '/sellers', label:
|
|
31
|
+
{ path: '/sellers', label: <Trans>Sellers</Trans> },
|
|
32
32
|
isNew ? <Trans>New seller</Trans> : entity?.name,
|
|
33
33
|
],
|
|
34
34
|
}),
|
|
@@ -39,7 +39,7 @@ export const Route = createFileRoute('/_authenticated/_shipping-methods/shipping
|
|
|
39
39
|
queryDocument: shippingMethodDetailDocument,
|
|
40
40
|
breadcrumb(isNew, entity) {
|
|
41
41
|
return [
|
|
42
|
-
{ path: '/shipping-methods', label:
|
|
42
|
+
{ path: '/shipping-methods', label: <Trans>Shipping Methods</Trans> },
|
|
43
43
|
isNew ? <Trans>New shipping method</Trans> : entity?.name,
|
|
44
44
|
];
|
|
45
45
|
},
|
|
@@ -35,7 +35,7 @@ export const Route = createFileRoute('/_authenticated/_stock-locations/stock-loc
|
|
|
35
35
|
queryDocument: stockLocationDetailQuery,
|
|
36
36
|
breadcrumb(isNew, entity) {
|
|
37
37
|
return [
|
|
38
|
-
{ path: '/stock-locations', label:
|
|
38
|
+
{ path: '/stock-locations', label: <Trans>Stock Locations</Trans> },
|
|
39
39
|
isNew ? <Trans>New stock location</Trans> : entity?.name,
|
|
40
40
|
];
|
|
41
41
|
},
|
|
@@ -10,6 +10,7 @@ import { uiConfig } from 'virtual:vendure-ui-config';
|
|
|
10
10
|
|
|
11
11
|
export const Route = createFileRoute('/_authenticated/_system/healthchecks')({
|
|
12
12
|
component: HealthchecksPage,
|
|
13
|
+
loader: () => ({ breadcrumb: () => <Trans>Healthchecks</Trans> }),
|
|
13
14
|
});
|
|
14
15
|
|
|
15
16
|
interface HealthcheckItem {
|
|
@@ -35,7 +35,7 @@ export const Route = createFileRoute('/_authenticated/_tax-categories/tax-catego
|
|
|
35
35
|
queryDocument: taxCategoryDetailDocument,
|
|
36
36
|
breadcrumb(isNew, entity) {
|
|
37
37
|
return [
|
|
38
|
-
{ path: '/tax-categories', label:
|
|
38
|
+
{ path: '/tax-categories', label: <Trans>Tax Categories</Trans> },
|
|
39
39
|
isNew ? <Trans>New tax category</Trans> : entity?.name,
|
|
40
40
|
];
|
|
41
41
|
},
|
|
@@ -34,7 +34,7 @@ export const Route = createFileRoute('/_authenticated/_tax-rates/tax-rates_/$id'
|
|
|
34
34
|
queryDocument: taxRateDetailDocument,
|
|
35
35
|
breadcrumb(isNew, entity) {
|
|
36
36
|
return [
|
|
37
|
-
{ path: '/tax-rates', label:
|
|
37
|
+
{ path: '/tax-rates', label: <Trans>Tax Rates</Trans> },
|
|
38
38
|
isNew ? <Trans>New tax rate</Trans> : entity?.name,
|
|
39
39
|
];
|
|
40
40
|
},
|
|
@@ -30,7 +30,7 @@ export const Route = createFileRoute('/_authenticated/_zones/zones_/$id')({
|
|
|
30
30
|
pageId,
|
|
31
31
|
queryDocument: zoneDetailDocument,
|
|
32
32
|
breadcrumb(isNew, entity) {
|
|
33
|
-
return [{ path: '/zones', label:
|
|
33
|
+
return [{ path: '/zones', label: <Trans>Zones</Trans> }, isNew ? <Trans>New zone</Trans> : entity?.name];
|
|
34
34
|
},
|
|
35
35
|
}),
|
|
36
36
|
errorComponent: ({ error }) => <ErrorPage message={error.message} />,
|
|
@@ -5,9 +5,11 @@ import {
|
|
|
5
5
|
BreadcrumbList,
|
|
6
6
|
BreadcrumbSeparator,
|
|
7
7
|
} from '@/vdb/components/ui/breadcrumb.js';
|
|
8
|
-
import { Link, useRouterState } from '@tanstack/react-router';
|
|
8
|
+
import { Link, useRouter, useRouterState } from '@tanstack/react-router';
|
|
9
9
|
import * as React from 'react';
|
|
10
10
|
import { Fragment } from 'react';
|
|
11
|
+
import { getNavMenuConfig } from '@/vdb/framework/nav-menu/nav-menu-extensions.js';
|
|
12
|
+
import type { NavMenuItem, NavMenuSection } from '@/vdb/framework/nav-menu/nav-menu-extensions.js';
|
|
11
13
|
|
|
12
14
|
export interface BreadcrumbPair {
|
|
13
15
|
label: string | React.ReactElement;
|
|
@@ -20,54 +22,112 @@ export type PageBreadcrumb = BreadcrumbPair | BreadcrumbShorthand;
|
|
|
20
22
|
|
|
21
23
|
export function GeneratedBreadcrumbs() {
|
|
22
24
|
const matches = useRouterState({ select: s => s.matches });
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
25
|
+
const currentPath = useRouterState({ select: s => s.location.pathname });
|
|
26
|
+
const router = useRouter();
|
|
27
|
+
const navMenuConfig = getNavMenuConfig();
|
|
28
|
+
const basePath = router.basepath || '';
|
|
29
|
+
|
|
30
|
+
const normalizeBreadcrumb = (breadcrumb: any, pathname: string): BreadcrumbPair[] => {
|
|
31
|
+
if (typeof breadcrumb === 'string') {
|
|
32
|
+
return [{ label: breadcrumb, path: pathname }];
|
|
33
|
+
}
|
|
34
|
+
if (React.isValidElement(breadcrumb)) {
|
|
35
|
+
return [{ label: breadcrumb, path: pathname }];
|
|
36
|
+
}
|
|
37
|
+
if (typeof breadcrumb === 'function') {
|
|
38
|
+
return [{ label: breadcrumb(), path: pathname }];
|
|
39
|
+
}
|
|
40
|
+
if (Array.isArray(breadcrumb)) {
|
|
41
|
+
return breadcrumb.map((crumb: PageBreadcrumb) => {
|
|
42
|
+
if (typeof crumb === 'string' || React.isValidElement(crumb)) {
|
|
43
|
+
return { label: crumb, path: pathname };
|
|
44
|
+
}
|
|
45
|
+
return { label: crumb.label, path: crumb.path };
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
return [];
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const rawCrumbs: BreadcrumbPair[] = React.useMemo(() => {
|
|
52
|
+
return matches
|
|
53
|
+
.filter(match => match.loaderData?.breadcrumb)
|
|
54
|
+
.flatMap(({ pathname, loaderData }) =>
|
|
55
|
+
normalizeBreadcrumb(loaderData.breadcrumb, pathname)
|
|
56
|
+
);
|
|
57
|
+
}, [matches]);
|
|
58
|
+
|
|
59
|
+
const isBaseRoute = (p: string) => p === basePath || p === `${basePath}/`;
|
|
60
|
+
const pageCrumbs: BreadcrumbPair[] = rawCrumbs.filter(c => !isBaseRoute(c.path));
|
|
61
|
+
|
|
62
|
+
const normalizePath = (path: string): string => {
|
|
63
|
+
const normalizedPath = basePath && path.startsWith(basePath) ? path.slice(basePath.length) : path;
|
|
64
|
+
return normalizedPath.startsWith('/') ? normalizedPath : `/${normalizedPath}`;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const pathMatches = (cleanPath: string, rawUrl?: string): boolean => {
|
|
68
|
+
if (!rawUrl) return false;
|
|
69
|
+
const strip = (p: string) => (p !== '/' && p.endsWith('/') ? p.slice(0, -1) : p);
|
|
70
|
+
const p = strip(cleanPath);
|
|
71
|
+
const u = strip(rawUrl);
|
|
72
|
+
return p === u || p.startsWith(`${u}/`);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const checkSectionItems = (
|
|
76
|
+
section: NavMenuSection | NavMenuItem,
|
|
77
|
+
cleanPath: string,
|
|
78
|
+
): BreadcrumbPair | undefined => {
|
|
79
|
+
if (!('items' in section) || !Array.isArray(section.items)) {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
for (const item of section.items) {
|
|
84
|
+
if (!item?.url) continue;
|
|
85
|
+
if (pathMatches(cleanPath, item.url)) {
|
|
86
|
+
return { label: section.title, path: item.url };
|
|
57
87
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
88
|
+
}
|
|
89
|
+
return undefined;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const checkDirectSection = (
|
|
93
|
+
section: NavMenuSection | NavMenuItem,
|
|
94
|
+
cleanPath: string,
|
|
95
|
+
): BreadcrumbPair | undefined => {
|
|
96
|
+
if ('url' in section && section.url && pathMatches(cleanPath, section.url)) {
|
|
97
|
+
return { label: section.title, path: section.url };
|
|
98
|
+
}
|
|
99
|
+
return undefined;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const findSectionCrumb = (path: string): BreadcrumbPair | undefined => {
|
|
103
|
+
const cleanPath = normalizePath(path);
|
|
104
|
+
const sections: Array<NavMenuSection | NavMenuItem> = navMenuConfig?.sections ?? [];
|
|
105
|
+
if (sections.length === 0) return undefined;
|
|
106
|
+
|
|
107
|
+
for (const section of sections) {
|
|
108
|
+
const result = checkSectionItems(section, cleanPath) || checkDirectSection(section, cleanPath);
|
|
109
|
+
if (result) {
|
|
110
|
+
return result;
|
|
63
111
|
}
|
|
64
|
-
}
|
|
65
|
-
|
|
112
|
+
}
|
|
113
|
+
return undefined;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const sectionCrumb = React.useMemo(
|
|
117
|
+
() => findSectionCrumb(currentPath),
|
|
118
|
+
[currentPath, basePath, navMenuConfig],
|
|
119
|
+
);
|
|
120
|
+
const breadcrumbs: BreadcrumbPair[] = React.useMemo(() => {
|
|
121
|
+
const arr = sectionCrumb ? [sectionCrumb, ...pageCrumbs] : pageCrumbs;
|
|
122
|
+
return arr.filter((c, i, self) =>
|
|
123
|
+
self.findIndex(x => x.path === c.path && x.label === c.label) === i,
|
|
124
|
+
);
|
|
125
|
+
}, [sectionCrumb, pageCrumbs]);
|
|
66
126
|
return (
|
|
67
127
|
<Breadcrumb>
|
|
68
128
|
<BreadcrumbList>
|
|
69
129
|
{breadcrumbs.map(({ label, path }, index, arr) => (
|
|
70
|
-
<Fragment key={index}>
|
|
130
|
+
<Fragment key={`${path}-${index}`}>
|
|
71
131
|
<BreadcrumbItem className="hidden md:block">
|
|
72
132
|
<BreadcrumbLink asChild>
|
|
73
133
|
<Link to={path}>{label}</Link>
|