@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
|
@@ -2,85 +2,88 @@ import React from 'react';
|
|
|
2
2
|
import { Icon } from '@/components/icons';
|
|
3
3
|
import { IconName } from '@/types';
|
|
4
4
|
import { Label } from '../subcomponents/Label';
|
|
5
|
-
import {
|
|
5
|
+
import { DisplayFormError } from '../subcomponents/DisplayFormError';
|
|
6
6
|
|
|
7
7
|
export interface InputProps {
|
|
8
|
-
ref?: React.LegacyRef<HTMLInputElement>;
|
|
9
8
|
label: string;
|
|
10
9
|
name: string;
|
|
10
|
+
value?: string | undefined;
|
|
11
11
|
placeholder: string;
|
|
12
|
-
value: string;
|
|
13
12
|
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
14
13
|
isRequired?: boolean;
|
|
15
14
|
isDisabled?: boolean;
|
|
16
|
-
|
|
15
|
+
errorMessage?: string | undefined;
|
|
17
16
|
helpText?: string;
|
|
18
17
|
hasHiddenLabel?: boolean;
|
|
19
18
|
iconName?: IconName;
|
|
20
19
|
isClearable?: boolean;
|
|
20
|
+
className?: string;
|
|
21
|
+
defaultValue?: string;
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
export const Input = (
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
24
|
+
export const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
25
|
+
(
|
|
26
|
+
{
|
|
27
|
+
label,
|
|
28
|
+
name,
|
|
29
|
+
placeholder,
|
|
30
|
+
onChange,
|
|
31
|
+
isRequired,
|
|
32
|
+
isDisabled,
|
|
33
|
+
isClearable,
|
|
34
|
+
errorMessage,
|
|
35
|
+
helpText,
|
|
36
|
+
iconName,
|
|
37
|
+
hasHiddenLabel,
|
|
38
|
+
className,
|
|
39
|
+
...rest
|
|
40
|
+
},
|
|
41
|
+
ref,
|
|
42
|
+
) => {
|
|
43
|
+
const hasErrors = errorMessage && errorMessage.length > 0;
|
|
44
|
+
const handleClear = () => {
|
|
45
|
+
onChange({ target: { value: '' } } as React.ChangeEvent<HTMLInputElement>);
|
|
46
|
+
};
|
|
43
47
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
aria-label={label}
|
|
66
|
-
{...rest}
|
|
67
|
-
/>
|
|
68
|
-
{isClearable && (
|
|
69
|
-
<Icon
|
|
70
|
-
name="x-close"
|
|
71
|
-
data-testid={`${name}-clearable-icon`}
|
|
72
|
-
size="sm"
|
|
73
|
-
onClick={handleClear}
|
|
74
|
-
className="clearable-icon"
|
|
48
|
+
return (
|
|
49
|
+
<div className="form-control">
|
|
50
|
+
<Label label={label} name={name} isRequired={isRequired} hasHiddenLabel={hasHiddenLabel} />
|
|
51
|
+
<div className="input-wrapper">
|
|
52
|
+
{iconName && (
|
|
53
|
+
<Icon name={iconName} data-testid={`${name}-embedded-icon`} className="embedded-icon" />
|
|
54
|
+
)}
|
|
55
|
+
<input
|
|
56
|
+
ref={ref}
|
|
57
|
+
data-testid={`form-input-${name}`}
|
|
58
|
+
name={name}
|
|
59
|
+
type="text"
|
|
60
|
+
disabled={isDisabled}
|
|
61
|
+
placeholder={placeholder}
|
|
62
|
+
onChange={onChange}
|
|
63
|
+
className={`input ${hasErrors ? 'error' : ''} ${iconName ? 'input--has-icon' : ''} ${className}`}
|
|
64
|
+
aria-invalid={hasErrors ? true : undefined}
|
|
65
|
+
aria-describedby={hasErrors || helpText ? `${name}-helper` : undefined}
|
|
66
|
+
aria-required={isRequired}
|
|
67
|
+
aria-label={label}
|
|
68
|
+
{...rest}
|
|
75
69
|
/>
|
|
70
|
+
{isClearable && (
|
|
71
|
+
<Icon
|
|
72
|
+
name="x-close"
|
|
73
|
+
data-testid={`${name}-clearable-icon`}
|
|
74
|
+
size="sm"
|
|
75
|
+
onClick={handleClear}
|
|
76
|
+
className="clearable-icon"
|
|
77
|
+
/>
|
|
78
|
+
)}
|
|
79
|
+
</div>
|
|
80
|
+
{hasErrors && <DisplayFormError message={errorMessage} />}
|
|
81
|
+
{helpText && (
|
|
82
|
+
<div data-testid={`${name}-help-text`} className="help-text" id={`${name}-helper`}>
|
|
83
|
+
{helpText}
|
|
84
|
+
</div>
|
|
76
85
|
)}
|
|
77
86
|
</div>
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
{helpText}
|
|
82
|
-
</div>
|
|
83
|
-
)}
|
|
84
|
-
</div>
|
|
85
|
-
);
|
|
86
|
-
};
|
|
87
|
+
);
|
|
88
|
+
},
|
|
89
|
+
);
|
|
@@ -17,7 +17,6 @@ describe('Input', () => {
|
|
|
17
17
|
iconName="user"
|
|
18
18
|
isClearable={true}
|
|
19
19
|
ref={undefined}
|
|
20
|
-
value={''}
|
|
21
20
|
onChange={handleOnChange}
|
|
22
21
|
/>,
|
|
23
22
|
);
|
|
@@ -34,7 +33,6 @@ describe('Input', () => {
|
|
|
34
33
|
iconName="user"
|
|
35
34
|
isClearable={true}
|
|
36
35
|
ref={undefined}
|
|
37
|
-
value={''}
|
|
38
36
|
onChange={handleOnChange}
|
|
39
37
|
/>,
|
|
40
38
|
);
|
|
@@ -53,7 +51,6 @@ describe('Input', () => {
|
|
|
53
51
|
iconName="user"
|
|
54
52
|
isClearable={false}
|
|
55
53
|
ref={undefined}
|
|
56
|
-
value={''}
|
|
57
54
|
onChange={handleOnChange}
|
|
58
55
|
/>,
|
|
59
56
|
);
|
|
@@ -71,9 +68,8 @@ describe('Input', () => {
|
|
|
71
68
|
placeholder="Please enter a value"
|
|
72
69
|
iconName="user"
|
|
73
70
|
isClearable={true}
|
|
74
|
-
ref={undefined}
|
|
75
|
-
value={'test'}
|
|
76
71
|
onChange={handleOnChange}
|
|
72
|
+
value="test"
|
|
77
73
|
/>,
|
|
78
74
|
);
|
|
79
75
|
const input = screen.getByTestId('form-input-name');
|
|
@@ -95,8 +91,6 @@ describe('Input', () => {
|
|
|
95
91
|
placeholder="Please enter a value"
|
|
96
92
|
iconName="user"
|
|
97
93
|
isClearable={true}
|
|
98
|
-
ref={undefined}
|
|
99
|
-
value={'test'}
|
|
100
94
|
onChange={handleOnChange}
|
|
101
95
|
/>,
|
|
102
96
|
);
|
|
@@ -113,8 +107,6 @@ describe('Input', () => {
|
|
|
113
107
|
name="name"
|
|
114
108
|
placeholder="Please enter a value"
|
|
115
109
|
isClearable={true}
|
|
116
|
-
ref={undefined}
|
|
117
|
-
value={'test'}
|
|
118
110
|
onChange={handleOnChange}
|
|
119
111
|
/>,
|
|
120
112
|
);
|
|
@@ -126,13 +118,12 @@ describe('Input', () => {
|
|
|
126
118
|
render(
|
|
127
119
|
<Input
|
|
128
120
|
isRequired={true}
|
|
129
|
-
|
|
121
|
+
errorMessage="You require a username value."
|
|
130
122
|
label="Enter your name"
|
|
131
123
|
helpText="In order to submit the form, this field is required."
|
|
132
124
|
name="name"
|
|
133
125
|
placeholder="Please enter a value"
|
|
134
126
|
isClearable={true}
|
|
135
|
-
ref={undefined}
|
|
136
127
|
value={'test'}
|
|
137
128
|
onChange={handleOnChange}
|
|
138
129
|
/>,
|
|
@@ -201,8 +192,6 @@ describe('Input', () => {
|
|
|
201
192
|
label="Enter your name"
|
|
202
193
|
name="name"
|
|
203
194
|
placeholder="Please enter a value"
|
|
204
|
-
ref={undefined}
|
|
205
|
-
value={''}
|
|
206
195
|
onChange={handleOnChange}
|
|
207
196
|
/>,
|
|
208
197
|
);
|
|
@@ -73,14 +73,7 @@
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
.form-control {
|
|
76
|
-
|
|
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
|
-
}
|
|
76
|
+
margin-bottom: var(--pf-margin-3);
|
|
84
77
|
.help-text {
|
|
85
78
|
margin-top: var(--pf-margin-2);
|
|
86
79
|
margin-bottom: var(--pf-margin-2);
|
|
@@ -100,6 +93,7 @@
|
|
|
100
93
|
top: var(--pf-margin-3);
|
|
101
94
|
right: var(--pf-margin-2);
|
|
102
95
|
color: var(--pf-input-text-color);
|
|
96
|
+
cursor: pointer;
|
|
103
97
|
}
|
|
104
98
|
}
|
|
105
99
|
.is-visually-hidden {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Canvas, Meta, Controls } from '@storybook/blocks';
|
|
2
|
+
import * as PasswordInput from './PasswordInput.stories';
|
|
3
|
+
|
|
4
|
+
<Meta title="Forms/PasswordInput" name="PasswordInput" />
|
|
5
|
+
|
|
6
|
+
# Password Input
|
|
7
|
+
|
|
8
|
+
The password input component is the building block of any form. Below you will find the accepted properties for this component. It is encouraged to build forms utilizing [React Hook Form](https://react-hook-form.com/) library in your application. This will facilitate form state management and enforce best practices. (***Our components are compatible with but do not provide the plugin***)
|
|
9
|
+
|
|
10
|
+
<Canvas
|
|
11
|
+
of={PasswordInput.Default}
|
|
12
|
+
source={{
|
|
13
|
+
code: `
|
|
14
|
+
<PasswordInput
|
|
15
|
+
name="first_name"
|
|
16
|
+
isRequired
|
|
17
|
+
hasShowPassword
|
|
18
|
+
placeholder="Placeholder Text"
|
|
19
|
+
value=''
|
|
20
|
+
onChange={() => {}}
|
|
21
|
+
helpText="This Is Help Text"
|
|
22
|
+
label="Label Name"
|
|
23
|
+
/>
|
|
24
|
+
`,
|
|
25
|
+
}}
|
|
26
|
+
/>
|
|
27
|
+
|
|
28
|
+
<Controls of={PasswordInput.Default} />
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { PasswordInput, PasswordInputProps } from './PasswordInput';
|
|
3
|
+
import { SetStateAction, useEffect, useState } from 'react';
|
|
4
|
+
import { iconNames } from 'build/generated/iconTypes';
|
|
5
|
+
|
|
6
|
+
const meta: Meta = {
|
|
7
|
+
title: 'Forms/PasswordInput',
|
|
8
|
+
component: PasswordInput,
|
|
9
|
+
argTypes: {
|
|
10
|
+
hasShowPassword: {
|
|
11
|
+
control: 'boolean',
|
|
12
|
+
description: 'Toggles the visibility of the password',
|
|
13
|
+
table: {
|
|
14
|
+
category: 'Props',
|
|
15
|
+
type: {
|
|
16
|
+
summary: 'boolean',
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
defaultValue: { summary: true },
|
|
20
|
+
},
|
|
21
|
+
onChange: {
|
|
22
|
+
control: false,
|
|
23
|
+
description: 'onChange event handler',
|
|
24
|
+
table: {
|
|
25
|
+
category: 'Callbacks',
|
|
26
|
+
type: {
|
|
27
|
+
summary: '(e: React.ChangeEvent<HTMLInputElement>) => void',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
action: 'onChange',
|
|
31
|
+
},
|
|
32
|
+
label: {
|
|
33
|
+
control: 'text',
|
|
34
|
+
description: 'The label for the password field',
|
|
35
|
+
table: {
|
|
36
|
+
category: 'Props',
|
|
37
|
+
type: {
|
|
38
|
+
summary: 'string',
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
name: {
|
|
43
|
+
control: 'text',
|
|
44
|
+
description: 'The name for the password field',
|
|
45
|
+
table: {
|
|
46
|
+
category: 'Props',
|
|
47
|
+
type: {
|
|
48
|
+
summary: 'string',
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
placeholder: {
|
|
53
|
+
control: 'text',
|
|
54
|
+
description: 'The placeholder for the password field',
|
|
55
|
+
table: {
|
|
56
|
+
category: 'Props',
|
|
57
|
+
type: {
|
|
58
|
+
summary: 'string',
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
value: {
|
|
63
|
+
control: 'text',
|
|
64
|
+
description: 'The value for the password field',
|
|
65
|
+
table: {
|
|
66
|
+
category: 'Props',
|
|
67
|
+
type: {
|
|
68
|
+
summary: 'string',
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
isRequired: {
|
|
73
|
+
control: 'boolean',
|
|
74
|
+
description: 'Toggles the required astherisk on the label',
|
|
75
|
+
table: {
|
|
76
|
+
category: 'Props',
|
|
77
|
+
type: {
|
|
78
|
+
summary: 'boolean',
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
defaultValue: { summary: 'false' },
|
|
82
|
+
},
|
|
83
|
+
isDisabled: {
|
|
84
|
+
control: 'boolean',
|
|
85
|
+
description: 'Toggles the disabled state of the input',
|
|
86
|
+
table: {
|
|
87
|
+
category: 'Props',
|
|
88
|
+
type: {
|
|
89
|
+
summary: 'boolean',
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
defaultValue: { summary: 'false' },
|
|
93
|
+
},
|
|
94
|
+
errorMessage: {
|
|
95
|
+
control: false,
|
|
96
|
+
description: 'An array of error messages',
|
|
97
|
+
table: {
|
|
98
|
+
category: 'Props',
|
|
99
|
+
type: {
|
|
100
|
+
summary: 'string',
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
defaultValue: { summary: undefined },
|
|
104
|
+
},
|
|
105
|
+
helpText: {
|
|
106
|
+
control: 'text',
|
|
107
|
+
description: 'The help text for the password field',
|
|
108
|
+
table: {
|
|
109
|
+
category: 'Props',
|
|
110
|
+
type: {
|
|
111
|
+
summary: 'string',
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
hasHiddenLabel: {
|
|
116
|
+
control: 'boolean',
|
|
117
|
+
description: 'Hides the label visually (retains it for screen readers)',
|
|
118
|
+
table: {
|
|
119
|
+
category: 'Props',
|
|
120
|
+
type: {
|
|
121
|
+
summary: 'boolean',
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
defaultValue: { summary: 'false' },
|
|
125
|
+
},
|
|
126
|
+
ref: {
|
|
127
|
+
table: {
|
|
128
|
+
disable: true,
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
export default meta;
|
|
135
|
+
|
|
136
|
+
type Story = StoryObj<typeof PasswordInput>;
|
|
137
|
+
|
|
138
|
+
const defaultArgs = {
|
|
139
|
+
label: 'Enter your name',
|
|
140
|
+
name: 'first_name',
|
|
141
|
+
placeholder: 'Please enter a value',
|
|
142
|
+
} as PasswordInputProps;
|
|
143
|
+
|
|
144
|
+
export const Default: Story = {
|
|
145
|
+
args: {
|
|
146
|
+
isRequired: false,
|
|
147
|
+
helpText: 'This Is Help Text',
|
|
148
|
+
label: 'Label Name',
|
|
149
|
+
name: 'first_name',
|
|
150
|
+
placeholder: 'Please enter a value',
|
|
151
|
+
hasHiddenLabel: false,
|
|
152
|
+
hasShowPassword: true,
|
|
153
|
+
isDisabled: false,
|
|
154
|
+
errorMessage: '',
|
|
155
|
+
},
|
|
156
|
+
render: (args) => {
|
|
157
|
+
const [value, setValue] = useState('');
|
|
158
|
+
|
|
159
|
+
useEffect(() => {
|
|
160
|
+
setValue(args.value || '');
|
|
161
|
+
}, [args.value]);
|
|
162
|
+
|
|
163
|
+
const handleChange = (e: { target: { value: SetStateAction<string> } }) => {
|
|
164
|
+
setValue(e.target.value);
|
|
165
|
+
};
|
|
166
|
+
return <PasswordInput {...args} value={value} onChange={handleChange} />;
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
export const Errors: Story = {
|
|
171
|
+
args: {
|
|
172
|
+
...defaultArgs,
|
|
173
|
+
errorMessage: 'You require a password value.',
|
|
174
|
+
},
|
|
175
|
+
render: (args) => {
|
|
176
|
+
const [value, setValue] = useState('');
|
|
177
|
+
|
|
178
|
+
useEffect(() => {
|
|
179
|
+
setValue(args.value || '');
|
|
180
|
+
}, [args.value]);
|
|
181
|
+
|
|
182
|
+
const handleChange = (e: { target: { value: SetStateAction<string> } }) => {
|
|
183
|
+
setValue(e.target.value);
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
return <PasswordInput {...args} value={value} onChange={handleChange} />;
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
export const HiddenLabel: Story = {
|
|
191
|
+
args: {
|
|
192
|
+
...defaultArgs,
|
|
193
|
+
hasHiddenLabel: true,
|
|
194
|
+
},
|
|
195
|
+
render: (args) => {
|
|
196
|
+
const [value, setValue] = useState('');
|
|
197
|
+
|
|
198
|
+
useEffect(() => {
|
|
199
|
+
setValue(args.value || '');
|
|
200
|
+
}, [args.value]);
|
|
201
|
+
|
|
202
|
+
const handleChange = (e: { target: { value: SetStateAction<string> } }) => {
|
|
203
|
+
setValue(e.target.value);
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
return <PasswordInput {...args} value={value} onChange={handleChange} />;
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
export const HelpText: Story = {
|
|
211
|
+
args: {
|
|
212
|
+
...defaultArgs,
|
|
213
|
+
helpText: 'In order to submit the form, this field is required.',
|
|
214
|
+
},
|
|
215
|
+
render: (args) => {
|
|
216
|
+
const [value, setValue] = useState('');
|
|
217
|
+
|
|
218
|
+
useEffect(() => {
|
|
219
|
+
setValue(args.value || '');
|
|
220
|
+
}, [args.value]);
|
|
221
|
+
|
|
222
|
+
const handleChange = (e: { target: { value: SetStateAction<string> } }) => {
|
|
223
|
+
setValue(e.target.value);
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
return <PasswordInput {...args} value={value} onChange={handleChange} />;
|
|
227
|
+
},
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
export const Required: Story = {
|
|
231
|
+
args: {
|
|
232
|
+
...defaultArgs,
|
|
233
|
+
isRequired: true,
|
|
234
|
+
},
|
|
235
|
+
render: (args) => {
|
|
236
|
+
const [value, setValue] = useState('');
|
|
237
|
+
|
|
238
|
+
useEffect(() => {
|
|
239
|
+
setValue(args.value || '');
|
|
240
|
+
}, [args.value]);
|
|
241
|
+
|
|
242
|
+
const handleChange = (e: { target: { value: SetStateAction<string> } }) => {
|
|
243
|
+
setValue(e.target.value);
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
return <PasswordInput {...args} value={value} onChange={handleChange} />;
|
|
247
|
+
},
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
export const NoTogglePasswordVisibility: Story = {
|
|
251
|
+
args: {
|
|
252
|
+
...defaultArgs,
|
|
253
|
+
hasShowPassword: false,
|
|
254
|
+
},
|
|
255
|
+
render: (args) => {
|
|
256
|
+
const [value, setValue] = useState('');
|
|
257
|
+
|
|
258
|
+
useEffect(() => {
|
|
259
|
+
setValue(args.value || '');
|
|
260
|
+
}, [args.value]);
|
|
261
|
+
|
|
262
|
+
const handleChange = (e: { target: { value: SetStateAction<string> } }) => {
|
|
263
|
+
setValue(e.target.value);
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
return <PasswordInput {...args} value={value} onChange={handleChange} />;
|
|
267
|
+
},
|
|
268
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { Icon } from '@/components/icons';
|
|
3
|
+
import { Label } from '../subcomponents/Label';
|
|
4
|
+
import { DisplayFormError } from '../subcomponents/DisplayFormError';
|
|
5
|
+
|
|
6
|
+
export interface PasswordInputProps {
|
|
7
|
+
ref?: React.LegacyRef<HTMLInputElement>;
|
|
8
|
+
label: string;
|
|
9
|
+
value?: string | undefined;
|
|
10
|
+
name: string;
|
|
11
|
+
placeholder: string;
|
|
12
|
+
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
13
|
+
isRequired?: boolean;
|
|
14
|
+
isDisabled?: boolean;
|
|
15
|
+
errorMessage?: string | undefined;
|
|
16
|
+
helpText?: string;
|
|
17
|
+
hasHiddenLabel?: boolean;
|
|
18
|
+
hasShowPassword?: boolean;
|
|
19
|
+
defaultValue?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const PasswordInput = React.forwardRef<HTMLInputElement, PasswordInputProps>(
|
|
23
|
+
(
|
|
24
|
+
{
|
|
25
|
+
label,
|
|
26
|
+
name,
|
|
27
|
+
placeholder,
|
|
28
|
+
onChange,
|
|
29
|
+
isRequired,
|
|
30
|
+
isDisabled,
|
|
31
|
+
errorMessage,
|
|
32
|
+
helpText,
|
|
33
|
+
hasHiddenLabel,
|
|
34
|
+
hasShowPassword = true,
|
|
35
|
+
...rest
|
|
36
|
+
},
|
|
37
|
+
ref,
|
|
38
|
+
) => {
|
|
39
|
+
const hasErrors = errorMessage && errorMessage.length > 0;
|
|
40
|
+
|
|
41
|
+
const [showPassword, setShowPassword] = useState(false);
|
|
42
|
+
|
|
43
|
+
const handleShowPassword = () => {
|
|
44
|
+
setShowPassword((prevShowPassword) => !prevShowPassword);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<div className="form-control">
|
|
49
|
+
<Label label={label} name={name} isRequired={isRequired} hasHiddenLabel={hasHiddenLabel} />
|
|
50
|
+
<div className="password-input-wrapper">
|
|
51
|
+
<Icon name="lock" data-testid={`${name}-embedded-icon`} className="embedded-icon" />
|
|
52
|
+
<input
|
|
53
|
+
ref={ref}
|
|
54
|
+
data-testid={`form-password-input-${name}`}
|
|
55
|
+
name={name}
|
|
56
|
+
type={showPassword ? 'text' : 'password'}
|
|
57
|
+
disabled={isDisabled}
|
|
58
|
+
placeholder={placeholder}
|
|
59
|
+
onChange={onChange}
|
|
60
|
+
className={`password-input ${hasErrors ? 'error' : ''} password-input--has-icon`}
|
|
61
|
+
aria-invalid={hasErrors ? 'true' : 'false'}
|
|
62
|
+
aria-describedby={hasErrors || helpText ? `${name}-helper` : undefined}
|
|
63
|
+
aria-required={isRequired}
|
|
64
|
+
aria-label={label}
|
|
65
|
+
{...rest}
|
|
66
|
+
/>
|
|
67
|
+
{hasShowPassword && (
|
|
68
|
+
<Icon
|
|
69
|
+
name={showPassword ? 'fa-eye-slash' : 'eye'}
|
|
70
|
+
data-testid={`${name}-${showPassword ? 'hide' : 'show'}-password-icon`}
|
|
71
|
+
size="md"
|
|
72
|
+
onClick={handleShowPassword}
|
|
73
|
+
className="toggle-show-password-icon"
|
|
74
|
+
/>
|
|
75
|
+
)}
|
|
76
|
+
</div>
|
|
77
|
+
{hasErrors && <DisplayFormError message={errorMessage} />}
|
|
78
|
+
{helpText && (
|
|
79
|
+
<div data-testid={`${name}-help-text`} className="help-text" id={`${name}-helper`}>
|
|
80
|
+
{helpText}
|
|
81
|
+
</div>
|
|
82
|
+
)}
|
|
83
|
+
</div>
|
|
84
|
+
);
|
|
85
|
+
},
|
|
86
|
+
);
|