Skip to content

Instantly share code, notes, and snippets.

@isaac-martin
Last active August 31, 2021 15:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save isaac-martin/8bc079bf62fa11bb35f0b2dac1b93afb to your computer and use it in GitHub Desktop.
Save isaac-martin/8bc079bf62fa11bb35f0b2dac1b93afb to your computer and use it in GitHub Desktop.
custom input component
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