@indico-data/design-system 2.9.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.
@@ -14,5 +14,6 @@ export interface InputProps {
14
14
  hasHiddenLabel?: boolean;
15
15
  iconName?: IconName;
16
16
  isClearable?: boolean;
17
+ className?: string;
17
18
  }
18
- export declare const Input: ({ ref, label, name, placeholder, value, onChange, isRequired, isDisabled, errorList, helpText, iconName, hasHiddenLabel, isClearable, ...rest }: InputProps) => import("react/jsx-runtime").JSX.Element;
19
+ export declare const Input: ({ ref, label, name, placeholder, value, onChange, isRequired, isDisabled, errorList, helpText, iconName, hasHiddenLabel, isClearable, className, ...rest }: InputProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,16 @@
1
+ import React from 'react';
2
+ export interface PasswordInputProps {
3
+ ref?: React.LegacyRef<HTMLInputElement>;
4
+ label: string;
5
+ name: string;
6
+ placeholder: string;
7
+ value: string;
8
+ onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
9
+ isRequired?: boolean;
10
+ isDisabled?: boolean;
11
+ errorList?: string[];
12
+ helpText?: string;
13
+ hasHiddenLabel?: boolean;
14
+ hasShowPassword?: boolean;
15
+ }
16
+ export declare const PasswordInput: ({ label, name, placeholder, value, onChange, isRequired, isDisabled, errorList, helpText, hasHiddenLabel, hasShowPassword, ...rest }: PasswordInputProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,11 @@
1
+ import { Meta, StoryObj } from '@storybook/react';
2
+ import { PasswordInput } from './PasswordInput';
3
+ declare const meta: Meta;
4
+ export default meta;
5
+ type Story = StoryObj<typeof PasswordInput>;
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 Required: Story;
11
+ export declare const NoTogglePasswordVisibility: Story;
@@ -0,0 +1 @@
1
+ export { PasswordInput } from './PasswordInput';
@@ -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';
@@ -11,3 +11,5 @@ export { Input } from './components/forms/input';
11
11
  export { Radio as RadioInput } from './components/forms/radio';
12
12
  export { Checkbox } from './components/forms/checkbox';
13
13
  export { Toggle as ToggleInput } from './components/forms/toggle';
14
+ export { Textarea } from './components/forms/textarea';
15
+ export { PasswordInput } from './components/forms/passwordInput';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indico-data/design-system",
3
- "version": "2.9.0",
3
+ "version": "2.10.0",
4
4
  "description": "",
5
5
  "author": "",
6
6
  "main": "lib/index.js",
@@ -27,7 +27,6 @@ const meta: Meta = {
27
27
  summary: 'string',
28
28
  },
29
29
  },
30
- defaultValue: { summary: '' },
31
30
  },
32
31
  name: {
33
32
  control: 'text',
@@ -38,7 +37,6 @@ const meta: Meta = {
38
37
  summary: 'string',
39
38
  },
40
39
  },
41
- defaultValue: { summary: '' },
42
40
  },
43
41
  placeholder: {
44
42
  control: 'text',
@@ -49,7 +47,6 @@ const meta: Meta = {
49
47
  summary: 'string',
50
48
  },
51
49
  },
52
- defaultValue: { summary: '' },
53
50
  },
54
51
  value: {
55
52
  control: 'text',
@@ -60,7 +57,6 @@ const meta: Meta = {
60
57
  summary: 'string',
61
58
  },
62
59
  },
63
- defaultValue: { summary: '' },
64
60
  },
65
61
  isRequired: {
66
62
  control: 'boolean',
@@ -104,7 +100,6 @@ const meta: Meta = {
104
100
  summary: 'string',
105
101
  },
106
102
  },
107
- defaultValue: { summary: '' },
108
103
  },
109
104
  hasHiddenLabel: {
110
105
  control: 'boolean',
@@ -18,6 +18,7 @@ export interface InputProps {
18
18
  hasHiddenLabel?: boolean;
19
19
  iconName?: IconName;
20
20
  isClearable?: boolean;
21
+ className?: string;
21
22
  }
22
23
 
23
24
  export const Input = ({
@@ -34,6 +35,7 @@ export const Input = ({
34
35
  iconName,
35
36
  hasHiddenLabel,
36
37
  isClearable,
38
+ className = '',
37
39
  ...rest
38
40
  }: InputProps) => {
39
41
  const hasErrors = errorList && errorList.length > 0;
@@ -58,7 +60,7 @@ export const Input = ({
58
60
  placeholder={placeholder}
59
61
  value={value}
60
62
  onChange={onChange}
61
- className={`input ${hasErrors ? 'error' : ''} ${iconName ? 'input--has-icon' : ''}`}
63
+ className={`input ${hasErrors ? 'error' : ''} ${iconName ? 'input--has-icon' : ''} ${className}`}
62
64
  aria-invalid={hasErrors}
63
65
  aria-describedby={hasErrors || helpText ? `${name}-helper` : undefined}
64
66
  aria-required={isRequired}
@@ -100,6 +100,7 @@
100
100
  top: var(--pf-margin-3);
101
101
  right: var(--pf-margin-2);
102
102
  color: var(--pf-input-text-color);
103
+ cursor: pointer;
103
104
  }
104
105
  }
105
106
  .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,269 @@
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
+ errorList: {
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: '[]' },
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
+ errorList: [],
155
+ value: '',
156
+ },
157
+ render: (args) => {
158
+ const [value, setValue] = useState('');
159
+
160
+ useEffect(() => {
161
+ setValue(args.value);
162
+ }, [args.value]);
163
+
164
+ const handleChange = (e: { target: { value: SetStateAction<string> } }) => {
165
+ setValue(e.target.value);
166
+ };
167
+ return <PasswordInput {...args} value={value} onChange={handleChange} />;
168
+ },
169
+ };
170
+
171
+ export const Errors: Story = {
172
+ args: {
173
+ ...defaultArgs,
174
+ errorList: ['You require a password value.'],
175
+ },
176
+ render: (args) => {
177
+ const [value, setValue] = useState('');
178
+
179
+ useEffect(() => {
180
+ setValue(args.value);
181
+ }, [args.value]);
182
+
183
+ const handleChange = (e: { target: { value: SetStateAction<string> } }) => {
184
+ setValue(e.target.value);
185
+ };
186
+
187
+ return <PasswordInput {...args} value={value} onChange={handleChange} />;
188
+ },
189
+ };
190
+
191
+ export const HiddenLabel: Story = {
192
+ args: {
193
+ ...defaultArgs,
194
+ hasHiddenLabel: true,
195
+ },
196
+ render: (args) => {
197
+ const [value, setValue] = useState('');
198
+
199
+ useEffect(() => {
200
+ setValue(args.value);
201
+ }, [args.value]);
202
+
203
+ const handleChange = (e: { target: { value: SetStateAction<string> } }) => {
204
+ setValue(e.target.value);
205
+ };
206
+
207
+ return <PasswordInput {...args} value={value} onChange={handleChange} />;
208
+ },
209
+ };
210
+
211
+ export const HelpText: Story = {
212
+ args: {
213
+ ...defaultArgs,
214
+ helpText: 'In order to submit the form, this field is required.',
215
+ },
216
+ render: (args) => {
217
+ const [value, setValue] = useState('');
218
+
219
+ useEffect(() => {
220
+ setValue(args.value);
221
+ }, [args.value]);
222
+
223
+ const handleChange = (e: { target: { value: SetStateAction<string> } }) => {
224
+ setValue(e.target.value);
225
+ };
226
+
227
+ return <PasswordInput {...args} value={value} onChange={handleChange} />;
228
+ },
229
+ };
230
+
231
+ export const Required: Story = {
232
+ args: {
233
+ ...defaultArgs,
234
+ isRequired: true,
235
+ },
236
+ render: (args) => {
237
+ const [value, setValue] = useState('');
238
+
239
+ useEffect(() => {
240
+ setValue(args.value);
241
+ }, [args.value]);
242
+
243
+ const handleChange = (e: { target: { value: SetStateAction<string> } }) => {
244
+ setValue(e.target.value);
245
+ };
246
+
247
+ return <PasswordInput {...args} value={value} onChange={handleChange} />;
248
+ },
249
+ };
250
+
251
+ export const NoTogglePasswordVisibility: Story = {
252
+ args: {
253
+ ...defaultArgs,
254
+ hasShowPassword: false,
255
+ },
256
+ render: (args) => {
257
+ const [value, setValue] = useState('');
258
+
259
+ useEffect(() => {
260
+ setValue(args.value);
261
+ }, [args.value]);
262
+
263
+ const handleChange = (e: { target: { value: SetStateAction<string> } }) => {
264
+ setValue(e.target.value);
265
+ };
266
+
267
+ return <PasswordInput {...args} value={value} onChange={handleChange} />;
268
+ },
269
+ };
@@ -0,0 +1,82 @@
1
+ import React, { useState } from 'react';
2
+ import { Icon } from '@/components/icons';
3
+ import { Label } from '../subcomponents/Label';
4
+ import { ErrorList } from '../subcomponents/ErrorList';
5
+
6
+ export interface PasswordInputProps {
7
+ ref?: React.LegacyRef<HTMLInputElement>;
8
+ label: string;
9
+ name: string;
10
+ placeholder: string;
11
+ value: string;
12
+ onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
13
+ isRequired?: boolean;
14
+ isDisabled?: boolean;
15
+ errorList?: string[];
16
+ helpText?: string;
17
+ hasHiddenLabel?: boolean;
18
+ hasShowPassword?: boolean;
19
+ }
20
+
21
+ export const PasswordInput = ({
22
+ label,
23
+ name,
24
+ placeholder,
25
+ value,
26
+ onChange,
27
+ isRequired,
28
+ isDisabled,
29
+ errorList,
30
+ helpText,
31
+ hasHiddenLabel,
32
+ hasShowPassword = true,
33
+ ...rest
34
+ }: PasswordInputProps) => {
35
+ const hasErrors = errorList && errorList.length > 0;
36
+
37
+ const [showPassword, setShowPassword] = useState(false);
38
+
39
+ const handleShowPassword = () => {
40
+ setShowPassword((prevShowPassword) => !prevShowPassword);
41
+ };
42
+
43
+ return (
44
+ <div className="form-control">
45
+ <Label label={label} name={name} isRequired={isRequired} hasHiddenLabel={hasHiddenLabel} />
46
+ <div className="password-input-wrapper">
47
+ <Icon name="lock" data-testid={`${name}-embedded-icon`} className="embedded-icon" />
48
+ <input
49
+ data-testid={`form-password-input-${name}`}
50
+ name={name}
51
+ type={showPassword ? 'text' : 'password'}
52
+ disabled={isDisabled}
53
+ required={isRequired}
54
+ placeholder={placeholder}
55
+ value={value}
56
+ onChange={onChange}
57
+ className={`password-input ${hasErrors ? 'error' : ''} password-input--has-icon`}
58
+ aria-invalid={hasErrors}
59
+ aria-describedby={hasErrors || helpText ? `${name}-helper` : undefined}
60
+ aria-required={isRequired}
61
+ aria-label={label}
62
+ {...rest}
63
+ />
64
+ {hasShowPassword && (
65
+ <Icon
66
+ name={showPassword ? 'fa-eye-slash' : 'eye'}
67
+ data-testid={`${name}-${showPassword ? 'hide' : 'show'}-password-icon`}
68
+ size="md"
69
+ onClick={handleShowPassword}
70
+ className="toggle-show-password-icon"
71
+ />
72
+ )}
73
+ </div>
74
+ {hasErrors && <ErrorList errorList={errorList} name={name} />}
75
+ {helpText && (
76
+ <div data-testid={`${name}-help-text`} className="help-text" id={`${name}-helper`}>
77
+ {helpText}
78
+ </div>
79
+ )}
80
+ </div>
81
+ );
82
+ };
@@ -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
+ errorList={['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';