@lumx/react 2.2.19 → 2.2.20-alpha-type.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/esm/_internal/AutocompleteMultiple.js.map +1 -1
- package/esm/_internal/ButtonRoot.js.map +1 -1
- package/esm/_internal/Checkbox2.js +3 -1
- package/esm/_internal/Checkbox2.js.map +1 -1
- package/esm/_internal/ClickAwayProvider.js +90 -12
- package/esm/_internal/ClickAwayProvider.js.map +1 -1
- package/esm/_internal/DatePickerField.js +18 -11
- package/esm/_internal/DatePickerField.js.map +1 -1
- package/esm/_internal/Dialog2.js +2 -2
- package/esm/_internal/Dialog2.js.map +1 -1
- package/esm/_internal/Dropdown2.js.map +1 -1
- package/esm/_internal/GenericBlock.js +90 -0
- package/esm/_internal/GenericBlock.js.map +1 -0
- package/esm/_internal/Lightbox2.js +2 -2
- package/esm/_internal/Lightbox2.js.map +1 -1
- package/esm/_internal/LinkPreview.js +22 -12
- package/esm/_internal/LinkPreview.js.map +1 -1
- package/esm/_internal/Popover2.js +21 -8
- package/esm/_internal/Popover2.js.map +1 -1
- package/esm/_internal/SelectMultiple.js +16 -4
- package/esm/_internal/SelectMultiple.js.map +1 -1
- package/esm/_internal/TextField.js.map +1 -1
- package/esm/_internal/Thumbnail2.js.map +1 -1
- package/esm/_internal/alert-dialog.js +2 -2
- package/esm/_internal/autocomplete.js +2 -1
- package/esm/_internal/autocomplete.js.map +1 -1
- package/esm/_internal/button.js +2 -1
- package/esm/_internal/button.js.map +1 -1
- package/esm/_internal/comment-block.js +2 -1
- package/esm/_internal/comment-block.js.map +1 -1
- package/esm/_internal/date-picker.js +3 -2
- package/esm/_internal/date-picker.js.map +1 -1
- package/esm/_internal/dialog.js +2 -2
- package/esm/_internal/dropdown.js +2 -1
- package/esm/_internal/dropdown.js.map +1 -1
- package/esm/_internal/expansion-panel.js +1 -1
- package/esm/_internal/generic-block.js +12 -0
- package/esm/_internal/generic-block.js.map +1 -0
- package/esm/_internal/lightbox.js +3 -2
- package/esm/_internal/lightbox.js.map +1 -1
- package/esm/_internal/popover.js +2 -1
- package/esm/_internal/popover.js.map +1 -1
- package/esm/_internal/select.js +2 -1
- package/esm/_internal/select.js.map +1 -1
- package/esm/_internal/side-navigation.js +2 -1
- package/esm/_internal/side-navigation.js.map +1 -1
- package/esm/_internal/slideshow.js +2 -1
- package/esm/_internal/slideshow.js.map +1 -1
- package/esm/_internal/text-field.js +2 -1
- package/esm/_internal/text-field.js.map +1 -1
- package/esm/_internal/tooltip.js +2 -1
- package/esm/_internal/tooltip.js.map +1 -1
- package/esm/_internal/type.js.map +1 -1
- package/esm/_internal/useFocusTrap.js +62 -78
- package/esm/_internal/useFocusTrap.js.map +1 -1
- package/esm/index.js +3 -2
- package/esm/index.js.map +1 -1
- package/package.json +5 -5
- package/src/components/autocomplete/Autocomplete.tsx +4 -4
- package/src/components/button/Button.stories.tsx +1 -0
- package/src/components/button/ButtonRoot.tsx +4 -4
- package/src/components/checkbox/Checkbox.tsx +2 -1
- package/src/components/checkbox/__snapshots__/Checkbox.test.tsx.snap +4 -0
- package/src/components/date-picker/DatePickerField.tsx +15 -16
- package/src/components/date-picker/types.ts +2 -2
- package/src/components/dialog/Dialog.stories.tsx +57 -13
- package/src/components/dialog/Dialog.tsx +3 -3
- package/src/components/dialog/__snapshots__/Dialog.test.tsx.snap +82 -14
- package/src/components/dropdown/Dropdown.tsx +4 -3
- package/src/components/generic-block/GenericBlock.stories.tsx +149 -0
- package/src/components/generic-block/GenericBlock.test.tsx +28 -0
- package/src/components/generic-block/GenericBlock.tsx +120 -0
- package/src/components/generic-block/__snapshots__/GenericBlock.test.tsx.snap +92 -0
- package/src/components/generic-block/index.ts +1 -0
- package/src/components/lightbox/Lightbox.tsx +1 -1
- package/src/components/link-preview/LinkPreview.test.tsx +50 -55
- package/src/components/link-preview/LinkPreview.tsx +43 -16
- package/src/components/popover/Popover.tsx +20 -4
- package/src/components/select/Select.stories.tsx +2 -0
- package/src/components/select/Select.tsx +11 -1
- package/src/components/select/SelectMultiple.stories.tsx +2 -0
- package/src/components/select/SelectMultiple.tsx +11 -1
- package/src/components/select/constants.ts +2 -0
- package/src/components/table/__snapshots__/Table.test.tsx.snap +5 -0
- package/src/components/text-field/TextField.tsx +4 -4
- package/src/components/thumbnail/Thumbnail.tsx +2 -2
- package/src/hooks/useCallbackOnEscape.ts +21 -13
- package/src/hooks/useFocusTrap.ts +68 -51
- package/src/index.ts +1 -0
- package/src/stories/generated/GenericBlock/Demos.stories.tsx +6 -0
- package/src/utils/focus/getFirstAndLastFocusable.test.ts +6 -0
- package/src/utils/focus/getFirstAndLastFocusable.ts +2 -2
- package/src/utils/makeListenerTowerContext.ts +32 -0
- package/src/utils/type.ts +3 -0
- package/types.d.ts +70 -16
- package/src/components/link-preview/__snapshots__/LinkPreview.test.tsx.snap +0 -51
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`<GenericBlock> Snapshots and structure should render story 'Default' 1`] = `
|
|
4
|
+
Array [
|
|
5
|
+
<FlexBox
|
|
6
|
+
className="lumx-generic-block"
|
|
7
|
+
gap="regular"
|
|
8
|
+
hAlign="center"
|
|
9
|
+
orientation="vertical"
|
|
10
|
+
vAlign="center"
|
|
11
|
+
>
|
|
12
|
+
<FlexBox
|
|
13
|
+
className="lumx-generic-block__figure"
|
|
14
|
+
>
|
|
15
|
+
<Avatar
|
|
16
|
+
alt=""
|
|
17
|
+
image="/demo-assets/persona.png"
|
|
18
|
+
size="m"
|
|
19
|
+
theme="light"
|
|
20
|
+
/>
|
|
21
|
+
</FlexBox>
|
|
22
|
+
<FlexBox
|
|
23
|
+
className="lumx-generic-block__content"
|
|
24
|
+
fillSpace={true}
|
|
25
|
+
orientation="vertical"
|
|
26
|
+
vAlign="center"
|
|
27
|
+
>
|
|
28
|
+
<h2>
|
|
29
|
+
Content title
|
|
30
|
+
</h2>
|
|
31
|
+
<p>
|
|
32
|
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec rhoncus libero aliquet pharetra luctus. Fusce nisl turpis, posuere ac tellus at, euismod vulputate libero...
|
|
33
|
+
</p>
|
|
34
|
+
</FlexBox>
|
|
35
|
+
<FlexBox
|
|
36
|
+
className="lumx-generic-block__actions"
|
|
37
|
+
vAlign="center"
|
|
38
|
+
>
|
|
39
|
+
<Button
|
|
40
|
+
emphasis="high"
|
|
41
|
+
size="m"
|
|
42
|
+
theme="light"
|
|
43
|
+
>
|
|
44
|
+
Actions
|
|
45
|
+
</Button>
|
|
46
|
+
</FlexBox>
|
|
47
|
+
</FlexBox>,
|
|
48
|
+
<FlexBox
|
|
49
|
+
className="lumx-generic-block"
|
|
50
|
+
gap="regular"
|
|
51
|
+
hAlign="center"
|
|
52
|
+
orientation="horizontal"
|
|
53
|
+
vAlign="center"
|
|
54
|
+
>
|
|
55
|
+
<FlexBox
|
|
56
|
+
className="lumx-generic-block__figure"
|
|
57
|
+
>
|
|
58
|
+
<Avatar
|
|
59
|
+
alt=""
|
|
60
|
+
image="/demo-assets/persona.png"
|
|
61
|
+
size="m"
|
|
62
|
+
theme="light"
|
|
63
|
+
/>
|
|
64
|
+
</FlexBox>
|
|
65
|
+
<FlexBox
|
|
66
|
+
className="lumx-generic-block__content"
|
|
67
|
+
fillSpace={true}
|
|
68
|
+
orientation="vertical"
|
|
69
|
+
vAlign="left"
|
|
70
|
+
>
|
|
71
|
+
<h2>
|
|
72
|
+
Content title
|
|
73
|
+
</h2>
|
|
74
|
+
<p>
|
|
75
|
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec rhoncus libero aliquet pharetra luctus. Fusce nisl turpis, posuere ac tellus at, euismod vulputate libero...
|
|
76
|
+
</p>
|
|
77
|
+
</FlexBox>
|
|
78
|
+
<FlexBox
|
|
79
|
+
className="lumx-generic-block__actions"
|
|
80
|
+
vAlign="right"
|
|
81
|
+
>
|
|
82
|
+
<Button
|
|
83
|
+
emphasis="high"
|
|
84
|
+
size="m"
|
|
85
|
+
theme="light"
|
|
86
|
+
>
|
|
87
|
+
Actions
|
|
88
|
+
</Button>
|
|
89
|
+
</FlexBox>
|
|
90
|
+
</FlexBox>,
|
|
91
|
+
]
|
|
92
|
+
`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './GenericBlock';
|
|
@@ -87,7 +87,7 @@ export const Lightbox: Comp<LightboxProps, HTMLDivElement> = forwardRef((props,
|
|
|
87
87
|
|
|
88
88
|
// Handle focus trap.
|
|
89
89
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
90
|
-
useFocusTrap(wrapperRef.current, childrenRef.current?.firstChild);
|
|
90
|
+
useFocusTrap(isOpen && wrapperRef.current, childrenRef.current?.firstChild);
|
|
91
91
|
|
|
92
92
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
93
93
|
const previousOpen = useRef(isOpen);
|
|
@@ -5,7 +5,7 @@ import 'jest-enzyme';
|
|
|
5
5
|
|
|
6
6
|
import { commonTestsSuite, Wrapper } from '@lumx/react/testing/utils';
|
|
7
7
|
import { getBasicClass } from '@lumx/react/utils';
|
|
8
|
-
import { Thumbnail } from '@lumx/react';
|
|
8
|
+
import { Link, Thumbnail } from '@lumx/react';
|
|
9
9
|
|
|
10
10
|
import { Size, Theme } from '..';
|
|
11
11
|
import { LinkPreview, LinkPreviewProps } from './LinkPreview';
|
|
@@ -25,81 +25,76 @@ const setup = (propsOverride: SetupProps = {}, shallowRendering = true) => {
|
|
|
25
25
|
|
|
26
26
|
return {
|
|
27
27
|
thumbnail: wrapper.find(Thumbnail),
|
|
28
|
+
title: wrapper.find(`.${CLASSNAME}__title`),
|
|
29
|
+
description: wrapper.find(`.${CLASSNAME}__description`),
|
|
30
|
+
link: wrapper.find(`.${CLASSNAME}__link`).find(Link),
|
|
28
31
|
props,
|
|
29
32
|
wrapper,
|
|
30
33
|
};
|
|
31
34
|
};
|
|
32
35
|
|
|
33
36
|
describe(`<${LinkPreview.displayName}>`, () => {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
+
it('should render with default props', () => {
|
|
38
|
+
const { wrapper, thumbnail, title, link, description } = setup();
|
|
39
|
+
expect(wrapper).toHaveClassName(CLASSNAME);
|
|
37
40
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
expect(wrapper).toMatchSnapshot();
|
|
41
|
-
|
|
42
|
-
expect(wrapper).toExist();
|
|
43
|
-
expect(wrapper).toHaveClassName(CLASSNAME);
|
|
41
|
+
['size', 'theme'].forEach((type) => {
|
|
42
|
+
expect(wrapper).toHaveClassName(getBasicClass({ prefix: CLASSNAME, type, value: DEFAULT_PROPS[type] }));
|
|
44
43
|
});
|
|
44
|
+
expect(thumbnail).not.toExist();
|
|
45
|
+
expect(title).not.toExist();
|
|
46
|
+
expect(link).toExist();
|
|
47
|
+
expect(link).toHaveProp('tabIndex', undefined);
|
|
48
|
+
expect(description).not.toExist();
|
|
49
|
+
});
|
|
45
50
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
expect(wrapper).toMatchSnapshot();
|
|
51
|
+
it('should render with only the title', () => {
|
|
52
|
+
const { title, link } = setup({ title: 'Title' });
|
|
49
53
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
54
|
+
expect(title).toExist();
|
|
55
|
+
expect(link).toExist();
|
|
56
|
+
expect(link).toHaveProp('tabIndex', '-1');
|
|
53
57
|
});
|
|
54
58
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
expect(thumbnail).not.toExist();
|
|
59
|
+
it('should render with complete props', () => {
|
|
60
|
+
const { wrapper, thumbnail, title, link, description, props } = setup({
|
|
61
|
+
size: Size.big,
|
|
62
|
+
theme: Theme.dark,
|
|
63
|
+
thumbnailProps: { image: 'https://example.com/thumbnail.jpg', alt: '' },
|
|
64
|
+
link: 'https://example.com',
|
|
65
|
+
linkProps: { 'data-custom-attr': 'true' },
|
|
66
|
+
title: 'Title',
|
|
67
|
+
description: 'Description',
|
|
65
68
|
});
|
|
66
69
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
});
|
|
70
|
+
const validateLink = (linkElement: any) => {
|
|
71
|
+
expect(linkElement).toHaveProp('href', props.link);
|
|
72
|
+
// Props forwarding
|
|
73
|
+
expect(linkElement).toHaveProp('data-custom-attr', 'true');
|
|
74
|
+
};
|
|
73
75
|
|
|
74
|
-
|
|
75
|
-
const { wrapper } = setup({ theme: Theme.dark });
|
|
76
|
+
expect(wrapper).toExist();
|
|
76
77
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
// Thumbnail
|
|
79
|
+
expect(thumbnail).toExist();
|
|
80
|
+
validateLink((thumbnail as any).dive());
|
|
80
81
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const { thumbnail } = setup({
|
|
85
|
-
link: expectedUrl,
|
|
86
|
-
thumbnailProps: { image: 'https://expected.url/image.png', alt: 'Alt' },
|
|
87
|
-
});
|
|
88
|
-
window.open = jest.fn();
|
|
82
|
+
// Title
|
|
83
|
+
expect(title).toHaveText(props.title);
|
|
84
|
+
validateLink(title.find(Link));
|
|
89
85
|
|
|
90
|
-
|
|
86
|
+
// Link
|
|
87
|
+
expect(link).toHaveText(props.link);
|
|
88
|
+
validateLink(link.find(Link));
|
|
91
89
|
|
|
92
|
-
|
|
93
|
-
|
|
90
|
+
// Description
|
|
91
|
+
expect(description).toHaveText(props.description);
|
|
94
92
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
// Nothing to do here.
|
|
98
|
-
});
|
|
93
|
+
// Size prop applied
|
|
94
|
+
expect(wrapper).toHaveClassName(getBasicClass({ prefix: CLASSNAME, type: 'size', value: props.size }));
|
|
99
95
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
// Nothing to do here.
|
|
96
|
+
// Dark theme applied
|
|
97
|
+
expect(wrapper).toHaveClassName(getBasicClass({ prefix: CLASSNAME, type: 'theme', value: Theme.dark }));
|
|
103
98
|
});
|
|
104
99
|
|
|
105
100
|
// Common tests suite.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { forwardRef
|
|
1
|
+
import React, { forwardRef } from 'react';
|
|
2
2
|
|
|
3
3
|
import classNames from 'classnames';
|
|
4
4
|
|
|
@@ -14,16 +14,18 @@ import {
|
|
|
14
14
|
ThumbnailProps,
|
|
15
15
|
} from '@lumx/react';
|
|
16
16
|
|
|
17
|
-
import { Comp, GenericProps, getRootClassName, handleBasicClasses } from '@lumx/react/utils';
|
|
17
|
+
import { Comp, GenericProps, getRootClassName, handleBasicClasses, HeadingElement } from '@lumx/react/utils';
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* Defines the props of the component.
|
|
21
21
|
*/
|
|
22
22
|
export interface LinkPreviewProps extends GenericProps {
|
|
23
|
-
/** Description
|
|
24
|
-
description?: string
|
|
23
|
+
/** Description. */
|
|
24
|
+
description?: string;
|
|
25
25
|
/** Link URL. */
|
|
26
26
|
link: string;
|
|
27
|
+
/** Custom react component for the link (can be used to inject react router Link). */
|
|
28
|
+
linkAs?: 'a' | any;
|
|
27
29
|
/** Props to pass to the link (minus those already set by the LinkPreview props). */
|
|
28
30
|
linkProps?: Omit<LinkProps, 'color' | 'colorVariant' | 'href' | 'target'>;
|
|
29
31
|
/** Size variant. */
|
|
@@ -34,6 +36,8 @@ export interface LinkPreviewProps extends GenericProps {
|
|
|
34
36
|
thumbnailProps?: ThumbnailProps;
|
|
35
37
|
/** Title. */
|
|
36
38
|
title?: string;
|
|
39
|
+
/** Customize the title heading tag. */
|
|
40
|
+
titleHeading?: HeadingElement;
|
|
37
41
|
}
|
|
38
42
|
|
|
39
43
|
/**
|
|
@@ -49,10 +53,11 @@ const CLASSNAME = getRootClassName(COMPONENT_NAME);
|
|
|
49
53
|
/**
|
|
50
54
|
* Component default props.
|
|
51
55
|
*/
|
|
52
|
-
const DEFAULT_PROPS
|
|
56
|
+
const DEFAULT_PROPS = {
|
|
53
57
|
size: Size.regular,
|
|
54
58
|
theme: Theme.light,
|
|
55
|
-
|
|
59
|
+
titleHeading: 'h2',
|
|
60
|
+
} as const;
|
|
56
61
|
|
|
57
62
|
/**
|
|
58
63
|
* LinkPreview component.
|
|
@@ -62,13 +67,24 @@ const DEFAULT_PROPS: Partial<LinkPreviewProps> = {
|
|
|
62
67
|
* @return React element.
|
|
63
68
|
*/
|
|
64
69
|
export const LinkPreview: Comp<LinkPreviewProps, HTMLDivElement> = forwardRef((props, ref) => {
|
|
65
|
-
const {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
70
|
+
const {
|
|
71
|
+
className,
|
|
72
|
+
description,
|
|
73
|
+
link,
|
|
74
|
+
linkAs,
|
|
75
|
+
linkProps,
|
|
76
|
+
size,
|
|
77
|
+
theme,
|
|
78
|
+
thumbnailProps,
|
|
79
|
+
title,
|
|
80
|
+
titleHeading,
|
|
81
|
+
...forwardedProps
|
|
82
|
+
} = props;
|
|
83
|
+
// Use title heading as title wrapper (see DEFAULT_PROPS for the default value).
|
|
84
|
+
const TitleHeading = titleHeading as HeadingElement;
|
|
69
85
|
|
|
70
86
|
return (
|
|
71
|
-
<
|
|
87
|
+
<article
|
|
72
88
|
ref={ref}
|
|
73
89
|
{...forwardedProps}
|
|
74
90
|
className={classNames(
|
|
@@ -85,8 +101,14 @@ export const LinkPreview: Comp<LinkPreviewProps, HTMLDivElement> = forwardRef((p
|
|
|
85
101
|
<div className={`${CLASSNAME}__thumbnail`}>
|
|
86
102
|
<Thumbnail
|
|
87
103
|
{...thumbnailProps}
|
|
88
|
-
|
|
89
|
-
|
|
104
|
+
linkAs={linkAs}
|
|
105
|
+
linkProps={{
|
|
106
|
+
...linkProps,
|
|
107
|
+
href: link,
|
|
108
|
+
target: '_blank',
|
|
109
|
+
// Avoid redundant links in focus order
|
|
110
|
+
tabIndex: -1,
|
|
111
|
+
}}
|
|
90
112
|
aspectRatio={AspectRatio.free}
|
|
91
113
|
fillHeight
|
|
92
114
|
/>
|
|
@@ -95,9 +117,10 @@ export const LinkPreview: Comp<LinkPreviewProps, HTMLDivElement> = forwardRef((p
|
|
|
95
117
|
|
|
96
118
|
<div className={`${CLASSNAME}__container`}>
|
|
97
119
|
{title && (
|
|
98
|
-
<
|
|
120
|
+
<TitleHeading className={`${CLASSNAME}__title`}>
|
|
99
121
|
<Link
|
|
100
122
|
{...linkProps}
|
|
123
|
+
linkAs={linkAs}
|
|
101
124
|
target="_blank"
|
|
102
125
|
href={link}
|
|
103
126
|
color={theme === Theme.light ? ColorPalette.dark : ColorPalette.light}
|
|
@@ -105,25 +128,29 @@ export const LinkPreview: Comp<LinkPreviewProps, HTMLDivElement> = forwardRef((p
|
|
|
105
128
|
>
|
|
106
129
|
{title}
|
|
107
130
|
</Link>
|
|
108
|
-
</
|
|
131
|
+
</TitleHeading>
|
|
109
132
|
)}
|
|
133
|
+
|
|
110
134
|
{description && <p className={`${CLASSNAME}__description`}>{description}</p>}
|
|
111
135
|
|
|
112
136
|
<div className={`${CLASSNAME}__link`}>
|
|
113
137
|
<Link
|
|
114
138
|
{...linkProps}
|
|
139
|
+
linkAs={linkAs}
|
|
115
140
|
className={classNames(`${CLASSNAME}__link`, linkProps?.className)}
|
|
116
141
|
target="_blank"
|
|
117
142
|
href={link}
|
|
118
143
|
color={theme === Theme.light ? ColorPalette.primary : ColorPalette.light}
|
|
119
144
|
colorVariant={ColorVariant.N}
|
|
145
|
+
// Avoid redundant links in focus order
|
|
146
|
+
tabIndex={title ? '-1' : undefined}
|
|
120
147
|
>
|
|
121
148
|
{link}
|
|
122
149
|
</Link>
|
|
123
150
|
</div>
|
|
124
151
|
</div>
|
|
125
152
|
</div>
|
|
126
|
-
</
|
|
153
|
+
</article>
|
|
127
154
|
);
|
|
128
155
|
});
|
|
129
156
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { detectOverflow } from '@popperjs/core';
|
|
2
|
-
import React, { forwardRef, ReactNode, RefObject, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import React, { forwardRef, ReactNode, RefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
3
3
|
import { createPortal } from 'react-dom';
|
|
4
4
|
import { usePopper } from 'react-popper';
|
|
5
5
|
|
|
@@ -13,6 +13,7 @@ import { ClickAwayProvider } from '@lumx/react/utils/ClickAwayProvider';
|
|
|
13
13
|
import { Comp, GenericProps, getRootClassName, handleBasicClasses, ValueOf } from '@lumx/react/utils';
|
|
14
14
|
import { mergeRefs } from '@lumx/react/utils/mergeRefs';
|
|
15
15
|
import { useFocusWithin } from '@lumx/react/hooks/useFocusWithin';
|
|
16
|
+
import { getFirstAndLastFocusable } from '@lumx/react/utils/focus/getFirstAndLastFocusable';
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Different possible placements for the popover.
|
|
@@ -90,6 +91,8 @@ export interface PopoverProps extends GenericProps {
|
|
|
90
91
|
isOpen: boolean;
|
|
91
92
|
/** Offset placement relative to anchor. */
|
|
92
93
|
offset?: Offset;
|
|
94
|
+
/** Reference to the parent element that triggered the popover (will get back focus on close or else fallback on the anchor element). */
|
|
95
|
+
parentElement?: RefObject<HTMLElement>;
|
|
93
96
|
/** Placement relative to anchor. */
|
|
94
97
|
placement?: Placement;
|
|
95
98
|
/** Whether the popover should be rendered into a DOM node that exists outside the DOM hierarchy of the parent component. */
|
|
@@ -212,6 +215,7 @@ export const Popover: Comp<PopoverProps, HTMLDivElement> = forwardRef((props, re
|
|
|
212
215
|
isOpen,
|
|
213
216
|
offset,
|
|
214
217
|
onClose,
|
|
218
|
+
parentElement,
|
|
215
219
|
placement,
|
|
216
220
|
style,
|
|
217
221
|
usePortal,
|
|
@@ -245,7 +249,8 @@ export const Popover: Comp<PopoverProps, HTMLDivElement> = forwardRef((props, re
|
|
|
245
249
|
});
|
|
246
250
|
|
|
247
251
|
/** Action on close */
|
|
248
|
-
|
|
252
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
253
|
+
const handleClose = useCallback(() => {
|
|
249
254
|
if (!onClose) {
|
|
250
255
|
return;
|
|
251
256
|
}
|
|
@@ -256,11 +261,22 @@ export const Popover: Comp<PopoverProps, HTMLDivElement> = forwardRef((props, re
|
|
|
256
261
|
* unless specifically requested not to.
|
|
257
262
|
*/
|
|
258
263
|
if (isFocusedWithin.current && focusAnchorOnClose) {
|
|
259
|
-
|
|
264
|
+
if (parentElement?.current) {
|
|
265
|
+
parentElement?.current.focus();
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const firstFocusable = anchorRef?.current && getFirstAndLastFocusable(anchorRef?.current).first;
|
|
269
|
+
if (firstFocusable) {
|
|
270
|
+
// Focus the first focusable element in anchor.
|
|
271
|
+
firstFocusable.focus();
|
|
272
|
+
} else {
|
|
273
|
+
// Fallback on the anchor element.
|
|
274
|
+
anchorRef?.current?.focus();
|
|
275
|
+
}
|
|
260
276
|
}
|
|
261
277
|
|
|
262
278
|
onClose();
|
|
263
|
-
};
|
|
279
|
+
}, [anchorRef, focusAnchorOnClose, onClose, parentElement]);
|
|
264
280
|
|
|
265
281
|
const modifiers: any = [];
|
|
266
282
|
const actualOffset: [number, number] = [offset?.along ?? 0, (offset?.away ?? 0) + (hasArrow ? ARROW_SIZE : 0)];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { mdiBullhornOutline } from '@lumx/icons/';
|
|
1
2
|
import { List, ListItem, Select, Size, TextField } from '@lumx/react';
|
|
2
3
|
import { useBooleanState } from '@lumx/react/hooks/useBooleanState';
|
|
3
4
|
import { text } from '@storybook/addon-knobs';
|
|
@@ -32,6 +33,7 @@ export const SimpleSelect = ({ theme }: any) => {
|
|
|
32
33
|
theme={theme}
|
|
33
34
|
onInputClick={toggleSelect}
|
|
34
35
|
onDropdownClose={closeSelect}
|
|
36
|
+
icon={mdiBullhornOutline}
|
|
35
37
|
>
|
|
36
38
|
<List isClickable>
|
|
37
39
|
{CHOICES.length > 0
|
|
@@ -5,7 +5,7 @@ import lodashIsEmpty from 'lodash/isEmpty';
|
|
|
5
5
|
|
|
6
6
|
import { mdiAlertCircle, mdiCheckCircle, mdiCloseCircle, mdiMenuDown } from '@lumx/icons';
|
|
7
7
|
|
|
8
|
-
import { Emphasis, Size } from '@lumx/react/components';
|
|
8
|
+
import { Emphasis, Size, Theme } from '@lumx/react/components';
|
|
9
9
|
import { IconButton } from '@lumx/react/components/button/IconButton';
|
|
10
10
|
import { Chip } from '@lumx/react/components/chip/Chip';
|
|
11
11
|
import { Icon } from '@lumx/react/components/icon/Icon';
|
|
@@ -46,6 +46,7 @@ const SelectField: React.FC<SelectProps> = ({
|
|
|
46
46
|
handleKeyboardNav,
|
|
47
47
|
hasError,
|
|
48
48
|
hasInputClear,
|
|
49
|
+
icon,
|
|
49
50
|
id,
|
|
50
51
|
isDisabled,
|
|
51
52
|
isEmpty,
|
|
@@ -89,6 +90,15 @@ const SelectField: React.FC<SelectProps> = ({
|
|
|
89
90
|
aria-disabled={isDisabled || undefined}
|
|
90
91
|
{...forwardedProps}
|
|
91
92
|
>
|
|
93
|
+
{icon && (
|
|
94
|
+
<Icon
|
|
95
|
+
className={`${CLASSNAME}__input-icon`}
|
|
96
|
+
color={theme === Theme.dark ? 'light' : undefined}
|
|
97
|
+
icon={icon}
|
|
98
|
+
size={Size.xs}
|
|
99
|
+
/>
|
|
100
|
+
)}
|
|
101
|
+
|
|
92
102
|
<div
|
|
93
103
|
className={classNames([
|
|
94
104
|
`${CLASSNAME}__input-native`,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/* istanbul ignore file */
|
|
2
|
+
import { mdiTram } from '@lumx/icons/';
|
|
2
3
|
import { Chip, List, ListItem, SelectMultiple, Size } from '@lumx/react';
|
|
3
4
|
import { useBooleanState } from '@lumx/react/hooks/useBooleanState';
|
|
4
5
|
import noop from 'lodash/noop';
|
|
@@ -40,6 +41,7 @@ export const DefaultSelectMultiple = ({ theme }: any) => {
|
|
|
40
41
|
theme={theme}
|
|
41
42
|
onInputClick={toggleSelect}
|
|
42
43
|
onDropdownClose={closeSelect}
|
|
44
|
+
icon={mdiTram}
|
|
43
45
|
>
|
|
44
46
|
<List isClickable>
|
|
45
47
|
{CHOICES.length > 0
|
|
@@ -4,7 +4,7 @@ import classNames from 'classnames';
|
|
|
4
4
|
|
|
5
5
|
import { mdiAlertCircle, mdiCheckCircle, mdiClose, mdiCloseCircle, mdiMenuDown } from '@lumx/icons';
|
|
6
6
|
|
|
7
|
-
import { Size } from '@lumx/react/components';
|
|
7
|
+
import { Size, Theme } from '@lumx/react/components';
|
|
8
8
|
import { Chip } from '@lumx/react/components/chip/Chip';
|
|
9
9
|
import { Icon } from '@lumx/react/components/icon/Icon';
|
|
10
10
|
import { InputLabel } from '@lumx/react/components/input-label/InputLabel';
|
|
@@ -59,6 +59,7 @@ export const SelectMultipleField: React.FC<SelectMultipleProps> = ({
|
|
|
59
59
|
anchorRef,
|
|
60
60
|
handleKeyboardNav,
|
|
61
61
|
hasError,
|
|
62
|
+
icon,
|
|
62
63
|
id,
|
|
63
64
|
isDisabled,
|
|
64
65
|
isEmpty,
|
|
@@ -102,6 +103,15 @@ export const SelectMultipleField: React.FC<SelectMultipleProps> = ({
|
|
|
102
103
|
aria-disabled={isDisabled || undefined}
|
|
103
104
|
{...forwardedProps}
|
|
104
105
|
>
|
|
106
|
+
{icon && (
|
|
107
|
+
<Icon
|
|
108
|
+
className={`${CLASSNAME}__input-icon`}
|
|
109
|
+
color={theme === Theme.dark ? 'light' : undefined}
|
|
110
|
+
icon={icon}
|
|
111
|
+
size={Size.xs}
|
|
112
|
+
/>
|
|
113
|
+
)}
|
|
114
|
+
|
|
105
115
|
<div className={`${CLASSNAME}__chips`}>
|
|
106
116
|
{!isEmpty &&
|
|
107
117
|
value.map((val, index) => selectedChipRender?.(val, index, onClear, isDisabled, theme))}
|
|
@@ -21,6 +21,8 @@ export interface CoreSelectProps extends GenericProps {
|
|
|
21
21
|
helper?: string;
|
|
22
22
|
/** Whether the select should close on click. */
|
|
23
23
|
closeOnClick?: boolean;
|
|
24
|
+
/** Icon (SVG path). */
|
|
25
|
+
icon?: string;
|
|
24
26
|
/** Whether the component is disabled or not. */
|
|
25
27
|
isDisabled?: boolean;
|
|
26
28
|
/** Whether the component is required or not. */
|
|
@@ -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';
|
|
@@ -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,7 +53,7 @@ export interface TextFieldProps extends GenericProps {
|
|
|
53
53
|
/** Placeholder text. */
|
|
54
54
|
placeholder?: string;
|
|
55
55
|
/** Reference to the wrapper. */
|
|
56
|
-
textFieldRef?:
|
|
56
|
+
textFieldRef?: Ref<HTMLDivElement>;
|
|
57
57
|
/** Theme adapting the component to light or dark background. */
|
|
58
58
|
theme?: Theme;
|
|
59
59
|
/** Value. */
|
|
@@ -139,7 +139,7 @@ const useComputeNumberOfRows = (
|
|
|
139
139
|
|
|
140
140
|
interface InputNativeProps {
|
|
141
141
|
id?: string;
|
|
142
|
-
inputRef?:
|
|
142
|
+
inputRef?: TextFieldProps['inputRef'];
|
|
143
143
|
isDisabled?: boolean;
|
|
144
144
|
isRequired?: boolean;
|
|
145
145
|
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 } from '@lumx/react/utils';
|
|
17
17
|
|
|
18
18
|
import { mdiImageBroken } from '@lumx/icons';
|
|
19
19
|
import { mergeRefs } from '@lumx/react/utils/mergeRefs';
|
|
@@ -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. */
|