Mitchell Christ
Building SanityPress 🖤
Mitchell is located at Irvine, California
Want to add some preset buttons/chips below your text input field? Look no further!
'use client'
import { useCallback, type FormEvent } from 'react'
import { Badge, Card, Flex, Stack, Text, TextInput } from '@sanity/ui'
import type { StringInputProps, StringSchemaType } from 'sanity'
export type Preset =
| string
| {
label: string
value: string
}
export function getPreset(
preset: Preset,
property: 'label' | 'value' = 'value',
) {
return typeof preset === 'string' ? preset : preset[property]
}
export default function TextInputWithPresets({
elementProps,
prefix,
suffix,
presets,
}: {
prefix?: string
suffix?: string
presets?: Preset[]
} & StringInputProps<StringSchemaType>) {
const handleChange = useCallback(
(value: string) => {
elementProps.onChange({
currentTarget: { value },
} as FormEvent<HTMLInputElement>)
},
[elementProps.onChange],
)
return (
<Stack space={2}>
<Flex align="center" gap={1}>
{prefix && (
<Text size={1} muted>
{prefix}
</Text>
)}
<Card flex={1}>
<TextInput
{...elementProps}
onChange={(e) => handleChange(e.currentTarget.value)}
/>
</Card>
{suffix && (
<Text size={1} muted>
{suffix}
</Text>
)}
</Flex>
{presets && (
<Flex gap={1} paddingLeft={prefix ? prefix.length : undefined}>
{presets?.map((preset) => {
const presetValue = getPreset(preset)
const label = getPreset(preset, 'label')
return (
<Badge
style={{ cursor: 'pointer' }}
padding={2}
tone={
presetValue === elementProps.value ? 'primary' : 'default'
}
onClick={() => handleChange(presetValue)}
key={presetValue}
>
{label}
</Badge>
)
})}
</Flex>
)}
</Stack>
)
}
import TextInputWithPresets, {
getPreset,
type Preset,
} from '@/sanity/ui/TextInputWithPresets'
const presets: Preset[] = [
{ label: 'Tablet and below', value: '(width < 48rem)' },
{ label: 'Mobile only', value: '(width < 24rem)' },
{ label: 'Dark mode', value: '(prefers-color-scheme: dark)' },
]
export default defineType({
name: 'img',
title: 'Image',
type: 'object',
fields: [
defineField({
name: 'image',
type: 'image',
}),
defineField({
name: 'media',
title: 'Media query',
type: 'string',
placeholder: `e.g. ${presets.map((p) => getPreset(p)).join(', ')}`,
initialValue: getPreset(presets[0]),
components: {
input: (props) => (
<TextInputWithPresets
prefix="@media"
presets={presets}
{...props}
/>
),
},
}),
],
})
😒 Want more to your basic text input field?
🥂 Want to add some presets as one-click buttons just below to save a couple seconds, but still have the ability to type custom values?
✨ This single file snippet (and easy implementation) adds some additional UX improvements.
👀 Here are some examples:
***
This custom input component is included out-of-the-box in SanityPress, a fully customizable Next.js + Sanity.io + Tailwind 4 starter template. Go check that out! 🖤
Building SanityPress 🖤
Use modules to build your pages? Want to grab a link to a specific module? This schema is just for you!
Go to Adding Jump Links to Page Modules