@lumx/react 2.2.20 → 2.2.22
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/esm/_internal/AutocompleteMultiple.js.map +1 -1
- package/esm/_internal/Avatar2.js.map +1 -1
- package/esm/_internal/ButtonRoot.js.map +1 -1
- package/esm/_internal/Checkbox2.js.map +1 -1
- package/esm/_internal/Chip2.js.map +1 -1
- package/esm/_internal/ClickAwayProvider.js +45 -26
- package/esm/_internal/ClickAwayProvider.js.map +1 -1
- package/esm/_internal/CommentBlock.js.map +1 -1
- package/esm/_internal/Dialog2.js +6 -3
- package/esm/_internal/Dialog2.js.map +1 -1
- package/esm/_internal/Divider2.js.map +1 -1
- package/esm/_internal/DragHandle.js.map +1 -1
- package/esm/_internal/Dropdown2.js.map +1 -1
- package/esm/_internal/ExpansionPanel.js.map +1 -1
- package/esm/_internal/Flag2.js.map +1 -1
- package/esm/_internal/GenericBlock.js +70 -15
- package/esm/_internal/GenericBlock.js.map +1 -1
- package/esm/_internal/Icon2.js.map +1 -1
- package/esm/_internal/ImageBlock.js.map +1 -1
- package/esm/_internal/InputHelper.js.map +1 -1
- package/esm/_internal/InputLabel.js.map +1 -1
- package/esm/_internal/Lightbox2.js +1 -1
- package/esm/_internal/Lightbox2.js.map +1 -1
- package/esm/_internal/LinkPreview.js.map +1 -1
- package/esm/_internal/Mosaic2.js.map +1 -1
- package/esm/_internal/Notification2.js.map +1 -1
- package/esm/_internal/Popover2.js +1 -1
- package/esm/_internal/Popover2.js.map +1 -1
- package/esm/_internal/PostBlock.js.map +1 -1
- package/esm/_internal/Progress2.js.map +1 -1
- package/esm/_internal/RadioGroup.js.map +1 -1
- package/esm/_internal/SelectMultiple.js.map +1 -1
- package/esm/_internal/SideNavigationItem.js.map +1 -1
- package/esm/_internal/SkeletonTypography.js.map +1 -1
- package/esm/_internal/Slider2.js.map +1 -1
- package/esm/_internal/Slides.js.map +1 -1
- package/esm/_internal/Switch2.js.map +1 -1
- package/esm/_internal/TabPanel.js.map +1 -1
- package/esm/_internal/TableRow.js.map +1 -1
- package/esm/_internal/TextField.js.map +1 -1
- package/esm/_internal/Thumbnail2.js.map +1 -1
- package/esm/_internal/Uploader2.js.map +1 -1
- package/esm/_internal/UserBlock.js.map +1 -1
- package/esm/_internal/_rollupPluginBabelHelpers.js +5 -1
- package/esm/_internal/_rollupPluginBabelHelpers.js.map +1 -1
- package/esm/_internal/generic-block.js +8 -0
- package/esm/_internal/generic-block.js.map +1 -1
- package/esm/_internal/type.js +11 -1
- package/esm/_internal/type.js.map +1 -1
- package/package.json +5 -5
- package/src/components/autocomplete/Autocomplete.tsx +6 -8
- package/src/components/avatar/Avatar.tsx +2 -4
- package/src/components/button/Button.test.tsx +1 -1
- package/src/components/button/ButtonRoot.tsx +3 -4
- package/src/components/button/IconButton.test.tsx +1 -1
- package/src/components/checkbox/Checkbox.tsx +2 -4
- package/src/components/chip/Chip.tsx +2 -4
- package/src/components/comment-block/CommentBlock.tsx +2 -4
- package/src/components/dialog/Dialog.stories.tsx +7 -3
- package/src/components/dialog/Dialog.tsx +11 -4
- package/src/components/dialog/__snapshots__/Dialog.test.tsx.snap +7 -0
- package/src/components/divider/Divider.tsx +2 -5
- package/src/components/drag-handle/DragHandle.tsx +2 -5
- package/src/components/dropdown/Dropdown.tsx +4 -3
- package/src/components/expansion-panel/ExpansionPanel.tsx +2 -3
- package/src/components/flag/Flag.tsx +2 -4
- package/src/components/generic-block/GenericBlock.stories.tsx +65 -124
- package/src/components/generic-block/GenericBlock.test.tsx +111 -4
- package/src/components/generic-block/GenericBlock.tsx +107 -18
- package/src/components/icon/Icon.tsx +2 -4
- package/src/components/image-block/ImageBlock.tsx +2 -4
- package/src/components/input-helper/InputHelper.tsx +2 -4
- package/src/components/input-label/InputLabel.tsx +2 -4
- package/src/components/lightbox/Lightbox.tsx +4 -6
- package/src/components/lightbox/__snapshots__/Lightbox.test.tsx.snap +1 -1
- package/src/components/link-preview/LinkPreview.tsx +2 -4
- package/src/components/mosaic/Mosaic.tsx +2 -4
- package/src/components/notification/Notification.tsx +2 -4
- package/src/components/popover/Popover.tsx +1 -1
- package/src/components/popover/__snapshots__/Popover.test.tsx.snap +10 -10
- package/src/components/post-block/PostBlock.tsx +2 -4
- package/src/components/progress/Progress.tsx +2 -4
- package/src/components/radio-button/RadioButton.tsx +2 -4
- package/src/components/select/constants.ts +2 -5
- package/src/components/side-navigation/SideNavigation.tsx +2 -4
- package/src/components/skeleton/SkeletonCircle.tsx +2 -4
- package/src/components/skeleton/SkeletonRectangle.tsx +2 -4
- package/src/components/skeleton/SkeletonTypography.tsx +2 -4
- package/src/components/slider/Slider.tsx +2 -4
- package/src/components/slideshow/Slides.tsx +2 -7
- package/src/components/slideshow/SlideshowControls.tsx +2 -4
- package/src/components/switch/Switch.tsx +2 -4
- package/src/components/table/Table.tsx +2 -4
- package/src/components/tabs/TabList.tsx +2 -4
- package/src/components/text-field/TextField.tsx +6 -8
- package/src/components/thumbnail/Thumbnail.tsx +3 -5
- package/src/components/uploader/Uploader.tsx +2 -4
- package/src/components/user-block/UserBlock.tsx +2 -4
- package/src/hooks/useClickAway.tsx +5 -5
- package/src/testing/utils/commonTestsSuite.ts +2 -2
- package/src/utils/ClickAwayProvider/ClickAwayProvider.stories.jsx +58 -0
- package/src/utils/ClickAwayProvider/ClickAwayProvider.tsx +51 -19
- package/src/utils/focus/getFirstAndLastFocusable.test.ts +6 -0
- package/src/utils/focus/getFirstAndLastFocusable.ts +2 -2
- package/src/utils/type.ts +19 -2
- package/types.d.ts +112 -139
- package/src/components/generic-block/__snapshots__/GenericBlock.test.tsx.snap +0 -92
- package/src/utils/ClickAwayProvider/ClickAwayProvider.stories.tsx +0 -75
|
@@ -2,7 +2,7 @@ import classNames from 'classnames';
|
|
|
2
2
|
import React, { forwardRef } from 'react';
|
|
3
3
|
|
|
4
4
|
import { AspectRatio, GlobalSize, Theme, ColorPalette } from '@lumx/react';
|
|
5
|
-
import { Comp, GenericProps, getRootClassName, handleBasicClasses, ValueOf } from '@lumx/react/utils';
|
|
5
|
+
import { Comp, GenericProps, getRootClassName, handleBasicClasses, HasTheme, ValueOf } from '@lumx/react/utils';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Skeleton variants.
|
|
@@ -13,13 +13,11 @@ export type SkeletonRectangleVariant = ValueOf<typeof SkeletonRectangleVariant>;
|
|
|
13
13
|
/**
|
|
14
14
|
* Defines the props of the component.
|
|
15
15
|
*/
|
|
16
|
-
export interface SkeletonRectangleProps extends GenericProps {
|
|
16
|
+
export interface SkeletonRectangleProps extends GenericProps, HasTheme {
|
|
17
17
|
/** Aspect ratio (use with width and not height). */
|
|
18
18
|
aspectRatio?: Extract<AspectRatio, 'square' | 'horizontal' | 'vertical' | 'wide'>;
|
|
19
19
|
/** Height size. */
|
|
20
20
|
height?: GlobalSize;
|
|
21
|
-
/** Theme adapting the component to light or dark background. */
|
|
22
|
-
theme?: Theme;
|
|
23
21
|
/** Border variant. */
|
|
24
22
|
variant?: SkeletonRectangleVariant;
|
|
25
23
|
/** Width size. */
|
|
@@ -2,14 +2,12 @@ import classNames from 'classnames';
|
|
|
2
2
|
import React, { CSSProperties, forwardRef } from 'react';
|
|
3
3
|
|
|
4
4
|
import { Theme, TypographyInterface, ColorPalette } from '@lumx/react';
|
|
5
|
-
import { Comp, GenericProps, getRootClassName, handleBasicClasses } from '@lumx/react/utils';
|
|
5
|
+
import { Comp, GenericProps, getRootClassName, handleBasicClasses, HasTheme } from '@lumx/react/utils';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Defines the props of the component.
|
|
9
9
|
*/
|
|
10
|
-
export interface SkeletonTypographyProps extends GenericProps {
|
|
11
|
-
/** Theme adapting the component to light or dark background. */
|
|
12
|
-
theme?: Theme;
|
|
10
|
+
export interface SkeletonTypographyProps extends GenericProps, HasTheme {
|
|
13
11
|
/** Typography variant. */
|
|
14
12
|
typography: TypographyInterface;
|
|
15
13
|
/** Width CSS property. */
|
|
@@ -6,7 +6,7 @@ import classNames from 'classnames';
|
|
|
6
6
|
import { InputHelper, InputLabel, Theme } from '@lumx/react';
|
|
7
7
|
|
|
8
8
|
import useEventCallback from '@lumx/react/hooks/useEventCallback';
|
|
9
|
-
import { Comp, GenericProps, getRootClassName, handleBasicClasses } from '@lumx/react/utils';
|
|
9
|
+
import { Comp, GenericProps, getRootClassName, handleBasicClasses, HasTheme } from '@lumx/react/utils';
|
|
10
10
|
|
|
11
11
|
import { uid } from 'uid';
|
|
12
12
|
import { clamp } from '@lumx/react/utils/clamp';
|
|
@@ -14,7 +14,7 @@ import { clamp } from '@lumx/react/utils/clamp';
|
|
|
14
14
|
/**
|
|
15
15
|
* Defines the props of the component.
|
|
16
16
|
*/
|
|
17
|
-
export interface SliderProps extends GenericProps {
|
|
17
|
+
export interface SliderProps extends GenericProps, HasTheme {
|
|
18
18
|
/** Helper text. */
|
|
19
19
|
helper?: string;
|
|
20
20
|
/** Whether the min and max labels should be hidden or not. */
|
|
@@ -33,8 +33,6 @@ export interface SliderProps extends GenericProps {
|
|
|
33
33
|
precision?: number;
|
|
34
34
|
/** Range step value. */
|
|
35
35
|
steps?: number;
|
|
36
|
-
/** Theme adapting the component to light or dark background. */
|
|
37
|
-
theme?: Theme;
|
|
38
36
|
/** Selected ranged value. */
|
|
39
37
|
value: number;
|
|
40
38
|
/** On change callback. */
|
|
@@ -3,18 +3,13 @@ import React, { CSSProperties, forwardRef } from 'react';
|
|
|
3
3
|
import classNames from 'classnames';
|
|
4
4
|
|
|
5
5
|
import { FULL_WIDTH_PERCENT } from '@lumx/react/components/slideshow/constants';
|
|
6
|
-
import { Comp, GenericProps, getRootClassName, handleBasicClasses } from '@lumx/react/utils';
|
|
7
|
-
import { Theme } from '@lumx/react';
|
|
6
|
+
import { Comp, GenericProps, getRootClassName, handleBasicClasses, HasTheme } from '@lumx/react/utils';
|
|
8
7
|
|
|
9
|
-
export interface SlidesProps extends GenericProps {
|
|
8
|
+
export interface SlidesProps extends GenericProps, HasTheme {
|
|
10
9
|
/** current slide active */
|
|
11
10
|
activeIndex: number;
|
|
12
11
|
/** slides id to be added to the wrapper */
|
|
13
12
|
id?: string;
|
|
14
|
-
/** custom classname */
|
|
15
|
-
className?: string;
|
|
16
|
-
/** custom theme */
|
|
17
|
-
theme?: Theme;
|
|
18
13
|
/** Whether the automatic rotation of the slideshow is enabled or not. */
|
|
19
14
|
autoPlay?: boolean;
|
|
20
15
|
/** Whether the image has to fill its container height or not. */
|
|
@@ -5,7 +5,7 @@ import range from 'lodash/range';
|
|
|
5
5
|
|
|
6
6
|
import { mdiChevronLeft, mdiChevronRight, mdiPlayCircleOutline, mdiPauseCircleOutline } from '@lumx/icons';
|
|
7
7
|
import { Emphasis, IconButton, IconButtonProps, Theme } from '@lumx/react';
|
|
8
|
-
import { Comp, GenericProps, getRootClassName, handleBasicClasses } from '@lumx/react/utils';
|
|
8
|
+
import { Comp, GenericProps, getRootClassName, handleBasicClasses, HasTheme } from '@lumx/react/utils';
|
|
9
9
|
import { WINDOW } from '@lumx/react/constants';
|
|
10
10
|
import { useSlideshowControls, DEFAULT_OPTIONS } from '@lumx/react/hooks/useSlideshowControls';
|
|
11
11
|
|
|
@@ -17,7 +17,7 @@ import { usePaginationVisibleRange } from './usePaginationVisibleRange';
|
|
|
17
17
|
/**
|
|
18
18
|
* Defines the props of the component.
|
|
19
19
|
*/
|
|
20
|
-
export interface SlideshowControlsProps extends GenericProps {
|
|
20
|
+
export interface SlideshowControlsProps extends GenericProps, HasTheme {
|
|
21
21
|
/** Index of the current slide. */
|
|
22
22
|
activeIndex?: number;
|
|
23
23
|
/** Props to pass to the next button (minus those already set by the SlideshowControls props). */
|
|
@@ -30,8 +30,6 @@ export interface SlideshowControlsProps extends GenericProps {
|
|
|
30
30
|
Omit<IconButtonProps, 'label' | 'onClick' | 'icon' | 'emphasis' | 'color'>;
|
|
31
31
|
/** Number of slides. */
|
|
32
32
|
slidesCount: number;
|
|
33
|
-
/** Theme adapting the component to light or dark background. */
|
|
34
|
-
theme?: Theme;
|
|
35
33
|
/** On next button click callback. */
|
|
36
34
|
onNextClick?(loopback?: boolean): void;
|
|
37
35
|
/** On pagination change callback. */
|
|
@@ -7,12 +7,12 @@ import isEmpty from 'lodash/isEmpty';
|
|
|
7
7
|
|
|
8
8
|
import { Alignment, InputHelper, InputLabel, Theme } from '@lumx/react';
|
|
9
9
|
|
|
10
|
-
import { Comp, GenericProps, getRootClassName, handleBasicClasses } from '@lumx/react/utils';
|
|
10
|
+
import { Comp, GenericProps, getRootClassName, handleBasicClasses, HasTheme } from '@lumx/react/utils';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Defines the props of the component.
|
|
14
14
|
*/
|
|
15
|
-
export interface SwitchProps extends GenericProps {
|
|
15
|
+
export interface SwitchProps extends GenericProps, HasTheme {
|
|
16
16
|
/** Helper text. */
|
|
17
17
|
helper?: string;
|
|
18
18
|
/** Whether it is checked or not. */
|
|
@@ -23,8 +23,6 @@ export interface SwitchProps extends GenericProps {
|
|
|
23
23
|
name?: string;
|
|
24
24
|
/** Position of the switch relative to the label. */
|
|
25
25
|
position?: Extract<Alignment, 'right' | 'left'>;
|
|
26
|
-
/** Theme adapting the component to light or dark background. */
|
|
27
|
-
theme?: Theme;
|
|
28
26
|
/** Native input value property. */
|
|
29
27
|
value?: string;
|
|
30
28
|
/** On change callback. */
|
|
@@ -4,18 +4,16 @@ import classNames from 'classnames';
|
|
|
4
4
|
|
|
5
5
|
import { Theme } from '@lumx/react';
|
|
6
6
|
|
|
7
|
-
import { Comp, GenericProps, getRootClassName, handleBasicClasses } from '@lumx/react/utils';
|
|
7
|
+
import { Comp, GenericProps, getRootClassName, handleBasicClasses, HasTheme } from '@lumx/react/utils';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Defines the props of the component.
|
|
11
11
|
*/
|
|
12
|
-
export interface TableProps extends GenericProps {
|
|
12
|
+
export interface TableProps extends GenericProps, HasTheme {
|
|
13
13
|
/** Whether the table has checkbox or thumbnail on first cell or not. */
|
|
14
14
|
hasBefore?: boolean;
|
|
15
15
|
/** Whether the table has dividers or not. */
|
|
16
16
|
hasDividers?: boolean;
|
|
17
|
-
/** Theme adapting the component to light or dark background. */
|
|
18
|
-
theme?: Theme;
|
|
19
17
|
}
|
|
20
18
|
|
|
21
19
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Alignment, Theme } from '@lumx/react';
|
|
2
2
|
import { CSS_PREFIX } from '@lumx/react/constants';
|
|
3
|
-
import { Comp, GenericProps, handleBasicClasses } from '@lumx/react/utils';
|
|
3
|
+
import { Comp, GenericProps, handleBasicClasses, HasTheme } from '@lumx/react/utils';
|
|
4
4
|
import { mergeRefs } from '@lumx/react/utils/mergeRefs';
|
|
5
5
|
|
|
6
6
|
import classNames from 'classnames';
|
|
@@ -15,7 +15,7 @@ export enum TabListLayout {
|
|
|
15
15
|
/**
|
|
16
16
|
* Defines the props of the component.
|
|
17
17
|
*/
|
|
18
|
-
export interface TabListProps extends GenericProps {
|
|
18
|
+
export interface TabListProps extends GenericProps, HasTheme {
|
|
19
19
|
/** ARIA label (purpose of the set of tabs). */
|
|
20
20
|
['aria-label']: string;
|
|
21
21
|
/** Tab list. */
|
|
@@ -24,8 +24,6 @@ export interface TabListProps extends GenericProps {
|
|
|
24
24
|
layout?: TabListLayout;
|
|
25
25
|
/** Position of the tabs in the list (requires 'clustered' layout). */
|
|
26
26
|
position?: Alignment;
|
|
27
|
-
/** Theme adapting the component to light or dark background. */
|
|
28
|
-
theme?: Theme;
|
|
29
27
|
}
|
|
30
28
|
|
|
31
29
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { forwardRef, ReactNode,
|
|
1
|
+
import React, { forwardRef, ReactNode, Ref, SyntheticEvent, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
|
|
3
3
|
import classNames from 'classnames';
|
|
4
4
|
import get from 'lodash/get';
|
|
@@ -6,13 +6,13 @@ import { uid } from 'uid';
|
|
|
6
6
|
|
|
7
7
|
import { mdiAlertCircle, mdiCheckCircle, mdiCloseCircle } from '@lumx/icons';
|
|
8
8
|
import { Emphasis, Icon, IconButton, IconButtonProps, InputHelper, InputLabel, Kind, Size, Theme } from '@lumx/react';
|
|
9
|
-
import { Comp, GenericProps, getRootClassName, handleBasicClasses } from '@lumx/react/utils';
|
|
9
|
+
import { Comp, GenericProps, getRootClassName, handleBasicClasses, HasTheme } from '@lumx/react/utils';
|
|
10
10
|
import { mergeRefs } from '@lumx/react/utils/mergeRefs';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Defines the props of the component.
|
|
14
14
|
*/
|
|
15
|
-
export interface TextFieldProps extends GenericProps {
|
|
15
|
+
export interface TextFieldProps extends GenericProps, HasTheme {
|
|
16
16
|
/** Chip Group to be rendered before the main text input. */
|
|
17
17
|
chips?: HTMLElement | ReactNode;
|
|
18
18
|
/** Props to pass to the clear button (minus those already set by the TextField props). If not specified, the button won't be displayed. */
|
|
@@ -33,7 +33,7 @@ export interface TextFieldProps extends GenericProps {
|
|
|
33
33
|
/** Native input id property (generated if not provided to link the label element). */
|
|
34
34
|
id?: string;
|
|
35
35
|
/** Reference to the <input> or <textarea> element. */
|
|
36
|
-
inputRef?:
|
|
36
|
+
inputRef?: Ref<HTMLInputElement | HTMLTextAreaElement>;
|
|
37
37
|
/** Whether the component is disabled or not. */
|
|
38
38
|
isDisabled?: boolean;
|
|
39
39
|
/** Whether the component is required or not. */
|
|
@@ -53,9 +53,7 @@ export interface TextFieldProps extends GenericProps {
|
|
|
53
53
|
/** Placeholder text. */
|
|
54
54
|
placeholder?: string;
|
|
55
55
|
/** Reference to the wrapper. */
|
|
56
|
-
textFieldRef?:
|
|
57
|
-
/** Theme adapting the component to light or dark background. */
|
|
58
|
-
theme?: Theme;
|
|
56
|
+
textFieldRef?: Ref<HTMLDivElement>;
|
|
59
57
|
/** Value. */
|
|
60
58
|
value?: string;
|
|
61
59
|
/** On blur callback. */
|
|
@@ -139,7 +137,7 @@ const useComputeNumberOfRows = (
|
|
|
139
137
|
|
|
140
138
|
interface InputNativeProps {
|
|
141
139
|
id?: string;
|
|
142
|
-
inputRef?:
|
|
140
|
+
inputRef?: TextFieldProps['inputRef'];
|
|
143
141
|
isDisabled?: boolean;
|
|
144
142
|
isRequired?: boolean;
|
|
145
143
|
multiline?: boolean;
|
|
@@ -13,7 +13,7 @@ import classNames from 'classnames';
|
|
|
13
13
|
|
|
14
14
|
import { AspectRatio, HorizontalAlignment, Icon, Size, Theme } from '@lumx/react';
|
|
15
15
|
|
|
16
|
-
import { Comp, GenericProps, getRootClassName, handleBasicClasses } from '@lumx/react/utils';
|
|
16
|
+
import { Comp, Falsy, GenericProps, getRootClassName, handleBasicClasses, HasTheme } from '@lumx/react/utils';
|
|
17
17
|
|
|
18
18
|
import { mdiImageBroken } from '@lumx/icons';
|
|
19
19
|
import { mergeRefs } from '@lumx/react/utils/mergeRefs';
|
|
@@ -26,7 +26,7 @@ type ImgHTMLProps = ImgHTMLAttributes<HTMLImageElement>;
|
|
|
26
26
|
/**
|
|
27
27
|
* Defines the props of the component.
|
|
28
28
|
*/
|
|
29
|
-
export interface ThumbnailProps extends GenericProps {
|
|
29
|
+
export interface ThumbnailProps extends GenericProps, HasTheme {
|
|
30
30
|
/** Alignment of the thumbnail in it's parent (requires flex parent). */
|
|
31
31
|
align?: HorizontalAlignment;
|
|
32
32
|
/** Image alternative text. */
|
|
@@ -34,7 +34,7 @@ export interface ThumbnailProps extends GenericProps {
|
|
|
34
34
|
/** Image aspect ratio. */
|
|
35
35
|
aspectRatio?: AspectRatio;
|
|
36
36
|
/** Badge. */
|
|
37
|
-
badge?: ReactElement;
|
|
37
|
+
badge?: ReactElement | Falsy;
|
|
38
38
|
/** Image cross origin resource policy. */
|
|
39
39
|
crossOrigin?: ImgHTMLProps['crossOrigin'];
|
|
40
40
|
/** Fallback icon (SVG path) or react node when image fails to load. */
|
|
@@ -59,8 +59,6 @@ export interface ThumbnailProps extends GenericProps {
|
|
|
59
59
|
onClick?: MouseEventHandler<HTMLDivElement>;
|
|
60
60
|
/** On key press callback. */
|
|
61
61
|
onKeyPress?: KeyboardEventHandler<HTMLDivElement>;
|
|
62
|
-
/** Theme adapting the component to light or dark background. */
|
|
63
|
-
theme?: Theme;
|
|
64
62
|
/** Variant of the component. */
|
|
65
63
|
variant?: ThumbnailVariant;
|
|
66
64
|
/** Props to pass to the link wrapping the thumbnail. */
|
|
@@ -3,7 +3,7 @@ import React, { forwardRef, MouseEventHandler } from 'react';
|
|
|
3
3
|
import classNames from 'classnames';
|
|
4
4
|
|
|
5
5
|
import { AspectRatio, Icon, Size, Theme } from '@lumx/react';
|
|
6
|
-
import { Comp, GenericProps, getRootClassName, handleBasicClasses, ValueOf } from '@lumx/react/utils';
|
|
6
|
+
import { Comp, GenericProps, getRootClassName, handleBasicClasses, HasTheme, ValueOf } from '@lumx/react/utils';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Uploader variants.
|
|
@@ -23,7 +23,7 @@ export type UploaderSize = Extract<Size, 'xl' | 'xxl'>;
|
|
|
23
23
|
/**
|
|
24
24
|
* Defines the props of the component.
|
|
25
25
|
*/
|
|
26
|
-
export interface UploaderProps extends GenericProps {
|
|
26
|
+
export interface UploaderProps extends GenericProps, HasTheme {
|
|
27
27
|
/** Image aspect ratio. */
|
|
28
28
|
aspectRatio?: AspectRatio;
|
|
29
29
|
/** Icon (SVG path). */
|
|
@@ -32,8 +32,6 @@ export interface UploaderProps extends GenericProps {
|
|
|
32
32
|
label?: string;
|
|
33
33
|
/** Size variant. */
|
|
34
34
|
size?: UploaderSize;
|
|
35
|
-
/** Theme adapting the component to light or dark background. */
|
|
36
|
-
theme?: Theme;
|
|
37
35
|
/** Variant. */
|
|
38
36
|
variant?: UploaderVariant;
|
|
39
37
|
/** On click callback. */
|
|
@@ -4,7 +4,7 @@ import classNames from 'classnames';
|
|
|
4
4
|
import set from 'lodash/set';
|
|
5
5
|
|
|
6
6
|
import { Avatar, ColorPalette, Link, Orientation, Size, Theme } from '@lumx/react';
|
|
7
|
-
import { Comp, GenericProps, getRootClassName, handleBasicClasses } from '@lumx/react/utils';
|
|
7
|
+
import { Comp, GenericProps, getRootClassName, handleBasicClasses, HasTheme } from '@lumx/react/utils';
|
|
8
8
|
|
|
9
9
|
import { AvatarProps } from '../avatar/Avatar';
|
|
10
10
|
|
|
@@ -16,7 +16,7 @@ export type UserBlockSize = Extract<Size, 's' | 'm' | 'l'>;
|
|
|
16
16
|
/**
|
|
17
17
|
* Defines the props of the component.
|
|
18
18
|
*/
|
|
19
|
-
export interface UserBlockProps extends GenericProps {
|
|
19
|
+
export interface UserBlockProps extends GenericProps, HasTheme {
|
|
20
20
|
/** Props to pass to the avatar. */
|
|
21
21
|
avatarProps?: Omit<AvatarProps, 'alt'>;
|
|
22
22
|
/** Additional fields used to describe the user. */
|
|
@@ -37,8 +37,6 @@ export interface UserBlockProps extends GenericProps {
|
|
|
37
37
|
simpleAction?: ReactNode;
|
|
38
38
|
/** Size variant. */
|
|
39
39
|
size?: UserBlockSize;
|
|
40
|
-
/** Theme adapting the component to light or dark background. */
|
|
41
|
-
theme?: Theme;
|
|
42
40
|
/** On click callback. */
|
|
43
41
|
onClick?(): void;
|
|
44
42
|
/** On mouse enter callback. */
|
|
@@ -17,9 +17,9 @@ export interface ClickAwayParameters {
|
|
|
17
17
|
*/
|
|
18
18
|
callback: EventListener | Falsy;
|
|
19
19
|
/**
|
|
20
|
-
* Elements
|
|
20
|
+
* Elements considered within the click away context (clicking outside them will trigger the click away callback).
|
|
21
21
|
*/
|
|
22
|
-
|
|
22
|
+
childrenRefs: RefObject<Array<RefObject<HTMLElement>>>;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
/**
|
|
@@ -27,9 +27,9 @@ export interface ClickAwayParameters {
|
|
|
27
27
|
*
|
|
28
28
|
* Warning: If you need to detect click away on nested React portals, please use the `ClickAwayProvider` component.
|
|
29
29
|
*/
|
|
30
|
-
export function useClickAway({ callback,
|
|
30
|
+
export function useClickAway({ callback, childrenRefs }: ClickAwayParameters): void {
|
|
31
31
|
useEffect(() => {
|
|
32
|
-
const { current: currentRefs } =
|
|
32
|
+
const { current: currentRefs } = childrenRefs;
|
|
33
33
|
if (!callback || !currentRefs || isEmpty(currentRefs)) {
|
|
34
34
|
return undefined;
|
|
35
35
|
}
|
|
@@ -43,5 +43,5 @@ export function useClickAway({ callback, refs }: ClickAwayParameters): void {
|
|
|
43
43
|
return () => {
|
|
44
44
|
EVENT_TYPES.forEach((evtType) => document.removeEventListener(evtType, listener));
|
|
45
45
|
};
|
|
46
|
-
}, [callback,
|
|
46
|
+
}, [callback, childrenRefs]);
|
|
47
47
|
}
|
|
@@ -47,8 +47,8 @@ export function commonTestsSuite(
|
|
|
47
47
|
const wrappersToTest = isArray(tests.className)
|
|
48
48
|
? tests.className
|
|
49
49
|
: [tests.className as string, tests.className as string];
|
|
50
|
-
expect(wrappers[wrappersToTest[0]]).toHaveClassName(params.className);
|
|
51
|
-
expect(wrappers[wrappersToTest[1]]).toHaveClassName(modifiedProps.className);
|
|
50
|
+
expect(wrappers[wrappersToTest[0]]).toHaveClassName(params.className as string);
|
|
51
|
+
expect(wrappers[wrappersToTest[1]]).toHaveClassName(modifiedProps.className as string);
|
|
52
52
|
});
|
|
53
53
|
}
|
|
54
54
|
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-use-before-define */
|
|
2
|
+
import { Button } from '@lumx/react';
|
|
3
|
+
import { ClickAwayProvider } from '@lumx/react/utils/ClickAwayProvider';
|
|
4
|
+
import React, { useCallback, useRef, useState } from 'react';
|
|
5
|
+
import { createPortal } from 'react-dom';
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
title: 'LumX components/ClickAwayProvider',
|
|
9
|
+
parameters: {
|
|
10
|
+
// Disables Chromatic snapshot (not relevant for this story).
|
|
11
|
+
chromatic: { disable: true },
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// eslint-disable-next-line react/display-name
|
|
16
|
+
const Card = React.forwardRef(({ top, children, close }, ref) => (
|
|
17
|
+
<ClickAwayProvider callback={close} childrenRefs={useRef([ref])}>
|
|
18
|
+
{createPortal(
|
|
19
|
+
<div
|
|
20
|
+
className="lumx-spacing-padding"
|
|
21
|
+
ref={ref}
|
|
22
|
+
style={{ position: 'absolute', top, border: '1px solid red' }}
|
|
23
|
+
>
|
|
24
|
+
{children}
|
|
25
|
+
</div>,
|
|
26
|
+
document.body,
|
|
27
|
+
)}
|
|
28
|
+
</ClickAwayProvider>
|
|
29
|
+
));
|
|
30
|
+
|
|
31
|
+
const ButtonWithCard = ({ level, parentRef }) => {
|
|
32
|
+
const ref3 = useRef();
|
|
33
|
+
const [isOpen3, setOpen3] = useState(false);
|
|
34
|
+
const open3 = useCallback(() => setOpen3(true), []);
|
|
35
|
+
const close3 = useCallback(() => setOpen3(false), []);
|
|
36
|
+
const nextLevel = level + 1;
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<>
|
|
40
|
+
Level {level} - <Button onClick={open3}>Open level {nextLevel}</Button>
|
|
41
|
+
{isOpen3 && (
|
|
42
|
+
<Card ref={ref3} close={close3} top={parentRef?.current?.getBoundingClientRect().bottom || '130px'}>
|
|
43
|
+
{nextLevel === 4 ? `Level ${nextLevel}` : <ButtonWithCard level={nextLevel} parentRef={ref3} />}
|
|
44
|
+
</Card>
|
|
45
|
+
)}
|
|
46
|
+
</>
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* This story showcases the detection of click away inside a nested React portal tree (i.e. not nested in the DOM)
|
|
52
|
+
*/
|
|
53
|
+
export const NestedClickAway = () => (
|
|
54
|
+
<>
|
|
55
|
+
<p>Clicking inside a level should close all child levels but not parent levels</p>
|
|
56
|
+
<ButtonWithCard level={0} />
|
|
57
|
+
</>
|
|
58
|
+
);
|
|
@@ -1,33 +1,65 @@
|
|
|
1
|
-
import React, { createContext, useContext, useEffect } from 'react';
|
|
2
|
-
import pull from 'lodash/pull';
|
|
1
|
+
import React, { createContext, RefObject, useContext, useEffect, useMemo, useRef } from 'react';
|
|
3
2
|
import { ClickAwayParameters, useClickAway } from '@lumx/react/hooks/useClickAway';
|
|
4
3
|
|
|
5
|
-
|
|
4
|
+
interface ContextValue {
|
|
5
|
+
childrenRefs: Array<RefObject<HTMLElement>>;
|
|
6
|
+
addRefs(...newChildrenRefs: Array<RefObject<HTMLElement>>): void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const ClickAwayAncestorContext = createContext<ContextValue | null>(null);
|
|
10
|
+
|
|
11
|
+
interface ClickAwayProviderProps extends ClickAwayParameters {
|
|
12
|
+
/**
|
|
13
|
+
* (Optional) Element that should be considered as part of the parent
|
|
14
|
+
*/
|
|
15
|
+
parentRef?: RefObject<HTMLElement>;
|
|
16
|
+
}
|
|
6
17
|
|
|
7
18
|
/**
|
|
8
19
|
* Component combining the `useClickAway` hook with a React context to hook into the React component tree and make sure
|
|
9
|
-
* we take into account both the DOM tree and the React tree
|
|
20
|
+
* we take into account both the DOM tree and the React tree to detect click away.
|
|
10
21
|
*
|
|
11
22
|
* @return the react component.
|
|
12
23
|
*/
|
|
13
|
-
export const ClickAwayProvider: React.FC<
|
|
14
|
-
|
|
24
|
+
export const ClickAwayProvider: React.FC<ClickAwayProviderProps> = ({
|
|
25
|
+
children,
|
|
26
|
+
callback,
|
|
27
|
+
childrenRefs,
|
|
28
|
+
parentRef,
|
|
29
|
+
}) => {
|
|
30
|
+
const parentContext = useContext(ClickAwayAncestorContext);
|
|
31
|
+
const currentContext = useMemo(() => {
|
|
32
|
+
const context: ContextValue = {
|
|
33
|
+
childrenRefs: [],
|
|
34
|
+
/**
|
|
35
|
+
* Add element refs to the current context and propagate to the parent context.
|
|
36
|
+
*/
|
|
37
|
+
addRefs(...newChildrenRefs) {
|
|
38
|
+
// Add element refs that should be considered as inside the click away context.
|
|
39
|
+
context.childrenRefs.push(...newChildrenRefs);
|
|
40
|
+
|
|
41
|
+
if (parentContext) {
|
|
42
|
+
// Also add then to the parent context
|
|
43
|
+
parentContext.addRefs(...newChildrenRefs);
|
|
44
|
+
if (parentRef) {
|
|
45
|
+
// The parent element is also considered as inside the parent click away context but not inside the current context
|
|
46
|
+
parentContext.addRefs(parentRef);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
return context;
|
|
52
|
+
}, [parentContext, parentRef]);
|
|
15
53
|
|
|
16
54
|
useEffect(() => {
|
|
17
|
-
const { current: currentRefs } =
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
return undefined;
|
|
55
|
+
const { current: currentRefs } = childrenRefs;
|
|
56
|
+
if (!currentRefs) {
|
|
57
|
+
return;
|
|
21
58
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return () => {
|
|
25
|
-
// Pull current refs from parent.
|
|
26
|
-
pull(currentAncestorChildrenRefs, ...currentRefs);
|
|
27
|
-
};
|
|
28
|
-
}, [ancestorChildrenRefs, refs]);
|
|
59
|
+
currentContext.addRefs(...currentRefs);
|
|
60
|
+
}, [currentContext, childrenRefs]);
|
|
29
61
|
|
|
30
|
-
useClickAway({ callback,
|
|
31
|
-
return <ClickAwayAncestorContext.Provider value={
|
|
62
|
+
useClickAway({ callback, childrenRefs: useRef(currentContext.childrenRefs) });
|
|
63
|
+
return <ClickAwayAncestorContext.Provider value={currentContext}>{children}</ClickAwayAncestorContext.Provider>;
|
|
32
64
|
};
|
|
33
65
|
ClickAwayProvider.displayName = 'ClickAwayProvider';
|
|
@@ -124,5 +124,11 @@ describe(getFirstAndLastFocusable.name, () => {
|
|
|
124
124
|
const focusable = getFirstAndLastFocusable(element);
|
|
125
125
|
expect(focusable.first).toMatchInlineSnapshot(`<button />`);
|
|
126
126
|
});
|
|
127
|
+
|
|
128
|
+
it('should skip hidden input', () => {
|
|
129
|
+
const element = htmlToElement(`<div><input hidden /><button /></div>`);
|
|
130
|
+
const focusable = getFirstAndLastFocusable(element);
|
|
131
|
+
expect(focusable.first).toMatchInlineSnapshot(`<button />`);
|
|
132
|
+
});
|
|
127
133
|
});
|
|
128
134
|
});
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/** CSS selector listing all tabbable elements. */
|
|
2
|
-
const TABBABLE_ELEMENTS_SELECTOR = `a[href], button, textarea, input:not([type="hidden"]), [tabindex]`;
|
|
2
|
+
const TABBABLE_ELEMENTS_SELECTOR = `a[href], button, textarea, input:not([type="hidden"]):not([hidden]), [tabindex]`;
|
|
3
3
|
|
|
4
4
|
/** CSS selector matching element that are disabled (should not receive focus). */
|
|
5
|
-
const DISABLED_SELECTOR = `[tabindex="-1"], [disabled]:not([disabled="false"]), [aria-disabled]:not([aria-disabled="false"])`;
|
|
5
|
+
const DISABLED_SELECTOR = `[hidden], [tabindex="-1"], [disabled]:not([disabled="false"]), [aria-disabled]:not([aria-disabled="false"])`;
|
|
6
6
|
|
|
7
7
|
const isNotDisabled = (element: HTMLElement) => !element.matches(DISABLED_SELECTOR);
|
|
8
8
|
|
package/src/utils/type.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import get from 'lodash/get';
|
|
2
|
-
import { ReactElement, ReactNode, Ref } from 'react';
|
|
2
|
+
import React, { ReactElement, ReactNode, Ref } from 'react';
|
|
3
|
+
import { Theme } from '@lumx/react';
|
|
3
4
|
|
|
4
5
|
/** Get types of the values of a record. */
|
|
5
6
|
export type ValueOf<T extends Record<any, any>> = T[keyof T];
|
|
@@ -34,13 +35,23 @@ export type Comp<P, T = HTMLElement> = {
|
|
|
34
35
|
/** Union type of all heading elements */
|
|
35
36
|
export type HeadingElement = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
|
|
36
37
|
|
|
38
|
+
export interface HasTheme {
|
|
39
|
+
/**
|
|
40
|
+
* Theme adapting the component to light or dark background.
|
|
41
|
+
*/
|
|
42
|
+
theme?: Theme;
|
|
43
|
+
}
|
|
44
|
+
|
|
37
45
|
/**
|
|
38
46
|
* Define a generic props types.
|
|
39
47
|
*/
|
|
40
48
|
export interface GenericProps {
|
|
49
|
+
/**
|
|
50
|
+
* Class name forwarded to the root element of the component.
|
|
51
|
+
*/
|
|
52
|
+
className?: string;
|
|
41
53
|
/**
|
|
42
54
|
* Any prop (particularly any supported prop for a HTML element).
|
|
43
|
-
* E.g. classNames, onClick, disabled, ...
|
|
44
55
|
*/
|
|
45
56
|
[propName: string]: any;
|
|
46
57
|
}
|
|
@@ -66,6 +77,12 @@ export const isComponent = <C>(component: Comp<C, any> | string) => (instance: R
|
|
|
66
77
|
);
|
|
67
78
|
};
|
|
68
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Similar to `isComponent` but more precise as it's not based on the component `displayName` but on the component function reference.
|
|
82
|
+
*/
|
|
83
|
+
export const isComponentType = (type: ReactElement['type']) => (node: ReactNode): node is ReactElement =>
|
|
84
|
+
React.isValidElement(node) && node.type === type;
|
|
85
|
+
|
|
69
86
|
/**
|
|
70
87
|
* JS falsy values.
|
|
71
88
|
* (excluding `NaN` as it can't be distinguished from `number`)
|