@auto-engineer/generate-react-client 1.63.0 → 1.65.0
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/CHANGELOG.md +45 -0
- package/dist/starter/.storybook/main.ts +17 -22
- package/dist/starter/.storybook/manager-head.html +31 -31
- package/dist/starter/.storybook/manager.ts +133 -133
- package/dist/starter/.storybook/preview-head.html +12 -12
- package/dist/starter/.storybook/preview.tsx +79 -79
- package/dist/starter/biome.json +126 -0
- package/dist/starter/codegen.ts +11 -11
- package/dist/starter/components.json +27 -27
- package/dist/starter/package.json +86 -80
- package/dist/starter/public/mockServiceWorker.js +261 -261
- package/dist/starter/scripts/build-component-db.ts +17 -20
- package/dist/starter/src/App.tsx +15 -17
- package/dist/starter/src/components/ui/Accordion.stories.tsx +35 -35
- package/dist/starter/src/components/ui/Accordion.tsx +33 -33
- package/dist/starter/src/components/ui/Alert.stories.tsx +15 -15
- package/dist/starter/src/components/ui/Alert.tsx +32 -32
- package/dist/starter/src/components/ui/AlertDialog.stories.tsx +50 -50
- package/dist/starter/src/components/ui/AlertDialog.tsx +114 -115
- package/dist/starter/src/components/ui/AspectRatio.stories.tsx +20 -20
- package/dist/starter/src/components/ui/AspectRatio.tsx +1 -1
- package/dist/starter/src/components/ui/Avatar.stories.tsx +27 -27
- package/dist/starter/src/components/ui/Avatar.tsx +63 -63
- package/dist/starter/src/components/ui/Badge.stories.tsx +14 -14
- package/dist/starter/src/components/ui/Badge.tsx +27 -27
- package/dist/starter/src/components/ui/Breadcrumb.stories.tsx +38 -38
- package/dist/starter/src/components/ui/Breadcrumb.tsx +63 -62
- package/dist/starter/src/components/ui/Button.stories.tsx +55 -55
- package/dist/starter/src/components/ui/Button.tsx +49 -49
- package/dist/starter/src/components/ui/ButtonGroup.stories.tsx +17 -17
- package/dist/starter/src/components/ui/ButtonGroup.tsx +52 -53
- package/dist/starter/src/components/ui/Calendar.stories.tsx +20 -19
- package/dist/starter/src/components/ui/Calendar.tsx +142 -143
- package/dist/starter/src/components/ui/Card.stories.tsx +29 -29
- package/dist/starter/src/components/ui/Card.tsx +31 -31
- package/dist/starter/src/components/ui/Carousel.stories.tsx +41 -41
- package/dist/starter/src/components/ui/Carousel.tsx +171 -172
- package/dist/starter/src/components/ui/Chart.stories.tsx +21 -21
- package/dist/starter/src/components/ui/Chart.tsx +244 -247
- package/dist/starter/src/components/ui/Checkbox.stories.tsx +11 -11
- package/dist/starter/src/components/ui/Checkbox.tsx +18 -18
- package/dist/starter/src/components/ui/Collapsible.stories.tsx +40 -40
- package/dist/starter/src/components/ui/Collapsible.tsx +3 -3
- package/dist/starter/src/components/ui/Combobox.stories.tsx +48 -48
- package/dist/starter/src/components/ui/Combobox.tsx +204 -205
- package/dist/starter/src/components/ui/Command.stories.tsx +55 -55
- package/dist/starter/src/components/ui/Command.tsx +102 -103
- package/dist/starter/src/components/ui/ContextMenu.stories.tsx +52 -52
- package/dist/starter/src/components/ui/ContextMenu.tsx +151 -151
- package/dist/starter/src/components/ui/DesignSystem-Colors.stories.tsx +92 -92
- package/dist/starter/src/components/ui/DesignSystem-Layout.stories.tsx +139 -139
- package/dist/starter/src/components/ui/DesignSystem-Overview.stories.tsx +676 -657
- package/dist/starter/src/components/ui/DesignSystem-Typography.stories.tsx +59 -59
- package/dist/starter/src/components/ui/Dialog.stories.tsx +56 -56
- package/dist/starter/src/components/ui/Dialog.tsx +97 -98
- package/dist/starter/src/components/ui/Direction.stories.tsx +20 -20
- package/dist/starter/src/components/ui/Direction.tsx +7 -7
- package/dist/starter/src/components/ui/Drawer.stories.tsx +54 -54
- package/dist/starter/src/components/ui/Drawer.tsx +70 -70
- package/dist/starter/src/components/ui/DropdownMenu.stories.tsx +58 -58
- package/dist/starter/src/components/ui/DropdownMenu.tsx +157 -157
- package/dist/starter/src/components/ui/Empty.stories.tsx +22 -22
- package/dist/starter/src/components/ui/Empty.tsx +58 -58
- package/dist/starter/src/components/ui/Field.stories.tsx +31 -31
- package/dist/starter/src/components/ui/Field.tsx +180 -181
- package/dist/starter/src/components/ui/Form.stories.tsx +29 -29
- package/dist/starter/src/components/ui/Form.tsx +93 -96
- package/dist/starter/src/components/ui/HoverCard.stories.tsx +34 -34
- package/dist/starter/src/components/ui/HoverCard.tsx +21 -21
- package/dist/starter/src/components/ui/Input.stories.tsx +18 -18
- package/dist/starter/src/components/ui/Input.tsx +14 -14
- package/dist/starter/src/components/ui/InputGroup.stories.tsx +34 -34
- package/dist/starter/src/components/ui/InputGroup.tsx +110 -111
- package/dist/starter/src/components/ui/InputOTP.stories.tsx +28 -28
- package/dist/starter/src/components/ui/InputOTP.tsx +43 -43
- package/dist/starter/src/components/ui/Item.stories.tsx +45 -45
- package/dist/starter/src/components/ui/Item.tsx +113 -114
- package/dist/starter/src/components/ui/Kbd.stories.tsx +31 -31
- package/dist/starter/src/components/ui/Kbd.tsx +11 -11
- package/dist/starter/src/components/ui/Label.stories.tsx +62 -62
- package/dist/starter/src/components/ui/Label.tsx +26 -25
- package/dist/starter/src/components/ui/Menubar.stories.tsx +62 -62
- package/dist/starter/src/components/ui/Menubar.tsx +173 -173
- package/dist/starter/src/components/ui/NativeSelect.stories.tsx +26 -26
- package/dist/starter/src/components/ui/NativeSelect.tsx +29 -29
- package/dist/starter/src/components/ui/NavigationMenu.stories.tsx +64 -64
- package/dist/starter/src/components/ui/NavigationMenu.tsx +103 -103
- package/dist/starter/src/components/ui/Pagination.stories.tsx +61 -61
- package/dist/starter/src/components/ui/Pagination.tsx +69 -71
- package/dist/starter/src/components/ui/Popover.stories.tsx +38 -38
- package/dist/starter/src/components/ui/Popover.tsx +25 -25
- package/dist/starter/src/components/ui/Progress.stories.tsx +9 -9
- package/dist/starter/src/components/ui/Progress.tsx +14 -14
- package/dist/starter/src/components/ui/RadioGroup.stories.tsx +35 -35
- package/dist/starter/src/components/ui/RadioGroup.tsx +19 -19
- package/dist/starter/src/components/ui/Resizable.stories.tsx +54 -54
- package/dist/starter/src/components/ui/Resizable.tsx +29 -29
- package/dist/starter/src/components/ui/ScrollArea.stories.tsx +27 -27
- package/dist/starter/src/components/ui/ScrollArea.tsx +34 -34
- package/dist/starter/src/components/ui/Select.stories.tsx +43 -43
- package/dist/starter/src/components/ui/Select.tsx +120 -120
- package/dist/starter/src/components/ui/Separator.stories.tsx +27 -27
- package/dist/starter/src/components/ui/Separator.tsx +17 -17
- package/dist/starter/src/components/ui/Sheet.stories.tsx +53 -53
- package/dist/starter/src/components/ui/Sheet.tsx +69 -69
- package/dist/starter/src/components/ui/Sidebar.stories.tsx +77 -77
- package/dist/starter/src/components/ui/Sidebar.tsx +563 -564
- package/dist/starter/src/components/ui/Skeleton.stories.tsx +25 -25
- package/dist/starter/src/components/ui/Skeleton.tsx +1 -1
- package/dist/starter/src/components/ui/Slider.stories.tsx +5 -5
- package/dist/starter/src/components/ui/Slider.tsx +45 -44
- package/dist/starter/src/components/ui/Sonner.stories.tsx +32 -32
- package/dist/starter/src/components/ui/Sonner.tsx +23 -23
- package/dist/starter/src/components/ui/Spinner.stories.tsx +8 -8
- package/dist/starter/src/components/ui/Spinner.tsx +1 -1
- package/dist/starter/src/components/ui/Switch.stories.tsx +16 -17
- package/dist/starter/src/components/ui/Switch.tsx +24 -24
- package/dist/starter/src/components/ui/Table.stories.tsx +50 -50
- package/dist/starter/src/components/ui/Table.tsx +45 -45
- package/dist/starter/src/components/ui/Tabs.stories.tsx +39 -39
- package/dist/starter/src/components/ui/Tabs.tsx +47 -47
- package/dist/starter/src/components/ui/Textarea.stories.tsx +9 -9
- package/dist/starter/src/components/ui/Textarea.tsx +11 -11
- package/dist/starter/src/components/ui/Toast.stories.tsx +77 -77
- package/dist/starter/src/components/ui/Toast.tsx +75 -75
- package/dist/starter/src/components/ui/Toaster.tsx +17 -19
- package/dist/starter/src/components/ui/Toggle.stories.tsx +20 -20
- package/dist/starter/src/components/ui/Toggle.tsx +26 -26
- package/dist/starter/src/components/ui/ToggleGroup.stories.tsx +41 -41
- package/dist/starter/src/components/ui/ToggleGroup.tsx +61 -62
- package/dist/starter/src/components/ui/Tooltip.stories.tsx +26 -26
- package/dist/starter/src/components/ui/Tooltip.tsx +24 -24
- package/dist/starter/src/gql/execute.ts +1 -1
- package/dist/starter/src/gql/fragment-masking.ts +1 -1
- package/dist/starter/src/gql/graphql.ts +3 -0
- package/dist/starter/src/hooks/use-mobile.ts +11 -11
- package/dist/starter/src/hooks/use-toast.ts +135 -135
- package/dist/starter/src/index.css +105 -105
- package/dist/starter/src/lib/utils.ts +1 -1
- package/dist/starter/src/main.tsx +4 -1
- package/dist/starter/tsconfig.app.json +24 -24
- package/dist/starter/tsconfig.json +8 -8
- package/dist/starter/vite.config.ts +38 -37
- package/package.json +3 -3
|
@@ -1,50 +1,50 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
-
import { Field,
|
|
2
|
+
import { Field, FieldContent, FieldDescription, FieldError, FieldLabel } from '@/components/ui/Field';
|
|
3
3
|
import { Input } from '@/components/ui/Input';
|
|
4
4
|
|
|
5
5
|
const meta: Meta<typeof Field> = {
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
title: 'UI Components/Field',
|
|
7
|
+
component: Field,
|
|
8
8
|
};
|
|
9
9
|
export default meta;
|
|
10
10
|
type Story = StoryObj<typeof Field>;
|
|
11
11
|
|
|
12
12
|
/** Shows a vertical form field with label, input, and helper description text. */
|
|
13
13
|
export const Default: Story = {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
14
|
+
render: () => (
|
|
15
|
+
<Field>
|
|
16
|
+
<FieldLabel htmlFor="email">Email</FieldLabel>
|
|
17
|
+
<FieldContent>
|
|
18
|
+
<Input id="email" type="email" placeholder="you@example.com" />
|
|
19
|
+
<FieldDescription>We will never share your email.</FieldDescription>
|
|
20
|
+
</FieldContent>
|
|
21
|
+
</Field>
|
|
22
|
+
),
|
|
23
23
|
};
|
|
24
24
|
|
|
25
25
|
/** Shows a field in an invalid state with error styling and a validation error message. */
|
|
26
26
|
export const WithError: Story = {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
27
|
+
render: () => (
|
|
28
|
+
<Field data-invalid="true">
|
|
29
|
+
<FieldLabel htmlFor="email-err">Email</FieldLabel>
|
|
30
|
+
<FieldContent>
|
|
31
|
+
<Input id="email-err" type="email" defaultValue="invalid-email" aria-invalid="true" />
|
|
32
|
+
<FieldDescription>Enter a valid email address.</FieldDescription>
|
|
33
|
+
<FieldError>Please enter a valid email address.</FieldError>
|
|
34
|
+
</FieldContent>
|
|
35
|
+
</Field>
|
|
36
|
+
),
|
|
37
37
|
};
|
|
38
38
|
|
|
39
39
|
/** Shows a field with horizontal orientation where the label and input are side by side. */
|
|
40
40
|
export const Horizontal: Story = {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
41
|
+
render: () => (
|
|
42
|
+
<Field orientation="horizontal">
|
|
43
|
+
<FieldLabel htmlFor="name-h">Full Name</FieldLabel>
|
|
44
|
+
<FieldContent>
|
|
45
|
+
<Input id="name-h" placeholder="John Doe" />
|
|
46
|
+
<FieldDescription>Your first and last name.</FieldDescription>
|
|
47
|
+
</FieldContent>
|
|
48
|
+
</Field>
|
|
49
|
+
),
|
|
50
50
|
};
|
|
@@ -1,28 +1,27 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { useMemo } from 'react';
|
|
4
3
|
import { cva, type VariantProps } from 'class-variance-authority';
|
|
5
|
-
|
|
6
|
-
import { cn } from '@/lib/utils';
|
|
4
|
+
import { useMemo } from 'react';
|
|
7
5
|
import { Label } from '@/components/ui/Label';
|
|
8
6
|
import { Separator } from '@/components/ui/Separator';
|
|
7
|
+
import { cn } from '@/lib/utils';
|
|
9
8
|
|
|
10
9
|
/**
|
|
11
10
|
* A semantic fieldset container for grouping related form fields.
|
|
12
11
|
* Use as a top-level wrapper around FieldGroup or multiple Field components.
|
|
13
12
|
*/
|
|
14
13
|
function FieldSet({ className, ...props }: React.ComponentProps<'fieldset'>) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
14
|
+
return (
|
|
15
|
+
<fieldset
|
|
16
|
+
data-slot="field-set"
|
|
17
|
+
className={cn(
|
|
18
|
+
'flex flex-col gap-6',
|
|
19
|
+
'has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3',
|
|
20
|
+
className,
|
|
21
|
+
)}
|
|
22
|
+
{...props}
|
|
23
|
+
/>
|
|
24
|
+
);
|
|
26
25
|
}
|
|
27
26
|
|
|
28
27
|
/**
|
|
@@ -30,53 +29,53 @@ function FieldSet({ className, ...props }: React.ComponentProps<'fieldset'>) {
|
|
|
30
29
|
* Use variant="label" for a smaller label-sized heading.
|
|
31
30
|
*/
|
|
32
31
|
function FieldLegend({
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
className,
|
|
33
|
+
variant = 'legend',
|
|
34
|
+
...props
|
|
36
35
|
}: React.ComponentProps<'legend'> & { variant?: 'legend' | 'label' }) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
36
|
+
return (
|
|
37
|
+
<legend
|
|
38
|
+
data-slot="field-legend"
|
|
39
|
+
data-variant={variant}
|
|
40
|
+
className={cn('mb-3 font-medium', 'data-[variant=legend]:text-base', 'data-[variant=label]:text-sm', className)}
|
|
41
|
+
{...props}
|
|
42
|
+
/>
|
|
43
|
+
);
|
|
45
44
|
}
|
|
46
45
|
|
|
47
46
|
/** Container for a group of Field components. Provides container query context for responsive layouts. */
|
|
48
47
|
function FieldGroup({ className, ...props }: React.ComponentProps<'div'>) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
48
|
+
return (
|
|
49
|
+
<div
|
|
50
|
+
data-slot="field-group"
|
|
51
|
+
className={cn(
|
|
52
|
+
'group/field-group @container/field-group flex w-full flex-col gap-7 data-[slot=checkbox-group]:gap-3 [&>[data-slot=field-group]]:gap-4',
|
|
53
|
+
className,
|
|
54
|
+
)}
|
|
55
|
+
{...props}
|
|
56
|
+
/>
|
|
57
|
+
);
|
|
59
58
|
}
|
|
60
59
|
|
|
61
60
|
const fieldVariants = cva('group/field flex w-full gap-3 data-[invalid=true]:text-destructive', {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
61
|
+
variants: {
|
|
62
|
+
orientation: {
|
|
63
|
+
vertical: ['flex-col [&>*]:w-full [&>.sr-only]:w-auto'],
|
|
64
|
+
horizontal: [
|
|
65
|
+
'flex-row items-center',
|
|
66
|
+
'[&>[data-slot=field-label]]:flex-auto',
|
|
67
|
+
'has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px',
|
|
68
|
+
],
|
|
69
|
+
responsive: [
|
|
70
|
+
'flex-col [&>*]:w-full [&>.sr-only]:w-auto @md/field-group:flex-row @md/field-group:items-center @md/field-group:[&>*]:w-auto',
|
|
71
|
+
'@md/field-group:[&>[data-slot=field-label]]:flex-auto',
|
|
72
|
+
'@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px',
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
defaultVariants: {
|
|
77
|
+
orientation: 'vertical',
|
|
78
|
+
},
|
|
80
79
|
});
|
|
81
80
|
|
|
82
81
|
/**
|
|
@@ -85,105 +84,105 @@ const fieldVariants = cva('group/field flex w-full gap-3 data-[invalid=true]:tex
|
|
|
85
84
|
* Set data-invalid="true" to apply error styling.
|
|
86
85
|
*/
|
|
87
86
|
function Field({
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
87
|
+
className,
|
|
88
|
+
/** Layout direction: "vertical" stacks label above input, "horizontal" places them side by side, "responsive" switches at the md breakpoint. */
|
|
89
|
+
orientation = 'vertical',
|
|
90
|
+
...props
|
|
92
91
|
}: React.ComponentProps<'div'> & VariantProps<typeof fieldVariants>) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
92
|
+
return (
|
|
93
|
+
<div
|
|
94
|
+
role="group"
|
|
95
|
+
data-slot="field"
|
|
96
|
+
data-orientation={orientation}
|
|
97
|
+
className={cn(fieldVariants({ orientation }), className)}
|
|
98
|
+
{...props}
|
|
99
|
+
/>
|
|
100
|
+
);
|
|
102
101
|
}
|
|
103
102
|
|
|
104
103
|
/** Container for the input element plus optional description and error within a Field. */
|
|
105
104
|
function FieldContent({ className, ...props }: React.ComponentProps<'div'>) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
105
|
+
return (
|
|
106
|
+
<div
|
|
107
|
+
data-slot="field-content"
|
|
108
|
+
className={cn('group/field-content flex flex-1 flex-col gap-1.5 leading-snug', className)}
|
|
109
|
+
{...props}
|
|
110
|
+
/>
|
|
111
|
+
);
|
|
113
112
|
}
|
|
114
113
|
|
|
115
114
|
/** Label for a form field. Supports wrapping checkbox/radio inputs for card-style selection. */
|
|
116
115
|
function FieldLabel({ className, ...props }: React.ComponentProps<typeof Label>) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
116
|
+
return (
|
|
117
|
+
<Label
|
|
118
|
+
data-slot="field-label"
|
|
119
|
+
className={cn(
|
|
120
|
+
'group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50',
|
|
121
|
+
'has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border [&>*]:data-[slot=field]:p-4',
|
|
122
|
+
'has-data-[state=checked]:bg-primary/5 has-data-[state=checked]:border-primary dark:has-data-[state=checked]:bg-primary/10',
|
|
123
|
+
className,
|
|
124
|
+
)}
|
|
125
|
+
{...props}
|
|
126
|
+
/>
|
|
127
|
+
);
|
|
129
128
|
}
|
|
130
129
|
|
|
131
130
|
/** Non-interactive title text for a field, used when a label association is not needed. */
|
|
132
131
|
function FieldTitle({ className, ...props }: React.ComponentProps<'div'>) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
132
|
+
return (
|
|
133
|
+
<div
|
|
134
|
+
data-slot="field-label"
|
|
135
|
+
className={cn(
|
|
136
|
+
'flex w-fit items-center gap-2 text-sm leading-snug font-medium group-data-[disabled=true]/field:opacity-50',
|
|
137
|
+
className,
|
|
138
|
+
)}
|
|
139
|
+
{...props}
|
|
140
|
+
/>
|
|
141
|
+
);
|
|
143
142
|
}
|
|
144
143
|
|
|
145
144
|
/** Helper text displayed below the input to provide additional guidance. */
|
|
146
145
|
function FieldDescription({ className, ...props }: React.ComponentProps<'p'>) {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
146
|
+
return (
|
|
147
|
+
<p
|
|
148
|
+
data-slot="field-description"
|
|
149
|
+
className={cn(
|
|
150
|
+
'text-muted-foreground text-sm leading-normal font-normal group-has-[[data-orientation=horizontal]]/field:text-balance',
|
|
151
|
+
'last:mt-0 nth-last-2:-mt-1 [[data-variant=legend]+&]:-mt-1.5',
|
|
152
|
+
'[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4',
|
|
153
|
+
className,
|
|
154
|
+
)}
|
|
155
|
+
{...props}
|
|
156
|
+
/>
|
|
157
|
+
);
|
|
159
158
|
}
|
|
160
159
|
|
|
161
160
|
/** A visual separator between fields within a FieldGroup. Optionally displays centered text. */
|
|
162
161
|
function FieldSeparator({
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
162
|
+
children,
|
|
163
|
+
className,
|
|
164
|
+
...props
|
|
166
165
|
}: React.ComponentProps<'div'> & {
|
|
167
|
-
|
|
166
|
+
children?: React.ReactNode;
|
|
168
167
|
}) {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
168
|
+
return (
|
|
169
|
+
<div
|
|
170
|
+
data-slot="field-separator"
|
|
171
|
+
data-content={!!children}
|
|
172
|
+
className={cn('relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2', className)}
|
|
173
|
+
{...props}
|
|
174
|
+
>
|
|
175
|
+
<Separator className="absolute inset-0 top-1/2" />
|
|
176
|
+
{children && (
|
|
177
|
+
<span
|
|
178
|
+
className="bg-background text-muted-foreground relative mx-auto block w-fit px-2"
|
|
179
|
+
data-slot="field-separator-content"
|
|
180
|
+
>
|
|
181
|
+
{children}
|
|
182
|
+
</span>
|
|
183
|
+
)}
|
|
184
|
+
</div>
|
|
185
|
+
);
|
|
187
186
|
}
|
|
188
187
|
|
|
189
188
|
/**
|
|
@@ -191,61 +190,61 @@ function FieldSeparator({
|
|
|
191
190
|
* Accepts either children or an errors array; deduplicates and renders as a list if multiple.
|
|
192
191
|
*/
|
|
193
192
|
function FieldError({
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
193
|
+
className,
|
|
194
|
+
children,
|
|
195
|
+
/** Array of error objects with message strings. Automatically deduplicated. */
|
|
196
|
+
errors,
|
|
197
|
+
...props
|
|
199
198
|
}: React.ComponentProps<'div'> & {
|
|
200
|
-
|
|
199
|
+
errors?: Array<{ message?: string } | undefined>;
|
|
201
200
|
}) {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
201
|
+
const content = useMemo(() => {
|
|
202
|
+
if (children) {
|
|
203
|
+
return children;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (!errors?.length) {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const uniqueErrors = [...new Map(errors.map((error) => [error?.message, error])).values()];
|
|
211
|
+
|
|
212
|
+
if (uniqueErrors?.length === 1) {
|
|
213
|
+
return uniqueErrors[0]?.message;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return (
|
|
217
|
+
<ul className="ml-4 flex list-disc flex-col gap-1">
|
|
218
|
+
{uniqueErrors.map((error) => error?.message && <li key={error.message}>{error.message}</li>)}
|
|
219
|
+
</ul>
|
|
220
|
+
);
|
|
221
|
+
}, [children, errors]);
|
|
222
|
+
|
|
223
|
+
if (!content) {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return (
|
|
228
|
+
<div
|
|
229
|
+
role="alert"
|
|
230
|
+
data-slot="field-error"
|
|
231
|
+
className={cn('text-destructive text-sm font-normal', className)}
|
|
232
|
+
{...props}
|
|
233
|
+
>
|
|
234
|
+
{content}
|
|
235
|
+
</div>
|
|
236
|
+
);
|
|
238
237
|
}
|
|
239
238
|
|
|
240
239
|
export {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
240
|
+
Field,
|
|
241
|
+
FieldLabel,
|
|
242
|
+
FieldDescription,
|
|
243
|
+
FieldError,
|
|
244
|
+
FieldGroup,
|
|
245
|
+
FieldLegend,
|
|
246
|
+
FieldSeparator,
|
|
247
|
+
FieldSet,
|
|
248
|
+
FieldContent,
|
|
249
|
+
FieldTitle,
|
|
251
250
|
};
|
|
@@ -1,45 +1,45 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
-
import { useForm } from 'react-hook-form';
|
|
2
|
+
import { type FieldValues, useForm } from 'react-hook-form';
|
|
3
|
+
import { Button } from '@/components/ui/Button';
|
|
3
4
|
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/Form';
|
|
4
5
|
import { Input } from '@/components/ui/Input';
|
|
5
|
-
import { Button } from '@/components/ui/Button';
|
|
6
6
|
|
|
7
7
|
const meta: Meta<typeof Form> = {
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
title: 'UI Components/Form',
|
|
9
|
+
component: Form,
|
|
10
10
|
};
|
|
11
11
|
export default meta;
|
|
12
12
|
type Story = StoryObj<typeof Form>;
|
|
13
13
|
|
|
14
14
|
function FormDemo() {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
const form = useForm<FieldValues>({
|
|
16
|
+
defaultValues: { username: '' },
|
|
17
|
+
});
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
19
|
+
return (
|
|
20
|
+
<Form {...form}>
|
|
21
|
+
<form onSubmit={form.handleSubmit(() => {})} className="w-full max-w-sm space-y-6">
|
|
22
|
+
<FormField
|
|
23
|
+
control={form.control}
|
|
24
|
+
name="username"
|
|
25
|
+
render={({ field }) => (
|
|
26
|
+
<FormItem>
|
|
27
|
+
<FormLabel>Username</FormLabel>
|
|
28
|
+
<FormControl>
|
|
29
|
+
<Input placeholder="Enter username" {...field} />
|
|
30
|
+
</FormControl>
|
|
31
|
+
<FormDescription>This is your public display name.</FormDescription>
|
|
32
|
+
<FormMessage />
|
|
33
|
+
</FormItem>
|
|
34
|
+
)}
|
|
35
|
+
/>
|
|
36
|
+
<Button type="submit">Submit</Button>
|
|
37
|
+
</form>
|
|
38
|
+
</Form>
|
|
39
|
+
);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
/** Demonstrates a complete form field with label, input, description, and validation message wiring. */
|
|
43
43
|
export const Default: Story = {
|
|
44
|
-
|
|
44
|
+
render: () => <FormDemo />,
|
|
45
45
|
};
|