Skip to content

Instantly share code, notes, and snippets.

@AbeCole
Last active December 12, 2023 21:16
Show Gist options
  • Save AbeCole/648cf14b98bf135695ef8711fa975f2f to your computer and use it in GitHub Desktop.
Save AbeCole/648cf14b98bf135695ef8711fa975f2f to your computer and use it in GitHub Desktop.
Sanity - Custom input component for managing Asset metadata
// Use with Sanity schema on your existing 'image' field, add a subfields option:
// The subfield 'options.field' attribute defines which asset field we are managing
// If no 'options.field' is provided, the 'title' attribute will be downcased and used (i.e. Title -> title)
//
// {
// title: 'Image',
// name: 'image',
// type: 'image',
// fields: [
// {
// title: 'Title',
// type: 'string',
// name: 'title',
// inputComponent: SanityAssetCustomField,
// options: {
// isHighlighted: true,
// field: 'title'
// }
// }
// ]
// }
// @ts-nocheck
import React, { useEffect, useState, forwardRef } from 'react'
import {
Label,
TextInput,
Spinner,
Button,
Flex,
Stack,
Card,
Text
} from '@sanity/ui'
import sanityClient from 'part:@sanity/base/client'
import PatchEvent, { set, unset } from 'part:@sanity/form-builder/patch-event'
const client = sanityClient.withConfig({ apiVersion: '2021-06-07' })
function SanityAssetCustomField({ type, parentValue, onFocus, onChange, ...rest }) {
const [value, setValue] = useState('')
const [error, setError] = useState(null)
const [currentValue, setCurrentValue] = useState(null)
const [loading, setLoading] = useState(false)
const id = parentValue?.asset?._ref
const handleChange = (e) => {
setValue(e.target.value)
}
const targetAssetField = type.options?.field || type.title.toLowerCase()
useEffect(() => {
if (id && !loading && currentValue === null) {
setLoading(true)
client
.fetch(
`*[_type in ['sanity.imageAsset', 'sanity.fileAsset'] && _id == '${id}'][0].${targetAssetField}`
)
.then((resp) => {
setCurrentValue(resp || '')
setValue(resp || '')
setLoading(false)
})
}
}, [id, loading, currentValue, targetAssetField])
const handleUpdateValue = () => {
if (id) {
setLoading(true)
client
.patch(id)
.set({ [targetAssetField]: value })
.commit()
.then((resp) => {
setCurrentValue(resp[targetAssetField])
setLoading(false)
onChange(
PatchEvent.from(
!resp[targetAssetField] || resp[targetAssetField] === '' ? unset() : set(resp[targetAssetField])
)
)
})
.catch((err) => {
console.error('Error updating value: ', err.message)
setError(`An error occured updating the value (${err.message})`)
setLoading(false)
})
}
}
return (
<Stack space={1}>
<Label size={1} style={{ marginBottom: 10 }}>
{type.title}
</Label>
{error && (
<Card padding={2} radius={2} shadow={1} tone="critical">
<Text align="center" size={1}>
{error}
</Text>
</Card>
)}
{!parentValue?.asset?._ref ? (
<Card padding={2} radius={2} shadow={1}>
<Text align="center" size={1}>
You must select an asset first
</Text>
</Card>
) : loading ? (
<Spinner muted />
) : (
<Flex align="center">
<Card flex={2}>
<TextInput
type="text"
onChange={handleChange}
value={value || ''}
placeholder={currentValue}
/>
</Card>
{value !== currentValue && (
<Button
mode="ghost"
type="button"
onClick={handleUpdateValue}
onFocus={onFocus}
text={'Save changes'}
/>
)}
</Flex>
)}
</Stack>
)
}
// eslint-disable-next-line react/display-name
export default forwardRef((props, ref) => (
<SanityAssetCustomField {...props} forwardedRef={ref} />
))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment