Created
June 22, 2022 18:29
-
-
Save fabien/a836ac612ce6fd8b978bf1b2fc0d4efd to your computer and use it in GitHub Desktop.
Latest Advanced Numeric Input for Sanity Studio
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.