@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.
- package/dist/plugin/dashboard.plugin.js +1 -1
- package/dist/vite/utils/ast-utils.spec.js +3 -3
- package/dist/vite/vite-plugin-hmr.d.ts +8 -0
- package/dist/vite/vite-plugin-hmr.js +34 -0
- package/dist/vite/vite-plugin-theme.js +6 -6
- package/dist/vite/vite-plugin-transform-index.js +6 -1
- package/dist/vite/vite-plugin-vendure-dashboard.d.ts +31 -4
- package/dist/vite/vite-plugin-vendure-dashboard.js +89 -34
- package/package.json +17 -5
- package/src/app/app-providers.tsx +4 -1
- package/src/app/common/map-faceted-filter-fields.ts +21 -0
- package/src/app/main.tsx +3 -1
- package/src/app/routes/_authenticated/_administrators/administrators.graphql.ts +2 -2
- package/src/app/routes/_authenticated/_administrators/administrators.tsx +13 -3
- package/src/app/routes/_authenticated/_administrators/administrators_.$id.tsx +6 -13
- package/src/app/routes/_authenticated/_administrators/components/role-permissions-display.tsx +1 -1
- package/src/app/routes/_authenticated/_assets/assets.tsx +17 -1
- package/src/app/routes/_authenticated/_collections/collections.graphql.ts +1 -0
- package/src/app/routes/_authenticated/_collections/collections.tsx +5 -0
- package/src/app/routes/_authenticated/_collections/components/collection-bulk-actions.tsx +0 -1
- package/src/app/routes/_authenticated/_customers/customers.tsx +9 -5
- package/src/app/routes/_authenticated/_facets/components/facet-bulk-actions.tsx +0 -6
- package/src/app/routes/_authenticated/_facets/components/facet-value-bulk-actions.tsx +16 -0
- package/src/app/routes/_authenticated/_facets/components/facet-values-table.tsx +43 -12
- package/src/app/routes/_authenticated/_facets/facets_.$facetId.values_.$id.tsx +14 -5
- package/src/app/routes/_authenticated/_orders/components/edit-order-table.tsx +117 -92
- package/src/app/routes/_authenticated/_orders/components/order-detail-shared.tsx +1 -1
- package/src/app/routes/_authenticated/_orders/components/order-modification-summary.tsx +2 -1
- package/src/app/routes/_authenticated/_orders/components/order-table-totals.tsx +26 -27
- package/src/app/routes/_authenticated/_orders/components/order-table.tsx +5 -3
- package/src/app/routes/_authenticated/_orders/components/state-transition-control.tsx +6 -9
- package/src/app/routes/_authenticated/_orders/orders.graphql.ts +17 -1
- package/src/app/routes/_authenticated/_orders/orders_.$id_.modify.tsx +48 -281
- package/src/app/routes/_authenticated/_orders/orders_.draft.$id.tsx +59 -40
- package/src/app/routes/_authenticated/_orders/utils/order-utils.ts +73 -0
- package/src/app/routes/_authenticated/_orders/utils/use-modify-order.ts +312 -0
- package/src/app/routes/_authenticated/_payment-methods/payment-methods.graphql.ts +2 -2
- package/src/app/routes/_authenticated/_payment-methods/payment-methods.tsx +4 -0
- package/src/app/routes/_authenticated/_products/components/product-bulk-actions.tsx +0 -6
- package/src/app/routes/_authenticated/_products/products.tsx +6 -2
- package/src/app/routes/_authenticated/_products/products_.$productId.option-groups.$productOptionGroupId.options_.$id.tsx +4 -8
- package/src/app/routes/_authenticated/_promotions/components/promotion-bulk-actions.tsx +0 -10
- package/src/app/routes/_authenticated/_promotions/promotions.graphql.ts +2 -2
- package/src/app/routes/_authenticated/_promotions/promotions.tsx +12 -0
- package/src/app/routes/_authenticated/_promotions/promotions_.$id.tsx +6 -2
- package/src/app/routes/_authenticated/_sellers/sellers.graphql.ts +2 -2
- package/src/app/routes/_authenticated/_shipping-methods/shipping-methods.graphql.ts +2 -2
- package/src/app/routes/_authenticated/_shipping-methods/shipping-methods.tsx +4 -0
- package/src/app/routes/_authenticated/_shipping-methods/shipping-methods_.$id.tsx +4 -10
- package/src/app/routes/_authenticated/_stock-locations/stock-locations.graphql.ts +2 -2
- package/src/app/routes/_authenticated/_tax-categories/tax-categories.graphql.ts +2 -2
- package/src/app/routes/_authenticated/_tax-rates/tax-rates.tsx +9 -0
- package/src/app/routes/_authenticated/_tax-rates/tax-rates_.$id.tsx +1 -0
- package/src/app/routes/_authenticated/_zones/zones.graphql.ts +2 -2
- package/src/app/routes/login.tsx +2 -2
- package/src/i18n/locales/ar.po +420 -289
- package/src/i18n/locales/cs.po +420 -289
- package/src/i18n/locales/de.po +420 -289
- package/src/i18n/locales/en.po +420 -289
- package/src/i18n/locales/es.po +420 -289
- package/src/i18n/locales/fa.po +420 -289
- package/src/i18n/locales/fr.po +468 -337
- package/src/i18n/locales/he.po +420 -289
- package/src/i18n/locales/hr.po +420 -289
- package/src/i18n/locales/it.po +420 -289
- package/src/i18n/locales/ja.po +420 -289
- package/src/i18n/locales/nb.po +420 -289
- package/src/i18n/locales/ne.po +420 -289
- package/src/i18n/locales/pl.po +420 -289
- package/src/i18n/locales/pt_BR.po +420 -289
- package/src/i18n/locales/pt_PT.po +420 -289
- package/src/i18n/locales/ru.po +420 -289
- package/src/i18n/locales/sv.po +420 -289
- package/src/i18n/locales/tr.po +420 -289
- package/src/i18n/locales/uk.po +420 -289
- package/src/i18n/locales/zh_Hans.po +420 -289
- package/src/i18n/locales/zh_Hant.po +420 -289
- package/src/lib/components/data-input/affixed-input.stories.tsx +93 -0
- package/src/lib/components/data-input/affixed-input.tsx +5 -2
- package/src/lib/components/data-input/boolean-input.stories.tsx +102 -0
- package/src/lib/components/data-input/checkbox-input.stories.tsx +61 -0
- package/src/lib/components/data-input/datetime-input.stories.tsx +62 -0
- package/src/lib/components/data-input/datetime-input.tsx +27 -13
- package/src/lib/components/data-input/default-relation-input.tsx +18 -12
- package/src/lib/components/data-input/money-input.stories.tsx +88 -0
- package/src/lib/components/data-input/number-input.stories.tsx +103 -0
- package/src/lib/components/data-input/number-input.tsx +10 -4
- package/src/lib/components/data-input/password-input.stories.tsx +65 -0
- package/src/lib/components/data-input/rich-text-input.stories.tsx +92 -0
- package/src/lib/components/data-input/slug-input.stories.tsx +232 -0
- package/src/lib/components/data-input/slug-input.tsx +9 -10
- package/src/lib/components/data-input/text-input.stories.tsx +52 -0
- package/src/lib/components/data-input/textarea-input.stories.tsx +55 -0
- package/src/lib/components/data-table/add-filter-menu.tsx +6 -1
- package/src/lib/components/data-table/column-header-wrapper.tsx +106 -0
- package/src/lib/components/data-table/data-table-bulk-action-item.tsx +11 -9
- package/src/lib/components/data-table/data-table-bulk-actions.tsx +4 -4
- package/src/lib/components/data-table/data-table-column-header.tsx +17 -14
- package/src/lib/components/data-table/data-table-faceted-filter.tsx +33 -11
- package/src/lib/components/data-table/data-table-filter-badge-editable.tsx +35 -0
- package/src/lib/components/data-table/data-table-filter-badge.tsx +23 -16
- package/src/lib/components/data-table/data-table-filter-dialog.tsx +28 -8
- package/src/lib/components/data-table/data-table-pagination.tsx +23 -7
- package/src/lib/components/data-table/data-table.stories.tsx +249 -0
- package/src/lib/components/data-table/data-table.tsx +37 -9
- package/src/lib/components/data-table/filters/data-table-datetime-filter.tsx +79 -34
- package/src/lib/components/data-table/use-generated-columns.tsx +55 -27
- package/src/lib/components/layout/nav-user.tsx +19 -13
- package/src/lib/components/login/login-form.tsx +39 -123
- package/src/lib/components/shared/alerts.tsx +29 -17
- package/src/lib/components/shared/asset/asset-bulk-actions.tsx +3 -3
- package/src/lib/components/shared/asset/asset-gallery.stories.tsx +76 -0
- package/src/lib/components/shared/asset/asset-gallery.tsx +147 -113
- package/src/lib/components/shared/asset/asset-picker-dialog.stories.tsx +58 -0
- package/src/lib/components/shared/customer-group-selector.tsx +5 -2
- package/src/lib/components/shared/detail-page-button.stories.tsx +52 -0
- package/src/lib/components/shared/facet-value-selector.stories.tsx +48 -0
- package/src/lib/components/shared/facet-value-selector.tsx +130 -34
- package/src/lib/components/shared/paginated-list-data-table.stories.tsx +212 -0
- package/src/lib/components/shared/paginated-list-data-table.tsx +12 -12
- package/src/lib/components/shared/permission-guard.stories.tsx +46 -0
- package/src/lib/components/shared/remove-from-channel-bulk-action.tsx +2 -0
- package/src/lib/components/shared/rich-text-editor/responsive-toolbar.tsx +8 -4
- package/src/lib/components/shared/rich-text-editor/rich-text-editor.tsx +1 -0
- package/src/lib/components/shared/table-cell/order-table-cell-components.tsx +40 -0
- package/src/lib/components/shared/vendure-image.stories.tsx +167 -0
- package/src/lib/components/shared/vendure-image.tsx +6 -7
- package/src/lib/components/ui/accordion.stories.tsx +33 -0
- package/src/lib/components/ui/alert-dialog.stories.tsx +48 -0
- package/src/lib/components/ui/alert.stories.tsx +35 -0
- package/src/lib/components/ui/aspect-ratio.stories.tsx +28 -0
- package/src/lib/components/ui/badge.stories.tsx +28 -0
- package/src/lib/components/ui/breadcrumb.stories.tsx +41 -0
- package/src/lib/components/ui/button.stories.tsx +38 -0
- package/src/lib/components/ui/calendar.stories.tsx +22 -0
- package/src/lib/components/ui/card.stories.tsx +28 -0
- package/src/lib/components/ui/carousel.stories.tsx +34 -0
- package/src/lib/components/ui/checkbox.stories.tsx +31 -0
- package/src/lib/components/ui/collapsible.stories.tsx +39 -0
- package/src/lib/components/ui/command.stories.tsx +44 -0
- package/src/lib/components/ui/context-menu.stories.tsx +38 -0
- package/src/lib/components/ui/dialog.stories.tsx +52 -0
- package/src/lib/components/ui/drawer.stories.tsx +50 -0
- package/src/lib/components/ui/dropdown-menu.stories.tsx +41 -0
- package/src/lib/components/ui/hover-card.stories.tsx +38 -0
- package/src/lib/components/ui/input-group.tsx +148 -0
- package/src/lib/components/ui/input-otp.stories.tsx +30 -0
- package/src/lib/components/ui/input.stories.tsx +38 -0
- package/src/lib/components/ui/label.stories.tsx +24 -0
- package/src/lib/components/ui/menubar.stories.tsx +53 -0
- package/src/lib/components/ui/navigation-menu.stories.tsx +54 -0
- package/src/lib/components/ui/pagination.stories.tsx +51 -0
- package/src/lib/components/ui/password-input.stories.tsx +32 -0
- package/src/lib/components/ui/password-input.tsx +33 -0
- package/src/lib/components/ui/popover.stories.tsx +33 -0
- package/src/lib/components/ui/progress.stories.tsx +27 -0
- package/src/lib/components/ui/radio-group.stories.tsx +34 -0
- package/src/lib/components/ui/resizable.stories.tsx +32 -0
- package/src/lib/components/ui/scroll-area.stories.tsx +31 -0
- package/src/lib/components/ui/select.stories.tsx +36 -0
- package/src/lib/components/ui/separator.stories.tsx +35 -0
- package/src/lib/components/ui/sheet.stories.tsx +50 -0
- package/src/lib/components/ui/sidebar-context.ts +16 -0
- package/src/lib/components/ui/sidebar.tsx +2 -13
- package/src/lib/components/ui/skeleton.stories.tsx +26 -0
- package/src/lib/components/ui/slider.stories.tsx +37 -0
- package/src/lib/components/ui/switch.stories.tsx +31 -0
- package/src/lib/components/ui/table.stories.tsx +52 -0
- package/src/lib/components/ui/tabs.stories.tsx +29 -0
- package/src/lib/components/ui/textarea.stories.tsx +32 -0
- package/src/lib/components/ui/toggle-group.stories.tsx +31 -0
- package/src/lib/components/ui/toggle.stories.tsx +39 -0
- package/src/lib/components/ui/tooltip.stories.tsx +30 -0
- package/src/lib/components/ui/tooltip.tsx +2 -2
- package/src/lib/framework/alert/alert-extensions.tsx +0 -11
- package/src/lib/framework/alert/alert-item.tsx +14 -19
- package/src/lib/framework/alert/alerts-indicator.tsx +14 -15
- package/src/lib/framework/alert/search-index-buffer-alert/search-index-buffer-alert.ts +41 -0
- package/src/lib/framework/component-registry/component-registry.tsx +3 -14
- package/src/lib/framework/dashboard-widget/base-widget.tsx +18 -9
- package/src/lib/framework/dashboard-widget/widget-filters-context.tsx +12 -11
- package/src/lib/framework/defaults.ts +9 -13
- package/src/lib/framework/extension-api/input-component-extensions.tsx +6 -1
- package/src/lib/framework/extension-api/logic/alerts.ts +3 -2
- package/src/lib/framework/extension-api/types/alerts.ts +12 -6
- package/src/lib/framework/extension-api/types/data-table.ts +5 -2
- package/src/lib/framework/extension-api/types/login.ts +0 -21
- package/src/lib/framework/layout-engine/custom-form-page.stories.tsx +344 -0
- package/src/lib/framework/layout-engine/page-layout.tsx +11 -9
- package/src/lib/framework/layout-engine/page.stories.tsx +275 -0
- package/src/lib/framework/nav-menu/nav-menu-extensions.ts +32 -19
- package/src/lib/framework/page/detail-page.stories.tsx +151 -0
- package/src/lib/framework/page/list-page.stories.tsx +217 -0
- package/src/lib/framework/page/list-page.tsx +8 -1
- package/src/lib/graphql/api.ts +18 -1
- package/src/lib/graphql/graphql-env.d.ts +1 -1
- package/src/lib/hooks/use-alerts.ts +84 -0
- package/src/lib/hooks/use-floating-bulk-actions.ts +2 -3
- package/src/lib/index.ts +12 -5
- package/src/lib/providers/alerts-provider.tsx +60 -0
- package/src/lib/providers/theme-provider.tsx +6 -3
|
@@ -4,6 +4,12 @@ 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
|
+
export type NumberInputProps = DashboardFormComponentProps & {
|
|
8
|
+
min?: number;
|
|
9
|
+
max?: number;
|
|
10
|
+
step?: number;
|
|
11
|
+
};
|
|
12
|
+
|
|
7
13
|
/**
|
|
8
14
|
* @description
|
|
9
15
|
* A component for displaying a numeric value.
|
|
@@ -11,12 +17,12 @@ import { isReadonlyField } from '@/vdb/framework/form-engine/utils.js';
|
|
|
11
17
|
* @docsCategory form-components
|
|
12
18
|
* @docsPage NumberInput
|
|
13
19
|
*/
|
|
14
|
-
export function NumberInput({ fieldDef, onChange, ...fieldProps }: Readonly<
|
|
20
|
+
export function NumberInput({ fieldDef, onChange, ...fieldProps }: Readonly<NumberInputProps>) {
|
|
15
21
|
const readOnly = fieldProps.disabled || isReadonlyField(fieldDef);
|
|
16
22
|
const isFloat = fieldDef ? fieldDef.type === 'float' : false;
|
|
17
|
-
const min = fieldDef?.ui?.min;
|
|
18
|
-
const max = fieldDef?.ui?.max;
|
|
19
|
-
const step = fieldDef?.ui?.step || (isFloat ? 0.01 : 1);
|
|
23
|
+
const min = fieldProps.min ?? fieldDef?.ui?.min;
|
|
24
|
+
const max = fieldProps.max ?? fieldDef?.ui?.max;
|
|
25
|
+
const step = fieldProps.step ?? (fieldDef?.ui?.step || (isFloat ? 0.01 : 1));
|
|
20
26
|
const prefix = fieldDef?.ui?.prefix;
|
|
21
27
|
const suffix = fieldDef?.ui?.suffix;
|
|
22
28
|
const shouldUseAffixedInput = prefix || suffix;
|
|
@@ -0,0 +1,65 @@
|
|
|
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 { PasswordInput } from './password-input.js';
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
title: 'Form Inputs/PasswordInput',
|
|
8
|
+
component: PasswordInput,
|
|
9
|
+
...withDescription(import.meta.url, './password-input.js'),
|
|
10
|
+
parameters: {
|
|
11
|
+
layout: 'centered',
|
|
12
|
+
},
|
|
13
|
+
tags: ['autodocs'],
|
|
14
|
+
argTypes: {
|
|
15
|
+
value: {
|
|
16
|
+
control: 'text',
|
|
17
|
+
description: 'The current password value',
|
|
18
|
+
},
|
|
19
|
+
disabled: {
|
|
20
|
+
control: 'boolean',
|
|
21
|
+
description: 'Whether the input is disabled',
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
} satisfies Meta<typeof PasswordInput>;
|
|
25
|
+
|
|
26
|
+
export default meta;
|
|
27
|
+
type Story = StoryObj<typeof meta>;
|
|
28
|
+
|
|
29
|
+
export const Playground: Story = {
|
|
30
|
+
args: {
|
|
31
|
+
value: 'secret123',
|
|
32
|
+
disabled: false,
|
|
33
|
+
},
|
|
34
|
+
render: args => {
|
|
35
|
+
const { register } = useForm();
|
|
36
|
+
const field = register('password');
|
|
37
|
+
return (
|
|
38
|
+
<div className="w-[300px]">
|
|
39
|
+
<PasswordInput {...field} {...args} />
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const ChangePassword: Story = {
|
|
46
|
+
render: () => {
|
|
47
|
+
const { register } = useForm();
|
|
48
|
+
return (
|
|
49
|
+
<div className="w-[300px] space-y-4">
|
|
50
|
+
<div className="space-y-2">
|
|
51
|
+
<label className="text-sm font-medium">Current Password</label>
|
|
52
|
+
<PasswordInput {...register('currentPassword')} />
|
|
53
|
+
</div>
|
|
54
|
+
<div className="space-y-2">
|
|
55
|
+
<label className="text-sm font-medium">New Password</label>
|
|
56
|
+
<PasswordInput {...register('newPassword')} />
|
|
57
|
+
</div>
|
|
58
|
+
<div className="space-y-2">
|
|
59
|
+
<label className="text-sm font-medium">Confirm New Password</label>
|
|
60
|
+
<PasswordInput {...register('confirmPassword')} />
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
);
|
|
64
|
+
},
|
|
65
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
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 { RichTextInput } from './rich-text-input.js';
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
title: 'Form Inputs/RichTextInput',
|
|
8
|
+
component: RichTextInput,
|
|
9
|
+
...withDescription(import.meta.url, './rich-text-input.js'),
|
|
10
|
+
parameters: {
|
|
11
|
+
layout: 'centered',
|
|
12
|
+
},
|
|
13
|
+
tags: ['autodocs'],
|
|
14
|
+
argTypes: {
|
|
15
|
+
value: {
|
|
16
|
+
control: 'text',
|
|
17
|
+
description: 'The rich text HTML content',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
} satisfies Meta<typeof RichTextInput>;
|
|
21
|
+
|
|
22
|
+
export default meta;
|
|
23
|
+
type Story = StoryObj<typeof meta>;
|
|
24
|
+
|
|
25
|
+
export const Playground: Story = {
|
|
26
|
+
args: {
|
|
27
|
+
value: '<p>Edit this <strong>rich text</strong> content!</p>',
|
|
28
|
+
},
|
|
29
|
+
render: args => {
|
|
30
|
+
const { register } = useForm();
|
|
31
|
+
const field = register('content');
|
|
32
|
+
return (
|
|
33
|
+
<div className="w-[600px]">
|
|
34
|
+
<RichTextInput {...field} {...args} />
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const EmptyEditor: Story = {
|
|
41
|
+
render: () => {
|
|
42
|
+
const { register } = useForm();
|
|
43
|
+
const field = register('empty');
|
|
44
|
+
return (
|
|
45
|
+
<div className="w-[600px]">
|
|
46
|
+
<RichTextInput {...field} />
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const WithComplexContent: Story = {
|
|
53
|
+
render: () => {
|
|
54
|
+
const { register } = useForm();
|
|
55
|
+
const field = register('complex');
|
|
56
|
+
return (
|
|
57
|
+
<div className="w-[600px]">
|
|
58
|
+
<RichTextInput
|
|
59
|
+
{...field}
|
|
60
|
+
value={`
|
|
61
|
+
<h2>Product Description</h2>
|
|
62
|
+
<p>This is a <strong>high-quality</strong> product with the following features:</p>
|
|
63
|
+
<ul>
|
|
64
|
+
<li>Feature one with <em>emphasis</em></li>
|
|
65
|
+
<li>Feature two with a <a href="https://example.com">link</a></li>
|
|
66
|
+
<li>Feature three</li>
|
|
67
|
+
</ul>
|
|
68
|
+
<blockquote>
|
|
69
|
+
<p>Customer testimonial goes here</p>
|
|
70
|
+
</blockquote>
|
|
71
|
+
`}
|
|
72
|
+
/>
|
|
73
|
+
</div>
|
|
74
|
+
);
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export const ReadonlyMode: Story = {
|
|
79
|
+
render: () => {
|
|
80
|
+
const { register } = useForm();
|
|
81
|
+
const field = register('readonly');
|
|
82
|
+
return (
|
|
83
|
+
<div className="w-[600px]">
|
|
84
|
+
<RichTextInput
|
|
85
|
+
{...field}
|
|
86
|
+
value="<p>This content is <strong>readonly</strong> and cannot be edited.</p>"
|
|
87
|
+
fieldDef={{ readonly: true }}
|
|
88
|
+
/>
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
},
|
|
92
|
+
};
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import { FormProvider, useForm } from 'react-hook-form';
|
|
3
|
+
import { withDescription } from '../../../.storybook/with-description.js';
|
|
4
|
+
import { SlugInput } from './slug-input.js';
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
title: 'Form Inputs/SlugInput',
|
|
8
|
+
component: SlugInput,
|
|
9
|
+
...withDescription(import.meta.url, './slug-input.js'),
|
|
10
|
+
parameters: {
|
|
11
|
+
layout: 'centered',
|
|
12
|
+
},
|
|
13
|
+
tags: ['autodocs'],
|
|
14
|
+
decorators: [
|
|
15
|
+
Story => (
|
|
16
|
+
<div className="w-[500px]">
|
|
17
|
+
<Story />
|
|
18
|
+
</div>
|
|
19
|
+
),
|
|
20
|
+
],
|
|
21
|
+
argTypes: {
|
|
22
|
+
entityName: {
|
|
23
|
+
control: 'text',
|
|
24
|
+
description: 'The name of the entity (e.g., "Product", "Collection")',
|
|
25
|
+
},
|
|
26
|
+
fieldName: {
|
|
27
|
+
control: 'text',
|
|
28
|
+
description: 'The name of the field to check for uniqueness (e.g., "slug", "code")',
|
|
29
|
+
},
|
|
30
|
+
watchFieldName: {
|
|
31
|
+
control: 'text',
|
|
32
|
+
description: 'The name of the field to watch for changes',
|
|
33
|
+
},
|
|
34
|
+
defaultReadonly: {
|
|
35
|
+
control: 'boolean',
|
|
36
|
+
description: 'Whether the input should start in readonly mode',
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
} satisfies Meta<typeof SlugInput>;
|
|
40
|
+
|
|
41
|
+
export default meta;
|
|
42
|
+
type Story = StoryObj<typeof meta>;
|
|
43
|
+
|
|
44
|
+
export const AutoGenerating: Story = {
|
|
45
|
+
render: () => {
|
|
46
|
+
const form = useForm({
|
|
47
|
+
defaultValues: {
|
|
48
|
+
name: 'Product Name',
|
|
49
|
+
slug: '',
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<FormProvider {...form}>
|
|
55
|
+
<div className="space-y-4">
|
|
56
|
+
<div>
|
|
57
|
+
<label className="text-sm font-medium mb-2 block">Name</label>
|
|
58
|
+
<input
|
|
59
|
+
{...form.register('name')}
|
|
60
|
+
className="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors"
|
|
61
|
+
placeholder="Enter product name"
|
|
62
|
+
/>
|
|
63
|
+
</div>
|
|
64
|
+
<div>
|
|
65
|
+
<label className="text-sm font-medium mb-2 block">Slug (auto-generated)</label>
|
|
66
|
+
<SlugInput
|
|
67
|
+
{...form.register('slug')}
|
|
68
|
+
value={form.watch('slug')}
|
|
69
|
+
onChange={value => form.setValue('slug', value)}
|
|
70
|
+
entityName="Product"
|
|
71
|
+
fieldName="slug"
|
|
72
|
+
watchFieldName="name"
|
|
73
|
+
/>
|
|
74
|
+
</div>
|
|
75
|
+
<div className="text-sm text-muted-foreground">
|
|
76
|
+
<div>Name value: {form.watch('name')}</div>
|
|
77
|
+
<div>Slug value: {form.watch('slug')}</div>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
</FormProvider>
|
|
81
|
+
);
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export const WithExistingValue: Story = {
|
|
86
|
+
render: () => {
|
|
87
|
+
const form = useForm({
|
|
88
|
+
defaultValues: {
|
|
89
|
+
name: 'Existing Product',
|
|
90
|
+
slug: 'existing-product-slug',
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<FormProvider {...form}>
|
|
96
|
+
<div className="space-y-4">
|
|
97
|
+
<div>
|
|
98
|
+
<label className="text-sm font-medium mb-2 block">Name</label>
|
|
99
|
+
<input
|
|
100
|
+
{...form.register('name')}
|
|
101
|
+
className="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors"
|
|
102
|
+
placeholder="Enter product name"
|
|
103
|
+
/>
|
|
104
|
+
</div>
|
|
105
|
+
<div>
|
|
106
|
+
<label className="text-sm font-medium mb-2 block">Slug (with existing value)</label>
|
|
107
|
+
<SlugInput
|
|
108
|
+
{...form.register('slug')}
|
|
109
|
+
value={form.watch('slug')}
|
|
110
|
+
onChange={value => form.setValue('slug', value)}
|
|
111
|
+
entityName="Product"
|
|
112
|
+
fieldName="slug"
|
|
113
|
+
watchFieldName="name"
|
|
114
|
+
entityId="1"
|
|
115
|
+
/>
|
|
116
|
+
</div>
|
|
117
|
+
<div className="text-sm text-muted-foreground">
|
|
118
|
+
Click the Edit button to manually edit the slug, or click Regenerate to update from
|
|
119
|
+
the name field.
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
</FormProvider>
|
|
123
|
+
);
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export const ManualEditing: Story = {
|
|
128
|
+
render: () => {
|
|
129
|
+
const form = useForm({
|
|
130
|
+
defaultValues: {
|
|
131
|
+
name: 'Custom Product',
|
|
132
|
+
slug: '',
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
return (
|
|
137
|
+
<FormProvider {...form}>
|
|
138
|
+
<div className="space-y-4">
|
|
139
|
+
<div>
|
|
140
|
+
<label className="text-sm font-medium mb-2 block">Name</label>
|
|
141
|
+
<input
|
|
142
|
+
{...form.register('name')}
|
|
143
|
+
className="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors"
|
|
144
|
+
placeholder="Enter product name"
|
|
145
|
+
/>
|
|
146
|
+
</div>
|
|
147
|
+
<div>
|
|
148
|
+
<label className="text-sm font-medium mb-2 block">
|
|
149
|
+
Slug (click Edit to customize)
|
|
150
|
+
</label>
|
|
151
|
+
<SlugInput
|
|
152
|
+
{...form.register('slug')}
|
|
153
|
+
value={form.watch('slug')}
|
|
154
|
+
onChange={value => form.setValue('slug', value)}
|
|
155
|
+
entityName="Product"
|
|
156
|
+
fieldName="slug"
|
|
157
|
+
watchFieldName="name"
|
|
158
|
+
/>
|
|
159
|
+
</div>
|
|
160
|
+
<div className="text-sm text-muted-foreground">
|
|
161
|
+
Click the Edit button to switch to manual mode. Click the Lock button to switch back
|
|
162
|
+
to auto-generation.
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
</FormProvider>
|
|
166
|
+
);
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
export const StartInEditableMode: Story = {
|
|
171
|
+
render: () => {
|
|
172
|
+
const form = useForm({
|
|
173
|
+
defaultValues: {
|
|
174
|
+
code: '',
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
return (
|
|
179
|
+
<FormProvider {...form}>
|
|
180
|
+
<div className="space-y-4">
|
|
181
|
+
<div>
|
|
182
|
+
<label className="text-sm font-medium mb-2 block">Channel Code</label>
|
|
183
|
+
<SlugInput
|
|
184
|
+
{...form.register('code')}
|
|
185
|
+
value={form.watch('code')}
|
|
186
|
+
onChange={value => form.setValue('code', value)}
|
|
187
|
+
entityName="Channel"
|
|
188
|
+
fieldName="code"
|
|
189
|
+
watchFieldName="name"
|
|
190
|
+
defaultReadonly={false}
|
|
191
|
+
/>
|
|
192
|
+
</div>
|
|
193
|
+
<div className="text-sm text-muted-foreground">
|
|
194
|
+
This slug input starts in editable mode (defaultReadonly=false).
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
197
|
+
</FormProvider>
|
|
198
|
+
);
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
export const Readonly: Story = {
|
|
203
|
+
render: () => {
|
|
204
|
+
const form = useForm({
|
|
205
|
+
defaultValues: {
|
|
206
|
+
slug: 'readonly-slug',
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
return (
|
|
211
|
+
<FormProvider {...form}>
|
|
212
|
+
<div className="space-y-4">
|
|
213
|
+
<div>
|
|
214
|
+
<label className="text-sm font-medium mb-2 block">Readonly Slug</label>
|
|
215
|
+
<SlugInput
|
|
216
|
+
{...form.register('slug')}
|
|
217
|
+
value={form.watch('slug')}
|
|
218
|
+
onChange={value => form.setValue('slug', value)}
|
|
219
|
+
entityName="Product"
|
|
220
|
+
fieldName="slug"
|
|
221
|
+
watchFieldName="name"
|
|
222
|
+
fieldDef={{ readonly: true }}
|
|
223
|
+
/>
|
|
224
|
+
</div>
|
|
225
|
+
<div className="text-sm text-muted-foreground">
|
|
226
|
+
This slug input is readonly (fieldDef.readonly=true). No edit buttons are shown.
|
|
227
|
+
</div>
|
|
228
|
+
</div>
|
|
229
|
+
</FormProvider>
|
|
230
|
+
);
|
|
231
|
+
},
|
|
232
|
+
};
|
|
@@ -168,20 +168,19 @@ export function SlugInput({
|
|
|
168
168
|
|
|
169
169
|
const watchFieldState = form.getFieldState(actualWatchFieldName);
|
|
170
170
|
const debouncedWatchedValue = useDebounce(watchedValue, 500);
|
|
171
|
-
|
|
172
|
-
const
|
|
173
|
-
|
|
171
|
+
const shouldAutoGenerate = isReadonly && !entityId && watchFieldState.isDirty;
|
|
172
|
+
const queryKey = ['slugForEntity', entityName, fieldName, debouncedWatchedValue, entityId];
|
|
173
|
+
const enabled = !!debouncedWatchedValue && shouldAutoGenerate;
|
|
174
174
|
const {
|
|
175
175
|
data: generatedSlug,
|
|
176
176
|
isLoading,
|
|
177
177
|
refetch,
|
|
178
178
|
} = useQuery({
|
|
179
|
-
queryKey
|
|
179
|
+
queryKey,
|
|
180
180
|
queryFn: async () => {
|
|
181
181
|
if (!debouncedWatchedValue) {
|
|
182
182
|
return '';
|
|
183
183
|
}
|
|
184
|
-
|
|
185
184
|
const result = await api.query(slugForEntityDocument, {
|
|
186
185
|
input: {
|
|
187
186
|
entityName,
|
|
@@ -193,14 +192,14 @@ export function SlugInput({
|
|
|
193
192
|
|
|
194
193
|
return result.slugForEntity;
|
|
195
194
|
},
|
|
196
|
-
enabled
|
|
195
|
+
enabled,
|
|
197
196
|
});
|
|
198
197
|
|
|
199
198
|
useEffect(() => {
|
|
200
|
-
if (
|
|
199
|
+
if (shouldAutoGenerate && generatedSlug && generatedSlug !== value) {
|
|
201
200
|
onChange?.(generatedSlug);
|
|
202
201
|
}
|
|
203
|
-
}, [generatedSlug,
|
|
202
|
+
}, [generatedSlug, shouldAutoGenerate, value, onChange]);
|
|
204
203
|
|
|
205
204
|
const toggleReadonly = () => {
|
|
206
205
|
if (!isFormReadonly) {
|
|
@@ -221,8 +220,8 @@ export function SlugInput({
|
|
|
221
220
|
onChange?.(newValue);
|
|
222
221
|
};
|
|
223
222
|
|
|
224
|
-
const displayValue =
|
|
225
|
-
const showLoading = isLoading &&
|
|
223
|
+
const displayValue = shouldAutoGenerate && generatedSlug ? generatedSlug : value || '';
|
|
224
|
+
const showLoading = isLoading && shouldAutoGenerate;
|
|
226
225
|
|
|
227
226
|
return (
|
|
228
227
|
<div className="relative flex items-center gap-2">
|
|
@@ -0,0 +1,52 @@
|
|
|
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 { TextInput } from './text-input.js';
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
title: 'Form Inputs/TextInput',
|
|
8
|
+
component: TextInput,
|
|
9
|
+
...withDescription(import.meta.url, './text-input.js'),
|
|
10
|
+
parameters: {
|
|
11
|
+
layout: 'centered',
|
|
12
|
+
},
|
|
13
|
+
tags: ['autodocs'],
|
|
14
|
+
argTypes: {
|
|
15
|
+
value: {
|
|
16
|
+
control: 'text',
|
|
17
|
+
description: 'The current value',
|
|
18
|
+
},
|
|
19
|
+
placeholder: {
|
|
20
|
+
control: 'text',
|
|
21
|
+
description: 'Placeholder text',
|
|
22
|
+
},
|
|
23
|
+
disabled: {
|
|
24
|
+
control: 'boolean',
|
|
25
|
+
description: 'Whether the input is disabled',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
} satisfies Meta<typeof TextInput>;
|
|
29
|
+
|
|
30
|
+
export default meta;
|
|
31
|
+
type Story = StoryObj<typeof meta>;
|
|
32
|
+
|
|
33
|
+
export const Playground: Story = {
|
|
34
|
+
args: {
|
|
35
|
+
value: 'Edit me!',
|
|
36
|
+
placeholder: 'Enter text',
|
|
37
|
+
disabled: false,
|
|
38
|
+
},
|
|
39
|
+
render: args => {
|
|
40
|
+
const { register } = useForm();
|
|
41
|
+
const field = register('text');
|
|
42
|
+
return <TextInput {...field} {...args} />;
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const LongText: Story = {
|
|
47
|
+
render: () => {
|
|
48
|
+
const { register } = useForm();
|
|
49
|
+
const field = register('longText');
|
|
50
|
+
return <TextInput {...field} />;
|
|
51
|
+
},
|
|
52
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
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 { TextareaInput } from './textarea-input.js';
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
title: 'Form Inputs/TextareaInput',
|
|
8
|
+
component: TextareaInput,
|
|
9
|
+
...withDescription(import.meta.url, './textarea-input.js'),
|
|
10
|
+
parameters: {
|
|
11
|
+
layout: 'centered',
|
|
12
|
+
},
|
|
13
|
+
tags: ['autodocs'],
|
|
14
|
+
argTypes: {
|
|
15
|
+
value: {
|
|
16
|
+
control: 'text',
|
|
17
|
+
description: 'The current value',
|
|
18
|
+
},
|
|
19
|
+
disabled: {
|
|
20
|
+
control: 'boolean',
|
|
21
|
+
description: 'Whether the textarea is disabled',
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
} satisfies Meta<typeof TextareaInput>;
|
|
25
|
+
|
|
26
|
+
export default meta;
|
|
27
|
+
type Story = StoryObj<typeof meta>;
|
|
28
|
+
|
|
29
|
+
export const Playground: Story = {
|
|
30
|
+
args: {
|
|
31
|
+
value: 'Edit this text!\nMultiple lines supported.',
|
|
32
|
+
disabled: false,
|
|
33
|
+
},
|
|
34
|
+
render: args => {
|
|
35
|
+
const { register } = useForm();
|
|
36
|
+
const field = register('playground');
|
|
37
|
+
return (
|
|
38
|
+
<div className="w-[500px]">
|
|
39
|
+
<TextareaInput {...field} {...args} />
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const LongText: Story = {
|
|
46
|
+
render: () => {
|
|
47
|
+
const { register } = useForm();
|
|
48
|
+
const field = register('longText');
|
|
49
|
+
return (
|
|
50
|
+
<div className="w-[500px]">
|
|
51
|
+
<TextareaInput {...field} />
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
},
|
|
55
|
+
};
|
|
@@ -53,7 +53,12 @@ export function AddFilterMenu({ columns }: Readonly<AddFilterMenuProps>) {
|
|
|
53
53
|
))}
|
|
54
54
|
</DropdownMenuContent>
|
|
55
55
|
</DropdownMenu>
|
|
56
|
-
{selectedColumn &&
|
|
56
|
+
{selectedColumn && (
|
|
57
|
+
<DataTableFilterDialog
|
|
58
|
+
column={selectedColumn as any}
|
|
59
|
+
onEnter={() => setIsDialogOpen(false)}
|
|
60
|
+
/>
|
|
61
|
+
)}
|
|
57
62
|
</Dialog>
|
|
58
63
|
);
|
|
59
64
|
}
|