@lumx/react 3.18.1 → 3.18.2-alpha.1
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/_internal/index.js +236 -0
- package/_internal/index.js.map +1 -0
- package/index.d.ts +13 -8
- package/index.js +219 -226
- package/index.js.map +1 -1
- package/package.json +3 -3
- package/src/components/autocomplete/Autocomplete.tsx +5 -4
- package/src/components/autocomplete/AutocompleteMultiple.tsx +5 -3
- package/src/components/button/Button.stories.tsx +1 -0
- package/src/components/button/Button.test.tsx +41 -2
- package/src/components/button/ButtonRoot.tsx +10 -11
- package/src/components/checkbox/Checkbox.stories.tsx +13 -2
- package/src/components/checkbox/Checkbox.test.tsx +29 -0
- package/src/components/checkbox/Checkbox.tsx +8 -7
- package/src/components/chip/Chip.stories.tsx +17 -0
- package/src/components/chip/Chip.test.tsx +44 -0
- package/src/components/chip/Chip.tsx +10 -9
- package/src/components/date-picker/DatePickerField.stories.tsx +18 -0
- package/src/components/date-picker/DatePickerField.tsx +4 -4
- package/src/components/link/Link.stories.tsx +4 -1
- package/src/components/link/Link.test.tsx +45 -6
- package/src/components/link/Link.tsx +7 -6
- package/src/components/list/ListItem.stories.tsx +14 -48
- package/src/components/list/ListItem.test.tsx +78 -7
- package/src/components/list/ListItem.tsx +11 -9
- package/src/components/progress-tracker/ProgressTrackerStep.tsx +7 -7
- package/src/components/radio-button/RadioButton.stories.tsx +32 -0
- package/src/components/radio-button/RadioButton.test.tsx +30 -0
- package/src/components/radio-button/RadioButton.tsx +8 -7
- package/src/components/slider/Slider.tsx +6 -7
- package/src/components/switch/Switch.stories.tsx +11 -1
- package/src/components/switch/Switch.test.tsx +30 -0
- package/src/components/switch/Switch.tsx +8 -7
- package/src/components/table/TableRow.tsx +8 -6
- package/src/components/tabs/Tab.tsx +12 -9
- package/src/components/text-field/TextField.stories.tsx +22 -0
- package/src/components/text-field/TextField.test.tsx +56 -0
- package/src/components/text-field/TextField.tsx +12 -10
- package/src/utils/disabled/DisabledStateContext.tsx +29 -0
- package/src/utils/disabled/DisabledStateProvider.stories.tsx +88 -0
- package/src/utils/disabled/index.ts +2 -0
- package/src/utils/disabled/useDisableStateProps.tsx +37 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/type/HasAriaDisabled.ts +6 -0
- package/utils/index.d.ts +20 -1
- package/utils/index.js +1 -134
- package/utils/index.js.map +1 -1
package/package.json
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
"url": "https://github.com/lumapps/design-system/issues"
|
|
7
7
|
},
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@lumx/core": "^3.18.1",
|
|
10
|
-
"@lumx/icons": "^3.18.1",
|
|
9
|
+
"@lumx/core": "^3.18.2-alpha.1",
|
|
10
|
+
"@lumx/icons": "^3.18.2-alpha.1",
|
|
11
11
|
"@popperjs/core": "^2.5.4",
|
|
12
12
|
"body-scroll-lock": "^3.1.5",
|
|
13
13
|
"classnames": "^2.3.2",
|
|
@@ -105,5 +105,5 @@
|
|
|
105
105
|
"build:storybook": "storybook build"
|
|
106
106
|
},
|
|
107
107
|
"sideEffects": false,
|
|
108
|
-
"version": "3.18.1"
|
|
108
|
+
"version": "3.18.2-alpha.1"
|
|
109
109
|
}
|
|
@@ -11,6 +11,8 @@ import { mergeRefs } from '@lumx/react/utils/react/mergeRefs';
|
|
|
11
11
|
import { useTheme } from '@lumx/react/utils/theme/ThemeContext';
|
|
12
12
|
import { forwardRef } from '@lumx/react/utils/react/forwardRef';
|
|
13
13
|
|
|
14
|
+
import { useDisableStateProps } from '@lumx/react/utils/disabled/useDisableStateProps';
|
|
15
|
+
|
|
14
16
|
/**
|
|
15
17
|
* Defines the props of the component.
|
|
16
18
|
*/
|
|
@@ -203,6 +205,7 @@ const DEFAULT_PROPS: Partial<AutocompleteProps> = {
|
|
|
203
205
|
*/
|
|
204
206
|
export const Autocomplete = forwardRef<AutocompleteProps, HTMLDivElement>((props, ref) => {
|
|
205
207
|
const defaultTheme = useTheme();
|
|
208
|
+
const { disabledStateProps, otherProps } = useDisableStateProps(props);
|
|
206
209
|
const {
|
|
207
210
|
anchorToInput = DEFAULT_PROPS.anchorToInput,
|
|
208
211
|
children,
|
|
@@ -211,7 +214,6 @@ export const Autocomplete = forwardRef<AutocompleteProps, HTMLDivElement>((props
|
|
|
211
214
|
closeOnClick = DEFAULT_PROPS.closeOnClick,
|
|
212
215
|
closeOnClickAway = DEFAULT_PROPS.closeOnClickAway,
|
|
213
216
|
closeOnEscape = DEFAULT_PROPS.closeOnEscape,
|
|
214
|
-
disabled,
|
|
215
217
|
error,
|
|
216
218
|
fitToAnchorWidth,
|
|
217
219
|
hasError,
|
|
@@ -219,7 +221,6 @@ export const Autocomplete = forwardRef<AutocompleteProps, HTMLDivElement>((props
|
|
|
219
221
|
icon,
|
|
220
222
|
inputRef,
|
|
221
223
|
clearButtonProps,
|
|
222
|
-
isDisabled = disabled,
|
|
223
224
|
isRequired,
|
|
224
225
|
isOpen,
|
|
225
226
|
isValid,
|
|
@@ -239,7 +240,7 @@ export const Autocomplete = forwardRef<AutocompleteProps, HTMLDivElement>((props
|
|
|
239
240
|
textFieldProps = {},
|
|
240
241
|
focusAnchorOnClose,
|
|
241
242
|
...forwardedProps
|
|
242
|
-
} =
|
|
243
|
+
} = otherProps;
|
|
243
244
|
const inputAnchorRef = useRef<HTMLElement>(null);
|
|
244
245
|
const textFieldRef = useRef(null);
|
|
245
246
|
useFocus(inputAnchorRef.current, !isOpen && shouldFocusOnClose);
|
|
@@ -255,7 +256,7 @@ export const Autocomplete = forwardRef<AutocompleteProps, HTMLDivElement>((props
|
|
|
255
256
|
icon={icon}
|
|
256
257
|
inputRef={mergeRefs(inputAnchorRef as React.RefObject<HTMLInputElement>, inputRef)}
|
|
257
258
|
clearButtonProps={clearButtonProps}
|
|
258
|
-
|
|
259
|
+
{...disabledStateProps}
|
|
259
260
|
isRequired={isRequired}
|
|
260
261
|
isValid={isValid}
|
|
261
262
|
label={label}
|
|
@@ -9,6 +9,8 @@ import { getRootClassName } from '@lumx/react/utils/className';
|
|
|
9
9
|
import { forwardRef } from '@lumx/react/utils/react/forwardRef';
|
|
10
10
|
import { useTheme } from '@lumx/react/utils/theme/ThemeContext';
|
|
11
11
|
|
|
12
|
+
import { useDisableStateProps } from '@lumx/react/utils/disabled/useDisableStateProps';
|
|
13
|
+
|
|
12
14
|
/**
|
|
13
15
|
* Defines the props of the component.
|
|
14
16
|
*/
|
|
@@ -69,6 +71,7 @@ const DEFAULT_PROPS: Partial<AutocompleteMultipleProps> = {
|
|
|
69
71
|
*/
|
|
70
72
|
export const AutocompleteMultiple = forwardRef<AutocompleteMultipleProps, HTMLDivElement>((props, ref) => {
|
|
71
73
|
const defaultTheme = useTheme();
|
|
74
|
+
const { disabledStateProps, otherProps } = useDisableStateProps(props);
|
|
72
75
|
const {
|
|
73
76
|
anchorToInput,
|
|
74
77
|
children,
|
|
@@ -84,7 +87,6 @@ export const AutocompleteMultiple = forwardRef<AutocompleteMultipleProps, HTMLDi
|
|
|
84
87
|
icon,
|
|
85
88
|
inputRef,
|
|
86
89
|
clearButtonProps,
|
|
87
|
-
isDisabled,
|
|
88
90
|
isRequired,
|
|
89
91
|
isOpen,
|
|
90
92
|
isValid,
|
|
@@ -107,7 +109,7 @@ export const AutocompleteMultiple = forwardRef<AutocompleteMultipleProps, HTMLDi
|
|
|
107
109
|
value,
|
|
108
110
|
values = DEFAULT_PROPS.values,
|
|
109
111
|
...forwardedProps
|
|
110
|
-
} =
|
|
112
|
+
} = otherProps;
|
|
111
113
|
|
|
112
114
|
return (
|
|
113
115
|
<Autocomplete
|
|
@@ -127,7 +129,7 @@ export const AutocompleteMultiple = forwardRef<AutocompleteMultipleProps, HTMLDi
|
|
|
127
129
|
icon={icon}
|
|
128
130
|
inputRef={inputRef}
|
|
129
131
|
chips={values && values.map((chip: any, index: number) => selectedChipRender?.(chip, index, onClear))}
|
|
130
|
-
|
|
132
|
+
{...disabledStateProps}
|
|
131
133
|
isRequired={isRequired}
|
|
132
134
|
clearButtonProps={clearButtonProps}
|
|
133
135
|
isValid={isValid}
|
|
@@ -3,6 +3,7 @@ import React from 'react';
|
|
|
3
3
|
import { mdiCheck, mdiPlus } from '@lumx/icons';
|
|
4
4
|
import { commonTestsSuiteRTL, SetupRenderOptions } from '@lumx/react/testing/utils';
|
|
5
5
|
import { render, screen, within } from '@testing-library/react';
|
|
6
|
+
import userEvent from '@testing-library/user-event';
|
|
6
7
|
import { getByClassName, queryAllByClassName, queryByClassName } from '@lumx/react/testing/utils/queries';
|
|
7
8
|
import { Emphasis, Icon } from '@lumx/react';
|
|
8
9
|
|
|
@@ -63,11 +64,49 @@ describe(`<${Button.displayName}>`, () => {
|
|
|
63
64
|
expect(buttonWrapper).toBeInTheDocument();
|
|
64
65
|
expect(button).toBe(within(buttonWrapper as any).queryByRole('button', { name: label }));
|
|
65
66
|
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe('Disabled state', () => {
|
|
70
|
+
it('should render disabled button', async () => {
|
|
71
|
+
const onClick = jest.fn();
|
|
72
|
+
const { button } = setup({ children: 'Label', disabled: true, onClick });
|
|
73
|
+
expect(button).toHaveAttribute('disabled');
|
|
74
|
+
await userEvent.click(button);
|
|
75
|
+
expect(onClick).not.toHaveBeenCalled();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should render disabled link', async () => {
|
|
79
|
+
const onClick = jest.fn();
|
|
80
|
+
const { button } = setup({ children: 'Label', disabled: true, href: 'https://example.com', onClick });
|
|
81
|
+
// Disabled link do not exist so we fallback to a button
|
|
82
|
+
expect(screen.queryByRole('link')).not.toBeInTheDocument();
|
|
83
|
+
expect(button).toHaveAttribute('disabled');
|
|
84
|
+
await userEvent.click(button);
|
|
85
|
+
expect(onClick).not.toHaveBeenCalled();
|
|
86
|
+
});
|
|
66
87
|
|
|
67
|
-
it('should
|
|
88
|
+
it('should render aria-disabled button', async () => {
|
|
68
89
|
const onClick = jest.fn();
|
|
69
90
|
const { button } = setup({ children: 'Label', 'aria-disabled': true, onClick });
|
|
70
|
-
expect(button
|
|
91
|
+
expect(button).toHaveAttribute('aria-disabled');
|
|
92
|
+
await userEvent.click(button);
|
|
93
|
+
expect(onClick).not.toHaveBeenCalled();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should render aria-disabled link', async () => {
|
|
97
|
+
const onClick = jest.fn();
|
|
98
|
+
const { button } = setup({
|
|
99
|
+
children: 'Label',
|
|
100
|
+
'aria-disabled': true,
|
|
101
|
+
href: 'https://example.com',
|
|
102
|
+
onClick,
|
|
103
|
+
});
|
|
104
|
+
expect(button).toHaveAccessibleName('Label');
|
|
105
|
+
// Disabled link do not exist so we fallback to a button
|
|
106
|
+
expect(screen.queryByRole('link')).not.toBeInTheDocument();
|
|
107
|
+
expect(button).toHaveAttribute('aria-disabled', 'true');
|
|
108
|
+
await userEvent.click(button);
|
|
109
|
+
expect(onClick).not.toHaveBeenCalled();
|
|
71
110
|
});
|
|
72
111
|
});
|
|
73
112
|
|
|
@@ -10,6 +10,8 @@ import { GenericProps, HasTheme } from '@lumx/react/utils/type';
|
|
|
10
10
|
import { handleBasicClasses } from '@lumx/react/utils/className';
|
|
11
11
|
import { renderLink } from '@lumx/react/utils/react/renderLink';
|
|
12
12
|
import { forwardRef } from '@lumx/react/utils/react/forwardRef';
|
|
13
|
+
import { useDisableStateProps } from '@lumx/react/utils/disabled/useDisableStateProps';
|
|
14
|
+
import { HasAriaDisabled } from '@lumx/react/utils/type/HasAriaDisabled';
|
|
13
15
|
|
|
14
16
|
type HTMLButtonProps = DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>;
|
|
15
17
|
|
|
@@ -20,8 +22,9 @@ export type ButtonSize = Extract<Size, 's' | 'm'>;
|
|
|
20
22
|
|
|
21
23
|
export interface BaseButtonProps
|
|
22
24
|
extends GenericProps,
|
|
23
|
-
Pick<AriaAttributes, 'aria-expanded' | 'aria-haspopup' | 'aria-pressed' | 'aria-label'
|
|
24
|
-
HasTheme
|
|
25
|
+
Pick<AriaAttributes, 'aria-expanded' | 'aria-haspopup' | 'aria-pressed' | 'aria-label'>,
|
|
26
|
+
HasTheme,
|
|
27
|
+
HasAriaDisabled {
|
|
25
28
|
/** Color variant. */
|
|
26
29
|
color?: ColorPalette;
|
|
27
30
|
/** Emphasis variant. */
|
|
@@ -96,17 +99,15 @@ const renderButtonWrapper: React.FC<ButtonRootProps> = (props) => {
|
|
|
96
99
|
* @return React element.
|
|
97
100
|
*/
|
|
98
101
|
export const ButtonRoot = forwardRef<ButtonRootProps, HTMLButtonElement | HTMLAnchorElement>((props, ref) => {
|
|
102
|
+
const { isAnyDisabled, disabledStateProps, otherProps } = useDisableStateProps(props);
|
|
99
103
|
const {
|
|
100
104
|
'aria-label': ariaLabel,
|
|
101
105
|
children,
|
|
102
106
|
className,
|
|
103
107
|
color,
|
|
104
|
-
disabled,
|
|
105
108
|
emphasis,
|
|
106
109
|
hasBackground,
|
|
107
110
|
href,
|
|
108
|
-
isDisabled = disabled,
|
|
109
|
-
'aria-disabled': ariaDisabled = isDisabled,
|
|
110
111
|
isSelected,
|
|
111
112
|
isActive,
|
|
112
113
|
isFocused,
|
|
@@ -120,7 +121,7 @@ export const ButtonRoot = forwardRef<ButtonRootProps, HTMLButtonElement | HTMLAn
|
|
|
120
121
|
type = 'button',
|
|
121
122
|
fullWidth,
|
|
122
123
|
...forwardedProps
|
|
123
|
-
} =
|
|
124
|
+
} = otherProps;
|
|
124
125
|
|
|
125
126
|
const adaptedColor =
|
|
126
127
|
color ||
|
|
@@ -138,7 +139,7 @@ export const ButtonRoot = forwardRef<ButtonRootProps, HTMLButtonElement | HTMLAn
|
|
|
138
139
|
color: adaptedColor,
|
|
139
140
|
emphasis,
|
|
140
141
|
isSelected,
|
|
141
|
-
isDisabled,
|
|
142
|
+
isDisabled: isAnyDisabled,
|
|
142
143
|
isActive,
|
|
143
144
|
isFocused,
|
|
144
145
|
isHovered,
|
|
@@ -156,7 +157,7 @@ export const ButtonRoot = forwardRef<ButtonRootProps, HTMLButtonElement | HTMLAn
|
|
|
156
157
|
*
|
|
157
158
|
* However, in any case, if the component is disabled, we returned a <button> since disabled is not compatible with <a>.
|
|
158
159
|
*/
|
|
159
|
-
if ((linkAs || !isEmpty(props.href)) && !
|
|
160
|
+
if ((linkAs || !isEmpty(props.href)) && !isAnyDisabled) {
|
|
160
161
|
return renderLink(
|
|
161
162
|
{
|
|
162
163
|
linkAs,
|
|
@@ -173,9 +174,7 @@ export const ButtonRoot = forwardRef<ButtonRootProps, HTMLButtonElement | HTMLAn
|
|
|
173
174
|
return (
|
|
174
175
|
<button
|
|
175
176
|
{...forwardedProps}
|
|
176
|
-
{...
|
|
177
|
-
disabled={isDisabled}
|
|
178
|
-
aria-disabled={ariaDisabled}
|
|
177
|
+
{...disabledStateProps}
|
|
179
178
|
aria-label={ariaLabel}
|
|
180
179
|
ref={ref as RefObject<HTMLButtonElement>}
|
|
181
180
|
className={buttonClassName}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Checkbox } from '@lumx/react';
|
|
2
2
|
import { withValueOnChange } from '@lumx/react/stories/decorators/withValueOnChange';
|
|
3
3
|
import { loremIpsum } from '@lumx/react/stories/utils/lorem';
|
|
4
|
+
import { withCombinations } from '@lumx/react/stories/decorators/withCombinations';
|
|
4
5
|
|
|
5
6
|
export default {
|
|
6
7
|
title: 'LumX components/checkbox/Checkbox',
|
|
@@ -47,7 +48,17 @@ export const IntermediateState = {
|
|
|
47
48
|
*/
|
|
48
49
|
export const Disabled = {
|
|
49
50
|
args: {
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
label: 'Checkbox label',
|
|
52
|
+
helper: 'Checkbox is disabled because...',
|
|
52
53
|
},
|
|
54
|
+
decorators: [
|
|
55
|
+
withCombinations({
|
|
56
|
+
combinations: {
|
|
57
|
+
rows: {
|
|
58
|
+
disabled: { disabled: true },
|
|
59
|
+
'aria-disabled': { 'aria-disabled': true },
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
}),
|
|
63
|
+
],
|
|
53
64
|
};
|
|
@@ -112,6 +112,35 @@ describe(`<${Checkbox.displayName}>`, () => {
|
|
|
112
112
|
});
|
|
113
113
|
});
|
|
114
114
|
|
|
115
|
+
describe('Disabled state', () => {
|
|
116
|
+
it('should be disabled with isDisabled', async () => {
|
|
117
|
+
const onChange = jest.fn();
|
|
118
|
+
const { checkbox, input } = setup({ isDisabled: true, onChange });
|
|
119
|
+
|
|
120
|
+
expect(checkbox).toHaveClass('lumx-checkbox--is-disabled');
|
|
121
|
+
expect(input).toBeDisabled();
|
|
122
|
+
|
|
123
|
+
// Should not trigger onChange.
|
|
124
|
+
await userEvent.click(input);
|
|
125
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should be disabled with aria-disabled', async () => {
|
|
129
|
+
const onChange = jest.fn();
|
|
130
|
+
const { checkbox, input } = setup({ 'aria-disabled': true, onChange });
|
|
131
|
+
|
|
132
|
+
expect(checkbox).toHaveClass('lumx-checkbox--is-disabled');
|
|
133
|
+
// Note: input is not disabled (so it can be focused) but it's readOnly.
|
|
134
|
+
expect(input).not.toBeDisabled();
|
|
135
|
+
expect(input).toHaveAttribute('aria-disabled', 'true');
|
|
136
|
+
expect(input).toHaveAttribute('readOnly');
|
|
137
|
+
|
|
138
|
+
// Should not trigger onChange.
|
|
139
|
+
await userEvent.click(input);
|
|
140
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
115
144
|
// Common tests suite.
|
|
116
145
|
commonTestsSuiteRTL(setup, {
|
|
117
146
|
baseClassName: CLASSNAME,
|
|
@@ -11,6 +11,8 @@ import { useId } from '@lumx/react/hooks/useId';
|
|
|
11
11
|
import { useMergeRefs } from '@lumx/react/utils/react/mergeRefs';
|
|
12
12
|
import { useTheme } from '@lumx/react/utils/theme/ThemeContext';
|
|
13
13
|
import { forwardRef } from '@lumx/react/utils/react/forwardRef';
|
|
14
|
+
import { useDisableStateProps } from '@lumx/react/utils/disabled/useDisableStateProps';
|
|
15
|
+
import { HasAriaDisabled } from '@lumx/react/utils/type/HasAriaDisabled';
|
|
14
16
|
|
|
15
17
|
/**
|
|
16
18
|
* Intermediate state of checkbox.
|
|
@@ -20,7 +22,7 @@ const INTERMEDIATE_STATE = 'intermediate';
|
|
|
20
22
|
/**
|
|
21
23
|
* Defines the props of the component.
|
|
22
24
|
*/
|
|
23
|
-
export interface CheckboxProps extends GenericProps, HasTheme {
|
|
25
|
+
export interface CheckboxProps extends GenericProps, HasTheme, HasAriaDisabled {
|
|
24
26
|
/** Helper text. */
|
|
25
27
|
helper?: string;
|
|
26
28
|
/** Native input id property. */
|
|
@@ -66,16 +68,15 @@ const DEFAULT_PROPS: Partial<CheckboxProps> = {};
|
|
|
66
68
|
* @return React element.
|
|
67
69
|
*/
|
|
68
70
|
export const Checkbox = forwardRef<CheckboxProps, HTMLDivElement>((props, ref) => {
|
|
71
|
+
const { isAnyDisabled, disabledStateProps, otherProps } = useDisableStateProps(props);
|
|
69
72
|
const defaultTheme = useTheme() || Theme.light;
|
|
70
73
|
const {
|
|
71
74
|
checked,
|
|
72
75
|
className,
|
|
73
|
-
disabled,
|
|
74
76
|
helper,
|
|
75
77
|
id,
|
|
76
78
|
inputRef,
|
|
77
79
|
isChecked = checked,
|
|
78
|
-
isDisabled = disabled,
|
|
79
80
|
label,
|
|
80
81
|
name,
|
|
81
82
|
onChange,
|
|
@@ -83,7 +84,7 @@ export const Checkbox = forwardRef<CheckboxProps, HTMLDivElement>((props, ref) =
|
|
|
83
84
|
value,
|
|
84
85
|
inputProps = {},
|
|
85
86
|
...forwardedProps
|
|
86
|
-
} =
|
|
87
|
+
} = otherProps;
|
|
87
88
|
const localInputRef = React.useRef<HTMLInputElement>(null);
|
|
88
89
|
const generatedInputId = useId();
|
|
89
90
|
const inputId = id || generatedInputId;
|
|
@@ -110,7 +111,7 @@ export const Checkbox = forwardRef<CheckboxProps, HTMLDivElement>((props, ref) =
|
|
|
110
111
|
handleBasicClasses({
|
|
111
112
|
// Whether state is intermediate class name will "-checked"
|
|
112
113
|
isChecked: intermediateState ? true : isChecked,
|
|
113
|
-
isDisabled,
|
|
114
|
+
isDisabled: isAnyDisabled,
|
|
114
115
|
isUnchecked: !isChecked,
|
|
115
116
|
prefix: CLASSNAME,
|
|
116
117
|
theme,
|
|
@@ -123,14 +124,14 @@ export const Checkbox = forwardRef<CheckboxProps, HTMLDivElement>((props, ref) =
|
|
|
123
124
|
type="checkbox"
|
|
124
125
|
id={inputId}
|
|
125
126
|
className={`${CLASSNAME}__input-native`}
|
|
126
|
-
|
|
127
|
-
tabIndex={isDisabled ? -1 : 0}
|
|
127
|
+
{...disabledStateProps}
|
|
128
128
|
name={name}
|
|
129
129
|
value={value}
|
|
130
130
|
checked={isChecked}
|
|
131
131
|
onChange={handleChange}
|
|
132
132
|
aria-describedby={helper ? `${inputId}-helper` : undefined}
|
|
133
133
|
aria-checked={intermediateState ? 'mixed' : Boolean(isChecked)}
|
|
134
|
+
readOnly={inputProps.readOnly || disabledStateProps['aria-disabled']}
|
|
134
135
|
{...inputProps}
|
|
135
136
|
/>
|
|
136
137
|
|
|
@@ -147,3 +147,20 @@ export const Theming = {
|
|
|
147
147
|
}),
|
|
148
148
|
],
|
|
149
149
|
};
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Test Chip disabled states
|
|
153
|
+
*/
|
|
154
|
+
export const Disabled = {
|
|
155
|
+
args: {
|
|
156
|
+
children: 'Chip label',
|
|
157
|
+
},
|
|
158
|
+
decorators: [
|
|
159
|
+
withCombinations({
|
|
160
|
+
combinations: {
|
|
161
|
+
rows: { disabled: { disabled: true }, 'aria-disabled': { 'aria-disabled': true } },
|
|
162
|
+
cols: { button: { onClick: () => {} }, link: { href: 'https://example.com' } },
|
|
163
|
+
},
|
|
164
|
+
}),
|
|
165
|
+
],
|
|
166
|
+
};
|
|
@@ -185,6 +185,50 @@ describe('<Chip />', () => {
|
|
|
185
185
|
});
|
|
186
186
|
});
|
|
187
187
|
|
|
188
|
+
describe('Disabled state', () => {
|
|
189
|
+
it('should render disabled chip button', async () => {
|
|
190
|
+
const onClick = jest.fn();
|
|
191
|
+
const { chip } = setup({ children: 'Label', isDisabled: true, onClick });
|
|
192
|
+
expect(chip).toHaveAttribute('aria-disabled', 'true');
|
|
193
|
+
await userEvent.click(chip);
|
|
194
|
+
expect(onClick).not.toHaveBeenCalled();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('should render disabled chip link', async () => {
|
|
198
|
+
const onClick = jest.fn();
|
|
199
|
+
const { chip } = setup({ children: 'Label', isDisabled: true, href: 'https://example.com', onClick });
|
|
200
|
+
// Disabled link should not have an href.
|
|
201
|
+
expect(chip).not.toHaveAttribute('href');
|
|
202
|
+
expect(chip).toHaveAttribute('aria-disabled', 'true');
|
|
203
|
+
await userEvent.click(chip);
|
|
204
|
+
expect(onClick).not.toHaveBeenCalled();
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('should render aria-disabled chip button', async () => {
|
|
208
|
+
const onClick = jest.fn();
|
|
209
|
+
const { chip } = setup({ children: 'Label', 'aria-disabled': true, onClick });
|
|
210
|
+
expect(chip).toHaveAttribute('aria-disabled', 'true');
|
|
211
|
+
await userEvent.click(chip);
|
|
212
|
+
// userEvent doesn't dispatch click on aria-disabled elements, but we check just in case.
|
|
213
|
+
expect(onClick).not.toHaveBeenCalled();
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('should render aria-disabled chip link', async () => {
|
|
217
|
+
const onClick = jest.fn();
|
|
218
|
+
const { chip } = setup({
|
|
219
|
+
children: 'Label',
|
|
220
|
+
'aria-disabled': true,
|
|
221
|
+
href: 'https://example.com',
|
|
222
|
+
onClick,
|
|
223
|
+
});
|
|
224
|
+
expect(chip).toHaveAttribute('href', 'https://example.com');
|
|
225
|
+
expect(chip).toHaveAttribute('aria-disabled', 'true');
|
|
226
|
+
await userEvent.click(chip);
|
|
227
|
+
// userEvent doesn't dispatch click on aria-disabled elements, but we check just in case.
|
|
228
|
+
expect(onClick).not.toHaveBeenCalled();
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
188
232
|
commonTestsSuiteRTL(setup, {
|
|
189
233
|
baseClassName: CLASSNAME,
|
|
190
234
|
forwardClassName: 'chip',
|
|
@@ -11,6 +11,8 @@ import { getRootClassName, handleBasicClasses } from '@lumx/react/utils/classNam
|
|
|
11
11
|
import { onEnterPressed } from '@lumx/react/utils/browser/event';
|
|
12
12
|
import { forwardRef } from '@lumx/react/utils/react/forwardRef';
|
|
13
13
|
import { useTheme } from '@lumx/react/utils/theme/ThemeContext';
|
|
14
|
+
import { useDisableStateProps } from '@lumx/react/utils/disabled/useDisableStateProps';
|
|
15
|
+
import { HasAriaDisabled } from '@lumx/react/utils/type/HasAriaDisabled';
|
|
14
16
|
|
|
15
17
|
/**
|
|
16
18
|
* Chip sizes.
|
|
@@ -20,7 +22,7 @@ type ChipSize = Extract<Size, 's' | 'm'>;
|
|
|
20
22
|
/**
|
|
21
23
|
* Defines the props of the component.
|
|
22
24
|
*/
|
|
23
|
-
export interface ChipProps extends GenericProps, HasTheme {
|
|
25
|
+
export interface ChipProps extends GenericProps, HasTheme, HasAriaDisabled {
|
|
24
26
|
/** A component to be rendered after the content. */
|
|
25
27
|
after?: ReactNode;
|
|
26
28
|
/** A component to be rendered before the content. */
|
|
@@ -71,15 +73,14 @@ const DEFAULT_PROPS: Partial<ChipProps> = {
|
|
|
71
73
|
*/
|
|
72
74
|
export const Chip = forwardRef<ChipProps, HTMLAnchorElement>((props, ref) => {
|
|
73
75
|
const defaultTheme = useTheme() || Theme.light;
|
|
76
|
+
const { isAnyDisabled, disabledStateProps, otherProps } = useDisableStateProps(props);
|
|
74
77
|
const {
|
|
75
78
|
after,
|
|
76
79
|
before,
|
|
77
80
|
children,
|
|
78
81
|
className,
|
|
79
82
|
color,
|
|
80
|
-
disabled,
|
|
81
83
|
isClickable: propIsClickable,
|
|
82
|
-
isDisabled = disabled,
|
|
83
84
|
isHighlighted,
|
|
84
85
|
isSelected,
|
|
85
86
|
onAfterClick,
|
|
@@ -90,10 +91,10 @@ export const Chip = forwardRef<ChipProps, HTMLAnchorElement>((props, ref) => {
|
|
|
90
91
|
href,
|
|
91
92
|
onKeyDown,
|
|
92
93
|
...forwardedProps
|
|
93
|
-
} =
|
|
94
|
+
} = otherProps;
|
|
94
95
|
const hasAfterClick = isFunction(onAfterClick);
|
|
95
96
|
const hasBeforeClick = isFunction(onBeforeClick);
|
|
96
|
-
const hasOnClick = isFunction(onClick);
|
|
97
|
+
const hasOnClick = isFunction(props.onClick);
|
|
97
98
|
const isButton = hasOnClick && !href;
|
|
98
99
|
const isClickable = Boolean(hasOnClick) || Boolean(href) || propIsClickable;
|
|
99
100
|
|
|
@@ -113,16 +114,16 @@ export const Chip = forwardRef<ChipProps, HTMLAnchorElement>((props, ref) => {
|
|
|
113
114
|
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
|
114
115
|
<a
|
|
115
116
|
role={isButton ? 'button' : undefined}
|
|
116
|
-
tabIndex={isClickable ? 0 : undefined}
|
|
117
|
+
tabIndex={isClickable && !disabledStateProps.disabled ? 0 : undefined}
|
|
117
118
|
{...forwardedProps}
|
|
118
|
-
href={href}
|
|
119
|
+
href={!disabledStateProps.disabled ? href : undefined}
|
|
119
120
|
ref={ref}
|
|
120
121
|
className={classNames(
|
|
121
122
|
className,
|
|
122
123
|
handleBasicClasses({
|
|
123
124
|
clickable: isClickable,
|
|
124
125
|
color: chipColor,
|
|
125
|
-
isDisabled,
|
|
126
|
+
isDisabled: isAnyDisabled,
|
|
126
127
|
hasAfter: Boolean(after),
|
|
127
128
|
hasBefore: Boolean(before),
|
|
128
129
|
highlighted: Boolean(isHighlighted),
|
|
@@ -132,7 +133,7 @@ export const Chip = forwardRef<ChipProps, HTMLAnchorElement>((props, ref) => {
|
|
|
132
133
|
unselected: Boolean(!isSelected),
|
|
133
134
|
}),
|
|
134
135
|
)}
|
|
135
|
-
aria-disabled={(isClickable &&
|
|
136
|
+
aria-disabled={(isClickable && isAnyDisabled) || undefined}
|
|
136
137
|
onClick={hasOnClick ? onClick : undefined}
|
|
137
138
|
onKeyDown={handleKeyDown}
|
|
138
139
|
>
|
|
@@ -2,6 +2,7 @@ import { DatePickerField } from '@lumx/react';
|
|
|
2
2
|
import { withValueOnChange } from '@lumx/react/stories/decorators/withValueOnChange';
|
|
3
3
|
import { withNestedProps } from '@lumx/react/stories/decorators/withNestedProps';
|
|
4
4
|
import { loremIpsum } from '@lumx/react/stories/utils/lorem';
|
|
5
|
+
import { withCombinations } from '@lumx/react/stories/decorators/withCombinations';
|
|
5
6
|
|
|
6
7
|
export default {
|
|
7
8
|
title: 'LumX components/date-picker/DatePickerField',
|
|
@@ -80,3 +81,20 @@ export const DefaultMonth = {
|
|
|
80
81
|
defaultMonth: new Date('2019-07-14'),
|
|
81
82
|
},
|
|
82
83
|
};
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Disabled states
|
|
87
|
+
*/
|
|
88
|
+
export const Disabled = {
|
|
89
|
+
args: DefaultValue.args,
|
|
90
|
+
decorators: [
|
|
91
|
+
withCombinations({
|
|
92
|
+
combinations: {
|
|
93
|
+
rows: {
|
|
94
|
+
disabled: { disabled: true },
|
|
95
|
+
'aria-disabled': { 'aria-disabled': true },
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
}),
|
|
99
|
+
],
|
|
100
|
+
};
|
|
@@ -5,6 +5,7 @@ import { GenericProps } from '@lumx/react/utils/type';
|
|
|
5
5
|
import { getCurrentLocale } from '@lumx/react/utils/locale/getCurrentLocale';
|
|
6
6
|
import { useBooleanState } from '@lumx/react/hooks/useBooleanState';
|
|
7
7
|
import { forwardRef } from '@lumx/react/utils/react/forwardRef';
|
|
8
|
+
import { useDisableStateProps } from '@lumx/react/utils/disabled/useDisableStateProps';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Defines the props of the component.
|
|
@@ -42,10 +43,9 @@ const COMPONENT_NAME = 'DatePickerField';
|
|
|
42
43
|
* @return React element.
|
|
43
44
|
*/
|
|
44
45
|
export const DatePickerField = forwardRef<DatePickerFieldProps, HTMLDivElement>((props, ref) => {
|
|
46
|
+
const { disabledStateProps, otherProps } = useDisableStateProps(props);
|
|
45
47
|
const {
|
|
46
48
|
defaultMonth,
|
|
47
|
-
disabled,
|
|
48
|
-
isDisabled = disabled,
|
|
49
49
|
locale = getCurrentLocale(),
|
|
50
50
|
maxDate,
|
|
51
51
|
minDate,
|
|
@@ -55,7 +55,7 @@ export const DatePickerField = forwardRef<DatePickerFieldProps, HTMLDivElement>(
|
|
|
55
55
|
previousButtonProps,
|
|
56
56
|
value,
|
|
57
57
|
...forwardedProps
|
|
58
|
-
} =
|
|
58
|
+
} = otherProps;
|
|
59
59
|
const anchorRef = useRef(null);
|
|
60
60
|
|
|
61
61
|
const [isOpen, close, , toggleOpen] = useBooleanState(false);
|
|
@@ -101,7 +101,7 @@ export const DatePickerField = forwardRef<DatePickerFieldProps, HTMLDivElement>(
|
|
|
101
101
|
onClick={toggleOpen}
|
|
102
102
|
onChange={onTextFieldChange}
|
|
103
103
|
onKeyPress={handleKeyboardNav}
|
|
104
|
-
|
|
104
|
+
{...disabledStateProps}
|
|
105
105
|
readOnly
|
|
106
106
|
aria-haspopup="dialog"
|
|
107
107
|
/>
|
|
@@ -76,13 +76,15 @@ export const WithCustomizableTypography = {
|
|
|
76
76
|
export const AllStates = {
|
|
77
77
|
argTypes: {
|
|
78
78
|
isDisabled: { control: false },
|
|
79
|
+
onClick: { action: true },
|
|
79
80
|
},
|
|
80
81
|
decorators: [
|
|
81
82
|
withThemedBackground(),
|
|
82
83
|
withCombinations({
|
|
83
84
|
combinations: {
|
|
84
85
|
sections: {
|
|
85
|
-
Default: {},
|
|
86
|
+
Default: { href: '#' },
|
|
87
|
+
'As button': {},
|
|
86
88
|
'with icon': {
|
|
87
89
|
children: ['Link', <Icon key="icon" icon={mdiEarth} />, 'with icon'],
|
|
88
90
|
},
|
|
@@ -91,6 +93,7 @@ export const AllStates = {
|
|
|
91
93
|
Default: {},
|
|
92
94
|
Disabled: { isDisabled: true },
|
|
93
95
|
Focused: { 'data-focus-visible-added': true },
|
|
96
|
+
'ARIA Disabled': { 'aria-disabled': true },
|
|
94
97
|
Hovered: { 'data-lumx-hover': true },
|
|
95
98
|
},
|
|
96
99
|
rows: {
|