@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.
Files changed (78) hide show
  1. package/README.md +1 -1
  2. package/esm/_internal/Avatar2.js +5 -1
  3. package/esm/_internal/Avatar2.js.map +1 -1
  4. package/esm/_internal/Button2.js.map +1 -1
  5. package/esm/_internal/ButtonRoot.js +14 -4
  6. package/esm/_internal/ButtonRoot.js.map +1 -1
  7. package/esm/_internal/ClickAwayProvider.js +1 -1
  8. package/esm/_internal/Dialog2.js +13 -8
  9. package/esm/_internal/Dialog2.js.map +1 -1
  10. package/esm/_internal/DragHandle.js +1 -1
  11. package/esm/_internal/DragHandle.js.map +1 -1
  12. package/esm/_internal/Flag2.js +1 -3
  13. package/esm/_internal/Flag2.js.map +1 -1
  14. package/esm/_internal/IconButton.js +0 -2
  15. package/esm/_internal/IconButton.js.map +1 -1
  16. package/esm/_internal/List2.js +16 -9
  17. package/esm/_internal/List2.js.map +1 -1
  18. package/esm/_internal/Message2.js +2 -2
  19. package/esm/_internal/Message2.js.map +1 -1
  20. package/esm/_internal/SlideshowControls.js +3 -3
  21. package/esm/_internal/SlideshowControls.js.map +1 -1
  22. package/esm/_internal/TextField.js +5 -2
  23. package/esm/_internal/TextField.js.map +1 -1
  24. package/esm/_internal/Thumbnail2.js +29 -34
  25. package/esm/_internal/Thumbnail2.js.map +1 -1
  26. package/esm/_internal/Tooltip2.js +1 -1
  27. package/esm/_internal/UserBlock.js +44 -14
  28. package/esm/_internal/UserBlock.js.map +1 -1
  29. package/esm/_internal/getRootClassName.js +17 -1
  30. package/esm/_internal/getRootClassName.js.map +1 -1
  31. package/esm/_internal/user-block.js +1 -0
  32. package/esm/_internal/user-block.js.map +1 -1
  33. package/package.json +4 -8
  34. package/src/components/avatar/Avatar.stories.tsx +1 -1
  35. package/src/components/avatar/Avatar.tsx +8 -0
  36. package/src/components/button/Button.stories.tsx +85 -15
  37. package/src/components/button/Button.tsx +2 -0
  38. package/src/components/button/ButtonRoot.test.tsx +13 -0
  39. package/src/components/button/ButtonRoot.tsx +10 -1
  40. package/src/components/button/IconButton.test.tsx +9 -0
  41. package/src/components/button/IconButton.tsx +11 -26
  42. package/src/components/button/__snapshots__/ButtonRoot.test.tsx.snap +13 -0
  43. package/src/components/button/__snapshots__/IconButton.test.tsx.snap +19 -0
  44. package/src/components/comment-block/CommentBlock.stories.tsx +1 -1
  45. package/src/components/dialog/Dialog.stories.tsx +45 -3
  46. package/src/components/dialog/Dialog.tsx +15 -11
  47. package/src/components/dialog/__snapshots__/Dialog.test.tsx.snap +76 -0
  48. package/src/components/drag-handle/DragHandle.tsx +5 -1
  49. package/src/components/flag/Flag.test.tsx +1 -2
  50. package/src/components/flag/Flag.tsx +2 -10
  51. package/src/components/flag/__snapshots__/Flag.test.tsx.snap +0 -15
  52. package/src/components/image-block/ImageBlock.stories.tsx +1 -1
  53. package/src/components/link-preview/LinkPreview.stories.tsx +1 -1
  54. package/src/components/list/List.stories.tsx +7 -1
  55. package/src/components/list/ListItem.stories.tsx +28 -3
  56. package/src/components/list/ListItem.tsx +25 -7
  57. package/src/components/list/__snapshots__/List.test.tsx.snap +23 -3
  58. package/src/components/list/__snapshots__/ListItem.test.tsx.snap +84 -11
  59. package/src/components/list/useInteractiveList.tsx +1 -1
  60. package/src/components/message/Message.tsx +2 -2
  61. package/src/components/skeleton/SkeletonRectangle.stories.tsx +1 -1
  62. package/src/components/slideshow/useKeyNavigate.ts +2 -2
  63. package/src/components/text-field/TextField.stories.tsx +97 -82
  64. package/src/components/text-field/TextField.tsx +5 -0
  65. package/src/components/thumbnail/Thumbnail.stories.tsx +22 -1
  66. package/src/components/thumbnail/Thumbnail.test.tsx +20 -2
  67. package/src/components/thumbnail/Thumbnail.tsx +40 -15
  68. package/src/components/thumbnail/__snapshots__/Thumbnail.test.tsx.snap +53 -6
  69. package/src/components/user-block/UserBlock.stories.tsx +28 -5
  70. package/src/components/user-block/UserBlock.tsx +40 -16
  71. package/src/components/user-block/__snapshots__/UserBlock.test.tsx.snap +244 -145
  72. package/src/stories/generated/Button/Demos.stories.tsx +1 -0
  73. package/src/stories/knobs/buttonKnob.ts +9 -0
  74. package/src/stories/knobs/emphasisKnob.ts +8 -0
  75. package/src/utils/MaterialThemeSwitcher/MaterialThemeSwitcher.tsx +54 -0
  76. package/src/utils/MaterialThemeSwitcher/index.ts +1 -0
  77. package/src/stories/knobs/index.ts +0 -2
  78. 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 'Default' 1`] = `
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
- <div
8
- className="lumx-list-item__wrapper"
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
- </div>
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
- tabIndex={0}
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
- tabIndex={0}
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
- <CustomLink
76
- className="lumx-list-item__link"
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
- </CustomLink>
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="lumx-message__icon" icon={customIcon || icon} size={Size.xs} />}
66
- <div className="lumx-message__text">{children}</div>
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
  });
@@ -12,7 +12,7 @@ import {
12
12
  Thumbnail,
13
13
  ColorPalette,
14
14
  } from '@lumx/react';
15
- import { imageKnob } from '@lumx/react/stories/knobs';
15
+ import { imageKnob } from '@lumx/react/stories/knobs/image';
16
16
 
17
17
  export default { title: 'LumX components/skeleton/Skeleton' };
18
18
 
@@ -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
- * TextField story
10
- * @return simple TextField.
11
- */
12
- export const SimpleTextField = ({ theme }: any) => (
13
- <TextField
14
- value={text('Value', 'myvalue')}
15
- label={text('Label', 'I am the label')}
16
- placeholder={text('Placeholder', 'ex: A value')}
17
- theme={theme}
18
- onChange={noop}
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 SimpleTextNumberField = ({ theme }: any) => (
23
- <TextField
24
- value={text('Value', '2')}
25
- label={text('Label', 'I am the label')}
26
- placeholder={text('Placeholder', 'ex: A value')}
27
- theme={theme}
28
- onChange={noop}
29
- type="number"
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 TextFieldWithHelp = ({ theme }: any) => (
34
- <TextField
35
- value={text('Value', 'myvalue')}
36
- label={text('Label', 'I am the label')}
37
- placeholder={text('Placeholder', 'ex: A value')}
38
- theme={theme}
39
- onChange={noop}
40
- helper={<span>{text('Helper', 'ex: toto@acme.com')}</span>}
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 TextFieldWithError = ({ theme }: any) => (
45
- <TextField
46
- value={text('Value', 'myvalue')}
47
- label={text('Label', 'I am the label')}
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 TextArea = ({ theme }: any) => {
62
- const [value, setValue] = React.useState('my 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
- label={text('Label', 'I am the label')}
67
- placeholder={text('Placeholder', 'ex: A value')}
68
- multiline
69
- minimumRows={1}
64
+ onChange={onChange}
65
+ label={text('Label', 'Label')}
66
+ placeholder={text('Placeholder', 'Placeholder')}
70
67
  theme={theme}
71
- onChange={setValue}
72
- helper={<span>{text('Helper', 'ex: toto@acme.com')}</span>}
68
+ helper={<span>{text('Helper', 'Helper')}</span>}
73
69
  />
74
70
  );
75
71
  };
76
- export const TextAreaWith2Lines = ({ theme }: any) => {
77
- const [value, setValue] = React.useState('the value');
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', 'I am the label')}
82
- placeholder={text('Placeholder', 'ex: A value')}
78
+ label={text('Label', 'Label')}
79
+ placeholder={text('Placeholder', 'Placeholder')}
83
80
  multiline
84
- minimumRows={2}
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', 'ex: toto@acme.com')}</span>}
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', 'I am the label')}
103
- placeholder={text('Placeholder', 'ex: A value')}
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', 'ex: toto@acme.com')}</span>}
105
+ helper={<span>{text('Helper', 'Helper')}</span>}
109
106
  />
110
107
  );
111
108
  };
112
109
 
113
- // Even with value set programmatically, the number of rows should be updated
114
- export const TextAreaWithKnobValue = ({ theme }: any) => (
115
- <TextField
116
- value={text('Value', 'myvalue')}
117
- label={text('Label', 'I am the label')}
118
- placeholder={text('Placeholder', 'ex: A value')}
119
- multiline
120
- minimumRows={1}
121
- theme={theme}
122
- onChange={noop}
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 { Clickable, CustomFallback, Default, DefaultFallback, IconFallback, WithBadge } from './Thumbnail.stories';
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
- { Default, Clickable, DefaultFallback, CustomFallback, IconFallback, WithBadge },
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
- const wrapperProps: any = {
123
- ...forwardedProps,
124
- ref: mergeRefs(setWrapper, ref),
125
- className: classNames(
126
- className,
127
- handleBasicClasses({ align, aspectRatio, prefix: CLASSNAME, size, theme, variant, hasBadge: !!badge }),
128
- isLoading && wrapper?.getBoundingClientRect()?.height && 'lumx-color-background-dark-L6',
129
- fillHeight && `${CLASSNAME}--fill-height`,
130
- ),
131
- // Handle clickable Thumbnail a11y.
132
- ...useClickable(props),
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
- <div {...wrapperProps}>
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
- </div>
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
- <div
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
- </div>
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`] = `