@hyphen/hyphen-components 8.0.0 → 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/fonts.css +5 -3
- package/dist/css/fonts.css.map +1 -0
- package/dist/css/index.css +117 -111
- package/dist/css/index.css.map +1 -0
- package/dist/css/reset.css +5 -3
- package/dist/css/reset.css.map +1 -0
- package/dist/css/utilities.css +9 -7
- package/dist/css/utilities.css.map +1 -0
- package/dist/css/variables.css +11 -9
- package/dist/css/variables.css.map +1 -0
- package/dist/hyphen-components.cjs.development.js +348 -317
- 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 +352 -318
- package/dist/hyphen-components.esm.js.map +1 -1
- package/dist/index.d.ts +212 -202
- package/package.json +8 -7
- package/src/components/Alert/Alert.tsx +5 -6
- package/src/components/Badge/Badge.module.scss +1 -1
- 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/Button/Button.module.scss +1 -1
- 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.module.scss +2 -2
- 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.module.scss +1 -1
- package/src/components/Drawer/Drawer.test.tsx +8 -0
- package/src/components/Drawer/Drawer.tsx +1 -1
- package/src/components/DropdownMenu/DropdownMenu.tsx +6 -8
- 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.module.scss +1 -1
- 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/RadioInput.module.scss +2 -2
- package/src/components/RadioGroup/RadioInput/RadioInputIcon.tsx +0 -4
- package/src/components/SelectInput/SelectInput.module.scss +1 -1
- package/src/components/SelectInput/SelectInput.test.tsx +18 -0
- package/src/components/SelectInput/SelectInput.tsx +47 -27
- package/src/components/SelectInputInset/SelectInputInset.module.scss +1 -1
- package/src/components/SelectInputInset/SelectInputInset.tsx +8 -9
- package/src/components/SelectInputNative/SelectInputNative.module.scss +1 -1
- package/src/components/SelectInputNative/SelectInputNative.test.tsx +24 -0
- package/src/components/SelectInputNative/SelectInputNative.tsx +79 -14
- package/src/components/Switch/Switch.module.scss +1 -1
- 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.module.scss +1 -1
- package/src/components/TextInput/TextInput.tsx +10 -8
- package/src/components/TextInputInset/TextInputInset.module.scss +1 -1
- package/src/components/TextInputInset/TextInputInset.tsx +5 -6
- package/src/components/TextareaInput/TextareaInput.module.scss +1 -1
- package/src/components/TextareaInput/TextareaInput.tsx +4 -5
- package/src/components/TextareaInputInset/TextareaInputInset.module.scss +1 -1
- package/src/components/TextareaInputInset/TextareaInputInset.tsx +10 -17
- package/src/components/Toast/Toast.stories.tsx +5 -1
- package/src/components/Toggle/Toggle.module.scss +1 -1
- package/src/components/ToggleGroup/ToggleGroup.module.scss +1 -1
- package/src/styles/display.scss +1 -1
- package/src/styles/flex.scss +1 -1
- package/src/styles/overflow.scss +2 -0
- package/src/styles/position.scss +2 -0
- package/src/styles/text-align.scss +2 -0
- package/src/styles/utilities.scss +10 -9
- package/src/styles/variables/index.scss +2 -1
- package/src/styles/white-space.scss +2 -0
- package/src/types/index.ts +2 -3
|
@@ -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> = ({
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
@
|
|
1
|
+
@use '@hyphen/hyphen-design-tokens/build/scss/variables' as *;
|
|
2
2
|
// The following styles required the :global tag since they need to be applied to react-select elements for which we do not own the markup
|
|
3
3
|
|
|
4
4
|
@mixin react-select-icon {
|
|
@@ -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', () => {
|
|
@@ -7,7 +7,9 @@ import Select, {
|
|
|
7
7
|
OptionsOrGroups,
|
|
8
8
|
OnChangeValue,
|
|
9
9
|
} from 'react-select';
|
|
10
|
-
import AsyncCreatableSelect
|
|
10
|
+
import AsyncCreatableSelect, {
|
|
11
|
+
AsyncCreatableProps,
|
|
12
|
+
} from 'react-select/async-creatable';
|
|
11
13
|
import AsyncSelect from 'react-select/async';
|
|
12
14
|
import CreatableSelect from 'react-select/creatable';
|
|
13
15
|
import { ResponsiveProp } from '../../types';
|
|
@@ -42,7 +44,7 @@ export type TextInputSize =
|
|
|
42
44
|
| 'lg'
|
|
43
45
|
| ResponsiveProp<'sm' | 'md' | 'lg'>;
|
|
44
46
|
|
|
45
|
-
|
|
47
|
+
interface SelectInputOwnProps {
|
|
46
48
|
/**
|
|
47
49
|
* The id attribute of the input.
|
|
48
50
|
*/
|
|
@@ -59,10 +61,6 @@ export interface SelectInputProps {
|
|
|
59
61
|
* The value(s) of select.
|
|
60
62
|
*/
|
|
61
63
|
value: any | any[]; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
62
|
-
/**
|
|
63
|
-
* Options for dropdown list.
|
|
64
|
-
*/
|
|
65
|
-
options: SelectInputOptions | AsyncOptions;
|
|
66
64
|
/**
|
|
67
65
|
* Autofocus select input on render.
|
|
68
66
|
*/
|
|
@@ -84,10 +82,6 @@ export interface SelectInputProps {
|
|
|
84
82
|
* Visually hide the label.
|
|
85
83
|
*/
|
|
86
84
|
hideLabel?: boolean;
|
|
87
|
-
/**
|
|
88
|
-
* Load the input asynchronously.
|
|
89
|
-
*/
|
|
90
|
-
isAsync?: boolean;
|
|
91
85
|
/**
|
|
92
86
|
* If the input value is clearable programmatically.
|
|
93
87
|
*/
|
|
@@ -137,18 +131,36 @@ export interface SelectInputProps {
|
|
|
137
131
|
* The size of the text input.
|
|
138
132
|
*/
|
|
139
133
|
size?: TextInputSize;
|
|
140
|
-
/**
|
|
141
|
-
* Additional props to be spread. These will be applied specifically to
|
|
142
|
-
* the `react-select` component that powers the select. For full docs on
|
|
143
|
-
* react-select props, [Click Here](https://react-select.com/props)
|
|
144
|
-
*/
|
|
145
|
-
[x: string]: any; // eslint-disable-line
|
|
146
134
|
}
|
|
147
135
|
|
|
136
|
+
type ReactSelectAsyncProps = AsyncCreatableProps<
|
|
137
|
+
SelectOption,
|
|
138
|
+
boolean,
|
|
139
|
+
SelectGroupOptions
|
|
140
|
+
>;
|
|
141
|
+
|
|
142
|
+
type ControlledReactSelectProps =
|
|
143
|
+
| 'aria-label'
|
|
144
|
+
| 'aria-labelledby'
|
|
145
|
+
| 'cacheOptions'
|
|
146
|
+
| 'classNamePrefix'
|
|
147
|
+
| 'components'
|
|
148
|
+
| 'defaultOptions'
|
|
149
|
+
| 'inputId'
|
|
150
|
+
| 'loadOptions'
|
|
151
|
+
| 'options'
|
|
152
|
+
| 'styles';
|
|
153
|
+
|
|
154
|
+
type SelectInputBaseProps = SelectInputOwnProps &
|
|
155
|
+
Omit<
|
|
156
|
+
ReactSelectAsyncProps,
|
|
157
|
+
keyof SelectInputOwnProps | ControlledReactSelectProps
|
|
158
|
+
>;
|
|
159
|
+
|
|
148
160
|
type AsyncOptions = (
|
|
149
161
|
inputValue: string
|
|
150
162
|
) => Promise<OptionsOrGroups<SelectOption, SelectGroupOptions>>;
|
|
151
|
-
type AsyncSelectInputProps =
|
|
163
|
+
export type AsyncSelectInputProps = SelectInputBaseProps & {
|
|
152
164
|
/**
|
|
153
165
|
* Load the input asynchronously.
|
|
154
166
|
*/
|
|
@@ -160,14 +172,14 @@ type AsyncSelectInputProps = SelectInputProps & {
|
|
|
160
172
|
/**
|
|
161
173
|
* If cacheOptions is passed, then the loaded data will be cached.
|
|
162
174
|
*/
|
|
163
|
-
cacheOptions?:
|
|
175
|
+
cacheOptions?: ReactSelectAsyncProps['cacheOptions'];
|
|
164
176
|
/**
|
|
165
177
|
* The default set of options to show before the user starts searching.
|
|
166
178
|
*/
|
|
167
|
-
defaultOptions?:
|
|
179
|
+
defaultOptions?: ReactSelectAsyncProps['defaultOptions'];
|
|
168
180
|
};
|
|
169
181
|
|
|
170
|
-
type SyncSelectInputProps =
|
|
182
|
+
export type SyncSelectInputProps = SelectInputBaseProps & {
|
|
171
183
|
/**
|
|
172
184
|
* Load the input synchronously.
|
|
173
185
|
*/
|
|
@@ -176,10 +188,12 @@ type SyncSelectInputProps = SelectInputProps & {
|
|
|
176
188
|
* Options for dropdown list.
|
|
177
189
|
*/
|
|
178
190
|
options: SelectInputOptions;
|
|
191
|
+
cacheOptions?: never;
|
|
192
|
+
defaultOptions?: never;
|
|
179
193
|
};
|
|
180
194
|
|
|
181
|
-
export
|
|
182
|
-
|
|
195
|
+
export type SelectInputProps = AsyncSelectInputProps | SyncSelectInputProps;
|
|
196
|
+
|
|
183
197
|
export function SelectInput(props: SelectInputProps): JSX.Element {
|
|
184
198
|
const {
|
|
185
199
|
id,
|
|
@@ -205,6 +219,8 @@ export function SelectInput(props: SelectInputProps): JSX.Element {
|
|
|
205
219
|
placeholder = undefined,
|
|
206
220
|
requiredIndicator = ' *',
|
|
207
221
|
size = 'md',
|
|
222
|
+
cacheOptions,
|
|
223
|
+
defaultOptions,
|
|
208
224
|
...restProps
|
|
209
225
|
} = props;
|
|
210
226
|
|
|
@@ -262,13 +278,17 @@ export function SelectInput(props: SelectInputProps): JSX.Element {
|
|
|
262
278
|
isCreatable && isAsync
|
|
263
279
|
? AsyncCreatableSelect
|
|
264
280
|
: isCreatable
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
281
|
+
? CreatableSelect
|
|
282
|
+
: isAsync
|
|
283
|
+
? AsyncSelect
|
|
284
|
+
: Select;
|
|
269
285
|
|
|
270
286
|
const selectOptions = isAsync
|
|
271
|
-
?
|
|
287
|
+
? {
|
|
288
|
+
loadOptions: options as AsyncOptions,
|
|
289
|
+
cacheOptions,
|
|
290
|
+
defaultOptions,
|
|
291
|
+
}
|
|
272
292
|
: ({ options } as { options: SelectInputOptions });
|
|
273
293
|
|
|
274
294
|
return (
|
|
@@ -6,7 +6,6 @@ import React, {
|
|
|
6
6
|
FocusEvent,
|
|
7
7
|
ForwardRefExoticComponent,
|
|
8
8
|
ReactNode,
|
|
9
|
-
HTMLProps,
|
|
10
9
|
} from 'react';
|
|
11
10
|
import classNames from 'classnames';
|
|
12
11
|
import { ResponsiveProp } from '../../types';
|
|
@@ -36,11 +35,15 @@ export interface SelectInputInsetProps {
|
|
|
36
35
|
/**
|
|
37
36
|
* Callback function to call on change event.
|
|
38
37
|
*/
|
|
39
|
-
onChange: (event: ChangeEvent<
|
|
38
|
+
onChange: (event: ChangeEvent<HTMLSelectElement>) => void;
|
|
40
39
|
/**
|
|
41
40
|
* Value of selected option. Should match the value key in the option object.
|
|
42
41
|
*/
|
|
43
42
|
value: string | number | null;
|
|
43
|
+
/**
|
|
44
|
+
* The select's autocomplete behavior.
|
|
45
|
+
*/
|
|
46
|
+
autoComplete?: boolean | string;
|
|
44
47
|
/**
|
|
45
48
|
* Automatically focus the input when the page is loaded.
|
|
46
49
|
*/
|
|
@@ -61,7 +64,7 @@ export interface SelectInputInsetProps {
|
|
|
61
64
|
/**
|
|
62
65
|
* Props passed directly to the input element of the component
|
|
63
66
|
*/
|
|
64
|
-
inputProps?: BoxProps
|
|
67
|
+
inputProps?: Omit<BoxProps<'select'>, 'as'>;
|
|
65
68
|
/**
|
|
66
69
|
* The input's disabled attribute
|
|
67
70
|
*/
|
|
@@ -77,7 +80,7 @@ export interface SelectInputInsetProps {
|
|
|
77
80
|
/**
|
|
78
81
|
* Callback function to call on blur event.
|
|
79
82
|
*/
|
|
80
|
-
onBlur?: (event: FocusEvent<
|
|
83
|
+
onBlur?: (event: FocusEvent<HTMLSelectElement>) => void;
|
|
81
84
|
/**
|
|
82
85
|
* Callback function to call when input us cleared. When this is passed,
|
|
83
86
|
* the input will display an icon on the right side, for triggering this callback.
|
|
@@ -88,7 +91,7 @@ export interface SelectInputInsetProps {
|
|
|
88
91
|
/**
|
|
89
92
|
* Callback function to call on focus event.
|
|
90
93
|
*/
|
|
91
|
-
onFocus?: (event: FocusEvent<
|
|
94
|
+
onFocus?: (event: FocusEvent<HTMLSelectElement>) => void;
|
|
92
95
|
/**
|
|
93
96
|
* The input placeholder attribute.
|
|
94
97
|
*/
|
|
@@ -101,10 +104,6 @@ export interface SelectInputInsetProps {
|
|
|
101
104
|
* The size of the text input.
|
|
102
105
|
*/
|
|
103
106
|
size?: SelectInputInsetSize | ResponsiveProp<SelectInputInsetSize>;
|
|
104
|
-
/**
|
|
105
|
-
* Additional props to be spread to rendered element
|
|
106
|
-
*/
|
|
107
|
-
[x: string]: any; // eslint-disable-line
|
|
108
107
|
}
|
|
109
108
|
|
|
110
109
|
export const SelectInputInset: ForwardRefExoticComponent<SelectInputInsetProps> =
|
|
@@ -102,6 +102,30 @@ describe('SelectInputNative', () => {
|
|
|
102
102
|
|
|
103
103
|
expect(mockedHandleBlur).toBeCalledTimes(1);
|
|
104
104
|
});
|
|
105
|
+
|
|
106
|
+
test('it forwards native select attributes to the select element', () => {
|
|
107
|
+
render(
|
|
108
|
+
<SelectInputNative
|
|
109
|
+
id="testId"
|
|
110
|
+
label="native attributes"
|
|
111
|
+
onChange={() => undefined}
|
|
112
|
+
options={selectOptions}
|
|
113
|
+
value={null}
|
|
114
|
+
form="settings-form"
|
|
115
|
+
disabled
|
|
116
|
+
required
|
|
117
|
+
data-testid="native-select"
|
|
118
|
+
inputProps={{ title: 'Select a flavor' }}
|
|
119
|
+
/>
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
const select = screen.getByTestId('native-select');
|
|
123
|
+
expect(select).toHaveAttribute('form', 'settings-form');
|
|
124
|
+
expect(select).toHaveAttribute('title', 'Select a flavor');
|
|
125
|
+
expect(select).toBeDisabled();
|
|
126
|
+
expect(select).toBeRequired();
|
|
127
|
+
expect(select.parentElement).not.toHaveAttribute('form');
|
|
128
|
+
});
|
|
105
129
|
});
|
|
106
130
|
|
|
107
131
|
describe('States', () => {
|
|
@@ -2,7 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import classNames from 'classnames';
|
|
3
3
|
import { generateResponsiveClasses } from '../../lib/generateResponsiveClasses';
|
|
4
4
|
import { ResponsiveProp } from '../../types';
|
|
5
|
-
import { Box, BoxProps } from '../Box/Box';
|
|
5
|
+
import { Box, BoxOwnProps, BoxProps, boxPropsKeys } from '../Box/Box';
|
|
6
6
|
import { FormControl, FormControlProps } from '../FormControl/FormControl';
|
|
7
7
|
import styles from './SelectInputNative.module.scss';
|
|
8
8
|
|
|
@@ -13,7 +13,7 @@ export interface SelectInputNativeOption {
|
|
|
13
13
|
disabled?: boolean;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
interface SelectInputNativeOwnProps {
|
|
17
17
|
/**
|
|
18
18
|
* List of options for the select input.
|
|
19
19
|
*/
|
|
@@ -25,11 +25,36 @@ export interface SelectInputNativeProps extends BoxProps, FormControlProps {
|
|
|
25
25
|
/**
|
|
26
26
|
* Value of selected option. Should match the value key in the option object.
|
|
27
27
|
*/
|
|
28
|
-
value:
|
|
28
|
+
value: React.ComponentPropsWithoutRef<'select'>['value'] | null;
|
|
29
|
+
/**
|
|
30
|
+
* Props passed directly to the select element of the component.
|
|
31
|
+
*/
|
|
32
|
+
inputProps?: Omit<
|
|
33
|
+
BoxProps<'select'>,
|
|
34
|
+
| 'aria-label'
|
|
35
|
+
| 'aria-labelledby'
|
|
36
|
+
| 'aria-required'
|
|
37
|
+
| 'as'
|
|
38
|
+
| 'autoFocus'
|
|
39
|
+
| 'children'
|
|
40
|
+
| 'color'
|
|
41
|
+
| 'defaultValue'
|
|
42
|
+
| 'disabled'
|
|
43
|
+
| 'id'
|
|
44
|
+
| 'name'
|
|
45
|
+
| 'onChange'
|
|
46
|
+
| 'required'
|
|
47
|
+
| 'size'
|
|
48
|
+
| 'value'
|
|
49
|
+
>;
|
|
29
50
|
/**
|
|
30
51
|
* The input's 'name' attribute.
|
|
31
52
|
*/
|
|
32
53
|
name?: string;
|
|
54
|
+
/**
|
|
55
|
+
* Placeholder option displayed before a value is selected.
|
|
56
|
+
*/
|
|
57
|
+
placeholder?: string;
|
|
33
58
|
/**
|
|
34
59
|
* Visual indicator that the field is required, that gets appended to the label
|
|
35
60
|
*/
|
|
@@ -42,12 +67,38 @@ export interface SelectInputNativeProps extends BoxProps, FormControlProps {
|
|
|
42
67
|
* Whether the input is autofocused on initial render.
|
|
43
68
|
*/
|
|
44
69
|
autoFocus?: HTMLSelectElement['autofocus'];
|
|
45
|
-
/**
|
|
46
|
-
* Additional props to be spread.
|
|
47
|
-
*/
|
|
48
|
-
[x: string]: any; // eslint-disable-line
|
|
49
70
|
}
|
|
50
71
|
|
|
72
|
+
type SelectInputNativeFormControlProps = Pick<
|
|
73
|
+
FormControlProps,
|
|
74
|
+
| 'error'
|
|
75
|
+
| 'helpText'
|
|
76
|
+
| 'hideLabel'
|
|
77
|
+
| 'id'
|
|
78
|
+
| 'isDisabled'
|
|
79
|
+
| 'isRequired'
|
|
80
|
+
| 'label'
|
|
81
|
+
>;
|
|
82
|
+
|
|
83
|
+
type SelectInputNativeElementProps = Omit<
|
|
84
|
+
React.ComponentPropsWithoutRef<'select'>,
|
|
85
|
+
| keyof SelectInputNativeOwnProps
|
|
86
|
+
| keyof SelectInputNativeFormControlProps
|
|
87
|
+
| keyof BoxOwnProps
|
|
88
|
+
| 'aria-label'
|
|
89
|
+
| 'aria-labelledby'
|
|
90
|
+
| 'aria-required'
|
|
91
|
+
| 'children'
|
|
92
|
+
| 'dangerouslySetInnerHTML'
|
|
93
|
+
| 'defaultValue'
|
|
94
|
+
| 'size'
|
|
95
|
+
>;
|
|
96
|
+
|
|
97
|
+
export type SelectInputNativeProps = SelectInputNativeOwnProps &
|
|
98
|
+
SelectInputNativeFormControlProps &
|
|
99
|
+
Omit<BoxOwnProps, 'children'> &
|
|
100
|
+
SelectInputNativeElementProps;
|
|
101
|
+
|
|
51
102
|
export const SelectInputNative: React.FC<SelectInputNativeProps> = ({
|
|
52
103
|
autoFocus = false,
|
|
53
104
|
label,
|
|
@@ -55,6 +106,7 @@ export const SelectInputNative: React.FC<SelectInputNativeProps> = ({
|
|
|
55
106
|
helpText,
|
|
56
107
|
error,
|
|
57
108
|
id,
|
|
109
|
+
inputProps = {},
|
|
58
110
|
isDisabled,
|
|
59
111
|
isRequired,
|
|
60
112
|
name,
|
|
@@ -66,6 +118,17 @@ export const SelectInputNative: React.FC<SelectInputNativeProps> = ({
|
|
|
66
118
|
size = 'md',
|
|
67
119
|
...restProps
|
|
68
120
|
}) => {
|
|
121
|
+
const isBoxProp = (key: string) =>
|
|
122
|
+
boxPropsKeys.includes(key as keyof BoxOwnProps);
|
|
123
|
+
const formControlProps = Object.fromEntries(
|
|
124
|
+
Object.entries(restProps).filter(([key]) => isBoxProp(key))
|
|
125
|
+
) as Omit<BoxOwnProps, 'children'>;
|
|
126
|
+
const selectProps = Object.fromEntries(
|
|
127
|
+
Object.entries(restProps).filter(([key]) => !isBoxProp(key))
|
|
128
|
+
) as SelectInputNativeElementProps;
|
|
129
|
+
const selectIsDisabled = isDisabled || selectProps.disabled;
|
|
130
|
+
const selectIsRequired = isRequired || selectProps.required;
|
|
131
|
+
|
|
69
132
|
const placeholderOption: SelectInputNativeOption = {
|
|
70
133
|
value: '',
|
|
71
134
|
label: placeholder,
|
|
@@ -83,7 +146,7 @@ export const SelectInputNative: React.FC<SelectInputNativeProps> = ({
|
|
|
83
146
|
styles['select-input-native-wrapper'],
|
|
84
147
|
...responsiveClasses.map((className) => styles[className]),
|
|
85
148
|
{
|
|
86
|
-
[styles.disabled]:
|
|
149
|
+
[styles.disabled]: selectIsDisabled,
|
|
87
150
|
[styles.error]: error,
|
|
88
151
|
}
|
|
89
152
|
);
|
|
@@ -95,25 +158,27 @@ export const SelectInputNative: React.FC<SelectInputNativeProps> = ({
|
|
|
95
158
|
id={id}
|
|
96
159
|
error={error}
|
|
97
160
|
helpText={helpText}
|
|
98
|
-
isDisabled={
|
|
99
|
-
isRequired={
|
|
161
|
+
isDisabled={selectIsDisabled}
|
|
162
|
+
isRequired={selectIsRequired}
|
|
100
163
|
requiredIndicator={requiredIndicator}
|
|
101
|
-
{...
|
|
164
|
+
{...formControlProps}
|
|
102
165
|
>
|
|
103
166
|
<Box className={selectWrapperClasses}>
|
|
104
167
|
<Box
|
|
105
168
|
as="select"
|
|
169
|
+
{...inputProps}
|
|
170
|
+
{...selectProps}
|
|
106
171
|
aria-label={label}
|
|
107
172
|
aria-labelledby={label && !hideLabel ? `${id}Label` : undefined}
|
|
108
|
-
aria-required={
|
|
173
|
+
aria-required={selectIsRequired}
|
|
109
174
|
value={value ?? ''}
|
|
110
175
|
onChange={onChange}
|
|
111
176
|
color={!value ? 'disabled' : 'base'}
|
|
112
177
|
autoFocus={autoFocus}
|
|
113
|
-
disabled={
|
|
178
|
+
disabled={selectIsDisabled}
|
|
114
179
|
name={name}
|
|
115
180
|
id={id}
|
|
116
|
-
required={
|
|
181
|
+
required={selectIsRequired}
|
|
117
182
|
>
|
|
118
183
|
{optionsWithPlaceholder.map((option) => (
|
|
119
184
|
<Box
|
|
@@ -139,7 +139,7 @@ export const Switch: FC<SwitchProps> = ({
|
|
|
139
139
|
alignItems: helpText ? 'flex-start' : ('center' as BoxProps['alignItems']),
|
|
140
140
|
isFieldRequired: isRequired,
|
|
141
141
|
requiredIndicator,
|
|
142
|
-
size,
|
|
142
|
+
...(typeof size === 'string' && { size }),
|
|
143
143
|
};
|
|
144
144
|
|
|
145
145
|
return (
|
|
@@ -146,7 +146,7 @@ The `Column` object is equiped with a `render` method. When used, the cells will
|
|
|
146
146
|
|
|
147
147
|
The render method also exposes data about the cell and row, namely:
|
|
148
148
|
|
|
149
|
-
`cell: the content in the cell,`
|
|
149
|
+
`cell: the content in the cell, typed as Cell — narrow it before rendering,`
|
|
150
150
|
|
|
151
151
|
`row: the entire row object,`
|
|
152
152
|
|
|
@@ -106,7 +106,11 @@ export const Column = () =>
|
|
|
106
106
|
{
|
|
107
107
|
heading: 'Type',
|
|
108
108
|
dataKey: 'type',
|
|
109
|
-
render: (cell
|
|
109
|
+
render: (cell?: Cell) => (
|
|
110
|
+
<code style={codePreviewStyle}>
|
|
111
|
+
{typeof cell === 'string' || typeof cell === 'number' ? cell : null}
|
|
112
|
+
</code>
|
|
113
|
+
),
|
|
110
114
|
},
|
|
111
115
|
{ heading: 'Description', dataKey: 'description' },
|
|
112
116
|
];
|
|
@@ -48,5 +48,21 @@ describe('TableRow', () => {
|
|
|
48
48
|
const element = document.getElementsByClassName('string-class')[0];
|
|
49
49
|
expect(element).toBeInTheDocument();
|
|
50
50
|
});
|
|
51
|
+
|
|
52
|
+
test('renders a placeholder instead of a non-renderable row value', () => {
|
|
53
|
+
render(
|
|
54
|
+
<TableRow
|
|
55
|
+
row={{ metadata: { state: 'ready' }, action: <button>Open</button> }}
|
|
56
|
+
columns={[
|
|
57
|
+
{ heading: 'Metadata', dataKey: 'metadata' },
|
|
58
|
+
{ heading: 'Action', dataKey: 'action' },
|
|
59
|
+
]}
|
|
60
|
+
emptyCellPlaceholder="--"
|
|
61
|
+
/>
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
expect(screen.getByText('--')).toBeInTheDocument();
|
|
65
|
+
expect(screen.getByRole('button', { name: 'Open' })).toBeInTheDocument();
|
|
66
|
+
});
|
|
51
67
|
});
|
|
52
68
|
});
|
|
@@ -93,13 +93,31 @@ export const TableRow: FC<TableRowProps> = ({
|
|
|
93
93
|
className
|
|
94
94
|
);
|
|
95
95
|
|
|
96
|
+
const isRenderableCell = (value: unknown): value is ReactNode => {
|
|
97
|
+
if (value === null || value === undefined) return true;
|
|
98
|
+
|
|
99
|
+
const valueType = typeof value;
|
|
100
|
+
if (
|
|
101
|
+
valueType === 'string' ||
|
|
102
|
+
valueType === 'number' ||
|
|
103
|
+
valueType === 'boolean'
|
|
104
|
+
) {
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (Array.isArray(value)) return value.every(isRenderableCell);
|
|
109
|
+
|
|
110
|
+
return React.isValidElement(value);
|
|
111
|
+
};
|
|
112
|
+
|
|
96
113
|
const renderCellContent = (column: Column): ReactNode => {
|
|
97
114
|
if (column.render) {
|
|
98
115
|
const cellValue = column.dataKey && row ? row[column.dataKey] : undefined;
|
|
99
116
|
return column.render(cellValue, row, rowIndex);
|
|
100
117
|
}
|
|
101
118
|
|
|
102
|
-
|
|
119
|
+
const cellValue = column.dataKey && row ? row[column.dataKey] : null;
|
|
120
|
+
return isRenderableCell(cellValue) ? cellValue : null;
|
|
103
121
|
};
|
|
104
122
|
|
|
105
123
|
const getCellClassName = (column: Column): string | undefined => {
|
|
@@ -3,7 +3,6 @@ import React, {
|
|
|
3
3
|
ChangeEvent,
|
|
4
4
|
FocusEvent,
|
|
5
5
|
ForwardRefExoticComponent,
|
|
6
|
-
HTMLProps,
|
|
7
6
|
InputHTMLAttributes,
|
|
8
7
|
KeyboardEvent,
|
|
9
8
|
MouseEvent,
|
|
@@ -11,7 +10,7 @@ import React, {
|
|
|
11
10
|
forwardRef,
|
|
12
11
|
} from 'react';
|
|
13
12
|
|
|
14
|
-
import { FormControl } from '../FormControl/FormControl';
|
|
13
|
+
import { FormControl, FormControlProps } from '../FormControl/FormControl';
|
|
15
14
|
import { Icon } from '../Icon/Icon';
|
|
16
15
|
import { ResponsiveProp } from '../../types';
|
|
17
16
|
import classNames from 'classnames';
|
|
@@ -21,7 +20,7 @@ import { getAutoCompleteValue } from '../../lib/getAutoCompleteValue';
|
|
|
21
20
|
import styles from './TextInput.module.scss';
|
|
22
21
|
|
|
23
22
|
export type TextInputSizeType = 'sm' | 'md' | 'lg';
|
|
24
|
-
|
|
23
|
+
interface TextInputOwnProps {
|
|
25
24
|
/**
|
|
26
25
|
* The input's id attribute. Used to programmatically tie the input with its label.
|
|
27
26
|
*/
|
|
@@ -38,6 +37,10 @@ export interface TextInputProps {
|
|
|
38
37
|
* The text value of the input. Required since our Input is a controlled component.
|
|
39
38
|
*/
|
|
40
39
|
value: InputHTMLAttributes<HTMLInputElement>['value'];
|
|
40
|
+
/**
|
|
41
|
+
* The input's autocomplete behavior.
|
|
42
|
+
*/
|
|
43
|
+
autoComplete?: boolean | string;
|
|
41
44
|
/**
|
|
42
45
|
* Automatically focus the input when the page is loaded.
|
|
43
46
|
*/
|
|
@@ -62,7 +65,7 @@ export interface TextInputProps {
|
|
|
62
65
|
/**
|
|
63
66
|
* Props passed directly to the input element of the component
|
|
64
67
|
*/
|
|
65
|
-
inputProps?: BoxProps
|
|
68
|
+
inputProps?: Omit<BoxProps<'input'>, 'as'>;
|
|
66
69
|
/**
|
|
67
70
|
* The input's disabled attribute
|
|
68
71
|
*/
|
|
@@ -119,12 +122,11 @@ export interface TextInputProps {
|
|
|
119
122
|
* The input 'type' value. Defaults to type 'text'.
|
|
120
123
|
*/
|
|
121
124
|
type?: InputHTMLAttributes<HTMLInputElement>['type'];
|
|
122
|
-
/**
|
|
123
|
-
* Additional props to be spread to rendered element
|
|
124
|
-
*/
|
|
125
|
-
[x: string]: any; // eslint-disable-line
|
|
126
125
|
}
|
|
127
126
|
|
|
127
|
+
export type TextInputProps = TextInputOwnProps &
|
|
128
|
+
Omit<FormControlProps, keyof TextInputOwnProps>;
|
|
129
|
+
|
|
128
130
|
export const TextInput: ForwardRefExoticComponent<TextInputProps> = forwardRef<
|
|
129
131
|
HTMLDivElement,
|
|
130
132
|
TextInputProps
|