@lumx/react 2.2.20-alpha.xss.datatable → 2.2.20
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/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/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/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 +4 -4
- 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 +53 -13
- package/src/components/dialog/Dialog.tsx +1 -1
- package/src/components/dialog/__snapshots__/Dialog.test.tsx.snap +75 -14
- 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/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/makeListenerTowerContext.ts +32 -0
- package/src/utils/type.ts +3 -0
- package/types.d.ts +48 -5
- package/src/components/link-preview/__snapshots__/LinkPreview.test.tsx.snap +0 -51
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { mdiPencil } from '@lumx/icons';
|
|
3
|
+
import { GenericBlock, Button, Icon, Size, Orientation, Alignment } from '@lumx/react';
|
|
4
|
+
|
|
5
|
+
export default { title: 'LumX components/generic-block/GenericBlock' };
|
|
6
|
+
|
|
7
|
+
export const Horizontal = ({ theme }: any) => (
|
|
8
|
+
<GenericBlock
|
|
9
|
+
orientation={Orientation.horizontal}
|
|
10
|
+
figure={<Icon icon={mdiPencil} size={Size.m} />}
|
|
11
|
+
actionsProps={{
|
|
12
|
+
style: { border: '1px solid red' },
|
|
13
|
+
}}
|
|
14
|
+
figureProps={{
|
|
15
|
+
style: { border: '1px solid red' },
|
|
16
|
+
}}
|
|
17
|
+
contentProps={{
|
|
18
|
+
style: { border: '1px solid red' },
|
|
19
|
+
}}
|
|
20
|
+
actions={<Button theme={theme}>Button</Button>}
|
|
21
|
+
>
|
|
22
|
+
Content
|
|
23
|
+
</GenericBlock>
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
export const HorizontalWithAlignment = ({ theme }: any) => (
|
|
27
|
+
<GenericBlock
|
|
28
|
+
orientation={Orientation.horizontal}
|
|
29
|
+
figure={<Icon icon={mdiPencil} size={Size.m} />}
|
|
30
|
+
actionsProps={{
|
|
31
|
+
fillSpace: true,
|
|
32
|
+
style: { border: '1px solid red' },
|
|
33
|
+
vAlign: 'center',
|
|
34
|
+
}}
|
|
35
|
+
figureProps={{
|
|
36
|
+
style: { border: '1px solid red' },
|
|
37
|
+
}}
|
|
38
|
+
contentProps={{
|
|
39
|
+
style: { border: '1px solid red' },
|
|
40
|
+
}}
|
|
41
|
+
actions={<Button theme={theme}>Centered button</Button>}
|
|
42
|
+
>
|
|
43
|
+
Content
|
|
44
|
+
</GenericBlock>
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
export const HorizontalTop = ({ theme }: any) => (
|
|
48
|
+
<GenericBlock
|
|
49
|
+
orientation={Orientation.horizontal}
|
|
50
|
+
hAlign={Alignment.top}
|
|
51
|
+
figure={<Icon icon={mdiPencil} size={Size.m} />}
|
|
52
|
+
actionsProps={{
|
|
53
|
+
style: { border: '1px solid red' },
|
|
54
|
+
}}
|
|
55
|
+
figureProps={{
|
|
56
|
+
style: { border: '1px solid red' },
|
|
57
|
+
}}
|
|
58
|
+
contentProps={{
|
|
59
|
+
style: { border: '1px solid red' },
|
|
60
|
+
}}
|
|
61
|
+
actions={<Button theme={theme}>Centered button</Button>}
|
|
62
|
+
>
|
|
63
|
+
Content
|
|
64
|
+
</GenericBlock>
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
export const Vertical = ({ theme }: any) => (
|
|
68
|
+
<GenericBlock
|
|
69
|
+
orientation={Orientation.vertical}
|
|
70
|
+
figure={<Icon icon={mdiPencil} size={Size.m} />}
|
|
71
|
+
actionsProps={{
|
|
72
|
+
fillSpace: true,
|
|
73
|
+
style: { border: '1px solid red' },
|
|
74
|
+
}}
|
|
75
|
+
figureProps={{
|
|
76
|
+
style: { border: '1px solid red' },
|
|
77
|
+
}}
|
|
78
|
+
contentProps={{
|
|
79
|
+
style: { border: '1px solid red' },
|
|
80
|
+
}}
|
|
81
|
+
actions={<Button theme={theme}>Button</Button>}
|
|
82
|
+
>
|
|
83
|
+
Content
|
|
84
|
+
</GenericBlock>
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
export const GapSizes = ({ theme }: any) => (
|
|
88
|
+
<>
|
|
89
|
+
<GenericBlock
|
|
90
|
+
orientation={Orientation.vertical}
|
|
91
|
+
figure={<Icon icon={mdiPencil} size={Size.m} />}
|
|
92
|
+
gap={Size.regular}
|
|
93
|
+
style={{ marginBottom: 40 }}
|
|
94
|
+
actionsProps={{
|
|
95
|
+
style: { border: '1px solid red' },
|
|
96
|
+
}}
|
|
97
|
+
figureProps={{
|
|
98
|
+
style: { border: '1px solid red' },
|
|
99
|
+
}}
|
|
100
|
+
contentProps={{
|
|
101
|
+
style: { border: '1px solid red' },
|
|
102
|
+
}}
|
|
103
|
+
actions={<Button theme={theme}>Button</Button>}
|
|
104
|
+
>
|
|
105
|
+
<h2>Small gap size</h2>
|
|
106
|
+
<p>For small blocks</p>
|
|
107
|
+
</GenericBlock>
|
|
108
|
+
|
|
109
|
+
<GenericBlock
|
|
110
|
+
orientation={Orientation.vertical}
|
|
111
|
+
figure={<Icon icon={mdiPencil} size={Size.m} />}
|
|
112
|
+
gap={Size.big}
|
|
113
|
+
style={{ marginBottom: 40 }}
|
|
114
|
+
actionsProps={{
|
|
115
|
+
style: { border: '1px solid red' },
|
|
116
|
+
}}
|
|
117
|
+
figureProps={{
|
|
118
|
+
style: { border: '1px solid red' },
|
|
119
|
+
}}
|
|
120
|
+
contentProps={{
|
|
121
|
+
style: { border: '1px solid red' },
|
|
122
|
+
}}
|
|
123
|
+
actions={<Button theme={theme}>Button</Button>}
|
|
124
|
+
>
|
|
125
|
+
<h2>Medium gap size</h2>
|
|
126
|
+
<p>For medium blocks</p>
|
|
127
|
+
</GenericBlock>
|
|
128
|
+
|
|
129
|
+
<GenericBlock
|
|
130
|
+
orientation={Orientation.vertical}
|
|
131
|
+
figure={<Icon icon={mdiPencil} size={Size.m} />}
|
|
132
|
+
gap={Size.huge}
|
|
133
|
+
style={{ marginBottom: 40 }}
|
|
134
|
+
actionsProps={{
|
|
135
|
+
style: { border: '1px solid red' },
|
|
136
|
+
}}
|
|
137
|
+
figureProps={{
|
|
138
|
+
style: { border: '1px solid red' },
|
|
139
|
+
}}
|
|
140
|
+
contentProps={{
|
|
141
|
+
style: { border: '1px solid red' },
|
|
142
|
+
}}
|
|
143
|
+
actions={<Button theme={theme}>Button</Button>}
|
|
144
|
+
>
|
|
145
|
+
<h2>Big gap size</h2>
|
|
146
|
+
<p>For large blocks</p>
|
|
147
|
+
</GenericBlock>
|
|
148
|
+
</>
|
|
149
|
+
);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { mount, shallow } from 'enzyme';
|
|
3
|
+
import 'jest-enzyme';
|
|
4
|
+
import { commonTestsSuite, itShouldRenderStories } from '@lumx/react/testing/utils';
|
|
5
|
+
|
|
6
|
+
import { GenericBlock, GenericBlockProps } from './GenericBlock';
|
|
7
|
+
import * as stories from '../../stories/generated/GenericBlock/Demos.stories';
|
|
8
|
+
|
|
9
|
+
const CLASSNAME = GenericBlock.className as string;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Mounts the component and returns common DOM elements / data needed in multiple tests further down.
|
|
13
|
+
*/
|
|
14
|
+
const setup = (props: Partial<GenericBlockProps> = {}, shallowRendering = true) => {
|
|
15
|
+
const renderer: any = shallowRendering ? shallow : mount;
|
|
16
|
+
const wrapper: any = renderer(<GenericBlock {...(props as any)} />);
|
|
17
|
+
return { props, wrapper };
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
describe(`<${GenericBlock.displayName}>`, () => {
|
|
21
|
+
// 1. Test render via snapshot.
|
|
22
|
+
describe('Snapshots and structure', () => {
|
|
23
|
+
itShouldRenderStories(stories, GenericBlock);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Common tests suite.
|
|
27
|
+
commonTestsSuite(setup, { className: 'wrapper', prop: 'wrapper' }, { className: CLASSNAME });
|
|
28
|
+
});
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import React, { forwardRef, ReactNode } from 'react';
|
|
2
|
+
import classNames from 'classnames';
|
|
3
|
+
import { Comp, getRootClassName } from '@lumx/react/utils';
|
|
4
|
+
import { Orientation, Size, FlexBox, FlexBoxProps, Alignment, HorizontalAlignment } from '@lumx/react';
|
|
5
|
+
|
|
6
|
+
export interface GenericBlockProps extends FlexBoxProps {
|
|
7
|
+
/** Component to use as visual element. */
|
|
8
|
+
figure?: ReactNode;
|
|
9
|
+
/** Actions to set after the main content. */
|
|
10
|
+
actions?: ReactNode;
|
|
11
|
+
/** Main content to display */
|
|
12
|
+
children: ReactNode;
|
|
13
|
+
/** Orientation of the 3 sections */
|
|
14
|
+
orientation?: FlexBoxProps['orientation'];
|
|
15
|
+
/** Horizontal alignment. */
|
|
16
|
+
hAlign?: FlexBoxProps['hAlign'];
|
|
17
|
+
/** Vertical alignment. */
|
|
18
|
+
vAlign?: FlexBoxProps['vAlign'];
|
|
19
|
+
/**
|
|
20
|
+
* The props to forward to the content.
|
|
21
|
+
* By default, the content will have the same alignment as wrapper.
|
|
22
|
+
*/
|
|
23
|
+
contentProps?: Omit<FlexBoxProps, 'children'>;
|
|
24
|
+
/** props to forward to the actions element. */
|
|
25
|
+
actionsProps?: Omit<FlexBoxProps, 'children'>;
|
|
26
|
+
/** props to forward to the figure element. */
|
|
27
|
+
figureProps?: Omit<FlexBoxProps, 'children'>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Component display name.
|
|
32
|
+
*/
|
|
33
|
+
const COMPONENT_NAME = 'GenericBlock';
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Component default class name and class prefix.
|
|
37
|
+
*/
|
|
38
|
+
const CLASSNAME = getRootClassName(COMPONENT_NAME);
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Component default props.
|
|
42
|
+
*/
|
|
43
|
+
const DEFAULT_PROPS: Partial<GenericBlockProps> = {
|
|
44
|
+
gap: Size.regular,
|
|
45
|
+
orientation: Orientation.vertical,
|
|
46
|
+
hAlign: Alignment.center,
|
|
47
|
+
vAlign: Alignment.center,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The GenericBlock is a layout component made of 3 sections that can be
|
|
52
|
+
* displayed either horizontally of vertically with the same gap between each section.
|
|
53
|
+
*
|
|
54
|
+
* The sections are:
|
|
55
|
+
* * (Optional) `Figure` => A visual element to display before the main content.
|
|
56
|
+
* * (Required) `Content` => The main content displayed
|
|
57
|
+
* * (Optional) `Actions` => One or more actions to set after the element.
|
|
58
|
+
*
|
|
59
|
+
* @see https://www.figma.com/file/lzzrQmsfaXRaOyRfoEogPZ/DS%3A-playground?node-id=1%3A4076
|
|
60
|
+
*/
|
|
61
|
+
export const GenericBlock: Comp<GenericBlockProps, HTMLDivElement> = forwardRef((props, ref) => {
|
|
62
|
+
const {
|
|
63
|
+
className,
|
|
64
|
+
figure,
|
|
65
|
+
figureProps,
|
|
66
|
+
children,
|
|
67
|
+
actions,
|
|
68
|
+
actionsProps,
|
|
69
|
+
gap,
|
|
70
|
+
orientation,
|
|
71
|
+
contentProps,
|
|
72
|
+
...forwardedProps
|
|
73
|
+
} = props;
|
|
74
|
+
|
|
75
|
+
let actionsVAlign: HorizontalAlignment = Alignment.center;
|
|
76
|
+
if (orientation === Orientation.horizontal) {
|
|
77
|
+
actionsVAlign = Alignment.right;
|
|
78
|
+
}
|
|
79
|
+
let contentVAlign: HorizontalAlignment = Alignment.center;
|
|
80
|
+
if (orientation === Orientation.horizontal) {
|
|
81
|
+
contentVAlign = Alignment.left;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<FlexBox
|
|
86
|
+
ref={ref}
|
|
87
|
+
className={classNames(className, CLASSNAME)}
|
|
88
|
+
gap={gap}
|
|
89
|
+
orientation={orientation}
|
|
90
|
+
{...forwardedProps}
|
|
91
|
+
>
|
|
92
|
+
<FlexBox {...figureProps} className={classNames(figureProps?.className, `${CLASSNAME}__figure`)}>
|
|
93
|
+
{figure}
|
|
94
|
+
</FlexBox>
|
|
95
|
+
|
|
96
|
+
{children && (
|
|
97
|
+
<FlexBox
|
|
98
|
+
orientation={Orientation.vertical}
|
|
99
|
+
fillSpace
|
|
100
|
+
vAlign={contentVAlign}
|
|
101
|
+
{...contentProps}
|
|
102
|
+
className={classNames(contentProps?.className, `${CLASSNAME}__content`)}
|
|
103
|
+
>
|
|
104
|
+
{children}
|
|
105
|
+
</FlexBox>
|
|
106
|
+
)}
|
|
107
|
+
|
|
108
|
+
<FlexBox
|
|
109
|
+
vAlign={actionsVAlign}
|
|
110
|
+
{...actionsProps}
|
|
111
|
+
className={classNames(actionsProps?.className, `${CLASSNAME}__actions`)}
|
|
112
|
+
>
|
|
113
|
+
{actions}
|
|
114
|
+
</FlexBox>
|
|
115
|
+
</FlexBox>
|
|
116
|
+
);
|
|
117
|
+
});
|
|
118
|
+
GenericBlock.displayName = COMPONENT_NAME;
|
|
119
|
+
GenericBlock.className = CLASSNAME;
|
|
120
|
+
GenericBlock.defaultProps = DEFAULT_PROPS;
|
|
@@ -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
|
|