Input Number
Um input numérico com controles de incremento e decremento
Código:
'use client';
import { cn } from '@/lib/utils';
import { cva } from 'class-variance-authority';
import { Control, Controller, FieldValues, Path } from 'react-hook-form';
import { Minus, Plus } from 'lucide-react';
import { HTMLAttributes } from 'react';
export interface InputNumberProps<TFieldValues extends FieldValues> extends HTMLAttributes<HTMLDivElement> {
name: Path<TFieldValues>;
control: Control<TFieldValues>;
min?: number;
max?: number;
step?: number;
size?: 'sm' | 'lg';
disabled?: boolean;
formatter?: (value: number) => string;
}
const inputNumberVariants = cva('flex w-max items-center justify-between bg-input rounded-xl transition-all duration-150 ease-out', {
variants: {
size: {
lg: 'px-4 py-3 gap-4',
sm: 'px-3 py-1.5 gap-3',
},
},
defaultVariants: {
size: 'lg',
},
});
const valueVariants = cva('font-medium text-foreground tabular-nums select-none', {
variants: {
size: {
lg: 'text-base min-w-[40px] text-center',
sm: 'text-sm min-w-[32px] text-center',
},
},
defaultVariants: {
size: 'lg',
},
});
export function InputNumber<TFieldValues extends FieldValues>({
name,
control,
min = 0,
max = 100,
step = 1,
size = 'lg',
formatter,
...props
}: InputNumberProps<TFieldValues>) {
return (
<Controller
name={name}
control={control}
render={({ field: { value, onChange } }) => {
const currentValue = typeof value === 'number' ? value : min;
const handleDecrement = () => {
const newValue = Math.max(min, currentValue - step);
onChange(newValue);
};
const handleIncrement = () => {
const newValue = Math.min(max, currentValue + step);
onChange(newValue);
};
const isMinDisabled = props.disabled || currentValue <= min;
const isMaxDisabled = props.disabled || currentValue >= max;
const displayedValue = formatter ? formatter(currentValue) : currentValue;
return (
<div className={cn(inputNumberVariants({ size }), props.disabled && 'cursor-not-allowed opacity-50', props.className)}>
<button type='button' onClick={handleDecrement} disabled={isMinDisabled} aria-label='Diminuir valor'>
<Minus className={size === 'lg' ? 'size-5' : 'size-4'} />
</button>
<span className={cn(valueVariants({ size }))}>{displayedValue}</span>
<button type='button' onClick={handleIncrement} disabled={isMaxDisabled} aria-label='Aumentar valor'>
<Plus className={size === 'lg' ? 'size-5' : 'size-4'} />
</button>
</div>
);
}}
/>
);
}
yarn add react-hook-form lucide-react class-variance-authority
Prévia:
0
R$ 10
5
0
Min
0
Max
15
Step
1
Disabled
Formatter (R$)
Props:
| Property | Type | Default | Description |
|---|---|---|---|
name | Path<TFieldValues> | - | Nome do campo controlado pelo react-hook-form. |
control | Control<TFieldValues> | - | Instância de controle retornada por useForm. |
min | number | 0 | Valor mínimo permitido. |
max | number | 100 | Valor máximo permitido. |
step | number | 1 | Incremento/decremento aplicado a cada clique. |
size | 'sm' | 'lg' | 'lg' | Tamanho do input. |
disabled | boolean | false | Desabilita os controles de incremento e decremento. |
formatter | (value: number) => string | - | Função para formatar o valor exibido (ex: moeda). |
