pattanshetti / src /components /NumericInput.tsx
triflix's picture
Upload 99 files
4be2b2b verified
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>
);
}