@indico-data/design-system 2.33.0 → 2.34.0
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/.storybook/preview-head.html +0 -4
- package/lib/index.css +95 -3
- package/lib/index.d.ts +8 -8
- package/lib/index.esm.css +95 -3
- package/lib/index.esm.js +1 -1
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/src/components/forms/input/Input.d.ts +5 -3
- package/lib/src/components/forms/numberInput/NumberInput.d.ts +11 -0
- package/lib/src/components/forms/numberInput/NumberInput.stories.d.ts +12 -0
- package/lib/src/components/forms/numberInput/__tests__/NumberInput.test.d.ts +1 -0
- package/lib/src/components/forms/numberInput/index.d.ts +1 -0
- package/lib/src/components/forms/passwordInput/PasswordInput.d.ts +3 -3
- package/lib/src/components/forms/subcomponents/Label.d.ts +1 -3
- package/lib/src/components/forms/textarea/Textarea.d.ts +3 -3
- package/lib/src/storybook/formArgTypes.d.ts +5 -0
- package/package.json +1 -1
- package/src/components/button/styles/Button.scss +0 -4
- package/src/components/forms/input/Input.stories.tsx +2 -96
- package/src/components/forms/input/Input.tsx +5 -3
- package/src/components/forms/numberInput/NumberInput.mdx +32 -0
- package/src/components/forms/numberInput/NumberInput.stories.tsx +215 -0
- package/src/components/forms/numberInput/NumberInput.tsx +90 -0
- package/src/components/forms/numberInput/__tests__/NumberInput.test.tsx +94 -0
- package/src/components/forms/numberInput/index.ts +1 -0
- package/src/components/forms/numberInput/styles/NumberInput.scss +108 -0
- package/src/components/forms/passwordInput/PasswordInput.stories.tsx +3 -70
- package/src/components/forms/passwordInput/PasswordInput.tsx +2 -2
- package/src/components/forms/subcomponents/Label.tsx +1 -4
- package/src/components/forms/textarea/Textarea.stories.tsx +3 -68
- package/src/components/forms/textarea/Textarea.tsx +2 -2
- package/src/storybook/formArgTypes.ts +152 -0
- package/src/styles/index.scss +1 -0
- package/lib/src/storybook/labelArgTypes.d.ts +0 -3
- package/src/storybook/labelArgTypes.ts +0 -50
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { IconName } from '@/types';
|
|
3
|
-
import {
|
|
4
|
-
export interface
|
|
3
|
+
import { LabelProps } from '../subcomponents/Label';
|
|
4
|
+
export interface BaseInputProps {
|
|
5
5
|
value?: string | undefined;
|
|
6
6
|
placeholder?: string;
|
|
7
7
|
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
@@ -13,5 +13,7 @@ export interface InputProps extends WithLabelProps {
|
|
|
13
13
|
className?: string;
|
|
14
14
|
defaultValue?: string;
|
|
15
15
|
}
|
|
16
|
-
|
|
16
|
+
export interface InputProps extends BaseInputProps, LabelProps {
|
|
17
|
+
}
|
|
18
|
+
declare const LabeledInput: React.ForwardRefExoticComponent<Omit<InputProps & React.RefAttributes<HTMLInputElement> & LabelProps, "ref"> & React.RefAttributes<any>>;
|
|
17
19
|
export { LabeledInput as Input };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { LabelProps } from '../subcomponents/Label';
|
|
3
|
+
import { BaseInputProps } from '../input/Input';
|
|
4
|
+
export interface NumberInputProps extends Omit<BaseInputProps, 'value'>, LabelProps {
|
|
5
|
+
value?: number | '';
|
|
6
|
+
min?: number;
|
|
7
|
+
max?: number;
|
|
8
|
+
step?: number;
|
|
9
|
+
}
|
|
10
|
+
declare const LabeledNumberInput: React.ForwardRefExoticComponent<Omit<NumberInputProps & React.RefAttributes<HTMLInputElement> & LabelProps, "ref"> & React.RefAttributes<any>>;
|
|
11
|
+
export { LabeledNumberInput as NumberInput };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { NumberInput } from './NumberInput';
|
|
3
|
+
declare const meta: Meta;
|
|
4
|
+
export default meta;
|
|
5
|
+
type Story = StoryObj<typeof NumberInput>;
|
|
6
|
+
export declare const Default: Story;
|
|
7
|
+
export declare const Errors: Story;
|
|
8
|
+
export declare const HiddenLabel: Story;
|
|
9
|
+
export declare const HelpText: Story;
|
|
10
|
+
export declare const Clearable: Story;
|
|
11
|
+
export declare const Icon: Story;
|
|
12
|
+
export declare const Required: Story;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { NumberInput } from './NumberInput';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
export interface PasswordInputProps extends
|
|
2
|
+
import { LabelProps } from '../subcomponents/Label';
|
|
3
|
+
export interface PasswordInputProps extends LabelProps {
|
|
4
4
|
ref?: React.LegacyRef<HTMLInputElement>;
|
|
5
5
|
value?: string | undefined;
|
|
6
6
|
placeholder?: string;
|
|
@@ -11,5 +11,5 @@ export interface PasswordInputProps extends WithLabelProps {
|
|
|
11
11
|
hasShowPassword?: boolean;
|
|
12
12
|
defaultValue?: string;
|
|
13
13
|
}
|
|
14
|
-
declare const LabeledPasswordInput: React.ForwardRefExoticComponent<Omit<Omit<PasswordInputProps, "ref"> & React.RefAttributes<HTMLInputElement> &
|
|
14
|
+
declare const LabeledPasswordInput: React.ForwardRefExoticComponent<Omit<Omit<PasswordInputProps, "ref"> & React.RefAttributes<HTMLInputElement> & LabelProps, "ref"> & React.RefAttributes<any>>;
|
|
15
15
|
export { LabeledPasswordInput as PasswordInput };
|
|
@@ -3,9 +3,7 @@ export interface LabelProps {
|
|
|
3
3
|
label: string;
|
|
4
4
|
name: string;
|
|
5
5
|
isRequired?: boolean;
|
|
6
|
-
}
|
|
7
|
-
export interface WithLabelProps extends LabelProps {
|
|
8
6
|
hasHiddenLabel?: boolean;
|
|
9
7
|
}
|
|
10
8
|
export declare const Label: ({ label, name, isRequired }: LabelProps) => import("react/jsx-runtime").JSX.Element;
|
|
11
|
-
export declare function withLabel<P extends object>(WrappedComponent: React.ComponentType<P>): React.ForwardRefExoticComponent<React.PropsWithoutRef<P &
|
|
9
|
+
export declare function withLabel<P extends object>(WrappedComponent: React.ComponentType<P>): React.ForwardRefExoticComponent<React.PropsWithoutRef<P & LabelProps> & React.RefAttributes<any>>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
export interface TextareaProps extends
|
|
2
|
+
import { LabelProps } from '../subcomponents/Label';
|
|
3
|
+
export interface TextareaProps extends LabelProps {
|
|
4
4
|
ref?: React.LegacyRef<HTMLTextAreaElement>;
|
|
5
5
|
placeholder?: string;
|
|
6
6
|
value?: string | undefined;
|
|
@@ -17,5 +17,5 @@ export interface TextareaProps extends WithLabelProps {
|
|
|
17
17
|
autofocus?: boolean;
|
|
18
18
|
defaultValue?: string;
|
|
19
19
|
}
|
|
20
|
-
declare const LabeledTextarea: React.ForwardRefExoticComponent<Omit<Omit<TextareaProps, "ref"> & React.RefAttributes<HTMLTextAreaElement> &
|
|
20
|
+
declare const LabeledTextarea: React.ForwardRefExoticComponent<Omit<Omit<TextareaProps, "ref"> & React.RefAttributes<HTMLTextAreaElement> & LabelProps, "ref"> & React.RefAttributes<any>>;
|
|
21
21
|
export { LabeledTextarea as Textarea };
|
package/package.json
CHANGED
|
@@ -1,107 +1,13 @@
|
|
|
1
1
|
import { Meta, StoryObj } from '@storybook/react';
|
|
2
2
|
import { Input, InputProps } from './Input';
|
|
3
3
|
import { SetStateAction, useEffect, useState } from 'react';
|
|
4
|
-
import {
|
|
5
|
-
import labelArgTypes from '@/storybook/labelArgTypes';
|
|
4
|
+
import { inputArgTypes, labelArgTypes } from '@/storybook/formArgTypes';
|
|
6
5
|
|
|
7
6
|
const meta: Meta = {
|
|
8
7
|
title: 'Forms/Input',
|
|
9
8
|
component: Input,
|
|
10
9
|
argTypes: {
|
|
11
|
-
|
|
12
|
-
control: false,
|
|
13
|
-
description: 'onChange event handler',
|
|
14
|
-
table: {
|
|
15
|
-
category: 'Callbacks',
|
|
16
|
-
type: {
|
|
17
|
-
summary: '(e: React.ChangeEvent<HTMLInputElement>) => void',
|
|
18
|
-
},
|
|
19
|
-
},
|
|
20
|
-
type: { name: 'function', required: true },
|
|
21
|
-
action: 'onChange',
|
|
22
|
-
},
|
|
23
|
-
placeholder: {
|
|
24
|
-
control: 'text',
|
|
25
|
-
description: 'The placeholder for the input field',
|
|
26
|
-
table: {
|
|
27
|
-
category: 'Props',
|
|
28
|
-
type: {
|
|
29
|
-
summary: 'string',
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
type: { name: 'string', required: false },
|
|
33
|
-
},
|
|
34
|
-
value: {
|
|
35
|
-
control: 'text',
|
|
36
|
-
description: 'The value for the input field',
|
|
37
|
-
table: {
|
|
38
|
-
category: 'Props',
|
|
39
|
-
type: {
|
|
40
|
-
summary: 'string',
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
type: { name: 'string', required: false },
|
|
44
|
-
},
|
|
45
|
-
isDisabled: {
|
|
46
|
-
control: 'boolean',
|
|
47
|
-
description: 'Toggles the disabled state of the input',
|
|
48
|
-
table: {
|
|
49
|
-
category: 'Props',
|
|
50
|
-
type: {
|
|
51
|
-
summary: 'boolean',
|
|
52
|
-
},
|
|
53
|
-
},
|
|
54
|
-
defaultValue: { summary: 'false' },
|
|
55
|
-
},
|
|
56
|
-
errorMessage: {
|
|
57
|
-
control: 'text',
|
|
58
|
-
description: 'Error message',
|
|
59
|
-
table: {
|
|
60
|
-
category: 'Props',
|
|
61
|
-
type: {
|
|
62
|
-
summary: 'string',
|
|
63
|
-
},
|
|
64
|
-
},
|
|
65
|
-
defaultValue: { summary: '' },
|
|
66
|
-
},
|
|
67
|
-
helpText: {
|
|
68
|
-
control: 'text',
|
|
69
|
-
description: 'The help text for the input field',
|
|
70
|
-
table: {
|
|
71
|
-
category: 'Props',
|
|
72
|
-
type: {
|
|
73
|
-
summary: 'string',
|
|
74
|
-
},
|
|
75
|
-
},
|
|
76
|
-
},
|
|
77
|
-
iconName: {
|
|
78
|
-
control: 'select',
|
|
79
|
-
options: iconNames,
|
|
80
|
-
description: 'Adds an icon to the left hand side of the input field',
|
|
81
|
-
table: {
|
|
82
|
-
category: 'Props',
|
|
83
|
-
type: {
|
|
84
|
-
summary: 'string',
|
|
85
|
-
},
|
|
86
|
-
},
|
|
87
|
-
defaultValue: { summary: '' },
|
|
88
|
-
},
|
|
89
|
-
isClearable: {
|
|
90
|
-
control: 'boolean',
|
|
91
|
-
description: 'Adds a clear x icon to the right hand side of the input field',
|
|
92
|
-
table: {
|
|
93
|
-
category: 'Props',
|
|
94
|
-
type: {
|
|
95
|
-
summary: 'boolean',
|
|
96
|
-
},
|
|
97
|
-
},
|
|
98
|
-
defaultValue: { summary: 'false' },
|
|
99
|
-
},
|
|
100
|
-
ref: {
|
|
101
|
-
table: {
|
|
102
|
-
disable: true,
|
|
103
|
-
},
|
|
104
|
-
},
|
|
10
|
+
...inputArgTypes,
|
|
105
11
|
...labelArgTypes,
|
|
106
12
|
},
|
|
107
13
|
};
|
|
@@ -3,10 +3,10 @@ import classNames from 'classnames';
|
|
|
3
3
|
|
|
4
4
|
import { Icon } from '@/components/icons';
|
|
5
5
|
import { IconName } from '@/types';
|
|
6
|
-
import { withLabel,
|
|
6
|
+
import { withLabel, LabelProps } from '../subcomponents/Label';
|
|
7
7
|
import { DisplayFormError } from '../subcomponents/DisplayFormError';
|
|
8
8
|
|
|
9
|
-
export interface
|
|
9
|
+
export interface BaseInputProps {
|
|
10
10
|
value?: string | undefined;
|
|
11
11
|
placeholder?: string;
|
|
12
12
|
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
@@ -19,6 +19,8 @@ export interface InputProps extends WithLabelProps {
|
|
|
19
19
|
defaultValue?: string;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
export interface InputProps extends BaseInputProps, LabelProps {}
|
|
23
|
+
|
|
22
24
|
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
23
25
|
(
|
|
24
26
|
{
|
|
@@ -70,7 +72,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
|
70
72
|
aria-required={isRequired}
|
|
71
73
|
{...rest}
|
|
72
74
|
/>
|
|
73
|
-
{isClearable && (
|
|
75
|
+
{isClearable && !isDisabled && (
|
|
74
76
|
<Icon
|
|
75
77
|
name="x-close"
|
|
76
78
|
data-testid={`${name}-clearable-icon`}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Canvas, Meta, Controls } from '@storybook/blocks';
|
|
2
|
+
import * as NumberInput from './NumberInput.stories';
|
|
3
|
+
|
|
4
|
+
<Meta title="Forms/Number Input" name="Number Input" />
|
|
5
|
+
|
|
6
|
+
# Number Input
|
|
7
|
+
|
|
8
|
+
The `NumberInput` component is a specialized input field designed to handle numeric values. It supports features such as min and max constraints, step increments, and optional icons or clear buttons. This component is intended to be used within forms where numeric input is required.
|
|
9
|
+
|
|
10
|
+
<Canvas
|
|
11
|
+
of={NumberInput.Default}
|
|
12
|
+
source={{
|
|
13
|
+
code: `
|
|
14
|
+
<NumberInput
|
|
15
|
+
label="Enter a number"
|
|
16
|
+
name="number_input"
|
|
17
|
+
placeholder="Please enter a value"
|
|
18
|
+
helpText="This Is Help Text"
|
|
19
|
+
isRequired
|
|
20
|
+
hasHiddenLabel={false}
|
|
21
|
+
isClearable
|
|
22
|
+
iconName="fa-calculator"
|
|
23
|
+
isDisabled={false}
|
|
24
|
+
errorMessage=""
|
|
25
|
+
value={0}
|
|
26
|
+
onChange={() => {}}
|
|
27
|
+
/>
|
|
28
|
+
`,
|
|
29
|
+
}}
|
|
30
|
+
/>
|
|
31
|
+
|
|
32
|
+
<Controls of={NumberInput.Default} />
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { NumberInput, NumberInputProps } from './NumberInput';
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import { iconNames } from 'build/generated/iconTypes';
|
|
5
|
+
import { labelArgTypes, inputArgTypes } from '@/storybook/formArgTypes';
|
|
6
|
+
|
|
7
|
+
const meta: Meta = {
|
|
8
|
+
title: 'Forms/Number Input',
|
|
9
|
+
component: NumberInput,
|
|
10
|
+
argTypes: {
|
|
11
|
+
...labelArgTypes,
|
|
12
|
+
...inputArgTypes,
|
|
13
|
+
value: {
|
|
14
|
+
control: 'number',
|
|
15
|
+
description: 'The value for the input field',
|
|
16
|
+
table: {
|
|
17
|
+
category: 'Props',
|
|
18
|
+
type: {
|
|
19
|
+
summary: 'number | ""',
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
type: { name: 'number', required: false },
|
|
23
|
+
},
|
|
24
|
+
min: {
|
|
25
|
+
control: 'number',
|
|
26
|
+
description: 'The minimum value for the input field',
|
|
27
|
+
table: {
|
|
28
|
+
category: 'Props',
|
|
29
|
+
type: {
|
|
30
|
+
summary: 'number',
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
type: { name: 'number', required: false },
|
|
34
|
+
},
|
|
35
|
+
max: {
|
|
36
|
+
control: 'number',
|
|
37
|
+
description: 'The maximum value for the input field',
|
|
38
|
+
table: {
|
|
39
|
+
category: 'Props',
|
|
40
|
+
type: {
|
|
41
|
+
summary: 'number',
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
type: { name: 'number', required: false },
|
|
45
|
+
},
|
|
46
|
+
step: {
|
|
47
|
+
control: 'number',
|
|
48
|
+
description: 'The step value for the input field',
|
|
49
|
+
table: {
|
|
50
|
+
category: 'Props',
|
|
51
|
+
type: {
|
|
52
|
+
summary: 'number',
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
type: { name: 'number', required: false },
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export default meta;
|
|
61
|
+
|
|
62
|
+
type Story = StoryObj<typeof NumberInput>;
|
|
63
|
+
|
|
64
|
+
const defaultArgs: NumberInputProps = {
|
|
65
|
+
label: 'Enter a number',
|
|
66
|
+
name: 'number_input',
|
|
67
|
+
placeholder: 'Please enter a value',
|
|
68
|
+
onChange: () => {},
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export const Default: Story = {
|
|
72
|
+
args: {
|
|
73
|
+
...defaultArgs,
|
|
74
|
+
isRequired: true,
|
|
75
|
+
iconName: 'fa-calculator',
|
|
76
|
+
helpText: 'This Is Help Text',
|
|
77
|
+
isClearable: true,
|
|
78
|
+
hasHiddenLabel: false,
|
|
79
|
+
isDisabled: false,
|
|
80
|
+
errorMessage: '',
|
|
81
|
+
},
|
|
82
|
+
render: (args) => {
|
|
83
|
+
const [value, setValue] = useState<number | ''>(0);
|
|
84
|
+
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
setValue(args.value || '');
|
|
87
|
+
}, [args.value]);
|
|
88
|
+
|
|
89
|
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
90
|
+
setValue(e.target.value === '' ? '' : parseFloat(e.target.value));
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
return <NumberInput {...args} value={value} onChange={handleChange} />;
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export const Errors: Story = {
|
|
98
|
+
args: {
|
|
99
|
+
...defaultArgs,
|
|
100
|
+
errorMessage: 'This field requires a number.',
|
|
101
|
+
},
|
|
102
|
+
render: (args) => {
|
|
103
|
+
const [value, setValue] = useState<number | ''>(0);
|
|
104
|
+
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
setValue(args.value || '');
|
|
107
|
+
}, [args.value]);
|
|
108
|
+
|
|
109
|
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
110
|
+
setValue(e.target.value === '' ? '' : parseFloat(e.target.value));
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
return <NumberInput {...args} value={value} onChange={handleChange} />;
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
export const HiddenLabel: Story = {
|
|
118
|
+
args: {
|
|
119
|
+
...defaultArgs,
|
|
120
|
+
hasHiddenLabel: true,
|
|
121
|
+
},
|
|
122
|
+
render: (args) => {
|
|
123
|
+
const [value, setValue] = useState<number | ''>(0);
|
|
124
|
+
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
setValue(args.value || '');
|
|
127
|
+
}, [args.value]);
|
|
128
|
+
|
|
129
|
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
130
|
+
setValue(e.target.value === '' ? '' : parseFloat(e.target.value));
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
return <NumberInput {...args} value={value} onChange={handleChange} />;
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
export const HelpText: Story = {
|
|
138
|
+
args: {
|
|
139
|
+
...defaultArgs,
|
|
140
|
+
helpText: 'In order to submit the form, this field is required.',
|
|
141
|
+
},
|
|
142
|
+
render: (args) => {
|
|
143
|
+
const [value, setValue] = useState<number | ''>(0);
|
|
144
|
+
|
|
145
|
+
useEffect(() => {
|
|
146
|
+
setValue(args.value || '');
|
|
147
|
+
}, [args.value]);
|
|
148
|
+
|
|
149
|
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
150
|
+
setValue(e.target.value === '' ? '' : parseFloat(e.target.value));
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
return <NumberInput {...args} value={value} onChange={handleChange} />;
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
export const Clearable: Story = {
|
|
158
|
+
args: {
|
|
159
|
+
...defaultArgs,
|
|
160
|
+
isClearable: true,
|
|
161
|
+
},
|
|
162
|
+
render: (args) => {
|
|
163
|
+
const [value, setValue] = useState<number | ''>(args.value || '');
|
|
164
|
+
|
|
165
|
+
useEffect(() => {
|
|
166
|
+
setValue(args.value || '');
|
|
167
|
+
}, [args.value]);
|
|
168
|
+
|
|
169
|
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
170
|
+
setValue(e.target.value === '' ? '' : parseFloat(e.target.value));
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
return <NumberInput {...args} value={value} onChange={handleChange} />;
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
export const Icon: Story = {
|
|
178
|
+
args: {
|
|
179
|
+
...defaultArgs,
|
|
180
|
+
iconName: 'fa-calculator',
|
|
181
|
+
},
|
|
182
|
+
render: (args) => {
|
|
183
|
+
const [value, setValue] = useState<number | ''>(0);
|
|
184
|
+
|
|
185
|
+
useEffect(() => {
|
|
186
|
+
setValue(args.value || '');
|
|
187
|
+
}, [args.value]);
|
|
188
|
+
|
|
189
|
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
190
|
+
setValue(e.target.value === '' ? '' : parseFloat(e.target.value));
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
return <NumberInput {...args} value={value} onChange={handleChange} />;
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
export const Required: Story = {
|
|
198
|
+
args: {
|
|
199
|
+
...defaultArgs,
|
|
200
|
+
isRequired: true,
|
|
201
|
+
},
|
|
202
|
+
render: (args) => {
|
|
203
|
+
const [value, setValue] = useState<number | ''>(0);
|
|
204
|
+
|
|
205
|
+
useEffect(() => {
|
|
206
|
+
setValue(args.value || '');
|
|
207
|
+
}, [args.value]);
|
|
208
|
+
|
|
209
|
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
210
|
+
setValue(e.target.value === '' ? '' : parseFloat(e.target.value));
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
return <NumberInput {...args} value={value} onChange={handleChange} />;
|
|
214
|
+
},
|
|
215
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import classNames from 'classnames';
|
|
3
|
+
|
|
4
|
+
import { Icon } from '@/components/icons';
|
|
5
|
+
import { LabelProps, withLabel } from '../subcomponents/Label';
|
|
6
|
+
import { DisplayFormError } from '../subcomponents/DisplayFormError';
|
|
7
|
+
import { BaseInputProps } from '../input/Input';
|
|
8
|
+
|
|
9
|
+
export interface NumberInputProps extends Omit<BaseInputProps, 'value'>, LabelProps {
|
|
10
|
+
value?: number | '';
|
|
11
|
+
min?: number;
|
|
12
|
+
max?: number;
|
|
13
|
+
step?: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const NumberInput = React.forwardRef<HTMLInputElement, NumberInputProps>(
|
|
17
|
+
(
|
|
18
|
+
{
|
|
19
|
+
name,
|
|
20
|
+
onChange,
|
|
21
|
+
isRequired,
|
|
22
|
+
isDisabled,
|
|
23
|
+
isClearable,
|
|
24
|
+
errorMessage,
|
|
25
|
+
helpText,
|
|
26
|
+
iconName,
|
|
27
|
+
className,
|
|
28
|
+
...rest
|
|
29
|
+
},
|
|
30
|
+
ref,
|
|
31
|
+
) => {
|
|
32
|
+
const hasErrors = errorMessage && errorMessage.length > 0;
|
|
33
|
+
|
|
34
|
+
const handleClear = () => {
|
|
35
|
+
onChange({ target: { value: '' } } as React.ChangeEvent<HTMLInputElement>);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const inputClasses = classNames(
|
|
39
|
+
'number-input',
|
|
40
|
+
{
|
|
41
|
+
error: hasErrors,
|
|
42
|
+
'number-input--has-icon': iconName,
|
|
43
|
+
'number-input--is-clearable': isClearable,
|
|
44
|
+
},
|
|
45
|
+
className,
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<>
|
|
50
|
+
<div className="number-input-wrapper">
|
|
51
|
+
{iconName && (
|
|
52
|
+
<Icon name={iconName} data-testid={`${name}-embedded-icon`} className="embedded-icon" />
|
|
53
|
+
)}
|
|
54
|
+
<input
|
|
55
|
+
ref={ref}
|
|
56
|
+
data-testid={`form-number-input-${name}`}
|
|
57
|
+
name={name}
|
|
58
|
+
type="number"
|
|
59
|
+
disabled={isDisabled}
|
|
60
|
+
onChange={onChange}
|
|
61
|
+
className={inputClasses}
|
|
62
|
+
aria-invalid={hasErrors ? true : undefined}
|
|
63
|
+
aria-describedby={hasErrors || helpText ? `${name}-helper` : undefined}
|
|
64
|
+
aria-required={isRequired}
|
|
65
|
+
{...rest}
|
|
66
|
+
/>
|
|
67
|
+
{isClearable && !isDisabled && (
|
|
68
|
+
<Icon
|
|
69
|
+
name="x-close"
|
|
70
|
+
data-testid={`${name}-clearable-icon`}
|
|
71
|
+
onClick={handleClear}
|
|
72
|
+
className="clearable-icon"
|
|
73
|
+
size="sm"
|
|
74
|
+
/>
|
|
75
|
+
)}
|
|
76
|
+
</div>
|
|
77
|
+
{hasErrors && <DisplayFormError message={errorMessage} />}
|
|
78
|
+
{helpText && !hasErrors && (
|
|
79
|
+
<div data-testid={`${name}-help-text`} className="help-text" id={`${name}-helper`}>
|
|
80
|
+
{helpText}
|
|
81
|
+
</div>
|
|
82
|
+
)}
|
|
83
|
+
</>
|
|
84
|
+
);
|
|
85
|
+
},
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const LabeledNumberInput = withLabel(NumberInput);
|
|
89
|
+
LabeledNumberInput.displayName = 'NumberInput';
|
|
90
|
+
export { LabeledNumberInput as NumberInput };
|