@phsa.tec/design-system-react 0.1.5 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phsa.tec/design-system-react",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "private": false,
5
5
  "main": "./src/index.ts",
6
6
  "scripts": {
@@ -0,0 +1,175 @@
1
+ import "@testing-library/jest-dom";
2
+ import { render, screen } from "@testing-library/react";
3
+ import userEvent from "@testing-library/user-event";
4
+ import { NumberInput } from "../number-input";
5
+
6
+ describe("NumberInput", () => {
7
+ it("should render label", () => {
8
+ render(<NumberInput label="test" data-testid="number-input" />);
9
+ const label = screen.getByTestId("number-input-label");
10
+ expect(label).toBeInTheDocument();
11
+ expect(label).toHaveTextContent("test");
12
+ });
13
+
14
+ it("should render error", () => {
15
+ render(<NumberInput error="test" data-testid="number-input" />);
16
+ const error = screen.getByTestId("number-input-error-label");
17
+ expect(error).toBeInTheDocument();
18
+ expect(error).toHaveTextContent("test");
19
+ });
20
+
21
+ it("should be disabled", () => {
22
+ render(<NumberInput disabled data-testid="number-input" />);
23
+ const input = screen.getByTestId("number-input");
24
+ expect(input).toBeDisabled();
25
+ });
26
+
27
+ it("should be required", () => {
28
+ render(<NumberInput required data-testid="number-input" label="test" />);
29
+ const label = screen.getByTestId("number-input-label");
30
+ expect(label).toHaveTextContent("test *");
31
+ });
32
+
33
+ it("should handle placeholder", () => {
34
+ render(
35
+ <NumberInput data-testid="number-input" placeholder="Digite um número" />
36
+ );
37
+ const input = screen.getByTestId("number-input");
38
+
39
+ expect(input).toHaveAttribute("placeholder", "Digite um número");
40
+ });
41
+
42
+ it("should handle name attribute", () => {
43
+ render(<NumberInput data-testid="number-input" name="amount" />);
44
+ const input = screen.getByTestId("number-input");
45
+
46
+ expect(input).toHaveAttribute("name", "amount");
47
+ });
48
+
49
+ it("should format numbers with thousands separator", async () => {
50
+ const user = userEvent.setup();
51
+ render(<NumberInput data-testid="number-input" thousandSeparator={true} />);
52
+ const input = screen.getByTestId("number-input");
53
+
54
+ await user.type(input, "1234567");
55
+
56
+ expect(input).toHaveValue("1,234,567");
57
+ });
58
+
59
+ it("should format currency values", async () => {
60
+ const user = userEvent.setup();
61
+ render(
62
+ <NumberInput
63
+ data-testid="number-input"
64
+ thousandSeparator={true}
65
+ prefix="R$ "
66
+ decimalScale={2}
67
+ fixedDecimalScale={true}
68
+ />
69
+ );
70
+ const input = screen.getByTestId("number-input");
71
+
72
+ await user.type(input, "1234.56");
73
+
74
+ expect(input).toHaveValue("R$ 1,234.56");
75
+ });
76
+
77
+ it("should limit decimal places", async () => {
78
+ const user = userEvent.setup();
79
+ render(<NumberInput data-testid="number-input" decimalScale={2} />);
80
+ const input = screen.getByTestId("number-input");
81
+
82
+ await user.type(input, "123.456789");
83
+
84
+ expect(input).toHaveValue("123.45");
85
+ });
86
+
87
+ it("should handle percentage format", async () => {
88
+ const user = userEvent.setup();
89
+ render(
90
+ <NumberInput data-testid="number-input" suffix="%" decimalScale={2} />
91
+ );
92
+ const input = screen.getByTestId("number-input");
93
+
94
+ await user.type(input, "15.75");
95
+
96
+ expect(input).toHaveValue("15.75%");
97
+ });
98
+
99
+ it("should call onChange with synthetic event", async () => {
100
+ const user = userEvent.setup();
101
+ const mockOnChange = jest.fn();
102
+ render(
103
+ <NumberInput
104
+ data-testid="number-input"
105
+ onChange={mockOnChange}
106
+ name="testInput"
107
+ />
108
+ );
109
+ const input = screen.getByTestId("number-input");
110
+
111
+ await user.type(input, "123");
112
+
113
+ expect(mockOnChange).toHaveBeenCalled();
114
+ const lastCall =
115
+ mockOnChange.mock.calls[mockOnChange.mock.calls.length - 1][0];
116
+ expect(lastCall.target.name).toBe("testInput");
117
+ expect(lastCall.target.value).toBe("123");
118
+ });
119
+
120
+ it("should call onValueChange with formatted data", async () => {
121
+ const user = userEvent.setup();
122
+ const mockOnValueChange = jest.fn();
123
+ render(
124
+ <NumberInput
125
+ data-testid="number-input"
126
+ onValueChange={mockOnValueChange}
127
+ thousandSeparator={true}
128
+ />
129
+ );
130
+ const input = screen.getByTestId("number-input");
131
+
132
+ await user.type(input, "1234");
133
+
134
+ expect(mockOnValueChange).toHaveBeenCalled();
135
+ const lastCall =
136
+ mockOnValueChange.mock.calls[mockOnValueChange.mock.calls.length - 1][0];
137
+ expect(lastCall.value).toBe("1234");
138
+ expect(lastCall.floatValue).toBe(1234);
139
+ expect(lastCall.formattedValue).toBe("1,234");
140
+ });
141
+
142
+ it("should handle negative numbers", async () => {
143
+ const user = userEvent.setup();
144
+ render(<NumberInput data-testid="number-input" allowNegative={true} />);
145
+ const input = screen.getByTestId("number-input");
146
+
147
+ await user.type(input, "-123");
148
+
149
+ expect(input).toHaveValue("-123");
150
+ });
151
+
152
+ it("should prevent negative numbers when allowNegative is false", async () => {
153
+ const user = userEvent.setup();
154
+ render(<NumberInput data-testid="number-input" allowNegative={false} />);
155
+ const input = screen.getByTestId("number-input");
156
+
157
+ await user.type(input, "-123");
158
+
159
+ expect(input).toHaveValue("123");
160
+ });
161
+
162
+ it("should handle controlled value", () => {
163
+ render(<NumberInput data-testid="number-input" value="1000" />);
164
+ const input = screen.getByTestId("number-input");
165
+
166
+ expect(input).toHaveValue("1000");
167
+ });
168
+
169
+ it("should handle empty value", () => {
170
+ render(<NumberInput data-testid="number-input" value="" />);
171
+ const input = screen.getByTestId("number-input");
172
+
173
+ expect(input).toHaveValue("");
174
+ });
175
+ });
@@ -68,7 +68,7 @@ export const WithForm = () => {
68
68
  name="price"
69
69
  label="Preço"
70
70
  prefix="R$ "
71
- description="Digite o preço do produto"
71
+ placeholder="Digite o preço do produto"
72
72
  />
73
73
  </form>
74
74
  </Form>
@@ -1,68 +1,70 @@
1
1
  "use client";
2
2
 
3
3
  import * as React from "react";
4
- import { NumericFormat, NumericFormatProps } from "react-number-format";
5
- import { InputBase, InputBaseProps } from "../InputBase";
4
+ import {
5
+ NumericFormat,
6
+ NumericFormatProps,
7
+ SourceInfo,
8
+ } from "react-number-format";
9
+ import { InputBase } from "../InputBase";
6
10
  import { Input } from "../../../../ui/input";
11
+ import { InputProps } from "../Input/types";
12
+ import { useCallback, useMemo } from "react";
13
+ import { useConditionalController } from "@/hooks/use-conditional-controller";
7
14
 
8
- export type NumberInputProps = Omit<
9
- NumericFormatProps,
10
- "onChange" | "onValueChange"
11
- > &
12
- Omit<InputBaseProps, "children"> & {
13
- onChange?: (value: number) => void;
14
- "data-testid"?: string;
15
- component?: React.ReactNode;
16
- };
15
+ export type NumberInputProps = Omit<NumericFormatProps, "onChange"> &
16
+ InputProps;
17
+
18
+ export const NumberInput = (props: NumberInputProps) => {
19
+ const formData = useConditionalController({
20
+ name: props.name || "",
21
+ withoutForm: props.withoutForm,
22
+ });
17
23
 
18
- export const NumberInput = React.forwardRef<HTMLInputElement, NumberInputProps>(
19
- (props, ref) => {
20
- const {
21
- name,
22
- label,
23
- error,
24
- className,
25
- withoutForm,
26
- onChange,
27
- "data-testid": testId,
28
- component,
29
- ...inputProps
30
- } = props;
24
+ const inputProps = useMemo(() => {
25
+ return {
26
+ ...formData,
27
+ ...props,
28
+ };
29
+ }, [formData, props]);
31
30
 
32
- const baseTestId = testId || name || "";
31
+ const onValueChange = useCallback(
32
+ (
33
+ data: {
34
+ value: string;
35
+ floatValue: number | undefined;
36
+ formattedValue: string;
37
+ },
38
+ sourceInfo: SourceInfo
39
+ ) => {
40
+ const syntheticEvent = {
41
+ target: {
42
+ value: data.formattedValue,
43
+ name: props.name,
44
+ },
45
+ currentTarget: {
46
+ value: data.formattedValue,
47
+ name: props.name,
48
+ },
49
+ } as React.ChangeEvent<HTMLInputElement>;
33
50
 
34
- return (
35
- <InputBase
36
- label={label}
37
- error={error}
38
- className={className}
39
- name={name}
40
- withoutForm={withoutForm}
41
- data-testid={baseTestId}
42
- >
43
- {({ onChange: onBaseChange, value }) => (
44
- <div
45
- className="flex w-full gap-3"
46
- data-testid={`number-input-wrapper-${baseTestId}`}
47
- >
48
- <NumericFormat
49
- value={value as number}
50
- customInput={Input}
51
- getInputRef={ref}
52
- onValueChange={({ floatValue }) => {
53
- const numberValue = floatValue;
54
- onBaseChange?.(numberValue);
55
- onChange?.(numberValue as number);
56
- }}
57
- data-testid={`${baseTestId}-number-input`}
58
- {...inputProps}
59
- />
60
- {component}
61
- </div>
62
- )}
63
- </InputBase>
64
- );
65
- }
66
- );
51
+ props.onChange?.(syntheticEvent);
52
+ props.onValueChange?.(data, sourceInfo);
53
+ if (formData.onChange) {
54
+ formData.onChange(data.formattedValue);
55
+ }
56
+ },
57
+ [props, formData]
58
+ );
67
59
 
68
- NumberInput.displayName = "NumberInput";
60
+ return (
61
+ <InputBase {...props}>
62
+ <NumericFormat
63
+ {...inputProps}
64
+ customInput={Input}
65
+ value={props.value}
66
+ onValueChange={onValueChange}
67
+ />
68
+ </InputBase>
69
+ );
70
+ };