-
-
Save kmelve/e34feccafe8e9ee3c7e09d60ee3fd4d2 to your computer and use it in GitHub Desktop.
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' | |
// FormBuilderInput automatically generates fields from a schema | |
import {FormBuilderInput} from 'part:@sanity/form-builder' | |
// a Higher Order Component that passes document values as props | |
import {withDocument} from 'part:@sanity/form-builder' | |
class confitionalFields extends React.PureComponent { | |
static propTypes = { | |
type: PropTypes.shape({ | |
title: PropTypes.string, | |
name: PropTypes.string | |
}).isRequired, | |
level: PropTypes.number, | |
value: PropTypes.shape({ | |
_type: PropTypes.string | |
}), | |
focusPath: PropTypes.array.isRequired, | |
onFocus: PropTypes.func.isRequired, | |
onChange: PropTypes.func.isRequired, | |
onBlur: PropTypes.func.isRequired | |
} | |
firstFieldInput = React.createRef() | |
handleFieldChange = (field, fieldPatchEvent) => { | |
const {onChange, type} = this.props | |
// 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 | |
onChange(fieldPatchEvent.prefixAll(field.name).prepend(setIfMissing({_type: type.name}))) | |
} | |
focus() { | |
this.firstFieldInput.current.focus() | |
} | |
render() { | |
console.log(this.props) | |
const {document, type, value, level, focusPath, onFocus, onBlur} = this.props | |
let condition = this.props && this.props.value && this.props.value.condition; | |
return ( | |
<Fieldset level={level} legend={type.title} description={type.description}> | |
<div> | |
{type.fields[0].type.fields | |
.map((field, i) => ( | |
// Delegate to the generic FormBuilderInput. It will resolve and insert the actual input component | |
// for the given field type | |
<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]} | |
focusPath={focusPath} | |
onFocus={onFocus} | |
onBlur={onBlur} | |
/> | |
))} | |
<br/> | |
{type.fields[1].type.fields | |
.filter(field => ( | |
field.name === condition ? true : false | |
)) | |
.map((field, i) => ( | |
// Delegate to the generic FormBuilderInput. It will resolve and insert the actual input component | |
// for the given field type | |
<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]} | |
focusPath={focusPath} | |
onFocus={onFocus} | |
onBlur={onBlur} | |
/> | |
))} | |
</div> | |
</Fieldset> | |
) | |
} | |
} | |
export default withDocument(confitionalFields) |
import React from 'react'; | |
import conditionalFields from "../objects/confitionalFields"; | |
export default { | |
title: "Link", | |
name: "link", | |
type: 'object', | |
fields: [ | |
{ | |
title: 'Link Label', | |
name: 'label', | |
type: 'localeString', | |
}, | |
{ | |
title: "Select the link type", | |
name: 'aFieldWithCondition', | |
type: 'object', | |
inputComponent: conditionalFields, | |
fields: [ | |
{ | |
type: 'object', | |
name: 'input', | |
fields: [ | |
{ | |
name: 'condition', | |
title: 'Link Type', | |
type: 'string', | |
options: { | |
list: [ | |
{title: 'Internal, inside this website', value: 'linkInternal'}, | |
{title: 'External, outside this website', value: 'linkExternal'}, | |
{title: 'String', value: 'name'}, | |
], | |
// layout: 'radio', // <-- leave out to make it a dropdown menu | |
}, | |
}, | |
] | |
}, | |
{ | |
type: 'object', | |
name: 'options', | |
fields: [ | |
{ | |
title: 'Page Reference', | |
name: 'linkInternal', | |
type: 'reference', | |
to: [{type: 'landing'}] | |
}, | |
{ | |
name: "linkExternal", | |
title: "URL", | |
type: "url", | |
validation: false | |
}, | |
{ | |
title: 'This one is just to show you can add more options', | |
name: 'name', | |
type: 'string' | |
}, | |
] | |
} | |
] | |
}, | |
] | |
}; |
This is useful, but all the inline objects need to be separated out and defined as a global schema type for use with GraphQL which is not ideal since they are specific to the parent object (link
in this case).
I found the same issue. Is there a way to use this solution with GraphQL?
Hey! Perfect. Currently, I try to wrap my head around building a custom input component, based on a document.
I still wonder if I need to do it in a class component. However, let me describe my problem.
What I try to achieve: Set a document were I define all the fields that need to be rendered (reviewTemplate).
-> create another document (review) where I select the template and the field will be rendered accordingly.
The Input fields shall show up like a "native" sanity ui component.
It works with an HTML input field. but on every change event, it loses the focus and I have to click the input field again to type any further (just one character at a time).
So I played around with the formbuilderinput. But unfortunately, this error message pops up: func.apply is not a function
.
Is this the right place to comment with code? Or where can I ask for help? Do you have documentations for the api to
build such a custom form?
THX. :)
import React, { Component } from 'react'
import { withDocument } from 'part:@sanity/form-builder'
import FormField from 'part:@sanity/components/formfields/default'
import Fieldset from 'part:@sanity/components/fieldsets/default'
import { FormBuilderInput } from 'part:@sanity/form-builder'
import { Document, Type } from '../../@types'
import PatchEvent, { set, unset } from 'part:@sanity/form-builder/patch-event'
import client from 'part:@sanity/base/client'
import { v4 } from 'uuid'
interface InputComponentProps {
type: Type
document?: Document
value: any | any[]
onChange: Function
markers: any[]
level: number
onBlur: Function
}
interface Evaluation {
document: {
productReviewFeatures: any
}
}
const EvaluationContainer: React.FC = ({
type,
document,
value,
markers,
level,
onChange,
onBlur,
onFocus,
focusPath
}) => {
const { productReviewFeatures } = document
const { title, description } = type
console.log('focus path', focusPath)
const [isLoading, setIsloading] = React.useState<Boolean>(true)
const [evaluationFeatures, setEvaluationFeautures] = React.useState([])
const [evaluation, setEvaluation] = React.useState([])
const getSubEvaluationStructure = subFeatures => {
let subFeatureStructure = []
subFeatures.map(sub => {
subFeatureStructure.push({
_type: 'evaluation',
type: sub.type,
name: sub.name
})
})
return subFeatureStructure
}
const getMainEvaluationStructure = mainFeatures => {
let mainEvaluationStructure = []
mainFeatures.map(mainFeature => {
console.log('mainFeature', mainFeature)
mainEvaluationStructure.push({
_type: 'mainFeatureEvaluation',
name: mainFeature.name,
subFeatureEvaluation: getSubEvaluationStructure(
mainFeature.subFeatureEvaluation || mainFeature.subFeatures
)
})
})
return mainEvaluationStructure
}
React.useEffect(async () => {
if (productReviewFeatures && productReviewFeatures._ref) {
try {
const document = await client.fetch(`*[_id =="${productReviewFeatures._ref}"][0]`)
if (document && document.mainFeatures) {
console.log('document', document)
setEvaluationFeautures(document.mainFeatures)
setEvaluation(
getMainEvaluationStructure(document.reviewEvaluation || document.mainFeatures)
)
setIsloading(false)
}
} catch (error) {
console.log(error)
}
}
}, [productReviewFeatures._ref])
if (isLoading) {
return <div>Data Loading</div>
}
const handleChange = (event, { j, i }) => {
const newEvaluation = evaluation
console.log('handle', (newEvaluation[j].subFeatureEvaluation[i].value = event.target.value))
setEvaluation([...newEvaluation])
console.log('evaluation', evaluation)
onChange(PatchEvent.from(set(evaluation)))
}
console.log('value', value)
return (
<Fieldset legend={title} description={description}>
{evaluationFeatures &&
evaluationFeatures.map((feature, j) => {
return (
<div key={v4()}>
<p>{feature.name}</p>
{feature.subFeatures &&
feature.subFeatures.map((subFeature, i) => (
<FormField label={subFeature.name} description={subFeature.description}>
<input
type="text"
value={value[j].subFeatureEvaluation[i].value}
name={subFeature.name}
onChange={event => handleChange(event, { j, i })}
/>
<FormBuilderInput
level={level + 1}
ref={i === 0 ? React.createRef() : null}
key={subFeature.name}
type={subFeature.type}
value={value[j].subFeatureEvaluation[i].value}
onChange={event => handleChange(event, { j, i })}
path={[`${j}${i}`]}
focusPath={focusPath}
onFocus={onFocus}
onBlur={onBlur}
/>
</FormField>
))}
</div>
)
})}
</Fieldset>
)
}
const exportData = [
{
type: 'mainFeature',
title: 'Connectivity',
subFeature: [{ title: 'Bluetooth', type: 'Boolen', value: true }]
},
{ type: 'mainFeature', subFeature: [{ title: 'Bluetooth', type: 'Boolen', value: true }] }
]
export default withDocument(EvaluationContainer)
Thanks for this! Exactly what I needed.