@sanity/form-toolkit 2.1.0 → 2.2.1
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/README.md +33 -3
- package/dist/form-renderer/index.d.mts +66 -0
- package/dist/form-renderer/index.d.ts +66 -0
- package/dist/form-renderer/index.js +128 -0
- package/dist/form-renderer/index.js.map +1 -0
- package/dist/form-renderer/index.mjs +128 -0
- package/dist/form-renderer/index.mjs.map +1 -0
- package/dist/form-schema/index.d.mts +23 -64
- package/dist/form-schema/index.d.ts +23 -64
- package/dist/form-schema/index.js +72 -185
- package/dist/form-schema/index.js.map +1 -1
- package/dist/form-schema/index.mjs +72 -186
- package/dist/form-schema/index.mjs.map +1 -1
- package/package.json +9 -1
- package/src/{form-schema → form-renderer}/components/default-field.tsx +11 -3
- package/src/{form-schema → form-renderer}/components/types.ts +0 -2
- package/src/form-renderer/index.ts +4 -0
- package/src/form-schema/components/validation-type.tsx +14 -0
- package/src/form-schema/index.ts +12 -6
- package/src/form-schema/schema-types/form-field.ts +66 -52
- package/src/form-schema/schema-types/form.ts +50 -53
- package/src/form-schema/schema-types/index.ts +3 -2
- package/src/form-schema/structure/document-view.tsx +0 -78
- package/src/form-schema/structure/index.ts +0 -11
- /package/src/{form-schema → form-renderer}/components/form-renderer.tsx +0 -0
|
@@ -1,31 +1,33 @@
|
|
|
1
1
|
import {LuTextCursorInput} from 'react-icons/lu'
|
|
2
2
|
import {defineField, defineType} from 'sanity'
|
|
3
3
|
|
|
4
|
+
import {ValidationType} from '../components/validation-type'
|
|
4
5
|
interface ValidationContextDocument {
|
|
5
6
|
fields?: Array<{
|
|
6
7
|
name: string
|
|
7
8
|
type?: string
|
|
8
9
|
}>
|
|
9
10
|
}
|
|
11
|
+
|
|
10
12
|
// Validation options by field type
|
|
11
|
-
export const validationTypesByFieldType = {
|
|
12
|
-
checkbox: ['minSelectedCount', 'maxSelectedCount'
|
|
13
|
-
color: [
|
|
14
|
-
date: ['minDate', 'maxDate'
|
|
15
|
-
'datetime-local': ['minDate', 'maxDate'
|
|
16
|
-
email: ['pattern'
|
|
17
|
-
file: ['maxSize', 'fileType'
|
|
18
|
-
hidden: [
|
|
19
|
-
number: ['min', 'max'
|
|
20
|
-
// password: ['minLength', 'pattern'
|
|
21
|
-
radio: [
|
|
22
|
-
range: ['min', 'max', 'step'
|
|
23
|
-
select: [
|
|
24
|
-
tel: ['pattern'
|
|
25
|
-
text: ['minLength', 'maxLength', 'pattern'
|
|
26
|
-
textarea: ['minLength', 'maxLength'
|
|
27
|
-
time: [
|
|
28
|
-
url: ['pattern'
|
|
13
|
+
export const validationTypesByFieldType: Record<string, string[]> = {
|
|
14
|
+
checkbox: ['minSelectedCount', 'maxSelectedCount'],
|
|
15
|
+
color: [],
|
|
16
|
+
date: ['minDate', 'maxDate'],
|
|
17
|
+
'datetime-local': ['minDate', 'maxDate'],
|
|
18
|
+
email: ['pattern'],
|
|
19
|
+
file: ['maxSize', 'fileType'],
|
|
20
|
+
hidden: [],
|
|
21
|
+
number: ['min', 'max'],
|
|
22
|
+
// password: ['minLength', 'pattern'],
|
|
23
|
+
radio: [],
|
|
24
|
+
range: ['min', 'max', 'step'],
|
|
25
|
+
select: [],
|
|
26
|
+
tel: ['pattern'],
|
|
27
|
+
text: ['minLength', 'maxLength', 'pattern'],
|
|
28
|
+
textarea: ['minLength', 'maxLength'],
|
|
29
|
+
time: [],
|
|
30
|
+
url: ['pattern'],
|
|
29
31
|
}
|
|
30
32
|
export const formFieldType = defineType({
|
|
31
33
|
name: 'formField',
|
|
@@ -114,40 +116,52 @@ export const formFieldType = defineType({
|
|
|
114
116
|
type: 'boolean',
|
|
115
117
|
initialValue: false,
|
|
116
118
|
}),
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
119
|
+
defineField({
|
|
120
|
+
name: 'validation',
|
|
121
|
+
title: 'Validation Rules',
|
|
122
|
+
type: 'array',
|
|
123
|
+
hidden: ({parent}) => {
|
|
124
|
+
if (!parent?.type) return true
|
|
125
|
+
const validationTypes = validationTypesByFieldType[parent.type]
|
|
126
|
+
return !validationTypes || validationTypes.length === 0
|
|
127
|
+
},
|
|
128
|
+
of: [
|
|
129
|
+
{
|
|
130
|
+
type: 'object',
|
|
131
|
+
fields: [
|
|
132
|
+
defineField({
|
|
133
|
+
name: 'type',
|
|
134
|
+
title: 'Validation Type',
|
|
135
|
+
type: 'string',
|
|
136
|
+
options: {
|
|
137
|
+
// TODO: I think this needs to be a custom input component?
|
|
138
|
+
// list: ({parent}) => (parent?.type ? validationTypesByFieldType[parent.type] : []),
|
|
139
|
+
list: [],
|
|
140
|
+
},
|
|
141
|
+
components: {
|
|
142
|
+
input: ValidationType,
|
|
143
|
+
},
|
|
144
|
+
}),
|
|
145
|
+
defineField({
|
|
146
|
+
name: 'value',
|
|
147
|
+
title: 'Value',
|
|
148
|
+
type: 'string',
|
|
149
|
+
}),
|
|
150
|
+
defineField({
|
|
151
|
+
name: 'message',
|
|
152
|
+
title: 'Error Message',
|
|
153
|
+
type: 'string',
|
|
154
|
+
}),
|
|
155
|
+
],
|
|
156
|
+
preview: {
|
|
157
|
+
select: {
|
|
158
|
+
title: 'type',
|
|
159
|
+
subtitle: 'value',
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
],
|
|
164
|
+
}),
|
|
151
165
|
defineField({
|
|
152
166
|
name: 'choices',
|
|
153
167
|
title: 'Choices',
|
|
@@ -1,55 +1,52 @@
|
|
|
1
1
|
import {FaWpforms} from 'react-icons/fa'
|
|
2
|
-
import {defineField, defineType} from 'sanity'
|
|
2
|
+
import {defineField, defineType, type SchemaTypeDefinition} from 'sanity'
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}),
|
|
54
|
-
],
|
|
55
|
-
})
|
|
4
|
+
import type {FieldsOption} from '..'
|
|
5
|
+
|
|
6
|
+
export const formType = (fields: FieldsOption): SchemaTypeDefinition => {
|
|
7
|
+
// const fieldsOf =
|
|
8
|
+
// fields && fields.length ? [{type: 'formField'}, ...fields] : [{type: 'formField'}]
|
|
9
|
+
return defineType({
|
|
10
|
+
name: 'form',
|
|
11
|
+
title: 'Form',
|
|
12
|
+
type: 'document',
|
|
13
|
+
icon: FaWpforms,
|
|
14
|
+
fields: [
|
|
15
|
+
defineField({
|
|
16
|
+
name: 'title',
|
|
17
|
+
title: 'Form Title',
|
|
18
|
+
type: 'string',
|
|
19
|
+
description: 'Internal title for the form',
|
|
20
|
+
validation: (Rule) => Rule.required(),
|
|
21
|
+
}),
|
|
22
|
+
defineField({
|
|
23
|
+
name: 'id',
|
|
24
|
+
title: 'Form ID',
|
|
25
|
+
type: 'slug',
|
|
26
|
+
options: {
|
|
27
|
+
source: 'title',
|
|
28
|
+
},
|
|
29
|
+
// validation: (Rule) => Rule.required(),
|
|
30
|
+
}),
|
|
31
|
+
defineField({
|
|
32
|
+
name: 'fields',
|
|
33
|
+
title: 'Form Fields',
|
|
34
|
+
type: 'array',
|
|
35
|
+
of: [{type: 'formField'}, ...fields],
|
|
36
|
+
}),
|
|
37
|
+
defineField({
|
|
38
|
+
name: 'submitButton',
|
|
39
|
+
title: 'Submit Button',
|
|
40
|
+
type: 'object',
|
|
41
|
+
fields: [
|
|
42
|
+
defineField({
|
|
43
|
+
name: 'text',
|
|
44
|
+
title: 'Button Text',
|
|
45
|
+
type: 'string',
|
|
46
|
+
initialValue: 'Submit',
|
|
47
|
+
}),
|
|
48
|
+
],
|
|
49
|
+
}),
|
|
50
|
+
],
|
|
51
|
+
})
|
|
52
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type {SchemaTypeDefinition} from 'sanity'
|
|
2
2
|
|
|
3
|
+
import type {FieldsOption} from '..'
|
|
3
4
|
import {formType} from './form'
|
|
4
5
|
import {formFieldType} from './form-field'
|
|
5
6
|
|
|
6
|
-
export const schema: {types: SchemaTypeDefinition[]}
|
|
7
|
-
types: [formType, formFieldType]
|
|
7
|
+
export const schema = (fields: FieldsOption): {types: SchemaTypeDefinition[]} => {
|
|
8
|
+
return {types: [formType(fields), formFieldType]}
|
|
8
9
|
}
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import {type FC, type FormEvent, useState} from 'react'
|
|
2
|
-
|
|
3
|
-
import {FormRenderer} from '../components/form-renderer'
|
|
4
|
-
import type {FieldState, FormDataProps} from '../components/types'
|
|
5
|
-
|
|
6
|
-
interface UseStateExampleProps {
|
|
7
|
-
formData: FormDataProps
|
|
8
|
-
onSubmit?: (data: Record<string, unknown>) => void
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const UseStateExample: FC<UseStateExampleProps> = ({formData, onSubmit = () => null}) => {
|
|
12
|
-
const [values, setValues] = useState<Record<string, FieldState['value']>>({})
|
|
13
|
-
const [errors, setErrors] = useState<Record<string, string | undefined>>({})
|
|
14
|
-
|
|
15
|
-
const getFieldState = (fieldName: string) => ({
|
|
16
|
-
value: values[fieldName],
|
|
17
|
-
onChange: (value: unknown) => {
|
|
18
|
-
// @ts-expect-error todo: fix this
|
|
19
|
-
setValues((prev) => ({
|
|
20
|
-
...prev,
|
|
21
|
-
[fieldName]: value,
|
|
22
|
-
}))
|
|
23
|
-
// Clear error when value changes
|
|
24
|
-
if (errors[fieldName]) {
|
|
25
|
-
setErrors((prev) => ({
|
|
26
|
-
...prev,
|
|
27
|
-
[fieldName]: undefined,
|
|
28
|
-
}))
|
|
29
|
-
}
|
|
30
|
-
},
|
|
31
|
-
onBlur: () => {
|
|
32
|
-
// Example validation on blur
|
|
33
|
-
const field = formData.fields
|
|
34
|
-
?.flatMap((formField) => formField)
|
|
35
|
-
.find((formField) => formField?.name === fieldName)
|
|
36
|
-
|
|
37
|
-
if (field?.required && !values[fieldName]) {
|
|
38
|
-
setErrors((prev) => ({
|
|
39
|
-
...prev,
|
|
40
|
-
[fieldName]: 'This field is required',
|
|
41
|
-
}))
|
|
42
|
-
}
|
|
43
|
-
},
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
const getFieldError = (fieldName: string) => errors[fieldName]
|
|
47
|
-
|
|
48
|
-
const handleSubmit = (e: FormEvent) => {
|
|
49
|
-
e.preventDefault()
|
|
50
|
-
onSubmit(values)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return (
|
|
54
|
-
<>
|
|
55
|
-
<style>
|
|
56
|
-
{`
|
|
57
|
-
.form-field > label {
|
|
58
|
-
display: block;
|
|
59
|
-
}
|
|
60
|
-
.form-field {
|
|
61
|
-
margin-bottom: 1rem;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
`}
|
|
65
|
-
</style>
|
|
66
|
-
<FormRenderer
|
|
67
|
-
formData={formData}
|
|
68
|
-
onSubmit={handleSubmit}
|
|
69
|
-
getFieldState={getFieldState}
|
|
70
|
-
getFieldError={getFieldError}
|
|
71
|
-
/>
|
|
72
|
-
</>
|
|
73
|
-
)
|
|
74
|
-
}
|
|
75
|
-
export const DocumentView = (props: {document: {displayed: FormDataProps}}) => {
|
|
76
|
-
// console.log('props', props)
|
|
77
|
-
return <UseStateExample formData={props.document.displayed} />
|
|
78
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type {DefaultDocumentNodeResolver} from 'sanity/structure'
|
|
2
|
-
|
|
3
|
-
import {DocumentView} from './document-view'
|
|
4
|
-
|
|
5
|
-
export const defaultDocumentNode: DefaultDocumentNodeResolver = (S, {schemaType}) => {
|
|
6
|
-
// Conditionally return a different configuration based on the schema type
|
|
7
|
-
if (schemaType === 'form') {
|
|
8
|
-
return S.document().views([S.view.form(), S.view.component(DocumentView).title('Web')])
|
|
9
|
-
}
|
|
10
|
-
return S.document().views([S.view.form()])
|
|
11
|
-
}
|
|
File without changes
|