@vendure/dashboard 3.4.2-master-202509081248 → 3.4.2-master-202509110229

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 (81) hide show
  1. package/package.json +4 -4
  2. package/src/app/routes/_authenticated/_administrators/administrators_.$id.tsx +4 -4
  3. package/src/app/routes/_authenticated/_assets/assets.tsx +1 -0
  4. package/src/app/routes/_authenticated/_assets/assets_.$id.tsx +1 -1
  5. package/src/app/routes/_authenticated/_channels/channels_.$id.tsx +5 -5
  6. package/src/app/routes/_authenticated/_collections/collections_.$id.tsx +4 -4
  7. package/src/app/routes/_authenticated/_countries/countries_.$id.tsx +4 -4
  8. package/src/app/routes/_authenticated/_customer-groups/customer-groups_.$id.tsx +4 -4
  9. package/src/app/routes/_authenticated/_customers/customers_.$id.tsx +5 -5
  10. package/src/app/routes/_authenticated/_facets/facets.tsx +5 -8
  11. package/src/app/routes/_authenticated/_facets/facets_.$facetId.values_.$id.tsx +4 -4
  12. package/src/app/routes/_authenticated/_facets/facets_.$id.tsx +4 -4
  13. package/src/app/routes/_authenticated/_global-settings/global-settings.tsx +2 -2
  14. package/src/app/routes/_authenticated/_orders/orders_.$id.tsx +1 -1
  15. package/src/app/routes/_authenticated/_orders/orders_.$id_.modify.tsx +5 -1
  16. package/src/app/routes/_authenticated/_orders/orders_.draft.$id.tsx +1 -1
  17. package/src/app/routes/_authenticated/_payment-methods/payment-methods_.$id.tsx +4 -4
  18. package/src/app/routes/_authenticated/_product-variants/product-variants_.$id.tsx +5 -5
  19. package/src/app/routes/_authenticated/_products/products_.$id.tsx +4 -4
  20. package/src/app/routes/_authenticated/_products/products_.$id_.variants.tsx +2 -2
  21. package/src/app/routes/_authenticated/_promotions/promotions_.$id.tsx +5 -5
  22. package/src/app/routes/_authenticated/_roles/roles_.$id.tsx +4 -4
  23. package/src/app/routes/_authenticated/_sellers/sellers_.$id.tsx +4 -4
  24. package/src/app/routes/_authenticated/_shipping-methods/shipping-methods_.$id.tsx +4 -4
  25. package/src/app/routes/_authenticated/_stock-locations/stock-locations_.$id.tsx +4 -4
  26. package/src/app/routes/_authenticated/_system/healthchecks.tsx +1 -0
  27. package/src/app/routes/_authenticated/_tax-categories/tax-categories_.$id.tsx +4 -4
  28. package/src/app/routes/_authenticated/_tax-rates/tax-rates_.$id.tsx +5 -4
  29. package/src/app/routes/_authenticated/_zones/zones_.$id.tsx +4 -4
  30. package/src/lib/components/data-input/affixed-input.tsx +18 -0
  31. package/src/lib/components/data-input/boolean-input.tsx +7 -0
  32. package/src/lib/components/data-input/checkbox-input.tsx +7 -0
  33. package/src/lib/components/data-input/datetime-input.tsx +7 -0
  34. package/src/lib/components/data-input/money-input.tsx +8 -0
  35. package/src/lib/components/data-input/number-input.tsx +7 -0
  36. package/src/lib/components/data-input/password-input.tsx +7 -0
  37. package/src/lib/components/data-input/rich-text-input.tsx +7 -0
  38. package/src/lib/components/data-input/text-input.tsx +7 -0
  39. package/src/lib/components/data-input/textarea-input.tsx +7 -0
  40. package/src/lib/components/data-table/data-table-view-options.tsx +29 -26
  41. package/src/lib/components/data-table/data-table.tsx +20 -0
  42. package/src/lib/components/data-table/types.ts +39 -0
  43. package/src/lib/components/layout/channel-switcher.tsx +1 -3
  44. package/src/lib/components/layout/generated-breadcrumbs.tsx +103 -43
  45. package/src/lib/components/shared/asset/asset-gallery.tsx +58 -0
  46. package/src/lib/components/shared/asset/asset-picker-dialog.tsx +39 -0
  47. package/src/lib/components/shared/detail-page-button.tsx +8 -22
  48. package/src/lib/components/shared/facet-value-chip.tsx +7 -0
  49. package/src/lib/components/shared/facet-value-selector.tsx +55 -0
  50. package/src/lib/components/shared/form-field-wrapper.tsx +51 -0
  51. package/src/lib/components/shared/paginated-list-data-table.tsx +128 -16
  52. package/src/lib/components/shared/permission-guard.tsx +30 -0
  53. package/src/lib/components/shared/table-cell/order-table-cell-components.tsx +1 -1
  54. package/src/lib/components/shared/translatable-form-field.tsx +52 -0
  55. package/src/lib/components/shared/vendure-image.tsx +114 -2
  56. package/src/lib/framework/extension-api/define-dashboard-extension.ts +25 -3
  57. package/src/lib/framework/extension-api/extension-api-types.ts +12 -3
  58. package/src/lib/framework/extension-api/types/alerts.ts +2 -3
  59. package/src/lib/framework/extension-api/types/data-table.ts +2 -2
  60. package/src/lib/framework/extension-api/types/detail-forms.ts +2 -2
  61. package/src/lib/framework/extension-api/types/form-components.ts +2 -2
  62. package/src/lib/framework/extension-api/types/layout.ts +24 -13
  63. package/src/lib/framework/extension-api/types/login.ts +6 -5
  64. package/src/lib/framework/extension-api/types/navigation.ts +3 -3
  65. package/src/lib/framework/extension-api/types/widgets.ts +7 -3
  66. package/src/lib/framework/form-engine/form-engine-types.ts +13 -7
  67. package/src/lib/framework/form-engine/use-generated-form.tsx +44 -0
  68. package/src/lib/framework/layout-engine/page-layout.tsx +94 -31
  69. package/src/lib/framework/page/detail-page.tsx +3 -5
  70. package/src/lib/framework/page/list-page.tsx +87 -5
  71. package/src/lib/framework/page/use-detail-page.ts +4 -5
  72. package/src/lib/graphql/api.ts +2 -2
  73. package/src/lib/graphql/graphql-env.d.ts +7 -16
  74. package/src/lib/hooks/use-auth.tsx +1 -3
  75. package/src/lib/hooks/use-channel.ts +4 -2
  76. package/src/lib/hooks/use-page-block.tsx +9 -0
  77. package/src/lib/hooks/use-permissions.ts +6 -2
  78. package/src/lib/index.ts +2 -0
  79. package/src/lib/providers/auth.tsx +34 -2
  80. package/src/lib/providers/channel-provider.tsx +22 -1
  81. package/src/lib/components/shared/table-cell/table-cell-types.ts +0 -33
@@ -28,7 +28,7 @@ export const Route = createFileRoute('/_authenticated/_sellers/sellers_/$id')({
28
28
  pageId,
29
29
  queryDocument: sellerDetailDocument,
30
30
  breadcrumb: (isNew, entity) => [
31
- { path: '/sellers', label: 'Sellers' },
31
+ { path: '/sellers', label: <Trans>Sellers</Trans> },
32
32
  isNew ? <Trans>New seller</Trans> : entity?.name,
33
33
  ],
34
34
  }),
@@ -55,14 +55,14 @@ function SellerDetailPage() {
55
55
  },
56
56
  params: { id: params.id },
57
57
  onSuccess: async data => {
58
- toast(i18n.t('Successfully updated seller'));
58
+ toast(i18n.t(creatingNewEntity ? 'Successfully created seller' : 'Successfully updated seller'));
59
59
  form.reset(form.getValues());
60
60
  if (creatingNewEntity) {
61
61
  await navigate({ to: `../$id`, params: { id: data.id } });
62
62
  }
63
63
  },
64
64
  onError: err => {
65
- toast(i18n.t('Failed to update seller'), {
65
+ toast(i18n.t(creatingNewEntity ? 'Failed to create seller' : 'Failed to update seller'), {
66
66
  description: err instanceof Error ? err.message : 'Unknown error',
67
67
  });
68
68
  },
@@ -78,7 +78,7 @@ function SellerDetailPage() {
78
78
  type="submit"
79
79
  disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
80
80
  >
81
- <Trans>Update</Trans>
81
+ {creatingNewEntity ? <Trans>Create</Trans> : <Trans>Update</Trans>}
82
82
  </Button>
83
83
  </PermissionGuard>
84
84
  </PageActionBarRight>
@@ -39,7 +39,7 @@ export const Route = createFileRoute('/_authenticated/_shipping-methods/shipping
39
39
  queryDocument: shippingMethodDetailDocument,
40
40
  breadcrumb(isNew, entity) {
41
41
  return [
42
- { path: '/shipping-methods', label: 'Shipping methods' },
42
+ { path: '/shipping-methods', label: <Trans>Shipping Methods</Trans> },
43
43
  isNew ? <Trans>New shipping method</Trans> : entity?.name,
44
44
  ];
45
45
  },
@@ -84,14 +84,14 @@ function ShippingMethodDetailPage() {
84
84
  },
85
85
  params: { id: params.id },
86
86
  onSuccess: async data => {
87
- toast.success(i18n.t('Successfully updated shipping method'));
87
+ toast.success(i18n.t(creatingNewEntity ? 'Successfully created shipping method' : 'Successfully updated shipping method'));
88
88
  resetForm();
89
89
  if (creatingNewEntity) {
90
90
  await navigate({ to: `../$id`, params: { id: data.id } });
91
91
  }
92
92
  },
93
93
  onError: err => {
94
- toast.error(i18n.t('Failed to update shipping method'), {
94
+ toast.error(i18n.t(creatingNewEntity ? 'Failed to create shipping method' : 'Failed to update shipping method'), {
95
95
  description: err instanceof Error ? err.message : 'Unknown error',
96
96
  });
97
97
  },
@@ -109,7 +109,7 @@ function ShippingMethodDetailPage() {
109
109
  type="submit"
110
110
  disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
111
111
  >
112
- <Trans>Update</Trans>
112
+ {creatingNewEntity ? <Trans>Create</Trans> : <Trans>Update</Trans>}
113
113
  </Button>
114
114
  </PermissionGuard>
115
115
  </PageActionBarRight>
@@ -35,7 +35,7 @@ export const Route = createFileRoute('/_authenticated/_stock-locations/stock-loc
35
35
  queryDocument: stockLocationDetailQuery,
36
36
  breadcrumb(isNew, entity) {
37
37
  return [
38
- { path: '/stock-locations', label: 'Stock locations' },
38
+ { path: '/stock-locations', label: <Trans>Stock Locations</Trans> },
39
39
  isNew ? <Trans>New stock location</Trans> : entity?.name,
40
40
  ];
41
41
  },
@@ -64,14 +64,14 @@ function StockLocationDetailPage() {
64
64
  },
65
65
  params: { id: params.id },
66
66
  onSuccess: async data => {
67
- toast.success(i18n.t('Successfully updated stock location'));
67
+ toast.success(i18n.t(creatingNewEntity ? 'Successfully created stock location' : 'Successfully updated stock location'));
68
68
  resetForm();
69
69
  if (creatingNewEntity) {
70
70
  await navigate({ to: `../$id`, params: { id: data.id } });
71
71
  }
72
72
  },
73
73
  onError: err => {
74
- toast.error(i18n.t('Failed to update stock location'), {
74
+ toast.error(i18n.t(creatingNewEntity ? 'Failed to create stock location' : 'Failed to update stock location'), {
75
75
  description: err instanceof Error ? err.message : 'Unknown error',
76
76
  });
77
77
  },
@@ -89,7 +89,7 @@ function StockLocationDetailPage() {
89
89
  type="submit"
90
90
  disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
91
91
  >
92
- <Trans>Update</Trans>
92
+ {creatingNewEntity ? <Trans>Create</Trans> : <Trans>Update</Trans>}
93
93
  </Button>
94
94
  </PermissionGuard>
95
95
  </PageActionBarRight>
@@ -10,6 +10,7 @@ import { uiConfig } from 'virtual:vendure-ui-config';
10
10
 
11
11
  export const Route = createFileRoute('/_authenticated/_system/healthchecks')({
12
12
  component: HealthchecksPage,
13
+ loader: () => ({ breadcrumb: () => <Trans>Healthchecks</Trans> }),
13
14
  });
14
15
 
15
16
  interface HealthcheckItem {
@@ -35,7 +35,7 @@ export const Route = createFileRoute('/_authenticated/_tax-categories/tax-catego
35
35
  queryDocument: taxCategoryDetailDocument,
36
36
  breadcrumb(isNew, entity) {
37
37
  return [
38
- { path: '/tax-categories', label: 'Tax categories' },
38
+ { path: '/tax-categories', label: <Trans>Tax Categories</Trans> },
39
39
  isNew ? <Trans>New tax category</Trans> : entity?.name,
40
40
  ];
41
41
  },
@@ -63,14 +63,14 @@ function TaxCategoryDetailPage() {
63
63
  },
64
64
  params: { id: params.id },
65
65
  onSuccess: async data => {
66
- toast.success(i18n.t('Successfully updated tax category'));
66
+ toast.success(i18n.t(creatingNewEntity ? 'Successfully created tax category' : 'Successfully updated tax category'));
67
67
  form.reset(form.getValues());
68
68
  if (creatingNewEntity) {
69
69
  await navigate({ to: `../$id`, params: { id: data.id } });
70
70
  }
71
71
  },
72
72
  onError: err => {
73
- toast.error(i18n.t('Failed to update tax category'), {
73
+ toast.error(i18n.t(creatingNewEntity ? 'Failed to create tax category' : 'Failed to update tax category'), {
74
74
  description: err instanceof Error ? err.message : 'Unknown error',
75
75
  });
76
76
  },
@@ -88,7 +88,7 @@ function TaxCategoryDetailPage() {
88
88
  type="submit"
89
89
  disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
90
90
  >
91
- <Trans>Update</Trans>
91
+ {creatingNewEntity ? <Trans>Create</Trans> : <Trans>Update</Trans>}
92
92
  </Button>
93
93
  </PermissionGuard>
94
94
  </PageActionBarRight>
@@ -34,7 +34,7 @@ export const Route = createFileRoute('/_authenticated/_tax-rates/tax-rates_/$id'
34
34
  queryDocument: taxRateDetailDocument,
35
35
  breadcrumb(isNew, entity) {
36
36
  return [
37
- { path: '/tax-rates', label: 'Tax rates' },
37
+ { path: '/tax-rates', label: <Trans>Tax Rates</Trans> },
38
38
  isNew ? <Trans>New tax rate</Trans> : entity?.name,
39
39
  ];
40
40
  },
@@ -67,14 +67,14 @@ function TaxRateDetailPage() {
67
67
  },
68
68
  params: { id: params.id },
69
69
  onSuccess: async data => {
70
- toast.success(i18n.t('Successfully updated tax rate'));
70
+ toast.success(i18n.t(creatingNewEntity ? 'Successfully created tax rate' : 'Successfully updated tax rate'));
71
71
  resetForm();
72
72
  if (creatingNewEntity) {
73
73
  await navigate({ to: `../$id`, params: { id: data.id } });
74
74
  }
75
75
  },
76
76
  onError: err => {
77
- toast.error(i18n.t('Failed to update tax rate'), {
77
+ toast.error(i18n.t(creatingNewEntity ? 'Failed to create tax rate' : 'Failed to update tax rate'), {
78
78
  description: err instanceof Error ? err.message : 'Unknown error',
79
79
  });
80
80
  },
@@ -90,7 +90,7 @@ function TaxRateDetailPage() {
90
90
  type="submit"
91
91
  disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
92
92
  >
93
- <Trans>Update</Trans>
93
+ {creatingNewEntity ? <Trans>Create</Trans> : <Trans>Update</Trans>}
94
94
  </Button>
95
95
  </PermissionGuard>
96
96
  </PageActionBarRight>
@@ -120,6 +120,7 @@ function TaxRateDetailPage() {
120
120
  label={<Trans>Rate</Trans>}
121
121
  render={({ field }) => (
122
122
  <AffixedInput
123
+ {...field}
123
124
  type="number"
124
125
  suffix="%"
125
126
  value={field.value}
@@ -30,7 +30,7 @@ export const Route = createFileRoute('/_authenticated/_zones/zones_/$id')({
30
30
  pageId,
31
31
  queryDocument: zoneDetailDocument,
32
32
  breadcrumb(isNew, entity) {
33
- return [{ path: '/zones', label: 'Zones' }, isNew ? <Trans>New zone</Trans> : entity?.name];
33
+ return [{ path: '/zones', label: <Trans>Zones</Trans> }, isNew ? <Trans>New zone</Trans> : entity?.name];
34
34
  },
35
35
  }),
36
36
  errorComponent: ({ error }) => <ErrorPage message={error.message} />,
@@ -56,14 +56,14 @@ function ZoneDetailPage() {
56
56
  },
57
57
  params: { id: params.id },
58
58
  onSuccess: async data => {
59
- toast.success(i18n.t('Successfully updated zone'));
59
+ toast.success(i18n.t(creatingNewEntity ? 'Successfully created zone' : 'Successfully updated zone'));
60
60
  resetForm();
61
61
  if (creatingNewEntity) {
62
62
  await navigate({ to: `../$id`, params: { id: data.id } });
63
63
  }
64
64
  },
65
65
  onError: err => {
66
- toast.error(i18n.t('Failed to update zone'), {
66
+ toast.error(i18n.t(creatingNewEntity ? 'Failed to create zone' : 'Failed to update zone'), {
67
67
  description: err instanceof Error ? err.message : 'Unknown error',
68
68
  });
69
69
  },
@@ -79,7 +79,7 @@ function ZoneDetailPage() {
79
79
  type="submit"
80
80
  disabled={!form.formState.isDirty || !form.formState.isValid || isPending}
81
81
  >
82
- <Trans>Update</Trans>
82
+ {creatingNewEntity ? <Trans>Create</Trans> : <Trans>Update</Trans>}
83
83
  </Button>
84
84
  </PermissionGuard>
85
85
  </PageActionBarRight>
@@ -10,6 +10,24 @@ export type AffixedInputProps = Omit<React.InputHTMLAttributes<HTMLInputElement>
10
10
  suffix?: ReactNode;
11
11
  };
12
12
 
13
+ /**
14
+ * @description
15
+ * A component for displaying an input with a prefix and/or a suffix.
16
+ *
17
+ * @example
18
+ * ```tsx
19
+ * <AffixedInput
20
+ * {...field}
21
+ * type="number"
22
+ * suffix="%"
23
+ * value={field.value}
24
+ * onChange={e => field.onChange(e.target.valueAsNumber)}
25
+ * />
26
+ * ```
27
+ *
28
+ * @docsCategory form-components
29
+ * @docsPage AffixedInput
30
+ */
13
31
  export function AffixedInput({ prefix, suffix, className = '', ...props }: Readonly<AffixedInputProps>) {
14
32
  const readOnly = props.disabled || isReadonlyField(props.fieldDef);
15
33
  const prefixRef = useRef<HTMLSpanElement>(null);
@@ -2,6 +2,13 @@ import { Switch } from '@/vdb/components/ui/switch.js';
2
2
  import { DashboardFormComponentProps } from '@/vdb/framework/form-engine/form-engine-types.js';
3
3
  import { isReadonlyField } from '@/vdb/framework/form-engine/utils.js';
4
4
 
5
+ /**
6
+ * @description
7
+ * Displays a boolean value as a switch toggle.
8
+ *
9
+ * @docsCategory form-components
10
+ * @docsPage BooleanInput
11
+ */
5
12
  export function BooleanInput({ value, onChange, fieldDef }: Readonly<DashboardFormComponentProps>) {
6
13
  const checked = typeof value === 'string' ? value === 'true' : value;
7
14
  const readOnly = isReadonlyField(fieldDef);
@@ -2,6 +2,13 @@ import { Checkbox } from '../ui/checkbox.js';
2
2
  import { DashboardFormComponentProps } from '@/vdb/framework/form-engine/form-engine-types.js';
3
3
  import { isReadonlyField } from '@/vdb/framework/form-engine/utils.js';
4
4
 
5
+ /**
6
+ * @description
7
+ * Displays a boolean value as a checkbox.
8
+ *
9
+ * @docsCategory form-components
10
+ * @docsPage CheckboxInput
11
+ */
5
12
  export function CheckboxInput({ value, onChange, fieldDef }: Readonly<DashboardFormComponentProps>) {
6
13
  const readOnly = isReadonlyField(fieldDef);
7
14
  return <Checkbox checked={value} onCheckedChange={onChange} disabled={readOnly} />;
@@ -12,6 +12,13 @@ import { cn } from '@/vdb/lib/utils.js';
12
12
  import { CalendarClock } from 'lucide-react';
13
13
  import { isReadonlyField } from '@/vdb/framework/form-engine/utils.js';
14
14
 
15
+ /**
16
+ * @description
17
+ * A component for selecting a date and time.
18
+ *
19
+ * @docsCategory form-components
20
+ * @docsPage DateTimeInput
21
+ */
15
22
  export function DateTimeInput({ value, onChange, fieldDef }: Readonly<DashboardFormComponentProps>) {
16
23
  const readOnly = isReadonlyField(fieldDef);
17
24
  const date = value && value instanceof Date ? value.toISOString() : (value ?? '');
@@ -11,6 +11,14 @@ export interface MoneyInputProps extends DashboardFormComponentProps {
11
11
  currency?: string;
12
12
  }
13
13
 
14
+ /**
15
+ * @description
16
+ * A component for displaying a money value. The `currency` can be specified, but otherwise
17
+ * will be taken from the active channel's default currency.
18
+ *
19
+ * @docsCategory form-components
20
+ * @docsPage MoneyInput
21
+ */
14
22
  export function MoneyInput(props: Readonly<MoneyInputProps>) {
15
23
  const { value, onChange, currency, ...rest } = props;
16
24
  const { activeChannel } = useChannel();
@@ -4,6 +4,13 @@ import { Input } from '@/vdb/components/ui/input.js';
4
4
  import { DashboardFormComponentProps } from '@/vdb/framework/form-engine/form-engine-types.js';
5
5
  import { isReadonlyField } from '@/vdb/framework/form-engine/utils.js';
6
6
 
7
+ /**
8
+ * @description
9
+ * A component for displaying a numeric value.
10
+ *
11
+ * @docsCategory form-components
12
+ * @docsPage NumberInput
13
+ */
7
14
  export function NumberInput({ fieldDef, onChange, ...fieldProps }: Readonly<DashboardFormComponentProps>) {
8
15
  const readOnly = fieldProps.disabled || isReadonlyField(fieldDef);
9
16
  const isFloat = fieldDef ? fieldDef.type === 'float' : false;
@@ -2,6 +2,13 @@ import { DashboardFormComponentProps } from '@/vdb/framework/form-engine/form-en
2
2
  import { isReadonlyField } from '@/vdb/framework/form-engine/utils.js';
3
3
  import { Input } from '../ui/input.js';
4
4
 
5
+ /**
6
+ * @description
7
+ * A component for displaying a password input.
8
+ *
9
+ * @docsCategory form-components
10
+ * @docsPage PasswordInput
11
+ */
5
12
  export function PasswordInput(props: Readonly<DashboardFormComponentProps>) {
6
13
  const readOnly = props.disabled || isReadonlyField(props.fieldDef);
7
14
  return (
@@ -22,6 +22,13 @@ const extensions = [
22
22
  }),
23
23
  ];
24
24
 
25
+ /**
26
+ * @description
27
+ * A component for displaying a rich text editor. Internally uses ProseMirror (rich text editor) under the hood.
28
+ *
29
+ * @docsCategory form-components
30
+ * @docsPage RichTextInput
31
+ */
25
32
  export function RichTextInput({ value, onChange, fieldDef }: Readonly<DashboardFormComponentProps>) {
26
33
  const readOnly = isReadonlyField(fieldDef);
27
34
  const isInternalUpdate = useRef(false);
@@ -3,6 +3,13 @@ import { Input } from '@/vdb/components/ui/input.js';
3
3
  import { DashboardFormComponent } from '@/vdb/framework/form-engine/form-engine-types.js';
4
4
  import { isReadonlyField } from '@/vdb/framework/form-engine/utils.js';
5
5
 
6
+ /**
7
+ * @description
8
+ * A component for displaying a text input.
9
+ *
10
+ * @docsCategory form-components
11
+ * @docsPage TextInput
12
+ */
6
13
  export const TextInput: DashboardFormComponent = ({ value, onChange, fieldDef }) => {
7
14
  const readOnly = isReadonlyField(fieldDef);
8
15
  return <Input value={value || ''} onChange={e => onChange(e.target.value)} disabled={readOnly} />;
@@ -2,6 +2,13 @@ import { Textarea } from '@/vdb/components/ui/textarea.js';
2
2
  import { DashboardFormComponentProps } from '@/vdb/framework/form-engine/form-engine-types.js';
3
3
  import { isReadonlyField } from '@/vdb/framework/form-engine/utils.js';
4
4
 
5
+ /**
6
+ * @description
7
+ * A component for displaying a textarea input.
8
+ *
9
+ * @docsCategory form-components
10
+ * @docsPage TextareaInput
11
+ */
5
12
  export function TextareaInput(props: Readonly<DashboardFormComponentProps>) {
6
13
  const readOnly = props.disabled || isReadonlyField(props.fieldDef);
7
14
  return (
@@ -16,6 +16,7 @@ import {
16
16
  DropdownMenuSeparator,
17
17
  DropdownMenuTrigger,
18
18
  } from '@/vdb/components/ui/dropdown-menu.js';
19
+ import { ScrollArea } from '@/vdb/components/ui/scroll-area.js';
19
20
  import { usePage } from '@/vdb/hooks/use-page.js';
20
21
  import { useUserSettings } from '@/vdb/hooks/use-user-settings.js';
21
22
  import { Trans } from '@/vdb/lib/trans.js';
@@ -76,39 +77,41 @@ export function DataTableViewOptions<TData>({ table }: DataTableViewOptionsProps
76
77
 
77
78
  return (
78
79
  <div className="flex items-center gap-2">
79
- <DropdownMenu>
80
+ <DropdownMenu modal={false}>
80
81
  <DropdownMenuTrigger asChild>
81
82
  <Button variant="ghost" size="sm" className="ml-auto hidden h-8 lg:flex">
82
83
  <Settings2 />
83
84
  <Trans>Columns</Trans>
84
85
  </Button>
85
86
  </DropdownMenuTrigger>
86
- <DropdownMenuContent align="end">
87
- <DndContext
88
- collisionDetection={closestCenter}
89
- onDragEnd={handleDragEnd}
90
- modifiers={[restrictToVerticalAxis]}
91
- >
92
- <SortableContext
93
- items={columns.map(col => col.id)}
94
- strategy={verticalListSortingStrategy}
87
+ <DropdownMenuContent align="end" className="overflow-auto">
88
+ <ScrollArea className="max-h-[60vh]" type="always">
89
+ <DndContext
90
+ collisionDetection={closestCenter}
91
+ onDragEnd={handleDragEnd}
92
+ modifiers={[restrictToVerticalAxis]}
95
93
  >
96
- {columns.map(column => (
97
- <SortableItem key={column.id} id={column.id}>
98
- <DropdownMenuCheckboxItem
99
- className="capitalize"
100
- checked={column.getIsVisible()}
101
- onCheckedChange={value => column.toggleVisibility(!!value)}
102
- onSelect={e => e.preventDefault()}
103
- >
104
- {column.id}
105
- </DropdownMenuCheckboxItem>
106
- </SortableItem>
107
- ))}
108
- </SortableContext>
109
- </DndContext>
110
- <DropdownMenuSeparator />
111
- <DropdownMenuItem onClick={handleReset}>Reset</DropdownMenuItem>
94
+ <SortableContext
95
+ items={columns.map(col => col.id)}
96
+ strategy={verticalListSortingStrategy}
97
+ >
98
+ {columns.map(column => (
99
+ <SortableItem key={column.id} id={column.id}>
100
+ <DropdownMenuCheckboxItem
101
+ className="capitalize"
102
+ checked={column.getIsVisible()}
103
+ onCheckedChange={value => column.toggleVisibility(!!value)}
104
+ onSelect={e => e.preventDefault()}
105
+ >
106
+ {column.id}
107
+ </DropdownMenuCheckboxItem>
108
+ </SortableItem>
109
+ ))}
110
+ </SortableContext>
111
+ </DndContext>
112
+ <DropdownMenuSeparator />
113
+ <DropdownMenuItem onClick={handleReset}>Reset</DropdownMenuItem>
114
+ </ScrollArea>
112
115
  </DropdownMenuContent>
113
116
  </DropdownMenu>
114
117
  </div>
@@ -35,6 +35,14 @@ export interface FacetedFilter {
35
35
  options?: DataTableFacetedFilterOption[];
36
36
  }
37
37
 
38
+ /**
39
+ * @description
40
+ * Props for configuring the {@link DataTable}.
41
+ *
42
+ * @docsCategory list-views
43
+ * @docsPage DataTable
44
+ * @since 3.4.0
45
+ */
38
46
  interface DataTableProps<TData> {
39
47
  children?: React.ReactNode;
40
48
  columns: ColumnDef<TData, any>[];
@@ -55,6 +63,7 @@ interface DataTableProps<TData> {
55
63
  disableViewOptions?: boolean;
56
64
  bulkActions?: BulkAction[];
57
65
  /**
66
+ * @description
58
67
  * This property allows full control over _all_ features of TanStack Table
59
68
  * when needed.
60
69
  */
@@ -62,6 +71,17 @@ interface DataTableProps<TData> {
62
71
  onRefresh?: () => void;
63
72
  }
64
73
 
74
+ /**
75
+ * @description
76
+ * A data table which includes sorting, filtering, pagination, bulk actions, column controls etc.
77
+ *
78
+ * This is the building block of all data tables in the Dashboard.
79
+ *
80
+ * @docsCategory list-views
81
+ * @docsPage DataTable
82
+ * @since 3.4.0
83
+ * @docsWeight 0
84
+ */
65
85
  export function DataTable<TData>({
66
86
  children,
67
87
  columns,
@@ -1 +1,40 @@
1
+ import { CellContext } from '@tanstack/table-core';
2
+
1
3
  export type ColumnDataType = 'String' | 'Int' | 'Float' | 'DateTime' | 'Boolean' | 'ID' | 'Money';
4
+
5
+ /**
6
+ * @description
7
+ * This type is used to define re-usable components that can render a table cell in a
8
+ * DataTable.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * import { DataTableCellComponent } from '\@vendure/dashboard';
13
+ *
14
+ * type CustomerCellData = {
15
+ * customer: {
16
+ * id: string;
17
+ * firstName: string;
18
+ * lastName: string;
19
+ * } | null;
20
+ * };
21
+ *
22
+ * export const CustomerCell: DataTableCellComponent<CustomerCellData> = ({ row }) => {
23
+ * const value = row.original.customer;
24
+ * if (!value) {
25
+ * return null;
26
+ * }
27
+ * return (
28
+ * <Button asChild variant="ghost">
29
+ * <Link to={`/customers/${value.id}`}>
30
+ * {value.firstName} {value.lastName}
31
+ * </Link>
32
+ * </Button>
33
+ * );
34
+ * };
35
+ * ```
36
+ *
37
+ * @docsCategory list-views
38
+ * @since 3.4.0
39
+ */
40
+ export type DataTableCellComponent<T> = <Data extends T>(context: CellContext<Data, any>) => any;
@@ -54,9 +54,7 @@ export function ChannelSwitcher() {
54
54
  setContentLanguage,
55
55
  } = useUserSettings();
56
56
  const [showManageLanguagesDialog, setShowManageLanguagesDialog] = useState(false);
57
-
58
- // Use the selected channel if available, otherwise fall back to the active channel
59
- const displayChannel = activeChannel || activeChannel;
57
+ const displayChannel = activeChannel;
60
58
 
61
59
  // Get available languages from server config
62
60
  const availableLanguages = serverConfig?.availableLanguages || [];