63 lines
2.1 KiB
TypeScript
63 lines
2.1 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
|
|
/** عددِ گروهبندیشده با جداکنندهی هزارگان (مثلِ 12,500). */
|
|
export function fmtNum(n: number | null | undefined): string {
|
|
const v = Number(n ?? 0);
|
|
return Number.isFinite(v) ? v.toLocaleString("en-US") : "0";
|
|
}
|
|
|
|
/**
|
|
* ورودیِ عددی با جداکنندهی هزارگان که با کیبورد هم قابلِ تایپ است.
|
|
* برخلافِ <input type="number"> که جداکننده را نمیپذیرد، این یک ورودیِ متنی است
|
|
* که هنگامِ تایپ زنده گروهبندی میکند و مقدارِ عددی را به onChange میدهد.
|
|
*/
|
|
export default function NumberInput({
|
|
value,
|
|
onChange,
|
|
allowNegative = false,
|
|
className = "inp",
|
|
style,
|
|
disabled,
|
|
}: {
|
|
value: number;
|
|
onChange: (n: number) => void;
|
|
allowNegative?: boolean;
|
|
className?: string;
|
|
style?: React.CSSProperties;
|
|
disabled?: boolean;
|
|
}) {
|
|
const show = (n: number) => n.toLocaleString("en-US");
|
|
const [text, setText] = useState(show(value));
|
|
|
|
// اگر مقدار از بیرون عوض شد (مثلاً ریست فرم)، متن را همگام کن.
|
|
useEffect(() => {
|
|
const cur = parseInt(text.replace(/[^\d-]/g, "") || "0", 10) || 0;
|
|
if (cur !== value) setText(show(value));
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [value]);
|
|
|
|
return (
|
|
<input
|
|
className={className}
|
|
style={style}
|
|
type="text"
|
|
inputMode={allowNegative ? "text" : "numeric"}
|
|
dir="ltr"
|
|
value={text}
|
|
disabled={disabled}
|
|
onChange={(e) => {
|
|
let raw = e.target.value.replace(/[^\d-]/g, "");
|
|
if (!allowNegative) raw = raw.replace(/-/g, "");
|
|
raw = raw.replace(/(?!^)-/g, ""); // فقط منفیِ ابتدایی
|
|
const n = raw === "" || raw === "-" ? 0 : parseInt(raw, 10);
|
|
const grouped =
|
|
raw === "" ? "" : raw === "-" ? "-" : (n || 0).toLocaleString("en-US");
|
|
setText(grouped);
|
|
onChange(Number.isNaN(n) ? 0 : n);
|
|
}}
|
|
/>
|
|
);
|
|
}
|