Last active
August 31, 2021 15:06
-
-
Save isaac-martin/8bc079bf62fa11bb35f0b2dac1b93afb to your computer and use it in GitHub Desktop.
custom input component
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 * as React from "react"; | |
import ObjectInput from "@sanity/form-builder/lib/inputs/ObjectInput"; | |
import { TabList, TabPanel, Tab, Card } from "@sanity/ui"; | |
import { Fieldset } from "@sanity/types/lib/schema/types"; | |
import ValidationStatus from "@sanity/base/lib/__legacy/@sanity/components/validation/ValidationStatus"; | |
import { FieldsetMarkersCollection } from "../MarkersCollection"; | |
import styled from "@emotion/styled"; | |
import { sanityTheme } from "../common/theme"; | |
import { ObjectFormBuilder } from "../ObjectFormBuilder"; | |
const TabLabel = styled.span<{ hasErrors: boolean }>(({ hasErrors }) => { | |
return { | |
color: hasErrors ? sanityTheme.colors.error : "initial", | |
display: `flex`, | |
paddingRight: 5 | |
}; | |
}); | |
const TabIconSpacer = styled.div({ | |
paddingLeft: ".5em", | |
}); | |
/** | |
* Custom component to render an object schema's fieldsets as tabs | |
*/ | |
export const FieldsetTabs = (props) => { | |
// console.log({ topLevel: props }); | |
const { onChange, onFocus, onBlur, presence, focusPath } = props; | |
const activateFirstFieldset = () => { | |
const fieldsets = props.type.fieldsets ?? []; | |
const first: Fieldset | undefined = fieldsets[0]; | |
setActiveFieldset(first); | |
}; | |
React.useEffect(() => { | |
activateFirstFieldset(); | |
}, []); | |
const setActiveFieldset = (fieldset: Fieldset | undefined) => { | |
if (!fieldset) return; | |
if ("name" in fieldset) { | |
setActiveFieldsetName(fieldset.name); | |
} | |
}; | |
const [activeFieldsetName, setActiveFieldsetName] = React.useState(""); | |
const { type } = props; | |
const fieldsets = type.fieldsets ?? []; | |
const markers = new FieldsetMarkersCollection(type, props.markers); | |
if (fieldsets.length === 0) | |
return ( | |
<h2> | |
`This schema has no fieldsets. Double check the schema named $ | |
{type.name}` | |
</h2> | |
); | |
return ( | |
<Card padding={4}> | |
<TabList> | |
{fieldsets.map((fieldset) => { | |
if (fieldset.single) return <></>; | |
const hasErrors = markers.fieldsetHasErrors(fieldset.name); | |
const isActive = fieldset.name === activeFieldsetName; | |
return ( | |
<Tab | |
aria-controls={`${fieldset.name}-panel`} | |
id={fieldset.name} | |
selected={isActive} | |
key={fieldset.name} | |
space={2} | |
label={ | |
<> | |
<TabLabel hasErrors={hasErrors}> | |
{fieldset.title} | |
<TabIconSpacer /> | |
<ValidationStatus | |
markers={markers.getMarkersForFieldset(fieldset.name)} | |
/> | |
</TabLabel> | |
</> | |
} | |
onClick={() => setActiveFieldset(fieldset)} | |
/> | |
); | |
})} | |
</TabList> | |
{fieldsets.map((fieldset) => { | |
if (!fieldset.fields.length) return <></>; | |
if (props.level !== 0) return <>0</> | |
const isActive = fieldset.name === activeFieldsetName; | |
// console.log({ fieldset }) | |
return ( | |
<TabPanel | |
aria-labelledby={fieldset.name} | |
hidden={!isActive} | |
id={`${fieldset.name}-panel`} | |
> | |
<Card border marginTop={2} padding={4} radius={2}> | |
{fieldset.fields.map((field, i) => { | |
// console.log({ field }) | |
// console.log(field.name) | |
console.log(props.value["meta"]) | |
console.log(props.value) | |
return ( | |
<ObjectFormBuilder | |
focusPath={focusPath} | |
presence={presence} | |
markers={props.markers} | |
compareValue={props.compareValue} | |
level={props.level} | |
value={props.value} | |
onChange={onChange} | |
onFocus={onFocus} | |
onBlur={onBlur} | |
{...field.type} | |
/> | |
); | |
})} | |
</Card> | |
</TabPanel> | |
); | |
})} | |
</Card> | |
); | |
}; | |
// /** | |
// * Adds a message above the fieldset tabs. Useful for deprecation notices | |
// */ | |
export const createFieldsetTabsWithMessage = | |
(message: string) => (props: ObjectInput["props"]) => { | |
return ( | |
<> | |
{message && <h1>{message}</h1>} | |
<FieldsetTabs {...props} /> | |
</> | |
); | |
}; | |
// /ObjectFromBuilder.tsx | |
import React from 'react' | |
import { FormBuilderInput } from '@sanity/form-builder/lib/FormBuilderInput' | |
import Fieldset from 'part:@sanity/components/fieldsets/default' | |
// Utilities for patching | |
import { setIfMissing } from '@sanity/form-builder/PatchEvent' | |
export const ObjectFormBuilder = React.forwardRef((props, ref) => { | |
const { | |
compareValue, | |
focusPath, | |
markers, | |
onBlur, | |
onChange, | |
onFocus, | |
presence, | |
type, | |
value, | |
level | |
} = props | |
// console.log(level) | |
// console.log({ | |
// compareValue, | |
// focusPath, | |
// markers, | |
// onBlur, | |
// onChange, | |
// onFocus, | |
// presence, | |
// type, | |
// value, | |
// level | |
// }) | |
const handleFieldChange = React.useCallback( | |
(field, fieldPatchEvent) => { | |
console.log(field.name) | |
onChange( | |
fieldPatchEvent | |
.prefixAll(field.name) | |
.prepend(setIfMissing({ _type: type.name })) | |
) | |
}, | |
[onChange] | |
) | |
// Get an array of field names for use in a few instances in the code | |
const fieldNames = type.fields.map((f) => f.name) | |
// If Presence exist, get the presence as an array for the children of this field | |
const childPresence = | |
presence.length === 0 | |
? presence | |
: presence.filter((item) => fieldNames.includes(item.path[0])) | |
// If Markers exist, get the markers as an array for the children of this field | |
const childMarkers = | |
markers.length === 0 | |
? markers | |
: markers.filter((item) => { | |
// console.log({ item }) | |
// console.log({ fieldNames }) | |
return fieldNames.includes(item.path[0]) | |
}) | |
// console.log({ childMarkers }) | |
return ( | |
<Fieldset | |
legend={type.title} // schema title | |
description={type.description} // schema description | |
markers={childMarkers} // markers built above | |
presence={childPresence} // presence built above | |
> | |
{type.fields.map((field, i) => { | |
// console.log(field) | |
// return ( | |
// <p>{value[field.name]}</p> | |
// ) | |
console.log(value[field.name]) | |
console.log(value) | |
return ( | |
<FormBuilderInput | |
level={level + 1} | |
ref={i === 0 ? ref : null} | |
key={field.name} | |
type={field.type} | |
value={value && value[field.name] ? value[field.name] : undefined} | |
onChange={(patchEvent) => handleFieldChange(field, patchEvent)} | |
path={[field.name]} | |
markers={childMarkers} | |
focusPath={focusPath} | |
readOnly={field.readOnly} | |
presence={presence} | |
onFocus={onFocus} | |
onBlur={onBlur} | |
compareValue={compareValue} | |
/> | |
) | |
})} | |
</Fieldset> | |
) | |
} | |
) | |
// MarkersCollection | |
import { ValidationMarker } from "@sanity/types/lib/markers"; | |
import { ObjectSchemaTypeWithOptions } from "@sanity/types/lib/schema"; | |
/** | |
* Utility class for manipulating lists of ValidationMarkers from Sanity | |
*/ | |
export class FieldsetMarkersCollection { | |
private markers: { | |
[key: string]: ValidationMarker[] | undefined; | |
}; | |
constructor( | |
type: ObjectSchemaTypeWithOptions, | |
markers: ValidationMarker[] | undefined | |
) { | |
// key markers by corresponding fieldset name | |
this.markers = | |
markers | |
?.map((marker) => { | |
return { | |
...marker, | |
fieldName: marker.path.length ? marker.path[0].toString() : "", | |
}; | |
}) | |
.map((markerWithFieldName) => { | |
return { | |
...markerWithFieldName, | |
fieldsetName: type.fields.find( | |
(field) => field.name === markerWithFieldName.fieldName | |
)?.fieldset, | |
}; | |
}) | |
.reduce((groups, marker) => { | |
if (!marker.fieldsetName) return groups; | |
groups[marker.fieldsetName] = groups[marker.fieldsetName] ?? []; | |
groups[marker.fieldsetName].push(marker); | |
return groups; | |
}, {} as Record<string, ValidationMarker[]>) ?? {}; | |
} | |
fieldsetHasErrors(fieldsetName: string) { | |
const markers = this.markers[fieldsetName] ?? []; | |
return new MarkersCollection(markers).hasErrors(); | |
} | |
getMarkersForFieldset(fieldsetName: string) { | |
return this.markers[fieldsetName] ?? []; | |
} | |
} | |
export class MarkersCollection { | |
private _rawMarkers: ValidationMarker[]; | |
constructor(markers: ValidationMarker[] | undefined) { | |
this._rawMarkers = markers ?? []; | |
} | |
hasErrors() { | |
return this.errors().length > 0 | |
} | |
errors() { | |
return this._rawMarkers.filter((marker) => marker.level === "error"); | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment