@lumx/react 2.1.1 → 2.1.5
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/README.md +1 -1
- package/esm/_internal/Avatar2.js +5 -1
- package/esm/_internal/Avatar2.js.map +1 -1
- package/esm/_internal/Button2.js.map +1 -1
- package/esm/_internal/ButtonRoot.js +14 -4
- package/esm/_internal/ButtonRoot.js.map +1 -1
- package/esm/_internal/ClickAwayProvider.js +1 -1
- package/esm/_internal/Dialog2.js +13 -8
- package/esm/_internal/Dialog2.js.map +1 -1
- package/esm/_internal/DragHandle.js +1 -1
- package/esm/_internal/DragHandle.js.map +1 -1
- package/esm/_internal/Flag2.js +1 -3
- package/esm/_internal/Flag2.js.map +1 -1
- package/esm/_internal/IconButton.js +0 -2
- package/esm/_internal/IconButton.js.map +1 -1
- package/esm/_internal/List2.js +16 -9
- package/esm/_internal/List2.js.map +1 -1
- package/esm/_internal/Message2.js +2 -2
- package/esm/_internal/Message2.js.map +1 -1
- package/esm/_internal/SlideshowControls.js +3 -3
- package/esm/_internal/SlideshowControls.js.map +1 -1
- package/esm/_internal/TextField.js +5 -2
- package/esm/_internal/TextField.js.map +1 -1
- package/esm/_internal/Thumbnail2.js +29 -34
- package/esm/_internal/Thumbnail2.js.map +1 -1
- package/esm/_internal/Tooltip2.js +1 -1
- package/esm/_internal/UserBlock.js +44 -14
- package/esm/_internal/UserBlock.js.map +1 -1
- package/esm/_internal/getRootClassName.js +17 -1
- package/esm/_internal/getRootClassName.js.map +1 -1
- package/esm/_internal/user-block.js +1 -0
- package/esm/_internal/user-block.js.map +1 -1
- package/package.json +4 -8
- package/src/components/avatar/Avatar.stories.tsx +1 -1
- package/src/components/avatar/Avatar.tsx +8 -0
- package/src/components/button/Button.stories.tsx +85 -15
- package/src/components/button/Button.tsx +2 -0
- package/src/components/button/ButtonRoot.test.tsx +13 -0
- package/src/components/button/ButtonRoot.tsx +10 -1
- package/src/components/button/IconButton.test.tsx +9 -0
- package/src/components/button/IconButton.tsx +11 -26
- package/src/components/button/__snapshots__/ButtonRoot.test.tsx.snap +13 -0
- package/src/components/button/__snapshots__/IconButton.test.tsx.snap +19 -0
- package/src/components/comment-block/CommentBlock.stories.tsx +1 -1
- package/src/components/dialog/Dialog.stories.tsx +45 -3
- package/src/components/dialog/Dialog.tsx +15 -11
- package/src/components/dialog/__snapshots__/Dialog.test.tsx.snap +76 -0
- package/src/components/drag-handle/DragHandle.tsx +5 -1
- package/src/components/flag/Flag.test.tsx +1 -2
- package/src/components/flag/Flag.tsx +2 -10
- package/src/components/flag/__snapshots__/Flag.test.tsx.snap +0 -15
- package/src/components/image-block/ImageBlock.stories.tsx +1 -1
- package/src/components/link-preview/LinkPreview.stories.tsx +1 -1
- package/src/components/list/List.stories.tsx +7 -1
- package/src/components/list/ListItem.stories.tsx +28 -3
- package/src/components/list/ListItem.tsx +25 -7
- package/src/components/list/__snapshots__/List.test.tsx.snap +23 -3
- package/src/components/list/__snapshots__/ListItem.test.tsx.snap +84 -11
- package/src/components/list/useInteractiveList.tsx +1 -1
- package/src/components/message/Message.tsx +2 -2
- package/src/components/skeleton/SkeletonRectangle.stories.tsx +1 -1
- package/src/components/slideshow/useKeyNavigate.ts +2 -2
- package/src/components/text-field/TextField.stories.tsx +97 -82
- package/src/components/text-field/TextField.tsx +5 -0
- package/src/components/thumbnail/Thumbnail.stories.tsx +22 -1
- package/src/components/thumbnail/Thumbnail.test.tsx +20 -2
- package/src/components/thumbnail/Thumbnail.tsx +40 -15
- package/src/components/thumbnail/__snapshots__/Thumbnail.test.tsx.snap +53 -6
- package/src/components/user-block/UserBlock.stories.tsx +28 -5
- package/src/components/user-block/UserBlock.tsx +40 -16
- package/src/components/user-block/__snapshots__/UserBlock.test.tsx.snap +244 -145
- package/src/stories/generated/Button/Demos.stories.tsx +1 -0
- package/src/stories/knobs/buttonKnob.ts +9 -0
- package/src/stories/knobs/emphasisKnob.ts +8 -0
- package/src/utils/MaterialThemeSwitcher/MaterialThemeSwitcher.tsx +54 -0
- package/src/utils/MaterialThemeSwitcher/index.ts +1 -0
- package/src/stories/knobs/index.ts +0 -2
- package/types.d.ts +0 -2561
|
@@ -1,18 +1,41 @@
|
|
|
1
1
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
2
|
|
|
3
|
-
exports[`<ListItem> Snapshots and structure should render story '
|
|
3
|
+
exports[`<ListItem> Snapshots and structure should render story 'Button' 1`] = `
|
|
4
4
|
<li
|
|
5
5
|
className="lumx-list-item lumx-list-item--size-regular"
|
|
6
6
|
>
|
|
7
|
-
<
|
|
8
|
-
className="lumx-list-
|
|
7
|
+
<a
|
|
8
|
+
className="lumx-list-item__link"
|
|
9
|
+
onClick={[Function]}
|
|
10
|
+
onKeyDown={[Function]}
|
|
11
|
+
role="button"
|
|
12
|
+
tabIndex={0}
|
|
9
13
|
>
|
|
10
14
|
<div
|
|
11
15
|
className="lumx-list-item__content"
|
|
12
16
|
>
|
|
13
17
|
Text
|
|
14
18
|
</div>
|
|
15
|
-
</
|
|
19
|
+
</a>
|
|
20
|
+
</li>
|
|
21
|
+
`;
|
|
22
|
+
|
|
23
|
+
exports[`<ListItem> Snapshots and structure should render story 'ButtonDisabled' 1`] = `
|
|
24
|
+
<li
|
|
25
|
+
className="lumx-list-item lumx-list-item--size-regular"
|
|
26
|
+
>
|
|
27
|
+
<a
|
|
28
|
+
aria-disabled={true}
|
|
29
|
+
className="lumx-list-item__link lumx-list-item__link--is-disabled"
|
|
30
|
+
onKeyDown={[Function]}
|
|
31
|
+
role="button"
|
|
32
|
+
>
|
|
33
|
+
<div
|
|
34
|
+
className="lumx-list-item__content"
|
|
35
|
+
>
|
|
36
|
+
Text
|
|
37
|
+
</div>
|
|
38
|
+
</a>
|
|
16
39
|
</li>
|
|
17
40
|
`;
|
|
18
41
|
|
|
@@ -23,7 +46,7 @@ exports[`<ListItem> Snapshots and structure should render story 'Highlighted' 1`
|
|
|
23
46
|
<a
|
|
24
47
|
className="lumx-list-item__link lumx-list-item__link--is-highlighted"
|
|
25
48
|
href="#"
|
|
26
|
-
|
|
49
|
+
role="link"
|
|
27
50
|
>
|
|
28
51
|
<div
|
|
29
52
|
className="lumx-list-item__content"
|
|
@@ -34,6 +57,58 @@ exports[`<ListItem> Snapshots and structure should render story 'Highlighted' 1`
|
|
|
34
57
|
</li>
|
|
35
58
|
`;
|
|
36
59
|
|
|
60
|
+
exports[`<ListItem> Snapshots and structure should render story 'Link' 1`] = `
|
|
61
|
+
<li
|
|
62
|
+
className="lumx-list-item lumx-list-item--size-regular"
|
|
63
|
+
>
|
|
64
|
+
<a
|
|
65
|
+
className="lumx-list-item__link"
|
|
66
|
+
href="#"
|
|
67
|
+
role="link"
|
|
68
|
+
>
|
|
69
|
+
<div
|
|
70
|
+
className="lumx-list-item__content"
|
|
71
|
+
>
|
|
72
|
+
Text
|
|
73
|
+
</div>
|
|
74
|
+
</a>
|
|
75
|
+
</li>
|
|
76
|
+
`;
|
|
77
|
+
|
|
78
|
+
exports[`<ListItem> Snapshots and structure should render story 'LinkDisabled' 1`] = `
|
|
79
|
+
<li
|
|
80
|
+
className="lumx-list-item lumx-list-item--size-regular"
|
|
81
|
+
>
|
|
82
|
+
<a
|
|
83
|
+
aria-disabled={true}
|
|
84
|
+
className="lumx-list-item__link lumx-list-item__link--is-disabled"
|
|
85
|
+
role="link"
|
|
86
|
+
>
|
|
87
|
+
<div
|
|
88
|
+
className="lumx-list-item__content"
|
|
89
|
+
>
|
|
90
|
+
Text
|
|
91
|
+
</div>
|
|
92
|
+
</a>
|
|
93
|
+
</li>
|
|
94
|
+
`;
|
|
95
|
+
|
|
96
|
+
exports[`<ListItem> Snapshots and structure should render story 'NonClickable' 1`] = `
|
|
97
|
+
<li
|
|
98
|
+
className="lumx-list-item lumx-list-item--size-regular"
|
|
99
|
+
>
|
|
100
|
+
<div
|
|
101
|
+
className="lumx-list-item__wrapper"
|
|
102
|
+
>
|
|
103
|
+
<div
|
|
104
|
+
className="lumx-list-item__content"
|
|
105
|
+
>
|
|
106
|
+
Text
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
</li>
|
|
110
|
+
`;
|
|
111
|
+
|
|
37
112
|
exports[`<ListItem> Snapshots and structure should render story 'Selected' 1`] = `
|
|
38
113
|
<li
|
|
39
114
|
className="lumx-list-item lumx-list-item--size-regular"
|
|
@@ -41,7 +116,7 @@ exports[`<ListItem> Snapshots and structure should render story 'Selected' 1`] =
|
|
|
41
116
|
<a
|
|
42
117
|
className="lumx-list-item__link lumx-list-item__link--is-selected"
|
|
43
118
|
href="#"
|
|
44
|
-
|
|
119
|
+
role="link"
|
|
45
120
|
>
|
|
46
121
|
<div
|
|
47
122
|
className="lumx-list-item__content"
|
|
@@ -72,16 +147,14 @@ exports[`<ListItem> Snapshots and structure should render story 'WithCustomLink'
|
|
|
72
147
|
<li
|
|
73
148
|
className="lumx-list-item lumx-list-item--size-regular"
|
|
74
149
|
>
|
|
75
|
-
<
|
|
76
|
-
className="lumx-list-
|
|
77
|
-
href="http://google.com"
|
|
78
|
-
tabIndex={0}
|
|
150
|
+
<div
|
|
151
|
+
className="lumx-list-item__wrapper"
|
|
79
152
|
>
|
|
80
153
|
<div
|
|
81
154
|
className="lumx-list-item__content"
|
|
82
155
|
>
|
|
83
156
|
My custom link
|
|
84
157
|
</div>
|
|
85
|
-
</
|
|
158
|
+
</div>
|
|
86
159
|
</li>
|
|
87
160
|
`;
|
|
@@ -75,7 +75,7 @@ function onKeyboardFocus(props: any, handler: (evt: FocusEvent) => void) {
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
const isNavigableItem = (node: ReactNode): node is ReactElement => {
|
|
78
|
-
return isComponent('ListItem')(node) && isClickable(node.props);
|
|
78
|
+
return isComponent('ListItem')(node) && isClickable(node.props) && !node.props.isDisabled;
|
|
79
79
|
};
|
|
80
80
|
|
|
81
81
|
/**
|
|
@@ -62,8 +62,8 @@ export const Message: Comp<MessageProps, HTMLDivElement> = forwardRef((props, re
|
|
|
62
62
|
)}
|
|
63
63
|
{...forwardedProps}
|
|
64
64
|
>
|
|
65
|
-
{(customIcon || icon) && <Icon className=
|
|
66
|
-
<div className=
|
|
65
|
+
{(customIcon || icon) && <Icon className={`${CLASSNAME}__icon`} icon={customIcon || icon} size={Size.xs} />}
|
|
66
|
+
<div className={`${CLASSNAME}__text`}>{children}</div>
|
|
67
67
|
</div>
|
|
68
68
|
);
|
|
69
69
|
});
|
|
@@ -9,9 +9,9 @@ export function useKeyNavigate(element?: HTMLElement | null, onNext?: () => void
|
|
|
9
9
|
const onKeyNavigate = (evt: KeyboardEvent) => {
|
|
10
10
|
let callback;
|
|
11
11
|
if (evt?.key === 'ArrowRight') {
|
|
12
|
-
callback = onPrevious;
|
|
13
|
-
} else if (evt?.key === 'ArrowLeft') {
|
|
14
12
|
callback = onNext;
|
|
13
|
+
} else if (evt?.key === 'ArrowLeft') {
|
|
14
|
+
callback = onPrevious;
|
|
15
15
|
}
|
|
16
16
|
if (!callback) return;
|
|
17
17
|
|
|
@@ -1,90 +1,87 @@
|
|
|
1
|
-
import { TextField } from '@lumx/react';
|
|
2
|
-
import { text } from '@storybook/addon-knobs';
|
|
3
|
-
import noop from 'lodash/noop';
|
|
4
1
|
import React from 'react';
|
|
2
|
+
import { mdiTranslate } from '@lumx/icons/';
|
|
3
|
+
import { Emphasis, IconButton, Size, TextField } from '@lumx/react';
|
|
4
|
+
import { boolean, number, text } from '@storybook/addon-knobs';
|
|
5
|
+
import { buttonSize } from '@lumx/react/stories/knobs/buttonKnob';
|
|
6
|
+
import { emphasis } from '@lumx/react/stories/knobs/emphasisKnob';
|
|
5
7
|
|
|
6
8
|
export default { title: 'LumX components/text-field/TextField' };
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
);
|
|
10
|
+
export const TextField_ = ({ theme }: any) => {
|
|
11
|
+
const [value, onChange] = React.useState('Value');
|
|
12
|
+
return (
|
|
13
|
+
<TextField
|
|
14
|
+
value={value}
|
|
15
|
+
onChange={onChange}
|
|
16
|
+
label={text('Label', 'Label')}
|
|
17
|
+
placeholder={text('Placeholder', 'Placeholder')}
|
|
18
|
+
theme={theme}
|
|
19
|
+
/>
|
|
20
|
+
);
|
|
21
|
+
};
|
|
21
22
|
|
|
22
|
-
export const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
23
|
+
export const Clearable = ({ theme }: any) => {
|
|
24
|
+
const [value, onChange] = React.useState('Value');
|
|
25
|
+
return (
|
|
26
|
+
<TextField
|
|
27
|
+
value={value}
|
|
28
|
+
onChange={onChange}
|
|
29
|
+
label={text('Label', 'Label')}
|
|
30
|
+
clearButtonProps={{ label: 'Clear' }}
|
|
31
|
+
theme={theme}
|
|
32
|
+
/>
|
|
33
|
+
);
|
|
34
|
+
};
|
|
32
35
|
|
|
33
|
-
export const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
36
|
+
export const States = ({ theme }: any) => {
|
|
37
|
+
const [value1, onChange1] = React.useState('Value');
|
|
38
|
+
const [value2, onChange2] = React.useState('Value');
|
|
39
|
+
return (
|
|
40
|
+
<>
|
|
41
|
+
<TextField
|
|
42
|
+
label="Has error"
|
|
43
|
+
hasError
|
|
44
|
+
error="Error message"
|
|
45
|
+
theme={theme}
|
|
46
|
+
value={value1}
|
|
47
|
+
onChange={onChange1}
|
|
48
|
+
/>
|
|
49
|
+
<TextField label="Is valid" isValid theme={theme} value={value2} onChange={onChange2} />
|
|
50
|
+
</>
|
|
51
|
+
);
|
|
52
|
+
};
|
|
43
53
|
|
|
44
|
-
export const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
placeholder={text('Placeholder', 'ex: A value')}
|
|
49
|
-
theme={theme}
|
|
50
|
-
onChange={noop}
|
|
51
|
-
helper={<span>{text('Helper', 'ex: toto@acme.com')}</span>}
|
|
52
|
-
hasError
|
|
53
|
-
error={
|
|
54
|
-
<span>
|
|
55
|
-
You must provide <strong>something</strong>
|
|
56
|
-
</span>
|
|
57
|
-
}
|
|
58
|
-
/>
|
|
59
|
-
);
|
|
54
|
+
export const NumberField = ({ theme }: any) => {
|
|
55
|
+
const [value, onChange] = React.useState('0');
|
|
56
|
+
return <TextField value={value} onChange={onChange} label={text('Label', 'Label')} theme={theme} type="number" />;
|
|
57
|
+
};
|
|
60
58
|
|
|
61
|
-
export const
|
|
62
|
-
const [value,
|
|
59
|
+
export const WithHelper = ({ theme }: any) => {
|
|
60
|
+
const [value, onChange] = React.useState('Value');
|
|
63
61
|
return (
|
|
64
62
|
<TextField
|
|
65
63
|
value={value}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
minimumRows={1}
|
|
64
|
+
onChange={onChange}
|
|
65
|
+
label={text('Label', 'Label')}
|
|
66
|
+
placeholder={text('Placeholder', 'Placeholder')}
|
|
70
67
|
theme={theme}
|
|
71
|
-
|
|
72
|
-
helper={<span>{text('Helper', 'ex: toto@acme.com')}</span>}
|
|
68
|
+
helper={<span>{text('Helper', 'Helper')}</span>}
|
|
73
69
|
/>
|
|
74
70
|
);
|
|
75
71
|
};
|
|
76
|
-
|
|
77
|
-
|
|
72
|
+
|
|
73
|
+
export const TextArea = ({ theme }: any) => {
|
|
74
|
+
const [value, setValue] = React.useState('Value');
|
|
78
75
|
return (
|
|
79
76
|
<TextField
|
|
80
77
|
value={value}
|
|
81
|
-
label={text('Label', '
|
|
82
|
-
placeholder={text('Placeholder', '
|
|
78
|
+
label={text('Label', 'Label')}
|
|
79
|
+
placeholder={text('Placeholder', 'Placeholder')}
|
|
83
80
|
multiline
|
|
84
|
-
minimumRows={
|
|
81
|
+
minimumRows={number('Minimum number of rows', 1, { min: 0, max: 100 })}
|
|
85
82
|
theme={theme}
|
|
86
83
|
onChange={setValue}
|
|
87
|
-
helper={<span>{text('Helper', '
|
|
84
|
+
helper={<span>{text('Helper', 'Helper')}</span>}
|
|
88
85
|
/>
|
|
89
86
|
);
|
|
90
87
|
};
|
|
@@ -99,26 +96,44 @@ text`;
|
|
|
99
96
|
return (
|
|
100
97
|
<TextField
|
|
101
98
|
value={value}
|
|
102
|
-
label={text('Label', '
|
|
103
|
-
placeholder={text('Placeholder', '
|
|
99
|
+
label={text('Label', 'Label')}
|
|
100
|
+
placeholder={text('Placeholder', 'Placeholder')}
|
|
104
101
|
multiline
|
|
105
|
-
minimumRows={2}
|
|
102
|
+
minimumRows={number('Minimum number of rows', 2, { min: 0, max: 100 })}
|
|
106
103
|
theme={theme}
|
|
107
104
|
onChange={setValue}
|
|
108
|
-
helper={<span>{text('Helper', '
|
|
105
|
+
helper={<span>{text('Helper', 'Helper')}</span>}
|
|
109
106
|
/>
|
|
110
107
|
);
|
|
111
108
|
};
|
|
112
109
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
110
|
+
export const WithAfterElement = ({ theme }: any) => {
|
|
111
|
+
const [value, onChange] = React.useState('Value');
|
|
112
|
+
const multiline = boolean('Multiline', true);
|
|
113
|
+
const minimumRows = number('Minimum number of rows', 2, { min: 0, max: 100 });
|
|
114
|
+
const isClearable = boolean('Clearable', true);
|
|
115
|
+
const hasError = boolean('Has error', true);
|
|
116
|
+
return (
|
|
117
|
+
<TextField
|
|
118
|
+
value={value}
|
|
119
|
+
label={text('Label', 'Label')}
|
|
120
|
+
placeholder={text('Placeholder', 'Placeholder')}
|
|
121
|
+
theme={theme}
|
|
122
|
+
onChange={onChange}
|
|
123
|
+
multiline={multiline}
|
|
124
|
+
minimumRows={minimumRows}
|
|
125
|
+
hasError={hasError}
|
|
126
|
+
maxLength={200}
|
|
127
|
+
clearButtonProps={isClearable ? { label: 'Clear' } : undefined}
|
|
128
|
+
helper={<span>{text('Helper', 'Helper')}</span>}
|
|
129
|
+
afterElement={
|
|
130
|
+
<IconButton
|
|
131
|
+
label="foo"
|
|
132
|
+
emphasis={emphasis('Button emphasis', Emphasis.medium, 'After element')}
|
|
133
|
+
size={buttonSize('Button size', Size.s, 'After element')}
|
|
134
|
+
icon={mdiTranslate}
|
|
135
|
+
/>
|
|
136
|
+
}
|
|
137
|
+
/>
|
|
138
|
+
);
|
|
139
|
+
};
|
|
@@ -24,6 +24,8 @@ export interface TextFieldProps extends GenericProps {
|
|
|
24
24
|
forceFocusStyle?: boolean;
|
|
25
25
|
/** Whether the text field is displayed with error style or not. */
|
|
26
26
|
hasError?: boolean;
|
|
27
|
+
/** Additional element to put at the end of the text field. */
|
|
28
|
+
afterElement?: ReactNode;
|
|
27
29
|
/** Helper text. */
|
|
28
30
|
helper?: string | ReactNode;
|
|
29
31
|
/** Icon (SVG path). */
|
|
@@ -259,6 +261,7 @@ export const TextField: Comp<TextFieldProps, HTMLDivElement> = forwardRef((props
|
|
|
259
261
|
theme,
|
|
260
262
|
type,
|
|
261
263
|
value,
|
|
264
|
+
afterElement,
|
|
262
265
|
...forwardedProps
|
|
263
266
|
} = props;
|
|
264
267
|
const textFieldId = useMemo(() => id || `text-field-${uid()}`, [id]);
|
|
@@ -405,6 +408,8 @@ export const TextField: Comp<TextFieldProps, HTMLDivElement> = forwardRef((props
|
|
|
405
408
|
type="button"
|
|
406
409
|
/>
|
|
407
410
|
)}
|
|
411
|
+
|
|
412
|
+
{afterElement && <div className={`${CLASSNAME}__after-element`}>{afterElement}</div>}
|
|
408
413
|
</div>
|
|
409
414
|
|
|
410
415
|
{hasError && error && (
|
|
@@ -12,12 +12,13 @@ import {
|
|
|
12
12
|
Thumbnail,
|
|
13
13
|
ThumbnailVariant,
|
|
14
14
|
} from '@lumx/react';
|
|
15
|
-
import { imageKnob, IMAGES } from '@lumx/react/stories/knobs';
|
|
15
|
+
import { imageKnob, IMAGES } from '@lumx/react/stories/knobs/image';
|
|
16
16
|
import { htmlDecode } from '@lumx/react/utils/htmlDecode';
|
|
17
17
|
import { boolean, select, text } from '@storybook/addon-knobs';
|
|
18
18
|
import { enumKnob } from '@lumx/react/stories/knobs/enumKnob';
|
|
19
19
|
import { focusKnob } from '@lumx/react/stories/knobs/focusKnob';
|
|
20
20
|
import { sizeKnob } from '@lumx/react/stories/knobs/sizeKnob';
|
|
21
|
+
import classNames from 'classnames';
|
|
21
22
|
|
|
22
23
|
export default { title: 'LumX components/thumbnail/Thumbnail' };
|
|
23
24
|
|
|
@@ -25,6 +26,26 @@ export const Default = () => <Thumbnail alt="Image alt text" image={imageKnob()}
|
|
|
25
26
|
|
|
26
27
|
export const Clickable = () => <Thumbnail alt="Click me" image={imageKnob()} size={Size.xxl} onClick={console.log} />;
|
|
27
28
|
|
|
29
|
+
export const ClickableLink = () => (
|
|
30
|
+
<Thumbnail alt="Click me" image={imageKnob()} size={Size.xxl} linkProps={{ href: 'https://google.fr' }} />
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const CustomLinkComponent = (props: any) => (
|
|
34
|
+
<a {...props} className={classNames('custom-link-component', props.className)}>
|
|
35
|
+
{props.children}
|
|
36
|
+
</a>
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
export const ClickableCustomLink = () => (
|
|
40
|
+
<Thumbnail
|
|
41
|
+
alt="Click me"
|
|
42
|
+
image={imageKnob()}
|
|
43
|
+
size={Size.xxl}
|
|
44
|
+
linkAs={CustomLinkComponent}
|
|
45
|
+
linkProps={{ href: 'https://google.fr', className: 'custom-class-name' }}
|
|
46
|
+
/>
|
|
47
|
+
);
|
|
48
|
+
|
|
28
49
|
export const DefaultFallback = () => <Thumbnail alt="foo" image="foo" />;
|
|
29
50
|
|
|
30
51
|
export const IconFallback = () => <Thumbnail alt="foo" image="foo" fallback={mdiAbTesting} />;
|
|
@@ -5,7 +5,16 @@ import 'jest-enzyme';
|
|
|
5
5
|
import { commonTestsSuite, itShouldRenderStories } from '@lumx/react/testing/utils';
|
|
6
6
|
|
|
7
7
|
import { Thumbnail, ThumbnailProps } from './Thumbnail';
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
Clickable,
|
|
10
|
+
ClickableCustomLink,
|
|
11
|
+
ClickableLink,
|
|
12
|
+
CustomFallback,
|
|
13
|
+
Default,
|
|
14
|
+
DefaultFallback,
|
|
15
|
+
IconFallback,
|
|
16
|
+
WithBadge,
|
|
17
|
+
} from './Thumbnail.stories';
|
|
9
18
|
|
|
10
19
|
const CLASSNAME = Thumbnail.className as string;
|
|
11
20
|
|
|
@@ -22,7 +31,16 @@ describe(`<${Thumbnail.displayName}>`, () => {
|
|
|
22
31
|
// 1. Test render via snapshot.
|
|
23
32
|
describe('Snapshots and structure', () => {
|
|
24
33
|
itShouldRenderStories(
|
|
25
|
-
{
|
|
34
|
+
{
|
|
35
|
+
Default,
|
|
36
|
+
Clickable,
|
|
37
|
+
ClickableLink,
|
|
38
|
+
ClickableCustomLink,
|
|
39
|
+
DefaultFallback,
|
|
40
|
+
CustomFallback,
|
|
41
|
+
IconFallback,
|
|
42
|
+
WithBadge,
|
|
43
|
+
},
|
|
26
44
|
Thumbnail,
|
|
27
45
|
);
|
|
28
46
|
});
|
|
@@ -20,7 +20,6 @@ import { isInternetExplorer } from '@lumx/react/utils/isInternetExplorer';
|
|
|
20
20
|
import { mergeRefs } from '@lumx/react/utils/mergeRefs';
|
|
21
21
|
import { useFocusPoint } from '@lumx/react/components/thumbnail/useFocusPoint';
|
|
22
22
|
import { useImageLoad } from '@lumx/react/components/thumbnail/useImageLoad';
|
|
23
|
-
import { useClickable } from '@lumx/react/components/thumbnail/useClickable';
|
|
24
23
|
import { FocusPoint, ThumbnailSize, ThumbnailVariant } from './types';
|
|
25
24
|
|
|
26
25
|
type ImgHTMLProps = ImgHTMLAttributes<HTMLImageElement>;
|
|
@@ -63,6 +62,10 @@ export interface ThumbnailProps extends GenericProps {
|
|
|
63
62
|
theme?: Theme;
|
|
64
63
|
/** Variant of the component. */
|
|
65
64
|
variant?: ThumbnailVariant;
|
|
65
|
+
/** Props to pass to the link wrapping the thumbnail. */
|
|
66
|
+
linkProps?: React.DetailedHTMLProps<React.AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>;
|
|
67
|
+
/** Custom react component for the link (can be used to inject react router Link). */
|
|
68
|
+
linkAs?: 'a' | any;
|
|
66
69
|
}
|
|
67
70
|
|
|
68
71
|
/**
|
|
@@ -109,6 +112,8 @@ export const Thumbnail: Comp<ThumbnailProps> = forwardRef((props, ref) => {
|
|
|
109
112
|
size,
|
|
110
113
|
theme,
|
|
111
114
|
variant,
|
|
115
|
+
linkProps,
|
|
116
|
+
linkAs,
|
|
112
117
|
...forwardedProps
|
|
113
118
|
} = props;
|
|
114
119
|
const imgRef = useRef<HTMLImageElement>(null);
|
|
@@ -119,24 +124,44 @@ export const Thumbnail: Comp<ThumbnailProps> = forwardRef((props, ref) => {
|
|
|
119
124
|
const isLoading = loadingState === 'isLoading';
|
|
120
125
|
|
|
121
126
|
const [wrapper, setWrapper] = useState<HTMLElement>();
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
127
|
+
|
|
128
|
+
const isLink = Boolean(linkProps?.href || linkAs);
|
|
129
|
+
const isButton = !!forwardedProps.onClick;
|
|
130
|
+
const isClickable = isButton || isLink;
|
|
131
|
+
|
|
132
|
+
let Wrapper: any = 'div';
|
|
133
|
+
const wrapperProps = { ...forwardedProps };
|
|
134
|
+
if (isLink) {
|
|
135
|
+
Wrapper = linkAs || 'a';
|
|
136
|
+
Object.assign(wrapperProps, linkProps);
|
|
137
|
+
} else if (isButton) {
|
|
138
|
+
Wrapper = 'button';
|
|
139
|
+
}
|
|
134
140
|
|
|
135
141
|
// Update img style according to focus point and aspect ratio.
|
|
136
142
|
const style = useFocusPoint({ image, focusPoint, aspectRatio, imgRef, loadingState, wrapper });
|
|
137
143
|
|
|
138
144
|
return (
|
|
139
|
-
<
|
|
145
|
+
<Wrapper
|
|
146
|
+
{...wrapperProps}
|
|
147
|
+
ref={mergeRefs(setWrapper, ref) as any}
|
|
148
|
+
className={classNames(
|
|
149
|
+
linkProps?.className,
|
|
150
|
+
className,
|
|
151
|
+
handleBasicClasses({
|
|
152
|
+
align,
|
|
153
|
+
aspectRatio,
|
|
154
|
+
prefix: CLASSNAME,
|
|
155
|
+
size,
|
|
156
|
+
theme,
|
|
157
|
+
variant,
|
|
158
|
+
isClickable,
|
|
159
|
+
hasBadge: !!badge,
|
|
160
|
+
}),
|
|
161
|
+
isLoading && wrapper?.getBoundingClientRect()?.height && 'lumx-color-background-dark-L6',
|
|
162
|
+
fillHeight && `${CLASSNAME}--fill-height`,
|
|
163
|
+
)}
|
|
164
|
+
>
|
|
140
165
|
<div
|
|
141
166
|
className={`${CLASSNAME}__background`}
|
|
142
167
|
style={{
|
|
@@ -169,7 +194,7 @@ export const Thumbnail: Comp<ThumbnailProps> = forwardRef((props, ref) => {
|
|
|
169
194
|
))}
|
|
170
195
|
{badge &&
|
|
171
196
|
React.cloneElement(badge, { className: classNames(`${CLASSNAME}__badge`, badge.props.className) })}
|
|
172
|
-
</
|
|
197
|
+
</Wrapper>
|
|
173
198
|
);
|
|
174
199
|
});
|
|
175
200
|
Thumbnail.displayName = COMPONENT_NAME;
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
2
|
|
|
3
3
|
exports[`<Thumbnail> Snapshots and structure should render story 'Clickable' 1`] = `
|
|
4
|
-
<
|
|
5
|
-
className="lumx-thumbnail lumx-thumbnail--size-xxl lumx-thumbnail--theme-light"
|
|
4
|
+
<button
|
|
5
|
+
className="lumx-thumbnail lumx-thumbnail--size-xxl lumx-thumbnail--theme-light lumx-thumbnail--is-clickable"
|
|
6
6
|
onClick={[Function]}
|
|
7
|
-
onKeyPress={[Function]}
|
|
8
|
-
role="button"
|
|
9
|
-
tabIndex={0}
|
|
10
7
|
>
|
|
11
8
|
<div
|
|
12
9
|
className="lumx-thumbnail__background"
|
|
@@ -25,7 +22,57 @@ exports[`<Thumbnail> Snapshots and structure should render story 'Clickable' 1`]
|
|
|
25
22
|
style={Object {}}
|
|
26
23
|
/>
|
|
27
24
|
</div>
|
|
28
|
-
</
|
|
25
|
+
</button>
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
exports[`<Thumbnail> Snapshots and structure should render story 'ClickableCustomLink' 1`] = `
|
|
29
|
+
<CustomLinkComponent
|
|
30
|
+
className="custom-class-name lumx-thumbnail lumx-thumbnail--size-xxl lumx-thumbnail--theme-light lumx-thumbnail--is-clickable"
|
|
31
|
+
href="https://google.fr"
|
|
32
|
+
>
|
|
33
|
+
<div
|
|
34
|
+
className="lumx-thumbnail__background"
|
|
35
|
+
style={
|
|
36
|
+
Object {
|
|
37
|
+
"display": undefined,
|
|
38
|
+
"visibility": "hidden",
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
>
|
|
42
|
+
<img
|
|
43
|
+
alt="Click me"
|
|
44
|
+
className="lumx-thumbnail__image"
|
|
45
|
+
loading="lazy"
|
|
46
|
+
src="landscape1.jpg"
|
|
47
|
+
style={Object {}}
|
|
48
|
+
/>
|
|
49
|
+
</div>
|
|
50
|
+
</CustomLinkComponent>
|
|
51
|
+
`;
|
|
52
|
+
|
|
53
|
+
exports[`<Thumbnail> Snapshots and structure should render story 'ClickableLink' 1`] = `
|
|
54
|
+
<a
|
|
55
|
+
className="lumx-thumbnail lumx-thumbnail--size-xxl lumx-thumbnail--theme-light lumx-thumbnail--is-clickable"
|
|
56
|
+
href="https://google.fr"
|
|
57
|
+
>
|
|
58
|
+
<div
|
|
59
|
+
className="lumx-thumbnail__background"
|
|
60
|
+
style={
|
|
61
|
+
Object {
|
|
62
|
+
"display": undefined,
|
|
63
|
+
"visibility": "hidden",
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
>
|
|
67
|
+
<img
|
|
68
|
+
alt="Click me"
|
|
69
|
+
className="lumx-thumbnail__image"
|
|
70
|
+
loading="lazy"
|
|
71
|
+
src="landscape1.jpg"
|
|
72
|
+
style={Object {}}
|
|
73
|
+
/>
|
|
74
|
+
</div>
|
|
75
|
+
</a>
|
|
29
76
|
`;
|
|
30
77
|
|
|
31
78
|
exports[`<Thumbnail> Snapshots and structure should render story 'CustomFallback' 1`] = `
|