autoforma 1.0.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.
Files changed (105) hide show
  1. package/.github/workflows/npm_test.yml +27 -0
  2. package/.nvmrc +1 -0
  3. package/.prettierrc.mjs +35 -0
  4. package/.storybook/main.ts +17 -0
  5. package/.storybook/preview.tsx +18 -0
  6. package/.storybook/vitest.setup.ts +9 -0
  7. package/.stylelintignore +1 -0
  8. package/.stylelintrc.json +28 -0
  9. package/.yarn/releases/yarn-4.9.1.cjs +948 -0
  10. package/.yarnrc.yml +3 -0
  11. package/README.md +157 -0
  12. package/eslint.config.js +11 -0
  13. package/index.html +16 -0
  14. package/package.json +87 -0
  15. package/postcss.config.cjs +14 -0
  16. package/src/components/AutoForm.tsx +201 -0
  17. package/src/components/FieldRender.tsx +217 -0
  18. package/src/components/fields/ArrayField.tsx +59 -0
  19. package/src/components/fields/CheckField.tsx +23 -0
  20. package/src/components/fields/DateField.tsx +26 -0
  21. package/src/components/fields/DateTimeField.tsx +26 -0
  22. package/src/components/fields/NumberField.tsx +20 -0
  23. package/src/components/fields/ObjectField.tsx +41 -0
  24. package/src/components/fields/SelectField.tsx +23 -0
  25. package/src/components/fields/TextAreaField.tsx +25 -0
  26. package/src/components/fields/TextField.tsx +20 -0
  27. package/src/components/fields/TimeField.tsx +32 -0
  28. package/src/favicon.svg +1 -0
  29. package/src/index.ts +3 -0
  30. package/src/stories/AutoForm.stories.tsx +473 -0
  31. package/src/stories/Fields.stories.tsx +190 -0
  32. package/src/theme.ts +5 -0
  33. package/src/types/custom-render.ts +22 -0
  34. package/src/types/field.ts +32 -0
  35. package/src/types/form.ts +8 -0
  36. package/src/vite-env.d.ts +1 -0
  37. package/storybook-static/assets/AutoForm-CEr4m2za.js +53 -0
  38. package/storybook-static/assets/AutoForm.stories-CtOmRKOV.js +164 -0
  39. package/storybook-static/assets/Color-YHDXOIA2-pc5-Ao7n.js +1 -0
  40. package/storybook-static/assets/DocsRenderer-CFRXHY34-D-_1VvMG.js +575 -0
  41. package/storybook-static/assets/Fields.stories-CoRkpMbJ.js +29 -0
  42. package/storybook-static/assets/chunk-XP5HYGXS-BpfKkqn7.js +1 -0
  43. package/storybook-static/assets/entry-preview-CkHoqRUh.js +2 -0
  44. package/storybook-static/assets/entry-preview-docs-hFR7sEJd.js +46 -0
  45. package/storybook-static/assets/get-contrast-color-Db6nT3bs.js +1 -0
  46. package/storybook-static/assets/iframe-CzpwBD2i.js +211 -0
  47. package/storybook-static/assets/index-B8rYRX2K.js +1 -0
  48. package/storybook-static/assets/index-BQQLSK9g.js +1 -0
  49. package/storybook-static/assets/index-CXQShRbs.js +8 -0
  50. package/storybook-static/assets/index-D4lIrffr.js +9 -0
  51. package/storybook-static/assets/index-Domkg0jQ.js +240 -0
  52. package/storybook-static/assets/index-DrFu-skq.js +6 -0
  53. package/storybook-static/assets/index-DsJinFGm.js +9 -0
  54. package/storybook-static/assets/jsx-runtime-D_zvdyIk.js +9 -0
  55. package/storybook-static/assets/preview-B8lJiyuQ.js +34 -0
  56. package/storybook-static/assets/preview-BBWR9nbA.js +1 -0
  57. package/storybook-static/assets/preview-BOfrPh1e.js +2 -0
  58. package/storybook-static/assets/preview-BWzBA1C2.js +396 -0
  59. package/storybook-static/assets/preview-C2Qr78FS.js +4 -0
  60. package/storybook-static/assets/preview-CvbIS5ZJ.js +1 -0
  61. package/storybook-static/assets/preview-DD_OYowb.js +1 -0
  62. package/storybook-static/assets/preview-DGUiP6tS.js +7 -0
  63. package/storybook-static/assets/preview-DioFdWiE.css +1 -0
  64. package/storybook-static/assets/preview-Dyg8NwXV.js +1 -0
  65. package/storybook-static/assets/react-18-Dhjvimrl.js +25 -0
  66. package/storybook-static/assets/test-utils--_O2wM1y.js +9 -0
  67. package/storybook-static/favicon.svg +1 -0
  68. package/storybook-static/iframe.html +666 -0
  69. package/storybook-static/index.html +181 -0
  70. package/storybook-static/index.json +1 -0
  71. package/storybook-static/nunito-sans-bold-italic.woff2 +0 -0
  72. package/storybook-static/nunito-sans-bold.woff2 +0 -0
  73. package/storybook-static/nunito-sans-italic.woff2 +0 -0
  74. package/storybook-static/nunito-sans-regular.woff2 +0 -0
  75. package/storybook-static/project.json +1 -0
  76. package/storybook-static/sb-addons/chromatic-com-storybook-9/manager-bundle.js +331 -0
  77. package/storybook-static/sb-addons/chromatic-com-storybook-9/manager-bundle.js.LEGAL.txt +51 -0
  78. package/storybook-static/sb-addons/docs-11/manager-bundle.js +242 -0
  79. package/storybook-static/sb-addons/essentials-actions-2/manager-bundle.js +3 -0
  80. package/storybook-static/sb-addons/essentials-backgrounds-3/manager-bundle.js +12 -0
  81. package/storybook-static/sb-addons/essentials-controls-1/manager-bundle.js +402 -0
  82. package/storybook-static/sb-addons/essentials-measure-6/manager-bundle.js +3 -0
  83. package/storybook-static/sb-addons/essentials-outline-7/manager-bundle.js +3 -0
  84. package/storybook-static/sb-addons/essentials-toolbars-5/manager-bundle.js +3 -0
  85. package/storybook-static/sb-addons/essentials-viewport-4/manager-bundle.js +3 -0
  86. package/storybook-static/sb-addons/onboarding-8/manager-bundle.js +127 -0
  87. package/storybook-static/sb-addons/storybook-core-core-server-presets-0/common-manager-bundle.js +3 -0
  88. package/storybook-static/sb-addons/storybook-experimental-addon-test-10/manager-bundle.js +223 -0
  89. package/storybook-static/sb-common-assets/favicon.svg +1 -0
  90. package/storybook-static/sb-common-assets/nunito-sans-bold-italic.woff2 +0 -0
  91. package/storybook-static/sb-common-assets/nunito-sans-bold.woff2 +0 -0
  92. package/storybook-static/sb-common-assets/nunito-sans-italic.woff2 +0 -0
  93. package/storybook-static/sb-common-assets/nunito-sans-regular.woff2 +0 -0
  94. package/storybook-static/sb-manager/globals-module-info.js +1051 -0
  95. package/storybook-static/sb-manager/globals-runtime.js +41591 -0
  96. package/storybook-static/sb-manager/globals.js +48 -0
  97. package/storybook-static/sb-manager/runtime.js +12048 -0
  98. package/test-utils/index.ts +5 -0
  99. package/test-utils/render.tsx +11 -0
  100. package/text +64 -0
  101. package/tsconfig.json +25 -0
  102. package/vite.config.mjs +19 -0
  103. package/vitest.setup.mjs +29 -0
  104. package/vitest.shims.d.ts +1 -0
  105. package/vitest.workspace.js +30 -0
@@ -0,0 +1,473 @@
1
+ import AutoForm from '@/components/AutoForm';
2
+ import FieldRender, { getArrayOptions } from '@/components/FieldRender';
3
+ import { CustomFieldRender } from '@/types/custom-render';
4
+ import { FieldSchema } from '@/types/field';
5
+ import { Accordion, Button, Grid, Group, Modal, Table, Text } from '@mantine/core';
6
+ import { useDisclosure } from '@mantine/hooks';
7
+ import type { Meta, StoryObj } from '@storybook/react';
8
+
9
+ // ------------------------------
10
+ // Schema Definitions
11
+ // ------------------------------
12
+
13
+ const schema: FieldSchema[] = [
14
+ {
15
+ type: 'check',
16
+ label: 'Enabled',
17
+ name: 'enabled',
18
+ initialValue: true,
19
+ },
20
+ {
21
+ type: 'object',
22
+ name: 'personalInformation',
23
+ label: 'Personal Information',
24
+ fields: [
25
+ {
26
+ type: 'text',
27
+ name: 'firstName',
28
+ label: 'First Name',
29
+ initialValue: 'Saji',
30
+ },
31
+ {
32
+ type: 'text',
33
+ name: 'lastName',
34
+ label: 'Last Name',
35
+ initialValue: 'Nael',
36
+ },
37
+ {
38
+ type: 'select',
39
+ name: 'gender',
40
+ label: 'Gender',
41
+ data: [
42
+ { label: 'Male', value: 'male' },
43
+ { label: 'Female', value: 'female' },
44
+ ],
45
+ initialValue: 'male',
46
+ },
47
+ ],
48
+ },
49
+ {
50
+ type: 'object',
51
+ name: 'accountInformation',
52
+ label: 'Account Information',
53
+ fields: [
54
+ {
55
+ type: 'text',
56
+ name: 'username',
57
+ label: 'Username',
58
+ initialValue: 'saji-nael',
59
+ },
60
+ {
61
+ type: 'text',
62
+ name: 'email',
63
+ label: 'Email',
64
+ initialValue: 'saji.nael.98@gmail.com',
65
+ },
66
+ {
67
+ type: 'date',
68
+ name: 'dateOfJoin',
69
+ label: 'Date Of Join',
70
+ initialValue: new Date(),
71
+ },
72
+ ],
73
+ },
74
+ {
75
+ type: 'object',
76
+ name: 'contactInformation',
77
+ label: 'Contact Information',
78
+ fields: [
79
+ {
80
+ type: 'array',
81
+ name: 'phones',
82
+ label: 'Phones',
83
+ fields: [
84
+ {
85
+ type: 'text',
86
+ name: 'phoneNo',
87
+ label: 'Phone NO',
88
+ },
89
+ ],
90
+ },
91
+ ],
92
+ },
93
+ ];
94
+
95
+ const schemaWithValidation: FieldSchema[] = [
96
+ ...schema.slice(0, 3),
97
+ {
98
+ ...schema[3],
99
+ fields: [
100
+ {
101
+ ...schema[3].fields[0],
102
+ required: true,
103
+ },
104
+ ],
105
+ },
106
+ ];
107
+
108
+ // ------------------------------
109
+ // Storybook Meta
110
+ // ------------------------------
111
+
112
+ const meta: Meta<typeof AutoForm> = {
113
+ title: 'components/AutoForm',
114
+ component: AutoForm,
115
+ tags: ['autodocs'],
116
+ args: {
117
+ onSubmit: (values) => alert(JSON.stringify(values, null, 2)),
118
+ schema,
119
+ container: (form, onSubmit, readOnly) => (
120
+ <>
121
+ <Grid>{form}</Grid>
122
+ {!readOnly && (
123
+ <Group justify="flex-end">
124
+ <Button onClick={onSubmit}>Submit</Button>
125
+ </Group>
126
+ )}
127
+ </>
128
+ ),
129
+ fieldContainer: (field, fieldSchema) => (
130
+ <Grid.Col span={{ base: 12, md: fieldSchema.type === 'object' ? 12 : 6 }}>{field}</Grid.Col>
131
+ ),
132
+ },
133
+
134
+ argTypes: {
135
+ values: {
136
+ description: 'Initial form values. Useful for editing or setting default values.',
137
+ control: false,
138
+ },
139
+ schema: {
140
+ description:
141
+ 'Array of field definitions used to render the form. Each item defines one input field.',
142
+ control: false,
143
+ },
144
+ onSubmit: {
145
+ description: 'Function called with form values when the form is submitted.',
146
+ control: false,
147
+ },
148
+ container: {
149
+ description:
150
+ 'Function that receives the rendered form and the onSubmit handler. Used to wrap the form with layout and controls.',
151
+ control: false,
152
+ },
153
+ fieldContainer: {
154
+ description: 'Optional function to wrap each individual field with custom layout or styling.',
155
+ control: false,
156
+ },
157
+ customRender: {
158
+ description:
159
+ 'Optional custom render function to override the default field rendering based on field type.',
160
+ control: false,
161
+ },
162
+ validate: {
163
+ description:
164
+ 'Optional validation logic. Receives values and returns an object with field errors or nulls.',
165
+ control: false,
166
+ },
167
+ readOnly: {
168
+ description: 'If true, all fields will be rendered in read-only mode.',
169
+ control: { type: 'boolean' },
170
+ },
171
+ onFieldChange: {
172
+ description:
173
+ 'Optional function triggered on field change. You can return modified values dynamically.',
174
+ control: false,
175
+ },
176
+ },
177
+ parameters: {
178
+ docs: {
179
+ description: {
180
+ component: `
181
+ ### 📄 AutoForm
182
+
183
+ \`AutoForm\` is a dynamic, schema-driven form component designed for full control and customization.
184
+
185
+ ---
186
+
187
+ ### 🧩 AutoFormProps Overview
188
+
189
+ | Prop | Type | Description |
190
+ |------------------|----------------------------------------------------------------------|-------------|
191
+ | \`schema\` | \`FieldSchema[]\` | Array of field definitions |
192
+ | \`onSubmit\` | \`(values: Record<string, any>) => void\` | Called when form is submitted |
193
+ | \`values\` | \`Record<string, any>\` | Initial values |
194
+ | \`readOnly\` | \`true?\` | Makes the form non-editable |
195
+ | \`validate\` | \`FormValidateInput<Record<string, any>>\` | Optional validation function |
196
+ | \`onFieldChange\` | \`(name, value, values) => Record<string, any>\` | Callback when a field changes |
197
+ | \`container\` | \`(formNode, onSubmit, readOnly) => React.ReactNode\` | Customize the outer layout of the form |
198
+ | \`fieldContainer\` | \`(fieldNode, fieldSchema) => React.ReactNode\` | Customize how each field is rendered |
199
+ | \`customRender\` | \`(field, value, error, onChange, values, options, readOnly) => JSX\` | Advanced per-field rendering |
200
+
201
+ ---
202
+
203
+ ### 🧱 Layout Customization
204
+
205
+ #### 🔲 \`container\`
206
+
207
+ Use the \`container\` prop to wrap the full form, including layout, submit buttons, etc.
208
+
209
+ #### 🔳 \`fieldContainer\`
210
+
211
+ With \`fieldContainer\`, you can wrap **each field** with your own layout:
212
+
213
+ \`\`\`tsx
214
+ fieldContainer: (field, fieldSchema) => (
215
+ <Grid.Col span={{ base: 12, md: fieldSchema.type === 'object' ? 12 : 6 }}>
216
+ {field}
217
+ </Grid.Col>
218
+ )
219
+ \`\`\`
220
+
221
+ This allows **full control per field** — spacing, layout, responsiveness, etc.
222
+
223
+ ---
224
+
225
+ ### 🧠 Advanced Rendering with \`customRender\`
226
+
227
+ \`customRender\` gives you full control over how a field is rendered.
228
+
229
+ #### 📦 Example
230
+
231
+ \`\`\`tsx
232
+ customRender: (field, objValue, error, onObjChange, formValues, options, readOnly) => {
233
+ if (field.type === 'object') {
234
+ return (
235
+ <Accordion defaultValue={field.name}>
236
+ <Accordion.Item value={field.name}>
237
+ <Accordion.Control>{field.label}</Accordion.Control>
238
+ <Accordion.Panel>
239
+ <Grid>
240
+ {field.fields?.map((childField) => {
241
+ function onChange(name: string, value: any) {
242
+ objValue[name] = value;
243
+ onObjChange(field.name, objValue);
244
+ }
245
+
246
+ if (childField.name === 'phones') {
247
+ return (
248
+ <CustomPhoneList
249
+ key={childField.name}
250
+ field={childField}
251
+ value={objValue[childField.name]}
252
+ onChange={onChange}
253
+ error={error}
254
+ formValues={formValues}
255
+ options={options}
256
+ readOnly={readOnly}
257
+ />
258
+ );
259
+ }
260
+
261
+ return (
262
+ <Grid.Col key={childField.name} span={6}>
263
+ <FieldRender
264
+ field={childField}
265
+ formValues={formValues}
266
+ value={objValue[childField.name]}
267
+ onChange={onChange}
268
+ readOnly={readOnly}
269
+ />
270
+ </Grid.Col>
271
+ );
272
+ })}
273
+ </Grid>
274
+ </Accordion.Panel>
275
+ </Accordion.Item>
276
+ </Accordion>
277
+ );
278
+ }
279
+ return null;
280
+ }
281
+ \`\`\`
282
+
283
+ You can use this to:
284
+ - Nest fields inside custom layouts
285
+ - Open modals, drawers, or complex editors
286
+ - Show/hide fields dynamically
287
+ - Render fields as fully custom components
288
+
289
+ ---
290
+ `,
291
+ },
292
+ },
293
+ },
294
+ };
295
+
296
+ export default meta;
297
+
298
+ // ------------------------------
299
+ // Stories
300
+ // ------------------------------
301
+
302
+ type Story = StoryObj<typeof AutoForm>;
303
+
304
+ export const Standard: Story = {};
305
+
306
+ export const ReadOnly: Story = {
307
+ args: { readOnly: true },
308
+ };
309
+
310
+ export const FormValidation: Story = {
311
+ args: {
312
+ schema: schemaWithValidation,
313
+ validate: {
314
+ contactInformation: {
315
+ phones: {
316
+ phoneNo: (value: string) => {
317
+ const regex = /^(059|056)\d{7}$/;
318
+ return regex.test(value)
319
+ ? null
320
+ : "Invalid phone number. It must be exactly 10 digits long and start with '059' or '056'.";
321
+ },
322
+ },
323
+ },
324
+ },
325
+ },
326
+ };
327
+
328
+ export const CustomizedForm: Story = {
329
+ args: {
330
+ schema,
331
+ customRender: (field, objValue, error, onObjChange, formValues, options, readOnly) => {
332
+ if (field.type === 'object') {
333
+ return (
334
+ <Grid.Col span={12}>
335
+ <Accordion defaultValue={field.name} variant="contained">
336
+ <Accordion.Item value={field.name}>
337
+ <Accordion.Control>{field.label}</Accordion.Control>
338
+ <Accordion.Panel>
339
+ <Grid>
340
+ {field.fields?.map((field) => {
341
+ const onChange = (name: string, value: any) => {
342
+ objValue[name] = value;
343
+ onObjChange(field.name, objValue);
344
+ };
345
+
346
+ if (field.name === 'phones') {
347
+ return (
348
+ <CustomPhoneList
349
+ key={field.name}
350
+ error={error}
351
+ field={field}
352
+ formValues={formValues}
353
+ onChange={onChange}
354
+ value={objValue[field.name]}
355
+ options={options}
356
+ readOnly={readOnly}
357
+ />
358
+ );
359
+ }
360
+
361
+ return (
362
+ <Grid.Col
363
+ key={field.name}
364
+ span={{ base: 12, md: field.type === 'object' ? 12 : 6 }}
365
+ >
366
+ <FieldRender
367
+ field={field}
368
+ formValues={formValues}
369
+ value={objValue[field.name]}
370
+ error={undefined}
371
+ readOnly={readOnly}
372
+ onChange={onChange}
373
+ />
374
+ </Grid.Col>
375
+ );
376
+ })}
377
+ </Grid>
378
+ </Accordion.Panel>
379
+ </Accordion.Item>
380
+ </Accordion>
381
+ </Grid.Col>
382
+ );
383
+ }
384
+ return null;
385
+ },
386
+ },
387
+ };
388
+
389
+ // ------------------------------
390
+ // Custom Field Renderer
391
+ // ------------------------------
392
+
393
+ const CustomPhoneList: React.FC<CustomFieldRender> = (props) => {
394
+ const [visible, { open, close }] = useDisclosure(false);
395
+ const { error, field, formValues, onChange, value, options, readOnly } = props;
396
+
397
+ const arrayOptions = getArrayOptions(field.name, onChange, value);
398
+
399
+ return (
400
+ <>
401
+ <FieldRender
402
+ field={field}
403
+ formValues={formValues}
404
+ onChange={onChange}
405
+ value={value}
406
+ error={error}
407
+ readOnly={readOnly}
408
+ customRender={() => (
409
+ <>
410
+ <Modal title="Add Phone" onClose={close} opened={visible}>
411
+ <AutoForm
412
+ schema={field.fields as FieldSchema[]}
413
+ fieldContainer={(field, fieldSchema) => (
414
+ <Grid.Col span={{ base: 12, md: fieldSchema.type === 'object' ? 12 : 6 }}>
415
+ {field}
416
+ </Grid.Col>
417
+ )}
418
+ container={(form, onSubmit, readOnly) => (
419
+ <>
420
+ <Grid>{form}</Grid>
421
+ {!readOnly && (
422
+ <Group justify="flex-end">
423
+ <Button onClick={onSubmit}>Submit</Button>
424
+ </Group>
425
+ )}
426
+ </>
427
+ )}
428
+ onSubmit={(values) => {
429
+ arrayOptions?.addElement(values);
430
+ close();
431
+ }}
432
+ />
433
+ </Modal>
434
+
435
+ <Button onClick={open} mb="md">
436
+ Add Phone
437
+ </Button>
438
+
439
+ {value.length > 0 ? (
440
+ <Table withColumnBorders withRowBorders withTableBorder striped>
441
+ <Table.Thead>
442
+ <Table.Tr>
443
+ <Table.Th>Phone NO.</Table.Th>
444
+ <Table.Th>Action</Table.Th>
445
+ </Table.Tr>
446
+ </Table.Thead>
447
+ <Table.Tbody>
448
+ {value.map((phone, index) => (
449
+ <Table.Tr key={index}>
450
+ <Table.Td>{phone.phoneNo}</Table.Td>
451
+ <Table.Td>
452
+ <Button
453
+ variant="light"
454
+ size="xs"
455
+ color="red"
456
+ onClick={() => arrayOptions.removeElement(index)}
457
+ >
458
+ Delete
459
+ </Button>
460
+ </Table.Td>
461
+ </Table.Tr>
462
+ ))}
463
+ </Table.Tbody>
464
+ </Table>
465
+ ) : (
466
+ <Text>No phones are inserted</Text>
467
+ )}
468
+ </>
469
+ )}
470
+ />
471
+ </>
472
+ );
473
+ };
@@ -0,0 +1,190 @@
1
+ import { Meta, StoryObj } from '@storybook/react';
2
+ import { Button, Grid, Group } from '@mantine/core';
3
+ import AutoForm from '@/components/AutoForm';
4
+ import { FieldSchema } from '@/types/field';
5
+
6
+
7
+ // ------------------------------
8
+ // Schema Definitions
9
+ // ------------------------------
10
+ const textFieldSchema: FieldSchema[] = [
11
+ {
12
+ type: 'text',
13
+ name: 'firstName',
14
+ label: 'First Name',
15
+ initialValue: 'John',
16
+ },
17
+ ];
18
+
19
+ const numberFieldSchema: FieldSchema[] = [
20
+ {
21
+ type: 'number',
22
+ name: 'age',
23
+ label: 'Age',
24
+ initialValue: 30,
25
+ },
26
+ ];
27
+
28
+ const selectFieldSchema: FieldSchema[] = [
29
+ {
30
+ type: 'select',
31
+ name: 'gender',
32
+ label: 'Gender',
33
+ data: [
34
+ { label: 'Male', value: 'male' },
35
+ { label: 'Female', value: 'female' },
36
+ ],
37
+ initialValue: 'male',
38
+ },
39
+ ];
40
+
41
+ const checkFieldSchema: FieldSchema[] = [
42
+ {
43
+ type: 'check',
44
+ name: 'enabled',
45
+ label: 'Enabled',
46
+ initialValue: true,
47
+ },
48
+ ];
49
+
50
+ const dateFieldSchema: FieldSchema[] = [
51
+ {
52
+ type: 'date',
53
+ name: 'birthDate',
54
+ label: 'Birth Date',
55
+ initialValue: new Date(),
56
+ },
57
+ ];
58
+
59
+ const arrayFieldSchema: FieldSchema[] = [
60
+ {
61
+ type: 'array',
62
+ name: 'tags',
63
+ label: 'Tags',
64
+ fields: [
65
+ {
66
+ type: 'text',
67
+ name: 'tag',
68
+ label: 'Tag',
69
+ },
70
+ ],
71
+ },
72
+ ];
73
+
74
+ const objectFieldSchema: FieldSchema[] = [
75
+ {
76
+ type: 'object',
77
+ name: 'address',
78
+ label: 'Address',
79
+ fields: [
80
+ {
81
+ type: 'text',
82
+ name: 'street',
83
+ label: 'Street',
84
+ },
85
+ {
86
+ type: 'text',
87
+ name: 'city',
88
+ label: 'City',
89
+ },
90
+ ],
91
+ },
92
+ ];
93
+
94
+ // ------------------------------
95
+ // Storybook Meta
96
+ // ------------------------------
97
+
98
+ const meta: Meta<typeof AutoForm> = {
99
+ title: 'components/Fields',
100
+ component: AutoForm,
101
+ tags: ['autodocs'],
102
+ args: {
103
+ onSubmit: (values) => alert(JSON.stringify(values, null, 2)),
104
+ container: (form, onSubmit, readOnly) => (
105
+ <>
106
+ <Grid>{form}</Grid>
107
+ {!readOnly && (
108
+ <Group justify="flex-end">
109
+ <Button onClick={onSubmit}>Submit</Button>
110
+ </Group>
111
+ )}
112
+ </>
113
+ ),
114
+ fieldContainer: (field, fieldSchema) => (
115
+ <Grid.Col span={{ base: 12, md: fieldSchema.type === 'object' ? 12 : 6 }}>{field}</Grid.Col>
116
+ ),
117
+ },
118
+ argTypes: {
119
+ onSubmit: {
120
+ table: {
121
+ disable: true,
122
+ },
123
+ },
124
+ container: {
125
+ table: {
126
+ disable: true,
127
+ },
128
+ },
129
+ fieldContainer: {
130
+ table: {
131
+ disable: true,
132
+ },
133
+ },
134
+ schema: {
135
+ description:
136
+ 'Array of field definitions used to render the form. Each item defines one input field.',
137
+ control: false,
138
+ },
139
+ },
140
+ };
141
+
142
+ export default meta;
143
+
144
+ // ------------------------------
145
+ // Stories
146
+ // ------------------------------
147
+
148
+ type Story = StoryObj<typeof AutoForm>;
149
+
150
+ export const TextField: Story = {
151
+ args: {
152
+ schema: textFieldSchema,
153
+ },
154
+ };
155
+
156
+ export const NumberField: Story = {
157
+ args: {
158
+ schema: numberFieldSchema,
159
+ },
160
+ };
161
+
162
+ export const SelectField: Story = {
163
+ args: {
164
+ schema: selectFieldSchema,
165
+ },
166
+ };
167
+
168
+ export const CheckField: Story = {
169
+ args: {
170
+ schema: checkFieldSchema,
171
+ },
172
+ };
173
+
174
+ export const DateField: Story = {
175
+ args: {
176
+ schema: dateFieldSchema,
177
+ },
178
+ };
179
+
180
+ export const ArrayField: Story = {
181
+ args: {
182
+ schema: arrayFieldSchema,
183
+ },
184
+ };
185
+
186
+ export const ObjectField: Story = {
187
+ args: {
188
+ schema: objectFieldSchema,
189
+ },
190
+ };
package/src/theme.ts ADDED
@@ -0,0 +1,5 @@
1
+ import { createTheme } from '@mantine/core';
2
+
3
+ export const theme = createTheme({
4
+ /** Put your mantine theme override here */
5
+ });
@@ -0,0 +1,22 @@
1
+ // types/custom-render.ts
2
+ import { ArrayFieldOptions, FieldSchema } from './field';
3
+
4
+ export type FieldRenderCustomRender = (
5
+ field: FieldSchema,
6
+ value: any,
7
+ error: React.ReactNode | Record<string, React.ReactNode>,
8
+ onChange: (name: string, value: any) => void,
9
+ formValues: Record<string, any>,
10
+ options?: ArrayFieldOptions,
11
+ readOnly?: true
12
+ ) => React.ReactNode;
13
+
14
+ export interface CustomFieldRender {
15
+ field: FieldSchema;
16
+ value: any;
17
+ error: React.ReactNode | Record<string, React.ReactNode>;
18
+ onChange: (name: string, value: any) => void;
19
+ formValues: Record<string, any>;
20
+ options?: ArrayFieldOptions;
21
+ readOnly?: true;
22
+ }