Skip to content

Instantly share code, notes, and snippets.

@fabien
Created June 22, 2022 18:29
Show Gist options
  • Save fabien/a836ac612ce6fd8b978bf1b2fc0d4efd to your computer and use it in GitHub Desktop.
Save fabien/a836ac612ce6fd8b978bf1b2fc0d4efd to your computer and use it in GitHub Desktop.
Latest Advanced Numeric Input for Sanity Studio
import React, { useState, useCallback, useMemo, useEffect } from 'react';
import { useId } from '@reach/auto-id';
import omit from 'lodash.omit';
import pick from 'lodash.pick';
import { FormField } from '@sanity/base/components';
import { TextInput } from '@sanity/ui';
import PatchEvent, { set } from 'part:@sanity/form-builder/patch-event';
import NumberFormat from 'react-number-format';
const INPUT_PROPS = [
'value',
'onChange',
'onKeyDown',
'onMouseUp',
'onFocus',
'onBlur',
];
const NUMBER_FORMAT_PROPS = [
'thousandSeparator',
'decimalSeparator',
'allowedDecimalSeparators',
'thousandsGroupStyle',
'decimalScale',
'fixedDecimalScale',
'displayType',
'prefix',
'suffix',
'format',
'removeFormatting',
'mask',
'value',
'defaultValue',
'isNumericString',
'customInput',
'allowNegative',
'allowEmptyFormatting',
'allowLeadingZeros',
'onValueChange',
'onKeyDown',
'onMouseUp',
'onChange',
'onFocus',
'onBlur',
'type',
'isAllowed',
'renderText',
'getInputRef',
'customNumerals',
];
const NumberInput = (props) => {
const { id, type, markers, readOnly, forwardedRef } = props;
const forwardedProps = pick(props, INPUT_PROPS);
const errors = useMemo(() => {
return markers.filter(
(marker) => marker.type === 'validation' && marker.level === 'error'
);
}, [markers]);
return (
<TextInput
id={id}
ref={forwardedRef}
customValidity={errors && errors.length > 0 ? errors[0].item.message : ''}
readOnly={Boolean(readOnly)}
placeholder={type.placeholder}
inputMode={props.isDecimal ? 'decimal' : 'numeric'}
{...forwardedProps}
/>
);
};
const NumericInput = React.forwardRef((props, forwardedRef) => {
const { type, markers, level, presence, onChange } = props;
const numberFormatProps = {
allowEmptyFormatting: true,
allowedDecimalSeparators: ['.', ','],
...pick(type.options, NUMBER_FORMAT_PROPS),
};
const forwardedProps = omit(props, ['value', 'onChange']);
// Use local state and effects to prevent issue with
// NumberFormat triggering additional onChangeValue
// calls, causing unwanted patches (reverting values).
const [value, setValue] = useState(props.value ?? 0);
const onValueChange = useCallback(
({ floatValue }) => {
setValue(floatValue);
if (props.value !== floatValue) {
onChange(PatchEvent.from(set(Number(floatValue))));
}
},
[onChange, props.value]
);
// Required for external changes, like reverting a document.
useEffect(() => {
if (props.value !== value) {
setValue(typeof props.value === 'undefined' ? 0 : props.value);
}
}, [props.value, value]);
const id = useId();
return (
<FormField
inputId={id}
level={level}
title={type.title}
description={type.description}
__unstable_markers={markers}
__unstable_presence={presence}
>
<NumberFormat
id={id}
forwardedRef={forwardedRef}
customInput={NumberInput}
value={value}
onValueChange={onValueChange}
isDecimal={numberFormatProps.decimalScale !== 0}
{...forwardedProps}
{...numberFormatProps}
/>
</FormField>
);
});
export default NumericInput;
@ask4clarity
Copy link

Any interest in updating this for use in Sanity v3? I found your article helpful but I'm having trouble migrating to the new setup for custom inputs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment