@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.
Files changed (38) hide show
  1. package/lib/index.css +216 -4
  2. package/lib/index.d.ts +41 -2
  3. package/lib/index.esm.css +216 -4
  4. package/lib/index.esm.js +19 -3
  5. package/lib/index.esm.js.map +1 -1
  6. package/lib/index.js +20 -2
  7. package/lib/index.js.map +1 -1
  8. package/lib/src/components/forms/input/Input.d.ts +2 -1
  9. package/lib/src/components/forms/passwordInput/PasswordInput.d.ts +16 -0
  10. package/lib/src/components/forms/passwordInput/PasswordInput.stories.d.ts +11 -0
  11. package/lib/src/components/forms/passwordInput/__tests__/PasswordInput.test.d.ts +1 -0
  12. package/lib/src/components/forms/passwordInput/index.d.ts +1 -0
  13. package/lib/src/components/forms/textarea/Textarea.d.ts +22 -0
  14. package/lib/src/components/forms/textarea/Textarea.stories.d.ts +11 -0
  15. package/lib/src/components/forms/textarea/__tests__/Textarea.test.d.ts +1 -0
  16. package/lib/src/components/forms/textarea/index.d.ts +1 -0
  17. package/lib/src/components/index.d.ts +2 -0
  18. package/lib/src/index.d.ts +2 -0
  19. package/package.json +1 -1
  20. package/src/components/forms/checkbox/styles/Checkbox.scss +1 -1
  21. package/src/components/forms/input/Input.stories.tsx +70 -69
  22. package/src/components/forms/input/Input.tsx +3 -1
  23. package/src/components/forms/input/styles/Input.scss +11 -3
  24. package/src/components/forms/passwordInput/PasswordInput.mdx +28 -0
  25. package/src/components/forms/passwordInput/PasswordInput.stories.tsx +269 -0
  26. package/src/components/forms/passwordInput/PasswordInput.tsx +82 -0
  27. package/src/components/forms/passwordInput/__tests__/PasswordInput.test.tsx +129 -0
  28. package/src/components/forms/passwordInput/index.ts +1 -0
  29. package/src/components/forms/passwordInput/styles/PasswordInput.scss +120 -0
  30. package/src/components/forms/textarea/Textarea.mdx +19 -0
  31. package/src/components/forms/textarea/Textarea.stories.tsx +363 -0
  32. package/src/components/forms/textarea/Textarea.tsx +85 -0
  33. package/src/components/forms/textarea/__tests__/Textarea.test.tsx +103 -0
  34. package/src/components/forms/textarea/index.ts +1 -0
  35. package/src/components/forms/textarea/styles/Textarea.scss +102 -0
  36. package/src/components/index.ts +2 -0
  37. package/src/index.ts +2 -0
  38. package/src/styles/index.scss +2 -0
@@ -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';
@@ -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
+ }
@@ -0,0 +1,19 @@
1
+ import { Canvas, Meta, Controls } from '@storybook/blocks';
2
+ import * as Textarea from './Textarea.stories';
3
+
4
+ <Meta title="Forms/Textarea" name="Textarea" />
5
+
6
+ # Textarea
7
+
8
+ The Textarea 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={Textarea.Default}
12
+ source={{
13
+ code: `
14
+ <Textarea name="first_name" isRequired helpText="This Is Help Text" placeholder="This is a placeholder" value="''" onChange={handleChange} label="Label Name" />
15
+ `,
16
+ }}
17
+ />
18
+
19
+ <Controls of={Textarea.Default} />