Skip to content

Commit e6185ef

Browse files
authored
fix(form): fix issue where FormInput was not rendering field when passed 'includeField' (#7350)
* chore(test-studio): add example schema + custom FormInput example/playground * fix(form): fix issue where FormInput was not rendering field when passed 'includeField'
1 parent 7531cda commit e6185ef

File tree

5 files changed

+164
-28
lines changed

5 files changed

+164
-28
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import {CloseIcon, EyeOpenIcon} from '@sanity/icons'
2+
import {Box, Button, Card, Checkbox, Flex, Inline, Stack, Text} from '@sanity/ui'
3+
import {useState} from 'react'
4+
import {
5+
FormInput,
6+
type ObjectInputProps,
7+
type Path,
8+
pathToString,
9+
type RenderInputCallback,
10+
} from 'sanity'
11+
12+
export function FormInputExample(props: ObjectInputProps) {
13+
const [path, setPath] = useState<Path>([])
14+
15+
const [includeField, setIncludeField] = useState(false)
16+
const [includeItem, setIncludeItem] = useState(false)
17+
18+
const renderDefaultForm = path.length === 0
19+
20+
const renderInput: RenderInputCallback = (inputProps) => {
21+
// wraps each input with a button that allows rendering only the selected input
22+
const selected = inputProps.path === path
23+
return (
24+
<Flex>
25+
<Box flex={1}>{props.renderInput(inputProps)}</Box>
26+
<Flex marginLeft={2}>
27+
<Button
28+
mode="ghost"
29+
tone="primary"
30+
fontSize={1}
31+
onClick={() => setPath(selected ? [] : inputProps.path)}
32+
icon={selected ? CloseIcon : EyeOpenIcon}
33+
/>
34+
</Flex>
35+
</Flex>
36+
)
37+
}
38+
39+
if (renderDefaultForm) {
40+
return (
41+
<Stack space={2}>
42+
<Card shadow={2} margin={3} padding={4} radius={2}>
43+
{props.renderDefault({...props, renderInput})}
44+
</Card>
45+
</Stack>
46+
)
47+
}
48+
return (
49+
<Stack space={2}>
50+
<Card padding={3} radius={2}>
51+
<Stack space={4}>
52+
<Stack space={4}>
53+
<Flex gap={2}>
54+
<Text weight="semibold">
55+
Input at <code>{pathToString(path)}</code>
56+
</Text>
57+
</Flex>
58+
<Flex gap={4}>
59+
<Inline space={2}>
60+
<Checkbox checked={includeField} onChange={() => setIncludeField((v) => !v)} />{' '}
61+
<Text>Include field</Text>
62+
</Inline>
63+
<Inline space={2}>
64+
<Checkbox checked={includeItem} onChange={() => setIncludeItem((v) => !v)} />{' '}
65+
<Text>Include item</Text>
66+
</Inline>
67+
</Flex>
68+
</Stack>
69+
<Card shadow={2} padding={3} radius={2}>
70+
<FormInput
71+
{...props}
72+
renderInput={renderInput}
73+
absolutePath={path}
74+
includeField={includeField}
75+
includeItem={includeItem}
76+
/>
77+
</Card>
78+
</Stack>
79+
</Card>
80+
</Stack>
81+
)
82+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import {defineType} from '@sanity/types'
2+
3+
import {FormInputExample} from '../../../../components/formBuilder/FormInputExample'
4+
import {structureGroupOptions} from '../../../../structure/groupByOption'
5+
6+
export const formInputTest = defineType({
7+
name: 'formInputTest',
8+
title: 'FormInput Test',
9+
type: 'document',
10+
options: structureGroupOptions({
11+
structureGroup: 'v3',
12+
}),
13+
components: {
14+
input: FormInputExample,
15+
},
16+
fields: [
17+
{
18+
name: 'title',
19+
title: 'Title',
20+
type: 'string',
21+
},
22+
{
23+
name: 'array',
24+
type: 'array',
25+
of: [
26+
{
27+
type: 'object',
28+
fields: [
29+
{name: 'title', type: 'string'},
30+
{name: 'text', type: 'string'},
31+
],
32+
},
33+
],
34+
},
35+
{
36+
name: 'description',
37+
title: 'Description',
38+
type: 'text',
39+
},
40+
{
41+
name: 'nested',
42+
type: 'object',
43+
fields: [{name: 'nested', type: 'string'}],
44+
},
45+
],
46+
})
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
export * from './formInputTest'
12
export * from './schema'
+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {validationTest} from './async-functions/schemaType'
22
import {example1SchemaType} from './example1'
3-
import {formComponentsSchema} from './form-components-api'
3+
import {formComponentsSchema, formInputTest} from './form-components-api'
44

55
export const v3docs = {
6-
types: [example1SchemaType, validationTest, formComponentsSchema],
6+
types: [example1SchemaType, validationTest, formComponentsSchema, formInputTest],
77
}

packages/sanity/src/core/form/components/FormInput.tsx

+33-26
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export const FormInput = memo(function FormInput(
4040
* Whether to include the field around the input. Defaults to false
4141
*/
4242
includeField?: boolean
43+
includeItem?: boolean
4344
},
4445
) {
4546
const absolutePath = useMemo(() => {
@@ -101,6 +102,34 @@ const FormInputInner = memo(function FormInputInner(
101102

102103
const {t} = useTranslation()
103104

105+
const renderField: RenderFieldCallback = useCallback(
106+
(fieldProps) => {
107+
// we want to render the field around the input if either of these are true:
108+
// 1. we have reached the destination path and the `includeField`-prop is passed as true
109+
// 2. we are currently at a node somewhere below/inside the destination path
110+
const atDestination = isEqual(absolutePath, fieldProps.path)
111+
const shouldRenderField = atDestination
112+
? props.includeField
113+
: startsWith(absolutePath, fieldProps.path)
114+
return shouldRenderField ? destinationRenderField(fieldProps) : pass(fieldProps)
115+
},
116+
[absolutePath, destinationRenderField, props.includeField],
117+
)
118+
119+
const renderItem: RenderArrayOfObjectsItemCallback = useCallback(
120+
(itemProps) => {
121+
// we want to render the item around the input if either of these are true:
122+
// 1. we have reached the destination path and the `includeItem`-prop is passed as true
123+
// 2. we are currently at a node somewhere below/inside the destination path
124+
const atDestination = isEqual(absolutePath, itemProps.path)
125+
const shouldRenderItem = atDestination
126+
? props.includeItem
127+
: startsWith(absolutePath, itemProps.path)
128+
return shouldRenderItem ? destinationRenderItem(itemProps) : pass(itemProps)
129+
},
130+
[absolutePath, destinationRenderItem, props.includeItem],
131+
)
132+
104133
const renderInput: RenderInputCallback = useCallback(
105134
(inputProps) => {
106135
const isDestinationReached =
@@ -120,6 +149,8 @@ const FormInputInner = memo(function FormInputInner(
120149
return (
121150
<FormInputInner
122151
{...inputProps}
152+
includeField={props.includeField}
153+
includeItem={props.includeItem}
123154
absolutePath={absolutePath}
124155
destinationRenderAnnotation={destinationRenderAnnotation}
125156
destinationRenderBlock={destinationRenderBlock}
@@ -140,35 +171,11 @@ const FormInputInner = memo(function FormInputInner(
140171
destinationRenderInput,
141172
destinationRenderItem,
142173
destinationRenderPreview,
174+
props.includeField,
175+
props.includeItem,
143176
],
144177
)
145178

146-
const renderField: RenderFieldCallback = useCallback(
147-
(fieldProps) => {
148-
// we want to render the field around the input if either of these are true:
149-
// 1. we have reached the destination path and the `includeField`-prop is passed as true
150-
// 2. we are currently at a node somewhere below/inside the destination path
151-
const shouldRenderField =
152-
startsWith(absolutePath, fieldProps.path) &&
153-
(props.includeField || !isEqual(absolutePath, fieldProps.path))
154-
return shouldRenderField ? destinationRenderField(fieldProps) : pass(fieldProps)
155-
},
156-
[absolutePath, destinationRenderField, props.includeField],
157-
)
158-
159-
const renderItem: RenderArrayOfObjectsItemCallback = useCallback(
160-
(itemProps) => {
161-
// we want to render the item around the input if either of these are true:
162-
// 1. we have reached the destination path and the `includeItem`-prop is passed as true
163-
// 2. we are currently at a node somewhere below/inside the destination path
164-
const shouldRenderField =
165-
startsWith(absolutePath, itemProps.path) &&
166-
(props.includeItem || !isEqual(absolutePath, itemProps.path))
167-
return shouldRenderField ? destinationRenderItem(itemProps) : pass(itemProps)
168-
},
169-
[absolutePath, destinationRenderItem, props.includeItem],
170-
)
171-
172179
const renderBlock: RenderBlockCallback = useCallback(
173180
(blockProps) => {
174181
const shouldRenderBlock =

0 commit comments

Comments
 (0)