Spaces:
Sleeping
Sleeping
| import { useState } from "react"; | |
| import { Button } from "@/components/ui/button"; | |
| import { Input } from "@/components/ui/input"; | |
| import { Minus, Plus } from "lucide-react"; | |
| import { cn } from "@/lib/utils"; | |
| interface NumericInputProps { | |
| value: number; | |
| onChange: (value: number) => void; | |
| min?: number; | |
| max?: number; | |
| step?: number; | |
| showSteppers?: boolean; | |
| disabled?: boolean; | |
| placeholder?: string; | |
| className?: string; | |
| quickValues?: number[]; | |
| label?: string; | |
| } | |
| export function NumericInput({ | |
| value, | |
| onChange, | |
| min = 0, | |
| max, | |
| step = 1, | |
| showSteppers = true, | |
| disabled = false, | |
| placeholder = "0", | |
| className, | |
| quickValues, | |
| label | |
| }: NumericInputProps) { | |
| const [isFocused, setIsFocused] = useState(false); | |
| const handleDecrement = () => { | |
| const newValue = value - step; | |
| if (newValue >= min) { | |
| onChange(newValue); | |
| } | |
| }; | |
| const handleIncrement = () => { | |
| const newValue = value + step; | |
| if (!max || newValue <= max) { | |
| onChange(newValue); | |
| } | |
| }; | |
| const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { | |
| const newValue = parseFloat(e.target.value) || 0; | |
| if (newValue >= min && (!max || newValue <= max)) { | |
| onChange(newValue); | |
| } | |
| }; | |
| const handleQuickValue = (quickValue: number) => { | |
| onChange(quickValue); | |
| }; | |
| return ( | |
| <div className="space-y-2"> | |
| {label && ( | |
| <label className="text-sm font-medium">{label}</label> | |
| )} | |
| <div className={cn("flex items-center gap-2", className)}> | |
| {showSteppers && ( | |
| <Button | |
| type="button" | |
| size="icon" | |
| variant="outline" | |
| className="h-12 w-12 touch-manipulation shrink-0" | |
| onClick={handleDecrement} | |
| disabled={disabled || value <= min} | |
| > | |
| <Minus className="h-5 w-5" /> | |
| </Button> | |
| )} | |
| <div className="relative flex-1"> | |
| <Input | |
| type="number" | |
| inputMode="numeric" | |
| value={value || ""} | |
| onChange={handleChange} | |
| onFocus={() => setIsFocused(true)} | |
| onBlur={() => setIsFocused(false)} | |
| disabled={disabled} | |
| placeholder={placeholder} | |
| min={min} | |
| max={max} | |
| step={step} | |
| className={cn( | |
| "h-12 text-center text-lg font-semibold", | |
| "touch-manipulation", | |
| isFocused && "ring-2 ring-primary" | |
| )} | |
| /> | |
| </div> | |
| {showSteppers && ( | |
| <Button | |
| type="button" | |
| size="icon" | |
| variant="outline" | |
| className="h-12 w-12 touch-manipulation shrink-0" | |
| onClick={handleIncrement} | |
| disabled={disabled || (max !== undefined && value >= max)} | |
| > | |
| <Plus className="h-5 w-5" /> | |
| </Button> | |
| )} | |
| </div> | |
| {quickValues && quickValues.length > 0 && ( | |
| <div className="flex flex-wrap gap-2"> | |
| {quickValues.map((quickValue) => ( | |
| <Button | |
| key={quickValue} | |
| type="button" | |
| variant="outline" | |
| size="sm" | |
| className="h-10 px-4 touch-manipulation" | |
| onClick={() => handleQuickValue(quickValue)} | |
| > | |
| {quickValue} | |
| </Button> | |
| ))} | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| } | |
| // Currency input variant | |
| interface CurrencyInputProps extends Omit<NumericInputProps, 'showSteppers' | 'quickValues'> { | |
| currency?: string; | |
| showSteppers?: boolean; | |
| largeStep?: number; | |
| } | |
| export function CurrencyInput({ | |
| value, | |
| onChange, | |
| min = 0, | |
| max, | |
| step = 100, | |
| largeStep = 1000, | |
| disabled = false, | |
| placeholder = "0", | |
| className, | |
| currency = "₹", | |
| label, | |
| showSteppers = true | |
| }: CurrencyInputProps) { | |
| const quickValues = [1000, 5000, 10000, 25000, 50000, 100000]; | |
| return ( | |
| <div className="space-y-2"> | |
| {label && ( | |
| <label className="text-sm font-medium">{label}</label> | |
| )} | |
| <div className="flex items-center gap-2"> | |
| {showSteppers && ( | |
| <Button | |
| type="button" | |
| size="icon" | |
| variant="outline" | |
| className="h-12 w-12 touch-manipulation" | |
| onClick={() => onChange(Math.max(min, value - largeStep))} | |
| disabled={disabled || value <= min} | |
| > | |
| <Minus className="h-5 w-5" /> | |
| </Button> | |
| )} | |
| <div className="relative flex-1"> | |
| <span className="absolute left-4 top-1/2 -translate-y-1/2 text-lg font-semibold text-muted-foreground"> | |
| {currency} | |
| </span> | |
| <Input | |
| type="number" | |
| inputMode="numeric" | |
| value={value || ""} | |
| onChange={(e) => onChange(parseFloat(e.target.value) || 0)} | |
| disabled={disabled} | |
| placeholder={placeholder} | |
| min={min} | |
| max={max} | |
| step={step} | |
| className={cn( | |
| "h-12 pl-10 pr-4 text-center text-lg font-semibold", | |
| "touch-manipulation", | |
| className | |
| )} | |
| /> | |
| </div> | |
| {showSteppers && ( | |
| <Button | |
| type="button" | |
| size="icon" | |
| variant="outline" | |
| className="h-12 w-12 touch-manipulation" | |
| onClick={() => onChange(max ? Math.min(max, value + largeStep) : value + largeStep)} | |
| disabled={disabled || (max !== undefined && value >= max)} | |
| > | |
| <Plus className="h-5 w-5" /> | |
| </Button> | |
| )} | |
| </div> | |
| <div className="flex flex-wrap gap-2"> | |
| {quickValues.map((quickValue) => ( | |
| <Button | |
| key={quickValue} | |
| type="button" | |
| variant="outline" | |
| size="sm" | |
| className="h-10 px-3 text-xs touch-manipulation" | |
| onClick={() => onChange(quickValue)} | |
| > | |
| {currency}{(quickValue / 1000).toFixed(0)}k | |
| </Button> | |
| ))} | |
| </div> | |
| </div> | |
| ); | |
| } | |