Christian Garrison
Software Engineer
Christian is located at Joplin, Missouri
Prefix slugs for catch all routes
import { useId } from '@reach/auto-id';
import { ChangeIndicatorCompareValueProvider, FormField } from '@sanity/base/components';
import PatchEvent, { set, unset } from '@sanity/form-builder/PatchEvent';
import { Box, Button, Flex, TextInput } from '@sanity/ui';
import { withDocument } from 'part:@sanity/form-builder';
import PropTypes from 'prop-types';
import React from 'react';
import slugify from 'slugify';
import styled from 'styled-components';
const SlugBox = styled(Flex)`
align-items: center;
width: 100%;
& > span:nth-of-type(2) {
flex: 1;
}
`;
const SlugPrefix = styled(TextInput)`
border-right: 0;
border-bottom-right-radius: 0;
border-top-right-radius: 0;
overflow: hidden;
text-overflow: ellipsis;
padding-right: 0;
`;
const SlugInput = styled(TextInput)`
border-left: 0;
border-bottom-left-radius: 0;
border-top-left-radius: 0;
`;
const PrefixedSlugInput = React.forwardRef((props, ref) => {
const {
document, // The document being edited
level, // The nesting level of the field
compareValue, // The value of the field in the current document
type, // Schema information
value, // Current field value
readOnly, // Boolean if field is not editable
markers, // Markers including validation rules
presence, // Presence information for collaborative avatars
onFocus, // Method to handle focus state
onBlur, // Method to handle blur state,
onChange, // Method to handle patch events
} = props;
const inputId = useId();
const prefix = type?.options?.prefix ? `${type.options.prefix}/` : `${document?._type}/`;
const handleChange = React.useCallback(
(event) => {
const slug = slugify(event.currentTarget.value, {
lower: true,
strict: true,
});
onChange(
PatchEvent.from(slug ? set({ _type: 'slug', current: `${prefix}${slug}` }) : unset()),
);
},
[onChange, prefix],
);
const handleGenerate = React.useCallback(() => {
const inputSource = type?.options?.source;
const source = inputSource ? document?.[inputSource] : document?.title;
const slug = slugify(source, {
lower: true,
strict: true,
});
onChange(PatchEvent.from(set({ _type: 'slug', current: `${prefix}${slug}` })));
}, [document, onChange, type, prefix]);
return (
<FormField
title={type.title}
description={type.description}
level={level}
compareValue={compareValue}
__unstable_markers={markers}
__unstable_presence={presence}
__unstable_changeIndicator={false}
inputId={inputId}
>
<ChangeIndicatorCompareValueProvider value={value} compareValue={compareValue}>
<Flex align='center'>
<SlugBox>
<SlugPrefix value={prefix} readOnly />
<SlugInput
id={inputId}
value={value?.current?.replace(prefix, '') || ''}
onChange={handleChange}
readOnly={readOnly}
onFocus={onFocus}
onBlur={onBlur}
ref={ref}
/>
</SlugBox>
<Box marginLeft={1}>
<Button
mode='ghost'
type='button'
disabled={readOnly}
text='Generate'
onClick={handleGenerate}
/>
</Box>
</Flex>
</ChangeIndicatorCompareValueProvider>
</FormField>
);
});
PrefixedSlugInput.propTypes = {
document: PropTypes.shape({
_type: PropTypes.string,
title: PropTypes.string,
}),
level: PropTypes.number,
compareValue: PropTypes.shape({
current: PropTypes.string,
}),
type: PropTypes.shape({
title: PropTypes.string,
description: PropTypes.string,
options: PropTypes.shape({
prefix: PropTypes.string,
source: PropTypes.string,
}),
}),
value: PropTypes.shape({
current: PropTypes.string,
}),
readOnly: PropTypes.bool,
markers: PropTypes.arrayOf(
PropTypes.shape({
type: PropTypes.string,
}),
),
presence: PropTypes.arrayOf(
PropTypes.shape({
path: PropTypes.arrayOf(PropTypes.string),
}),
),
onFocus: PropTypes.func,
onBlur: PropTypes.func,
onChange: PropTypes.func,
};
export default withDocument(PrefixedSlugInput);
import PrefixedSlugInput from '../../src/components/PrefixedSlugInput';
export default {
name: 'blog',
title: 'Blog',
type: 'document',
fields: [
{
name: 'title',
title: 'Title',
type: 'string',
validation: (Rule) => Rule.required().error('A title is required'),
},
{
name: 'slug',
title: 'Slug',
type: 'slug',
inputComponent: PrefixedSlugInput,
options: {
source: 'title',
maxLength: 96,
prefix: 'news', // "news/my-slug" instead of "document._type/my-slug"
},
validation: (Rule) => Rule.required().error('A slug is required'),
},
{
name: 'author',
title: 'Author',
type: 'reference',
to: { type: 'person' },
},
],
};
Software Engineer