diff --git a/.changeset/ninety-cats-pull.md b/.changeset/ninety-cats-pull.md new file mode 100644 index 000000000..36cc8addf --- /dev/null +++ b/.changeset/ninety-cats-pull.md @@ -0,0 +1,5 @@ +--- +"@radui/ui": patch +--- + +AddedsnapOnStep, smallStep support for NumberField diff --git a/src/components/ui/NumberField/contexts/NumberFieldContext.tsx b/src/components/ui/NumberField/contexts/NumberFieldContext.tsx index b2ffc4594..6a45fd59a 100644 --- a/src/components/ui/NumberField/contexts/NumberFieldContext.tsx +++ b/src/components/ui/NumberField/contexts/NumberFieldContext.tsx @@ -2,14 +2,15 @@ import React from 'react'; export type NumberFieldContextType = { inputValue: number|''; - handleOnChange: (input: number|'') => void; - handleStep: (opts: { direction: 'increment' | 'decrement'; type: 'small' | 'large' }) => void; + handleOnChange: (input: number | '') => void; + handleStep: (opts: { direction: 'increment' | 'decrement'; type: 'small' | 'normal' | 'large' }) => void; id?: string; name?: string; disabled?: boolean; readOnly?: boolean; required?: boolean; rootClass?: string; + locale?: string; }; const NumberFieldContext = React.createContext(null); diff --git a/src/components/ui/NumberField/fragments/NumberFieldDecrement.tsx b/src/components/ui/NumberField/fragments/NumberFieldDecrement.tsx index d578a5ad9..80d7af3c1 100644 --- a/src/components/ui/NumberField/fragments/NumberFieldDecrement.tsx +++ b/src/components/ui/NumberField/fragments/NumberFieldDecrement.tsx @@ -15,7 +15,7 @@ const NumberFieldDecrement = forwardRef handleStep({ direction: 'decrement', type: 'small' })} + onClick={() => handleStep({ direction: 'decrement', type: 'normal' })} className={clsx(`${rootClass}-decrement`, className)} disabled={disabled || readOnly} type="button" diff --git a/src/components/ui/NumberField/fragments/NumberFieldIncrement.tsx b/src/components/ui/NumberField/fragments/NumberFieldIncrement.tsx index 3cbd34033..8c2a2c609 100644 --- a/src/components/ui/NumberField/fragments/NumberFieldIncrement.tsx +++ b/src/components/ui/NumberField/fragments/NumberFieldIncrement.tsx @@ -16,7 +16,7 @@ const NumberFieldIncrement = forwardRef handleStep({ direction: 'increment', type: 'small' })} + onClick={() => handleStep({ direction: 'increment', type: 'normal' })} className={clsx(`${rootClass}-increment`, className)} disabled={disabled || readOnly} type="button" diff --git a/src/components/ui/NumberField/fragments/NumberFieldInput.tsx b/src/components/ui/NumberField/fragments/NumberFieldInput.tsx index b52c20bbf..f5773edd3 100644 --- a/src/components/ui/NumberField/fragments/NumberFieldInput.tsx +++ b/src/components/ui/NumberField/fragments/NumberFieldInput.tsx @@ -20,17 +20,18 @@ const NumberFieldInput = forwardRef) => { if (event.key === 'ArrowUp' && !event.shiftKey) { event.preventDefault(); - handleStep({ direction: 'increment', type: 'small' }); + handleStep({ direction: 'increment', type: 'normal' }); } if (event.key === 'ArrowDown' && !event.shiftKey) { event.preventDefault(); - handleStep({ direction: 'decrement', type: 'small' }); + handleStep({ direction: 'decrement', type: 'normal' }); } if (event.key === 'ArrowUp' && event.shiftKey) { event.preventDefault(); @@ -40,13 +41,21 @@ const NumberFieldInput = forwardRef { const val = e.target.value; handleOnChange(val === '' ? '' : Number(val)); }} id={id} name={name} diff --git a/src/components/ui/NumberField/fragments/NumberFieldRoot.tsx b/src/components/ui/NumberField/fragments/NumberFieldRoot.tsx index 0110ce7b2..e03f30df9 100644 --- a/src/components/ui/NumberField/fragments/NumberFieldRoot.tsx +++ b/src/components/ui/NumberField/fragments/NumberFieldRoot.tsx @@ -14,6 +14,9 @@ export type NumberFieldRootProps = { onValueChange?: (value: number | '') => void step?: number largeStep?: number + smallStep?: number + snapOnStep?: boolean + locale?: string min?: number max?: number disabled?: boolean @@ -21,14 +24,14 @@ export type NumberFieldRootProps = { required?: boolean } & ComponentPropsWithoutRef<'div'>; -const NumberFieldRoot = forwardRef(({ children, name, defaultValue = '', value, onValueChange, largeStep, step, min, max, disabled, readOnly, required, id, className, ...props }, ref) => { +const NumberFieldRoot = forwardRef(({ children, name, defaultValue = '', value, onValueChange, largeStep = 10, step = 1, smallStep = 0.1, snapOnStep = false, locale, min, max, disabled, readOnly, required, id, className, ...props }, ref) => { const rootClass = customClassSwitcher(className, COMPONENT_NAME); const [inputValue, setInputValue] = useControllableState( value, defaultValue, onValueChange); - const handleOnChange = (input: number| '') => { + const handleOnChange = (input: number | '') => { if (input === '') { setInputValue(''); return; @@ -47,33 +50,46 @@ const NumberFieldRoot = forwardRef }; const applyStep = (amount: number) => { setInputValue((prev) => { - let temp = prev; - if (temp === '') { - if (min !== undefined) { - temp = min; - } else { - temp = -1; - } + let temp: number; + let nextValue: number; + + // Handle empty input + if (prev === '' || prev === null) { + temp = min !== undefined ? min : 0; + } else { + temp = Number(prev); } - const nextValue = temp + amount; - if (max !== undefined && nextValue > max) { - return max; + // Round to nearest step + if (temp % largeStep != 0 && snapOnStep && largeStep === Math.abs(amount)) { + temp = Math.round(temp / largeStep) * largeStep; } - if (min !== undefined && nextValue < min) { - return min; - } + const amountDecimals = (amount.toString().split('.')[1] || '').length; + const tempDecimals = (temp.toString().split('.')[1] || '').length; + const decimal = amountDecimals > tempDecimals ? amountDecimals : tempDecimals; + + // multiply temp and amount with same decimal count to remove decimal places + nextValue = (temp * Math.pow(10, decimal)) + (amount * Math.pow(10, decimal)); + + // divide nextValue by same decimal count + nextValue = nextValue / Math.pow(10, decimal); + const factor = Math.pow(10, decimal); + nextValue = (Math.round(temp * factor) + Math.round(amount * factor)) / factor; + + // Clamp to min/max + if (max !== undefined && nextValue > max) return max; + if (min !== undefined && nextValue < min) return min; return nextValue; }); }; - const handleStep = ({ type, direction } : {type: 'small'| 'large', direction: 'increment' | 'decrement' }) => { + const handleStep = ({ type, direction } : {type: 'small'| 'normal' | 'large', direction: 'increment' | 'decrement' }) => { let amount = 0; switch (type) { - case 'small': + case 'normal': if (!step) return; amount = step; break; @@ -81,10 +97,15 @@ const NumberFieldRoot = forwardRef if (!largeStep) return; amount = largeStep; break; + case 'small': + if (!smallStep) return; + amount = smallStep; + break; } if (direction === 'decrement') { - amount *= -1; + amount = -amount; + console.log(amount); } applyStep(amount); @@ -99,6 +120,7 @@ const NumberFieldRoot = forwardRef disabled, readOnly, required, + locale, rootClass }; diff --git a/src/components/ui/NumberField/stories/NumberField.stories.tsx b/src/components/ui/NumberField/stories/NumberField.stories.tsx index 69ecd8feb..89d2827fe 100644 --- a/src/components/ui/NumberField/stories/NumberField.stories.tsx +++ b/src/components/ui/NumberField/stories/NumberField.stories.tsx @@ -21,7 +21,7 @@ export const Controlled = () => { const [value, setValue] = React.useState(3); return ( - + - +