@indico-data/design-system 2.4.1 → 2.5.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 (30) hide show
  1. package/lib/index.css +119 -0
  2. package/lib/index.d.ts +2 -2
  3. package/lib/index.esm.css +119 -0
  4. package/lib/index.esm.js.map +1 -1
  5. package/lib/index.js.map +1 -1
  6. package/lib/src/components/forms/input/Input.d.ts +18 -0
  7. package/lib/src/components/forms/input/Input.stories.d.ts +12 -0
  8. package/lib/src/components/forms/input/__tests__/Input.test.d.ts +1 -0
  9. package/lib/src/components/forms/input/index.d.ts +1 -0
  10. package/lib/src/components/forms/subcomponents/ErrorList.d.ts +6 -0
  11. package/lib/src/components/forms/subcomponents/Label.d.ts +8 -0
  12. package/lib/src/components/forms/subcomponents/__tests__/ErrorList.test.d.ts +1 -0
  13. package/lib/src/components/forms/subcomponents/__tests__/Label.test.d.ts +1 -0
  14. package/lib/src/components/index.d.ts +1 -0
  15. package/lib/src/components/table/Table.d.ts +2 -2
  16. package/package.json +1 -1
  17. package/src/components/forms/input/Input.mdx +19 -0
  18. package/src/components/forms/input/Input.stories.tsx +301 -0
  19. package/src/components/forms/input/Input.tsx +86 -0
  20. package/src/components/forms/input/__tests__/Input.test.tsx +213 -0
  21. package/src/components/forms/input/index.ts +1 -0
  22. package/src/components/forms/input/styles/Input.scss +112 -0
  23. package/src/components/forms/subcomponents/ErrorList.tsx +14 -0
  24. package/src/components/forms/subcomponents/Label.tsx +20 -0
  25. package/src/components/forms/subcomponents/__tests__/ErrorList.test.tsx +16 -0
  26. package/src/components/forms/subcomponents/__tests__/Label.test.tsx +33 -0
  27. package/src/components/index.ts +1 -0
  28. package/src/components/table/Table.tsx +2 -2
  29. package/src/styles/_typography.scss +29 -11
  30. package/src/styles/index.scss +1 -0
@@ -0,0 +1,213 @@
1
+ import { render, screen, act } from '@testing-library/react';
2
+ import { Input } from '@/components/forms/input/Input';
3
+ import { ChangeEvent } from 'react';
4
+ import userEvent from '@testing-library/user-event';
5
+
6
+ const handleOnChange = jest.fn();
7
+
8
+ describe('Input', () => {
9
+ it('renders the input field', () => {
10
+ render(
11
+ <Input
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
+ iconName="user"
18
+ isClearable={true}
19
+ ref={undefined}
20
+ value={''}
21
+ onChange={handleOnChange}
22
+ />,
23
+ );
24
+ expect(screen.getByText('Enter your name')).toBeInTheDocument();
25
+ });
26
+ it('shows an x when the text is clearable', () => {
27
+ render(
28
+ <Input
29
+ isRequired={true}
30
+ label="Enter your name"
31
+ helpText="In order to submit the form, this field is required."
32
+ name="name"
33
+ placeholder="Please enter a value"
34
+ iconName="user"
35
+ isClearable={true}
36
+ ref={undefined}
37
+ value={''}
38
+ onChange={handleOnChange}
39
+ />,
40
+ );
41
+ const icon = screen.getByTestId('name-clearable-icon');
42
+ expect(icon).toBeInTheDocument();
43
+ expect(icon).toBeVisible();
44
+ });
45
+ it('does not show an x when the text is not clearable', () => {
46
+ render(
47
+ <Input
48
+ isRequired={true}
49
+ label="Enter your name"
50
+ helpText="In order to submit the form, this field is required."
51
+ name="name"
52
+ placeholder="Please enter a value"
53
+ iconName="user"
54
+ isClearable={false}
55
+ ref={undefined}
56
+ value={''}
57
+ onChange={handleOnChange}
58
+ />,
59
+ );
60
+ const icon = screen.queryByTestId('name-clearable-icon');
61
+ expect(icon).not.toBeInTheDocument();
62
+ expect(icon).toBe(null);
63
+ });
64
+ it('clicking on the x clears the value', async () => {
65
+ render(
66
+ <Input
67
+ isRequired={true}
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
+ iconName="user"
73
+ isClearable={true}
74
+ ref={undefined}
75
+ value={'test'}
76
+ onChange={handleOnChange}
77
+ />,
78
+ );
79
+ const input = screen.getByTestId('form-input-name');
80
+ const icon = screen.getByTestId('name-clearable-icon');
81
+ expect(input).toHaveValue('test');
82
+ await userEvent.click(icon);
83
+ expect(handleOnChange).toHaveBeenCalledWith({
84
+ target: { value: '' },
85
+ } as ChangeEvent<HTMLInputElement>);
86
+ });
87
+
88
+ it('it renders an icon on the left when one exists', () => {
89
+ render(
90
+ <Input
91
+ isRequired={true}
92
+ label="Enter your name"
93
+ helpText="In order to submit the form, this field is required."
94
+ name="name"
95
+ placeholder="Please enter a value"
96
+ iconName="user"
97
+ isClearable={true}
98
+ ref={undefined}
99
+ value={'test'}
100
+ onChange={handleOnChange}
101
+ />,
102
+ );
103
+ const icon = screen.getByTestId('name-embedded-icon');
104
+ expect(icon).toBeInTheDocument();
105
+ expect(icon).toBeVisible();
106
+ });
107
+ it('it does not render an embedded icon when one does not exist', () => {
108
+ render(
109
+ <Input
110
+ isRequired={true}
111
+ label="Enter your name"
112
+ helpText="In order to submit the form, this field is required."
113
+ name="name"
114
+ placeholder="Please enter a value"
115
+ isClearable={true}
116
+ ref={undefined}
117
+ value={'test'}
118
+ onChange={handleOnChange}
119
+ />,
120
+ );
121
+ const icon = screen.queryByTestId('name-embedded-icon');
122
+ expect(icon).not.toBeInTheDocument();
123
+ expect(icon).toBe(null);
124
+ });
125
+ it('adds the error class when errors exist', () => {
126
+ render(
127
+ <Input
128
+ isRequired={true}
129
+ errorList={['You require a username value.']}
130
+ label="Enter your name"
131
+ helpText="In order to submit the form, this field is required."
132
+ name="name"
133
+ placeholder="Please enter a value"
134
+ isClearable={true}
135
+ ref={undefined}
136
+ value={'test'}
137
+ onChange={handleOnChange}
138
+ />,
139
+ );
140
+ const input = screen.getByTestId('form-input-name');
141
+ expect(input).toHaveClass('error');
142
+ });
143
+ it('does not highlight the input when no errors exist', () => {
144
+ render(
145
+ <Input
146
+ isRequired={true}
147
+ label="Enter your name"
148
+ helpText="In order to submit the form, this field is required."
149
+ name="name"
150
+ placeholder="Please enter a value"
151
+ isClearable={true}
152
+ ref={undefined}
153
+ value={'test'}
154
+ onChange={handleOnChange}
155
+ />,
156
+ );
157
+ const input = screen.getByTestId('form-input-name');
158
+ expect(input).not.toHaveClass('error');
159
+ });
160
+ it('renders help text when help text exists', () => {
161
+ render(
162
+ <Input
163
+ isRequired={true}
164
+ label="Enter your name"
165
+ helpText="In order to submit the form, this field is required."
166
+ name="name"
167
+ placeholder="Please enter a value"
168
+ isClearable={true}
169
+ ref={undefined}
170
+ value={'test'}
171
+ onChange={handleOnChange}
172
+ />,
173
+ );
174
+ const helpText = screen.getByText('In order to submit the form, this field is required.');
175
+ expect(helpText).toBeInTheDocument();
176
+ expect(helpText).toBeVisible();
177
+ });
178
+ it('does not render help text when help text does not exist', () => {
179
+ render(
180
+ <Input
181
+ isRequired={true}
182
+ label="Enter your name"
183
+ name="name"
184
+ placeholder="Please enter a value"
185
+ isClearable={true}
186
+ ref={undefined}
187
+ value={'test'}
188
+ onChange={handleOnChange}
189
+ />,
190
+ );
191
+ const helpText = screen.queryByTestId('name-help-text');
192
+ expect(helpText).not.toBeInTheDocument();
193
+ expect(helpText).toBeNull();
194
+ });
195
+
196
+ it('emits the value when user types', async () => {
197
+ const handleOnChange = jest.fn();
198
+ render(
199
+ <Input
200
+ isRequired={true}
201
+ label="Enter your name"
202
+ name="name"
203
+ placeholder="Please enter a value"
204
+ ref={undefined}
205
+ value={''}
206
+ onChange={handleOnChange}
207
+ />,
208
+ );
209
+ const input = screen.getByTestId('form-input-name');
210
+ await userEvent.type(input, 't');
211
+ expect(handleOnChange).toHaveBeenCalled();
212
+ });
213
+ });
@@ -0,0 +1 @@
1
+ export { Input } from './Input';
@@ -0,0 +1,112 @@
1
+ // Common Variables
2
+ :root,
3
+ :root [data-theme='light'],
4
+ :root [data-theme='dark'] {
5
+ // Typography
6
+ --pf-input-background-color: var(--pf-white-color);
7
+ --pf-input-border-color: var(--pf-gray-color);
8
+ --pf-input-text-color: var(--pf-gray-color);
9
+ --pf-input-placeholder-text-color: var(--pf-gray-color-300);
10
+ --pf-input-help-text-color: var(--pf-gray-color-400);
11
+
12
+ // input Radius
13
+ --pf-input-rounded: var(--pf-rounded);
14
+ }
15
+
16
+ // Dark Theme Specific Variables
17
+ :root [data-theme='dark'] {
18
+ --pf-input-background-color: var(--pf-primary-color);
19
+ --pf-input-border-color: var(--pf-gray-color-100);
20
+ --pf-input-text-color: var(--pf-gray-color-100);
21
+ --pf-input-placeholder-text-color: var(--pf-gray-color);
22
+ --pf-input-help-text-color: var(--pf-gray-color-200);
23
+ }
24
+
25
+ .input {
26
+ background-color: var(--pf-input-background-color);
27
+ border: 1px solid var(--pf-input-border-color);
28
+ border-radius: var(--pf-input-rounded);
29
+ color: var(--pf-input-text-color);
30
+ padding: 10px;
31
+ width: 100%;
32
+ box-sizing: border-box;
33
+ height: 36px;
34
+ &::placeholder {
35
+ color: var(--pf-input-placeholder-text-color);
36
+ }
37
+
38
+ &:focus {
39
+ border-color: var(--pf-primary-color);
40
+ }
41
+
42
+ &.error {
43
+ border-color: var(--pf-error-color);
44
+ }
45
+
46
+ &.success {
47
+ border-color: var(--pf-success-color);
48
+ }
49
+
50
+ &.warning {
51
+ border-color: var(--pf-warning-color);
52
+ }
53
+
54
+ &.info {
55
+ border-color: var(--pf-info-color);
56
+ }
57
+
58
+ &:disabled {
59
+ background-color: var(--pf-gray-color-100);
60
+ border-color: var(--pf-gray-color-300);
61
+ color: var(--pf-gray-color-400);
62
+ }
63
+ &--has-icon {
64
+ padding-left: var(--pf-padding-7);
65
+ }
66
+ }
67
+
68
+ .form-control {
69
+ .error-list {
70
+ list-style: none;
71
+ padding: 0;
72
+ margin: 0;
73
+ margin-top: var(--pf-margin-2);
74
+ margin-bottom: var(--pf-margin-2);
75
+ color: var(--pf-error-color);
76
+ }
77
+ .help-text {
78
+ margin-top: var(--pf-margin-2);
79
+ margin-bottom: var(--pf-margin-2);
80
+ color: var(--pf-input-help-text-color);
81
+ font-size: var(--pf-font-size-subtitle2);
82
+ }
83
+ .input-wrapper {
84
+ position: relative;
85
+ .embedded-icon {
86
+ position: absolute;
87
+ top: 10px;
88
+ left: var(--pf-margin-2);
89
+ color: var(--pf-input-text-color);
90
+ }
91
+ .clearable-icon {
92
+ position: absolute;
93
+ top: var(--pf-margin-3);
94
+ right: var(--pf-margin-2);
95
+ color: var(--pf-input-text-color);
96
+ }
97
+ }
98
+ .is-visually-hidden {
99
+ position: absolute;
100
+ width: 1px;
101
+ height: 1px;
102
+ padding: 0;
103
+ margin: -1px;
104
+ overflow: hidden;
105
+ clip: rect(0, 0, 0, 0);
106
+ white-space: nowrap;
107
+ border: 0;
108
+ }
109
+ .form-label {
110
+ margin-bottom: var(--pf-margin-2);
111
+ }
112
+ }
@@ -0,0 +1,14 @@
1
+ interface ErrorListProps {
2
+ name: string;
3
+ errorList: string[];
4
+ }
5
+
6
+ export const ErrorList = ({ errorList, name }: ErrorListProps) => {
7
+ return (
8
+ <ul className="error-list" id={`${name}-helper`}>
9
+ {(errorList ?? []).map((error, index) => (
10
+ <li key={index}>{error}</li>
11
+ ))}
12
+ </ul>
13
+ );
14
+ };
@@ -0,0 +1,20 @@
1
+ interface LabelProps {
2
+ label: string;
3
+ name: string;
4
+ isRequired?: boolean;
5
+ hasHiddenLabel?: boolean;
6
+ }
7
+
8
+ export const Label = ({ label, name, isRequired, hasHiddenLabel }: LabelProps) => {
9
+ return (
10
+ <div
11
+ data-testid={`${name}-testId`}
12
+ className={`form-label ${hasHiddenLabel ? 'is-visually-hidden' : ''}`}
13
+ >
14
+ <label htmlFor={`${name}`}>
15
+ {label}
16
+ {isRequired ? <span className="text-error"> *</span> : ''}
17
+ </label>
18
+ </div>
19
+ );
20
+ };
@@ -0,0 +1,16 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import { ErrorList } from '@/components/forms/subcomponents/ErrorList';
3
+
4
+ describe('ErrorList', () => {
5
+ it('renders the error list', () => {
6
+ render(<ErrorList name="name" errorList={['error1', 'error2']} />);
7
+ expect(screen.getByText('error1')).toBeInTheDocument();
8
+ expect(screen.getByText('error2')).toBeInTheDocument();
9
+ });
10
+
11
+ it('does not render the error list when it is empty', () => {
12
+ render(<ErrorList name="name" errorList={[]} />);
13
+ expect(screen.queryByText('error1')).not.toBeInTheDocument();
14
+ expect(screen.queryByText('error2')).not.toBeInTheDocument();
15
+ });
16
+ });
@@ -0,0 +1,33 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import { Label } from '@/components/forms/subcomponents/Label';
3
+
4
+ describe('Label', () => {
5
+ it('renders the `required` astherisc when the label is required', () => {
6
+ render(<Label label={'name'} name={'name'} isRequired={true} hasHiddenLabel={false} />);
7
+ expect(screen.getByText('*')).toBeInTheDocument();
8
+ expect(screen.getByText('*')).toBeVisible();
9
+ });
10
+ it('does not render the `required` astherisc when the label is not required', () => {
11
+ render(<Label label={'name'} name={'name'} isRequired={false} hasHiddenLabel={false} />);
12
+ expect(screen.queryByText('*')).not.toBeInTheDocument();
13
+ expect(screen.queryByText('*')).toBeNull();
14
+ });
15
+ it('renders the label for screen readers while hidden visually', () => {
16
+ render(<Label label={'name'} name={'name'} isRequired={false} hasHiddenLabel={true} />);
17
+ expect(screen.getByText('name')).toBeInTheDocument();
18
+ expect(screen.getByText('name')).toBeVisible();
19
+ });
20
+ it('renders the label text', () => {
21
+ render(<Label label={'name'} name={'name'} isRequired={false} hasHiddenLabel={false} />);
22
+ const label = screen.getByTestId('name-testId');
23
+ expect(label).toBeInTheDocument();
24
+ expect(label).toBeVisible();
25
+ expect(label).not.toHaveClass('is-visually-hidden');
26
+ });
27
+
28
+ it('renders the label text when the label is hidden', () => {
29
+ render(<Label label={'name'} name={'name'} isRequired={false} hasHiddenLabel={true} />);
30
+ const label = screen.getByTestId('name-testId');
31
+ expect(label).toHaveClass('is-visually-hidden');
32
+ });
33
+ });
@@ -2,3 +2,4 @@ export { Container, Col, Row } from './grid';
2
2
  export { Button } from './button';
3
3
  export { Icon } from './icons';
4
4
  export { Table } from './table';
5
+ export { Input } from './forms/input';
@@ -15,8 +15,8 @@ export type TableProps<T> = Omit<
15
15
  > & {
16
16
  isDisabled?: boolean;
17
17
  isLoading?: boolean;
18
- direction: Direction;
19
- subHeaderAlign: Alignment;
18
+ direction?: Direction;
19
+ subHeaderAlign?: Alignment;
20
20
  };
21
21
 
22
22
  export const Table = <T,>(props: TableProps<T>) => {
@@ -5,14 +5,17 @@ html {
5
5
  font-family: var(--pf-font-family-base);
6
6
  }
7
7
  // Font Sizing and Family
8
- @each $tag, $size in (
9
- h1: --pf-font-size-h1,
10
- h2: --pf-font-size-h2,
11
- h3: --pf-font-size-h3,
12
- h4: --pf-font-size-h4,
13
- h5: --pf-font-size-h5,
14
- p: --pf-font-size-body) {
15
-
8
+ @each $tag,
9
+ $size
10
+ in (
11
+ h1: --pf-font-size-h1,
12
+ h2: --pf-font-size-h2,
13
+ h3: --pf-font-size-h3,
14
+ h4: --pf-font-size-h4,
15
+ h5: --pf-font-size-h5,
16
+ p: --pf-font-size-body
17
+ )
18
+ {
16
19
  #{$tag},
17
20
  .text-#{$tag} {
18
21
  font-size: var(#{$size});
@@ -21,9 +24,8 @@ html {
21
24
  padding: 0;
22
25
 
23
26
  // If the tag is not 'p', make it bold
24
- @if $tag !='p' {
27
+ @if $tag != 'p' {
25
28
  font-weight: var(--pf-font-weight-bold);
26
-
27
29
  }
28
30
  }
29
31
  }
@@ -46,7 +48,6 @@ html {
46
48
  font-size: var(--pf-font-size-overine);
47
49
  }
48
50
 
49
-
50
51
  // Transform
51
52
  .text-capitalize {
52
53
  text-transform: capitalize;
@@ -114,3 +115,20 @@ html {
114
115
  .text-break--word {
115
116
  overflow-wrap: break-word;
116
117
  }
118
+
119
+ // Text Colors
120
+ .text-error {
121
+ color: var(--pf-error-color);
122
+ }
123
+
124
+ .text-warning {
125
+ color: var(--pf-warning-color);
126
+ }
127
+
128
+ .text-success {
129
+ color: var(--pf-success-color);
130
+ }
131
+
132
+ .text-info {
133
+ color: var(--pf-info-color);
134
+ }
@@ -6,6 +6,7 @@
6
6
  @import '../components/icons/styles/Icon.scss';
7
7
  @import '../components/grid/styles/Grid.scss';
8
8
  @import '../components/table/styles/Table.scss';
9
+ @import '../components/forms/input/styles/Input.scss';
9
10
 
10
11
  @import 'typography';
11
12
  @import 'colors';