@indico-data/design-system 2.10.0 → 2.12.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 (61) hide show
  1. package/lib/index.css +99 -8
  2. package/lib/index.d.ts +47 -15
  3. package/lib/index.esm.css +99 -8
  4. package/lib/index.esm.js +61 -28
  5. package/lib/index.esm.js.map +1 -1
  6. package/lib/index.js +62 -27
  7. package/lib/index.js.map +1 -1
  8. package/lib/src/components/forms/checkbox/Checkbox.d.ts +2 -1
  9. package/lib/src/components/forms/form/Form.d.ts +14 -0
  10. package/lib/src/components/forms/form/Form.stories.d.ts +8 -0
  11. package/lib/src/components/forms/form/index.d.ts +1 -0
  12. package/lib/src/components/forms/input/Input.d.ts +4 -4
  13. package/lib/src/components/forms/passwordInput/PasswordInput.d.ts +4 -3
  14. package/lib/src/components/forms/radio/Radio.d.ts +2 -1
  15. package/lib/src/components/forms/select/Select.d.ts +6 -0
  16. package/lib/src/components/forms/select/Select.stories.d.ts +7 -0
  17. package/lib/src/components/forms/select/__tests__/Select.test.d.ts +1 -0
  18. package/lib/src/components/forms/select/index.d.ts +1 -0
  19. package/lib/src/components/forms/select/types.d.ts +6 -0
  20. package/lib/src/components/forms/subcomponents/DisplayFormError.d.ts +5 -0
  21. package/lib/src/components/forms/textarea/Textarea.d.ts +4 -3
  22. package/lib/src/components/forms/toggle/Toggle.d.ts +2 -1
  23. package/lib/src/components/index.d.ts +2 -0
  24. package/lib/src/index.d.ts +2 -0
  25. package/lib/src/types.d.ts +2 -0
  26. package/package.json +5 -2
  27. package/src/components/forms/checkbox/Checkbox.stories.tsx +2 -2
  28. package/src/components/forms/checkbox/Checkbox.tsx +32 -41
  29. package/src/components/forms/form/Form.mdx +134 -0
  30. package/src/components/forms/form/Form.stories.tsx +413 -0
  31. package/src/components/forms/form/Form.tsx +64 -0
  32. package/src/components/forms/form/__tests__/Form.test.tsx +35 -0
  33. package/src/components/forms/form/index.ts +1 -0
  34. package/src/components/forms/form/styles/Form.scss +3 -0
  35. package/src/components/forms/input/Input.tsx +66 -65
  36. package/src/components/forms/input/__tests__/Input.test.tsx +2 -13
  37. package/src/components/forms/input/styles/Input.scss +1 -8
  38. package/src/components/forms/passwordInput/PasswordInput.stories.tsx +11 -12
  39. package/src/components/forms/passwordInput/PasswordInput.tsx +63 -59
  40. package/src/components/forms/passwordInput/__tests__/PasswordInput.test.tsx +1 -1
  41. package/src/components/forms/radio/Radio.tsx +32 -35
  42. package/src/components/forms/select/Select.stories.tsx +118 -0
  43. package/src/components/forms/select/Select.tsx +43 -0
  44. package/src/components/forms/select/__tests__/Select.test.tsx +67 -0
  45. package/src/components/forms/select/index.ts +1 -0
  46. package/src/components/forms/select/styles/Select.scss +120 -0
  47. package/src/components/forms/select/types.ts +6 -0
  48. package/src/components/forms/subcomponents/DisplayFormError.tsx +7 -0
  49. package/src/components/forms/textarea/Textarea.stories.tsx +15 -21
  50. package/src/components/forms/textarea/Textarea.tsx +64 -62
  51. package/src/components/forms/textarea/__tests__/Textarea.test.tsx +1 -1
  52. package/src/components/forms/textarea/styles/Textarea.scss +1 -1
  53. package/src/components/forms/toggle/Toggle.tsx +30 -37
  54. package/src/components/index.ts +2 -0
  55. package/src/index.ts +2 -0
  56. package/src/styles/index.scss +3 -1
  57. package/src/types.ts +3 -0
  58. package/lib/src/components/forms/subcomponents/ErrorList.d.ts +0 -6
  59. package/src/components/forms/subcomponents/ErrorList.tsx +0 -14
  60. package/src/components/forms/subcomponents/__tests__/ErrorList.test.tsx +0 -16
  61. /package/lib/src/components/forms/{subcomponents/__tests__/ErrorList.test.d.ts → form/__tests__/Form.test.d.ts} +0 -0
@@ -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
- errorList={['You require a username value.']}
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
- .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
- }
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);
@@ -91,16 +91,16 @@ const meta: Meta = {
91
91
  },
92
92
  defaultValue: { summary: 'false' },
93
93
  },
94
- errorList: {
94
+ errorMessage: {
95
95
  control: false,
96
96
  description: 'An array of error messages',
97
97
  table: {
98
98
  category: 'Props',
99
99
  type: {
100
- summary: 'string[]',
100
+ summary: 'string',
101
101
  },
102
102
  },
103
- defaultValue: { summary: '[]' },
103
+ defaultValue: { summary: undefined },
104
104
  },
105
105
  helpText: {
106
106
  control: 'text',
@@ -151,14 +151,13 @@ export const Default: Story = {
151
151
  hasHiddenLabel: false,
152
152
  hasShowPassword: true,
153
153
  isDisabled: false,
154
- errorList: [],
155
- value: '',
154
+ errorMessage: '',
156
155
  },
157
156
  render: (args) => {
158
157
  const [value, setValue] = useState('');
159
158
 
160
159
  useEffect(() => {
161
- setValue(args.value);
160
+ setValue(args.value || '');
162
161
  }, [args.value]);
163
162
 
164
163
  const handleChange = (e: { target: { value: SetStateAction<string> } }) => {
@@ -171,13 +170,13 @@ export const Default: Story = {
171
170
  export const Errors: Story = {
172
171
  args: {
173
172
  ...defaultArgs,
174
- errorList: ['You require a password value.'],
173
+ errorMessage: 'You require a password value.',
175
174
  },
176
175
  render: (args) => {
177
176
  const [value, setValue] = useState('');
178
177
 
179
178
  useEffect(() => {
180
- setValue(args.value);
179
+ setValue(args.value || '');
181
180
  }, [args.value]);
182
181
 
183
182
  const handleChange = (e: { target: { value: SetStateAction<string> } }) => {
@@ -197,7 +196,7 @@ export const HiddenLabel: Story = {
197
196
  const [value, setValue] = useState('');
198
197
 
199
198
  useEffect(() => {
200
- setValue(args.value);
199
+ setValue(args.value || '');
201
200
  }, [args.value]);
202
201
 
203
202
  const handleChange = (e: { target: { value: SetStateAction<string> } }) => {
@@ -217,7 +216,7 @@ export const HelpText: Story = {
217
216
  const [value, setValue] = useState('');
218
217
 
219
218
  useEffect(() => {
220
- setValue(args.value);
219
+ setValue(args.value || '');
221
220
  }, [args.value]);
222
221
 
223
222
  const handleChange = (e: { target: { value: SetStateAction<string> } }) => {
@@ -237,7 +236,7 @@ export const Required: Story = {
237
236
  const [value, setValue] = useState('');
238
237
 
239
238
  useEffect(() => {
240
- setValue(args.value);
239
+ setValue(args.value || '');
241
240
  }, [args.value]);
242
241
 
243
242
  const handleChange = (e: { target: { value: SetStateAction<string> } }) => {
@@ -257,7 +256,7 @@ export const NoTogglePasswordVisibility: Story = {
257
256
  const [value, setValue] = useState('');
258
257
 
259
258
  useEffect(() => {
260
- setValue(args.value);
259
+ setValue(args.value || '');
261
260
  }, [args.value]);
262
261
 
263
262
  const handleChange = (e: { target: { value: SetStateAction<string> } }) => {
@@ -1,82 +1,86 @@
1
1
  import React, { useState } from 'react';
2
2
  import { Icon } from '@/components/icons';
3
3
  import { Label } from '../subcomponents/Label';
4
- import { ErrorList } from '../subcomponents/ErrorList';
4
+ import { DisplayFormError } from '../subcomponents/DisplayFormError';
5
5
 
6
6
  export interface PasswordInputProps {
7
7
  ref?: React.LegacyRef<HTMLInputElement>;
8
8
  label: string;
9
+ value?: string | undefined;
9
10
  name: string;
10
11
  placeholder: string;
11
- value: string;
12
12
  onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
13
13
  isRequired?: boolean;
14
14
  isDisabled?: boolean;
15
- errorList?: string[];
15
+ errorMessage?: string | undefined;
16
16
  helpText?: string;
17
17
  hasHiddenLabel?: boolean;
18
18
  hasShowPassword?: boolean;
19
+ defaultValue?: string;
19
20
  }
20
21
 
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;
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;
36
40
 
37
- const [showPassword, setShowPassword] = useState(false);
41
+ const [showPassword, setShowPassword] = useState(false);
38
42
 
39
- const handleShowPassword = () => {
40
- setShowPassword((prevShowPassword) => !prevShowPassword);
41
- };
43
+ const handleShowPassword = () => {
44
+ setShowPassword((prevShowPassword) => !prevShowPassword);
45
+ };
42
46
 
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"
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}
71
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>
72
82
  )}
73
83
  </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
- };
84
+ );
85
+ },
86
+ );
@@ -48,7 +48,7 @@ describe('Input', () => {
48
48
  render(
49
49
  <PasswordInput
50
50
  isRequired
51
- errorList={['You require a username value.']}
51
+ errorMessage="You require a username value."
52
52
  label="Enter your name"
53
53
  helpText="In order to submit the form, this field is required."
54
54
  name="name"
@@ -8,40 +8,37 @@ export interface RadioProps {
8
8
  value?: string;
9
9
  onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
10
10
  isDisabled?: boolean;
11
+ defaultChecked?: boolean;
11
12
  }
12
-
13
- export const Radio = ({
14
- ref,
15
- id,
16
- label,
17
- name,
18
- value,
19
- onChange,
20
- isDisabled,
21
- ...rest
22
- }: RadioProps) => {
23
- return (
24
- <div className="form-control">
25
- <div className="radio-wrapper">
26
- <input
27
- data-testid={`form-radio-input-${name}`}
28
- {...rest}
29
- className="radio-input"
30
- type="radio"
31
- id={id}
32
- name={name}
33
- value={value}
34
- disabled={isDisabled}
35
- ref={ref}
36
- onChange={onChange}
37
- tabIndex={0}
38
- aria-describedby={id}
39
- aria-label={label}
40
- />
41
- <label htmlFor={id} className="radio-input-label" data-testid={`label-radio-input-${name}`}>
42
- {label}
43
- </label>
13
+ export const Radio = React.forwardRef<HTMLInputElement, RadioProps>(
14
+ ({ id, label, name, value, onChange, isDisabled, ...rest }, ref) => {
15
+ return (
16
+ <div className="form-control">
17
+ <div className="radio-wrapper">
18
+ <input
19
+ data-testid={`form-radio-input-${name}`}
20
+ className="radio-input"
21
+ type="radio"
22
+ id={id}
23
+ name={name}
24
+ value={value}
25
+ disabled={isDisabled}
26
+ ref={ref}
27
+ onChange={onChange}
28
+ tabIndex={0}
29
+ aria-describedby={id}
30
+ aria-label={label}
31
+ {...rest}
32
+ />
33
+ <label
34
+ htmlFor={id}
35
+ className="radio-input-label"
36
+ data-testid={`label-radio-input-${name}`}
37
+ >
38
+ {label}
39
+ </label>
40
+ </div>
44
41
  </div>
45
- </div>
46
- );
47
- };
42
+ );
43
+ },
44
+ );
@@ -0,0 +1,118 @@
1
+ import { Meta, StoryObj } from '@storybook/react';
2
+ import { Select, SelectProps } from './Select';
3
+ import { SelectOption } from './types';
4
+
5
+ const meta: Meta<SelectProps<SelectOption>> = {
6
+ title: 'Forms/Select',
7
+ component: Select,
8
+ argTypes: {
9
+ options: {
10
+ control: 'object',
11
+ description: 'Options for the select component',
12
+ table: {
13
+ category: 'Props',
14
+ type: {
15
+ summary: '{ value: string, label: string, detail?: string }[]',
16
+ },
17
+ },
18
+ defaultValue: { summary: '[]' },
19
+ },
20
+ isDisabled: {
21
+ control: 'boolean',
22
+ description: 'Toggles the disabled state of the select component',
23
+ table: {
24
+ category: 'Props',
25
+ type: {
26
+ summary: 'boolean',
27
+ },
28
+ },
29
+ defaultValue: { summary: 'false' },
30
+ },
31
+ isLoading: {
32
+ control: 'boolean',
33
+ description: 'Toggles the loading state of the select component',
34
+ table: {
35
+ category: 'Props',
36
+ type: {
37
+ summary: 'boolean',
38
+ },
39
+ },
40
+ defaultValue: { summary: 'false' },
41
+ },
42
+ isClearable: {
43
+ control: 'boolean',
44
+ description: 'Enables the clearable feature of the select component',
45
+ table: {
46
+ category: 'Props',
47
+ type: {
48
+ summary: 'boolean',
49
+ },
50
+ },
51
+ defaultValue: { summary: 'false' },
52
+ },
53
+ isSearchable: {
54
+ control: 'boolean',
55
+ description: 'Enables the searchable feature of the select component',
56
+ table: {
57
+ category: 'Props',
58
+ type: {
59
+ summary: 'boolean',
60
+ },
61
+ },
62
+ defaultValue: { summary: 'true' },
63
+ },
64
+ placeholder: {
65
+ control: 'text',
66
+ description: 'The placeholder text for the select component',
67
+ table: {
68
+ category: 'Props',
69
+ type: {
70
+ summary: 'string',
71
+ },
72
+ },
73
+ defaultValue: { summary: 'Select...' },
74
+ },
75
+ className: {
76
+ control: 'text',
77
+ description: 'Additional CSS class for the select component',
78
+ table: {
79
+ category: 'Props',
80
+ type: {
81
+ summary: 'string',
82
+ },
83
+ },
84
+ defaultValue: { summary: '' },
85
+ },
86
+ onChange: {
87
+ control: false,
88
+ description: 'Event handler for when the selected value changes',
89
+ table: {
90
+ category: 'Callbacks',
91
+ type: {
92
+ summary:
93
+ '(newValue: SingleValue<SelectOption> | MultiValue<SelectOption>, actionMeta: ActionMeta<SelectOption>) => void',
94
+ },
95
+ },
96
+ action: 'onChange',
97
+ },
98
+ },
99
+ };
100
+
101
+ export default meta;
102
+
103
+ type Story = StoryObj<SelectProps<SelectOption>>;
104
+
105
+ export const Default: Story = {
106
+ args: {
107
+ options: [
108
+ { value: 'option1', label: 'Option 1', detail: '123 Count' },
109
+ { value: 'option2', label: 'Option 2', detail: '456 Count' },
110
+ { value: 'option3', label: 'Option 3', detail: '789 Count' },
111
+ ],
112
+ placeholder: 'Select an option...',
113
+ isClearable: false,
114
+ isSearchable: true,
115
+ isDisabled: false,
116
+ isLoading: false,
117
+ },
118
+ };
@@ -0,0 +1,43 @@
1
+ import React from 'react';
2
+ import classNames from 'classnames';
3
+ import ReactSelect, { Props as ReactSelectProps, components, OptionProps } from 'react-select';
4
+ import { SelectOption } from './types';
5
+
6
+ export interface SelectProps<OptionType extends SelectOption> extends ReactSelectProps<OptionType> {
7
+ options: OptionType[];
8
+ }
9
+
10
+ const OptionComponent = <OptionType extends SelectOption>({
11
+ ...props
12
+ }: OptionProps<OptionType>) => {
13
+ return (
14
+ <components.Option {...props}>
15
+ <div className="select__items">
16
+ <div className="select__item-value">{props?.data?.label}</div>
17
+ {props?.data?.detail && <div className="select__item-detail">{props?.data?.detail}</div>}
18
+ </div>
19
+ </components.Option>
20
+ );
21
+ };
22
+
23
+ export const Select = <OptionType extends SelectOption>({
24
+ classNamePrefix = 'select',
25
+ className,
26
+ components: customComponents,
27
+ ...props
28
+ }: SelectProps<OptionType>) => {
29
+ const defaultComponents = {
30
+ Option: OptionComponent as React.ComponentType<OptionProps<OptionType>>,
31
+ };
32
+
33
+ const mergedComponents = { ...defaultComponents, ...customComponents };
34
+
35
+ return (
36
+ <ReactSelect
37
+ classNamePrefix={classNamePrefix}
38
+ className={classNames('select-wrapper', className)}
39
+ components={mergedComponents}
40
+ {...props}
41
+ />
42
+ );
43
+ };
@@ -0,0 +1,67 @@
1
+ import { render, screen, fireEvent } from '@testing-library/react';
2
+
3
+ import { Select } from '../Select';
4
+ import { SelectOption } from '../types';
5
+ import { OptionProps, components } from 'react-select';
6
+
7
+ const options: SelectOption[] = [
8
+ { value: 'option1', label: 'Option 1' },
9
+ { value: 'option2', label: 'Option 2' },
10
+ { value: 'option3', label: 'Option 3' },
11
+ ];
12
+
13
+ describe('Select Component', () => {
14
+ it('displays options correctly', () => {
15
+ render(<Select options={options} menuIsOpen />);
16
+ options.forEach((option) => {
17
+ expect(screen.getByText(option.label)).toBeInTheDocument();
18
+ });
19
+ });
20
+
21
+ it('handles onChange event', () => {
22
+ const handleChange = jest.fn();
23
+ render(<Select options={options} menuIsOpen onChange={handleChange} />);
24
+ const option = screen.getByText('Option 1');
25
+ fireEvent.click(option);
26
+ expect(handleChange).toHaveBeenCalledWith(options[0], expect.any(Object));
27
+ });
28
+
29
+ it('applies custom class names', () => {
30
+ const customClassName = 'custom-select-class';
31
+ render(<Select options={options} className={customClassName} />);
32
+ const selectWrapper = screen.getByRole('combobox').closest('.select-wrapper');
33
+ expect(selectWrapper).toHaveClass(customClassName);
34
+ });
35
+
36
+ it('applies custom class name prefix', () => {
37
+ const customClassNamePrefix = 'custom-prefix';
38
+ render(<Select options={options} classNamePrefix={customClassNamePrefix} />);
39
+ const selectWrapper = screen.getByRole('combobox');
40
+ expect(selectWrapper).toHaveClass(`${customClassNamePrefix}__input`);
41
+ });
42
+
43
+ it('displays detail when present', () => {
44
+ const optionsWithDetail = options.map((option, index) => ({
45
+ ...option,
46
+ detail: `${index + 1}23 Count`,
47
+ }));
48
+
49
+ render(<Select options={optionsWithDetail} menuIsOpen />);
50
+ optionsWithDetail.forEach((option) => {
51
+ expect(screen.getByText(option.detail)).toBeInTheDocument();
52
+ });
53
+ });
54
+
55
+ it('uses custom components', () => {
56
+ const oneOption = options.slice(0, 1);
57
+ const CustomOptionComponent = (props: OptionProps<SelectOption>) => (
58
+ <div data-testid="custom-option">{props.data.label}</div>
59
+ );
60
+
61
+ render(
62
+ <Select options={oneOption} components={{ Option: CustomOptionComponent }} menuIsOpen />,
63
+ );
64
+
65
+ expect(screen.getByTestId('custom-option')).toHaveTextContent(oneOption[0].label);
66
+ });
67
+ });
@@ -0,0 +1 @@
1
+ export { Select } from './Select';