-
Notifications
You must be signed in to change notification settings - Fork 782
Expand file tree
/
Copy pathnumber-input.tsx
More file actions
126 lines (113 loc) · 3.42 KB
/
Copy pathnumber-input.tsx
File metadata and controls
126 lines (113 loc) · 3.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import * as React from "react";
import { useEffect, useState } from "react";
import type { InputProps } from "ra-core";
import { FieldTitle, useInput, useResourceContext } from "ra-core";
import { FormControl, FormField, FormLabel } from "@/components/admin/form";
import { Input } from "@/components/ui/input";
import { FormError } from "@/components/admin/form";
import { InputHelperText } from "@/components/admin/input-helper-text";
/**
* Input component for numeric values (integers and floats) with parsing and formatting support.
*
* Use `<NumberInput>` for prices, quantities, counts, or any numeric field. Manages a local string
* state internally so users can type incomplete numbers (e.g. '-' or '0.') before the value is parsed.
* Supports min/max constraints and step increments.
*
* @see {@link https://marmelab.com/shadcn-admin-kit/docs/numberinput/ NumberInput documentation}
*
* @example
* import { Edit, SimpleForm, NumberInput, TextInput } from '@/components/admin';
*
* const ProductEdit = () => (
* <Edit>
* <SimpleForm>
* <TextInput source="name" />
* <NumberInput source="price" step={0.01} min={0} />
* <NumberInput source="quantity" min={0} />
* </SimpleForm>
* </Edit>
* );
*/
export const NumberInput = (props: NumberInputProps) => {
const {
label,
source,
className,
resource: resourceProp,
validate: _validateProp,
format: _formatProp,
parse = convertStringToNumber,
onFocus,
helperText,
...rest
} = props;
const resource = useResourceContext({ resource: resourceProp });
const { id, field, isRequired } = useInput(props);
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const value = event.target.value;
const numberValue = parse(value);
setValue(value);
field.onChange(numberValue ?? 0);
};
const [value, setValue] = useState<string | undefined>(
field.value?.toString() ?? "",
);
const hasFocus = React.useRef(false);
const handleFocus = (event: React.FocusEvent<HTMLInputElement>) => {
onFocus?.(event);
hasFocus.current = true;
};
const handleBlur = (event: React.FocusEvent<HTMLInputElement>) => {
field.onBlur?.(event);
hasFocus.current = false;
setValue(field.value?.toString() ?? "");
};
useEffect(() => {
if (!hasFocus.current) {
setValue(field.value?.toString() ?? "");
}
}, [field.value]);
return (
<FormField id={id} className={className} name={field.name}>
{label !== false && (
<FormLabel>
<FieldTitle
label={label}
source={source}
resource={resource}
isRequired={isRequired}
/>
</FormLabel>
)}
<FormControl>
<Input
{...rest}
{...field}
type="number"
value={value}
onChange={handleChange}
onFocus={handleFocus}
onBlur={handleBlur}
/>
</FormControl>
<InputHelperText helperText={helperText} />
<FormError />
</FormField>
);
};
export interface NumberInputProps
extends
InputProps,
Omit<
React.ComponentProps<"input">,
"defaultValue" | "onBlur" | "onChange" | "type"
> {
parse?: (value: string) => number;
}
const convertStringToNumber = (value?: string | null) => {
if (value == null || value === "") {
return null;
}
const float = parseFloat(value);
return isNaN(float) ? 0 : float;
};