@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.
- package/dist/css/utilities.css +1 -1
- package/dist/css/utilities.css.map +1 -1
- package/dist/hyphen-components.cjs.development.js +342 -311
- package/dist/hyphen-components.cjs.development.js.map +1 -1
- package/dist/hyphen-components.cjs.production.min.js +2 -2
- package/dist/hyphen-components.cjs.production.min.js.map +1 -1
- package/dist/hyphen-components.esm.js +346 -312
- package/dist/hyphen-components.esm.js.map +1 -1
- package/dist/index.d.ts +212 -202
- package/package.json +6 -5
- package/src/components/Alert/Alert.tsx +5 -6
- package/src/components/Badge/Badge.test.tsx +12 -0
- package/src/components/Badge/Badge.tsx +52 -20
- package/src/components/Box/Box.mdx +1 -1
- package/src/components/Box/Box.stories.tsx +1 -1
- package/src/components/Box/Box.test.tsx +24 -1
- package/src/components/Box/Box.tsx +108 -82
- package/src/components/Card/Card.tsx +5 -1
- package/src/components/Card/components/CardFooter/CardFooter.tsx +0 -4
- package/src/components/Card/components/CardHeader/CardHeader.tsx +4 -1
- package/src/components/Card/components/CardSection/CardSection.tsx +4 -5
- package/src/components/CheckboxInput/CheckboxInput.test.tsx +17 -0
- package/src/components/CheckboxInput/CheckboxInput.tsx +15 -4
- package/src/components/CheckboxInput/components/Checkbox.test.tsx +36 -0
- package/src/components/CheckboxInput/components/Checkbox.tsx +8 -2
- package/src/components/Details/Details.test.tsx +8 -0
- package/src/components/Details/Details.tsx +5 -2
- package/src/components/Details/DetailsSummary.tsx +4 -1
- package/src/components/Drawer/Drawer.test.tsx +8 -0
- package/src/components/Drawer/Drawer.tsx +1 -1
- package/src/components/FormLabel/FormLabel.tsx +6 -3
- package/src/components/Formik/FormikCheckboxInput/FormikCheckboxInput.test.tsx +9 -0
- package/src/components/Formik/FormikCheckboxInput/FormikCheckboxInput.tsx +1 -0
- package/src/components/Formik/FormikSelectInput/FormikSelectInput.tsx +21 -11
- package/src/components/Formik/FormikSelectInputNative/FormikSelectInputNative.tsx +1 -1
- package/src/components/Formik/FormikTimePicker/FormikTimePicker.tsx +1 -1
- package/src/components/Heading/Heading.tsx +5 -6
- package/src/components/Icon/Icon.test.tsx +8 -0
- package/src/components/Icon/Icon.tsx +6 -7
- package/src/components/Modal/Modal.test.tsx +31 -1
- package/src/components/Modal/Modal.tsx +13 -7
- package/src/components/Popover/Popover.test.tsx +3 -4
- package/src/components/RadioGroup/RadioGroup.tsx +11 -6
- package/src/components/RadioGroup/RadioInput/RadioInputIcon.tsx +0 -4
- package/src/components/SelectInput/SelectInput.test.tsx +18 -0
- package/src/components/SelectInput/SelectInput.tsx +47 -27
- package/src/components/SelectInputInset/SelectInputInset.tsx +8 -9
- package/src/components/SelectInputNative/SelectInputNative.test.tsx +24 -0
- package/src/components/SelectInputNative/SelectInputNative.tsx +79 -14
- package/src/components/Switch/Switch.tsx +1 -1
- package/src/components/Table/Table.mdx +1 -1
- package/src/components/Table/Table.stories.tsx +5 -1
- package/src/components/Table/common/TableRow/TableRow.test.tsx +16 -0
- package/src/components/Table/common/TableRow/TableRow.tsx +19 -1
- package/src/components/TextInput/TextInput.tsx +10 -8
- package/src/components/TextInputInset/TextInputInset.tsx +5 -6
- package/src/components/TextareaInput/TextareaInput.tsx +4 -5
- package/src/components/TextareaInputInset/TextareaInputInset.tsx +10 -17
- package/src/components/Toast/Toast.stories.tsx +5 -1
- 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
|
-
|
|
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
|
-
*
|
|
100
|
+
* Value of the checkbox input element.
|
|
97
101
|
*/
|
|
98
|
-
[
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
*
|
|
41
|
+
* The label size.
|
|
42
42
|
*/
|
|
43
|
-
|
|
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', () => {
|
|
@@ -6,10 +6,14 @@ import {
|
|
|
6
6
|
FormikValues,
|
|
7
7
|
getIn,
|
|
8
8
|
} from 'formik';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
AsyncSelectInputProps,
|
|
11
|
+
SelectInput,
|
|
12
|
+
SelectInputProps,
|
|
13
|
+
SyncSelectInputProps,
|
|
14
|
+
} from '../../SelectInput/SelectInput';
|
|
10
15
|
|
|
11
|
-
|
|
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
|
-
...
|
|
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
|
-
|
|
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
|
-
|
|
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, {
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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?:
|
|
19
|
-
[key: string]: any;
|
|
18
|
+
contentProps?: ComponentPropsWithoutRef<typeof PopoverContent>;
|
|
20
19
|
};
|
|
21
20
|
|
|
22
21
|
function ControlledPopover({
|
|
@@ -1,11 +1,17 @@
|
|
|
1
|
-
import 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
|
-
|
|
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', () => {
|