trptk-sanity/components/WorkSlugInput.tsx

144 lines
3.7 KiB
TypeScript

import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'
import {Stack, Button, Inline} from '@sanity/ui'
import {PatchEvent, set, unset} from 'sanity'
import {SlugInput, type SlugInputProps} from 'sanity'
import {useClient, useFormValue} from 'sanity'
type PersonDoc = {name?: string}
function slugify(input: string) {
const folded = input
.normalize('NFKD')
.replace(/\p{M}+/gu, '')
.replace(/ß/g, 'ss')
.replace(/ø/g, 'o')
.replace(/đ/g, 'd')
.replace(/ł/g, 'l')
return folded
.toLowerCase()
.trim()
.replace(/[^\p{L}\p{N}]+/gu, '-')
.replace(/(^-|-$)+/g, '')
.slice(0, 96)
}
export function WorkSlugInput(props: SlugInputProps) {
const client = useClient({apiVersion: '2026-01-01'})
const title = (useFormValue(['title']) as string) || ''
const composerRef = (useFormValue(['composer', '_ref']) as string) || ''
const arrangerRef = (useFormValue(['arranger', '_ref']) as string) || ''
const [composerName, setComposerName] = useState<string>('')
const lastFetchedRef = useRef<string>('')
const [arrangerName, setArrangerName] = useState<string>('')
const lastFetchedArrangerRef = useRef<string>('')
const lastAutoSlugRef = useRef<string>('')
const desiredSource = useMemo(() => {
const parts = [composerName, title, arrangerName ? `arr ${arrangerName}` : null].filter(Boolean)
return parts.join(' ')
}, [composerName, title, arrangerName])
const generateSlug = useCallback(() => {
if (!desiredSource) return
const nextSlug = slugify(desiredSource)
lastAutoSlugRef.current = nextSlug
props.onChange(PatchEvent.from(nextSlug ? set({_type: 'slug', current: nextSlug}) : unset()))
}, [desiredSource, props.onChange])
useEffect(() => {
let alive = true
async function run() {
if (!composerRef) {
setComposerName('')
lastFetchedRef.current = ''
return
}
if (lastFetchedRef.current === composerRef) return
lastFetchedRef.current = composerRef
const doc = await client.fetch<PersonDoc>(`*[_type == "composer" && _id == $id][0]{name}`, {
id: composerRef,
})
if (!alive) return
setComposerName(doc?.name || '')
}
run()
return () => {
alive = false
}
}, [composerRef, client])
useEffect(() => {
let alive = true
async function run() {
if (!arrangerRef) {
setArrangerName('')
lastFetchedArrangerRef.current = ''
return
}
if (lastFetchedArrangerRef.current === arrangerRef) return
lastFetchedArrangerRef.current = arrangerRef
const doc = await client.fetch<PersonDoc>(`*[_type == "composer" && _id == $id][0]{name}`, {
id: arrangerRef,
})
if (!alive) return
setArrangerName(doc?.name || '')
}
run()
return () => {
alive = false
}
}, [arrangerRef, client])
useEffect(() => {
if (!composerName || !title) return
const current = props.value?.current || ''
const nextSlug = slugify(desiredSource)
if (!lastAutoSlugRef.current && current && current === nextSlug) {
lastAutoSlugRef.current = current
return
}
if (!current || current === lastAutoSlugRef.current) {
lastAutoSlugRef.current = nextSlug
props.onChange(PatchEvent.from(set({_type: 'slug', current: nextSlug})))
}
}, [composerName, arrangerName, title, desiredSource, props.value?.current, props.onChange])
return (
<Stack space={3}>
<SlugInput {...props} />
<Stack space={2}>
<Inline space={2}>
<Button
text="Generate"
mode="default"
onClick={generateSlug}
disabled={!composerName || !title}
/>
</Inline>
</Stack>
</Stack>
)
}