| 
									
										
										
										
											2024-12-26 12:01:51 +08:00
										 |  |  | import type { FC } from 'react' | 
					
						
							|  |  |  | import { RiArrowDownSLine, RiArrowUpSLine } from '@remixicon/react' | 
					
						
							|  |  |  | import Input, { type InputProps } from '../input' | 
					
						
							|  |  |  | import classNames from '@/utils/classnames' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export type InputNumberProps = { | 
					
						
							|  |  |  |   unit?: string | 
					
						
							|  |  |  |   value?: number | 
					
						
							|  |  |  |   onChange: (value?: number) => void | 
					
						
							|  |  |  |   amount?: number | 
					
						
							| 
									
										
										
										
											2025-04-18 15:54:22 +08:00
										 |  |  |   size?: 'regular' | 'large' | 
					
						
							| 
									
										
										
										
											2024-12-26 12:01:51 +08:00
										 |  |  |   max?: number | 
					
						
							|  |  |  |   min?: number | 
					
						
							|  |  |  |   defaultValue?: number | 
					
						
							| 
									
										
										
										
											2025-03-18 10:42:29 +08:00
										 |  |  |   disabled?: boolean | 
					
						
							| 
									
										
										
										
											2025-03-18 11:01:06 +08:00
										 |  |  |   wrapClassName?: string | 
					
						
							|  |  |  |   controlWrapClassName?: string | 
					
						
							|  |  |  |   controlClassName?: string | 
					
						
							| 
									
										
										
										
											2024-12-26 12:01:51 +08:00
										 |  |  | } & Omit<InputProps, 'value' | 'onChange' | 'size' | 'min' | 'max' | 'defaultValue'> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export const InputNumber: FC<InputNumberProps> = (props) => { | 
					
						
							| 
									
										
										
										
											2025-04-18 15:54:22 +08:00
										 |  |  |   const { unit, className, onChange, amount = 1, value, size = 'regular', max, min, defaultValue, wrapClassName, controlWrapClassName, controlClassName, disabled, ...rest } = props | 
					
						
							| 
									
										
										
										
											2024-12-26 12:01:51 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   const isValidValue = (v: number) => { | 
					
						
							| 
									
										
										
										
											2025-04-18 15:54:22 +08:00
										 |  |  |     if (typeof max === 'number' && v > max) | 
					
						
							| 
									
										
										
										
											2024-12-26 12:01:51 +08:00
										 |  |  |       return false | 
					
						
							| 
									
										
										
										
											2025-04-18 15:54:22 +08:00
										 |  |  |     return !(typeof min === 'number' && v < min) | 
					
						
							| 
									
										
										
										
											2024-12-26 12:01:51 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const inc = () => { | 
					
						
							| 
									
										
										
										
											2025-03-18 10:42:29 +08:00
										 |  |  |     if (disabled) return | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-26 12:01:51 +08:00
										 |  |  |     if (value === undefined) { | 
					
						
							|  |  |  |       onChange(defaultValue) | 
					
						
							|  |  |  |       return | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     const newValue = value + amount | 
					
						
							|  |  |  |     if (!isValidValue(newValue)) | 
					
						
							|  |  |  |       return | 
					
						
							|  |  |  |     onChange(newValue) | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   const dec = () => { | 
					
						
							| 
									
										
										
										
											2025-03-18 10:42:29 +08:00
										 |  |  |     if (disabled) return | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-26 12:01:51 +08:00
										 |  |  |     if (value === undefined) { | 
					
						
							|  |  |  |       onChange(defaultValue) | 
					
						
							|  |  |  |       return | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     const newValue = value - amount | 
					
						
							|  |  |  |     if (!isValidValue(newValue)) | 
					
						
							|  |  |  |       return | 
					
						
							|  |  |  |     onChange(newValue) | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-18 11:01:06 +08:00
										 |  |  |   return <div className={classNames('flex', wrapClassName)}> | 
					
						
							| 
									
										
										
										
											2024-12-26 12:01:51 +08:00
										 |  |  |     <Input {...rest} | 
					
						
							|  |  |  |       // disable default controller
 | 
					
						
							|  |  |  |       type='text' | 
					
						
							|  |  |  |       className={classNames('rounded-r-none', className)} | 
					
						
							|  |  |  |       value={value} | 
					
						
							|  |  |  |       max={max} | 
					
						
							|  |  |  |       min={min} | 
					
						
							| 
									
										
										
										
											2025-03-18 10:42:29 +08:00
										 |  |  |       disabled={disabled} | 
					
						
							| 
									
										
										
										
											2024-12-26 12:01:51 +08:00
										 |  |  |       onChange={(e) => { | 
					
						
							|  |  |  |         if (e.target.value === '') | 
					
						
							|  |  |  |           onChange(undefined) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const parsed = Number(e.target.value) | 
					
						
							|  |  |  |         if (Number.isNaN(parsed)) | 
					
						
							|  |  |  |           return | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (!isValidValue(parsed)) | 
					
						
							|  |  |  |           return | 
					
						
							|  |  |  |         onChange(parsed) | 
					
						
							|  |  |  |       }} | 
					
						
							|  |  |  |       unit={unit} | 
					
						
							| 
									
										
										
										
											2025-04-18 15:54:22 +08:00
										 |  |  |       size={size} | 
					
						
							| 
									
										
										
										
											2024-12-26 12:01:51 +08:00
										 |  |  |     /> | 
					
						
							| 
									
										
										
										
											2025-03-18 10:42:29 +08:00
										 |  |  |     <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', | 
					
						
							| 
									
										
										
										
											2025-03-18 11:01:06 +08:00
										 |  |  |       controlWrapClassName)} | 
					
						
							|  |  |  |     > | 
					
						
							| 
									
										
										
										
											2025-04-18 15:54:22 +08:00
										 |  |  |       <button | 
					
						
							|  |  |  |         type='button' | 
					
						
							|  |  |  |         onClick={inc} | 
					
						
							|  |  |  |         disabled={disabled} | 
					
						
							|  |  |  |         aria-label='increment' | 
					
						
							|  |  |  |         className={classNames( | 
					
						
							|  |  |  |           size === 'regular' ? 'pt-1' : 'pt-1.5', | 
					
						
							|  |  |  |           'px-1.5 hover:bg-components-input-bg-hover', | 
					
						
							|  |  |  |           disabled && 'cursor-not-allowed hover:bg-transparent', | 
					
						
							|  |  |  |           controlClassName, | 
					
						
							|  |  |  |         )} | 
					
						
							|  |  |  |       > | 
					
						
							| 
									
										
										
										
											2024-12-26 12:01:51 +08:00
										 |  |  |         <RiArrowUpSLine className='size-3' /> | 
					
						
							|  |  |  |       </button> | 
					
						
							| 
									
										
										
										
											2025-03-18 10:42:29 +08:00
										 |  |  |       <button | 
					
						
							| 
									
										
										
										
											2025-04-18 15:54:22 +08:00
										 |  |  |         type='button' | 
					
						
							| 
									
										
										
										
											2025-03-18 10:42:29 +08:00
										 |  |  |         onClick={dec} | 
					
						
							|  |  |  |         disabled={disabled} | 
					
						
							| 
									
										
										
										
											2025-04-18 15:54:22 +08:00
										 |  |  |         aria-label='decrement' | 
					
						
							| 
									
										
										
										
											2025-03-18 10:42:29 +08:00
										 |  |  |         className={classNames( | 
					
						
							| 
									
										
										
										
											2025-04-18 15:54:22 +08:00
										 |  |  |           size === 'regular' ? 'pb-1' : 'pb-1.5', | 
					
						
							| 
									
										
										
										
											2025-03-18 10:42:29 +08:00
										 |  |  |           'px-1.5 hover:bg-components-input-bg-hover', | 
					
						
							|  |  |  |           disabled && 'cursor-not-allowed hover:bg-transparent', | 
					
						
							| 
									
										
										
										
											2025-03-18 11:01:06 +08:00
										 |  |  |           controlClassName, | 
					
						
							| 
									
										
										
										
											2025-04-18 15:54:22 +08:00
										 |  |  |         )} | 
					
						
							|  |  |  |       > | 
					
						
							| 
									
										
										
										
											2024-12-26 12:01:51 +08:00
										 |  |  |         <RiArrowDownSLine className='size-3' /> | 
					
						
							|  |  |  |       </button> | 
					
						
							|  |  |  |     </div> | 
					
						
							|  |  |  |   </div> | 
					
						
							|  |  |  | } |