@lumx/react 3.10.0 → 3.10.1-alpha.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.
Files changed (101) hide show
  1. package/_internal/index.d.ts +1 -1
  2. package/index.d.ts +13 -5
  3. package/index.js +217 -202
  4. package/index.js.map +1 -1
  5. package/package.json +3 -3
  6. package/src/components/autocomplete/Autocomplete.test.tsx +9 -2
  7. package/src/components/autocomplete/Autocomplete.tsx +3 -1
  8. package/src/components/autocomplete/AutocompleteMultiple.test.tsx +9 -2
  9. package/src/components/autocomplete/AutocompleteMultiple.tsx +3 -1
  10. package/src/components/avatar/Avatar.test.tsx +14 -4
  11. package/src/components/avatar/Avatar.tsx +3 -2
  12. package/src/components/button/Button.test.tsx +9 -3
  13. package/src/components/button/Button.tsx +3 -2
  14. package/src/components/button/IconButton.test.tsx +9 -3
  15. package/src/components/button/IconButton.tsx +13 -2
  16. package/src/components/checkbox/Checkbox.test.tsx +9 -3
  17. package/src/components/checkbox/Checkbox.tsx +4 -4
  18. package/src/components/chip/Chip.test.tsx +14 -4
  19. package/src/components/chip/Chip.tsx +3 -2
  20. package/src/components/comment-block/CommentBlock.test.tsx +9 -3
  21. package/src/components/comment-block/CommentBlock.tsx +3 -2
  22. package/src/components/date-picker/DatePickerField.test.tsx +9 -3
  23. package/src/components/dialog/Dialog.test.tsx +17 -4
  24. package/src/components/dialog/Dialog.tsx +61 -58
  25. package/src/components/divider/Divider.test.tsx +9 -3
  26. package/src/components/divider/Divider.tsx +4 -4
  27. package/src/components/drag-handle/DragHandle.test.tsx +38 -0
  28. package/src/components/drag-handle/DragHandle.tsx +3 -1
  29. package/src/components/expansion-panel/ExpansionPanel.test.tsx +12 -3
  30. package/src/components/expansion-panel/ExpansionPanel.tsx +4 -4
  31. package/src/components/flag/Flag.test.tsx +14 -4
  32. package/src/components/flag/Flag.tsx +4 -4
  33. package/src/components/icon/Icon.test.tsx +13 -4
  34. package/src/components/icon/Icon.tsx +13 -1
  35. package/src/components/image-block/ImageBlock.test.tsx +12 -4
  36. package/src/components/image-block/ImageBlock.tsx +3 -2
  37. package/src/components/input-helper/InputHelper.test.tsx +14 -4
  38. package/src/components/input-helper/InputHelper.tsx +3 -2
  39. package/src/components/input-label/InputLabel.test.tsx +14 -4
  40. package/src/components/input-label/InputLabel.tsx +4 -4
  41. package/src/components/lightbox/Lightbox.test.tsx +17 -6
  42. package/src/components/lightbox/Lightbox.tsx +8 -5
  43. package/src/components/link-preview/LinkPreview.test.tsx +9 -3
  44. package/src/components/link-preview/LinkPreview.tsx +3 -2
  45. package/src/components/mosaic/Mosaic.test.tsx +9 -3
  46. package/src/components/mosaic/Mosaic.tsx +4 -4
  47. package/src/components/navigation/Navigation.test.tsx +18 -9
  48. package/src/components/navigation/Navigation.tsx +13 -5
  49. package/src/components/navigation/NavigationItem.tsx +3 -3
  50. package/src/components/navigation/NavigationSection.tsx +4 -4
  51. package/src/components/notification/Notification.tsx +3 -2
  52. package/src/components/popover/Popover.test.tsx +18 -4
  53. package/src/components/popover/Popover.tsx +2 -1
  54. package/src/components/post-block/PostBlock.test.tsx +9 -3
  55. package/src/components/post-block/PostBlock.tsx +3 -2
  56. package/src/components/progress/Progress.tsx +3 -2
  57. package/src/components/progress/ProgressCircular.test.tsx +9 -16
  58. package/src/components/progress/ProgressCircular.tsx +3 -2
  59. package/src/components/progress/ProgressLinear.test.tsx +13 -18
  60. package/src/components/progress/ProgressLinear.tsx +4 -4
  61. package/src/components/radio-button/RadioButton.test.tsx +9 -3
  62. package/src/components/radio-button/RadioButton.tsx +4 -4
  63. package/src/components/select/Select.test.tsx +9 -3
  64. package/src/components/select/Select.tsx +27 -23
  65. package/src/components/select/SelectMultiple.test.tsx +9 -3
  66. package/src/components/select/SelectMultiple.tsx +109 -103
  67. package/src/components/select/WithSelectContext.tsx +8 -6
  68. package/src/components/side-navigation/SideNavigation.tsx +3 -1
  69. package/src/components/skeleton/SkeletonCircle.test.tsx +9 -3
  70. package/src/components/skeleton/SkeletonCircle.tsx +4 -4
  71. package/src/components/skeleton/SkeletonRectangle.test.tsx +9 -3
  72. package/src/components/skeleton/SkeletonRectangle.tsx +3 -2
  73. package/src/components/skeleton/SkeletonTypography.test.tsx +9 -3
  74. package/src/components/skeleton/SkeletonTypography.tsx +4 -4
  75. package/src/components/slider/Slider.test.tsx +9 -3
  76. package/src/components/slider/Slider.tsx +3 -2
  77. package/src/components/slideshow/Slides.tsx +3 -1
  78. package/src/components/slideshow/Slideshow.test.tsx +9 -3
  79. package/src/components/slideshow/Slideshow.tsx +3 -2
  80. package/src/components/slideshow/SlideshowControls.tsx +3 -2
  81. package/src/components/switch/Switch.test.tsx +9 -3
  82. package/src/components/switch/Switch.tsx +3 -2
  83. package/src/components/table/Table.test.tsx +9 -3
  84. package/src/components/table/Table.tsx +4 -4
  85. package/src/components/tabs/TabList.test.tsx +9 -3
  86. package/src/components/tabs/TabList.tsx +11 -2
  87. package/src/components/text-field/TextField.test.tsx +9 -3
  88. package/src/components/text-field/TextField.tsx +3 -2
  89. package/src/components/thumbnail/Thumbnail.test.tsx +9 -3
  90. package/src/components/thumbnail/Thumbnail.tsx +3 -2
  91. package/src/components/uploader/Uploader.test.tsx +9 -3
  92. package/src/components/uploader/Uploader.tsx +13 -2
  93. package/src/components/user-block/UserBlock.test.tsx +9 -3
  94. package/src/components/user-block/UserBlock.tsx +3 -2
  95. package/src/index.ts +1 -0
  96. package/src/testing/utils/ThemeSentinel.tsx +11 -0
  97. package/src/testing/utils/commonTestsSuiteRTL.tsx +190 -0
  98. package/src/utils/theme/ThemeContext.ts +16 -0
  99. package/src/utils/theme/invertTheme.ts +4 -0
  100. package/src/testing/utils/commonTestsSuiteRTL.ts +0 -64
  101. package/src/utils/ThemeContext.ts +0 -4
@@ -6,6 +6,7 @@ import { Comp, GenericProps, HasTheme, ValueOf } from '@lumx/react/utils/type';
6
6
  import { getRootClassName, handleBasicClasses } from '@lumx/react/utils/className';
7
7
  import { useBooleanState } from '@lumx/react/hooks/useBooleanState';
8
8
  import { useId } from '@lumx/react/hooks/useId';
9
+ import { useTheme } from '@lumx/react/utils/theme/ThemeContext';
9
10
 
10
11
  /**
11
12
  * Uploader variants.
@@ -65,7 +66,6 @@ const CLASSNAME = getRootClassName(COMPONENT_NAME);
65
66
  const DEFAULT_PROPS: Partial<UploaderProps> = {
66
67
  aspectRatio: AspectRatio.horizontal,
67
68
  size: Size.xl,
68
- theme: Theme.light,
69
69
  variant: UploaderVariant.square,
70
70
  };
71
71
 
@@ -77,7 +77,18 @@ const DEFAULT_PROPS: Partial<UploaderProps> = {
77
77
  * @return React element.
78
78
  */
79
79
  export const Uploader: Comp<UploaderProps> = forwardRef((props, ref) => {
80
- const { aspectRatio, className, label, icon, size, theme, variant, fileInputProps, ...forwardedProps } = props;
80
+ const defaultTheme = useTheme() || Theme.light;
81
+ const {
82
+ aspectRatio,
83
+ className,
84
+ label,
85
+ icon,
86
+ size,
87
+ theme = defaultTheme,
88
+ variant,
89
+ fileInputProps,
90
+ ...forwardedProps
91
+ } = props;
81
92
  // Adjust to square aspect ratio when using circle variants.
82
93
  const adjustedAspectRatio = variant === UploaderVariant.circle ? AspectRatio.square : aspectRatio;
83
94
 
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
 
3
- import { commonTestsSuiteRTL } from '@lumx/react/testing/utils';
3
+ import { commonTestsSuiteRTL, SetupRenderOptions } from '@lumx/react/testing/utils';
4
4
  import { render, within } from '@testing-library/react';
5
5
  import { getByClassName, queryByClassName } from '@lumx/react/testing/utils/queries';
6
6
  import { Thumbnail } from '@lumx/react';
@@ -13,10 +13,10 @@ const CLASSNAME = UserBlock.className as string;
13
13
  /**
14
14
  * Mounts the component and returns common DOM elements / data needed in multiple tests further down.
15
15
  */
16
- const setup = (propsOverride: Partial<UserBlockProps> = {}) => {
16
+ const setup = (propsOverride: Partial<UserBlockProps> = {}, { wrapper }: SetupRenderOptions = {}) => {
17
17
  const props: UserBlockProps = { ...propsOverride };
18
18
 
19
- render(<UserBlock {...props} />);
19
+ render(<UserBlock {...props} />, { wrapper });
20
20
  const userBlock = getByClassName(document.body, CLASSNAME);
21
21
  const name = queryByClassName(userBlock, `${CLASSNAME}__name`);
22
22
  const avatar = queryByClassName(userBlock, `${CLASSNAME}__avatar`);
@@ -86,5 +86,11 @@ describe(`<${UserBlock.displayName}>`, () => {
86
86
  forwardClassName: 'userBlock',
87
87
  forwardAttributes: 'userBlock',
88
88
  forwardRef: 'userBlock',
89
+ applyTheme: {
90
+ affects: [{ element: 'userBlock' }],
91
+ viaProp: true,
92
+ viaContext: true,
93
+ defaultTheme: 'light',
94
+ },
89
95
  });
90
96
  });
@@ -7,6 +7,7 @@ import { Avatar, ColorPalette, Link, Orientation, Size, Theme } from '@lumx/reac
7
7
  import { Comp, GenericProps, HasTheme } from '@lumx/react/utils/type';
8
8
  import { getRootClassName, handleBasicClasses } from '@lumx/react/utils/className';
9
9
 
10
+ import { useTheme } from '@lumx/react/utils/theme/ThemeContext';
10
11
  import { AvatarProps } from '../avatar/Avatar';
11
12
 
12
13
  /**
@@ -62,7 +63,6 @@ const CLASSNAME = getRootClassName(COMPONENT_NAME);
62
63
  const DEFAULT_PROPS: Partial<UserBlockProps> = {
63
64
  orientation: Orientation.horizontal,
64
65
  size: Size.m,
65
- theme: Theme.light,
66
66
  };
67
67
 
68
68
  /**
@@ -73,6 +73,7 @@ const DEFAULT_PROPS: Partial<UserBlockProps> = {
73
73
  * @return React element.
74
74
  */
75
75
  export const UserBlock: Comp<UserBlockProps, HTMLDivElement> = forwardRef((props, ref) => {
76
+ const defaultTheme = useTheme() || Theme.light;
76
77
  const {
77
78
  avatarProps,
78
79
  className,
@@ -88,7 +89,7 @@ export const UserBlock: Comp<UserBlockProps, HTMLDivElement> = forwardRef((props
88
89
  orientation,
89
90
  simpleAction,
90
91
  size,
91
- theme,
92
+ theme = defaultTheme,
92
93
  ...forwardedProps
93
94
  } = props;
94
95
  let componentSize = size;
package/src/index.ts CHANGED
@@ -58,3 +58,4 @@ export * from './components/toolbar';
58
58
  export * from './components/tooltip';
59
59
  export * from './components/uploader';
60
60
  export * from './components/user-block';
61
+ export { ThemeProvider, useTheme } from './utils/theme/ThemeContext';
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ import { useTheme } from '@lumx/react/utils/theme/ThemeContext';
3
+
4
+ const TEST_ID = 'theme-sentinel';
5
+
6
+ /** Test component used as sentinel to detect the value of the theme context */
7
+ export function ThemeSentinel() {
8
+ const theme = useTheme();
9
+ return <div data-testid={TEST_ID} className={`--theme-${theme}`} />;
10
+ }
11
+ ThemeSentinel.testId = TEST_ID;
@@ -0,0 +1,190 @@
1
+ import isEmpty from 'lodash/isEmpty';
2
+
3
+ import { GenericProps } from '@lumx/react/utils/type';
4
+ import { queryByClassName } from '@lumx/react/testing/utils/queries';
5
+ import React from 'react';
6
+ import { Theme } from '@lumx/react';
7
+ import { RenderOptions } from '@testing-library/react';
8
+ import { ThemeProvider } from '@lumx/react/utils/theme/ThemeContext';
9
+ import castArray from 'lodash/castArray';
10
+ import { invertTheme } from '@lumx/react/utils/theme/invertTheme';
11
+
12
+ interface CommonSetup {
13
+ props: GenericProps;
14
+ }
15
+
16
+ type AffectConfig<E> = { element: E; classModifier?: 'color' | 'theme'; inverted?: boolean };
17
+ type Not<E> = { not: E };
18
+
19
+ interface Options<S extends CommonSetup> {
20
+ baseClassName: string;
21
+ forwardClassName?: keyof S;
22
+ forwardAttributes?: keyof S;
23
+ forwardRef?: keyof S;
24
+ applyTheme?: {
25
+ /** Element(s) to which we apply the theme class */
26
+ affects: Array<AffectConfig<keyof S> | Not<AffectConfig<keyof S>>>;
27
+ /** Apply theme via theme prop */
28
+ viaProp: boolean;
29
+ /** Apply theme via theme context */
30
+ viaContext: boolean;
31
+ /** Apply a default theme if no prop or context was provided */
32
+ defaultTheme?: Theme;
33
+ };
34
+ }
35
+
36
+ export type SetupRenderOptions = { wrapper?: RenderOptions['wrapper'] };
37
+ export type SetupFunction<S extends CommonSetup> = (
38
+ props?: GenericProps,
39
+ options?: SetupRenderOptions,
40
+ ) => S | Promise<S>;
41
+
42
+ /**
43
+ * Common tests on components
44
+ * - Check base class name and class name forwarding
45
+ * - Check props forwarding
46
+ */
47
+ export function commonTestsSuiteRTL<S extends CommonSetup>(setup: SetupFunction<S>, options: Options<S>): void {
48
+ if (isEmpty(options)) {
49
+ return;
50
+ }
51
+ const { baseClassName, forwardClassName, forwardAttributes, forwardRef, applyTheme } = options;
52
+ describe('Common tests suite', () => {
53
+ it('should render with base class name', async () => {
54
+ await setup();
55
+ expect(queryByClassName(document.body, baseClassName)).toBeInTheDocument();
56
+ });
57
+
58
+ if (forwardClassName) {
59
+ it('should forward any CSS class', async () => {
60
+ const modifiedProps = {
61
+ className: 'component component--is-tested',
62
+ };
63
+ const wrappers = await setup(modifiedProps);
64
+ expect(wrappers[forwardClassName]).toHaveClass(modifiedProps.className);
65
+ });
66
+ }
67
+
68
+ if (forwardAttributes) {
69
+ it('should forward any other prop', async () => {
70
+ const modifiedProps = {
71
+ winter: 'is coming',
72
+ };
73
+ const wrappers = await setup(modifiedProps);
74
+ expect(wrappers[forwardAttributes]).toHaveAttribute('winter', modifiedProps.winter);
75
+ });
76
+ }
77
+
78
+ if (forwardRef) {
79
+ it('should forward ref', async () => {
80
+ const ref = React.createRef();
81
+ const wrappers = await setup({ ref });
82
+ expect(ref.current).toBe(wrappers[forwardRef]);
83
+ });
84
+ }
85
+
86
+ if (applyTheme) {
87
+ describe('theme', () => {
88
+ const { affects, defaultTheme, viaProp, viaContext } = applyTheme;
89
+ const testElements = affects.map((configOrNot) => {
90
+ let shouldHaveModifier: boolean = true;
91
+ let config: AffectConfig<any>;
92
+ if ('not' in configOrNot) {
93
+ shouldHaveModifier = false;
94
+ config = configOrNot.not;
95
+ } else config = configOrNot;
96
+
97
+ const {
98
+ element,
99
+ classModifier = 'theme',
100
+ inverted = false,
101
+ }: AffectConfig<any> = typeof config === 'object' ? config : { element: config };
102
+ return {
103
+ element,
104
+ getExpectedClassModifier: (theme: Theme) =>
105
+ `--${classModifier}-${inverted ? invertTheme(theme) : theme}`,
106
+ shouldHaveModifier,
107
+ apply: shouldHaveModifier ? 'apply' : 'not apply',
108
+ };
109
+ });
110
+
111
+ const expectTheme = (
112
+ wrappers: any,
113
+ { element, getExpectedClassModifier, shouldHaveModifier }: any,
114
+ theme: Theme,
115
+ override: { shouldHaveModifier?: boolean } = {},
116
+ ) => {
117
+ for (const wrapper of castArray(wrappers[element])) {
118
+ let expected: any = expect(wrapper.className);
119
+ if (override.shouldHaveModifier === false || !shouldHaveModifier) {
120
+ expected = expected.not;
121
+ }
122
+ expected.toContain(getExpectedClassModifier(theme));
123
+ }
124
+ };
125
+
126
+ if (defaultTheme) {
127
+ it.each(testElements)(
128
+ `should $apply default theme (${defaultTheme}) to \`$element\``,
129
+ async (affectedElement) => {
130
+ const wrappers = await setup();
131
+ expectTheme(wrappers, affectedElement, defaultTheme);
132
+ },
133
+ );
134
+ }
135
+
136
+ // Only the elements that are affected by theme
137
+ const affectedElements = testElements.filter((e) => e.shouldHaveModifier);
138
+ if (!defaultTheme && affectedElements.length) {
139
+ it.each(affectedElements)(
140
+ `should not apply default theme (${defaultTheme}) to \`$element\``,
141
+ async (affectedElement) => {
142
+ const wrappers = await setup();
143
+ expectTheme(wrappers, affectedElement, Theme.light, { shouldHaveModifier: false });
144
+ expectTheme(wrappers, affectedElement, Theme.dark, { shouldHaveModifier: false });
145
+ },
146
+ );
147
+ }
148
+
149
+ if (viaProp) {
150
+ const theme = invertTheme(defaultTheme || Theme.light);
151
+ it.each(testElements)(
152
+ `should $apply prop theme=${theme} to \`$element\``,
153
+ async (affectedElement) => {
154
+ const wrappers = await setup({ theme });
155
+ expectTheme(wrappers, affectedElement, theme);
156
+ },
157
+ );
158
+ }
159
+
160
+ const contextTheme = invertTheme(defaultTheme || Theme.light);
161
+ it.each(testElements)(
162
+ `should $apply context theme=${contextTheme} to \`$element\``,
163
+ async (affectedElement) => {
164
+ const Wrapper = ({ children }: any) => (
165
+ <ThemeProvider value={contextTheme}>{children}</ThemeProvider>
166
+ );
167
+ const wrappers = await setup({}, { wrapper: Wrapper });
168
+ expectTheme(wrappers, affectedElement, contextTheme, {
169
+ shouldHaveModifier: !viaContext ? false : undefined,
170
+ });
171
+ },
172
+ );
173
+
174
+ if (viaProp && viaContext) {
175
+ const propTheme = invertTheme(contextTheme);
176
+ it.each(testElements)(
177
+ `should $apply prop theme=${propTheme} to \`$element\` overriding the context theme=${contextTheme}`,
178
+ async (affectedElement) => {
179
+ const Wrapper = ({ children }: any) => (
180
+ <ThemeProvider value={contextTheme}>{children}</ThemeProvider>
181
+ );
182
+ const wrappers = await setup({ theme: propTheme }, { wrapper: Wrapper });
183
+ expectTheme(wrappers, affectedElement, propTheme);
184
+ },
185
+ );
186
+ }
187
+ });
188
+ }
189
+ });
190
+ }
@@ -0,0 +1,16 @@
1
+ import React from 'react';
2
+ import type { Theme } from '@lumx/react';
3
+
4
+ type ThemeContextValue = Theme | undefined;
5
+ export const ThemeContext = React.createContext<ThemeContextValue>(undefined);
6
+
7
+ /** Provide a theme context to all children. */
8
+ export const ThemeProvider = ThemeContext.Provider as React.FC<{
9
+ value: ThemeContextValue;
10
+ children?: React.ReactNode;
11
+ }>;
12
+
13
+ /** Get the theme in the current context. */
14
+ export function useTheme(): ThemeContextValue {
15
+ return React.useContext(ThemeContext);
16
+ }
@@ -0,0 +1,4 @@
1
+ import type { Theme } from '@lumx/react';
2
+
3
+ /** Invert the color of the given theme. */
4
+ export const invertTheme = (theme: Theme): Theme => (theme === 'light' ? 'dark' : 'light');
@@ -1,64 +0,0 @@
1
- import isEmpty from 'lodash/isEmpty';
2
-
3
- import { GenericProps } from '@lumx/react/utils/type';
4
- import { queryByClassName } from '@lumx/react/testing/utils/queries';
5
- import React from 'react';
6
-
7
- interface CommonSetup {
8
- props: GenericProps;
9
- }
10
-
11
- interface Options<S extends CommonSetup> {
12
- baseClassName: string;
13
- forwardClassName?: keyof S;
14
- forwardAttributes?: keyof S;
15
- forwardRef?: keyof S;
16
- }
17
-
18
- type SetupFunction<S extends CommonSetup> = (props?: GenericProps) => S | Promise<S>;
19
-
20
- /**
21
- * Common tests on components
22
- * - Check base class name and class name forwarding
23
- * - Check props forwarding
24
- */
25
- export function commonTestsSuiteRTL<S extends CommonSetup>(setup: SetupFunction<S>, options: Options<S>): void {
26
- if (isEmpty(options)) {
27
- return;
28
- }
29
- const { baseClassName, forwardClassName, forwardAttributes, forwardRef } = options;
30
- describe('Common tests suite', () => {
31
- it('should render with base class name', async () => {
32
- await setup();
33
- expect(queryByClassName(document.body, baseClassName)).toBeInTheDocument();
34
- });
35
-
36
- if (forwardClassName) {
37
- it('should forward any CSS class', async () => {
38
- const modifiedProps = {
39
- className: 'component component--is-tested',
40
- };
41
- const wrappers = await setup(modifiedProps);
42
- expect(wrappers[forwardClassName]).toHaveClass(modifiedProps.className);
43
- });
44
- }
45
-
46
- if (forwardAttributes) {
47
- it('should forward any other prop', async () => {
48
- const modifiedProps = {
49
- winter: 'is coming',
50
- };
51
- const wrappers = await setup(modifiedProps);
52
- expect(wrappers[forwardAttributes]).toHaveAttribute('winter', modifiedProps.winter);
53
- });
54
- }
55
-
56
- if (forwardRef) {
57
- it('should forward ref', async () => {
58
- const ref = React.createRef();
59
- const wrappers = await setup({ ref });
60
- expect(ref.current).toBe(wrappers[forwardRef]);
61
- });
62
- }
63
- });
64
- }
@@ -1,4 +0,0 @@
1
- import { createContext } from 'react';
2
- import { Theme } from '..';
3
-
4
- export const ThemeContext = createContext<Theme | undefined>(undefined);