@vendure/dashboard 3.6.0-minor-202511061550 → 3.6.0-minor-202512161252
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/plugin/constants.js +2 -2
- package/dist/vite/constants.js +1 -0
- package/dist/vite/utils/compiler.d.ts +1 -0
- package/dist/vite/utils/compiler.js +5 -4
- package/dist/vite/utils/get-dashboard-paths.d.ts +5 -0
- package/dist/vite/utils/get-dashboard-paths.js +20 -0
- package/dist/vite/vite-plugin-dashboard-metadata.js +2 -1
- package/dist/vite/vite-plugin-tailwind-source.js +2 -15
- package/dist/vite/vite-plugin-translations.d.ts +10 -1
- package/dist/vite/vite-plugin-translations.js +156 -45
- package/dist/vite/vite-plugin-vendure-dashboard.d.ts +12 -0
- package/dist/vite/vite-plugin-vendure-dashboard.js +1 -0
- package/lingui.config.js +1 -0
- package/package.json +7 -7
- package/src/app/routeTree.gen.ts +1221 -0
- package/src/app/routes/_authenticated/_administrators/administrators.tsx +9 -12
- package/src/app/routes/_authenticated/_administrators/administrators_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_assets/assets_.$id.tsx +6 -9
- package/src/app/routes/_authenticated/_channels/channels.tsx +9 -12
- package/src/app/routes/_authenticated/_channels/channels_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_collections/collections.tsx +9 -12
- package/src/app/routes/_authenticated/_collections/collections_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_countries/countries.tsx +9 -12
- package/src/app/routes/_authenticated/_countries/countries_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_customer-groups/customer-groups.tsx +9 -12
- package/src/app/routes/_authenticated/_customer-groups/customer-groups_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_customers/components/customer-history/index.ts +0 -1
- package/src/app/routes/_authenticated/_customers/customers.tsx +9 -12
- package/src/app/routes/_authenticated/_customers/customers_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_facets/facets.tsx +9 -12
- package/src/app/routes/_authenticated/_facets/facets_.$facetId.values_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_facets/facets_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_global-settings/global-settings.tsx +10 -13
- package/src/app/routes/_authenticated/_orders/components/add-surcharge-form.tsx +139 -0
- package/src/app/routes/_authenticated/_orders/components/edit-order-table.tsx +3 -0
- package/src/app/routes/_authenticated/_orders/components/fulfill-order-dialog.tsx +3 -1
- package/src/app/routes/_authenticated/_orders/components/order-address.tsx +3 -3
- package/src/app/routes/_authenticated/_orders/components/order-detail-shared.tsx +41 -41
- package/src/app/routes/_authenticated/_orders/components/order-history/order-history-utils.tsx +1 -1
- package/src/app/routes/_authenticated/_orders/components/order-modification-summary.tsx +49 -11
- package/src/app/routes/_authenticated/_orders/components/order-table.tsx +4 -1
- package/src/app/routes/_authenticated/_orders/components/use-transition-order-to-state.tsx +2 -3
- package/src/app/routes/_authenticated/_orders/orders.tsx +3 -3
- package/src/app/routes/_authenticated/_orders/orders_.$id_.modify.tsx +12 -3
- package/src/app/routes/_authenticated/_orders/orders_.draft.$id.tsx +27 -30
- package/src/app/routes/_authenticated/_orders/utils/use-modify-order.ts +23 -0
- package/src/app/routes/_authenticated/_payment-methods/payment-methods.tsx +9 -12
- package/src/app/routes/_authenticated/_payment-methods/payment-methods_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_product-variants/components/add-currency-dropdown.tsx +3 -3
- package/src/app/routes/_authenticated/_product-variants/components/add-stock-location-dropdown.tsx +2 -2
- package/src/app/routes/_authenticated/_product-variants/product-variants.graphql.ts +1 -0
- package/src/app/routes/_authenticated/_product-variants/product-variants_.$id.tsx +10 -12
- package/src/app/routes/_authenticated/_products/products.graphql.ts +1 -0
- package/src/app/routes/_authenticated/_products/products.tsx +15 -18
- package/src/app/routes/_authenticated/_products/products_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_products/products_.$productId.option-groups.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_products/products_.$productId.option-groups.$productOptionGroupId.options_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_profile/profile.tsx +3 -3
- package/src/app/routes/_authenticated/_promotions/promotions.tsx +9 -12
- package/src/app/routes/_authenticated/_promotions/promotions_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_roles/roles.tsx +9 -12
- package/src/app/routes/_authenticated/_roles/roles_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_sellers/sellers.tsx +9 -12
- package/src/app/routes/_authenticated/_sellers/sellers_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_shipping-methods/shipping-methods.tsx +11 -12
- package/src/app/routes/_authenticated/_shipping-methods/shipping-methods_.$id.tsx +19 -20
- package/src/app/routes/_authenticated/_stock-locations/stock-locations.tsx +9 -12
- package/src/app/routes/_authenticated/_stock-locations/stock-locations_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_system/healthchecks.tsx +2 -3
- package/src/app/routes/_authenticated/_system/job-queue.tsx +3 -3
- package/src/app/routes/_authenticated/_tax-categories/tax-categories.tsx +9 -12
- package/src/app/routes/_authenticated/_tax-categories/tax-categories_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_tax-rates/tax-rates.tsx +9 -12
- package/src/app/routes/_authenticated/_tax-rates/tax-rates_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/_zones/components/zone-bulk-actions.tsx +49 -1
- package/src/app/routes/_authenticated/_zones/components/zone-countries-table.tsx +34 -16
- package/src/app/routes/_authenticated/_zones/zones.tsx +9 -12
- package/src/app/routes/_authenticated/_zones/zones_.$id.tsx +9 -12
- package/src/app/routes/_authenticated/index.tsx +5 -3
- package/src/i18n/locales/bg.po +3436 -0
- package/src/lib/components/data-input/datetime-input.tsx +1 -1
- package/src/lib/components/data-input/default-relation-input.tsx +1 -1
- package/src/lib/components/data-input/relation-selector.tsx +1 -1
- package/src/lib/components/data-input/string-list-input.tsx +188 -26
- package/src/lib/components/data-input/struct-form-input.tsx +175 -174
- package/src/lib/components/data-table/column-header-wrapper.tsx +1 -1
- package/src/lib/components/data-table/data-table-filter-badge.tsx +2 -2
- package/src/lib/components/data-table/data-table.tsx +1 -1
- package/src/lib/components/data-table/use-generated-columns.tsx +1 -1
- package/src/lib/components/layout/channel-switcher.tsx +6 -2
- package/src/lib/components/layout/content-language-selector.tsx +6 -7
- package/src/lib/components/layout/dev-mode-indicator.tsx +7 -3
- package/src/lib/components/layout/language-dialog.tsx +26 -13
- package/src/lib/components/layout/manage-languages-dialog.tsx +10 -29
- package/src/lib/components/layout/nav-item-wrapper.tsx +1 -1
- package/src/lib/components/shared/asset/asset-gallery.tsx +8 -3
- package/src/lib/components/shared/configurable-operation-multi-selector.tsx +14 -16
- package/src/lib/components/shared/custom-fields-form.tsx +14 -9
- package/src/lib/components/shared/language-selector.tsx +14 -6
- package/src/lib/components/shared/multi-select.tsx +1 -1
- package/src/lib/components/shared/navigation-confirmation.tsx +1 -1
- package/src/lib/components/shared/table-cell/order-table-cell-components.tsx +4 -4
- package/src/lib/components/ui/carousel.tsx +2 -2
- package/src/lib/components/ui/chart.tsx +1 -1
- package/src/lib/components/ui/context-menu.tsx +1 -1
- package/src/lib/components/ui/drawer.tsx +1 -1
- package/src/lib/components/ui/grid-layout.tsx +1 -1
- package/src/lib/components/ui/input-group.tsx +1 -0
- package/src/lib/components/ui/input-otp.tsx +1 -1
- package/src/lib/components/ui/menubar.tsx +1 -1
- package/src/lib/components/ui/navigation-menu.tsx +1 -1
- package/src/lib/components/ui/progress.tsx +1 -1
- package/src/lib/components/ui/radio-group.tsx +1 -1
- package/src/lib/components/ui/resizable.tsx +1 -1
- package/src/lib/components/ui/select.tsx +1 -1
- package/src/lib/components/ui/slider.tsx +1 -1
- package/src/lib/components/ui/toggle-group.tsx +2 -2
- package/src/lib/components/ui/toggle.tsx +1 -1
- package/src/lib/framework/component-registry/component-registry.tsx +2 -6
- package/src/lib/framework/document-introspection/add-custom-fields.spec.ts +907 -1
- package/src/lib/framework/document-introspection/add-custom-fields.ts +248 -119
- package/src/lib/framework/extension-api/display-component-extensions.tsx +4 -3
- package/src/lib/framework/extension-api/logic/detail-forms.ts +0 -13
- package/src/lib/framework/extension-api/logic/navigation.ts +1 -1
- package/src/lib/framework/extension-api/types/data-table.ts +4 -2
- package/src/lib/framework/extension-api/types/layout.ts +34 -1
- package/src/lib/framework/extension-api/types/navigation.ts +7 -2
- package/src/lib/framework/form-engine/use-generated-form.tsx +7 -1
- package/src/lib/framework/history-entry/history-entry.tsx +1 -1
- package/src/lib/framework/layout-engine/action-bar-item-wrapper.tsx +185 -0
- package/src/lib/framework/layout-engine/dev-mode-button.tsx +15 -13
- package/src/lib/framework/layout-engine/location-wrapper.tsx +3 -1
- package/src/lib/framework/layout-engine/page-layout.spec.tsx +138 -0
- package/src/lib/framework/layout-engine/page-layout.tsx +294 -69
- package/src/lib/framework/nav-menu/nav-menu-extensions.ts +1 -1
- package/src/lib/framework/page/detail-page-route-loader.tsx +1 -1
- package/src/lib/framework/page/page-api.ts +1 -1
- package/src/lib/framework/page/use-detail-page.ts +4 -2
- package/src/lib/framework/page/use-extended-router.tsx +20 -16
- package/src/lib/framework/registry/registry-types.ts +2 -1
- package/src/lib/graphql/api.ts +3 -8
- package/src/lib/graphql/graphql-env.d.ts +29 -10
- package/src/lib/hooks/use-permissions.ts +3 -3
- package/src/lib/hooks/use-sorted-languages.ts +41 -0
- package/src/lib/index.ts +1 -0
- package/src/lib/lib/load-i18n-messages.ts +4 -1
- package/src/lib/providers/channel-provider.tsx +11 -7
- package/src/lib/utils/config-utils.ts +19 -0
- package/src/lib/virtual.d.ts +3 -0
- package/LICENSE.md +0 -42
- package/src/app/routes/_authenticated/_facets/components/edit-facet-value.tsx +0 -129
- /package/src/{app/routes/_authenticated/_global-settings → lib}/utils/global-languages.ts +0 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { CopyableText } from '@/vdb/components/shared/copyable-text.js';
|
|
2
|
+
import { PermissionGuard } from '@/vdb/components/shared/permission-guard.js';
|
|
3
|
+
import { Popover, PopoverContent, PopoverTrigger } from '@/vdb/components/ui/popover.js';
|
|
4
|
+
import { usePage } from '@/vdb/hooks/use-page.js';
|
|
5
|
+
import { useUserSettings } from '@/vdb/hooks/use-user-settings.js';
|
|
6
|
+
import { cn } from '@/vdb/lib/utils.js';
|
|
7
|
+
import React, { useEffect, useState } from 'react';
|
|
8
|
+
import { DevModeButton } from './dev-mode-button.js';
|
|
9
|
+
|
|
10
|
+
// Singleton state for hover tracking across all action bar items
|
|
11
|
+
let globalHoveredActionBarItemId: string | null = null;
|
|
12
|
+
const actionBarHoverListeners: Set<(id: string | null) => void> = new Set();
|
|
13
|
+
|
|
14
|
+
const setGlobalHoveredActionBarItemId = (id: string | null) => {
|
|
15
|
+
globalHoveredActionBarItemId = id;
|
|
16
|
+
actionBarHoverListeners.forEach(listener => listener(id));
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Internal component that renders the dev-mode wrapper with hover highlight and popover.
|
|
21
|
+
* Shared between ActionBarItem and ActionBarItemWrapper to eliminate duplication.
|
|
22
|
+
*/
|
|
23
|
+
function DevModeActionBarWrapper({
|
|
24
|
+
children,
|
|
25
|
+
itemId,
|
|
26
|
+
}: Readonly<{
|
|
27
|
+
children: React.ReactNode;
|
|
28
|
+
itemId: string;
|
|
29
|
+
}>) {
|
|
30
|
+
const page = usePage();
|
|
31
|
+
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
|
32
|
+
const [hoveredId, setHoveredId] = useState<string | null>(globalHoveredActionBarItemId);
|
|
33
|
+
|
|
34
|
+
// Generate a unique tracking ID that includes the page context
|
|
35
|
+
const trackingId = `${page.pageId ?? 'unknown'}-actionbar-${itemId}`;
|
|
36
|
+
const isHovered = hoveredId === trackingId;
|
|
37
|
+
|
|
38
|
+
// Subscribe to global hover changes
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
const listener = (newHoveredId: string | null) => {
|
|
41
|
+
setHoveredId(newHoveredId);
|
|
42
|
+
};
|
|
43
|
+
actionBarHoverListeners.add(listener);
|
|
44
|
+
return () => {
|
|
45
|
+
actionBarHoverListeners.delete(listener);
|
|
46
|
+
};
|
|
47
|
+
}, []);
|
|
48
|
+
|
|
49
|
+
const handleMouseEnter = () => {
|
|
50
|
+
setGlobalHoveredActionBarItemId(trackingId);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const handleMouseLeave = () => {
|
|
54
|
+
setGlobalHoveredActionBarItemId(null);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<div
|
|
59
|
+
className={cn(
|
|
60
|
+
'ring-1 ring-transparent rounded transition-all delay-50 relative',
|
|
61
|
+
isHovered || isPopoverOpen ? 'ring-dev-mode ring-offset-1 ring-offset-background' : '',
|
|
62
|
+
)}
|
|
63
|
+
onMouseEnter={handleMouseEnter}
|
|
64
|
+
onMouseLeave={handleMouseLeave}
|
|
65
|
+
>
|
|
66
|
+
<div
|
|
67
|
+
className={cn(
|
|
68
|
+
'absolute -top-1 -right-1 transition-all delay-50 z-10',
|
|
69
|
+
isHovered || isPopoverOpen ? 'visible' : 'invisible',
|
|
70
|
+
)}
|
|
71
|
+
>
|
|
72
|
+
<Popover open={isPopoverOpen} onOpenChange={setIsPopoverOpen}>
|
|
73
|
+
<PopoverTrigger asChild>
|
|
74
|
+
<DevModeButton className="h-5 w-5 top-0 -start-4" />
|
|
75
|
+
</PopoverTrigger>
|
|
76
|
+
<PopoverContent className="w-40 p-2">
|
|
77
|
+
<div className="space-y-1.5">
|
|
78
|
+
{page.pageId && (
|
|
79
|
+
<div className="text-xs">
|
|
80
|
+
<div className="text-muted-foreground mb-0.5">pageId</div>
|
|
81
|
+
<CopyableText text={page.pageId} />
|
|
82
|
+
</div>
|
|
83
|
+
)}
|
|
84
|
+
<div className="text-xs">
|
|
85
|
+
<div className="text-muted-foreground mb-0.5">itemId</div>
|
|
86
|
+
<CopyableText text={itemId} />
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
</PopoverContent>
|
|
90
|
+
</Popover>
|
|
91
|
+
</div>
|
|
92
|
+
{children}
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @description
|
|
99
|
+
* Props for the ActionBarItem component.
|
|
100
|
+
*
|
|
101
|
+
* @docsCategory page-layout
|
|
102
|
+
* @docsPage PageActionBar
|
|
103
|
+
* @since 3.5.2
|
|
104
|
+
*/
|
|
105
|
+
export interface ActionBarItemProps {
|
|
106
|
+
/**
|
|
107
|
+
* @description
|
|
108
|
+
* The content of the action bar item, typically a Button component.
|
|
109
|
+
*/
|
|
110
|
+
children: React.ReactNode;
|
|
111
|
+
/**
|
|
112
|
+
* @description
|
|
113
|
+
* A unique identifier for this action bar item. This ID is used by extensions
|
|
114
|
+
* to position their items relative to this one via `position.itemId`.
|
|
115
|
+
*
|
|
116
|
+
* Note: Extensions should use this exact `itemId` value in their `position.itemId`
|
|
117
|
+
* field to target this item.
|
|
118
|
+
*/
|
|
119
|
+
itemId: string;
|
|
120
|
+
/**
|
|
121
|
+
* @description
|
|
122
|
+
* If provided, the logged-in user must have one or more of the specified
|
|
123
|
+
* permissions in order for the item to render.
|
|
124
|
+
*/
|
|
125
|
+
requiresPermission?: string | string[];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* @description
|
|
130
|
+
* A component for wrapping action bar items with a unique ID. This should be used inside
|
|
131
|
+
* the {@link PageActionBarRight} component. Each item is given an `itemId` which allows
|
|
132
|
+
* extensions to position their items relative to it using `position.itemId`.
|
|
133
|
+
*
|
|
134
|
+
* In developer mode, hovering over the item will show a popover with the `pageId` and `itemId`,
|
|
135
|
+
* making it easy to discover the correct IDs for extension positioning.
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* ```tsx
|
|
139
|
+
* <PageActionBarRight>
|
|
140
|
+
* <ActionBarItem itemId="save-button" requiresPermission={['UpdateProduct']}>
|
|
141
|
+
* <Button type="submit">Save</Button>
|
|
142
|
+
* </ActionBarItem>
|
|
143
|
+
* </PageActionBarRight>
|
|
144
|
+
* ```
|
|
145
|
+
*
|
|
146
|
+
* @docsCategory page-layout
|
|
147
|
+
* @docsPage PageActionBar
|
|
148
|
+
* @since 3.5.2
|
|
149
|
+
*/
|
|
150
|
+
export function ActionBarItem({ children, itemId, requiresPermission }: Readonly<ActionBarItemProps>) {
|
|
151
|
+
const { settings } = useUserSettings();
|
|
152
|
+
|
|
153
|
+
const content = requiresPermission ? (
|
|
154
|
+
<PermissionGuard requires={requiresPermission}>{children}</PermissionGuard>
|
|
155
|
+
) : (
|
|
156
|
+
children
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
if (settings.devMode) {
|
|
160
|
+
return <DevModeActionBarWrapper itemId={itemId}>{content}</DevModeActionBarWrapper>;
|
|
161
|
+
}
|
|
162
|
+
return <>{content}</>;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Internal wrapper component used by PageActionBarRight to wrap extension items
|
|
167
|
+
* with dev-mode location information. Unlike ActionBarItem, this does not handle
|
|
168
|
+
* permissions (those are handled by PageActionBarItem).
|
|
169
|
+
*
|
|
170
|
+
* @internal
|
|
171
|
+
*/
|
|
172
|
+
export function ActionBarItemWrapper({
|
|
173
|
+
children,
|
|
174
|
+
itemId,
|
|
175
|
+
}: Readonly<{
|
|
176
|
+
children: React.ReactNode;
|
|
177
|
+
itemId: string;
|
|
178
|
+
}>) {
|
|
179
|
+
const { settings } = useUserSettings();
|
|
180
|
+
|
|
181
|
+
if (settings.devMode) {
|
|
182
|
+
return <DevModeActionBarWrapper itemId={itemId}>{children}</DevModeActionBarWrapper>;
|
|
183
|
+
}
|
|
184
|
+
return <>{children}</>;
|
|
185
|
+
}
|
|
@@ -1,24 +1,26 @@
|
|
|
1
1
|
import { Button } from '@/vdb/components/ui/button.js';
|
|
2
2
|
import { cn } from '@/vdb/lib/utils.js';
|
|
3
|
-
import {
|
|
3
|
+
import { Locate } from 'lucide-react';
|
|
4
4
|
import { forwardRef } from 'react';
|
|
5
5
|
|
|
6
6
|
export const DevModeButton = forwardRef<HTMLButtonElement, React.ComponentProps<typeof Button>>(
|
|
7
7
|
(props, ref) => {
|
|
8
8
|
const { className, ...rest } = props;
|
|
9
9
|
return (
|
|
10
|
-
<
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
10
|
+
<div className="relative">
|
|
11
|
+
<Button
|
|
12
|
+
ref={ref}
|
|
13
|
+
variant="secondary"
|
|
14
|
+
size="icon"
|
|
15
|
+
className={cn(
|
|
16
|
+
'h-6 w-6 absolute z-50 rounded-md bg-background text-dev-mode/70 hover:bg-background hover:text-dev-mode border border-dev-mode shadow-sm',
|
|
17
|
+
className,
|
|
18
|
+
)}
|
|
19
|
+
{...rest}
|
|
20
|
+
>
|
|
21
|
+
<Locate className="w-4 h-4" />
|
|
22
|
+
</Button>
|
|
23
|
+
</div>
|
|
22
24
|
);
|
|
23
25
|
},
|
|
24
26
|
);
|
|
@@ -86,7 +86,9 @@ export function LocationWrapper({ children, identifier }: Readonly<LocationWrapp
|
|
|
86
86
|
>
|
|
87
87
|
<Popover open={isPopoverOpen} onOpenChange={setIsPopoverOpen}>
|
|
88
88
|
<PopoverTrigger asChild>
|
|
89
|
-
<DevModeButton
|
|
89
|
+
<DevModeButton
|
|
90
|
+
className={isPageWrapper ? '-top-8 -end-1 border-2' : '-top-4 -end-4'}
|
|
91
|
+
/>
|
|
90
92
|
</PopoverTrigger>
|
|
91
93
|
<PopoverContent className="w-48 p-3">
|
|
92
94
|
<div className="space-y-2">
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { renderToStaticMarkup } from 'react-dom/server';
|
|
3
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import { PageBlock, PageLayout } from './page-layout.js';
|
|
6
|
+
import { registerDashboardPageBlock } from './layout-extensions.js';
|
|
7
|
+
import { PageContext } from './page-provider.js';
|
|
8
|
+
import { globalRegistry } from '../registry/global-registry.js';
|
|
9
|
+
import { UserSettingsContext, type UserSettingsContextType } from '../../providers/user-settings.js';
|
|
10
|
+
|
|
11
|
+
const useMediaQueryMock = vi.hoisted(() => vi.fn());
|
|
12
|
+
const useCopyToClipboardMock = vi.hoisted(() => vi.fn(() => [null, vi.fn()]));
|
|
13
|
+
|
|
14
|
+
vi.mock('@uidotdev/usehooks', () => ({
|
|
15
|
+
useMediaQuery: useMediaQueryMock,
|
|
16
|
+
useCopyToClipboard: useCopyToClipboardMock,
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
function registerBlock(
|
|
20
|
+
id: string,
|
|
21
|
+
order: 'before' | 'after' | 'replace',
|
|
22
|
+
pageId = 'customer-list',
|
|
23
|
+
): void {
|
|
24
|
+
registerDashboardPageBlock({
|
|
25
|
+
id,
|
|
26
|
+
title: id,
|
|
27
|
+
location: {
|
|
28
|
+
pageId,
|
|
29
|
+
column: 'main',
|
|
30
|
+
position: { blockId: 'list-table', order },
|
|
31
|
+
},
|
|
32
|
+
component: ({ context }) => <div data-testid={`page-block-${id}`}>{context.pageId}</div>,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function renderPageLayout(children: React.ReactNode, { isDesktop = true } = {}) {
|
|
37
|
+
useMediaQueryMock.mockReturnValue(isDesktop);
|
|
38
|
+
const noop = () => undefined;
|
|
39
|
+
const contextValue = {
|
|
40
|
+
settings: {
|
|
41
|
+
displayLanguage: 'en',
|
|
42
|
+
contentLanguage: 'en',
|
|
43
|
+
theme: 'system',
|
|
44
|
+
displayUiExtensionPoints: false,
|
|
45
|
+
mainNavExpanded: true,
|
|
46
|
+
activeChannelId: '',
|
|
47
|
+
devMode: false,
|
|
48
|
+
hasSeenOnboarding: false,
|
|
49
|
+
tableSettings: {},
|
|
50
|
+
},
|
|
51
|
+
settingsStoreIsAvailable: true,
|
|
52
|
+
setDisplayLanguage: noop,
|
|
53
|
+
setDisplayLocale: noop,
|
|
54
|
+
setContentLanguage: noop,
|
|
55
|
+
setTheme: noop,
|
|
56
|
+
setDisplayUiExtensionPoints: noop,
|
|
57
|
+
setMainNavExpanded: noop,
|
|
58
|
+
setActiveChannelId: noop,
|
|
59
|
+
setDevMode: noop,
|
|
60
|
+
setHasSeenOnboarding: noop,
|
|
61
|
+
setTableSettings: () => undefined,
|
|
62
|
+
setWidgetLayout: noop,
|
|
63
|
+
} as UserSettingsContextType;
|
|
64
|
+
|
|
65
|
+
return renderToStaticMarkup(
|
|
66
|
+
<UserSettingsContext.Provider value={contextValue}>
|
|
67
|
+
<PageContext.Provider value={{ pageId: 'customer-list' }}>
|
|
68
|
+
<PageLayout>{children}</PageLayout>
|
|
69
|
+
</PageContext.Provider>
|
|
70
|
+
</UserSettingsContext.Provider>,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function getRenderedBlockIds(markup: string) {
|
|
75
|
+
return Array.from(markup.matchAll(/data-testid="(page-block-[^"]+)"/g)).map(match => match[1]);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
describe('PageLayout', () => {
|
|
79
|
+
beforeEach(() => {
|
|
80
|
+
useMediaQueryMock.mockReset();
|
|
81
|
+
useCopyToClipboardMock.mockReset();
|
|
82
|
+
useCopyToClipboardMock.mockReturnValue([null, vi.fn()]);
|
|
83
|
+
const pageBlockRegistry = globalRegistry.get('dashboardPageBlockRegistry');
|
|
84
|
+
pageBlockRegistry.clear();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('renders multiple before/after extension blocks in registration order', () => {
|
|
88
|
+
registerBlock('before-1', 'before');
|
|
89
|
+
registerBlock('before-2', 'before');
|
|
90
|
+
registerBlock('after-1', 'after');
|
|
91
|
+
|
|
92
|
+
const markup = renderPageLayout(
|
|
93
|
+
<PageBlock column="main" blockId="list-table">
|
|
94
|
+
<div data-testid="page-block-original">original</div>
|
|
95
|
+
</PageBlock>,
|
|
96
|
+
{ isDesktop: true },
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
expect(getRenderedBlockIds(markup)).toEqual([
|
|
100
|
+
'page-block-before-1',
|
|
101
|
+
'page-block-before-2',
|
|
102
|
+
'page-block-original',
|
|
103
|
+
'page-block-after-1',
|
|
104
|
+
]);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('replaces original block when replacement extensions are registered', () => {
|
|
108
|
+
registerBlock('replacement-1', 'replace');
|
|
109
|
+
registerBlock('replacement-2', 'replace');
|
|
110
|
+
|
|
111
|
+
const markup = renderPageLayout(
|
|
112
|
+
<PageBlock column="main" blockId="list-table">
|
|
113
|
+
<div data-testid="page-block-original">original</div>
|
|
114
|
+
</PageBlock>,
|
|
115
|
+
{ isDesktop: true },
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
expect(getRenderedBlockIds(markup)).toEqual(['page-block-replacement-1', 'page-block-replacement-2']);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('renders extension blocks in mobile layout', () => {
|
|
122
|
+
registerBlock('before-mobile', 'before');
|
|
123
|
+
registerBlock('after-mobile', 'after');
|
|
124
|
+
|
|
125
|
+
const markup = renderPageLayout(
|
|
126
|
+
<PageBlock column="main" blockId="list-table">
|
|
127
|
+
<div data-testid="page-block-original">original</div>
|
|
128
|
+
</PageBlock>,
|
|
129
|
+
{ isDesktop: false },
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
expect(getRenderedBlockIds(markup)).toEqual([
|
|
133
|
+
'page-block-before-mobile',
|
|
134
|
+
'page-block-original',
|
|
135
|
+
'page-block-after-mobile',
|
|
136
|
+
]);
|
|
137
|
+
});
|
|
138
|
+
});
|