Last active
April 1, 2024 19:57
-
-
Save coxmi/f41aef54d46588fff27651cd0d35212f to your computer and use it in GitHub Desktop.
Sanity.io conditional field object
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import PropTypes from 'prop-types' | |
import React from 'react' | |
import Fieldset from 'part:@sanity/components/fieldsets/default' | |
import { setIfMissing } from 'part:@sanity/form-builder/patch-event' | |
import { FormBuilderInput, withDocument, withValuePath } from 'part:@sanity/form-builder' | |
import fieldStyle from '@sanity/form-builder/lib/inputs/ObjectInput/styles/Field.css' | |
const isFunction = (obj) => !!(obj && obj.constructor && obj.call && obj.apply) | |
/** | |
* | |
* condition comes from a field in the document schema | |
* | |
* { | |
* name: 'objectTitle', | |
* title: 'object Title' | |
* type: 'object', | |
* options: { | |
* condition: (document: obj, context: func) => bool | |
* } | |
* fields : [] | |
* } | |
* | |
*/ | |
class ConditionalFields extends React.PureComponent { | |
static propTypes = { | |
type: PropTypes.shape({ | |
title: PropTypes.string, | |
name: PropTypes.string.isRequired, | |
fields: PropTypes.array.isRequired, | |
options: PropTypes.shape({ | |
condition: PropTypes.func.isRequired | |
}).isRequired, | |
}).isRequired, | |
level: PropTypes.number, | |
value: PropTypes.shape({ | |
_type: PropTypes.string | |
}), | |
onFocus: PropTypes.func.isRequired, | |
onChange: PropTypes.func.isRequired, | |
onBlur: PropTypes.func.isRequired | |
} | |
firstFieldInput = React.createRef() | |
focus() { | |
this.firstFieldInput.current && this.firstFieldInput.current.focus() | |
} | |
getContext(level = 1) { | |
// gets value path from withValuePath HOC, and applies path to document | |
// we remove the last 𝑥 elements from the valuePath | |
const valuePath = this.props.getValuePath() | |
const removeItems = -Math.abs(level) | |
return (valuePath.length + removeItems <= 0) | |
? this.props.document | |
: valuePath | |
.slice(0, removeItems) | |
.reduce((context, current) => { | |
// basic string path | |
if (typeof current === 'string') { | |
return context[current] || {}; | |
} | |
// object path with key used on arrays | |
if ( | |
typeof current === 'object' && | |
Array.isArray(context) && | |
current._key | |
) { | |
return context.filter(item => item._key && item._key === current._key)[0] || {} | |
} | |
}, this.props.document) | |
} | |
handleFieldChange = (field, fieldPatchEvent) => { | |
// Whenever the field input emits a patch event, we need to make sure to each of the included patches | |
// are prefixed with its field name, e.g. going from: | |
// {path: [], set: <nextvalue>} to {path: [<fieldName>], set: <nextValue>} | |
// and ensure this input's value exists | |
const {onChange, type} = this.props | |
const event = fieldPatchEvent | |
.prefixAll(field.name) | |
.prepend(setIfMissing({ _type: type.name })) | |
onChange(event) | |
} | |
render() { | |
const { document, type, value, level, onFocus, onBlur } = this.props | |
const condition = isFunction(type.options.condition) && type.options.condition || function() { return true } | |
const showFields = !!condition(document, this.getContext.bind(this)) | |
if (!showFields) return <></> | |
return <> | |
{ type.fields | |
.map((field, i) => ( | |
// Delegate to the generic FormBuilderInput. It will resolve and insert the actual input component | |
// for the given field type | |
<div className={fieldStyle.root} key={i}> | |
<FormBuilderInput | |
level={level + 1} | |
ref={i === 0 ? this.firstFieldInput : null} | |
key={field.name} | |
type={field.type} | |
value={value && value[field.name]} | |
onChange={patchEvent => this.handleFieldChange(field, patchEvent)} | |
path={[field.name]} | |
onFocus={onFocus} | |
onBlur={onBlur} | |
/> | |
</div> | |
)) } | |
</> | |
} | |
} | |
export default withValuePath(withDocument(ConditionalFields)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import ConditionalFields from './ConditionalFields.js' | |
export default { | |
title: 'Link', | |
name: 'link', | |
type: 'object', | |
fields: [ | |
{ | |
title: 'Link type', | |
name: 'linkType', | |
type: 'string', | |
options: { | |
list: [ | |
{ title: 'Internal', value: 'internal'}, | |
{ title: 'External', value: 'external'} | |
], | |
layout: 'radio', | |
direction: 'horizontal' | |
} | |
}, | |
{ | |
name : 'internal', | |
type : 'object', | |
inputComponent : ConditionalFields, | |
fields : [ | |
{ | |
title: 'Page', | |
name: 'reference', | |
type: 'reference', | |
to: [{ type: 'movie' }] | |
} | |
], | |
options : { | |
condition : (document, context) => (context().linkType === 'internal'), | |
} | |
} | |
] | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks. I actually used that to begin with, but it only works on fields on the
document
-level, since it doesn't provide the context. But maybe I can try to take that from your script and merge it in.