@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
@@ -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
+ };
@@ -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
+ };
@@ -0,0 +1,103 @@
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 { NumberInput } from './number-input.js';
5
+
6
+ const meta = {
7
+ title: 'Form Inputs/NumberInput',
8
+ component: NumberInput,
9
+ ...withDescription(import.meta.url, './number-input.js'),
10
+ parameters: {
11
+ layout: 'centered',
12
+ },
13
+ tags: ['autodocs'],
14
+ argTypes: {
15
+ value: {
16
+ control: 'number',
17
+ description: 'The current value',
18
+ },
19
+ min: {
20
+ control: 'number',
21
+ description: 'Minimum value',
22
+ },
23
+ max: {
24
+ control: 'number',
25
+ description: 'Maximum value',
26
+ },
27
+ step: {
28
+ control: 'number',
29
+ description: 'Step increment',
30
+ },
31
+ disabled: {
32
+ control: 'boolean',
33
+ description: 'Whether the input is disabled',
34
+ },
35
+ },
36
+ } satisfies Meta<typeof NumberInput>;
37
+
38
+ export default meta;
39
+ type Story = StoryObj<typeof meta>;
40
+
41
+ export const Playground: Story = {
42
+ args: {
43
+ value: 42,
44
+ min: 0,
45
+ max: 100,
46
+ step: 1,
47
+ disabled: false,
48
+ },
49
+ render: args => {
50
+ const { register } = useForm();
51
+ const field = register('playground');
52
+ return (
53
+ <div className="w-[300px]">
54
+ <NumberInput {...field} {...args} />
55
+ </div>
56
+ );
57
+ },
58
+ };
59
+
60
+ export const Float: Story = {
61
+ render: () => {
62
+ const { register } = useForm();
63
+ const field = register('float');
64
+ return (
65
+ <div className="w-[300px] space-y-2">
66
+ <NumberInput {...field} step={0.01} fieldDef={{ type: 'float' }} />
67
+ <div className="text-sm text-muted-foreground">
68
+ <div>Floating point with step 0.01</div>
69
+ </div>
70
+ </div>
71
+ );
72
+ },
73
+ };
74
+
75
+ export const WithPrefixAndSuffix: Story = {
76
+ render: () => {
77
+ const { register } = useForm();
78
+ const field = register('withAffix');
79
+ return (
80
+ <div className="w-[300px] space-y-2">
81
+ <NumberInput {...field} fieldDef={{ ui: { prefix: '$', suffix: 'USD' } }} step={10} />
82
+ <div className="text-sm text-muted-foreground">
83
+ Demonstrates fieldDef.ui.prefix and fieldDef.ui.suffix
84
+ </div>
85
+ </div>
86
+ );
87
+ },
88
+ };
89
+
90
+ export const NullValue: Story = {
91
+ render: () => {
92
+ const { register } = useForm();
93
+ const field = register('nullValue');
94
+ return (
95
+ <div className="w-[300px] space-y-2">
96
+ <NumberInput {...field} />
97
+ <div className="text-sm text-muted-foreground">
98
+ <div className="mt-1 text-xs">When input is cleared, value becomes null</div>
99
+ </div>
100
+ </div>
101
+ );
102
+ },
103
+ };