@transferwise/components 0.0.0-experimental-946d3a7 → 0.0.0-experimental-bf4cb26
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/build/index.js +157 -57
- package/build/index.js.map +1 -1
- package/build/index.mjs +157 -59
- package/build/index.mjs.map +1 -1
- package/build/types/dateInput/DateInput.d.ts +3 -2
- package/build/types/dateInput/DateInput.d.ts.map +1 -1
- package/build/types/field/Field.d.ts +12 -0
- package/build/types/field/Field.d.ts.map +1 -0
- package/build/types/index.d.ts +4 -0
- package/build/types/index.d.ts.map +1 -1
- package/build/types/inputs/Input.d.ts.map +1 -1
- package/build/types/inputs/SelectInput.d.ts +1 -2
- package/build/types/inputs/SelectInput.d.ts.map +1 -1
- package/build/types/inputs/TextArea.d.ts.map +1 -1
- package/build/types/inputs/_common.d.ts +2 -2
- package/build/types/inputs/_common.d.ts.map +1 -1
- package/build/types/inputs/contexts.d.ts +17 -0
- package/build/types/inputs/contexts.d.ts.map +1 -0
- package/build/types/label/Label.d.ts +9 -0
- package/build/types/label/Label.d.ts.map +1 -0
- package/build/types/radioGroup/RadioGroup.d.ts.map +1 -1
- package/build/types/switch/Switch.d.ts +6 -3
- package/build/types/switch/Switch.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/dateInput/DateInput.rtl.spec.tsx +17 -0
- package/src/dateInput/DateInput.tsx +24 -18
- package/src/field/Field.spec.tsx +95 -0
- package/src/field/Field.story.tsx +59 -0
- package/src/field/Field.tsx +70 -0
- package/src/index.ts +4 -0
- package/src/inputs/Input.tsx +5 -3
- package/src/inputs/SelectInput.spec.tsx +8 -9
- package/src/inputs/SelectInput.tsx +9 -9
- package/src/inputs/TextArea.tsx +6 -3
- package/src/inputs/_ButtonInput.tsx +2 -2
- package/src/inputs/_common.ts +2 -2
- package/src/inputs/contexts.ts +28 -0
- package/src/label/Label.spec.tsx +26 -0
- package/src/label/Label.story.tsx +37 -0
- package/src/label/Label.tsx +20 -0
- package/src/radioGroup/RadioGroup.rtl.spec.tsx +14 -0
- package/src/radioGroup/RadioGroup.story.tsx +26 -0
- package/src/radioGroup/RadioGroup.tsx +4 -1
- package/src/switch/Switch.spec.tsx +10 -0
- package/src/switch/Switch.tsx +22 -13
package/src/inputs/Input.tsx
CHANGED
|
@@ -3,9 +3,9 @@ import { forwardRef } from 'react';
|
|
|
3
3
|
|
|
4
4
|
import { SizeLarge, SizeMedium, SizeSmall } from '../common';
|
|
5
5
|
import { Merge } from '../utils';
|
|
6
|
-
|
|
6
|
+
import { inputClassNameBase } from './_common';
|
|
7
|
+
import { useInputAttributes } from './contexts';
|
|
7
8
|
import { useInputPaddings } from './InputGroup';
|
|
8
|
-
import { formControlClassNameBase } from './_common';
|
|
9
9
|
|
|
10
10
|
export interface InputProps
|
|
11
11
|
extends Merge<
|
|
@@ -21,17 +21,19 @@ export const Input = forwardRef(function Input(
|
|
|
21
21
|
{ size = 'auto', shape = 'rectangle', className, ...restProps }: InputProps,
|
|
22
22
|
reference: React.ForwardedRef<HTMLInputElement>,
|
|
23
23
|
) {
|
|
24
|
+
const inputAttributes = useInputAttributes();
|
|
24
25
|
const inputPaddings = useInputPaddings();
|
|
25
26
|
|
|
26
27
|
return (
|
|
27
28
|
<input
|
|
28
29
|
ref={reference}
|
|
29
|
-
className={classNames(className,
|
|
30
|
+
className={classNames(className, inputClassNameBase({ size }), 'np-input', {
|
|
30
31
|
'np-input--shape-rectangle': shape === 'rectangle',
|
|
31
32
|
'np-input--shape-pill': shape === 'pill',
|
|
32
33
|
})}
|
|
33
34
|
// eslint-disable-next-line react/forbid-dom-props
|
|
34
35
|
style={inputPaddings}
|
|
36
|
+
{...inputAttributes}
|
|
35
37
|
{...restProps}
|
|
36
38
|
/>
|
|
37
39
|
);
|
|
@@ -4,6 +4,7 @@ import userEvent from '@testing-library/user-event';
|
|
|
4
4
|
import { render, mockMatchMedia, mockResizeObserver } from '../test-utils';
|
|
5
5
|
|
|
6
6
|
import { SelectInput } from './SelectInput';
|
|
7
|
+
import { Field } from '../field/Field';
|
|
7
8
|
|
|
8
9
|
mockMatchMedia();
|
|
9
10
|
mockResizeObserver();
|
|
@@ -216,14 +217,12 @@ describe('SelectInput', () => {
|
|
|
216
217
|
expect(trigger).toHaveAttribute('id', 'custom');
|
|
217
218
|
});
|
|
218
219
|
|
|
219
|
-
it('supports `
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
expect(
|
|
226
|
-
userEvent.tab();
|
|
227
|
-
expect(handleBlur).toHaveBeenCalled();
|
|
220
|
+
it('supports `Field` for labeling', () => {
|
|
221
|
+
render(
|
|
222
|
+
<Field label="Currency">
|
|
223
|
+
<SelectInput items={[{ type: 'option', value: 'USD' }]} value="USD" />
|
|
224
|
+
</Field>,
|
|
225
|
+
);
|
|
226
|
+
expect(screen.getByLabelText('Currency')).toHaveTextContent('USD');
|
|
228
227
|
});
|
|
229
228
|
});
|
|
@@ -13,12 +13,13 @@ import { Breakpoint } from '../common/propsValues/breakpoint';
|
|
|
13
13
|
import dateTriggerMessages from '../dateLookup/dateTrigger/DateTrigger.messages';
|
|
14
14
|
import { Merge } from '../utils';
|
|
15
15
|
|
|
16
|
-
import { InputGroup } from './InputGroup';
|
|
17
|
-
import { SearchInput } from './SearchInput';
|
|
18
|
-
import messages from './SelectInput.messages';
|
|
19
16
|
import { BottomSheet } from './_BottomSheet';
|
|
20
17
|
import { ButtonInput } from './_ButtonInput';
|
|
21
18
|
import { Popover } from './_Popover';
|
|
19
|
+
import { useInputAttributes } from './contexts';
|
|
20
|
+
import { InputGroup } from './InputGroup';
|
|
21
|
+
import { SearchInput } from './SearchInput';
|
|
22
|
+
import messages from './SelectInput.messages';
|
|
22
23
|
|
|
23
24
|
function searchableString(value: string) {
|
|
24
25
|
return value.trim().replace(/\s+/gu, ' ').normalize('NFKC').toLowerCase();
|
|
@@ -156,7 +157,6 @@ export interface SelectInputProps<T = string, M extends boolean = false> {
|
|
|
156
157
|
onFilterChange?: (args: { query: string; queryNormalized: string | null }) => void;
|
|
157
158
|
onChange?: (value: M extends true ? T[] : T) => void;
|
|
158
159
|
onClear?: () => void;
|
|
159
|
-
onBlur?: () => void;
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
const defaultRenderTrigger = (({ content, placeholderShown, clear, disabled, size, className }) => (
|
|
@@ -221,7 +221,7 @@ function SelectInputClearButton({ className, onClick }: SelectInputClearButtonPr
|
|
|
221
221
|
const noop = () => {};
|
|
222
222
|
|
|
223
223
|
export function SelectInput<T = string, M extends boolean = false>({
|
|
224
|
-
id,
|
|
224
|
+
id: idProp,
|
|
225
225
|
name,
|
|
226
226
|
multiple,
|
|
227
227
|
placeholder,
|
|
@@ -240,8 +240,10 @@ export function SelectInput<T = string, M extends boolean = false>({
|
|
|
240
240
|
onFilterChange = noop,
|
|
241
241
|
onChange,
|
|
242
242
|
onClear,
|
|
243
|
-
onBlur,
|
|
244
243
|
}: SelectInputProps<T, M>) {
|
|
244
|
+
const inputAttributes = useInputAttributes();
|
|
245
|
+
const id = idProp ?? inputAttributes.id;
|
|
246
|
+
|
|
245
247
|
const [open, setOpen] = useState(false);
|
|
246
248
|
|
|
247
249
|
const [filterQuery, _setFilterQuery] = useState('');
|
|
@@ -296,6 +298,7 @@ export function SelectInput<T = string, M extends boolean = false>({
|
|
|
296
298
|
ref(node);
|
|
297
299
|
triggerRef.current = node;
|
|
298
300
|
},
|
|
301
|
+
...inputAttributes,
|
|
299
302
|
id,
|
|
300
303
|
...mergeProps(
|
|
301
304
|
{
|
|
@@ -312,9 +315,6 @@ export function SelectInput<T = string, M extends boolean = false>({
|
|
|
312
315
|
setOpen((prev) => !prev);
|
|
313
316
|
}
|
|
314
317
|
},
|
|
315
|
-
onBlur: () => {
|
|
316
|
-
onBlur?.();
|
|
317
|
-
},
|
|
318
318
|
},
|
|
319
319
|
getInteractionProps(),
|
|
320
320
|
),
|
package/src/inputs/TextArea.tsx
CHANGED
|
@@ -2,8 +2,8 @@ import classNames from 'classnames';
|
|
|
2
2
|
import { forwardRef } from 'react';
|
|
3
3
|
|
|
4
4
|
import { Merge } from '../utils';
|
|
5
|
-
|
|
6
|
-
import {
|
|
5
|
+
import { inputClassNameBase } from './_common';
|
|
6
|
+
import { useInputAttributes } from './contexts';
|
|
7
7
|
|
|
8
8
|
export interface TextAreaProps
|
|
9
9
|
extends Merge<
|
|
@@ -17,10 +17,13 @@ export const TextArea = forwardRef(function TextArea(
|
|
|
17
17
|
{ className, ...restProps }: TextAreaProps,
|
|
18
18
|
reference: React.ForwardedRef<HTMLTextAreaElement>,
|
|
19
19
|
) {
|
|
20
|
+
const inputAttributes = useInputAttributes();
|
|
21
|
+
|
|
20
22
|
return (
|
|
21
23
|
<textarea
|
|
22
24
|
ref={reference}
|
|
23
|
-
className={classNames(className,
|
|
25
|
+
className={classNames(className, inputClassNameBase(), 'np-text-area')}
|
|
26
|
+
{...inputAttributes}
|
|
24
27
|
{...restProps}
|
|
25
28
|
/>
|
|
26
29
|
);
|
|
@@ -2,7 +2,7 @@ import classNames from 'classnames';
|
|
|
2
2
|
import { forwardRef } from 'react';
|
|
3
3
|
|
|
4
4
|
import { useInputPaddings } from './InputGroup';
|
|
5
|
-
import {
|
|
5
|
+
import { inputClassNameBase } from './_common';
|
|
6
6
|
|
|
7
7
|
export interface ButtonInputProps extends React.ComponentPropsWithRef<'button'> {
|
|
8
8
|
size?: 'sm' | 'md' | 'lg';
|
|
@@ -18,7 +18,7 @@ export const ButtonInput = forwardRef(function ButtonInput(
|
|
|
18
18
|
<button
|
|
19
19
|
ref={ref}
|
|
20
20
|
type="button"
|
|
21
|
-
className={classNames(className,
|
|
21
|
+
className={classNames(className, inputClassNameBase({ size }), 'np-button-input')}
|
|
22
22
|
// eslint-disable-next-line react/forbid-dom-props
|
|
23
23
|
style={{ ...inputPaddings, ...style }}
|
|
24
24
|
{...restProps}
|
package/src/inputs/_common.ts
CHANGED
|
@@ -2,11 +2,11 @@ import classNames from 'classnames';
|
|
|
2
2
|
|
|
3
3
|
import { SizeLarge, SizeMedium, SizeSmall } from '../common';
|
|
4
4
|
|
|
5
|
-
export type
|
|
5
|
+
export type InputPropsBase = {
|
|
6
6
|
size?: 'auto' | SizeSmall | SizeMedium | SizeLarge;
|
|
7
7
|
};
|
|
8
8
|
|
|
9
|
-
export function
|
|
9
|
+
export function inputClassNameBase({ size = 'auto' }: InputPropsBase = {}) {
|
|
10
10
|
return classNames(
|
|
11
11
|
'form-control', // TODO: Deprecate
|
|
12
12
|
'np-form-control',
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react';
|
|
2
|
+
|
|
3
|
+
const FieldLabelIdContext = createContext<string | undefined>(undefined);
|
|
4
|
+
export const FieldLabelIdContextProvider = FieldLabelIdContext.Provider;
|
|
5
|
+
|
|
6
|
+
const InputIdContext = createContext<string | undefined>(undefined);
|
|
7
|
+
export const InputIdContextProvider = InputIdContext.Provider;
|
|
8
|
+
|
|
9
|
+
const InputDescribedByContext = createContext<string | undefined>(undefined);
|
|
10
|
+
export const InputDescribedByProvider = InputDescribedByContext.Provider;
|
|
11
|
+
|
|
12
|
+
const InputInvalidContext = createContext<boolean | undefined>(undefined);
|
|
13
|
+
export const InputInvalidProvider = InputInvalidContext.Provider;
|
|
14
|
+
|
|
15
|
+
interface UseInputAttributesArgs {
|
|
16
|
+
/** Set this to `true` if the underlying element is not directly [labelable as per the HTML specification](https://html.spec.whatwg.org/multipage/forms.html#category-label). */
|
|
17
|
+
nonLabelable?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function useInputAttributes({ nonLabelable = false }: UseInputAttributesArgs = {}) {
|
|
21
|
+
const labelId = useContext(FieldLabelIdContext);
|
|
22
|
+
return {
|
|
23
|
+
id: useContext(InputIdContext),
|
|
24
|
+
'aria-labelledby': nonLabelable ? labelId : undefined,
|
|
25
|
+
'aria-describedby': useContext(InputDescribedByContext),
|
|
26
|
+
'aria-invalid': useContext(InputInvalidContext),
|
|
27
|
+
} satisfies React.HTMLAttributes<HTMLElement>;
|
|
28
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Input } from '../inputs/Input';
|
|
2
|
+
import { render, screen } from '../test-utils';
|
|
3
|
+
import { Label } from './Label';
|
|
4
|
+
|
|
5
|
+
describe('Label', () => {
|
|
6
|
+
it('renders string labels', () => {
|
|
7
|
+
render(
|
|
8
|
+
<Label>
|
|
9
|
+
Phone number
|
|
10
|
+
<Input readOnly />
|
|
11
|
+
</Label>,
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
expect(screen.getByLabelText('Phone number')).toBeInTheDocument();
|
|
15
|
+
});
|
|
16
|
+
it('renders node type labels', () => {
|
|
17
|
+
render(
|
|
18
|
+
<Label>
|
|
19
|
+
<span>Phone number</span>
|
|
20
|
+
<Input readOnly />
|
|
21
|
+
</Label>,
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
expect(screen.getByLabelText('Phone number')).toBeInTheDocument();
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import Info from '../info/Info';
|
|
4
|
+
import { Input } from '../inputs/Input';
|
|
5
|
+
import { Label } from './Label';
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
component: Label,
|
|
9
|
+
title: 'Label',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const Basic = () => {
|
|
13
|
+
const [value, setValue] = useState<string | undefined>('This is some text');
|
|
14
|
+
return (
|
|
15
|
+
<Label>
|
|
16
|
+
Phone number
|
|
17
|
+
<Input value={value} id="input" onChange={({ target }) => setValue(target.value)} />
|
|
18
|
+
</Label>
|
|
19
|
+
);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const WithInfo = () => {
|
|
23
|
+
const [value, setValue] = useState<string | undefined>('This is some text');
|
|
24
|
+
return (
|
|
25
|
+
<Label>
|
|
26
|
+
<span className="d-flex">
|
|
27
|
+
Phone number{' '}
|
|
28
|
+
<Info
|
|
29
|
+
content="This is some help in popover"
|
|
30
|
+
aria-label="The aria label"
|
|
31
|
+
className="m-l-1"
|
|
32
|
+
/>
|
|
33
|
+
</span>
|
|
34
|
+
<Input value={value} id="input" onChange={({ target }) => setValue(target.value)} />
|
|
35
|
+
</Label>
|
|
36
|
+
);
|
|
37
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import classNames from 'classnames';
|
|
2
|
+
|
|
3
|
+
export type LabelProps = {
|
|
4
|
+
id?: string;
|
|
5
|
+
htmlFor?: string;
|
|
6
|
+
className?: string;
|
|
7
|
+
children?: React.ReactNode;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const Label = ({ id, htmlFor, className, children }: LabelProps) => {
|
|
11
|
+
return (
|
|
12
|
+
<label
|
|
13
|
+
id={id}
|
|
14
|
+
htmlFor={htmlFor}
|
|
15
|
+
className={classNames('control-label d-flex flex-column gap-y-1 m-b-0', className)}
|
|
16
|
+
>
|
|
17
|
+
{children}
|
|
18
|
+
</label>
|
|
19
|
+
);
|
|
20
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { render, screen } from '@testing-library/react';
|
|
2
2
|
|
|
3
3
|
import RadioGroup from '.';
|
|
4
|
+
import { Field } from '../field/Field';
|
|
4
5
|
|
|
5
6
|
describe('RadioGroup', () => {
|
|
6
7
|
it('has accessible role', () => {
|
|
@@ -13,4 +14,17 @@ describe('RadioGroup', () => {
|
|
|
13
14
|
);
|
|
14
15
|
expect(screen.getByRole('radiogroup')).toBeInTheDocument();
|
|
15
16
|
});
|
|
17
|
+
|
|
18
|
+
it('supports `Field` for labeling', () => {
|
|
19
|
+
render(
|
|
20
|
+
<Field label="Currency">
|
|
21
|
+
<RadioGroup
|
|
22
|
+
name="currency"
|
|
23
|
+
radios={[{ label: 'USD' }, { label: 'EUR' }]}
|
|
24
|
+
onChange={() => {}}
|
|
25
|
+
/>
|
|
26
|
+
</Field>,
|
|
27
|
+
);
|
|
28
|
+
expect(screen.getByRole('radiogroup')).toHaveAccessibleName(/^Currency/);
|
|
29
|
+
});
|
|
16
30
|
});
|
|
@@ -5,6 +5,7 @@ import { Flag } from '@wise/art';
|
|
|
5
5
|
import Avatar, { AvatarType } from '../avatar';
|
|
6
6
|
|
|
7
7
|
import RadioGroup from './RadioGroup';
|
|
8
|
+
import { Field } from '../field/Field';
|
|
8
9
|
|
|
9
10
|
export default {
|
|
10
11
|
component: RadioGroup,
|
|
@@ -53,3 +54,28 @@ export const Basic = () => {
|
|
|
53
54
|
</div>
|
|
54
55
|
);
|
|
55
56
|
};
|
|
57
|
+
|
|
58
|
+
export const Labeled = () => {
|
|
59
|
+
return (
|
|
60
|
+
<Field label="Do you like our product?">
|
|
61
|
+
<RadioGroup
|
|
62
|
+
name="radio-group"
|
|
63
|
+
radios={[
|
|
64
|
+
{
|
|
65
|
+
value: 'yes',
|
|
66
|
+
label: 'Yes',
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
value: 'definitely',
|
|
70
|
+
label: 'Definitely',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
value: 'absolutely',
|
|
74
|
+
label: 'Absolutely',
|
|
75
|
+
},
|
|
76
|
+
]}
|
|
77
|
+
onChange={(v) => action(v)}
|
|
78
|
+
/>
|
|
79
|
+
</Field>
|
|
80
|
+
);
|
|
81
|
+
};
|
|
@@ -2,6 +2,7 @@ import { useState } from 'react';
|
|
|
2
2
|
|
|
3
3
|
import Radio from '../radio';
|
|
4
4
|
import { RadioProps } from '../radio/Radio';
|
|
5
|
+
import { useInputAttributes } from '../inputs/contexts';
|
|
5
6
|
|
|
6
7
|
export type RadioGroupRadio<T extends string | number = string> = Omit<
|
|
7
8
|
RadioProps<T>,
|
|
@@ -21,10 +22,12 @@ export default function RadioGroup<T extends string | number = never>({
|
|
|
21
22
|
selectedValue: controlledValue,
|
|
22
23
|
onChange,
|
|
23
24
|
}: RadioGroupProps<T>) {
|
|
25
|
+
const inputAttributes = useInputAttributes({ nonLabelable: true });
|
|
26
|
+
|
|
24
27
|
const [uncontrolledValue, setUncontrolledValue] = useState(controlledValue);
|
|
25
28
|
|
|
26
29
|
return radios.length > 0 ? (
|
|
27
|
-
<div role="radiogroup">
|
|
30
|
+
<div role="radiogroup" {...inputAttributes}>
|
|
28
31
|
{radios.map(({ value = '' as T, ...restProps }, index) => (
|
|
29
32
|
<Radio
|
|
30
33
|
// eslint-disable-next-line react/no-array-index-key
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Field } from '../field/Field';
|
|
1
2
|
import { render, fireEvent, screen } from '../test-utils';
|
|
2
3
|
|
|
3
4
|
import Switch from './Switch';
|
|
@@ -81,4 +82,13 @@ describe('Switch', () => {
|
|
|
81
82
|
fireEvent.click(input);
|
|
82
83
|
expect(mockCallback).not.toHaveBeenCalled();
|
|
83
84
|
});
|
|
85
|
+
|
|
86
|
+
it('supports `Field` for labeling', () => {
|
|
87
|
+
render(
|
|
88
|
+
<Field label="Dark mode">
|
|
89
|
+
<Switch checked onClick={props.onClick} />
|
|
90
|
+
</Field>,
|
|
91
|
+
);
|
|
92
|
+
expect(screen.getByLabelText('Dark mode')).toHaveAttribute('role', 'switch');
|
|
93
|
+
});
|
|
84
94
|
});
|
package/src/switch/Switch.tsx
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { CheckCircleFill, CrossCircleFill } from '@transferwise/icons';
|
|
2
2
|
import { useTheme } from '@wise/components-theming';
|
|
3
3
|
import classnames from 'classnames';
|
|
4
|
-
import { KeyboardEventHandler, MouseEvent } from 'react';
|
|
4
|
+
import type { KeyboardEventHandler, MouseEvent } from 'react';
|
|
5
5
|
|
|
6
|
-
import { CommonProps } from '../common';
|
|
7
|
-
import {
|
|
6
|
+
import type { CommonProps } from '../common';
|
|
7
|
+
import { useInputAttributes } from '../inputs/contexts';
|
|
8
8
|
|
|
9
9
|
export type SwitchProps = CommonProps & {
|
|
10
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* Used to describe the purpose of the switch. To be used if there is no external label (i.e. aria-labelledby is null)
|
|
12
|
+
* @deprecated Use `Field` wrapper or the `aria-labelledby` attribute instead.
|
|
13
|
+
*/
|
|
11
14
|
'aria-label'?: string;
|
|
12
15
|
/** A reference to a label that describes the purpose of the switch. Ignored if aria-label is provided */
|
|
13
16
|
'aria-labelledby'?: string;
|
|
@@ -21,8 +24,18 @@ export type SwitchProps = CommonProps & {
|
|
|
21
24
|
};
|
|
22
25
|
|
|
23
26
|
const Switch = (props: SwitchProps) => {
|
|
27
|
+
const inputAttributes = useInputAttributes({ nonLabelable: true });
|
|
28
|
+
|
|
24
29
|
const { isModern } = useTheme();
|
|
25
|
-
const {
|
|
30
|
+
const {
|
|
31
|
+
checked,
|
|
32
|
+
className,
|
|
33
|
+
id = inputAttributes.id,
|
|
34
|
+
'aria-label': ariaLabel,
|
|
35
|
+
'aria-labelledby': ariaLabelledbyProp,
|
|
36
|
+
onClick,
|
|
37
|
+
disabled,
|
|
38
|
+
} = props;
|
|
26
39
|
|
|
27
40
|
const handleKeyDown: KeyboardEventHandler = (event) => {
|
|
28
41
|
if (event.key === ' ') {
|
|
@@ -50,13 +63,8 @@ const Switch = (props: SwitchProps) => {
|
|
|
50
63
|
);
|
|
51
64
|
};
|
|
52
65
|
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
logActionRequiredIf(
|
|
57
|
-
'Switch now expects either `aria-label` or `aria-labelledby`, and will soon make these props required. Please update your usage to provide one or the other.',
|
|
58
|
-
!ariaLabel && !ariaLabelledby,
|
|
59
|
-
);
|
|
66
|
+
const ariaLabelledby =
|
|
67
|
+
(ariaLabel ? undefined : ariaLabelledbyProp) ?? inputAttributes['aria-labelledby'];
|
|
60
68
|
|
|
61
69
|
return (
|
|
62
70
|
<span
|
|
@@ -66,7 +74,7 @@ const Switch = (props: SwitchProps) => {
|
|
|
66
74
|
{
|
|
67
75
|
'np-switch--unchecked': !checked,
|
|
68
76
|
'np-switch--checked': checked,
|
|
69
|
-
disabled
|
|
77
|
+
disabled,
|
|
70
78
|
},
|
|
71
79
|
className,
|
|
72
80
|
)}
|
|
@@ -74,6 +82,7 @@ const Switch = (props: SwitchProps) => {
|
|
|
74
82
|
role="switch"
|
|
75
83
|
aria-checked={checked}
|
|
76
84
|
aria-label={ariaLabel}
|
|
85
|
+
{...inputAttributes}
|
|
77
86
|
aria-labelledby={ariaLabelledby}
|
|
78
87
|
id={id}
|
|
79
88
|
aria-disabled={disabled}
|