@indico-data/design-system 2.8.0 → 2.10.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 +216 -4
- package/lib/index.d.ts +41 -2
- package/lib/index.esm.css +216 -4
- package/lib/index.esm.js +19 -3
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +20 -2
- package/lib/index.js.map +1 -1
- package/lib/src/components/forms/input/Input.d.ts +2 -1
- package/lib/src/components/forms/passwordInput/PasswordInput.d.ts +16 -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/textarea/Textarea.d.ts +22 -0
- package/lib/src/components/forms/textarea/Textarea.stories.d.ts +11 -0
- package/lib/src/components/forms/textarea/__tests__/Textarea.test.d.ts +1 -0
- package/lib/src/components/forms/textarea/index.d.ts +1 -0
- package/lib/src/components/index.d.ts +2 -0
- package/lib/src/index.d.ts +2 -0
- package/package.json +1 -1
- package/src/components/forms/checkbox/styles/Checkbox.scss +1 -1
- package/src/components/forms/input/Input.stories.tsx +70 -69
- package/src/components/forms/input/Input.tsx +3 -1
- package/src/components/forms/input/styles/Input.scss +11 -3
- package/src/components/forms/passwordInput/PasswordInput.mdx +28 -0
- package/src/components/forms/passwordInput/PasswordInput.stories.tsx +269 -0
- package/src/components/forms/passwordInput/PasswordInput.tsx +82 -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/textarea/Textarea.mdx +19 -0
- package/src/components/forms/textarea/Textarea.stories.tsx +363 -0
- package/src/components/forms/textarea/Textarea.tsx +85 -0
- package/src/components/forms/textarea/__tests__/Textarea.test.tsx +103 -0
- package/src/components/forms/textarea/index.ts +1 -0
- package/src/components/forms/textarea/styles/Textarea.scss +102 -0
- package/src/components/index.ts +2 -0
- package/src/index.ts +2 -0
- package/src/styles/index.scss +2 -0
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { Textarea, TextareaProps } from './Textarea';
|
|
3
|
+
import { SetStateAction, useEffect, useState } from 'react';
|
|
4
|
+
|
|
5
|
+
const meta: Meta = {
|
|
6
|
+
title: 'Forms/Textarea',
|
|
7
|
+
component: Textarea,
|
|
8
|
+
argTypes: {
|
|
9
|
+
onChange: {
|
|
10
|
+
control: false,
|
|
11
|
+
description: 'onChange event handler',
|
|
12
|
+
table: {
|
|
13
|
+
category: 'Callbacks',
|
|
14
|
+
type: {
|
|
15
|
+
summary: '(e: React.ChangeEvent<HTMLInputElement>) => void',
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
action: 'onChange',
|
|
19
|
+
},
|
|
20
|
+
label: {
|
|
21
|
+
control: 'text',
|
|
22
|
+
description: 'The label for the textarea field',
|
|
23
|
+
table: {
|
|
24
|
+
category: 'Props',
|
|
25
|
+
type: {
|
|
26
|
+
summary: 'string',
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
defaultValue: { summary: '' },
|
|
30
|
+
},
|
|
31
|
+
name: {
|
|
32
|
+
control: 'text',
|
|
33
|
+
description: 'The name for the textarea field',
|
|
34
|
+
table: {
|
|
35
|
+
category: 'Props',
|
|
36
|
+
type: {
|
|
37
|
+
summary: 'string',
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
defaultValue: { summary: '' },
|
|
41
|
+
},
|
|
42
|
+
placeholder: {
|
|
43
|
+
control: 'text',
|
|
44
|
+
description: 'The placeholder for the textarea field',
|
|
45
|
+
table: {
|
|
46
|
+
category: 'Props',
|
|
47
|
+
type: {
|
|
48
|
+
summary: 'string',
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
defaultValue: { summary: '' },
|
|
52
|
+
},
|
|
53
|
+
value: {
|
|
54
|
+
control: 'text',
|
|
55
|
+
description: 'The value for the textarea field',
|
|
56
|
+
table: {
|
|
57
|
+
category: 'Props',
|
|
58
|
+
type: {
|
|
59
|
+
summary: 'string',
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
defaultValue: { summary: '' },
|
|
63
|
+
},
|
|
64
|
+
isRequired: {
|
|
65
|
+
control: 'boolean',
|
|
66
|
+
description: 'Toggles the required astherisc on the label',
|
|
67
|
+
table: {
|
|
68
|
+
category: 'Props',
|
|
69
|
+
type: {
|
|
70
|
+
summary: 'boolean',
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
defaultValue: { summary: 'false' },
|
|
74
|
+
},
|
|
75
|
+
isDisabled: {
|
|
76
|
+
control: 'boolean',
|
|
77
|
+
description: 'Toggles the disabled state of the textarea field',
|
|
78
|
+
table: {
|
|
79
|
+
category: 'Props',
|
|
80
|
+
type: {
|
|
81
|
+
summary: 'boolean',
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
defaultValue: { summary: 'false' },
|
|
85
|
+
},
|
|
86
|
+
errorList: {
|
|
87
|
+
control: false,
|
|
88
|
+
description: 'An array of error messages',
|
|
89
|
+
table: {
|
|
90
|
+
category: 'Props',
|
|
91
|
+
type: {
|
|
92
|
+
summary: 'string[]',
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
defaultValue: { summary: '[]' },
|
|
96
|
+
},
|
|
97
|
+
helpText: {
|
|
98
|
+
control: 'text',
|
|
99
|
+
description: 'The help text for the textarea field',
|
|
100
|
+
table: {
|
|
101
|
+
category: 'Props',
|
|
102
|
+
type: {
|
|
103
|
+
summary: 'string',
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
defaultValue: { summary: '' },
|
|
107
|
+
},
|
|
108
|
+
hasHiddenLabel: {
|
|
109
|
+
control: 'boolean',
|
|
110
|
+
description: 'Hides the label visually (retains it for screen readers)',
|
|
111
|
+
table: {
|
|
112
|
+
category: 'Props',
|
|
113
|
+
type: {
|
|
114
|
+
summary: 'boolean',
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
defaultValue: { summary: 'false' },
|
|
118
|
+
},
|
|
119
|
+
autofocus: {
|
|
120
|
+
control: 'boolean',
|
|
121
|
+
description: ' Specifies that a text area should automatically get focus when the page loads',
|
|
122
|
+
table: {
|
|
123
|
+
category: 'Props',
|
|
124
|
+
type: {
|
|
125
|
+
summary: 'boolean',
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
defaultValue: { summary: 'false' },
|
|
129
|
+
},
|
|
130
|
+
rows: {
|
|
131
|
+
control: 'number',
|
|
132
|
+
description: 'The number of rows for the textarea field',
|
|
133
|
+
table: {
|
|
134
|
+
category: 'Props',
|
|
135
|
+
type: {
|
|
136
|
+
summary: 'number',
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
defaultValue: { summary: undefined },
|
|
140
|
+
},
|
|
141
|
+
cols: {
|
|
142
|
+
control: 'number',
|
|
143
|
+
description: 'Specifies the visible width of a text area',
|
|
144
|
+
table: {
|
|
145
|
+
category: 'Props',
|
|
146
|
+
type: {
|
|
147
|
+
summary: 'number',
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
defaultValue: { summary: undefined },
|
|
151
|
+
},
|
|
152
|
+
readonly: {
|
|
153
|
+
control: 'boolean',
|
|
154
|
+
description: 'Sets the textarea field to readonly',
|
|
155
|
+
table: {
|
|
156
|
+
category: 'Props',
|
|
157
|
+
type: {
|
|
158
|
+
summary: 'boolean',
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
defaultValue: { summary: 'false' },
|
|
162
|
+
},
|
|
163
|
+
wrap: {
|
|
164
|
+
control: 'text',
|
|
165
|
+
description: 'Sets the wrap attribute for the textarea field',
|
|
166
|
+
table: {
|
|
167
|
+
category: 'Props',
|
|
168
|
+
type: {
|
|
169
|
+
summary: 'string',
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
defaultValue: { summary: 'soft' },
|
|
173
|
+
},
|
|
174
|
+
form: {
|
|
175
|
+
control: 'text',
|
|
176
|
+
description: 'Specifies which form the text area belongs to',
|
|
177
|
+
table: {
|
|
178
|
+
category: 'Props',
|
|
179
|
+
type: {
|
|
180
|
+
summary: 'string',
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
defaultValue: { summary: '' },
|
|
184
|
+
},
|
|
185
|
+
maxLength: {
|
|
186
|
+
control: 'number',
|
|
187
|
+
description: 'Specifies the maximum number of characters allowed in the text area',
|
|
188
|
+
table: {
|
|
189
|
+
category: 'Props',
|
|
190
|
+
type: {
|
|
191
|
+
summary: 'number',
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
defaultValue: { summary: undefined },
|
|
195
|
+
},
|
|
196
|
+
ref: {
|
|
197
|
+
table: {
|
|
198
|
+
disable: true,
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
export default meta;
|
|
205
|
+
|
|
206
|
+
type Story = StoryObj<typeof Textarea>;
|
|
207
|
+
|
|
208
|
+
export const Default: Story = {
|
|
209
|
+
args: {
|
|
210
|
+
isRequired: true,
|
|
211
|
+
helpText: 'This Is Help Text',
|
|
212
|
+
label: 'Label Name',
|
|
213
|
+
name: 'textarea',
|
|
214
|
+
placeholder: 'Please enter a value',
|
|
215
|
+
hasHiddenLabel: false,
|
|
216
|
+
isDisabled: false,
|
|
217
|
+
errorList: [],
|
|
218
|
+
value: '',
|
|
219
|
+
readonly: false,
|
|
220
|
+
cols: 0,
|
|
221
|
+
rows: 0,
|
|
222
|
+
wrap: 'soft',
|
|
223
|
+
form: '',
|
|
224
|
+
maxLength: 200,
|
|
225
|
+
autofocus: false,
|
|
226
|
+
},
|
|
227
|
+
render: (args) => {
|
|
228
|
+
const [value, setValue] = useState(args.value);
|
|
229
|
+
|
|
230
|
+
useEffect(() => {
|
|
231
|
+
setValue(args.value);
|
|
232
|
+
}, [args.value]);
|
|
233
|
+
|
|
234
|
+
const handleChange = (e: { target: { value: SetStateAction<string> } }) => {
|
|
235
|
+
setValue(e.target.value);
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
return <Textarea {...args} value={value} onChange={handleChange} />;
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
export const Errors: Story = {
|
|
243
|
+
args: {
|
|
244
|
+
isRequired: true,
|
|
245
|
+
label: 'Label Name',
|
|
246
|
+
name: 'textarea',
|
|
247
|
+
placeholder: 'Please enter a value',
|
|
248
|
+
hasHiddenLabel: false,
|
|
249
|
+
isDisabled: false,
|
|
250
|
+
errorList: ['This Is An Error', 'This Is Another Error'],
|
|
251
|
+
value: '',
|
|
252
|
+
},
|
|
253
|
+
render: (args) => {
|
|
254
|
+
const [value, setValue] = useState(args.value);
|
|
255
|
+
|
|
256
|
+
useEffect(() => {
|
|
257
|
+
setValue(args.value);
|
|
258
|
+
}, [args.value]);
|
|
259
|
+
|
|
260
|
+
const handleChange = (e: { target: { value: SetStateAction<string> } }) => {
|
|
261
|
+
setValue(e.target.value);
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
return <Textarea {...args} value={value} onChange={handleChange} />;
|
|
265
|
+
},
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
export const HiddenLabel: Story = {
|
|
269
|
+
args: {
|
|
270
|
+
isRequired: true,
|
|
271
|
+
label: 'Label Name',
|
|
272
|
+
name: 'textarea',
|
|
273
|
+
placeholder: 'Please enter a value',
|
|
274
|
+
hasHiddenLabel: true,
|
|
275
|
+
isDisabled: false,
|
|
276
|
+
value: '',
|
|
277
|
+
},
|
|
278
|
+
render: (args) => {
|
|
279
|
+
const [value, setValue] = useState(args.value);
|
|
280
|
+
|
|
281
|
+
useEffect(() => {
|
|
282
|
+
setValue(args.value);
|
|
283
|
+
}, [args.value]);
|
|
284
|
+
|
|
285
|
+
const handleChange = (e: { target: { value: SetStateAction<string> } }) => {
|
|
286
|
+
setValue(e.target.value);
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
return <Textarea {...args} value={value} onChange={handleChange} />;
|
|
290
|
+
},
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
export const HelpText: Story = {
|
|
294
|
+
args: {
|
|
295
|
+
helpText: 'This Is Help Text',
|
|
296
|
+
label: 'Label Name',
|
|
297
|
+
name: 'textarea',
|
|
298
|
+
placeholder: 'Please enter a value',
|
|
299
|
+
isDisabled: false,
|
|
300
|
+
value: '',
|
|
301
|
+
},
|
|
302
|
+
render: (args) => {
|
|
303
|
+
const [value, setValue] = useState(args.value);
|
|
304
|
+
|
|
305
|
+
useEffect(() => {
|
|
306
|
+
setValue(args.value);
|
|
307
|
+
}, [args.value]);
|
|
308
|
+
|
|
309
|
+
const handleChange = (e: { target: { value: SetStateAction<string> } }) => {
|
|
310
|
+
setValue(e.target.value);
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
return <Textarea {...args} value={value} onChange={handleChange} />;
|
|
314
|
+
},
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
export const Disabled: Story = {
|
|
318
|
+
args: {
|
|
319
|
+
helpText: 'This Is Help Text',
|
|
320
|
+
label: 'Label Name',
|
|
321
|
+
name: 'textarea',
|
|
322
|
+
placeholder: 'Please enter a value',
|
|
323
|
+
isDisabled: true,
|
|
324
|
+
value: '',
|
|
325
|
+
},
|
|
326
|
+
render: (args) => {
|
|
327
|
+
const [value, setValue] = useState(args.value);
|
|
328
|
+
|
|
329
|
+
useEffect(() => {
|
|
330
|
+
setValue(args.value);
|
|
331
|
+
}, [args.value]);
|
|
332
|
+
|
|
333
|
+
const handleChange = (e: { target: { value: SetStateAction<string> } }) => {
|
|
334
|
+
setValue(e.target.value);
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
return <Textarea {...args} value={value} onChange={handleChange} />;
|
|
338
|
+
},
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
export const Readonly: Story = {
|
|
342
|
+
args: {
|
|
343
|
+
helpText: 'This Is Help Text',
|
|
344
|
+
label: 'Label Name',
|
|
345
|
+
name: 'textarea',
|
|
346
|
+
placeholder: 'Please enter a value',
|
|
347
|
+
value: '',
|
|
348
|
+
readonly: true,
|
|
349
|
+
},
|
|
350
|
+
render: (args) => {
|
|
351
|
+
const [value, setValue] = useState(args.value);
|
|
352
|
+
|
|
353
|
+
useEffect(() => {
|
|
354
|
+
setValue(args.value);
|
|
355
|
+
}, [args.value]);
|
|
356
|
+
|
|
357
|
+
const handleChange = (e: { target: { value: SetStateAction<string> } }) => {
|
|
358
|
+
setValue(e.target.value);
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
return <Textarea {...args} value={value} onChange={handleChange} />;
|
|
362
|
+
},
|
|
363
|
+
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Label } from '../subcomponents/Label';
|
|
3
|
+
import { ErrorList } from '../subcomponents/ErrorList';
|
|
4
|
+
|
|
5
|
+
export interface TextareaProps {
|
|
6
|
+
ref?: React.LegacyRef<HTMLTextAreaElement>;
|
|
7
|
+
label: string;
|
|
8
|
+
name: string;
|
|
9
|
+
placeholder: string;
|
|
10
|
+
value: string;
|
|
11
|
+
onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void;
|
|
12
|
+
isRequired?: boolean;
|
|
13
|
+
isDisabled?: boolean;
|
|
14
|
+
errorList?: string[];
|
|
15
|
+
helpText?: string;
|
|
16
|
+
hasHiddenLabel?: boolean;
|
|
17
|
+
rows?: number;
|
|
18
|
+
cols?: number;
|
|
19
|
+
readonly?: boolean;
|
|
20
|
+
wrap?: 'hard' | 'soft';
|
|
21
|
+
form?: string;
|
|
22
|
+
maxLength?: number;
|
|
23
|
+
autofocus?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const Textarea = ({
|
|
27
|
+
ref,
|
|
28
|
+
label,
|
|
29
|
+
name,
|
|
30
|
+
placeholder,
|
|
31
|
+
value,
|
|
32
|
+
onChange,
|
|
33
|
+
isRequired,
|
|
34
|
+
isDisabled,
|
|
35
|
+
errorList,
|
|
36
|
+
helpText,
|
|
37
|
+
hasHiddenLabel,
|
|
38
|
+
rows,
|
|
39
|
+
cols,
|
|
40
|
+
readonly,
|
|
41
|
+
wrap,
|
|
42
|
+
form,
|
|
43
|
+
maxLength,
|
|
44
|
+
autofocus,
|
|
45
|
+
...rest
|
|
46
|
+
}: TextareaProps) => {
|
|
47
|
+
const hasErrors = errorList && errorList.length > 0;
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<div className="form-control">
|
|
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}
|
|
81
|
+
</div>
|
|
82
|
+
)}
|
|
83
|
+
</div>
|
|
84
|
+
);
|
|
85
|
+
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { render, screen, act } from '@testing-library/react';
|
|
2
|
+
import { Textarea } from '@/components/forms/textarea/Textarea';
|
|
3
|
+
import { ChangeEvent } from 'react';
|
|
4
|
+
import userEvent from '@testing-library/user-event';
|
|
5
|
+
|
|
6
|
+
const handleOnChange = jest.fn();
|
|
7
|
+
|
|
8
|
+
describe('Textarea', () => {
|
|
9
|
+
it('renders the input field', () => {
|
|
10
|
+
render(
|
|
11
|
+
<Textarea
|
|
12
|
+
isRequired={true}
|
|
13
|
+
label="Enter your name"
|
|
14
|
+
helpText="In order to submit the form, this field is required."
|
|
15
|
+
name="name"
|
|
16
|
+
placeholder="Please enter a value"
|
|
17
|
+
value={''}
|
|
18
|
+
onChange={handleOnChange}
|
|
19
|
+
/>,
|
|
20
|
+
);
|
|
21
|
+
expect(screen.getByText('Enter your name')).toBeInTheDocument();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('adds the error class when errors exist', () => {
|
|
25
|
+
render(
|
|
26
|
+
<Textarea
|
|
27
|
+
isRequired={true}
|
|
28
|
+
errorList={['You require a username value.']}
|
|
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 textareaElement = screen.getByTestId('form-textarea-name');
|
|
38
|
+
expect(textareaElement).toHaveClass('error');
|
|
39
|
+
});
|
|
40
|
+
it('does not highlight the Textarea when no errors exist', () => {
|
|
41
|
+
render(
|
|
42
|
+
<Textarea
|
|
43
|
+
isRequired={true}
|
|
44
|
+
label="Enter your name"
|
|
45
|
+
helpText="In order to submit the form, this field is required."
|
|
46
|
+
name="name"
|
|
47
|
+
placeholder="Please enter a value"
|
|
48
|
+
value={'test'}
|
|
49
|
+
onChange={handleOnChange}
|
|
50
|
+
/>,
|
|
51
|
+
);
|
|
52
|
+
const textareaElement = screen.getByTestId('form-textarea-name');
|
|
53
|
+
expect(textareaElement).not.toHaveClass('error');
|
|
54
|
+
});
|
|
55
|
+
it('renders help text when help text exists', () => {
|
|
56
|
+
render(
|
|
57
|
+
<Textarea
|
|
58
|
+
isRequired={true}
|
|
59
|
+
label="Enter your name"
|
|
60
|
+
helpText="In order to submit the form, this field is required."
|
|
61
|
+
name="name"
|
|
62
|
+
placeholder="Please enter a value"
|
|
63
|
+
value={'test'}
|
|
64
|
+
onChange={handleOnChange}
|
|
65
|
+
/>,
|
|
66
|
+
);
|
|
67
|
+
const helpText = screen.getByText('In order to submit the form, this field is required.');
|
|
68
|
+
expect(helpText).toBeInTheDocument();
|
|
69
|
+
expect(helpText).toBeVisible();
|
|
70
|
+
});
|
|
71
|
+
it('does not render help text when help text does not exist', () => {
|
|
72
|
+
render(
|
|
73
|
+
<Textarea
|
|
74
|
+
isRequired={true}
|
|
75
|
+
label="Enter your name"
|
|
76
|
+
name="name"
|
|
77
|
+
placeholder="Please enter a value"
|
|
78
|
+
value={'test'}
|
|
79
|
+
onChange={handleOnChange}
|
|
80
|
+
/>,
|
|
81
|
+
);
|
|
82
|
+
const helpText = screen.queryByTestId('name-help-text');
|
|
83
|
+
expect(helpText).not.toBeInTheDocument();
|
|
84
|
+
expect(helpText).toBeNull();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('emits the value when user types', async () => {
|
|
88
|
+
const handleOnChange = jest.fn();
|
|
89
|
+
render(
|
|
90
|
+
<Textarea
|
|
91
|
+
isRequired={true}
|
|
92
|
+
label="Enter your name"
|
|
93
|
+
name="name"
|
|
94
|
+
placeholder="Please enter a value"
|
|
95
|
+
value={''}
|
|
96
|
+
onChange={handleOnChange}
|
|
97
|
+
/>,
|
|
98
|
+
);
|
|
99
|
+
const textareaElement = screen.getByTestId('form-textarea-name');
|
|
100
|
+
await userEvent.type(textareaElement, 't');
|
|
101
|
+
expect(handleOnChange).toHaveBeenCalled();
|
|
102
|
+
});
|
|
103
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Textarea } from './Textarea';
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// Common Variables
|
|
2
|
+
:root,
|
|
3
|
+
:root [data-theme='light'],
|
|
4
|
+
:root [data-theme='dark'] {
|
|
5
|
+
// Typography
|
|
6
|
+
--pf-textarea-background-color: var(--pf-white-color);
|
|
7
|
+
--pf-textarea-border-color: var(--pf-gray-color);
|
|
8
|
+
--pf-textarea-text-color: var(--pf-gray-color);
|
|
9
|
+
--pf-textarea-placeholder-text-color: var(--pf-gray-color-300);
|
|
10
|
+
--pf-textarea-help-text-color: var(--pf-gray-color-400);
|
|
11
|
+
--pf-textarea-disabled-background-color: var(--pf-gray-color-100);
|
|
12
|
+
--pf-textarea-border-color: var(--pf-gray-color);
|
|
13
|
+
--pf-textarea-disabled-color: var(--pf-gray-color-400);
|
|
14
|
+
|
|
15
|
+
// textarea Radius
|
|
16
|
+
--pf-textarea-rounded: var(--pf-rounded);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Dark Theme Specific Variables
|
|
20
|
+
:root [data-theme='dark'] {
|
|
21
|
+
--pf-textarea-background-color: var(--pf-primary-color);
|
|
22
|
+
--pf-textarea-border-color: var(--pf-gray-color-100);
|
|
23
|
+
--pf-textarea-text-color: var(--pf-gray-color-100);
|
|
24
|
+
--pf-textarea-placeholder-text-color: var(--pf-gray-color);
|
|
25
|
+
--pf-textarea-help-text-color: var(--pf-gray-color-200);
|
|
26
|
+
--pf-textarea-disabled-background-color: var(--pf-primary-color-200);
|
|
27
|
+
--pf-textarea-disabled-border-color: var(--pf-gray-color-300);
|
|
28
|
+
--pf-textarea-disabled-color: var(--pf-gray-color-400);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.textarea {
|
|
32
|
+
background-color: var(--pf-textarea-background-color);
|
|
33
|
+
border: 1px solid var(--pf-textarea-border-color);
|
|
34
|
+
border-radius: var(--pf-textarea-rounded);
|
|
35
|
+
color: var(--pf-textarea-text-color);
|
|
36
|
+
padding: 10px;
|
|
37
|
+
width: 100%;
|
|
38
|
+
box-sizing: border-box;
|
|
39
|
+
&::placeholder {
|
|
40
|
+
color: var(--pf-textarea-placeholder-text-color);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
&:focus {
|
|
44
|
+
border-color: var(--pf-primary-color);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
&.error {
|
|
48
|
+
border-color: var(--pf-error-color);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
&.success {
|
|
52
|
+
border-color: var(--pf-success-color);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
&.warning {
|
|
56
|
+
border-color: var(--pf-warning-color);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
&.info {
|
|
60
|
+
border-color: var(--pf-info-color);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
&:disabled {
|
|
64
|
+
background-color: var(--pf-textarea-disabled-background-color);
|
|
65
|
+
border-color: var(--pf-textarea-disabled-border-color);
|
|
66
|
+
color: var(--pf-textarea-disabled-color);
|
|
67
|
+
}
|
|
68
|
+
&--has-icon {
|
|
69
|
+
padding-left: var(--pf-padding-7);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.form-control {
|
|
74
|
+
.error-list {
|
|
75
|
+
list-style: none;
|
|
76
|
+
padding: 0;
|
|
77
|
+
margin: 0;
|
|
78
|
+
margin-top: var(--pf-margin-2);
|
|
79
|
+
margin-bottom: var(--pf-margin-2);
|
|
80
|
+
color: var(--pf-error-color);
|
|
81
|
+
}
|
|
82
|
+
.help-text {
|
|
83
|
+
margin-top: var(--pf-margin-2);
|
|
84
|
+
margin-bottom: var(--pf-margin-2);
|
|
85
|
+
color: var(--pf-textarea-help-text-color);
|
|
86
|
+
font-size: var(--pf-font-size-subtitle2);
|
|
87
|
+
}
|
|
88
|
+
.is-visually-hidden {
|
|
89
|
+
position: absolute;
|
|
90
|
+
width: 1px;
|
|
91
|
+
height: 1px;
|
|
92
|
+
padding: 0;
|
|
93
|
+
margin: -1px;
|
|
94
|
+
overflow: hidden;
|
|
95
|
+
clip: rect(0, 0, 0, 0);
|
|
96
|
+
white-space: nowrap;
|
|
97
|
+
border: 0;
|
|
98
|
+
}
|
|
99
|
+
.form-label {
|
|
100
|
+
margin-bottom: var(--pf-margin-2);
|
|
101
|
+
}
|
|
102
|
+
}
|
package/src/components/index.ts
CHANGED
|
@@ -6,3 +6,5 @@ export { Input } from './forms/input';
|
|
|
6
6
|
export { Radio } from './forms/radio';
|
|
7
7
|
export { Checkbox } from './forms/checkbox';
|
|
8
8
|
export { Toggle } from './forms/toggle';
|
|
9
|
+
export { Textarea } from './forms/textarea';
|
|
10
|
+
export { PasswordInput } from './forms/passwordInput';
|
package/src/index.ts
CHANGED
|
@@ -65,3 +65,5 @@ export { Input } from './components/forms/input';
|
|
|
65
65
|
export { Radio as RadioInput } from './components/forms/radio';
|
|
66
66
|
export { Checkbox } from './components/forms/checkbox';
|
|
67
67
|
export { Toggle as ToggleInput } from './components/forms/toggle';
|
|
68
|
+
export { Textarea } from './components/forms/textarea';
|
|
69
|
+
export { PasswordInput } from './components/forms/passwordInput';
|
package/src/styles/index.scss
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
@import '../components/forms/input/styles/Input.scss';
|
|
10
10
|
@import '../components/forms/radio/styles/Radio.scss';
|
|
11
11
|
@import '../components/forms/checkbox/styles/Checkbox.scss';
|
|
12
|
+
@import '../components/forms/textarea/styles/Textarea.scss';
|
|
13
|
+
@import '../components/forms/passwordInput/styles/PasswordInput.scss';
|
|
12
14
|
|
|
13
15
|
@import '../components/forms/toggle/styles/Toggle.scss';
|
|
14
16
|
@import 'typography';
|