@k3-universe/react-kit 0.0.14 → 0.0.15
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/index.js +1169 -1145
- package/dist/kit/builder/form/components/FormBuilder.d.ts +1 -1
- package/dist/kit/builder/form/components/FormBuilder.d.ts.map +1 -1
- package/dist/kit/builder/form/components/FormBuilderContext.d.ts +18 -0
- package/dist/kit/builder/form/components/FormBuilderContext.d.ts.map +1 -0
- package/dist/kit/builder/form/components/sectionNodes.d.ts +17 -0
- package/dist/kit/builder/form/components/sectionNodes.d.ts.map +1 -0
- package/dist/kit/builder/form/types.d.ts +4 -3
- package/dist/kit/builder/form/types.d.ts.map +1 -1
- package/dist/kit/themes/clean-slate.css +3 -3
- package/dist/kit/themes/default.css +4 -4
- package/dist/kit/themes/minimal-modern.css +3 -3
- package/dist/kit/themes/spotify.css +3 -3
- package/package.json +1 -1
- package/src/kit/builder/form/components/FormBuilder.tsx +77 -139
- package/src/kit/builder/form/components/FormBuilderContext.tsx +45 -0
- package/src/kit/builder/form/components/sectionNodes.tsx +116 -0
- package/src/kit/builder/form/types.ts +4 -2
- package/src/kit/components/autocomplete/Autocomplete.tsx +1 -1
- package/src/kit/themes/default.css +1 -1
- package/src/shadcn/ui/button.tsx +1 -1
- package/src/shadcn/ui/command.tsx +1 -1
- package/src/shadcn/ui/input.tsx +1 -1
- package/src/shadcn/ui/popover.tsx +1 -1
- package/src/shadcn/ui/select.tsx +1 -1
- package/src/shadcn/ui/textarea.tsx +1 -1
- package/src/stories/kit/builder/Form.MultipleFormBuilder.stories.tsx +335 -0
|
@@ -5,12 +5,13 @@ import type {
|
|
|
5
5
|
FieldValues,
|
|
6
6
|
Path,
|
|
7
7
|
UseFormGetValues,
|
|
8
|
+
UseFormReturn,
|
|
8
9
|
UseFormSetValue,
|
|
9
10
|
DefaultValues,
|
|
10
11
|
} from 'react-hook-form'
|
|
11
12
|
import type { z } from 'zod'
|
|
12
13
|
import type { Accept } from 'react-dropzone'
|
|
13
|
-
import type { SectionFlexOptions, SectionGridOptions, SectionLayout
|
|
14
|
+
import type { SectionFlexOptions, SectionGridOptions, SectionLayout } from '../section/types'
|
|
14
15
|
import type { AutocompleteFetcher, AutocompleteOption } from '../../components/autocomplete/types'
|
|
15
16
|
import type { FileRecord, FileUploaderLayout } from '../../components/fileuploader/types'
|
|
16
17
|
|
|
@@ -192,7 +193,8 @@ export interface FormBuilderProps<TFieldValues extends FieldValues = FieldValues
|
|
|
192
193
|
showActions?: boolean
|
|
193
194
|
customActions?: React.ReactNode
|
|
194
195
|
showActionsSeparator?: boolean
|
|
196
|
+
form?: UseFormReturn<TFieldValues>
|
|
195
197
|
}
|
|
196
198
|
|
|
197
199
|
// Re-export for external consumers that build custom section nodes
|
|
198
|
-
export type { SectionNode }
|
|
200
|
+
export type { SectionNode } from '../section/types'
|
|
@@ -419,7 +419,7 @@ export function Autocomplete<T = unknown>({
|
|
|
419
419
|
tabIndex={disabled ? -1 : 0}
|
|
420
420
|
aria-disabled={disabled || undefined}
|
|
421
421
|
className={cn(
|
|
422
|
-
"w-full inline-flex items-center justify-between rounded-md border bg-background px-3 py-2 text-sm shadow-sm transition-colors",
|
|
422
|
+
"w-full inline-flex items-center justify-between rounded-md border border-border bg-background px-3 py-2 text-sm shadow-sm transition-colors",
|
|
423
423
|
"hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
424
424
|
disabled && "opacity-50 pointer-events-none",
|
|
425
425
|
className,
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
--destructive: oklch(0.6368 0.2078 25.3313);
|
|
20
20
|
--destructive-foreground: oklch(1.0000 0 0);
|
|
21
21
|
--border: oklch(0.9197 0.0040 286.3202);
|
|
22
|
-
--input: oklch(
|
|
22
|
+
--input: oklch(1.0000 0 0);
|
|
23
23
|
--ring: oklch(0.5234 0.1347 144.1672);
|
|
24
24
|
--chart-1: oklch(0.5234 0.1347 144.1672);
|
|
25
25
|
--chart-2: oklch(0.6731 0.1624 144.2083);
|
package/src/shadcn/ui/button.tsx
CHANGED
|
@@ -14,7 +14,7 @@ const buttonVariants = cva(
|
|
|
14
14
|
destructive:
|
|
15
15
|
"bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
|
16
16
|
outline:
|
|
17
|
-
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-
|
|
17
|
+
"border border-border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-border dark:hover:bg-input/50",
|
|
18
18
|
secondary:
|
|
19
19
|
"bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
|
|
20
20
|
ghost:
|
|
@@ -67,7 +67,7 @@ function CommandInput({
|
|
|
67
67
|
return (
|
|
68
68
|
<div
|
|
69
69
|
data-slot="command-input-wrapper"
|
|
70
|
-
className="flex h-9 items-center gap-2 border-b px-3"
|
|
70
|
+
className="flex h-9 items-center gap-2 border-b border-border px-3"
|
|
71
71
|
>
|
|
72
72
|
<SearchIcon className="size-4 shrink-0 opacity-50" />
|
|
73
73
|
<CommandPrimitive.Input
|
package/src/shadcn/ui/input.tsx
CHANGED
|
@@ -8,7 +8,7 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
|
|
8
8
|
type={type}
|
|
9
9
|
data-slot="input"
|
|
10
10
|
className={cn(
|
|
11
|
-
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-
|
|
11
|
+
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-border flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
|
12
12
|
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
|
13
13
|
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
|
14
14
|
className
|
|
@@ -30,7 +30,7 @@ function PopoverContent({
|
|
|
30
30
|
align={align}
|
|
31
31
|
sideOffset={sideOffset}
|
|
32
32
|
className={cn(
|
|
33
|
-
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
|
|
33
|
+
"border-border bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
|
|
34
34
|
className
|
|
35
35
|
)}
|
|
36
36
|
{...props}
|
package/src/shadcn/ui/select.tsx
CHANGED
|
@@ -37,7 +37,7 @@ function SelectTrigger({
|
|
|
37
37
|
data-slot="select-trigger"
|
|
38
38
|
data-size={size}
|
|
39
39
|
className={cn(
|
|
40
|
-
"border-
|
|
40
|
+
"border-border data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
41
41
|
className
|
|
42
42
|
)}
|
|
43
43
|
{...props}
|
|
@@ -7,7 +7,7 @@ function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
|
|
|
7
7
|
<textarea
|
|
8
8
|
data-slot="textarea"
|
|
9
9
|
className={cn(
|
|
10
|
-
"border-
|
|
10
|
+
"border-border placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
|
11
11
|
className
|
|
12
12
|
)}
|
|
13
13
|
{...props}
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import { useMemo } from 'react'
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react'
|
|
3
|
+
import { useForm } from 'react-hook-form'
|
|
4
|
+
import { FormBuilder } from '../../../index'
|
|
5
|
+
import type { FormBuilderSectionConfig } from '../../../kit/builder/form/types'
|
|
6
|
+
|
|
7
|
+
interface SplitFormValues {
|
|
8
|
+
firstName: string
|
|
9
|
+
lastName: string
|
|
10
|
+
email: string
|
|
11
|
+
accountType: 'individual' | 'business'
|
|
12
|
+
companyName?: string
|
|
13
|
+
address: {
|
|
14
|
+
street: string
|
|
15
|
+
city: string
|
|
16
|
+
zip: string
|
|
17
|
+
}
|
|
18
|
+
marketingOptIn: boolean
|
|
19
|
+
preferredContactMethod: 'email' | 'phone'
|
|
20
|
+
phoneNumber?: string
|
|
21
|
+
newsletterTopics: string[]
|
|
22
|
+
complianceContactEmail?: string
|
|
23
|
+
eventsWebhookUrl?: string
|
|
24
|
+
smsOptIn: boolean
|
|
25
|
+
smsFrequency?: 'daily' | 'weekly' | 'monthly'
|
|
26
|
+
timezone: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const generalInfoSections: FormBuilderSectionConfig<SplitFormValues>[] = [
|
|
30
|
+
{
|
|
31
|
+
title: 'Profile',
|
|
32
|
+
layout: 'grid',
|
|
33
|
+
grid: { cols: 2, gap: 'gap-4' },
|
|
34
|
+
fields: [
|
|
35
|
+
{
|
|
36
|
+
name: 'firstName',
|
|
37
|
+
label: 'First name',
|
|
38
|
+
type: 'text',
|
|
39
|
+
required: true,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'lastName',
|
|
43
|
+
label: 'Last name',
|
|
44
|
+
type: 'text',
|
|
45
|
+
required: true,
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: 'email',
|
|
49
|
+
label: 'Email',
|
|
50
|
+
type: 'email',
|
|
51
|
+
required: true,
|
|
52
|
+
gridCols: 2,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: 'accountType',
|
|
56
|
+
label: 'Account type',
|
|
57
|
+
type: 'radio',
|
|
58
|
+
options: [
|
|
59
|
+
{ label: 'Individual', value: 'individual' },
|
|
60
|
+
{ label: 'Business', value: 'business' },
|
|
61
|
+
],
|
|
62
|
+
defaultValue: 'individual',
|
|
63
|
+
gridCols: 2,
|
|
64
|
+
description: 'Switch to Business to reveal additional company details.',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: 'companyName',
|
|
68
|
+
label: 'Company name',
|
|
69
|
+
type: 'text',
|
|
70
|
+
placeholder: 'Acme Inc.',
|
|
71
|
+
dependencies: [
|
|
72
|
+
{
|
|
73
|
+
field: 'accountType',
|
|
74
|
+
condition: (value) => value === 'business',
|
|
75
|
+
action: 'show',
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
gridCols: 2,
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
title: 'Address',
|
|
84
|
+
layout: 'grid',
|
|
85
|
+
grid: { cols: 3, gap: 'gap-4' },
|
|
86
|
+
fields: [
|
|
87
|
+
{
|
|
88
|
+
name: 'address.street',
|
|
89
|
+
label: 'Street',
|
|
90
|
+
type: 'text',
|
|
91
|
+
required: true,
|
|
92
|
+
gridCols: 3,
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: 'address.city',
|
|
96
|
+
label: 'City',
|
|
97
|
+
type: 'text',
|
|
98
|
+
required: true,
|
|
99
|
+
gridCols: 2,
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: 'address.zip',
|
|
103
|
+
label: 'ZIP code',
|
|
104
|
+
type: 'text',
|
|
105
|
+
required: true,
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
},
|
|
109
|
+
]
|
|
110
|
+
|
|
111
|
+
const preferencesSections: FormBuilderSectionConfig<SplitFormValues>[] = [
|
|
112
|
+
{
|
|
113
|
+
title: 'Preferences',
|
|
114
|
+
layout: 'grid',
|
|
115
|
+
grid: { cols: 2, gap: 'gap-4' },
|
|
116
|
+
fields: [
|
|
117
|
+
{
|
|
118
|
+
name: 'marketingOptIn',
|
|
119
|
+
label: 'Marketing emails',
|
|
120
|
+
type: 'switch',
|
|
121
|
+
defaultValue: true,
|
|
122
|
+
description: 'Receive occasional product updates and tips.',
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
name: 'newsletterTopics',
|
|
126
|
+
label: 'Topics of interest',
|
|
127
|
+
type: 'select',
|
|
128
|
+
options: [
|
|
129
|
+
{ label: 'Product releases', value: 'product' },
|
|
130
|
+
{ label: 'Best practices', value: 'best-practices' },
|
|
131
|
+
{ label: 'Events', value: 'events' },
|
|
132
|
+
{ label: 'Integrations', value: 'integrations' },
|
|
133
|
+
],
|
|
134
|
+
multiple: true,
|
|
135
|
+
placeholder: 'Choose one or more topics',
|
|
136
|
+
dependencies: [
|
|
137
|
+
{
|
|
138
|
+
field: 'marketingOptIn',
|
|
139
|
+
condition: (value) => Boolean(value),
|
|
140
|
+
action: 'show',
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
field: 'marketingOptIn',
|
|
144
|
+
condition: (value) => !value,
|
|
145
|
+
action: 'setValue',
|
|
146
|
+
value: [],
|
|
147
|
+
},
|
|
148
|
+
],
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
name: 'eventsWebhookUrl',
|
|
152
|
+
label: 'Events webhook URL',
|
|
153
|
+
type: 'text',
|
|
154
|
+
placeholder: 'https://example.com/webhooks/events',
|
|
155
|
+
description: 'Provide a webhook to receive notifications about upcoming events.',
|
|
156
|
+
dependencies: [
|
|
157
|
+
{
|
|
158
|
+
field: 'newsletterTopics',
|
|
159
|
+
condition: (value) => Array.isArray(value) && value.includes('events'),
|
|
160
|
+
action: 'show',
|
|
161
|
+
},
|
|
162
|
+
],
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
name: 'preferredContactMethod',
|
|
166
|
+
label: 'Preferred contact method',
|
|
167
|
+
type: 'radio',
|
|
168
|
+
options: [
|
|
169
|
+
{ label: 'Email', value: 'email' },
|
|
170
|
+
{ label: 'Phone', value: 'phone' },
|
|
171
|
+
],
|
|
172
|
+
defaultValue: 'email',
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
name: 'phoneNumber',
|
|
176
|
+
label: 'Phone number',
|
|
177
|
+
type: 'text',
|
|
178
|
+
placeholder: '(555) 123-4567',
|
|
179
|
+
dependencies: [
|
|
180
|
+
{
|
|
181
|
+
field: 'preferredContactMethod',
|
|
182
|
+
condition: (value) => value === 'phone',
|
|
183
|
+
action: 'show',
|
|
184
|
+
},
|
|
185
|
+
],
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
name: 'smsOptIn',
|
|
189
|
+
label: 'SMS updates',
|
|
190
|
+
type: 'switch',
|
|
191
|
+
defaultValue: false,
|
|
192
|
+
description: 'Get real-time notifications via SMS.',
|
|
193
|
+
dependencies: [
|
|
194
|
+
{
|
|
195
|
+
field: 'preferredContactMethod',
|
|
196
|
+
condition: (value) => value === 'phone',
|
|
197
|
+
action: 'show',
|
|
198
|
+
},
|
|
199
|
+
],
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
name: 'smsFrequency',
|
|
203
|
+
label: 'SMS frequency',
|
|
204
|
+
type: 'select',
|
|
205
|
+
options: [
|
|
206
|
+
{ label: 'Daily recap', value: 'daily' },
|
|
207
|
+
{ label: 'Weekly summary', value: 'weekly' },
|
|
208
|
+
{ label: 'Monthly digest', value: 'monthly' },
|
|
209
|
+
],
|
|
210
|
+
placeholder: 'Choose how often we should text you',
|
|
211
|
+
dependencies: [
|
|
212
|
+
{
|
|
213
|
+
field: 'smsOptIn',
|
|
214
|
+
condition: (value) => Boolean(value),
|
|
215
|
+
action: 'show',
|
|
216
|
+
},
|
|
217
|
+
],
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
name: 'complianceContactEmail',
|
|
221
|
+
label: 'Compliance contact email',
|
|
222
|
+
type: 'email',
|
|
223
|
+
placeholder: 'compliance@company.com',
|
|
224
|
+
description: 'Required for business accounts to receive compliance updates.',
|
|
225
|
+
dependencies: [
|
|
226
|
+
{
|
|
227
|
+
field: 'accountType',
|
|
228
|
+
condition: (value) => value === 'business',
|
|
229
|
+
action: 'show',
|
|
230
|
+
},
|
|
231
|
+
],
|
|
232
|
+
gridCols: 2,
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
name: 'timezone',
|
|
236
|
+
label: 'Timezone',
|
|
237
|
+
type: 'select',
|
|
238
|
+
options: [
|
|
239
|
+
{ label: 'UTC−08:00 Pacific', value: 'America/Los_Angeles' },
|
|
240
|
+
{ label: 'UTC−05:00 Eastern', value: 'America/New_York' },
|
|
241
|
+
{ label: 'UTC+00:00 London', value: 'Europe/London' },
|
|
242
|
+
{ label: 'UTC+07:00 Jakarta', value: 'Asia/Jakarta' },
|
|
243
|
+
],
|
|
244
|
+
required: true,
|
|
245
|
+
},
|
|
246
|
+
],
|
|
247
|
+
},
|
|
248
|
+
]
|
|
249
|
+
|
|
250
|
+
const SplitFormExample = () => {
|
|
251
|
+
const form = useForm<SplitFormValues>({
|
|
252
|
+
defaultValues: {
|
|
253
|
+
firstName: 'Jane',
|
|
254
|
+
lastName: 'Doe',
|
|
255
|
+
email: 'jane.doe@example.com',
|
|
256
|
+
accountType: 'individual',
|
|
257
|
+
companyName: '',
|
|
258
|
+
address: {
|
|
259
|
+
street: '123 Storybook Way',
|
|
260
|
+
city: 'Componentville',
|
|
261
|
+
zip: '90210',
|
|
262
|
+
},
|
|
263
|
+
marketingOptIn: true,
|
|
264
|
+
newsletterTopics: ['product'],
|
|
265
|
+
eventsWebhookUrl: '',
|
|
266
|
+
preferredContactMethod: 'email',
|
|
267
|
+
phoneNumber: '',
|
|
268
|
+
smsOptIn: false,
|
|
269
|
+
smsFrequency: undefined,
|
|
270
|
+
complianceContactEmail: '',
|
|
271
|
+
timezone: 'Asia/Jakarta',
|
|
272
|
+
},
|
|
273
|
+
mode: 'onSubmit',
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
const logSubmit = useMemo(
|
|
277
|
+
() =>
|
|
278
|
+
form.handleSubmit((values) => {
|
|
279
|
+
console.log('Story submit', values)
|
|
280
|
+
}),
|
|
281
|
+
[form],
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
return (
|
|
285
|
+
<div className="space-y-6">
|
|
286
|
+
<FormBuilder
|
|
287
|
+
form={form}
|
|
288
|
+
sections={generalInfoSections}
|
|
289
|
+
onSubmit={async () => {
|
|
290
|
+
/* handled by explicit button */
|
|
291
|
+
}}
|
|
292
|
+
showActions={false}
|
|
293
|
+
/>
|
|
294
|
+
|
|
295
|
+
<FormBuilder
|
|
296
|
+
form={form}
|
|
297
|
+
sections={preferencesSections}
|
|
298
|
+
onSubmit={async () => {
|
|
299
|
+
/* handled by explicit button */
|
|
300
|
+
}}
|
|
301
|
+
customActions={(
|
|
302
|
+
<button
|
|
303
|
+
type="button"
|
|
304
|
+
className="px-4 py-2 text-sm font-medium text-white bg-primary rounded-md"
|
|
305
|
+
onClick={logSubmit}
|
|
306
|
+
>
|
|
307
|
+
Save all sections
|
|
308
|
+
</button>
|
|
309
|
+
)}
|
|
310
|
+
/>
|
|
311
|
+
</div>
|
|
312
|
+
)
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const meta: Meta<typeof SplitFormExample> = {
|
|
316
|
+
title: 'Kit/Builder/Form',
|
|
317
|
+
component: SplitFormExample,
|
|
318
|
+
parameters: {
|
|
319
|
+
docs: {
|
|
320
|
+
description: {
|
|
321
|
+
component:
|
|
322
|
+
'Demonstrates sharing a single `react-hook-form` instance across multiple `FormBuilder` blocks using the new `form` prop.',
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export default meta
|
|
329
|
+
|
|
330
|
+
type Story = StoryObj<typeof SplitFormExample>
|
|
331
|
+
|
|
332
|
+
export const SharedFormInstance: Story = {
|
|
333
|
+
name: 'Shared form instance across sections',
|
|
334
|
+
render: () => <SplitFormExample />,
|
|
335
|
+
}
|