| | |
| | | value?: number |
| | | onChange: (value?: number) => void |
| | | amount?: number |
| | | size?: 'regular' | 'large' |
| | | size?: 'sm' | 'md' |
| | | max?: number |
| | | min?: number |
| | | defaultValue?: number |
| | | disabled?: boolean |
| | | wrapClassName?: string |
| | | controlWrapClassName?: string |
| | | controlClassName?: string |
| | | } & Omit<InputProps, 'value' | 'onChange' | 'size' | 'min' | 'max' | 'defaultValue'> |
| | | |
| | | export const InputNumber: FC<InputNumberProps> = (props) => { |
| | | const { unit, className, onChange, amount = 1, value, size = 'regular', max, min, defaultValue, wrapClassName, controlWrapClassName, controlClassName, disabled, ...rest } = props |
| | | const { unit, className, onChange, amount = 1, value, size = 'md', max, min, defaultValue, ...rest } = props |
| | | |
| | | const isValidValue = (v: number) => { |
| | | if (typeof max === 'number' && v > max) |
| | | if (max && v > max) |
| | | return false |
| | | return !(typeof min === 'number' && v < min) |
| | | if (min && v < min) |
| | | return false |
| | | return true |
| | | } |
| | | |
| | | const inc = () => { |
| | | if (disabled) return |
| | | |
| | | if (value === undefined) { |
| | | onChange(defaultValue) |
| | | return |
| | |
| | | onChange(newValue) |
| | | } |
| | | const dec = () => { |
| | | if (disabled) return |
| | | |
| | | if (value === undefined) { |
| | | onChange(defaultValue) |
| | | return |
| | |
| | | onChange(newValue) |
| | | } |
| | | |
| | | return <div className={classNames('flex', wrapClassName)}> |
| | | return <div className='flex'> |
| | | <Input {...rest} |
| | | // disable default controller |
| | | type='text' |
| | |
| | | value={value} |
| | | max={max} |
| | | min={min} |
| | | disabled={disabled} |
| | | onChange={(e) => { |
| | | if (e.target.value === '') |
| | | onChange(undefined) |
| | |
| | | onChange(parsed) |
| | | }} |
| | | unit={unit} |
| | | size={size} |
| | | /> |
| | | <div className={classNames( |
| | | 'flex flex-col bg-components-input-bg-normal rounded-r-md border-l border-divider-subtle text-text-tertiary focus:shadow-xs', |
| | | disabled && 'opacity-50 cursor-not-allowed', |
| | | controlWrapClassName)} |
| | | > |
| | | <button |
| | | type='button' |
| | | onClick={inc} |
| | | disabled={disabled} |
| | | aria-label='increment' |
| | | className={classNames( |
| | | size === 'regular' ? 'pt-1' : 'pt-1.5', |
| | | <div className='flex flex-col bg-components-input-bg-normal rounded-r-md border-l border-divider-subtle text-text-tertiary focus:shadow-xs'> |
| | | <button onClick={inc} className={classNames( |
| | | size === 'sm' ? 'pt-1' : 'pt-1.5', |
| | | 'px-1.5 hover:bg-components-input-bg-hover', |
| | | disabled && 'cursor-not-allowed hover:bg-transparent', |
| | | controlClassName, |
| | | )} |
| | | > |
| | | )}> |
| | | <RiArrowUpSLine className='size-3' /> |
| | | </button> |
| | | <button |
| | | type='button' |
| | | onClick={dec} |
| | | disabled={disabled} |
| | | aria-label='decrement' |
| | | className={classNames( |
| | | size === 'regular' ? 'pb-1' : 'pb-1.5', |
| | | <button onClick={dec} className={classNames( |
| | | size === 'sm' ? 'pb-1' : 'pb-1.5', |
| | | 'px-1.5 hover:bg-components-input-bg-hover', |
| | | disabled && 'cursor-not-allowed hover:bg-transparent', |
| | | controlClassName, |
| | | )} |
| | | > |
| | | )}> |
| | | <RiArrowDownSLine className='size-3' /> |
| | | </button> |
| | | </div> |