@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.
Files changed (47) hide show
  1. package/_internal/index.js +236 -0
  2. package/_internal/index.js.map +1 -0
  3. package/index.d.ts +13 -8
  4. package/index.js +219 -226
  5. package/index.js.map +1 -1
  6. package/package.json +3 -3
  7. package/src/components/autocomplete/Autocomplete.tsx +5 -4
  8. package/src/components/autocomplete/AutocompleteMultiple.tsx +5 -3
  9. package/src/components/button/Button.stories.tsx +1 -0
  10. package/src/components/button/Button.test.tsx +41 -2
  11. package/src/components/button/ButtonRoot.tsx +10 -11
  12. package/src/components/checkbox/Checkbox.stories.tsx +13 -2
  13. package/src/components/checkbox/Checkbox.test.tsx +29 -0
  14. package/src/components/checkbox/Checkbox.tsx +8 -7
  15. package/src/components/chip/Chip.stories.tsx +17 -0
  16. package/src/components/chip/Chip.test.tsx +44 -0
  17. package/src/components/chip/Chip.tsx +10 -9
  18. package/src/components/date-picker/DatePickerField.stories.tsx +18 -0
  19. package/src/components/date-picker/DatePickerField.tsx +4 -4
  20. package/src/components/link/Link.stories.tsx +4 -1
  21. package/src/components/link/Link.test.tsx +45 -6
  22. package/src/components/link/Link.tsx +7 -6
  23. package/src/components/list/ListItem.stories.tsx +14 -48
  24. package/src/components/list/ListItem.test.tsx +78 -7
  25. package/src/components/list/ListItem.tsx +11 -9
  26. package/src/components/progress-tracker/ProgressTrackerStep.tsx +7 -7
  27. package/src/components/radio-button/RadioButton.stories.tsx +32 -0
  28. package/src/components/radio-button/RadioButton.test.tsx +30 -0
  29. package/src/components/radio-button/RadioButton.tsx +8 -7
  30. package/src/components/slider/Slider.tsx +6 -7
  31. package/src/components/switch/Switch.stories.tsx +11 -1
  32. package/src/components/switch/Switch.test.tsx +30 -0
  33. package/src/components/switch/Switch.tsx +8 -7
  34. package/src/components/table/TableRow.tsx +8 -6
  35. package/src/components/tabs/Tab.tsx +12 -9
  36. package/src/components/text-field/TextField.stories.tsx +22 -0
  37. package/src/components/text-field/TextField.test.tsx +56 -0
  38. package/src/components/text-field/TextField.tsx +12 -10
  39. package/src/utils/disabled/DisabledStateContext.tsx +29 -0
  40. package/src/utils/disabled/DisabledStateProvider.stories.tsx +88 -0
  41. package/src/utils/disabled/index.ts +2 -0
  42. package/src/utils/disabled/useDisableStateProps.tsx +37 -0
  43. package/src/utils/index.ts +1 -0
  44. package/src/utils/type/HasAriaDisabled.ts +6 -0
  45. package/utils/index.d.ts +20 -1
  46. package/utils/index.js +1 -134
  47. package/utils/index.js.map +1 -1
@@ -1,6 +1,7 @@
1
1
  import { Alignment, Switch, SwitchProps } from '@lumx/react';
2
2
  import { withValueOnChange } from '@lumx/react/stories/decorators/withValueOnChange';
3
3
  import { getSelectArgType } from '@lumx/react/stories/controls/selectArgType';
4
+ import { withCombinations } from '@lumx/react/stories/decorators/withCombinations';
4
5
 
5
6
  export default {
6
7
  title: 'LumX components/switch/Switch',
@@ -28,7 +29,16 @@ export const Default = {};
28
29
  * Switch disabled
29
30
  */
30
31
  export const Disabled = {
31
- args: { isDisabled: true },
32
+ decorators: [
33
+ withCombinations({
34
+ combinations: {
35
+ rows: {
36
+ disabled: { isDisabled: true },
37
+ 'aria-disabled': { 'aria-disabled': true },
38
+ },
39
+ },
40
+ }),
41
+ ],
32
42
  };
33
43
 
34
44
  /**
@@ -100,6 +100,36 @@ describe(`<${Switch.displayName}>`, () => {
100
100
  });
101
101
  });
102
102
 
103
+ describe('Disabled state', () => {
104
+ it('should be disabled with isDisabled', async () => {
105
+ const onChange = jest.fn();
106
+ const { switchWrapper, input } = setup({ isDisabled: true, onChange });
107
+
108
+ expect(switchWrapper).toHaveClass('lumx-switch--is-disabled');
109
+ expect(input).toBeDisabled();
110
+ expect(input).toHaveAttribute('readOnly');
111
+
112
+ // Should not trigger onChange.
113
+ await userEvent.click(input);
114
+ expect(onChange).not.toHaveBeenCalled();
115
+ });
116
+
117
+ it('should be disabled with aria-disabled', async () => {
118
+ const onChange = jest.fn();
119
+ const { switchWrapper, input } = setup({ 'aria-disabled': true, onChange });
120
+
121
+ expect(switchWrapper).toHaveClass('lumx-switch--is-disabled');
122
+ // Note: input is not disabled (so it can be focused) but it's readOnly.
123
+ expect(input).not.toBeDisabled();
124
+ expect(input).toHaveAttribute('aria-disabled', 'true');
125
+ expect(input).toHaveAttribute('readOnly');
126
+
127
+ // Should not trigger onChange.
128
+ await userEvent.click(input);
129
+ expect(onChange).not.toHaveBeenCalled();
130
+ });
131
+ });
132
+
103
133
  // Common tests suite.
104
134
  commonTestsSuiteRTL(setup, {
105
135
  baseClassName: CLASSNAME,
@@ -9,11 +9,13 @@ import { getRootClassName, handleBasicClasses } from '@lumx/react/utils/classNam
9
9
  import { useId } from '@lumx/react/hooks/useId';
10
10
  import { useTheme } from '@lumx/react/utils/theme/ThemeContext';
11
11
  import { forwardRef } from '@lumx/react/utils/react/forwardRef';
12
+ import { useDisableStateProps } from '@lumx/react/utils/disabled/useDisableStateProps';
13
+ import { HasAriaDisabled } from '@lumx/react/utils/type/HasAriaDisabled';
12
14
 
13
15
  /**
14
16
  * Defines the props of the component.
15
17
  */
16
- export interface SwitchProps extends GenericProps, HasTheme {
18
+ export interface SwitchProps extends GenericProps, HasTheme, HasAriaDisabled {
17
19
  /** Helper text. */
18
20
  helper?: string;
19
21
  /** Whether it is checked or not. */
@@ -59,16 +61,15 @@ const DEFAULT_PROPS: Partial<SwitchProps> = {
59
61
  * @return React element.
60
62
  */
61
63
  export const Switch = forwardRef<SwitchProps, HTMLDivElement>((props, ref) => {
64
+ const { isAnyDisabled, disabledStateProps, otherProps } = useDisableStateProps(props);
62
65
  const defaultTheme = useTheme() || Theme.light;
63
66
  const {
64
67
  checked,
65
68
  children,
66
69
  className,
67
- disabled,
68
70
  helper,
69
71
  id,
70
72
  isChecked = checked,
71
- isDisabled = disabled,
72
73
  name,
73
74
  onChange,
74
75
  position = DEFAULT_PROPS.position,
@@ -76,7 +77,7 @@ export const Switch = forwardRef<SwitchProps, HTMLDivElement>((props, ref) => {
76
77
  value,
77
78
  inputProps = {},
78
79
  ...forwardedProps
79
- } = props;
80
+ } = otherProps;
80
81
  const generatedInputId = useId();
81
82
  const inputId = id || generatedInputId;
82
83
  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
@@ -94,13 +95,12 @@ export const Switch = forwardRef<SwitchProps, HTMLDivElement>((props, ref) => {
94
95
  handleBasicClasses({
95
96
  prefix: CLASSNAME,
96
97
  isChecked,
97
- isDisabled,
98
+ isDisabled: isAnyDisabled,
98
99
  position,
99
100
  theme,
100
101
  isUnchecked: !isChecked,
101
102
  }),
102
103
  )}
103
- aria-disabled={isDisabled}
104
104
  >
105
105
  <div className={`${CLASSNAME}__input-wrapper`}>
106
106
  <input
@@ -110,7 +110,8 @@ export const Switch = forwardRef<SwitchProps, HTMLDivElement>((props, ref) => {
110
110
  className={`${CLASSNAME}__input-native`}
111
111
  name={name}
112
112
  value={value}
113
- disabled={isDisabled}
113
+ {...disabledStateProps}
114
+ readOnly={inputProps.readOnly || isAnyDisabled}
114
115
  checked={isChecked}
115
116
  aria-checked={Boolean(isChecked)}
116
117
  onChange={handleChange}
@@ -5,6 +5,7 @@ import classNames from 'classnames';
5
5
  import { GenericProps } from '@lumx/react/utils/type';
6
6
  import { getRootClassName, handleBasicClasses } from '@lumx/react/utils/className';
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.
@@ -43,23 +44,24 @@ const DEFAULT_PROPS: Partial<TableRowProps> = {};
43
44
  * @return React element.
44
45
  */
45
46
  export const TableRow = forwardRef<TableRowProps, HTMLTableRowElement>((props, ref) => {
46
- const { children, className, disabled, isClickable, isDisabled = disabled, isSelected, ...forwardedProps } = props;
47
+ const { isAnyDisabled, disabledStateProps, otherProps } = useDisableStateProps(props);
48
+ const { children, className, isClickable, isSelected, ...forwardedProps } = otherProps;
47
49
 
48
50
  return (
49
51
  <tr
50
52
  ref={ref}
51
- tabIndex={isClickable && !isDisabled ? 0 : -1}
53
+ tabIndex={isClickable && !disabledStateProps.disabled ? 0 : -1}
52
54
  {...forwardedProps}
53
55
  className={classNames(
54
56
  className,
55
57
  handleBasicClasses({
56
- isClickable: isClickable && !isDisabled,
57
- isDisabled,
58
- isSelected: isSelected && !isDisabled,
58
+ isClickable: isClickable && !isAnyDisabled,
59
+ isDisabled: isAnyDisabled,
60
+ isSelected: isSelected && !isAnyDisabled,
59
61
  prefix: CLASSNAME,
60
62
  }),
61
63
  )}
62
- aria-disabled={isDisabled}
64
+ aria-disabled={isAnyDisabled}
63
65
  >
64
66
  {children}
65
67
  </tr>
@@ -8,6 +8,7 @@ import { GenericProps } from '@lumx/react/utils/type';
8
8
  import { handleBasicClasses } from '@lumx/react/utils/className';
9
9
  import { forwardRef } from '@lumx/react/utils/react/forwardRef';
10
10
 
11
+ import { useDisableStateProps } from '@lumx/react/utils/disabled/useDisableStateProps';
11
12
  import { useTabProviderContext } from './state';
12
13
 
13
14
  /**
@@ -55,29 +56,28 @@ const DEFAULT_PROPS: Partial<TabProps> = {};
55
56
  * @return React element.
56
57
  */
57
58
  export const Tab = forwardRef<TabProps, HTMLButtonElement>((props, ref) => {
59
+ const { isAnyDisabled, otherProps } = useDisableStateProps(props);
58
60
  const {
59
61
  className,
60
- disabled,
61
62
  icon,
62
63
  iconProps = {},
63
64
  id,
64
65
  isActive: propIsActive,
65
- isDisabled = disabled,
66
66
  label,
67
67
  onFocus,
68
68
  onKeyPress,
69
69
  tabIndex = -1,
70
70
  ...forwardedProps
71
- } = props;
71
+ } = otherProps;
72
72
  const state = useTabProviderContext('tab', id);
73
73
  const isActive = propIsActive || state?.isActive;
74
74
 
75
75
  const changeToCurrentTab = useCallback(() => {
76
- if (isDisabled) {
76
+ if (isAnyDisabled) {
77
77
  return;
78
78
  }
79
79
  state?.changeToTab();
80
- }, [isDisabled, state]);
80
+ }, [isAnyDisabled, state]);
81
81
 
82
82
  const handleFocus: FocusEventHandler = useCallback(
83
83
  (event) => {
@@ -92,12 +92,12 @@ export const Tab = forwardRef<TabProps, HTMLButtonElement>((props, ref) => {
92
92
  const handleKeyPress: KeyboardEventHandler = useCallback(
93
93
  (event) => {
94
94
  onKeyPress?.(event);
95
- if (event.key !== 'Enter') {
95
+ if (event.key !== 'Enter' || isAnyDisabled) {
96
96
  return;
97
97
  }
98
98
  changeToCurrentTab();
99
99
  },
100
- [changeToCurrentTab, onKeyPress],
100
+ [changeToCurrentTab, isAnyDisabled, onKeyPress],
101
101
  );
102
102
 
103
103
  return (
@@ -106,13 +106,16 @@ export const Tab = forwardRef<TabProps, HTMLButtonElement>((props, ref) => {
106
106
  {...forwardedProps}
107
107
  type="button"
108
108
  id={state?.tabId}
109
- className={classNames(className, handleBasicClasses({ prefix: CLASSNAME, isActive, isDisabled }))}
109
+ className={classNames(
110
+ className,
111
+ handleBasicClasses({ prefix: CLASSNAME, isActive, isDisabled: isAnyDisabled }),
112
+ )}
110
113
  onClick={changeToCurrentTab}
111
114
  onKeyPress={handleKeyPress}
112
115
  onFocus={handleFocus}
113
116
  role="tab"
114
117
  tabIndex={isActive ? 0 : tabIndex}
115
- aria-disabled={isDisabled}
118
+ aria-disabled={isAnyDisabled}
116
119
  aria-selected={isActive}
117
120
  aria-controls={state?.tabPanelId}
118
121
  >
@@ -3,6 +3,7 @@ import { mdiTranslate } from '@lumx/icons';
3
3
  import { Chip, IconButton, TextField, Typography } from '@lumx/react';
4
4
  import { withValueOnChange } from '@lumx/react/stories/decorators/withValueOnChange';
5
5
  import { loremIpsum } from '@lumx/react/stories/utils/lorem';
6
+ import { withCombinations } from '@lumx/react/stories/decorators/withCombinations';
6
7
 
7
8
  export default {
8
9
  title: 'LumX components/text-field/TextField',
@@ -156,3 +157,24 @@ export const WithChips = {
156
157
  ),
157
158
  },
158
159
  };
160
+
161
+ /**
162
+ * Disabled state
163
+ */
164
+ export const Disabled = {
165
+ args: {
166
+ value: 'Some value',
167
+ label: 'Label',
168
+ helper: 'Helper',
169
+ },
170
+ decorators: [
171
+ withCombinations({
172
+ combinations: {
173
+ rows: {
174
+ disabled: { disabled: true },
175
+ 'aria-disabled': { 'aria-disabled': true },
176
+ },
177
+ },
178
+ }),
179
+ ],
180
+ };
@@ -227,6 +227,62 @@ describe(`<${TextField.displayName}>`, () => {
227
227
  });
228
228
  });
229
229
 
230
+ describe('Disabled state', () => {
231
+ it('should render with "isDisabled"', async () => {
232
+ const onChange = jest.fn();
233
+ const { element, inputNative } = setup({
234
+ label: 'Label',
235
+ isDisabled: true,
236
+ value: 'test',
237
+ onChange,
238
+ });
239
+
240
+ expect(element).toHaveClass('lumx-text-field--is-disabled');
241
+ expect(inputNative).toBeDisabled();
242
+
243
+ // Cannot type in disabled input.
244
+ await userEvent.type(inputNative, 'new value');
245
+ expect(onChange).not.toHaveBeenCalled();
246
+ });
247
+
248
+ it('should not render clear button when disabled', () => {
249
+ const { clearButton } = setup({
250
+ value: 'initial value',
251
+ clearButtonProps: { label: 'Clear' },
252
+ isDisabled: true,
253
+ });
254
+ expect(clearButton).not.toBeInTheDocument();
255
+ });
256
+
257
+ it('should render with "aria-disabled"', async () => {
258
+ const onChange = jest.fn();
259
+ const { element, inputNative } = setup({
260
+ label: 'Label',
261
+ 'aria-disabled': true,
262
+ value: 'test',
263
+ onChange,
264
+ });
265
+
266
+ expect(element).toHaveClass('lumx-text-field--is-disabled');
267
+ expect(inputNative).not.toBeDisabled();
268
+ expect(inputNative).toHaveAttribute('aria-disabled', 'true');
269
+ expect(inputNative).toHaveAttribute('readonly');
270
+
271
+ // Cannot type in readonly input.
272
+ await userEvent.type(inputNative, 'new value');
273
+ expect(onChange).not.toHaveBeenCalled();
274
+ });
275
+
276
+ it('should not render clear button when aria-disabled', () => {
277
+ const { clearButton } = setup({
278
+ value: 'initial value',
279
+ clearButtonProps: { label: 'Clear' },
280
+ 'aria-disabled': true,
281
+ });
282
+ expect(clearButton).not.toBeInTheDocument();
283
+ });
284
+ });
285
+
230
286
  // Common tests suite.
231
287
  commonTestsSuiteRTL(setup, {
232
288
  baseClassName: CLASSNAME,
@@ -22,11 +22,13 @@ import { mergeRefs } from '@lumx/react/utils/react/mergeRefs';
22
22
  import { useId } from '@lumx/react/hooks/useId';
23
23
  import { useTheme } from '@lumx/react/utils/theme/ThemeContext';
24
24
  import { forwardRef } from '@lumx/react/utils/react/forwardRef';
25
+ import { useDisableStateProps } from '@lumx/react/utils/disabled/useDisableStateProps';
26
+ import { HasAriaDisabled } from '@lumx/react/utils/type/HasAriaDisabled';
25
27
 
26
28
  /**
27
29
  * Defines the props of the component.
28
30
  */
29
- export interface TextFieldProps extends GenericProps, HasTheme {
31
+ export interface TextFieldProps extends GenericProps, HasTheme, HasAriaDisabled {
30
32
  /** Chip Group to be rendered before the main text input. */
31
33
  chips?: ReactNode;
32
34
  /** Props to pass to the clear button (minus those already set by the TextField props). If not specified, the button won't be displayed. */
@@ -158,7 +160,9 @@ interface InputNativeProps {
158
160
  id?: string;
159
161
  inputRef?: TextFieldProps['inputRef'];
160
162
  isDisabled?: boolean;
163
+ 'aria-disabled'?: boolean;
161
164
  isRequired?: boolean;
165
+ readOnly?: boolean;
162
166
  multiline?: boolean;
163
167
  maxLength?: number;
164
168
  placeholder?: string;
@@ -178,7 +182,6 @@ interface InputNativeProps {
178
182
  const renderInputNative: React.FC<InputNativeProps> = (props) => {
179
183
  const {
180
184
  id,
181
- isDisabled,
182
185
  isRequired,
183
186
  placeholder,
184
187
  multiline,
@@ -231,13 +234,13 @@ const renderInputNative: React.FC<InputNativeProps> = (props) => {
231
234
  placeholder,
232
235
  value,
233
236
  name,
234
- disabled: isDisabled,
235
237
  required: isRequired,
236
238
  onFocus: onTextFieldFocus,
237
239
  onBlur: onTextFieldBlur,
238
240
  onChange: handleChange,
239
241
  'aria-invalid': hasError ? 'true' : undefined,
240
242
  'aria-describedby': describedById,
243
+ readOnly: forwardedProps.readOnly || forwardedProps['aria-disabled'],
241
244
  ref: mergeRefs(inputRef as any, ref) as any,
242
245
  };
243
246
  if (multiline) {
@@ -256,12 +259,12 @@ const renderInputNative: React.FC<InputNativeProps> = (props) => {
256
259
  * @return React element.
257
260
  */
258
261
  export const TextField = forwardRef<TextFieldProps, HTMLDivElement>((props, ref) => {
262
+ const { isAnyDisabled, disabledStateProps, otherProps } = useDisableStateProps(props);
259
263
  const defaultTheme = useTheme() || Theme.light;
260
264
  const {
261
265
  chips,
262
266
  className,
263
267
  clearButtonProps,
264
- disabled,
265
268
  error,
266
269
  forceFocusStyle,
267
270
  hasError,
@@ -269,7 +272,6 @@ export const TextField = forwardRef<TextFieldProps, HTMLDivElement>((props, ref)
269
272
  icon,
270
273
  id,
271
274
  inputRef: inputRefProps,
272
- isDisabled = disabled,
273
275
  isRequired,
274
276
  isValid,
275
277
  label,
@@ -289,7 +291,7 @@ export const TextField = forwardRef<TextFieldProps, HTMLDivElement>((props, ref)
289
291
  value,
290
292
  afterElement,
291
293
  ...forwardedProps
292
- } = props;
294
+ } = otherProps;
293
295
  const generatedTextFieldId = useId();
294
296
  const textFieldId = id || generatedTextFieldId;
295
297
  /** Keep a clean local input ref to manage focus */
@@ -352,7 +354,7 @@ export const TextField = forwardRef<TextFieldProps, HTMLDivElement>((props, ref)
352
354
  hasPlaceholder: Boolean(placeholder),
353
355
  hasTextarea: multiline,
354
356
  hasValue: Boolean(value),
355
- isDisabled,
357
+ isDisabled: isAnyDisabled,
356
358
  isFocus: isFocus || forceFocusStyle,
357
359
  isValid,
358
360
  prefix: CLASSNAME,
@@ -400,7 +402,7 @@ export const TextField = forwardRef<TextFieldProps, HTMLDivElement>((props, ref)
400
402
  {renderInputNative({
401
403
  id: textFieldId,
402
404
  inputRef,
403
- isDisabled,
405
+ ...disabledStateProps,
404
406
  isRequired,
405
407
  maxLength,
406
408
  multiline,
@@ -426,7 +428,7 @@ export const TextField = forwardRef<TextFieldProps, HTMLDivElement>((props, ref)
426
428
  {renderInputNative({
427
429
  id: textFieldId,
428
430
  inputRef,
429
- isDisabled,
431
+ ...disabledStateProps,
430
432
  isRequired,
431
433
  maxLength,
432
434
  multiline,
@@ -456,7 +458,7 @@ export const TextField = forwardRef<TextFieldProps, HTMLDivElement>((props, ref)
456
458
  />
457
459
  )}
458
460
 
459
- {clearButtonProps && isNotEmpty && (
461
+ {clearButtonProps && isNotEmpty && !isAnyDisabled && (
460
462
  <IconButton
461
463
  {...clearButtonProps}
462
464
  className={`${CLASSNAME}__input-clear`}
@@ -0,0 +1,29 @@
1
+ import React, { useContext } from 'react';
2
+
3
+ /** Disable state */
4
+ type DisabledStateContextValue =
5
+ | {
6
+ state: 'disabled';
7
+ }
8
+ | { state: undefined | null };
9
+
10
+ export const DisabledStateContext = React.createContext<DisabledStateContextValue>({ state: null });
11
+
12
+ export type DisabledStateProviderProps = DisabledStateContextValue & {
13
+ children: React.ReactNode;
14
+ };
15
+
16
+ /**
17
+ * Disabled state provider.
18
+ * All nested LumX Design System components inherit this disabled state.
19
+ */
20
+ export function DisabledStateProvider({ children, ...value }: DisabledStateProviderProps) {
21
+ return <DisabledStateContext.Provider value={value}>{children}</DisabledStateContext.Provider>;
22
+ }
23
+
24
+ /**
25
+ * Get DisabledState context value
26
+ */
27
+ export function useDisabledStateContext(): DisabledStateContextValue {
28
+ return useContext(DisabledStateContext);
29
+ }
@@ -0,0 +1,88 @@
1
+ import React from 'react';
2
+
3
+ import {
4
+ Button,
5
+ Checkbox,
6
+ Chip,
7
+ DatePickerField,
8
+ IconButton,
9
+ Link,
10
+ List,
11
+ ListItem,
12
+ RadioButton,
13
+ Switch,
14
+ TextField,
15
+ } from '@lumx/react';
16
+ import { DisabledStateProvider } from '@lumx/react/utils';
17
+ import { getSelectArgType } from '@lumx/react/stories/controls/selectArgType';
18
+ import { disableArgTypes } from '@lumx/react/stories/utils/disableArgTypes';
19
+ import { mdiFoodApple } from '@lumx/icons';
20
+
21
+ export default {
22
+ title: 'LumX components/DisabledStateProvider',
23
+ component: DisabledStateProvider,
24
+ argTypes: {
25
+ state: getSelectArgType(['disabled', undefined]),
26
+ ...disableArgTypes(['children']),
27
+ },
28
+ };
29
+
30
+ /**
31
+ * Disabling nested children
32
+ */
33
+ export const Disabled = {
34
+ args: {
35
+ state: 'disabled',
36
+ },
37
+ render: ({ state }: any) => (
38
+ <DisabledStateProvider state={state}>
39
+ <Button>Button</Button>
40
+ <Button isDisabled>Disabled Button</Button>
41
+ </DisabledStateProvider>
42
+ ),
43
+ };
44
+
45
+ /**
46
+ * Testing when the context is not active
47
+ */
48
+ export const NotDisabled = {
49
+ args: {
50
+ state: undefined,
51
+ },
52
+ render: ({ state }: any) => (
53
+ <DisabledStateProvider state={state}>
54
+ <Button>Button</Button>
55
+ <Button isDisabled>Disabled Button</Button>
56
+ </DisabledStateProvider>
57
+ ),
58
+ };
59
+
60
+ /**
61
+ * Testing disabling children
62
+ */
63
+ export const AllComponents = {
64
+ args: {
65
+ state: 'disabled',
66
+ },
67
+ render: ({ state }: any) => (
68
+ <DisabledStateProvider state={state}>
69
+ <Button>Button</Button>
70
+ <IconButton label="Icon button" icon={mdiFoodApple} />
71
+ <Checkbox label="Checkbox" />
72
+ <Chip onClick={() => {}}>Chip</Chip>
73
+ <DatePickerField
74
+ nextButtonProps={{ label: 'Next' }}
75
+ previousButtonProps={{ label: 'Previous' }}
76
+ value={new Date()}
77
+ onChange={() => {}}
78
+ />
79
+ <Link href="https://example.com">Link</Link>
80
+ <List>
81
+ <ListItem onItemSelected={() => {}}>Clickable list item</ListItem>
82
+ </List>
83
+ <RadioButton label="Radio button" />
84
+ <Switch>Switch</Switch>
85
+ <TextField onChange={() => {}} value="" />
86
+ </DisabledStateProvider>
87
+ ),
88
+ };
@@ -0,0 +1,2 @@
1
+ export { useDisableStateProps } from './useDisableStateProps';
2
+ export { DisabledStateProvider, useDisabledStateContext } from './DisabledStateContext';
@@ -0,0 +1,37 @@
1
+ import { useDisabledStateContext } from './DisabledStateContext';
2
+
3
+ type GenericProps = {
4
+ disabled?: boolean;
5
+ isDisabled?: boolean;
6
+ 'aria-disabled'?: boolean | 'true' | 'false';
7
+ onClick?: any;
8
+ onChange?: any;
9
+ };
10
+
11
+ interface Output<TProps extends GenericProps> {
12
+ /** Is disabled or aria-disabled */
13
+ isAnyDisabled?: boolean;
14
+ disabledStateProps: { disabled?: boolean; 'aria-disabled'?: boolean };
15
+ otherProps: TProps & { disabled: never; 'aria-disabled': never; isDisabled: never };
16
+ }
17
+
18
+ /**
19
+ * Resolve disabled state from props.
20
+ * (handles `disabled`, `isDisabled` and `aria-disabled`)
21
+ *
22
+ * @params component props
23
+ */
24
+ export function useDisableStateProps<TProps extends GenericProps>(props: TProps): Output<TProps> {
25
+ const { disabled, isDisabled = disabled, 'aria-disabled': ariaDisabled, onClick, onChange, ...otherProps } = props;
26
+ const disabledStateContext = useDisabledStateContext();
27
+ const disabledStateProps = {
28
+ disabled: disabledStateContext?.state === 'disabled' || isDisabled,
29
+ 'aria-disabled': ariaDisabled === true || ariaDisabled === 'true',
30
+ };
31
+ const isAnyDisabled = disabledStateProps['aria-disabled'] || disabledStateProps.disabled;
32
+ if (!isAnyDisabled) {
33
+ (otherProps as any).onClick = onClick;
34
+ (otherProps as any).onChange = onChange;
35
+ }
36
+ return { disabledStateProps, otherProps: otherProps as Output<TProps>['otherProps'], isAnyDisabled };
37
+ }
@@ -4,3 +4,4 @@
4
4
 
5
5
  export { ClickAwayProvider } from './ClickAwayProvider';
6
6
  export { Portal, type PortalProps, type PortalInit, PortalProvider, type PortalProviderProps } from './Portal';
7
+ export { DisabledStateProvider, useDisabledStateContext } from './disabled';
@@ -0,0 +1,6 @@
1
+ import type { AriaAttributes } from 'react';
2
+
3
+ export interface HasAriaDisabled {
4
+ /** Similar to `disabled` but does not block pointer events or focus */
5
+ 'aria-disabled'?: AriaAttributes['aria-disabled'];
6
+ }
package/utils/index.d.ts CHANGED
@@ -58,4 +58,23 @@ interface PortalProps {
58
58
  */
59
59
  declare const Portal: React.FC<PortalProps>;
60
60
 
61
- export { ClickAwayProvider, Portal, type PortalInit, type PortalProps, PortalProvider, type PortalProviderProps };
61
+ /** Disable state */
62
+ type DisabledStateContextValue = {
63
+ state: 'disabled';
64
+ } | {
65
+ state: undefined | null;
66
+ };
67
+ type DisabledStateProviderProps = DisabledStateContextValue & {
68
+ children: React.ReactNode;
69
+ };
70
+ /**
71
+ * Disabled state provider.
72
+ * All nested LumX Design System components inherit this disabled state.
73
+ */
74
+ declare function DisabledStateProvider({ children, ...value }: DisabledStateProviderProps): React.JSX.Element;
75
+ /**
76
+ * Get DisabledState context value
77
+ */
78
+ declare function useDisabledStateContext(): DisabledStateContextValue;
79
+
80
+ export { ClickAwayProvider, DisabledStateProvider, Portal, type PortalInit, type PortalProps, PortalProvider, type PortalProviderProps, useDisabledStateContext };