@vendure/dashboard 3.5.0-minor-202510071456 → 3.5.0-minor-202510161257

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.
Files changed (201) hide show
  1. package/dist/plugin/dashboard.plugin.js +1 -1
  2. package/dist/vite/utils/ast-utils.spec.js +3 -3
  3. package/dist/vite/vite-plugin-hmr.d.ts +8 -0
  4. package/dist/vite/vite-plugin-hmr.js +34 -0
  5. package/dist/vite/vite-plugin-theme.js +6 -6
  6. package/dist/vite/vite-plugin-transform-index.js +6 -1
  7. package/dist/vite/vite-plugin-vendure-dashboard.d.ts +31 -4
  8. package/dist/vite/vite-plugin-vendure-dashboard.js +89 -34
  9. package/package.json +17 -5
  10. package/src/app/app-providers.tsx +4 -1
  11. package/src/app/common/map-faceted-filter-fields.ts +21 -0
  12. package/src/app/main.tsx +3 -1
  13. package/src/app/routes/_authenticated/_administrators/administrators.graphql.ts +2 -2
  14. package/src/app/routes/_authenticated/_administrators/administrators.tsx +13 -3
  15. package/src/app/routes/_authenticated/_administrators/administrators_.$id.tsx +6 -13
  16. package/src/app/routes/_authenticated/_administrators/components/role-permissions-display.tsx +1 -1
  17. package/src/app/routes/_authenticated/_assets/assets.tsx +17 -1
  18. package/src/app/routes/_authenticated/_collections/collections.graphql.ts +1 -0
  19. package/src/app/routes/_authenticated/_collections/collections.tsx +5 -0
  20. package/src/app/routes/_authenticated/_collections/components/collection-bulk-actions.tsx +0 -1
  21. package/src/app/routes/_authenticated/_customers/customers.tsx +9 -5
  22. package/src/app/routes/_authenticated/_facets/components/facet-bulk-actions.tsx +0 -6
  23. package/src/app/routes/_authenticated/_facets/components/facet-value-bulk-actions.tsx +16 -0
  24. package/src/app/routes/_authenticated/_facets/components/facet-values-table.tsx +43 -12
  25. package/src/app/routes/_authenticated/_facets/facets_.$facetId.values_.$id.tsx +14 -5
  26. package/src/app/routes/_authenticated/_orders/components/edit-order-table.tsx +117 -92
  27. package/src/app/routes/_authenticated/_orders/components/order-detail-shared.tsx +1 -1
  28. package/src/app/routes/_authenticated/_orders/components/order-modification-summary.tsx +2 -1
  29. package/src/app/routes/_authenticated/_orders/components/order-table-totals.tsx +26 -27
  30. package/src/app/routes/_authenticated/_orders/components/order-table.tsx +5 -3
  31. package/src/app/routes/_authenticated/_orders/components/state-transition-control.tsx +6 -9
  32. package/src/app/routes/_authenticated/_orders/orders.graphql.ts +17 -1
  33. package/src/app/routes/_authenticated/_orders/orders_.$id_.modify.tsx +48 -281
  34. package/src/app/routes/_authenticated/_orders/orders_.draft.$id.tsx +59 -40
  35. package/src/app/routes/_authenticated/_orders/utils/order-utils.ts +73 -0
  36. package/src/app/routes/_authenticated/_orders/utils/use-modify-order.ts +312 -0
  37. package/src/app/routes/_authenticated/_payment-methods/payment-methods.graphql.ts +2 -2
  38. package/src/app/routes/_authenticated/_payment-methods/payment-methods.tsx +4 -0
  39. package/src/app/routes/_authenticated/_products/components/product-bulk-actions.tsx +0 -6
  40. package/src/app/routes/_authenticated/_products/products.tsx +6 -2
  41. package/src/app/routes/_authenticated/_products/products_.$productId.option-groups.$productOptionGroupId.options_.$id.tsx +4 -8
  42. package/src/app/routes/_authenticated/_promotions/components/promotion-bulk-actions.tsx +0 -10
  43. package/src/app/routes/_authenticated/_promotions/promotions.graphql.ts +2 -2
  44. package/src/app/routes/_authenticated/_promotions/promotions.tsx +12 -0
  45. package/src/app/routes/_authenticated/_promotions/promotions_.$id.tsx +6 -2
  46. package/src/app/routes/_authenticated/_sellers/sellers.graphql.ts +2 -2
  47. package/src/app/routes/_authenticated/_shipping-methods/shipping-methods.graphql.ts +2 -2
  48. package/src/app/routes/_authenticated/_shipping-methods/shipping-methods.tsx +4 -0
  49. package/src/app/routes/_authenticated/_shipping-methods/shipping-methods_.$id.tsx +4 -10
  50. package/src/app/routes/_authenticated/_stock-locations/stock-locations.graphql.ts +2 -2
  51. package/src/app/routes/_authenticated/_tax-categories/tax-categories.graphql.ts +2 -2
  52. package/src/app/routes/_authenticated/_tax-rates/tax-rates.tsx +9 -0
  53. package/src/app/routes/_authenticated/_tax-rates/tax-rates_.$id.tsx +1 -0
  54. package/src/app/routes/_authenticated/_zones/zones.graphql.ts +2 -2
  55. package/src/app/routes/login.tsx +2 -2
  56. package/src/i18n/locales/ar.po +420 -289
  57. package/src/i18n/locales/cs.po +420 -289
  58. package/src/i18n/locales/de.po +420 -289
  59. package/src/i18n/locales/en.po +420 -289
  60. package/src/i18n/locales/es.po +420 -289
  61. package/src/i18n/locales/fa.po +420 -289
  62. package/src/i18n/locales/fr.po +468 -337
  63. package/src/i18n/locales/he.po +420 -289
  64. package/src/i18n/locales/hr.po +420 -289
  65. package/src/i18n/locales/it.po +420 -289
  66. package/src/i18n/locales/ja.po +420 -289
  67. package/src/i18n/locales/nb.po +420 -289
  68. package/src/i18n/locales/ne.po +420 -289
  69. package/src/i18n/locales/pl.po +420 -289
  70. package/src/i18n/locales/pt_BR.po +420 -289
  71. package/src/i18n/locales/pt_PT.po +420 -289
  72. package/src/i18n/locales/ru.po +420 -289
  73. package/src/i18n/locales/sv.po +420 -289
  74. package/src/i18n/locales/tr.po +420 -289
  75. package/src/i18n/locales/uk.po +420 -289
  76. package/src/i18n/locales/zh_Hans.po +420 -289
  77. package/src/i18n/locales/zh_Hant.po +420 -289
  78. package/src/lib/components/data-input/affixed-input.stories.tsx +93 -0
  79. package/src/lib/components/data-input/affixed-input.tsx +5 -2
  80. package/src/lib/components/data-input/boolean-input.stories.tsx +102 -0
  81. package/src/lib/components/data-input/checkbox-input.stories.tsx +61 -0
  82. package/src/lib/components/data-input/datetime-input.stories.tsx +62 -0
  83. package/src/lib/components/data-input/datetime-input.tsx +27 -13
  84. package/src/lib/components/data-input/default-relation-input.tsx +18 -12
  85. package/src/lib/components/data-input/money-input.stories.tsx +88 -0
  86. package/src/lib/components/data-input/number-input.stories.tsx +103 -0
  87. package/src/lib/components/data-input/number-input.tsx +10 -4
  88. package/src/lib/components/data-input/password-input.stories.tsx +65 -0
  89. package/src/lib/components/data-input/rich-text-input.stories.tsx +92 -0
  90. package/src/lib/components/data-input/slug-input.stories.tsx +232 -0
  91. package/src/lib/components/data-input/slug-input.tsx +9 -10
  92. package/src/lib/components/data-input/text-input.stories.tsx +52 -0
  93. package/src/lib/components/data-input/textarea-input.stories.tsx +55 -0
  94. package/src/lib/components/data-table/add-filter-menu.tsx +6 -1
  95. package/src/lib/components/data-table/column-header-wrapper.tsx +106 -0
  96. package/src/lib/components/data-table/data-table-bulk-action-item.tsx +11 -9
  97. package/src/lib/components/data-table/data-table-bulk-actions.tsx +4 -4
  98. package/src/lib/components/data-table/data-table-column-header.tsx +17 -14
  99. package/src/lib/components/data-table/data-table-faceted-filter.tsx +33 -11
  100. package/src/lib/components/data-table/data-table-filter-badge-editable.tsx +35 -0
  101. package/src/lib/components/data-table/data-table-filter-badge.tsx +23 -16
  102. package/src/lib/components/data-table/data-table-filter-dialog.tsx +28 -8
  103. package/src/lib/components/data-table/data-table-pagination.tsx +23 -7
  104. package/src/lib/components/data-table/data-table.stories.tsx +249 -0
  105. package/src/lib/components/data-table/data-table.tsx +37 -9
  106. package/src/lib/components/data-table/filters/data-table-datetime-filter.tsx +79 -34
  107. package/src/lib/components/data-table/use-generated-columns.tsx +55 -27
  108. package/src/lib/components/layout/nav-user.tsx +19 -13
  109. package/src/lib/components/login/login-form.tsx +39 -123
  110. package/src/lib/components/shared/alerts.tsx +29 -17
  111. package/src/lib/components/shared/asset/asset-bulk-actions.tsx +3 -3
  112. package/src/lib/components/shared/asset/asset-gallery.stories.tsx +76 -0
  113. package/src/lib/components/shared/asset/asset-gallery.tsx +147 -113
  114. package/src/lib/components/shared/asset/asset-picker-dialog.stories.tsx +58 -0
  115. package/src/lib/components/shared/customer-group-selector.tsx +5 -2
  116. package/src/lib/components/shared/detail-page-button.stories.tsx +52 -0
  117. package/src/lib/components/shared/facet-value-selector.stories.tsx +48 -0
  118. package/src/lib/components/shared/facet-value-selector.tsx +130 -34
  119. package/src/lib/components/shared/paginated-list-data-table.stories.tsx +212 -0
  120. package/src/lib/components/shared/paginated-list-data-table.tsx +12 -12
  121. package/src/lib/components/shared/permission-guard.stories.tsx +46 -0
  122. package/src/lib/components/shared/remove-from-channel-bulk-action.tsx +2 -0
  123. package/src/lib/components/shared/rich-text-editor/responsive-toolbar.tsx +8 -4
  124. package/src/lib/components/shared/rich-text-editor/rich-text-editor.tsx +1 -0
  125. package/src/lib/components/shared/table-cell/order-table-cell-components.tsx +40 -0
  126. package/src/lib/components/shared/vendure-image.stories.tsx +167 -0
  127. package/src/lib/components/shared/vendure-image.tsx +6 -7
  128. package/src/lib/components/ui/accordion.stories.tsx +33 -0
  129. package/src/lib/components/ui/alert-dialog.stories.tsx +48 -0
  130. package/src/lib/components/ui/alert.stories.tsx +35 -0
  131. package/src/lib/components/ui/aspect-ratio.stories.tsx +28 -0
  132. package/src/lib/components/ui/badge.stories.tsx +28 -0
  133. package/src/lib/components/ui/breadcrumb.stories.tsx +41 -0
  134. package/src/lib/components/ui/button.stories.tsx +38 -0
  135. package/src/lib/components/ui/calendar.stories.tsx +22 -0
  136. package/src/lib/components/ui/card.stories.tsx +28 -0
  137. package/src/lib/components/ui/carousel.stories.tsx +34 -0
  138. package/src/lib/components/ui/checkbox.stories.tsx +31 -0
  139. package/src/lib/components/ui/collapsible.stories.tsx +39 -0
  140. package/src/lib/components/ui/command.stories.tsx +44 -0
  141. package/src/lib/components/ui/context-menu.stories.tsx +38 -0
  142. package/src/lib/components/ui/dialog.stories.tsx +52 -0
  143. package/src/lib/components/ui/drawer.stories.tsx +50 -0
  144. package/src/lib/components/ui/dropdown-menu.stories.tsx +41 -0
  145. package/src/lib/components/ui/hover-card.stories.tsx +38 -0
  146. package/src/lib/components/ui/input-group.tsx +148 -0
  147. package/src/lib/components/ui/input-otp.stories.tsx +30 -0
  148. package/src/lib/components/ui/input.stories.tsx +38 -0
  149. package/src/lib/components/ui/label.stories.tsx +24 -0
  150. package/src/lib/components/ui/menubar.stories.tsx +53 -0
  151. package/src/lib/components/ui/navigation-menu.stories.tsx +54 -0
  152. package/src/lib/components/ui/pagination.stories.tsx +51 -0
  153. package/src/lib/components/ui/password-input.stories.tsx +32 -0
  154. package/src/lib/components/ui/password-input.tsx +33 -0
  155. package/src/lib/components/ui/popover.stories.tsx +33 -0
  156. package/src/lib/components/ui/progress.stories.tsx +27 -0
  157. package/src/lib/components/ui/radio-group.stories.tsx +34 -0
  158. package/src/lib/components/ui/resizable.stories.tsx +32 -0
  159. package/src/lib/components/ui/scroll-area.stories.tsx +31 -0
  160. package/src/lib/components/ui/select.stories.tsx +36 -0
  161. package/src/lib/components/ui/separator.stories.tsx +35 -0
  162. package/src/lib/components/ui/sheet.stories.tsx +50 -0
  163. package/src/lib/components/ui/sidebar-context.ts +16 -0
  164. package/src/lib/components/ui/sidebar.tsx +2 -13
  165. package/src/lib/components/ui/skeleton.stories.tsx +26 -0
  166. package/src/lib/components/ui/slider.stories.tsx +37 -0
  167. package/src/lib/components/ui/switch.stories.tsx +31 -0
  168. package/src/lib/components/ui/table.stories.tsx +52 -0
  169. package/src/lib/components/ui/tabs.stories.tsx +29 -0
  170. package/src/lib/components/ui/textarea.stories.tsx +32 -0
  171. package/src/lib/components/ui/toggle-group.stories.tsx +31 -0
  172. package/src/lib/components/ui/toggle.stories.tsx +39 -0
  173. package/src/lib/components/ui/tooltip.stories.tsx +30 -0
  174. package/src/lib/components/ui/tooltip.tsx +2 -2
  175. package/src/lib/framework/alert/alert-extensions.tsx +0 -11
  176. package/src/lib/framework/alert/alert-item.tsx +14 -19
  177. package/src/lib/framework/alert/alerts-indicator.tsx +14 -15
  178. package/src/lib/framework/alert/search-index-buffer-alert/search-index-buffer-alert.ts +41 -0
  179. package/src/lib/framework/component-registry/component-registry.tsx +3 -14
  180. package/src/lib/framework/dashboard-widget/base-widget.tsx +18 -9
  181. package/src/lib/framework/dashboard-widget/widget-filters-context.tsx +12 -11
  182. package/src/lib/framework/defaults.ts +9 -13
  183. package/src/lib/framework/extension-api/input-component-extensions.tsx +6 -1
  184. package/src/lib/framework/extension-api/logic/alerts.ts +3 -2
  185. package/src/lib/framework/extension-api/types/alerts.ts +12 -6
  186. package/src/lib/framework/extension-api/types/data-table.ts +5 -2
  187. package/src/lib/framework/extension-api/types/login.ts +0 -21
  188. package/src/lib/framework/layout-engine/custom-form-page.stories.tsx +344 -0
  189. package/src/lib/framework/layout-engine/page-layout.tsx +11 -9
  190. package/src/lib/framework/layout-engine/page.stories.tsx +275 -0
  191. package/src/lib/framework/nav-menu/nav-menu-extensions.ts +32 -19
  192. package/src/lib/framework/page/detail-page.stories.tsx +151 -0
  193. package/src/lib/framework/page/list-page.stories.tsx +217 -0
  194. package/src/lib/framework/page/list-page.tsx +8 -1
  195. package/src/lib/graphql/api.ts +18 -1
  196. package/src/lib/graphql/graphql-env.d.ts +1 -1
  197. package/src/lib/hooks/use-alerts.ts +84 -0
  198. package/src/lib/hooks/use-floating-bulk-actions.ts +2 -3
  199. package/src/lib/index.ts +12 -5
  200. package/src/lib/providers/alerts-provider.tsx +60 -0
  201. package/src/lib/providers/theme-provider.tsx +6 -3
@@ -7,17 +7,46 @@ export type NavMenuSectionPlacement = 'top' | 'bottom';
7
7
 
8
8
  /**
9
9
  * @description
10
- * The base configuration for navigation items and sections of the main app nav bar.
10
+ * Defines an items in the navigation menu.
11
11
  *
12
12
  * @docsCategory extensions-api
13
13
  * @docsPage Navigation
14
14
  * @since 3.4.0
15
15
  */
16
- interface NavMenuBaseItem {
16
+ interface NavMenuItem {
17
+ /**
18
+ * @description
19
+ * A unique ID for this nav menu item
20
+ */
17
21
  id: string;
22
+ /**
23
+ * @description
24
+ * The title that will appear in the nav menu
25
+ */
18
26
  title: string;
27
+ /**
28
+ * @description
29
+ * The url of the route which this nav item links to.
30
+ */
31
+ url: string;
32
+ /**
33
+ * @description
34
+ * An optional icon component to represent the item,
35
+ * which should be imported from `lucide-react`.
36
+ */
19
37
  icon?: LucideIcon;
38
+ /**
39
+ * @description
40
+ * The order is an number which allows you to control
41
+ * the relative position in relation to other items in the
42
+ * menu.
43
+ * A higher number appears further down the list.
44
+ */
20
45
  order?: number;
46
+ /**
47
+ * Whether this item should appear in the top of bottom section
48
+ * of the nav menu.
49
+ */
21
50
  placement?: NavMenuSectionPlacement;
22
51
  /**
23
52
  * @description
@@ -27,23 +56,7 @@ interface NavMenuBaseItem {
27
56
  requiresPermission?: string | string[];
28
57
  }
29
58
 
30
- /**
31
- * @description
32
- * Defines an items in the navigation menu.
33
- *
34
- * @docsCategory extensions-api
35
- * @docsPage Navigation
36
- * @since 3.4.0
37
- */
38
- export interface NavMenuItem extends NavMenuBaseItem {
39
- /**
40
- * @description
41
- * The url of the route which this nav item links to.
42
- */
43
- url: string;
44
- }
45
-
46
- export interface NavMenuSection extends NavMenuBaseItem {
59
+ export interface NavMenuSection extends Omit<NavMenuItem, 'url'> {
47
60
  defaultOpen?: boolean;
48
61
  items?: NavMenuItem[];
49
62
  }
@@ -0,0 +1,151 @@
1
+ import { graphql } from '@/vdb/graphql/graphql.js';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import { DemoRouterProvider } from '../../../../.storybook/providers.js';
4
+ import { DetailPage, DetailPageProps } from './detail-page.js';
5
+
6
+ // Sample GraphQL query for a product detail
7
+ const productFragment = graphql(`
8
+ fragment ProductDetail on Product {
9
+ id
10
+ createdAt
11
+ updatedAt
12
+ name
13
+ slug
14
+ description
15
+ enabled
16
+ featuredAsset {
17
+ id
18
+ }
19
+ assets {
20
+ id
21
+ }
22
+ facetValues {
23
+ id
24
+ }
25
+ translations {
26
+ id
27
+ languageCode
28
+ name
29
+ slug
30
+ description
31
+ }
32
+ }
33
+ `);
34
+
35
+ const productQuery = graphql(
36
+ `
37
+ query Product($id: ID!) {
38
+ product(id: $id) {
39
+ ...ProductDetail
40
+ }
41
+ }
42
+ `,
43
+ [productFragment],
44
+ );
45
+
46
+ const createProductDocument = graphql(`
47
+ mutation CreateProduct($input: CreateProductInput!) {
48
+ createProduct(input: $input) {
49
+ id
50
+ name
51
+ slug
52
+ description
53
+ enabled
54
+ }
55
+ }
56
+ `);
57
+
58
+ const updateProductDocument = graphql(`
59
+ mutation UpdateProduct($input: UpdateProductInput!) {
60
+ updateProduct(input: $input) {
61
+ id
62
+ name
63
+ slug
64
+ description
65
+ enabled
66
+ }
67
+ }
68
+ `);
69
+
70
+ function DetailPageStoryWrapper(props: Omit<DetailPageProps<any, any, any>, 'route'>) {
71
+ return (
72
+ <DemoRouterProvider
73
+ component={route => <DetailPage {...props} route={route} />}
74
+ path={'/products/$id'}
75
+ initialPath={'/products/1'}
76
+ />
77
+ );
78
+ }
79
+
80
+ const meta = {
81
+ title: 'Layout/DetailPage',
82
+ component: DetailPageStoryWrapper,
83
+ parameters: {
84
+ layout: 'fullscreen',
85
+ },
86
+ tags: ['autodocs'],
87
+ argTypes: {
88
+ pageId: {
89
+ control: 'text',
90
+ description: 'Unique identifier for the detail page',
91
+ },
92
+ entityName: {
93
+ control: 'text',
94
+ description: 'Name of the entity (inferred from query if not provided)',
95
+ },
96
+ title: {
97
+ control: false,
98
+ description: 'Function that returns the page title based on the entity',
99
+ },
100
+ queryDocument: {
101
+ control: false,
102
+ description: 'GraphQL query document for fetching entity data',
103
+ },
104
+ createDocument: {
105
+ control: false,
106
+ description: 'GraphQL mutation document for creating the entity',
107
+ },
108
+ updateDocument: {
109
+ control: false,
110
+ description: 'GraphQL mutation document for updating the entity',
111
+ },
112
+ setValuesForUpdate: {
113
+ control: false,
114
+ description: 'Function to map entity data to update input',
115
+ },
116
+ },
117
+ } satisfies Meta<typeof DetailPageStoryWrapper>;
118
+
119
+ export default meta;
120
+ type Story = StoryObj<typeof meta>;
121
+
122
+ /**
123
+ * Basic example of a DetailPage showing a product entity.
124
+ * This demonstrates the minimal configuration needed to render a detail page.
125
+ */
126
+ export const BasicDetail: Story = {
127
+ args: {
128
+ pageId: 'product-detail',
129
+ queryDocument: productQuery,
130
+ updateDocument: updateProductDocument,
131
+ title: entity => entity?.name || 'Product',
132
+ setValuesForUpdate: entity => ({
133
+ id: entity.id,
134
+ name: entity.name,
135
+ slug: entity.slug,
136
+ description: entity.description,
137
+ enabled: entity.enabled,
138
+ featuredAssetId: entity.featuredAsset?.id,
139
+ assetIds: entity.assets.map(asset => asset.id),
140
+ facetValueIds: entity.facetValues.map(facetValue => facetValue.id),
141
+ translations: entity.translations.map(translation => ({
142
+ id: translation.id,
143
+ languageCode: translation.languageCode,
144
+ name: translation.name,
145
+ slug: translation.slug,
146
+ description: translation.description,
147
+ customFields: (translation as any).customFields,
148
+ })),
149
+ }),
150
+ },
151
+ };
@@ -0,0 +1,217 @@
1
+ import { DetailPageButton } from '@/vdb/components/shared/detail-page-button.js';
2
+ import { Button } from '@/vdb/components/ui/button.js';
3
+ import { PageActionBarRight } from '@/vdb/framework/layout-engine/page-layout.js';
4
+ import { ListPage, ListPageProps } from '@/vdb/framework/page/list-page.js';
5
+ import { graphql } from '@/vdb/graphql/graphql.js';
6
+ import type { Meta, StoryObj } from '@storybook/react';
7
+ import { PlusIcon } from 'lucide-react';
8
+
9
+ import { DemoRouterProvider } from '../../../../.storybook/providers.js';
10
+
11
+ // Sample GraphQL query for countries
12
+ const countryItemFragment = graphql(`
13
+ fragment CountryItem on Country {
14
+ id
15
+ createdAt
16
+ updatedAt
17
+ name
18
+ code
19
+ enabled
20
+ }
21
+ `);
22
+
23
+ const countriesListQuery = graphql(
24
+ `
25
+ query CountriesList($options: CountryListOptions) {
26
+ countries(options: $options) {
27
+ items {
28
+ ...CountryItem
29
+ }
30
+ totalItems
31
+ }
32
+ }
33
+ `,
34
+ [countryItemFragment],
35
+ );
36
+
37
+ const deleteCountryDocument = graphql(`
38
+ mutation DeleteCountry($id: ID!) {
39
+ deleteCountry(id: $id) {
40
+ result
41
+ message
42
+ }
43
+ }
44
+ `);
45
+
46
+ function ListPageStoryWrapper(props: Omit<ListPageProps<any, any, any, any>, 'route'>) {
47
+ return <DemoRouterProvider component={route => <ListPage {...props} route={() => route} />} />;
48
+ }
49
+
50
+ const meta = {
51
+ title: 'Layout/ListPage',
52
+ component: ListPageStoryWrapper,
53
+ parameters: {
54
+ layout: 'fullscreen',
55
+ },
56
+ tags: ['autodocs'],
57
+ argTypes: {
58
+ pageId: {
59
+ control: 'text',
60
+ description: 'Unique identifier for the list page',
61
+ },
62
+ title: {
63
+ control: 'text',
64
+ description: 'Page title displayed in the header',
65
+ },
66
+ listQuery: {
67
+ control: false,
68
+ description: 'GraphQL query document for fetching list data',
69
+ },
70
+ deleteMutation: {
71
+ control: false,
72
+ description: 'GraphQL mutation document for deleting items',
73
+ },
74
+ customizeColumns: {
75
+ control: false,
76
+ description: 'Customize column rendering and behavior',
77
+ },
78
+ defaultVisibility: {
79
+ control: 'object',
80
+ description: 'Default visible columns',
81
+ },
82
+ },
83
+ } satisfies Meta<typeof ListPageStoryWrapper>;
84
+
85
+ export default meta;
86
+ type Story = StoryObj<typeof meta>;
87
+
88
+ /**
89
+ * Basic example of a ListPage showing countries from the demo API.
90
+ * This demonstrates the minimal configuration needed to render a list.
91
+ */
92
+ export const BasicList: Story = {
93
+ args: {
94
+ pageId: 'country-list',
95
+ listQuery: countriesListQuery,
96
+ title: 'Countries',
97
+ defaultVisibility: {
98
+ name: true,
99
+ code: true,
100
+ enabled: true,
101
+ },
102
+ },
103
+ };
104
+
105
+ /**
106
+ * ListPage with custom column rendering and delete mutation.
107
+ * Shows how to customize the name column to render as a detail page button.
108
+ */
109
+ export const WithCustomColumns: Story = {
110
+ args: {
111
+ pageId: 'country-list-custom',
112
+ listQuery: countriesListQuery,
113
+ deleteMutation: deleteCountryDocument,
114
+ title: 'Countries',
115
+ defaultVisibility: {
116
+ name: true,
117
+ code: true,
118
+ enabled: true,
119
+ },
120
+ customizeColumns: {
121
+ name: {
122
+ cell: ({ row }) => <DetailPageButton id={row.original.id} label={row.original.name} />,
123
+ },
124
+ },
125
+ },
126
+ };
127
+
128
+ /**
129
+ * ListPage with search functionality.
130
+ * Demonstrates how to configure search to filter on multiple fields.
131
+ */
132
+ export const WithSearch: Story = {
133
+ args: {
134
+ pageId: 'country-list-search',
135
+ listQuery: countriesListQuery,
136
+ title: 'Countries',
137
+ defaultVisibility: {
138
+ name: true,
139
+ code: true,
140
+ enabled: true,
141
+ },
142
+ onSearchTermChange: (searchTerm: string) => {
143
+ return {
144
+ name: {
145
+ contains: searchTerm,
146
+ },
147
+ code: {
148
+ contains: searchTerm,
149
+ },
150
+ };
151
+ },
152
+ transformVariables: (variables: any) => {
153
+ return {
154
+ ...variables,
155
+ options: {
156
+ ...variables.options,
157
+ filterOperator: 'OR',
158
+ },
159
+ };
160
+ },
161
+ customizeColumns: {
162
+ name: {
163
+ cell: ({ row }) => <DetailPageButton id={row.original.id} label={row.original.name} />,
164
+ },
165
+ },
166
+ },
167
+ };
168
+
169
+ /**
170
+ * Complete example with action bar and all features.
171
+ * Shows the full ListPage configuration including custom action buttons.
172
+ */
173
+ export const Complete: Story = {
174
+ args: {
175
+ pageId: 'country-list-complete',
176
+ listQuery: countriesListQuery,
177
+ deleteMutation: deleteCountryDocument,
178
+ title: 'Countries',
179
+ defaultVisibility: {
180
+ name: true,
181
+ code: true,
182
+ enabled: true,
183
+ },
184
+ onSearchTermChange: (searchTerm: string) => {
185
+ return {
186
+ name: {
187
+ contains: searchTerm,
188
+ },
189
+ code: {
190
+ contains: searchTerm,
191
+ },
192
+ };
193
+ },
194
+ transformVariables: (variables: any) => {
195
+ return {
196
+ ...variables,
197
+ options: {
198
+ ...variables.options,
199
+ filterOperator: 'OR',
200
+ },
201
+ };
202
+ },
203
+ customizeColumns: {
204
+ name: {
205
+ cell: ({ row }) => <DetailPageButton id={row.original.id} label={row.original.name} />,
206
+ },
207
+ },
208
+ children: (
209
+ <PageActionBarRight>
210
+ <Button>
211
+ <PlusIcon />
212
+ Add Country
213
+ </Button>
214
+ </PageActionBarRight>
215
+ ),
216
+ },
217
+ };
@@ -17,7 +17,13 @@ import { ColumnFiltersState, SortingState, Table } from '@tanstack/react-table';
17
17
  import { TableOptions } from '@tanstack/table-core';
18
18
 
19
19
  import { BulkAction } from '@/vdb/framework/extension-api/types/index.js';
20
- import { FullWidthPageBlock, Page, PageActionBar, PageLayout, PageTitle } from '../layout-engine/page-layout.js';
20
+ import {
21
+ FullWidthPageBlock,
22
+ Page,
23
+ PageActionBar,
24
+ PageLayout,
25
+ PageTitle,
26
+ } from '../layout-engine/page-layout.js';
21
27
 
22
28
  /**
23
29
  * @description
@@ -393,6 +399,7 @@ export interface ListPageProps<
393
399
  * body
394
400
  * customFields
395
401
  * }
402
+ * totalItems
396
403
  * }
397
404
  * }
398
405
  * `);
@@ -69,6 +69,20 @@ const awesomeClient = new AwesomeGraphQLClient({
69
69
  },
70
70
  });
71
71
 
72
+ /**
73
+ * @description
74
+ * Handles the scenario where there's an invalid channel token in local storage.
75
+ * Most often seen in local development when testing multiple backends on the same
76
+ * localhost origin.
77
+ */
78
+ function handleInvalidChannelToken(err: unknown) {
79
+ if (err instanceof Error) {
80
+ if ((err as any).extensions?.code === 'CHANNEL_NOT_FOUND') {
81
+ localStorage.removeItem(LS_KEY_SELECTED_CHANNEL_TOKEN);
82
+ }
83
+ }
84
+ }
85
+
72
86
  export type VariablesAndRequestHeadersArgs<V extends Variables> =
73
87
  V extends Record<any, never>
74
88
  ? [variables?: V, requestHeaders?: HeadersInit]
@@ -79,7 +93,10 @@ function query<T, V extends Variables = Variables>(
79
93
  variables?: V,
80
94
  ): Promise<T> {
81
95
  const documentString = typeof document === 'string' ? document : print(document);
82
- return awesomeClient.request(documentString, variables) as any;
96
+ return awesomeClient.request(documentString, variables).catch(err => {
97
+ handleInvalidChannelToken(err);
98
+ throw err;
99
+ }) as any;
83
100
  }
84
101
 
85
102
  function mutate<T, V extends Variables = Variables>(
@@ -416,7 +416,7 @@ export type introspection_types = {
416
416
  'TaxCategorySortParameter': { kind: 'INPUT_OBJECT'; name: 'TaxCategorySortParameter'; isOneOf: false; inputFields: [{ name: 'id'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'createdAt'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'updatedAt'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'name'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }]; };
417
417
  'TaxLine': { kind: 'OBJECT'; name: 'TaxLine'; fields: { 'description': { name: 'description'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'taxRate': { name: 'taxRate'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Float'; ofType: null; }; } }; }; };
418
418
  'TaxRate': { kind: 'OBJECT'; name: 'TaxRate'; fields: { 'category': { name: 'category'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'TaxCategory'; ofType: null; }; } }; 'createdAt': { name: 'createdAt'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; }; } }; 'customFields': { name: 'customFields'; type: { kind: 'SCALAR'; name: 'JSON'; ofType: null; } }; 'customerGroup': { name: 'customerGroup'; type: { kind: 'OBJECT'; name: 'CustomerGroup'; ofType: null; } }; 'enabled': { name: 'enabled'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'name': { name: 'name'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'updatedAt': { name: 'updatedAt'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; }; } }; 'value': { name: 'value'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Float'; ofType: null; }; } }; 'zone': { name: 'zone'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Zone'; ofType: null; }; } }; }; };
419
- 'TaxRateFilterParameter': { kind: 'INPUT_OBJECT'; name: 'TaxRateFilterParameter'; isOneOf: false; inputFields: [{ name: 'id'; type: { kind: 'INPUT_OBJECT'; name: 'IDOperators'; ofType: null; }; defaultValue: null }, { name: 'createdAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateOperators'; ofType: null; }; defaultValue: null }, { name: 'updatedAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateOperators'; ofType: null; }; defaultValue: null }, { name: 'name'; type: { kind: 'INPUT_OBJECT'; name: 'StringOperators'; ofType: null; }; defaultValue: null }, { name: 'enabled'; type: { kind: 'INPUT_OBJECT'; name: 'BooleanOperators'; ofType: null; }; defaultValue: null }, { name: 'value'; type: { kind: 'INPUT_OBJECT'; name: 'NumberOperators'; ofType: null; }; defaultValue: null }, { name: '_and'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'TaxRateFilterParameter'; ofType: null; }; }; }; defaultValue: null }, { name: '_or'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'TaxRateFilterParameter'; ofType: null; }; }; }; defaultValue: null }]; };
419
+ 'TaxRateFilterParameter': { kind: 'INPUT_OBJECT'; name: 'TaxRateFilterParameter'; isOneOf: false; inputFields: [{ name: 'zoneId'; type: { kind: 'INPUT_OBJECT'; name: 'IDOperators'; ofType: null; }; defaultValue: null }, { name: 'categoryId'; type: { kind: 'INPUT_OBJECT'; name: 'IDOperators'; ofType: null; }; defaultValue: null }, { name: 'id'; type: { kind: 'INPUT_OBJECT'; name: 'IDOperators'; ofType: null; }; defaultValue: null }, { name: 'createdAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateOperators'; ofType: null; }; defaultValue: null }, { name: 'updatedAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateOperators'; ofType: null; }; defaultValue: null }, { name: 'name'; type: { kind: 'INPUT_OBJECT'; name: 'StringOperators'; ofType: null; }; defaultValue: null }, { name: 'enabled'; type: { kind: 'INPUT_OBJECT'; name: 'BooleanOperators'; ofType: null; }; defaultValue: null }, { name: 'value'; type: { kind: 'INPUT_OBJECT'; name: 'NumberOperators'; ofType: null; }; defaultValue: null }, { name: '_and'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'TaxRateFilterParameter'; ofType: null; }; }; }; defaultValue: null }, { name: '_or'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'TaxRateFilterParameter'; ofType: null; }; }; }; defaultValue: null }]; };
420
420
  'TaxRateList': { kind: 'OBJECT'; name: 'TaxRateList'; fields: { 'items': { name: 'items'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'TaxRate'; ofType: null; }; }; }; } }; 'totalItems': { name: 'totalItems'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; };
421
421
  'TaxRateListOptions': { kind: 'INPUT_OBJECT'; name: 'TaxRateListOptions'; isOneOf: false; inputFields: [{ name: 'skip'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'take'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'sort'; type: { kind: 'INPUT_OBJECT'; name: 'TaxRateSortParameter'; ofType: null; }; defaultValue: null }, { name: 'filter'; type: { kind: 'INPUT_OBJECT'; name: 'TaxRateFilterParameter'; ofType: null; }; defaultValue: null }, { name: 'filterOperator'; type: { kind: 'ENUM'; name: 'LogicalOperator'; ofType: null; }; defaultValue: null }]; };
422
422
  'TaxRateSortParameter': { kind: 'INPUT_OBJECT'; name: 'TaxRateSortParameter'; isOneOf: false; inputFields: [{ name: 'id'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'createdAt'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'updatedAt'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'name'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'value'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }]; };
@@ -0,0 +1,84 @@
1
+ import { AlertSeverity, DashboardAlertDefinition } from '@/vdb/framework/extension-api/types/alerts.js';
2
+ import { useAlertsContext } from '@/vdb/providers/alerts-provider.js';
3
+ import { useMemo } from 'react';
4
+
5
+ /**
6
+ * @description
7
+ * An individual Alert item.
8
+ *
9
+ * @docsCategory hooks
10
+ * @docsPage useAlerts
11
+ * @since 3.5.0
12
+ */
13
+ export interface Alert {
14
+ definition: DashboardAlertDefinition;
15
+ active: boolean;
16
+ currentSeverity?: AlertSeverity;
17
+ lastData: any;
18
+ dismiss: () => void;
19
+ }
20
+
21
+ /**
22
+ * @description
23
+ * Returns information about all registered Alerts, including how many are
24
+ * active and at what severity.
25
+ *
26
+ * @docsCategory hooks
27
+ * @docsPage useAlerts
28
+ * @docsWeight 0
29
+ * @since 3.5.0
30
+ */
31
+ export function useAlerts(): { alerts: Alert[]; activeCount: number; highestSeverity: AlertSeverity } {
32
+ const { alertDefs, rawResults, dismissedAlerts, setDismissedAlerts } = useAlertsContext();
33
+
34
+ const alerts = useMemo(() => {
35
+ return rawResults.map((result, idx) => {
36
+ const alertDef = alertDefs[idx];
37
+ const dismissedAt = dismissedAlerts.get(alertDef.id);
38
+ const isDismissed =
39
+ dismissedAt !== undefined &&
40
+ result.dataUpdatedAt !== undefined &&
41
+ dismissedAt > result.dataUpdatedAt;
42
+ const active = alertDef.shouldShow(result.data) && !isDismissed;
43
+ const currentSeverity = getSeverity(alertDef, result.data);
44
+ return {
45
+ definition: alertDef,
46
+ active,
47
+ lastData: result.data,
48
+ currentSeverity,
49
+ dismiss: () => {
50
+ setDismissedAlerts(prev => new Map(prev).set(alertDef.id, Date.now()));
51
+ },
52
+ };
53
+ });
54
+ }, [rawResults, alertDefs, dismissedAlerts]);
55
+
56
+ const activeCount = useMemo(() => alerts.filter(alert => alert.active).length, [alerts]);
57
+ const highestSeverity: AlertSeverity =
58
+ alerts.length > 0
59
+ ? alerts.reduce((highest, a) => {
60
+ if (highest === 'warning' && a.currentSeverity === 'error') {
61
+ return 'error';
62
+ }
63
+ if (
64
+ highest === 'info' &&
65
+ (a.currentSeverity === 'warning' || a.currentSeverity === 'error')
66
+ ) {
67
+ return a.currentSeverity;
68
+ }
69
+ return highest;
70
+ }, 'info' as AlertSeverity)
71
+ : 'info';
72
+
73
+ return { alerts, activeCount, highestSeverity };
74
+ }
75
+
76
+ function getSeverity(alertDef: DashboardAlertDefinition, data: any) {
77
+ if (typeof alertDef.severity === 'string') {
78
+ return alertDef.severity;
79
+ } else if (typeof alertDef.severity === 'function') {
80
+ return alertDef.severity(data);
81
+ } else {
82
+ return 'info';
83
+ }
84
+ }
@@ -32,9 +32,8 @@ export function useFloatingBulkActions({
32
32
  }
33
33
 
34
34
  const updatePosition = () => {
35
- // Find the container by searching upwards from the current component
36
- const currentElement = document.activeElement || document.body;
37
- const container = currentElement.closest(containerSelector) as HTMLElement;
35
+ // Find the container in the document
36
+ const container = document.querySelector(containerSelector) as HTMLElement;
38
37
  if (!container) return;
39
38
 
40
39
  const containerRect = container.getBoundingClientRect();
package/src/lib/index.ts CHANGED
@@ -204,6 +204,8 @@ export * from './framework/document-extension/extend-document.js';
204
204
  export * from './framework/document-introspection/add-custom-fields.js';
205
205
  export * from './framework/document-introspection/get-document-structure.js';
206
206
  export * from './framework/document-introspection/hooks.js';
207
+ export * from './framework/document-introspection/include-only-selected-list-fields.js';
208
+ export * from './framework/document-introspection/testing-utils.js';
207
209
  export * from './framework/extension-api/define-dashboard-extension.js';
208
210
  export * from './framework/extension-api/display-component-extensions.js';
209
211
  export * from './framework/extension-api/extension-api-types.js';
@@ -249,6 +251,7 @@ export * from './framework/layout-engine/page-provider.js';
249
251
  export * from './framework/nav-menu/nav-menu-extensions.js';
250
252
  export * from './framework/page/detail-page-route-loader.js';
251
253
  export * from './framework/page/detail-page.js';
254
+ export * from './framework/page/list-page.stories.js';
252
255
  export * from './framework/page/list-page.js';
253
256
  export * from './framework/page/page-api.js';
254
257
  export * from './framework/page/page-types.js';
@@ -256,14 +259,11 @@ export * from './framework/page/use-detail-page.js';
256
259
  export * from './framework/page/use-extended-router.js';
257
260
  export * from './framework/registry/global-registry.js';
258
261
  export * from './framework/registry/registry-types.js';
259
- export * from './graphql/api.js';
260
- export * from './graphql/common-operations.js';
261
- export * from './graphql/fragments.js';
262
- export * from './graphql/graphql.js';
263
- export * from './graphql/settings-store-operations.js';
264
262
  export * from './hooks/use-auth.js';
265
263
  export * from './hooks/use-channel.js';
266
264
  export * from './hooks/use-custom-field-config.js';
265
+ export * from './hooks/use-display-locale.js';
266
+ export * from './hooks/use-dynamic-translations.js';
267
267
  export * from './hooks/use-extended-detail-query.js';
268
268
  export * from './hooks/use-extended-list-query.js';
269
269
  export * from './hooks/use-floating-bulk-actions.js';
@@ -276,6 +276,13 @@ export * from './hooks/use-permissions.js';
276
276
  export * from './hooks/use-saved-views.js';
277
277
  export * from './hooks/use-server-config.js';
278
278
  export * from './hooks/use-theme.js';
279
+ export * from './hooks/use-ui-language-loader.js';
279
280
  export * from './hooks/use-user-settings.js';
281
+ export * from './lib/load-i18n-messages.js';
280
282
  export * from './lib/trans.js';
281
283
  export * from './lib/utils.js';
284
+ export * from './graphql/api.js';
285
+ export * from './graphql/common-operations.js';
286
+ export * from './graphql/fragments.js';
287
+ export * from './graphql/graphql.js';
288
+ export * from './graphql/settings-store-operations.js';