@hyphen/hyphen-components 8.0.1 → 9.0.0-beta.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 (60) hide show
  1. package/dist/css/utilities.css +1 -1
  2. package/dist/css/utilities.css.map +1 -1
  3. package/dist/hyphen-components.cjs.development.js +342 -311
  4. package/dist/hyphen-components.cjs.development.js.map +1 -1
  5. package/dist/hyphen-components.cjs.production.min.js +2 -2
  6. package/dist/hyphen-components.cjs.production.min.js.map +1 -1
  7. package/dist/hyphen-components.esm.js +346 -312
  8. package/dist/hyphen-components.esm.js.map +1 -1
  9. package/dist/index.d.ts +212 -202
  10. package/package.json +6 -5
  11. package/src/components/Alert/Alert.tsx +5 -6
  12. package/src/components/Badge/Badge.test.tsx +12 -0
  13. package/src/components/Badge/Badge.tsx +52 -20
  14. package/src/components/Box/Box.mdx +1 -1
  15. package/src/components/Box/Box.stories.tsx +1 -1
  16. package/src/components/Box/Box.test.tsx +24 -1
  17. package/src/components/Box/Box.tsx +108 -82
  18. package/src/components/Card/Card.tsx +5 -1
  19. package/src/components/Card/components/CardFooter/CardFooter.tsx +0 -4
  20. package/src/components/Card/components/CardHeader/CardHeader.tsx +4 -1
  21. package/src/components/Card/components/CardSection/CardSection.tsx +4 -5
  22. package/src/components/CheckboxInput/CheckboxInput.test.tsx +17 -0
  23. package/src/components/CheckboxInput/CheckboxInput.tsx +15 -4
  24. package/src/components/CheckboxInput/components/Checkbox.test.tsx +36 -0
  25. package/src/components/CheckboxInput/components/Checkbox.tsx +8 -2
  26. package/src/components/Details/Details.test.tsx +8 -0
  27. package/src/components/Details/Details.tsx +5 -2
  28. package/src/components/Details/DetailsSummary.tsx +4 -1
  29. package/src/components/Drawer/Drawer.test.tsx +8 -0
  30. package/src/components/Drawer/Drawer.tsx +1 -1
  31. package/src/components/FormLabel/FormLabel.tsx +6 -3
  32. package/src/components/Formik/FormikCheckboxInput/FormikCheckboxInput.test.tsx +9 -0
  33. package/src/components/Formik/FormikCheckboxInput/FormikCheckboxInput.tsx +1 -0
  34. package/src/components/Formik/FormikSelectInput/FormikSelectInput.tsx +21 -11
  35. package/src/components/Formik/FormikSelectInputNative/FormikSelectInputNative.tsx +1 -1
  36. package/src/components/Formik/FormikTimePicker/FormikTimePicker.tsx +1 -1
  37. package/src/components/Heading/Heading.tsx +5 -6
  38. package/src/components/Icon/Icon.test.tsx +8 -0
  39. package/src/components/Icon/Icon.tsx +6 -7
  40. package/src/components/Modal/Modal.test.tsx +31 -1
  41. package/src/components/Modal/Modal.tsx +13 -7
  42. package/src/components/Popover/Popover.test.tsx +3 -4
  43. package/src/components/RadioGroup/RadioGroup.tsx +11 -6
  44. package/src/components/RadioGroup/RadioInput/RadioInputIcon.tsx +0 -4
  45. package/src/components/SelectInput/SelectInput.test.tsx +18 -0
  46. package/src/components/SelectInput/SelectInput.tsx +47 -27
  47. package/src/components/SelectInputInset/SelectInputInset.tsx +8 -9
  48. package/src/components/SelectInputNative/SelectInputNative.test.tsx +24 -0
  49. package/src/components/SelectInputNative/SelectInputNative.tsx +79 -14
  50. package/src/components/Switch/Switch.tsx +1 -1
  51. package/src/components/Table/Table.mdx +1 -1
  52. package/src/components/Table/Table.stories.tsx +5 -1
  53. package/src/components/Table/common/TableRow/TableRow.test.tsx +16 -0
  54. package/src/components/Table/common/TableRow/TableRow.tsx +19 -1
  55. package/src/components/TextInput/TextInput.tsx +10 -8
  56. package/src/components/TextInputInset/TextInputInset.tsx +5 -6
  57. package/src/components/TextareaInput/TextareaInput.tsx +4 -5
  58. package/src/components/TextareaInputInset/TextareaInputInset.tsx +10 -17
  59. package/src/components/Toast/Toast.stories.tsx +5 -1
  60. package/src/types/index.ts +2 -3
@@ -3,7 +3,7 @@ import classNames from 'classnames';
3
3
  import { cssShorthandToClasses } from '../../lib/cssShorthandToClasses';
4
4
  import { InputValidationMessage } from '../InputValidationMessage/InputValidationMessage';
5
5
  import { FormLabel } from '../FormLabel/FormLabel';
6
- import { Box } from '../Box/Box';
6
+ import { Box, BoxProps } from '../Box/Box';
7
7
  import { Checkbox, CheckboxSize, CheckboxProps } from './components/Checkbox';
8
8
 
9
9
  const labelMarginSizeMap = {
@@ -28,7 +28,7 @@ const computedResponsiveSize = (
28
28
 
29
29
  return labelMarginSizeMap[size || 'md'] as string;
30
30
  };
31
- export interface CheckboxInputProps {
31
+ interface CheckboxInputOwnProps {
32
32
  /**
33
33
  * The id attribute of the input.
34
34
  */
@@ -76,6 +76,10 @@ export interface CheckboxInputProps {
76
76
  * The required and aria-required attributes on the input
77
77
  */
78
78
  isRequired?: boolean;
79
+ /**
80
+ * The name attribute of the checkbox input element.
81
+ */
82
+ name?: CheckboxProps['name'];
79
83
  /**
80
84
  * Callback function when input is blurred.
81
85
  */
@@ -93,11 +97,14 @@ export interface CheckboxInputProps {
93
97
  */
94
98
  size?: CheckboxSize;
95
99
  /**
96
- * Additional props to be spread to rendered element
100
+ * Value of the checkbox input element.
97
101
  */
98
- [x: string]: any; // eslint-disable-line
102
+ value?: CheckboxProps['value'];
99
103
  }
100
104
 
105
+ export type CheckboxInputProps = CheckboxInputOwnProps &
106
+ Omit<BoxProps<'div'>, keyof CheckboxInputOwnProps | 'as'>;
107
+
101
108
  export const CheckboxInput: React.FC<CheckboxInputProps> = ({
102
109
  id,
103
110
  isChecked,
@@ -110,10 +117,12 @@ export const CheckboxInput: React.FC<CheckboxInputProps> = ({
110
117
  isDisabled = false,
111
118
  isIndeterminate = false,
112
119
  isRequired = false,
120
+ name = undefined,
113
121
  onBlur = undefined,
114
122
  onFocus = undefined,
115
123
  requiredIndicator = ' *',
116
124
  size = 'md',
125
+ value = undefined,
117
126
  ...restProps
118
127
  }) => {
119
128
  const handleBlur = (event: React.FocusEvent<HTMLInputElement>): void => {
@@ -133,11 +142,13 @@ export const CheckboxInput: React.FC<CheckboxInputProps> = ({
133
142
  isChecked: !!isChecked,
134
143
  isDisabled,
135
144
  isIndeterminate,
145
+ name,
136
146
  onBlur: handleBlur,
137
147
  onChange: handleChange,
138
148
  onFocus: handleFocus,
139
149
  isRequired,
140
150
  size,
151
+ value,
141
152
  label,
142
153
  className: classNames(
143
154
  'hyphen-components__variables__form-control',
@@ -41,6 +41,41 @@ describe('Checkbox', () => {
41
41
  });
42
42
  });
43
43
 
44
+ describe('Value', () => {
45
+ test.each([0, ''])('preserves the falsy value %p', (value) => {
46
+ render(
47
+ <Checkbox
48
+ id="testCheckbox"
49
+ label="test checkbox"
50
+ onChange={jest.fn(() => null)}
51
+ isChecked={false}
52
+ value={value}
53
+ />
54
+ );
55
+
56
+ expect(screen.getByRole('checkbox')).toHaveAttribute(
57
+ 'value',
58
+ String(value)
59
+ );
60
+ });
61
+
62
+ test('uses the browser default when value is omitted', () => {
63
+ render(
64
+ <Checkbox
65
+ id="testCheckbox"
66
+ label="test checkbox"
67
+ onChange={jest.fn(() => null)}
68
+ isChecked={false}
69
+ />
70
+ );
71
+
72
+ const checkbox = screen.getByRole('checkbox') as HTMLInputElement;
73
+
74
+ expect(checkbox).not.toHaveAttribute('value');
75
+ expect(checkbox.value).toBe('on');
76
+ });
77
+ });
78
+
44
79
  describe('Indeterminate', () => {
45
80
  test('It renders an indeterminate icon if isIndeterminate is true and checkbox is checked', () => {
46
81
  render(
@@ -195,6 +230,7 @@ describe('Checkbox', () => {
195
230
  const checkboxInputContainer = screen
196
231
  .getByRole('checkbox')
197
232
  .closest('div');
233
+ expect(screen.getByRole('checkbox')).toHaveAttribute('name', 'mockName');
198
234
  expect(checkboxInputContainer).toHaveClass('hidden');
199
235
  });
200
236
  });
@@ -40,6 +40,10 @@ export interface CheckboxProps
40
40
  * id of the element that labels the Checkbox
41
41
  */
42
42
  labelledby?: string;
43
+ /**
44
+ * The checkbox input's name attribute.
45
+ */
46
+ name?: string;
43
47
  /**
44
48
  * If the input should be disabled and not focusable.
45
49
  */
@@ -75,7 +79,7 @@ export interface CheckboxProps
75
79
  value?: string | number;
76
80
  }
77
81
 
78
- export const Checkbox: React.FC<CheckboxProps> = React.forwardRef(
82
+ export const Checkbox = React.forwardRef<HTMLDivElement, CheckboxProps>(
79
83
  (
80
84
  {
81
85
  className = '',
@@ -84,6 +88,7 @@ export const Checkbox: React.FC<CheckboxProps> = React.forwardRef(
84
88
  isChecked,
85
89
  label,
86
90
  labelledby,
91
+ name = undefined,
87
92
  onChange,
88
93
  error = false,
89
94
  isDisabled = false,
@@ -124,6 +129,7 @@ export const Checkbox: React.FC<CheckboxProps> = React.forwardRef(
124
129
  'aria-labelledby': labelledby,
125
130
  'aria-required': isRequired,
126
131
  id,
132
+ name,
127
133
  checked: !!isChecked,
128
134
  disabled: isDisabled,
129
135
  onBlur: handleBlur,
@@ -132,7 +138,7 @@ export const Checkbox: React.FC<CheckboxProps> = React.forwardRef(
132
138
  required: isRequired,
133
139
  type: 'checkbox',
134
140
  ref: inputRef,
135
- ...(value && { value }),
141
+ ...(value !== undefined && { value }),
136
142
  };
137
143
 
138
144
  const responsiveClasses = generateResponsiveClasses('size', size);
@@ -185,5 +185,13 @@ describe('Details', () => {
185
185
  const summary = screen.getByRole('button');
186
186
  expect(summary).toHaveClass('m-top-lg');
187
187
  });
188
+
189
+ it('forwards its ref to the details element', () => {
190
+ const ref = React.createRef<HTMLDetailsElement>();
191
+
192
+ render(<Details isOpen={false} ref={ref} />);
193
+
194
+ expect(ref.current).toBe(screen.getByRole('group'));
195
+ });
188
196
  });
189
197
  });
@@ -4,14 +4,17 @@ import { DetailsSummary } from './DetailsSummary';
4
4
  import styles from './Details.module.scss';
5
5
  import { Box, BoxProps } from '../Box/Box';
6
6
 
7
- export interface DetailsProps extends BoxProps {
7
+ interface DetailsOwnProps {
8
8
  /**
9
9
  * Whether the details below the summary are opened. Directly corresponds to `open` property in <details> element.
10
10
  */
11
11
  isOpen: boolean;
12
12
  }
13
13
 
14
- export const DetailsBaseComponent: React.FC<DetailsProps> = React.forwardRef<
14
+ export type DetailsProps = DetailsOwnProps &
15
+ Omit<BoxProps<'details'>, keyof DetailsOwnProps | 'as' | 'open'>;
16
+
17
+ export const DetailsBaseComponent = React.forwardRef<
15
18
  HTMLDetailsElement,
16
19
  DetailsProps
17
20
  >(({ children, className, display = 'block', isOpen, ...restProps }, ref) => {
@@ -2,13 +2,16 @@ import React, { MouseEvent, KeyboardEvent } from 'react';
2
2
  import { ENTER, SPACE } from '../../constants/keyCodes';
3
3
  import { Box, BoxProps } from '../Box/Box';
4
4
 
5
- export interface DetailsSummaryProps extends BoxProps {
5
+ interface DetailsSummaryOwnProps {
6
6
  isDetailsOpen: boolean;
7
7
  onToggle?: (
8
8
  event: MouseEvent<HTMLElement> | KeyboardEvent<HTMLElement>
9
9
  ) => void;
10
10
  }
11
11
 
12
+ export type DetailsSummaryProps = DetailsSummaryOwnProps &
13
+ Omit<BoxProps<'summary'>, keyof DetailsSummaryOwnProps | 'as'>;
14
+
12
15
  export const DetailsSummary: React.FC<DetailsSummaryProps> = ({
13
16
  children,
14
17
  display = 'block',
@@ -584,6 +584,14 @@ describe('Drawer', () => {
584
584
  });
585
585
 
586
586
  describe('Ref forwarding', () => {
587
+ test('forwards ref to the Drawer container', () => {
588
+ const ref = React.createRef<HTMLDivElement>();
589
+
590
+ render(<Drawer isOpen={false} ariaLabel="Drawer" ref={ref} />);
591
+
592
+ expect(ref.current).toBeInstanceOf(HTMLDivElement);
593
+ });
594
+
587
595
  test('forwards ref to DrawerProvider container', () => {
588
596
  const ref = React.createRef<HTMLDivElement>();
589
597
  render(
@@ -211,7 +211,7 @@ export interface DrawerProps {
211
211
  */
212
212
  width?: DimensionSize | CssDimensionValue;
213
213
  }
214
- const Drawer: React.FC<DrawerProps> = forwardRef<HTMLDivElement, DrawerProps>(
214
+ const Drawer = forwardRef<HTMLDivElement, DrawerProps>(
215
215
  (
216
216
  {
217
217
  ariaLabel = undefined,
@@ -4,7 +4,7 @@ import { Box, BoxProps } from '../Box/Box';
4
4
  import styles from './FormLabel.module.scss';
5
5
  import { HelpText } from '../HelpText/HelpText';
6
6
 
7
- export interface FormLabelProps extends BoxProps {
7
+ interface FormLabelOwnProps {
8
8
  /**
9
9
  * Content to be rendered inside the label.
10
10
  */
@@ -38,11 +38,14 @@ export interface FormLabelProps extends BoxProps {
38
38
  */
39
39
  requiredIndicator?: ReactNode;
40
40
  /**
41
- * Additional props to be spread to rendered element
41
+ * The label size.
42
42
  */
43
- [x: string]: any; // eslint-disable-line
43
+ size?: 'sm' | 'md' | 'lg';
44
44
  }
45
45
 
46
+ export type FormLabelProps = FormLabelOwnProps &
47
+ Omit<BoxProps<'label'>, keyof FormLabelOwnProps | 'as' | 'htmlFor'>;
48
+
46
49
  export const FormLabel: FC<FormLabelProps> = ({
47
50
  children,
48
51
  inputId,
@@ -54,6 +54,15 @@ describe('FormikCheckboxInput', () => {
54
54
  expect(checkbox.disabled).toBe(false);
55
55
  expect(checkbox.getAttribute('aria-invalid')).toBe('false');
56
56
  });
57
+
58
+ test('passes the Formik field name to the checkbox input', () => {
59
+ const { getByLabelText } = render(renderForm(true, {}));
60
+ const checkbox = getByLabelText(testLabelName) as HTMLInputElement;
61
+ const form = checkbox.closest('form') as HTMLFormElement;
62
+
63
+ expect(checkbox).toHaveAttribute('name', testLabelName);
64
+ expect(new FormData(form).has(testLabelName)).toBe(true);
65
+ });
57
66
  });
58
67
 
59
68
  describe('With initial value true', () => {
@@ -33,6 +33,7 @@ export const FormikCheckboxInput: React.FC<FormikCheckboxInputProps> = ({
33
33
  {...props}
34
34
  id={id}
35
35
  label={label}
36
+ name={name}
36
37
  error={getIn(touched, name) && getIn(errors, name)}
37
38
  isChecked={value}
38
39
  onBlur={onBlur}
@@ -6,10 +6,14 @@ import {
6
6
  FormikValues,
7
7
  getIn,
8
8
  } from 'formik';
9
- import { SelectInput, SelectInputProps } from '../../SelectInput/SelectInput';
9
+ import {
10
+ AsyncSelectInputProps,
11
+ SelectInput,
12
+ SelectInputProps,
13
+ SyncSelectInputProps,
14
+ } from '../../SelectInput/SelectInput';
10
15
 
11
- export interface FormikSelectInputProps
12
- extends Omit<SelectInputProps, 'onChange'> {
16
+ interface FormikSelectInputOwnProps {
13
17
  field: FieldAttributes<HTMLSelectElement>;
14
18
  form: {
15
19
  touched: FormikTouched<FormikValues>;
@@ -19,15 +23,24 @@ export interface FormikSelectInputProps
19
23
  error?: string | Array<{ label?: string }>;
20
24
  }
21
25
 
26
+ type FormikControlledSelectProps =
27
+ | keyof FormikSelectInputOwnProps
28
+ | 'name'
29
+ | 'onBlur'
30
+ | 'value';
31
+
32
+ export type FormikSelectInputProps = FormikSelectInputOwnProps &
33
+ (
34
+ | Omit<AsyncSelectInputProps, FormikControlledSelectProps>
35
+ | Omit<SyncSelectInputProps, FormikControlledSelectProps>
36
+ );
37
+
22
38
  export const FormikSelectInput: React.FC<FormikSelectInputProps> = ({
23
39
  field: { name, onBlur, onChange: formikOnChange, value },
24
40
  form: { touched, errors },
25
41
  onChange,
26
- id,
27
- label,
28
- options,
29
42
  error: errorProp,
30
- ...props
43
+ ...selectProps
31
44
  }) => {
32
45
  let errorMessage: string | undefined;
33
46
  const error: unknown =
@@ -41,15 +54,12 @@ export const FormikSelectInput: React.FC<FormikSelectInputProps> = ({
41
54
 
42
55
  return (
43
56
  <SelectInput
44
- id={id}
45
- label={label}
46
- options={options}
57
+ {...selectProps}
47
58
  name={name}
48
59
  onBlur={onBlur}
49
60
  onChange={onChange ?? formikOnChange}
50
61
  value={value}
51
62
  error={errorMessage}
52
- {...props}
53
63
  />
54
64
  );
55
65
  };
@@ -12,7 +12,7 @@ import {
12
12
  } from '../../SelectInputNative/SelectInputNative';
13
13
 
14
14
  export interface FormikSelectInputNativeProps
15
- extends Omit<SelectInputNativeProps, 'onChange'> {
15
+ extends Omit<SelectInputNativeProps, 'form' | 'onChange'> {
16
16
  field: FieldAttributes<HTMLSelectElement>;
17
17
  form: {
18
18
  touched: FormikTouched<FormikValues>;
@@ -9,7 +9,7 @@ import {
9
9
  import { TimePicker, TimePickerProps } from '../../TimePicker/TimePicker';
10
10
 
11
11
  export interface FormikTimePickerProps
12
- extends Omit<TimePickerProps, 'onChange'> {
12
+ extends Omit<TimePickerProps, 'form' | 'onChange'> {
13
13
  field: FieldAttributes<HTMLSelectElement>;
14
14
  form: {
15
15
  touched: FormikTouched<FormikValues>;
@@ -1,4 +1,4 @@
1
- import { FC, ReactNode, createElement } from 'react';
1
+ import { FC, HTMLAttributes, ReactNode, createElement } from 'react';
2
2
  import { FontColor, HeadingSize, ResponsiveProp } from '../../types';
3
3
  import {
4
4
  HEADING_DEFAULT_SIZE_MAP,
@@ -10,7 +10,7 @@ import { generateResponsiveClasses } from '../../lib/generateResponsiveClasses';
10
10
  import { getElementType } from '../../lib/getElementType';
11
11
  import styles from './Heading.module.scss';
12
12
 
13
- export interface HeadingProps {
13
+ interface HeadingOwnProps {
14
14
  /**
15
15
  * The DOM tag or react component to use for the element.
16
16
  * Select the appropriate semantic element (h1-h6).
@@ -36,12 +36,11 @@ export interface HeadingProps {
36
36
  * [here](https://github.com/Hyphen/hyphen-components/blob/main/packages/design-tokens/tokens/size/font.json).
37
37
  */
38
38
  size?: HeadingSize | ResponsiveProp<HeadingSize>;
39
- /**
40
- * Additional props to be spread to rendered element
41
- */
42
- [x: string]: any; // eslint-disable-line
43
39
  }
44
40
 
41
+ export type HeadingProps = HeadingOwnProps &
42
+ Omit<HTMLAttributes<HTMLHeadingElement>, keyof HeadingOwnProps>;
43
+
45
44
  export const Heading: FC<HeadingProps> = ({
46
45
  as = 'h4',
47
46
  children,
@@ -22,6 +22,14 @@ describe('Icon', () => {
22
22
  expect(icon.getAttribute('class')).toBe('testClass');
23
23
  });
24
24
 
25
+ test('forwards its ref to the svg element', () => {
26
+ const ref = React.createRef<SVGSVGElement>();
27
+
28
+ render(<Icon name="user" ref={ref} />);
29
+
30
+ expect(ref.current).toBe(screen.getByTestId('icon-testid--user'));
31
+ });
32
+
25
33
  test('fallback', () => {
26
34
  render(<Icon name={'does-not-exist' as 'user'} />);
27
35
  const icon = screen.getByText('???');
@@ -1,12 +1,12 @@
1
1
  import { FontColor, FontSize, IconName, ResponsiveProp } from '../../types';
2
- import React, { FC, forwardRef } from 'react';
2
+ import React, { ComponentPropsWithoutRef, forwardRef } from 'react';
3
3
 
4
4
  import { Box } from '../Box/Box';
5
5
  import classNames from 'classnames';
6
6
  import { generateResponsiveClasses } from '../../lib/generateResponsiveClasses';
7
7
  import icons from '@hyphen/hyphen-design-tokens/build/assets/icons/react';
8
8
 
9
- export interface IconProps {
9
+ interface IconOwnProps {
10
10
  className?: string;
11
11
  /**
12
12
  * A color token identifier to use for the text color.
@@ -20,13 +20,12 @@ export interface IconProps {
20
20
  * Name of the icon
21
21
  */
22
22
  name: IconName;
23
- /**
24
- * Additional props to be spread to rendered element
25
- */
26
- [x: string]: any; // eslint-disable-line
27
23
  }
28
24
 
29
- export const Icon: FC<IconProps> = forwardRef<SVGSVGElement, IconProps>(
25
+ export type IconProps = IconOwnProps &
26
+ Omit<ComponentPropsWithoutRef<'svg'>, keyof IconOwnProps>;
27
+
28
+ export const Icon = forwardRef<SVGSVGElement, IconProps>(
30
29
  ({ className = undefined, name, color, size, ...restProps }, ref) => {
31
30
  const IconComponent = icons[name];
32
31
 
@@ -7,7 +7,7 @@ import {
7
7
  act,
8
8
  } from '@testing-library/react';
9
9
  import userEvent from '@testing-library/user-event';
10
- import { Modal } from './Modal';
10
+ import { Modal, ModalProps } from './Modal';
11
11
 
12
12
  describe('Modal', () => {
13
13
  beforeEach(() => {
@@ -405,6 +405,36 @@ describe('Modal', () => {
405
405
  expect(handleDismiss).toHaveBeenCalledTimes(1);
406
406
  });
407
407
  });
408
+
409
+ test('does not allow passthrough props to override onDismiss', async () => {
410
+ const handleDismiss = jest.fn();
411
+ const handleRequestClose = jest.fn();
412
+ const unsupportedProps = {
413
+ onRequestClose: handleRequestClose,
414
+ } as unknown as Partial<ModalProps>;
415
+
416
+ render(
417
+ <Modal
418
+ {...unsupportedProps}
419
+ isOpen
420
+ onDismiss={handleDismiss}
421
+ ariaLabel="Escape Modal"
422
+ >
423
+ content
424
+ </Modal>
425
+ );
426
+
427
+ fireEvent.keyDown(screen.getByRole('dialog'), {
428
+ key: 'Escape',
429
+ code: 'Escape',
430
+ keyCode: 27,
431
+ });
432
+
433
+ await waitFor(() => {
434
+ expect(handleDismiss).toHaveBeenCalledTimes(1);
435
+ });
436
+ expect(handleRequestClose).not.toHaveBeenCalled();
437
+ });
408
438
  });
409
439
 
410
440
  describe('fullScreenMobile', () => {
@@ -9,7 +9,7 @@ import { Box, BoxProps } from '../Box/Box';
9
9
  import { ModalFooter, ModalHeader, ModalBody } from './components';
10
10
  import styles from './Modal.module.scss';
11
11
 
12
- export interface ModalProps {
12
+ interface ModalOwnProps {
13
13
  /**
14
14
  * Handle zoom/pinch gestures on iOS devices when scroll locking is enabled.
15
15
  */
@@ -75,13 +75,19 @@ export interface ModalProps {
75
75
  * Inline styles for the modal container
76
76
  */
77
77
  style?: React.CSSProperties;
78
- /**
79
- * Allows spread props
80
- */
81
- [x: string]: any; // eslint-disable-line
82
78
  }
83
79
 
84
- export const ModalBaseComponent: React.FC<ModalProps> = forwardRef<
80
+ export type ModalProps = ModalOwnProps &
81
+ Omit<
82
+ ReactModal.Props,
83
+ | keyof ModalOwnProps
84
+ | 'ariaHideApp'
85
+ | 'onRequestClose'
86
+ | 'overlayClassName'
87
+ | 'parentSelector'
88
+ >;
89
+
90
+ export const ModalBaseComponent = forwardRef<
85
91
  HTMLDivElement,
86
92
  ModalProps
87
93
  >(
@@ -143,6 +149,7 @@ export const ModalBaseComponent: React.FC<ModalProps> = forwardRef<
143
149
  <RemoveScroll allowPinchZoom={allowPinchZoom} enabled={isOpen}>
144
150
  <Box ref={ref}>
145
151
  <ReactModal
152
+ {...restProps}
146
153
  isOpen={isOpen}
147
154
  overlayClassName={overlayClassnames}
148
155
  className={contentClassnames}
@@ -150,7 +157,6 @@ export const ModalBaseComponent: React.FC<ModalProps> = forwardRef<
150
157
  ariaHideApp={false}
151
158
  parentSelector={parentElement ? () => parentElement : undefined}
152
159
  style={{ content: { ...maxWidthCss.styles, ...style } }}
153
- {...restProps}
154
160
  >
155
161
  <Box
156
162
  aria-label={ariaLabel}
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import React, { ComponentPropsWithoutRef } from 'react';
2
2
  import { render, screen, waitFor } from '@testing-library/react';
3
3
  import {
4
4
  Popover,
@@ -8,15 +8,14 @@ import {
8
8
  } from './Popover';
9
9
 
10
10
  // Helper to render a controlled Popover
11
- type ControlledPopoverProps = {
11
+ type ControlledPopoverProps = ComponentPropsWithoutRef<typeof Popover> & {
12
12
  isOpen?: boolean;
13
13
  onClickOutside?: (event: Event) => void;
14
14
  placement?: string;
15
15
  withPortal?: boolean;
16
16
  portalTarget?: HTMLElement;
17
17
  children: React.ReactNode;
18
- contentProps?: Record<string, any>;
19
- [key: string]: any;
18
+ contentProps?: ComponentPropsWithoutRef<typeof PopoverContent>;
20
19
  };
21
20
 
22
21
  function ControlledPopover({
@@ -1,11 +1,17 @@
1
- import React, { FC, ChangeEvent, FocusEvent, ReactNode } from 'react';
1
+ import React, {
2
+ FC,
3
+ ChangeEvent,
4
+ ComponentPropsWithoutRef,
5
+ FocusEvent,
6
+ ReactNode,
7
+ } from 'react';
2
8
  import classNames from 'classnames';
3
9
  import { Box } from '../Box/Box';
4
10
  import { InputValidationMessage } from '../InputValidationMessage/InputValidationMessage';
5
11
  import { RadioInput, RadioInputProps } from './RadioInput/RadioInput'; // eslint-disable-line import/no-cycle
6
12
  import styles from './RadioGroup.module.scss';
7
13
 
8
- export interface RadioGroupProps {
14
+ interface RadioGroupOwnProps {
9
15
  /**
10
16
  * Radio group name.
11
17
  */
@@ -72,12 +78,11 @@ export interface RadioGroupProps {
72
78
  * The value of selected radio input.
73
79
  */
74
80
  value?: string;
75
- /**
76
- * Additional props to be spread to rendered element
77
- */
78
- [x: string]: any; // eslint-disable-line
79
81
  }
80
82
 
83
+ export type RadioGroupProps = RadioGroupOwnProps &
84
+ Omit<ComponentPropsWithoutRef<'div'>, keyof RadioGroupOwnProps>;
85
+
81
86
  export const RadioGroup: FC<RadioGroupProps> = ({
82
87
  name,
83
88
  onChange,
@@ -22,10 +22,6 @@ export interface RadioIconProps extends BoxProps {
22
22
  * If the input should be disabled and not focusable.
23
23
  */
24
24
  isDisabled?: RadioInputProps['isDisabled'];
25
- /**
26
- * Additional props to be spread to rendered element
27
- */
28
- [x: string]: any; // eslint-disable-line
29
25
  }
30
26
 
31
27
  export const RadioInputIcon: React.FC<RadioIconProps> = ({
@@ -229,6 +229,24 @@ describe('SelectInput', () => {
229
229
  expect(loadOptions).toHaveBeenCalledTimes(1);
230
230
  });
231
231
  });
232
+
233
+ it('renders default options before searching', async () => {
234
+ render(
235
+ <SelectInput
236
+ id="testId"
237
+ onChange={jest.fn()}
238
+ label="Select Label"
239
+ options={jest.fn(() => Promise.resolve([]))}
240
+ value={null}
241
+ isAsync
242
+ defaultOptions={selectOptions}
243
+ />
244
+ );
245
+
246
+ await selectEvent.openMenu(screen.getByLabelText('Select Label'));
247
+
248
+ expect(screen.getByText('Vanilla')).toBeInTheDocument();
249
+ });
232
250
  });
233
251
 
234
252
  describe('Multi select, no selection', () => {