@vendure/dashboard 3.5.0-minor-202510031341 → 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 (220) hide show
  1. package/dist/plugin/dashboard.plugin.js +1 -1
  2. package/dist/plugin/default-page.html +1 -1
  3. package/dist/vite/utils/ast-utils.spec.js +3 -3
  4. package/dist/vite/utils/tsconfig-utils.js +2 -1
  5. package/dist/vite/vite-plugin-hmr.d.ts +8 -0
  6. package/dist/vite/vite-plugin-hmr.js +34 -0
  7. package/dist/vite/vite-plugin-theme.js +6 -6
  8. package/dist/vite/vite-plugin-transform-index.js +6 -1
  9. package/dist/vite/vite-plugin-vendure-dashboard.d.ts +31 -4
  10. package/dist/vite/vite-plugin-vendure-dashboard.js +89 -34
  11. package/package.json +18 -5
  12. package/src/app/app-providers.tsx +4 -1
  13. package/src/app/common/map-faceted-filter-fields.ts +21 -0
  14. package/src/app/main.tsx +3 -1
  15. package/src/app/routes/_authenticated/_administrators/administrators.graphql.ts +2 -2
  16. package/src/app/routes/_authenticated/_administrators/administrators.tsx +13 -3
  17. package/src/app/routes/_authenticated/_administrators/administrators_.$id.tsx +6 -13
  18. package/src/app/routes/_authenticated/_administrators/components/role-permissions-display.tsx +1 -1
  19. package/src/app/routes/_authenticated/_assets/assets.tsx +17 -1
  20. package/src/app/routes/_authenticated/_collections/collections.graphql.ts +1 -0
  21. package/src/app/routes/_authenticated/_collections/collections.tsx +5 -0
  22. package/src/app/routes/_authenticated/_collections/components/collection-bulk-actions.tsx +0 -1
  23. package/src/app/routes/_authenticated/_customers/customers.tsx +9 -5
  24. package/src/app/routes/_authenticated/_facets/components/facet-bulk-actions.tsx +0 -6
  25. package/src/app/routes/_authenticated/_facets/components/facet-value-bulk-actions.tsx +16 -0
  26. package/src/app/routes/_authenticated/_facets/components/facet-values-table.tsx +43 -12
  27. package/src/app/routes/_authenticated/_facets/facets_.$facetId.values_.$id.tsx +14 -5
  28. package/src/app/routes/_authenticated/_global-settings/global-settings.tsx +4 -8
  29. package/src/app/routes/_authenticated/_global-settings/utils/global-languages.ts +268 -0
  30. package/src/app/routes/_authenticated/_orders/components/edit-order-table.tsx +117 -92
  31. package/src/app/routes/_authenticated/_orders/components/order-address.tsx +15 -15
  32. package/src/app/routes/_authenticated/_orders/components/order-detail-shared.tsx +5 -5
  33. package/src/app/routes/_authenticated/_orders/components/order-modification-summary.tsx +2 -1
  34. package/src/app/routes/_authenticated/_orders/components/order-table-totals.tsx +26 -27
  35. package/src/app/routes/_authenticated/_orders/components/order-table.tsx +5 -3
  36. package/src/app/routes/_authenticated/_orders/components/state-transition-control.tsx +6 -9
  37. package/src/app/routes/_authenticated/_orders/orders.graphql.ts +17 -1
  38. package/src/app/routes/_authenticated/_orders/orders_.$id_.modify.tsx +48 -281
  39. package/src/app/routes/_authenticated/_orders/orders_.draft.$id.tsx +59 -40
  40. package/src/app/routes/_authenticated/_orders/utils/order-utils.ts +73 -0
  41. package/src/app/routes/_authenticated/_orders/utils/use-modify-order.ts +312 -0
  42. package/src/app/routes/_authenticated/_payment-methods/payment-methods.graphql.ts +2 -2
  43. package/src/app/routes/_authenticated/_payment-methods/payment-methods.tsx +4 -0
  44. package/src/app/routes/_authenticated/_product-variants/components/add-currency-dropdown.tsx +49 -0
  45. package/src/app/routes/_authenticated/_product-variants/components/add-stock-location-dropdown.tsx +56 -0
  46. package/src/app/routes/_authenticated/_product-variants/product-variants.graphql.ts +12 -0
  47. package/src/app/routes/_authenticated/_product-variants/product-variants_.$id.tsx +178 -50
  48. package/src/app/routes/_authenticated/_products/components/product-bulk-actions.tsx +0 -6
  49. package/src/app/routes/_authenticated/_products/components/product-variants-table.tsx +0 -11
  50. package/src/app/routes/_authenticated/_products/products.tsx +6 -2
  51. package/src/app/routes/_authenticated/_products/products_.$productId.option-groups.$productOptionGroupId.options_.$id.tsx +4 -8
  52. package/src/app/routes/_authenticated/_promotions/components/promotion-bulk-actions.tsx +0 -10
  53. package/src/app/routes/_authenticated/_promotions/promotions.graphql.ts +2 -2
  54. package/src/app/routes/_authenticated/_promotions/promotions.tsx +12 -0
  55. package/src/app/routes/_authenticated/_promotions/promotions_.$id.tsx +3 -10
  56. package/src/app/routes/_authenticated/_sellers/sellers.graphql.ts +2 -2
  57. package/src/app/routes/_authenticated/_shipping-methods/shipping-methods.graphql.ts +2 -2
  58. package/src/app/routes/_authenticated/_shipping-methods/shipping-methods.tsx +4 -0
  59. package/src/app/routes/_authenticated/_shipping-methods/shipping-methods_.$id.tsx +4 -10
  60. package/src/app/routes/_authenticated/_stock-locations/stock-locations.graphql.ts +2 -2
  61. package/src/app/routes/_authenticated/_tax-categories/tax-categories.graphql.ts +2 -2
  62. package/src/app/routes/_authenticated/_tax-rates/tax-rates.tsx +9 -0
  63. package/src/app/routes/_authenticated/_tax-rates/tax-rates_.$id.tsx +1 -0
  64. package/src/app/routes/_authenticated/_zones/zones.graphql.ts +2 -2
  65. package/src/app/routes/login.tsx +2 -2
  66. package/src/i18n/locales/ar.po +420 -289
  67. package/src/i18n/locales/cs.po +420 -289
  68. package/src/i18n/locales/de.po +420 -289
  69. package/src/i18n/locales/en.po +420 -289
  70. package/src/i18n/locales/es.po +420 -289
  71. package/src/i18n/locales/fa.po +420 -289
  72. package/src/i18n/locales/fr.po +468 -337
  73. package/src/i18n/locales/he.po +420 -289
  74. package/src/i18n/locales/hr.po +420 -289
  75. package/src/i18n/locales/it.po +420 -289
  76. package/src/i18n/locales/ja.po +420 -289
  77. package/src/i18n/locales/nb.po +420 -289
  78. package/src/i18n/locales/ne.po +420 -289
  79. package/src/i18n/locales/pl.po +420 -289
  80. package/src/i18n/locales/pt_BR.po +420 -289
  81. package/src/i18n/locales/pt_PT.po +420 -289
  82. package/src/i18n/locales/ru.po +420 -289
  83. package/src/i18n/locales/sv.po +420 -289
  84. package/src/i18n/locales/tr.po +420 -289
  85. package/src/i18n/locales/uk.po +420 -289
  86. package/src/i18n/locales/zh_Hans.po +420 -289
  87. package/src/i18n/locales/zh_Hant.po +420 -289
  88. package/src/lib/components/data-input/affixed-input.stories.tsx +93 -0
  89. package/src/lib/components/data-input/affixed-input.tsx +5 -2
  90. package/src/lib/components/data-input/boolean-input.stories.tsx +102 -0
  91. package/src/lib/components/data-input/checkbox-input.stories.tsx +61 -0
  92. package/src/lib/components/data-input/customer-group-input.tsx +0 -1
  93. package/src/lib/components/data-input/datetime-input.stories.tsx +62 -0
  94. package/src/lib/components/data-input/datetime-input.tsx +27 -13
  95. package/src/lib/components/data-input/default-relation-input.tsx +18 -12
  96. package/src/lib/components/data-input/money-input.stories.tsx +88 -0
  97. package/src/lib/components/data-input/money-input.tsx +7 -11
  98. package/src/lib/components/data-input/number-input.stories.tsx +103 -0
  99. package/src/lib/components/data-input/number-input.tsx +16 -5
  100. package/src/lib/components/data-input/password-input.stories.tsx +65 -0
  101. package/src/lib/components/data-input/rich-text-input.stories.tsx +92 -0
  102. package/src/lib/components/data-input/slug-input.stories.tsx +232 -0
  103. package/src/lib/components/data-input/slug-input.tsx +9 -10
  104. package/src/lib/components/data-input/text-input.stories.tsx +52 -0
  105. package/src/lib/components/data-input/textarea-input.stories.tsx +55 -0
  106. package/src/lib/components/data-table/add-filter-menu.tsx +6 -1
  107. package/src/lib/components/data-table/column-header-wrapper.tsx +106 -0
  108. package/src/lib/components/data-table/data-table-bulk-action-item.tsx +11 -9
  109. package/src/lib/components/data-table/data-table-bulk-actions.tsx +4 -4
  110. package/src/lib/components/data-table/data-table-column-header.tsx +17 -14
  111. package/src/lib/components/data-table/data-table-faceted-filter.tsx +33 -11
  112. package/src/lib/components/data-table/data-table-filter-badge-editable.tsx +35 -0
  113. package/src/lib/components/data-table/data-table-filter-badge.tsx +28 -14
  114. package/src/lib/components/data-table/data-table-filter-dialog.tsx +28 -8
  115. package/src/lib/components/data-table/data-table-pagination.tsx +23 -7
  116. package/src/lib/components/data-table/data-table.stories.tsx +249 -0
  117. package/src/lib/components/data-table/data-table.tsx +39 -11
  118. package/src/lib/components/data-table/filters/data-table-datetime-filter.tsx +79 -34
  119. package/src/lib/components/data-table/use-generated-columns.tsx +55 -27
  120. package/src/lib/components/layout/generated-breadcrumbs.tsx +4 -12
  121. package/src/lib/components/layout/nav-user.tsx +19 -13
  122. package/src/lib/components/login/login-form.tsx +39 -123
  123. package/src/lib/components/shared/alerts.tsx +29 -17
  124. package/src/lib/components/shared/asset/asset-bulk-actions.tsx +3 -3
  125. package/src/lib/components/shared/asset/asset-gallery.stories.tsx +76 -0
  126. package/src/lib/components/shared/asset/asset-gallery.tsx +147 -113
  127. package/src/lib/components/shared/asset/asset-picker-dialog.stories.tsx +58 -0
  128. package/src/lib/components/shared/configurable-operation-input.tsx +1 -1
  129. package/src/lib/components/shared/customer-group-selector.tsx +5 -2
  130. package/src/lib/components/shared/detail-page-button.stories.tsx +52 -0
  131. package/src/lib/components/shared/facet-value-selector.stories.tsx +48 -0
  132. package/src/lib/components/shared/facet-value-selector.tsx +130 -34
  133. package/src/lib/components/shared/paginated-list-data-table.stories.tsx +212 -0
  134. package/src/lib/components/shared/paginated-list-data-table.tsx +12 -12
  135. package/src/lib/components/shared/permission-guard.stories.tsx +46 -0
  136. package/src/lib/components/shared/remove-from-channel-bulk-action.tsx +2 -0
  137. package/src/lib/components/shared/rich-text-editor/responsive-toolbar.tsx +8 -4
  138. package/src/lib/components/shared/rich-text-editor/rich-text-editor.tsx +1 -0
  139. package/src/lib/components/shared/table-cell/order-table-cell-components.tsx +40 -0
  140. package/src/lib/components/shared/vendure-image.stories.tsx +167 -0
  141. package/src/lib/components/shared/vendure-image.tsx +6 -7
  142. package/src/lib/components/ui/accordion.stories.tsx +33 -0
  143. package/src/lib/components/ui/alert-dialog.stories.tsx +48 -0
  144. package/src/lib/components/ui/alert.stories.tsx +35 -0
  145. package/src/lib/components/ui/aspect-ratio.stories.tsx +28 -0
  146. package/src/lib/components/ui/badge.stories.tsx +28 -0
  147. package/src/lib/components/ui/breadcrumb.stories.tsx +41 -0
  148. package/src/lib/components/ui/button.stories.tsx +38 -0
  149. package/src/lib/components/ui/calendar.stories.tsx +22 -0
  150. package/src/lib/components/ui/card.stories.tsx +28 -0
  151. package/src/lib/components/ui/carousel.stories.tsx +34 -0
  152. package/src/lib/components/ui/checkbox.stories.tsx +31 -0
  153. package/src/lib/components/ui/collapsible.stories.tsx +39 -0
  154. package/src/lib/components/ui/command.stories.tsx +44 -0
  155. package/src/lib/components/ui/context-menu.stories.tsx +38 -0
  156. package/src/lib/components/ui/dialog.stories.tsx +52 -0
  157. package/src/lib/components/ui/drawer.stories.tsx +50 -0
  158. package/src/lib/components/ui/dropdown-menu.stories.tsx +41 -0
  159. package/src/lib/components/ui/hover-card.stories.tsx +38 -0
  160. package/src/lib/components/ui/input-group.tsx +148 -0
  161. package/src/lib/components/ui/input-otp.stories.tsx +30 -0
  162. package/src/lib/components/ui/input.stories.tsx +38 -0
  163. package/src/lib/components/ui/label.stories.tsx +24 -0
  164. package/src/lib/components/ui/menubar.stories.tsx +53 -0
  165. package/src/lib/components/ui/navigation-menu.stories.tsx +54 -0
  166. package/src/lib/components/ui/pagination.stories.tsx +51 -0
  167. package/src/lib/components/ui/password-input.stories.tsx +32 -0
  168. package/src/lib/components/ui/password-input.tsx +33 -0
  169. package/src/lib/components/ui/popover.stories.tsx +33 -0
  170. package/src/lib/components/ui/progress.stories.tsx +27 -0
  171. package/src/lib/components/ui/radio-group.stories.tsx +34 -0
  172. package/src/lib/components/ui/resizable.stories.tsx +32 -0
  173. package/src/lib/components/ui/scroll-area.stories.tsx +31 -0
  174. package/src/lib/components/ui/select.stories.tsx +36 -0
  175. package/src/lib/components/ui/separator.stories.tsx +35 -0
  176. package/src/lib/components/ui/sheet.stories.tsx +50 -0
  177. package/src/lib/components/ui/sidebar-context.ts +16 -0
  178. package/src/lib/components/ui/sidebar.tsx +2 -13
  179. package/src/lib/components/ui/skeleton.stories.tsx +26 -0
  180. package/src/lib/components/ui/slider.stories.tsx +37 -0
  181. package/src/lib/components/ui/switch.stories.tsx +31 -0
  182. package/src/lib/components/ui/table.stories.tsx +52 -0
  183. package/src/lib/components/ui/tabs.stories.tsx +29 -0
  184. package/src/lib/components/ui/textarea.stories.tsx +32 -0
  185. package/src/lib/components/ui/toggle-group.stories.tsx +31 -0
  186. package/src/lib/components/ui/toggle.stories.tsx +39 -0
  187. package/src/lib/components/ui/tooltip.stories.tsx +30 -0
  188. package/src/lib/components/ui/tooltip.tsx +2 -2
  189. package/src/lib/framework/alert/alert-extensions.tsx +0 -11
  190. package/src/lib/framework/alert/alert-item.tsx +14 -19
  191. package/src/lib/framework/alert/alerts-indicator.tsx +14 -15
  192. package/src/lib/framework/alert/search-index-buffer-alert/search-index-buffer-alert.ts +41 -0
  193. package/src/lib/framework/component-registry/component-registry.tsx +3 -14
  194. package/src/lib/framework/dashboard-widget/base-widget.tsx +18 -9
  195. package/src/lib/framework/dashboard-widget/latest-orders-widget/index.tsx +0 -2
  196. package/src/lib/framework/dashboard-widget/widget-filters-context.tsx +12 -11
  197. package/src/lib/framework/defaults.ts +9 -13
  198. package/src/lib/framework/extension-api/input-component-extensions.tsx +6 -1
  199. package/src/lib/framework/extension-api/logic/alerts.ts +3 -2
  200. package/src/lib/framework/extension-api/types/alerts.ts +12 -6
  201. package/src/lib/framework/extension-api/types/data-table.ts +5 -2
  202. package/src/lib/framework/extension-api/types/layout.ts +41 -1
  203. package/src/lib/framework/extension-api/types/login.ts +0 -21
  204. package/src/lib/framework/form-engine/value-transformers.ts +8 -1
  205. package/src/lib/framework/layout-engine/custom-form-page.stories.tsx +344 -0
  206. package/src/lib/framework/layout-engine/page-layout.tsx +69 -57
  207. package/src/lib/framework/layout-engine/page.stories.tsx +275 -0
  208. package/src/lib/framework/nav-menu/nav-menu-extensions.ts +32 -19
  209. package/src/lib/framework/page/detail-page.stories.tsx +151 -0
  210. package/src/lib/framework/page/detail-page.tsx +12 -15
  211. package/src/lib/framework/page/list-page.stories.tsx +217 -0
  212. package/src/lib/framework/page/list-page.tsx +8 -1
  213. package/src/lib/graphql/api.ts +18 -1
  214. package/src/lib/graphql/graphql-env.d.ts +1 -1
  215. package/src/lib/hooks/use-alerts.ts +84 -0
  216. package/src/lib/hooks/use-floating-bulk-actions.ts +2 -3
  217. package/src/lib/index.ts +12 -5
  218. package/src/lib/providers/alerts-provider.tsx +60 -0
  219. package/src/lib/providers/channel-provider.tsx +1 -0
  220. package/src/lib/providers/theme-provider.tsx +6 -3
@@ -0,0 +1,93 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { Mail } from 'lucide-react';
3
+ import { useForm } from 'react-hook-form';
4
+ import { withDescription } from '../../../.storybook/with-description.js';
5
+ import { AffixedInput } from './affixed-input.js';
6
+
7
+ const meta = {
8
+ title: 'Form Inputs/AffixedInput',
9
+ component: AffixedInput,
10
+ ...withDescription(import.meta.url, './affixed-input.js'),
11
+ parameters: {
12
+ layout: 'centered',
13
+ },
14
+ tags: ['autodocs'],
15
+ argTypes: {
16
+ prefix: {
17
+ control: 'text',
18
+ description: 'Content to display before the input',
19
+ },
20
+ suffix: {
21
+ control: 'text',
22
+ description: 'Content to display after the input',
23
+ },
24
+ type: {
25
+ control: 'select',
26
+ options: ['text', 'number', 'email', 'url', 'tel'],
27
+ description: 'Input type',
28
+ },
29
+ placeholder: {
30
+ control: 'text',
31
+ description: 'Placeholder text',
32
+ },
33
+ disabled: {
34
+ control: 'boolean',
35
+ description: 'Whether the input is disabled',
36
+ },
37
+ value: {
38
+ control: 'text',
39
+ description: 'Input value',
40
+ },
41
+ },
42
+ } satisfies Meta<typeof AffixedInput>;
43
+
44
+ export default meta;
45
+ type Story = StoryObj<typeof meta>;
46
+
47
+ export const Playground: Story = {
48
+ args: {
49
+ prefix: '$',
50
+ suffix: 'USD',
51
+ value: '100.00',
52
+ type: 'text',
53
+ placeholder: 'Enter amount',
54
+ disabled: false,
55
+ },
56
+ render: args => {
57
+ const { register } = useForm();
58
+ const field = register('amount');
59
+ return <AffixedInput {...field} {...args} />;
60
+ },
61
+ };
62
+
63
+ export const WithIconPrefix: Story = {
64
+ render: () => {
65
+ const { register } = useForm();
66
+ const field = register('email');
67
+ return <AffixedInput {...field} prefix={<Mail className="w-4 h-4" />} placeholder="Enter email" />;
68
+ },
69
+ };
70
+
71
+ export const WithSuffix: Story = {
72
+ render: () => {
73
+ const { register } = useForm();
74
+ const field = register('percentage');
75
+ return <AffixedInput {...field} type="number" suffix="%" placeholder="Enter percentage" />;
76
+ },
77
+ };
78
+
79
+ export const Disabled: Story = {
80
+ render: () => {
81
+ const { register } = useForm();
82
+ const field = register('website');
83
+ return <AffixedInput {...field} prefix="https://" suffix=".com" disabled />;
84
+ },
85
+ };
86
+
87
+ export const NumberWithSteps: Story = {
88
+ render: () => {
89
+ const { register } = useForm();
90
+ const field = register('weight');
91
+ return <AffixedInput {...field} type="number" suffix="kg" step="0.1" min="0" max="100" />;
92
+ },
93
+ };
@@ -13,7 +13,7 @@ export type AffixedInputProps = Omit<React.InputHTMLAttributes<HTMLInputElement>
13
13
  /**
14
14
  * @description
15
15
  * A component for displaying an input with a prefix and/or a suffix.
16
- *
16
+ *
17
17
  * @example
18
18
  * ```tsx
19
19
  * <AffixedInput
@@ -24,7 +24,7 @@ export type AffixedInputProps = Omit<React.InputHTMLAttributes<HTMLInputElement>
24
24
  * onChange={e => field.onChange(e.target.valueAsNumber)}
25
25
  * />
26
26
  * ```
27
- *
27
+ *
28
28
  * @docsCategory form-components
29
29
  * @docsPage AffixedInput
30
30
  */
@@ -65,6 +65,9 @@ export function AffixedInput({ prefix, suffix, className = '', ...props }: Reado
65
65
  className={className}
66
66
  style={style}
67
67
  disabled={readOnly}
68
+ min={props.min}
69
+ max={props.max}
70
+ step={props.step}
68
71
  />
69
72
  {suffix && (
70
73
  <span ref={suffixRef} className="absolute right-3 text-muted-foreground whitespace-nowrap">
@@ -0,0 +1,102 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { useForm } from 'react-hook-form';
3
+ import { withDescription } from '../../../.storybook/with-description.js';
4
+ import { BooleanInput } from './boolean-input.js';
5
+
6
+ const meta = {
7
+ title: 'Form Inputs/BooleanInput',
8
+ component: BooleanInput,
9
+ ...withDescription(import.meta.url, './boolean-input.js'),
10
+ parameters: {
11
+ layout: 'centered',
12
+ },
13
+ tags: ['autodocs'],
14
+ argTypes: {
15
+ value: {
16
+ control: 'boolean',
17
+ description: 'Whether the switch is on',
18
+ },
19
+ },
20
+ } satisfies Meta<typeof BooleanInput>;
21
+
22
+ export default meta;
23
+ type Story = StoryObj<typeof meta>;
24
+
25
+ export const Playground: Story = {
26
+ args: {
27
+ value: false,
28
+ },
29
+ render: args => {
30
+ const { register } = useForm();
31
+ const field = register('playground');
32
+ return (
33
+ <div className="flex items-center gap-2">
34
+ <BooleanInput {...field} {...args} />
35
+ <label className="text-sm font-medium">Enable notifications</label>
36
+ </div>
37
+ );
38
+ },
39
+ };
40
+
41
+ export const ProductSettings: Story = {
42
+ render: () => {
43
+ const { register } = useForm();
44
+ return (
45
+ <div className="w-[350px] space-y-3">
46
+ <div className="flex items-center justify-between">
47
+ <div>
48
+ <label className="text-sm font-medium">Product enabled</label>
49
+ <p className="text-xs text-muted-foreground">
50
+ Make this product visible in the catalog
51
+ </p>
52
+ </div>
53
+ <BooleanInput {...register('enabled')} />
54
+ </div>
55
+ <div className="flex items-center justify-between">
56
+ <div>
57
+ <label className="text-sm font-medium">Featured</label>
58
+ <p className="text-xs text-muted-foreground">Show on homepage</p>
59
+ </div>
60
+ <BooleanInput {...register('featured')} />
61
+ </div>
62
+ <div className="flex items-center justify-between">
63
+ <div>
64
+ <label className="text-sm font-medium">Track inventory</label>
65
+ <p className="text-xs text-muted-foreground">Monitor stock levels</p>
66
+ </div>
67
+ <BooleanInput {...register('trackInventory')} />
68
+ </div>
69
+ <div className="flex items-center justify-between">
70
+ <div>
71
+ <label className="text-sm font-medium">Allow backorder</label>
72
+ <p className="text-xs text-muted-foreground">Accept orders when out of stock</p>
73
+ </div>
74
+ <BooleanInput {...register('allowBackorder')} />
75
+ </div>
76
+ </div>
77
+ );
78
+ },
79
+ };
80
+
81
+ export const StringValues: Story = {
82
+ render: () => {
83
+ const { register } = useForm();
84
+ return (
85
+ <div className="space-y-4">
86
+ <div className="flex items-center gap-2">
87
+ <BooleanInput {...register('trueValue')} />
88
+ <label className="text-sm font-medium">String value: "true"</label>
89
+ </div>
90
+
91
+ <div className="flex items-center gap-2">
92
+ <BooleanInput {...register('falseValue')} />
93
+ <label className="text-sm font-medium">String value: "false"</label>
94
+ </div>
95
+
96
+ <div className="text-sm text-muted-foreground">
97
+ Demonstrates handling of string "true"/"false" values
98
+ </div>
99
+ </div>
100
+ );
101
+ },
102
+ };
@@ -0,0 +1,61 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { useForm } from 'react-hook-form';
3
+ import { withDescription } from '../../../.storybook/with-description.js';
4
+ import { CheckboxInput } from './checkbox-input.js';
5
+
6
+ const meta = {
7
+ title: 'Form Inputs/CheckboxInput',
8
+ component: CheckboxInput,
9
+ ...withDescription(import.meta.url, './checkbox-input.js'),
10
+ parameters: {
11
+ layout: 'centered',
12
+ },
13
+ tags: ['autodocs'],
14
+ argTypes: {
15
+ value: {
16
+ control: 'boolean',
17
+ description: 'Whether the checkbox is checked',
18
+ },
19
+ },
20
+ } satisfies Meta<typeof CheckboxInput>;
21
+
22
+ export default meta;
23
+ type Story = StoryObj<typeof meta>;
24
+
25
+ export const Playground: Story = {
26
+ args: {
27
+ value: false,
28
+ },
29
+ render: args => {
30
+ const { register } = useForm();
31
+ const field = register('playground');
32
+ return (
33
+ <div className="flex items-center gap-2">
34
+ <CheckboxInput {...field} {...args} />
35
+ <label className="text-sm font-medium">Accept terms and conditions</label>
36
+ </div>
37
+ );
38
+ },
39
+ };
40
+
41
+ export const MultipleCheckboxes: Story = {
42
+ render: () => {
43
+ const { register } = useForm();
44
+ return (
45
+ <div className="space-y-3">
46
+ <div className="flex items-center gap-2">
47
+ <CheckboxInput {...register('notifications')} />
48
+ <label className="text-sm font-medium">Email notifications</label>
49
+ </div>
50
+ <div className="flex items-center gap-2">
51
+ <CheckboxInput {...register('marketing')} />
52
+ <label className="text-sm font-medium">Marketing emails</label>
53
+ </div>
54
+ <div className="flex items-center gap-2">
55
+ <CheckboxInput {...register('updates')} />
56
+ <label className="text-sm font-medium">Product updates</label>
57
+ </div>
58
+ </div>
59
+ );
60
+ },
61
+ };
@@ -26,7 +26,6 @@ export function CustomerGroupInput({
26
26
  disabled,
27
27
  fieldDef,
28
28
  }: Readonly<DashboardFormComponentProps>) {
29
- console.log(fieldDef);
30
29
  const { data } = useQuery({
31
30
  queryKey: ['customerGroups', value],
32
31
  queryFn: () =>
@@ -0,0 +1,62 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { useForm } from 'react-hook-form';
3
+ import { withDescription } from '../../../.storybook/with-description.js';
4
+ import { DateTimeInput } from './datetime-input.js';
5
+
6
+ const meta = {
7
+ title: 'Form Inputs/DateTimeInput',
8
+ component: DateTimeInput,
9
+ ...withDescription(import.meta.url, './datetime-input.js'),
10
+ parameters: {
11
+ layout: 'centered',
12
+ },
13
+ tags: ['autodocs'],
14
+ argTypes: {
15
+ value: {
16
+ control: 'text',
17
+ description: 'ISO 8601 datetime string',
18
+ },
19
+ },
20
+ } satisfies Meta<typeof DateTimeInput>;
21
+
22
+ export default meta;
23
+ type Story = StoryObj<typeof meta>;
24
+
25
+ export const Playground: Story = {
26
+ args: {
27
+ value: new Date('2024-06-15T14:30:00').toISOString(),
28
+ },
29
+ render: args => {
30
+ const { register } = useForm();
31
+ const field = register('playground');
32
+ return (
33
+ <div className="w-[400px]">
34
+ <DateTimeInput {...field} {...args} />
35
+ </div>
36
+ );
37
+ },
38
+ };
39
+
40
+ export const FutureDate: Story = {
41
+ render: () => {
42
+ const { register } = useForm();
43
+ const field = register('future');
44
+ return (
45
+ <div className="w-[400px]">
46
+ <DateTimeInput {...field} />
47
+ </div>
48
+ );
49
+ },
50
+ };
51
+
52
+ export const ClearableValue: Story = {
53
+ render: () => {
54
+ const { register } = useForm();
55
+ const field = register('clearable');
56
+ return (
57
+ <div className="w-[400px]">
58
+ <DateTimeInput {...field} />
59
+ </div>
60
+ );
61
+ },
62
+ };
@@ -13,7 +13,7 @@ import { isReadonlyField } from '@/vdb/framework/form-engine/utils.js';
13
13
  import { useDisplayLocale } from '@/vdb/hooks/use-display-locale.js';
14
14
  import { cn } from '@/vdb/lib/utils.js';
15
15
  import type { Locale } from 'date-fns/locale';
16
- import { CalendarClock } from 'lucide-react';
16
+ import { CalendarClock, X } from 'lucide-react';
17
17
 
18
18
  /**
19
19
  * @description
@@ -62,24 +62,38 @@ export function DateTimeInput({ value, onChange, fieldDef }: Readonly<DashboardF
62
62
  const currentHours = newDate.getHours();
63
63
  newDate.setHours(value === 'PM' ? currentHours + 12 : currentHours - 12);
64
64
  }
65
- onChange(newDate);
65
+ onChange(newDate.toISOString());
66
66
  }
67
67
  };
68
68
 
69
69
  return (
70
70
  <Popover open={isOpen} onOpenChange={readOnly ? undefined : setIsOpen}>
71
71
  <PopoverTrigger asChild>
72
- <Button
73
- variant="outline"
74
- disabled={readOnly}
75
- className={cn(
76
- 'w-full justify-start text-left font-normal shadow-xs',
77
- !date && 'text-muted-foreground',
78
- )}
79
- >
80
- <CalendarClock className="mr-2 h-4 w-4" />
81
- {date ? format(date, 'MM/dd/yyyy hh:mm aa') : <span>MM/DD/YYYY hh:mm aa</span>}
82
- </Button>
72
+ <div className="flex items-center">
73
+ <Button
74
+ variant="outline"
75
+ disabled={readOnly}
76
+ className={cn(
77
+ 'w-full justify-start text-left font-normal shadow-xs',
78
+ date ? 'rounded-r-none' : 'text-muted-foreground',
79
+ )}
80
+ >
81
+ <CalendarClock className="mr-2 h-4 w-4" />
82
+ {date ? format(date, 'MM/dd/yyyy hh:mm aa') : <span>MM/DD/YYYY hh:mm aa</span>}
83
+ </Button>
84
+ {date ? (
85
+ <Button
86
+ variant="outline"
87
+ className="rounded-l-none border-l-0"
88
+ onClick={e => {
89
+ e.stopPropagation();
90
+ onChange(null);
91
+ }}
92
+ >
93
+ <X />
94
+ </Button>
95
+ ) : null}
96
+ </div>
83
97
  </PopoverTrigger>
84
98
  <PopoverContent className="w-auto p-0">
85
99
  <div className="sm:flex">
@@ -1,11 +1,14 @@
1
1
  import { graphql } from '@/vdb/graphql/graphql.js';
2
2
  import { Trans, useLingui } from '@lingui/react/macro';
3
- import { RelationCustomFieldConfig } from '@vendure/common/lib/generated-types';
4
- import { ControllerRenderProps } from 'react-hook-form';
3
+ import { useMemo } from 'react';
5
4
  import { MultiRelationInput, SingleRelationInput } from './relation-input.js';
6
5
  import { createRelationSelectorConfig } from './relation-selector.js';
7
6
 
8
- import { DashboardFormComponentProps } from '@/vdb/framework/form-engine/form-engine-types.js';
7
+ import {
8
+ DashboardFormComponentMetadata,
9
+ DashboardFormComponentProps,
10
+ RelationCustomFieldConfig,
11
+ } from '@/vdb/framework/form-engine/form-engine-types.js';
9
12
  import { isRelationCustomFieldConfig } from '@/vdb/framework/form-engine/utils.js';
10
13
 
11
14
  interface PlaceholderIconProps {
@@ -549,11 +552,9 @@ const createEntityConfigs = (i18n: any) => ({
549
552
  }),
550
553
  });
551
554
 
552
- interface DefaultRelationInputProps {
553
- fieldDef: RelationCustomFieldConfig;
554
- field: ControllerRenderProps<any, any>;
555
- disabled?: boolean;
556
- }
555
+ type DefaultRelationInputProps = DashboardFormComponentProps & {
556
+ entityType?: keyof ReturnType<typeof createEntityConfigs>;
557
+ };
557
558
 
558
559
  export function DefaultRelationInput({
559
560
  fieldDef,
@@ -563,13 +564,14 @@ export function DefaultRelationInput({
563
564
  name,
564
565
  ref,
565
566
  disabled,
566
- }: Readonly<DashboardFormComponentProps>) {
567
+ entityType,
568
+ }: Readonly<DefaultRelationInputProps>) {
567
569
  const { t } = useLingui();
568
- if (!fieldDef || !isRelationCustomFieldConfig(fieldDef)) {
570
+ if (!fieldDef || (!isRelationCustomFieldConfig(fieldDef) && !entityType)) {
569
571
  return null;
570
572
  }
571
- const entityName = fieldDef.entity;
572
- const ENTITY_CONFIGS = createEntityConfigs(t);
573
+ const entityName = entityType ?? (fieldDef as RelationCustomFieldConfig).entity;
574
+ const ENTITY_CONFIGS = useMemo(() => createEntityConfigs(t), [t]);
573
575
  const config = ENTITY_CONFIGS[entityName as keyof typeof ENTITY_CONFIGS];
574
576
 
575
577
  if (!config) {
@@ -618,3 +620,7 @@ export function DefaultRelationInput({
618
620
  );
619
621
  }
620
622
  }
623
+
624
+ DefaultRelationInput.metadata = {
625
+ isListInput: 'dynamic',
626
+ } as DashboardFormComponentMetadata;
@@ -0,0 +1,88 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { useForm } from 'react-hook-form';
3
+ import { withDescription } from '../../../.storybook/with-description.js';
4
+ import { MoneyInput } from './money-input.js';
5
+
6
+ const meta = {
7
+ title: 'Form Inputs/MoneyInput',
8
+ component: MoneyInput,
9
+ ...withDescription(import.meta.url, './money-input.js'),
10
+ parameters: {
11
+ layout: 'centered',
12
+ },
13
+ tags: ['autodocs'],
14
+ argTypes: {
15
+ value: {
16
+ control: 'number',
17
+ description: 'The current value in minor units (e.g., cents)',
18
+ },
19
+ currency: {
20
+ control: 'select',
21
+ options: ['USD', 'EUR', 'GBP', 'JPY', 'AUD', 'CAD'],
22
+ description: 'The currency code',
23
+ },
24
+ },
25
+ } satisfies Meta<typeof MoneyInput>;
26
+
27
+ export default meta;
28
+ type Story = StoryObj<typeof meta>;
29
+
30
+ export const Playground: Story = {
31
+ args: {
32
+ value: 9999,
33
+ currency: 'USD',
34
+ },
35
+ render: args => {
36
+ const { register } = useForm();
37
+ const field = register('playground');
38
+ return (
39
+ <div className="w-[300px]">
40
+ <MoneyInput {...field} {...args} />
41
+ </div>
42
+ );
43
+ },
44
+ };
45
+
46
+ export const DifferentCurrencies: Story = {
47
+ args: {
48
+ value: 9999,
49
+ },
50
+ render: (args: any) => {
51
+ const { register } = useForm();
52
+ return (
53
+ <div className="w-[300px] space-y-4">
54
+ <div className="space-y-2">
55
+ <label className="text-sm font-medium">USD</label>
56
+ <MoneyInput {...register('usd')} currency="USD" value={args.value} />
57
+ </div>
58
+
59
+ <div className="space-y-2">
60
+ <label className="text-sm font-medium">EUR</label>
61
+ <MoneyInput {...register('eur')} currency="EUR" value={args.value} />
62
+ </div>
63
+
64
+ <div className="space-y-2">
65
+ <label className="text-sm font-medium">GBP</label>
66
+ <MoneyInput {...register('gbp')} currency="GBP" value={args.value} />
67
+ </div>
68
+
69
+ <div className="space-y-2">
70
+ <label className="text-sm font-medium">JPY</label>
71
+ <MoneyInput {...register('jpy')} currency="JPY" value={args.value} />
72
+ </div>
73
+ </div>
74
+ );
75
+ },
76
+ };
77
+
78
+ export const LargeAmount: Story = {
79
+ render: () => {
80
+ const { register } = useForm();
81
+ const field = register('large');
82
+ return (
83
+ <div className="w-[300px]">
84
+ <MoneyInput {...field} currency="USD" value={123} />
85
+ </div>
86
+ );
87
+ },
88
+ };
@@ -1,11 +1,11 @@
1
1
  import { useLocalFormat } from '@/vdb/hooks/use-local-format.js';
2
- import { useUserSettings } from '@/vdb/hooks/use-user-settings.js';
3
2
  import { useEffect, useMemo, useState } from 'react';
4
3
  import { AffixedInput } from './affixed-input.js';
5
4
 
6
5
  import { DashboardFormComponentProps } from '@/vdb/framework/form-engine/form-engine-types.js';
7
6
  import { isReadonlyField } from '@/vdb/framework/form-engine/utils.js';
8
7
  import { useChannel } from '@/vdb/hooks/use-channel.js';
8
+ import { useDisplayLocale } from '@/vdb/hooks/use-display-locale.js';
9
9
 
10
10
  export interface MoneyInputProps extends DashboardFormComponentProps {
11
11
  currency?: string;
@@ -24,9 +24,7 @@ export function MoneyInput(props: Readonly<MoneyInputProps>) {
24
24
  const { activeChannel } = useChannel();
25
25
  const activeCurrency = currency ?? activeChannel?.defaultCurrencyCode;
26
26
  const readOnly = isReadonlyField(props.fieldDef);
27
- const {
28
- settings: { displayLanguage, displayLocale },
29
- } = useUserSettings();
27
+ const { bcp47Tag } = useDisplayLocale();
30
28
  const { toMajorUnits, toMinorUnits } = useLocalFormat();
31
29
  const [displayValue, setDisplayValue] = useState(toMajorUnits(value).toFixed(2));
32
30
 
@@ -40,32 +38,30 @@ export function MoneyInput(props: Readonly<MoneyInputProps>) {
40
38
  if (!activeCurrency) {
41
39
  return false;
42
40
  }
43
- const locale = displayLocale || displayLanguage.replace(/_/g, '-');
44
- const parts = new Intl.NumberFormat(locale, {
41
+ const parts = new Intl.NumberFormat(bcp47Tag, {
45
42
  style: 'currency',
46
43
  currency: activeCurrency,
47
44
  currencyDisplay: 'symbol',
48
45
  }).formatToParts();
49
46
  const NaNString = parts.find(p => p.type === 'nan')?.value ?? 'NaN';
50
- const localised = new Intl.NumberFormat(locale, {
47
+ const localised = new Intl.NumberFormat(bcp47Tag, {
51
48
  style: 'currency',
52
49
  currency: activeCurrency,
53
50
  currencyDisplay: 'symbol',
54
51
  }).format(undefined as any);
55
52
  return localised.indexOf(NaNString) > 0;
56
- }, [activeCurrency, displayLocale, displayLanguage]);
53
+ }, [activeCurrency, bcp47Tag]);
57
54
 
58
55
  // Get the currency symbol
59
56
  const currencySymbol = useMemo(() => {
60
57
  if (!activeCurrency) return '';
61
- const locale = displayLocale || displayLanguage.replace(/_/g, '-');
62
- const parts = new Intl.NumberFormat(locale, {
58
+ const parts = new Intl.NumberFormat(bcp47Tag, {
63
59
  style: 'currency',
64
60
  currency: activeCurrency,
65
61
  currencyDisplay: 'symbol',
66
62
  }).formatToParts();
67
63
  return parts.find(p => p.type === 'currency')?.value ?? activeCurrency;
68
- }, [activeCurrency, displayLocale, displayLanguage]);
64
+ }, [activeCurrency, bcp47Tag]);
69
65
 
70
66
  return (
71
67
  <AffixedInput