@indico-data/design-system 2.9.0 → 2.11.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/lib/index.css +119 -8
- package/lib/index.d.ts +52 -8
- package/lib/index.esm.css +119 -8
- package/lib/index.esm.js +35 -19
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +36 -18
- package/lib/index.js.map +1 -1
- package/lib/src/components/forms/checkbox/Checkbox.d.ts +2 -1
- package/lib/src/components/forms/form/Form.d.ts +14 -0
- package/lib/src/components/forms/form/Form.stories.d.ts +8 -0
- package/lib/src/components/forms/input/Input.d.ts +5 -4
- package/lib/src/components/forms/passwordInput/PasswordInput.d.ts +17 -0
- package/lib/src/components/forms/passwordInput/PasswordInput.stories.d.ts +11 -0
- package/lib/src/components/forms/passwordInput/__tests__/PasswordInput.test.d.ts +1 -0
- package/lib/src/components/forms/passwordInput/index.d.ts +1 -0
- package/lib/src/components/forms/radio/Radio.d.ts +2 -1
- package/lib/src/components/forms/subcomponents/DisplayFormError.d.ts +5 -0
- package/lib/src/components/forms/textarea/Textarea.d.ts +4 -3
- package/lib/src/components/forms/toggle/Toggle.d.ts +2 -1
- package/lib/src/components/index.d.ts +2 -0
- package/lib/src/index.d.ts +2 -0
- package/package.json +5 -2
- package/src/components/forms/checkbox/Checkbox.stories.tsx +2 -2
- package/src/components/forms/checkbox/Checkbox.tsx +32 -41
- package/src/components/forms/form/Form.mdx +134 -0
- package/src/components/forms/form/Form.stories.tsx +413 -0
- package/src/components/forms/form/Form.tsx +64 -0
- package/src/components/forms/form/__tests__/Form.test.tsx +35 -0
- package/src/components/forms/form/index.ts +0 -0
- package/src/components/forms/form/styles/Form.scss +3 -0
- package/src/components/forms/input/Input.stories.tsx +0 -5
- package/src/components/forms/input/Input.tsx +67 -64
- package/src/components/forms/input/__tests__/Input.test.tsx +2 -13
- package/src/components/forms/input/styles/Input.scss +2 -8
- package/src/components/forms/passwordInput/PasswordInput.mdx +28 -0
- package/src/components/forms/passwordInput/PasswordInput.stories.tsx +268 -0
- package/src/components/forms/passwordInput/PasswordInput.tsx +86 -0
- package/src/components/forms/passwordInput/__tests__/PasswordInput.test.tsx +129 -0
- package/src/components/forms/passwordInput/index.ts +1 -0
- package/src/components/forms/passwordInput/styles/PasswordInput.scss +120 -0
- package/src/components/forms/radio/Radio.tsx +32 -35
- package/src/components/forms/subcomponents/DisplayFormError.tsx +7 -0
- package/src/components/forms/textarea/Textarea.stories.tsx +15 -21
- package/src/components/forms/textarea/Textarea.tsx +64 -62
- package/src/components/forms/textarea/__tests__/Textarea.test.tsx +1 -1
- package/src/components/forms/textarea/styles/Textarea.scss +1 -1
- package/src/components/forms/toggle/Toggle.tsx +30 -37
- package/src/components/index.ts +2 -0
- package/src/index.ts +2 -0
- package/src/styles/index.scss +2 -0
- package/lib/src/components/forms/subcomponents/ErrorList.d.ts +0 -6
- package/src/components/forms/subcomponents/ErrorList.tsx +0 -14
- package/src/components/forms/subcomponents/__tests__/ErrorList.test.tsx +0 -16
- /package/lib/src/components/forms/{subcomponents/__tests__/ErrorList.test.d.ts → form/__tests__/Form.test.d.ts} +0 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { render, screen, act } from '@testing-library/react';
|
|
2
|
+
import { PasswordInput } from '@/components/forms/passwordInput/PasswordInput';
|
|
3
|
+
import { ChangeEvent } from 'react';
|
|
4
|
+
import userEvent from '@testing-library/user-event';
|
|
5
|
+
|
|
6
|
+
const handleOnChange = jest.fn();
|
|
7
|
+
const handleShowPassword = jest.fn();
|
|
8
|
+
|
|
9
|
+
describe('Input', () => {
|
|
10
|
+
it('renders the input field', () => {
|
|
11
|
+
render(
|
|
12
|
+
<PasswordInput
|
|
13
|
+
isRequired
|
|
14
|
+
label="Enter your name"
|
|
15
|
+
helpText="In order to submit the form, this field is required."
|
|
16
|
+
name="name"
|
|
17
|
+
placeholder="Please enter a value"
|
|
18
|
+
value={''}
|
|
19
|
+
onChange={handleOnChange}
|
|
20
|
+
/>,
|
|
21
|
+
);
|
|
22
|
+
expect(screen.getByText('Enter your name')).toBeInTheDocument();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('clicking on the eye changes the password input to a text input and back and changes the icon from a show icon to hide icon and back', async () => {
|
|
26
|
+
render(
|
|
27
|
+
<PasswordInput
|
|
28
|
+
isRequired
|
|
29
|
+
label="Enter your name"
|
|
30
|
+
helpText="In order to submit the form, this field is required."
|
|
31
|
+
name="name"
|
|
32
|
+
placeholder="Please enter a value"
|
|
33
|
+
value={'test'}
|
|
34
|
+
onChange={handleOnChange}
|
|
35
|
+
/>,
|
|
36
|
+
);
|
|
37
|
+
const input = screen.getByTestId('form-password-input-name');
|
|
38
|
+
const showPasswordIcon = screen.getByTestId('name-show-password-icon');
|
|
39
|
+
expect(input).toHaveValue('test');
|
|
40
|
+
await userEvent.click(showPasswordIcon);
|
|
41
|
+
expect(input).toHaveAttribute('type', 'text');
|
|
42
|
+
const hidePasswordIcon = screen.getByTestId('name-hide-password-icon');
|
|
43
|
+
await userEvent.click(hidePasswordIcon);
|
|
44
|
+
expect(input).toHaveAttribute('type', 'password');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('adds the error class when errors exist', () => {
|
|
48
|
+
render(
|
|
49
|
+
<PasswordInput
|
|
50
|
+
isRequired
|
|
51
|
+
errorMessage="You require a username value."
|
|
52
|
+
label="Enter your name"
|
|
53
|
+
helpText="In order to submit the form, this field is required."
|
|
54
|
+
name="name"
|
|
55
|
+
placeholder="Please enter a value"
|
|
56
|
+
value={'test'}
|
|
57
|
+
onChange={handleOnChange}
|
|
58
|
+
/>,
|
|
59
|
+
);
|
|
60
|
+
const input = screen.getByTestId('form-password-input-name');
|
|
61
|
+
expect(input).toHaveClass('error');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('does not highlight the input when no errors exist', () => {
|
|
65
|
+
render(
|
|
66
|
+
<PasswordInput
|
|
67
|
+
isRequired
|
|
68
|
+
label="Enter your name"
|
|
69
|
+
helpText="In order to submit the form, this field is required."
|
|
70
|
+
name="name"
|
|
71
|
+
placeholder="Please enter a value"
|
|
72
|
+
value={'test'}
|
|
73
|
+
onChange={handleOnChange}
|
|
74
|
+
/>,
|
|
75
|
+
);
|
|
76
|
+
const input = screen.getByTestId('form-password-input-name');
|
|
77
|
+
expect(input).not.toHaveClass('error');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('renders help text when help text exists', () => {
|
|
81
|
+
render(
|
|
82
|
+
<PasswordInput
|
|
83
|
+
isRequired
|
|
84
|
+
label="Enter your name"
|
|
85
|
+
helpText="In order to submit the form, this field is required."
|
|
86
|
+
name="name"
|
|
87
|
+
placeholder="Please enter a value"
|
|
88
|
+
value={'test'}
|
|
89
|
+
onChange={handleOnChange}
|
|
90
|
+
/>,
|
|
91
|
+
);
|
|
92
|
+
const helpText = screen.getByText('In order to submit the form, this field is required.');
|
|
93
|
+
expect(helpText).toBeInTheDocument();
|
|
94
|
+
expect(helpText).toBeVisible();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('does not render help text when help text does not exist', () => {
|
|
98
|
+
render(
|
|
99
|
+
<PasswordInput
|
|
100
|
+
isRequired
|
|
101
|
+
label="Enter your name"
|
|
102
|
+
name="name"
|
|
103
|
+
placeholder="Please enter a value"
|
|
104
|
+
value={'test'}
|
|
105
|
+
onChange={handleOnChange}
|
|
106
|
+
/>,
|
|
107
|
+
);
|
|
108
|
+
const helpText = screen.queryByTestId('name-help-text');
|
|
109
|
+
expect(helpText).not.toBeInTheDocument();
|
|
110
|
+
expect(helpText).toBeNull();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('emits the value when user types', async () => {
|
|
114
|
+
const handleOnChange = jest.fn();
|
|
115
|
+
render(
|
|
116
|
+
<PasswordInput
|
|
117
|
+
isRequired
|
|
118
|
+
label="Enter your name"
|
|
119
|
+
name="name"
|
|
120
|
+
placeholder="Please enter a value"
|
|
121
|
+
value={''}
|
|
122
|
+
onChange={handleOnChange}
|
|
123
|
+
/>,
|
|
124
|
+
);
|
|
125
|
+
const input = screen.getByTestId('form-password-input-name');
|
|
126
|
+
await userEvent.type(input, 't');
|
|
127
|
+
expect(handleOnChange).toHaveBeenCalled();
|
|
128
|
+
});
|
|
129
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { PasswordInput } from './PasswordInput';
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// Common Variables
|
|
2
|
+
:root,
|
|
3
|
+
:root [data-theme='light'],
|
|
4
|
+
:root [data-theme='dark'] {
|
|
5
|
+
// Typography
|
|
6
|
+
--pf-password-input-background-color: var(--pf-white-color);
|
|
7
|
+
--pf-password-input-border-color: var(--pf-gray-color);
|
|
8
|
+
--pf-password-input-text-color: var(--pf-gray-color);
|
|
9
|
+
--pf-password-input-placeholder-text-color: var(--pf-gray-color-300);
|
|
10
|
+
--pf-password-input-help-text-color: var(--pf-gray-color-400);
|
|
11
|
+
--pf-password-input-disabled-background-color: var(--pf-gray-color-100);
|
|
12
|
+
--pf-password-input-border-color: var(--pf-gray-color);
|
|
13
|
+
--pf-password-input-disabled-color: var(--pf-gray-color-400);
|
|
14
|
+
--pf-password-input-border-color: var(--pf-gray-color);
|
|
15
|
+
|
|
16
|
+
// input Radius
|
|
17
|
+
--pf-password-input-rounded: var(--pf-rounded);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Dark Theme Specific Variables
|
|
21
|
+
:root [data-theme='dark'] {
|
|
22
|
+
--pf-password-input-background-color: var(--pf-primary-color);
|
|
23
|
+
--pf-password-input-border-color: var(--pf-gray-color-100);
|
|
24
|
+
--pf-password-input-text-color: var(--pf-gray-color-100);
|
|
25
|
+
--pf-password-input-placeholder-text-color: var(--pf-gray-color);
|
|
26
|
+
--pf-password-input-help-text-color: var(--pf-gray-color-200);
|
|
27
|
+
--pf-password-input-disabled-background-color: var(--pf-primary-color-200);
|
|
28
|
+
--pf-password-input-disabled-border-color: var(--pf-gray-color-300);
|
|
29
|
+
--pf-password-input-disabled-color: var(--pf-gray-color-400);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.password-input {
|
|
33
|
+
background-color: var(--pf-password-input-background-color);
|
|
34
|
+
border: 1px solid var(--pf-password-input-border-color);
|
|
35
|
+
border-radius: var(--pf-password-input-rounded);
|
|
36
|
+
color: var(--pf-password-input-text-color);
|
|
37
|
+
padding: 10px;
|
|
38
|
+
width: 100%;
|
|
39
|
+
box-sizing: border-box;
|
|
40
|
+
height: 36px;
|
|
41
|
+
&::placeholder {
|
|
42
|
+
color: var(--pf-password-input-placeholder-text-color);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
&:focus {
|
|
46
|
+
border-color: var(--pf-primary-color);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
&.error {
|
|
50
|
+
border-color: var(--pf-error-color);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
&.success {
|
|
54
|
+
border-color: var(--pf-success-color);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
&.warning {
|
|
58
|
+
border-color: var(--pf-warning-color);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
&.info {
|
|
62
|
+
border-color: var(--pf-info-color);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
&:disabled {
|
|
66
|
+
background-color: var(--pf-password-input-disabled-background-color);
|
|
67
|
+
border-color: var(--pf-password-input-disabled-border-color);
|
|
68
|
+
color: var(--pf-password-input-disabled-color);
|
|
69
|
+
}
|
|
70
|
+
&--has-icon {
|
|
71
|
+
padding-left: var(--pf-padding-7);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.form-control {
|
|
76
|
+
.error-list {
|
|
77
|
+
list-style: none;
|
|
78
|
+
padding: 0;
|
|
79
|
+
margin: 0;
|
|
80
|
+
margin-top: var(--pf-margin-2);
|
|
81
|
+
margin-bottom: var(--pf-margin-2);
|
|
82
|
+
color: var(--pf-error-color);
|
|
83
|
+
}
|
|
84
|
+
.help-text {
|
|
85
|
+
margin-top: var(--pf-margin-2);
|
|
86
|
+
margin-bottom: var(--pf-margin-2);
|
|
87
|
+
color: var(--pf-password-input-help-text-color);
|
|
88
|
+
font-size: var(--pf-font-size-subtitle2);
|
|
89
|
+
}
|
|
90
|
+
.password-input-wrapper {
|
|
91
|
+
position: relative;
|
|
92
|
+
.embedded-icon {
|
|
93
|
+
position: absolute;
|
|
94
|
+
top: 10px;
|
|
95
|
+
left: var(--pf-margin-2);
|
|
96
|
+
color: var(--pf-password-input-text-color);
|
|
97
|
+
}
|
|
98
|
+
.toggle-show-password-icon {
|
|
99
|
+
position: absolute;
|
|
100
|
+
top: var(--pf-margin-3);
|
|
101
|
+
right: var(--pf-margin-2);
|
|
102
|
+
color: var(--pf-password-input-text-color);
|
|
103
|
+
cursor: pointer;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
.is-visually-hidden {
|
|
107
|
+
position: absolute;
|
|
108
|
+
width: 1px;
|
|
109
|
+
height: 1px;
|
|
110
|
+
padding: 0;
|
|
111
|
+
margin: -1px;
|
|
112
|
+
overflow: hidden;
|
|
113
|
+
clip: rect(0, 0, 0, 0);
|
|
114
|
+
white-space: nowrap;
|
|
115
|
+
border: 0;
|
|
116
|
+
}
|
|
117
|
+
.form-label {
|
|
118
|
+
margin-bottom: var(--pf-margin-2);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -8,40 +8,37 @@ export interface RadioProps {
|
|
|
8
8
|
value?: string;
|
|
9
9
|
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
10
10
|
isDisabled?: boolean;
|
|
11
|
+
defaultChecked?: boolean;
|
|
11
12
|
}
|
|
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
|
-
<label htmlFor={id} className="radio-input-label" data-testid={`label-radio-input-${name}`}>
|
|
42
|
-
{label}
|
|
43
|
-
</label>
|
|
13
|
+
export const Radio = React.forwardRef<HTMLInputElement, RadioProps>(
|
|
14
|
+
({ id, label, name, value, onChange, isDisabled, ...rest }, ref) => {
|
|
15
|
+
return (
|
|
16
|
+
<div className="form-control">
|
|
17
|
+
<div className="radio-wrapper">
|
|
18
|
+
<input
|
|
19
|
+
data-testid={`form-radio-input-${name}`}
|
|
20
|
+
className="radio-input"
|
|
21
|
+
type="radio"
|
|
22
|
+
id={id}
|
|
23
|
+
name={name}
|
|
24
|
+
value={value}
|
|
25
|
+
disabled={isDisabled}
|
|
26
|
+
ref={ref}
|
|
27
|
+
onChange={onChange}
|
|
28
|
+
tabIndex={0}
|
|
29
|
+
aria-describedby={id}
|
|
30
|
+
aria-label={label}
|
|
31
|
+
{...rest}
|
|
32
|
+
/>
|
|
33
|
+
<label
|
|
34
|
+
htmlFor={id}
|
|
35
|
+
className="radio-input-label"
|
|
36
|
+
data-testid={`label-radio-input-${name}`}
|
|
37
|
+
>
|
|
38
|
+
{label}
|
|
39
|
+
</label>
|
|
40
|
+
</div>
|
|
44
41
|
</div>
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
42
|
+
);
|
|
43
|
+
},
|
|
44
|
+
);
|
|
@@ -83,7 +83,7 @@ const meta: Meta = {
|
|
|
83
83
|
},
|
|
84
84
|
defaultValue: { summary: 'false' },
|
|
85
85
|
},
|
|
86
|
-
|
|
86
|
+
errorMessage: {
|
|
87
87
|
control: false,
|
|
88
88
|
description: 'An array of error messages',
|
|
89
89
|
table: {
|
|
@@ -214,8 +214,7 @@ export const Default: Story = {
|
|
|
214
214
|
placeholder: 'Please enter a value',
|
|
215
215
|
hasHiddenLabel: false,
|
|
216
216
|
isDisabled: false,
|
|
217
|
-
|
|
218
|
-
value: '',
|
|
217
|
+
errorMessage: '',
|
|
219
218
|
readonly: false,
|
|
220
219
|
cols: 0,
|
|
221
220
|
rows: 0,
|
|
@@ -228,10 +227,10 @@ export const Default: Story = {
|
|
|
228
227
|
const [value, setValue] = useState(args.value);
|
|
229
228
|
|
|
230
229
|
useEffect(() => {
|
|
231
|
-
setValue(args.value);
|
|
230
|
+
setValue(args.value || '');
|
|
232
231
|
}, [args.value]);
|
|
233
232
|
|
|
234
|
-
const handleChange = (e:
|
|
233
|
+
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
|
235
234
|
setValue(e.target.value);
|
|
236
235
|
};
|
|
237
236
|
|
|
@@ -247,17 +246,16 @@ export const Errors: Story = {
|
|
|
247
246
|
placeholder: 'Please enter a value',
|
|
248
247
|
hasHiddenLabel: false,
|
|
249
248
|
isDisabled: false,
|
|
250
|
-
|
|
251
|
-
value: '',
|
|
249
|
+
errorMessage: 'This Is An Error',
|
|
252
250
|
},
|
|
253
251
|
render: (args) => {
|
|
254
252
|
const [value, setValue] = useState(args.value);
|
|
255
253
|
|
|
256
254
|
useEffect(() => {
|
|
257
|
-
setValue(args.value);
|
|
255
|
+
setValue(args.value || '');
|
|
258
256
|
}, [args.value]);
|
|
259
257
|
|
|
260
|
-
const handleChange = (e:
|
|
258
|
+
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
|
261
259
|
setValue(e.target.value);
|
|
262
260
|
};
|
|
263
261
|
|
|
@@ -273,16 +271,15 @@ export const HiddenLabel: Story = {
|
|
|
273
271
|
placeholder: 'Please enter a value',
|
|
274
272
|
hasHiddenLabel: true,
|
|
275
273
|
isDisabled: false,
|
|
276
|
-
value: '',
|
|
277
274
|
},
|
|
278
275
|
render: (args) => {
|
|
279
276
|
const [value, setValue] = useState(args.value);
|
|
280
277
|
|
|
281
278
|
useEffect(() => {
|
|
282
|
-
setValue(args.value);
|
|
279
|
+
setValue(args.value || '');
|
|
283
280
|
}, [args.value]);
|
|
284
281
|
|
|
285
|
-
const handleChange = (e:
|
|
282
|
+
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
|
286
283
|
setValue(e.target.value);
|
|
287
284
|
};
|
|
288
285
|
|
|
@@ -297,16 +294,15 @@ export const HelpText: Story = {
|
|
|
297
294
|
name: 'textarea',
|
|
298
295
|
placeholder: 'Please enter a value',
|
|
299
296
|
isDisabled: false,
|
|
300
|
-
value: '',
|
|
301
297
|
},
|
|
302
298
|
render: (args) => {
|
|
303
299
|
const [value, setValue] = useState(args.value);
|
|
304
300
|
|
|
305
301
|
useEffect(() => {
|
|
306
|
-
setValue(args.value);
|
|
302
|
+
setValue(args.value || '');
|
|
307
303
|
}, [args.value]);
|
|
308
304
|
|
|
309
|
-
const handleChange = (e:
|
|
305
|
+
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
|
310
306
|
setValue(e.target.value);
|
|
311
307
|
};
|
|
312
308
|
|
|
@@ -321,16 +317,15 @@ export const Disabled: Story = {
|
|
|
321
317
|
name: 'textarea',
|
|
322
318
|
placeholder: 'Please enter a value',
|
|
323
319
|
isDisabled: true,
|
|
324
|
-
value: '',
|
|
325
320
|
},
|
|
326
321
|
render: (args) => {
|
|
327
322
|
const [value, setValue] = useState(args.value);
|
|
328
323
|
|
|
329
324
|
useEffect(() => {
|
|
330
|
-
setValue(args.value);
|
|
325
|
+
setValue(args.value || '');
|
|
331
326
|
}, [args.value]);
|
|
332
327
|
|
|
333
|
-
const handleChange = (e:
|
|
328
|
+
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
|
334
329
|
setValue(e.target.value);
|
|
335
330
|
};
|
|
336
331
|
|
|
@@ -344,17 +339,16 @@ export const Readonly: Story = {
|
|
|
344
339
|
label: 'Label Name',
|
|
345
340
|
name: 'textarea',
|
|
346
341
|
placeholder: 'Please enter a value',
|
|
347
|
-
value: '',
|
|
348
342
|
readonly: true,
|
|
349
343
|
},
|
|
350
344
|
render: (args) => {
|
|
351
345
|
const [value, setValue] = useState(args.value);
|
|
352
346
|
|
|
353
347
|
useEffect(() => {
|
|
354
|
-
setValue(args.value);
|
|
348
|
+
setValue(args.value || '');
|
|
355
349
|
}, [args.value]);
|
|
356
350
|
|
|
357
|
-
const handleChange = (e:
|
|
351
|
+
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
|
358
352
|
setValue(e.target.value);
|
|
359
353
|
};
|
|
360
354
|
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Label } from '../subcomponents/Label';
|
|
3
|
-
import {
|
|
3
|
+
import { DisplayFormError } from '../subcomponents/DisplayFormError';
|
|
4
4
|
|
|
5
5
|
export interface TextareaProps {
|
|
6
6
|
ref?: React.LegacyRef<HTMLTextAreaElement>;
|
|
7
7
|
label: string;
|
|
8
8
|
name: string;
|
|
9
9
|
placeholder: string;
|
|
10
|
-
value
|
|
10
|
+
value?: string | undefined;
|
|
11
11
|
onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void;
|
|
12
12
|
isRequired?: boolean;
|
|
13
13
|
isDisabled?: boolean;
|
|
14
|
-
|
|
14
|
+
errorMessage?: string | undefined;
|
|
15
15
|
helpText?: string;
|
|
16
16
|
hasHiddenLabel?: boolean;
|
|
17
17
|
rows?: number;
|
|
@@ -21,65 +21,67 @@ export interface TextareaProps {
|
|
|
21
21
|
form?: string;
|
|
22
22
|
maxLength?: number;
|
|
23
23
|
autofocus?: boolean;
|
|
24
|
+
defaultValue?: string;
|
|
24
25
|
}
|
|
26
|
+
export const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
27
|
+
(
|
|
28
|
+
{
|
|
29
|
+
label,
|
|
30
|
+
name,
|
|
31
|
+
placeholder,
|
|
32
|
+
value,
|
|
33
|
+
onChange,
|
|
34
|
+
isRequired,
|
|
35
|
+
isDisabled,
|
|
36
|
+
errorMessage,
|
|
37
|
+
helpText,
|
|
38
|
+
hasHiddenLabel,
|
|
39
|
+
rows,
|
|
40
|
+
cols,
|
|
41
|
+
readonly,
|
|
42
|
+
wrap,
|
|
43
|
+
form,
|
|
44
|
+
maxLength,
|
|
45
|
+
autofocus,
|
|
46
|
+
...rest
|
|
47
|
+
},
|
|
48
|
+
ref,
|
|
49
|
+
) => {
|
|
50
|
+
const hasErrors = errorMessage && errorMessage.length > 0;
|
|
25
51
|
|
|
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
|
-
<Label label={label} name={name} isRequired={isRequired} hasHiddenLabel={hasHiddenLabel} />
|
|
52
|
-
<div className="textarea-wrapper">
|
|
53
|
-
<textarea
|
|
54
|
-
ref={ref}
|
|
55
|
-
rows={rows}
|
|
56
|
-
cols={cols}
|
|
57
|
-
autoFocus={autofocus}
|
|
58
|
-
wrap={wrap}
|
|
59
|
-
form={form}
|
|
60
|
-
value={value}
|
|
61
|
-
maxLength={maxLength}
|
|
62
|
-
readOnly={readonly}
|
|
63
|
-
data-testid={`form-textarea-${name}`}
|
|
64
|
-
name={name}
|
|
65
|
-
disabled={isDisabled}
|
|
66
|
-
required={isRequired}
|
|
67
|
-
placeholder={placeholder}
|
|
68
|
-
onChange={onChange}
|
|
69
|
-
className={`textarea ${hasErrors ? 'error' : ''}`}
|
|
70
|
-
aria-invalid={hasErrors}
|
|
71
|
-
aria-describedby={hasErrors || helpText ? `${name}-helper` : undefined}
|
|
72
|
-
aria-required={isRequired}
|
|
73
|
-
aria-label={label}
|
|
74
|
-
{...rest}
|
|
75
|
-
/>
|
|
76
|
-
</div>
|
|
77
|
-
{hasErrors && <ErrorList errorList={errorList} name={name} />}
|
|
78
|
-
{helpText && (
|
|
79
|
-
<div data-testid={`${name}-help-text`} className="help-text" id={`${name}-helper`}>
|
|
80
|
-
{helpText}
|
|
52
|
+
return (
|
|
53
|
+
<div className="form-control">
|
|
54
|
+
<Label label={label} name={name} isRequired={isRequired} hasHiddenLabel={hasHiddenLabel} />
|
|
55
|
+
<div className="textarea-wrapper">
|
|
56
|
+
<textarea
|
|
57
|
+
ref={ref}
|
|
58
|
+
rows={rows}
|
|
59
|
+
cols={cols}
|
|
60
|
+
autoFocus={autofocus}
|
|
61
|
+
wrap={wrap}
|
|
62
|
+
form={form}
|
|
63
|
+
maxLength={maxLength}
|
|
64
|
+
readOnly={readonly}
|
|
65
|
+
data-testid={`form-textarea-${name}`}
|
|
66
|
+
name={name}
|
|
67
|
+
disabled={isDisabled}
|
|
68
|
+
placeholder={placeholder}
|
|
69
|
+
onChange={onChange}
|
|
70
|
+
className={`textarea ${hasErrors ? 'error' : ''}`}
|
|
71
|
+
aria-invalid={hasErrors ? true : undefined}
|
|
72
|
+
aria-describedby={hasErrors || helpText ? `${name}-helper` : undefined}
|
|
73
|
+
aria-required={isRequired}
|
|
74
|
+
aria-label={label}
|
|
75
|
+
{...rest}
|
|
76
|
+
/>
|
|
81
77
|
</div>
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
78
|
+
{hasErrors && <DisplayFormError message={errorMessage} />}
|
|
79
|
+
{helpText && (
|
|
80
|
+
<div data-testid={`${name}-help-text`} className="help-text" id={`${name}-helper`}>
|
|
81
|
+
{helpText}
|
|
82
|
+
</div>
|
|
83
|
+
)}
|
|
84
|
+
</div>
|
|
85
|
+
);
|
|
86
|
+
},
|
|
87
|
+
);
|
|
@@ -25,7 +25,7 @@ describe('Textarea', () => {
|
|
|
25
25
|
render(
|
|
26
26
|
<Textarea
|
|
27
27
|
isRequired={true}
|
|
28
|
-
|
|
28
|
+
errorMessage="You require a username value."
|
|
29
29
|
label="Enter your name"
|
|
30
30
|
helpText="In order to submit the form, this field is required."
|
|
31
31
|
name="name"
|