@lumx/react 2.1.3 → 2.1.6-alpha.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.
Files changed (37) hide show
  1. package/esm/_internal/Avatar2.js +5 -1
  2. package/esm/_internal/Avatar2.js.map +1 -1
  3. package/esm/_internal/Dialog2.js +13 -8
  4. package/esm/_internal/Dialog2.js.map +1 -1
  5. package/esm/_internal/DragHandle.js +1 -1
  6. package/esm/_internal/DragHandle.js.map +1 -1
  7. package/esm/_internal/Flag2.js +1 -3
  8. package/esm/_internal/Flag2.js.map +1 -1
  9. package/esm/_internal/Message2.js +2 -2
  10. package/esm/_internal/Message2.js.map +1 -1
  11. package/esm/_internal/SlideshowControls.js +2 -2
  12. package/esm/_internal/SlideshowControls.js.map +1 -1
  13. package/esm/_internal/Thumbnail2.js +29 -34
  14. package/esm/_internal/Thumbnail2.js.map +1 -1
  15. package/esm/_internal/UserBlock.js +44 -14
  16. package/esm/_internal/UserBlock.js.map +1 -1
  17. package/esm/_internal/user-block.js +1 -0
  18. package/esm/_internal/user-block.js.map +1 -1
  19. package/package.json +16 -17
  20. package/src/components/avatar/Avatar.tsx +8 -0
  21. package/src/components/dialog/Dialog.stories.tsx +44 -2
  22. package/src/components/dialog/Dialog.tsx +15 -11
  23. package/src/components/dialog/__snapshots__/Dialog.test.tsx.snap +76 -0
  24. package/src/components/drag-handle/DragHandle.tsx +5 -1
  25. package/src/components/flag/Flag.test.tsx +1 -2
  26. package/src/components/flag/Flag.tsx +2 -10
  27. package/src/components/flag/__snapshots__/Flag.test.tsx.snap +0 -15
  28. package/src/components/message/Message.tsx +2 -2
  29. package/src/components/slideshow/useKeyNavigate.ts +2 -2
  30. package/src/components/thumbnail/Thumbnail.stories.tsx +21 -0
  31. package/src/components/thumbnail/Thumbnail.test.tsx +20 -2
  32. package/src/components/thumbnail/Thumbnail.tsx +40 -15
  33. package/src/components/thumbnail/__snapshots__/Thumbnail.test.tsx.snap +53 -6
  34. package/src/components/user-block/UserBlock.stories.tsx +27 -4
  35. package/src/components/user-block/UserBlock.tsx +40 -16
  36. package/src/components/user-block/__snapshots__/UserBlock.test.tsx.snap +244 -145
  37. package/types.d.ts +12 -0
@@ -36,6 +36,10 @@ export interface AvatarProps extends GenericProps {
36
36
  ThumbnailProps,
37
37
  'image' | 'alt' | 'size' | 'theme' | 'align' | 'fillHeight' | 'variant' | 'aspectRatio'
38
38
  >;
39
+ /** Props to pass to the link wrapping the thumbnail. */
40
+ linkProps?: React.DetailedHTMLProps<React.AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>;
41
+ /** Custom react component for the link (can be used to inject react router Link). */
42
+ linkAs?: 'a' | any;
39
43
  }
40
44
 
41
45
  /**
@@ -75,6 +79,8 @@ export const Avatar: Comp<AvatarProps, HTMLDivElement> = forwardRef((props, ref)
75
79
  size,
76
80
  theme,
77
81
  thumbnailProps,
82
+ linkProps,
83
+ linkAs,
78
84
  ...forwardedProps
79
85
  } = props;
80
86
 
@@ -85,6 +91,8 @@ export const Avatar: Comp<AvatarProps, HTMLDivElement> = forwardRef((props, ref)
85
91
  className={classNames(className, handleBasicClasses({ prefix: CLASSNAME, size, theme }))}
86
92
  >
87
93
  <Thumbnail
94
+ linkProps={linkProps}
95
+ linkAs={linkAs}
88
96
  className={`${CLASSNAME}__thumbnail`}
89
97
  onClick={onClick}
90
98
  onKeyPress={onKeyPress}
@@ -1,5 +1,6 @@
1
1
  import { mdiClose } from '@lumx/icons';
2
2
  import {
3
+ AlertDialog,
3
4
  Button,
4
5
  Checkbox,
5
6
  DatePickerField,
@@ -32,9 +33,9 @@ const content = <div className="lumx-spacing-padding">{loremIpsum('short')}</div
32
33
  const longContent = <div className="lumx-spacing-padding">{loremIpsum('long')}</div>;
33
34
  const footer = <footer className="lumx-spacing-padding">Dialog footer</footer>;
34
35
 
35
- function useOpenButton(theme: Theme) {
36
+ function useOpenButton(theme: Theme, defaultState = true) {
36
37
  const buttonRef = useRef() as RefObject<HTMLButtonElement>;
37
- const [isOpen, setOpen] = useState(true);
38
+ const [isOpen, setOpen] = useState(defaultState);
38
39
  const openDialog = () => setOpen(true);
39
40
  const closeDialog = () => setOpen(false);
40
41
 
@@ -45,6 +46,7 @@ function useOpenButton(theme: Theme) {
45
46
  </Button>
46
47
  ),
47
48
  buttonRef,
49
+ openDialog,
48
50
  closeDialog,
49
51
  isOpen,
50
52
  };
@@ -83,6 +85,46 @@ export const PreventDialogAutoClose = ({ theme }: any) => {
83
85
  );
84
86
  };
85
87
 
88
+ export const DialogWithAlertDialog = ({ theme }: any) => {
89
+ const { button, buttonRef, closeDialog, isOpen } = useOpenButton(theme);
90
+ const { openDialog: openAlertDialog, closeDialog: closeAlertDialog, isOpen: isAlertDialogOpen } = useOpenButton(
91
+ theme,
92
+ false,
93
+ );
94
+
95
+ const handleSubmitDialog = () => {
96
+ closeDialog();
97
+ openAlertDialog();
98
+ };
99
+
100
+ return (
101
+ <>
102
+ {button}
103
+ <Dialog isOpen={isOpen} onClose={closeDialog} parentElement={buttonRef}>
104
+ {content}
105
+ <footer>
106
+ <Toolbar
107
+ after={
108
+ <Button onClick={handleSubmitDialog} emphasis={Emphasis.low}>
109
+ Close
110
+ </Button>
111
+ }
112
+ />
113
+ </footer>
114
+ </Dialog>
115
+ <AlertDialog
116
+ isOpen={isAlertDialogOpen}
117
+ onClose={closeDialog}
118
+ parentElement={buttonRef}
119
+ title="Default (info)"
120
+ confirmProps={{ onClick: closeAlertDialog, label: 'Confirm' }}
121
+ >
122
+ Consequat deserunt officia aute laborum tempor anim sint est.
123
+ </AlertDialog>
124
+ </>
125
+ );
126
+ };
127
+
86
128
  export const Sizes = ({ theme }: any) => {
87
129
  const { button, buttonRef, closeDialog, isOpen } = useOpenButton(theme);
88
130
  const sizes: DialogSizes[] = [Size.tiny, Size.regular, Size.big, Size.huge];
@@ -115,18 +115,22 @@ export const Dialog: Comp<DialogProps, HTMLDivElement> = forwardRef((props, ref)
115
115
  ...forwardedProps
116
116
  } = props;
117
117
 
118
- const handleClose = onClose
119
- ? () => {
120
- onClose();
121
- // Focus the parent element on close.
122
- if (parentElement && parentElement.current) {
123
- parentElement.current.focus();
124
- }
125
- }
126
- : undefined;
118
+ // eslint-disable-next-line react-hooks/rules-of-hooks
119
+ const previousOpen = React.useRef(isOpen);
120
+ // eslint-disable-next-line react-hooks/rules-of-hooks
121
+ React.useEffect(() => {
122
+ if (isOpen !== previousOpen.current) {
123
+ previousOpen.current = isOpen;
124
+
125
+ // Focus the parent element on close.
126
+ if (!isOpen && parentElement && parentElement.current) {
127
+ parentElement.current.focus();
128
+ }
129
+ }
130
+ }, [isOpen, parentElement]);
127
131
 
128
132
  // eslint-disable-next-line react-hooks/rules-of-hooks
129
- useCallbackOnEscape(handleClose, isOpen && !preventAutoClose);
133
+ useCallbackOnEscape(onClose, isOpen && !preventAutoClose);
130
134
 
131
135
  // eslint-disable-next-line react-hooks/rules-of-hooks
132
136
  const wrapperRef = useRef<HTMLDivElement>(null);
@@ -192,7 +196,7 @@ export const Dialog: Comp<DialogProps, HTMLDivElement> = forwardRef((props, ref)
192
196
  <div className={`${CLASSNAME}__overlay`} />
193
197
 
194
198
  <section className={`${CLASSNAME}__container`} role="dialog" aria-modal="true" {...dialogProps}>
195
- <ClickAwayProvider callback={!preventAutoClose && handleClose} refs={clickAwayRefs}>
199
+ <ClickAwayProvider callback={!preventAutoClose && onClose} refs={clickAwayRefs}>
196
200
  <div className={`${CLASSNAME}__wrapper`} ref={wrapperRef}>
197
201
  {(header || headerChildContent) && (
198
202
  <header
@@ -1,5 +1,81 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
+ exports[`<Dialog> Snapshots and structure should render story DialogWithAlertDialog 1`] = `
4
+ <Fragment>
5
+ <Button
6
+ emphasis="high"
7
+ onClick={[Function]}
8
+ size="m"
9
+ theme="light"
10
+ >
11
+ Open dialog
12
+ </Button>
13
+ <Dialog
14
+ isOpen={true}
15
+ onClose={[Function]}
16
+ parentElement={
17
+ Object {
18
+ "current": undefined,
19
+ }
20
+ }
21
+ size="big"
22
+ >
23
+ <div
24
+ className="lumx-spacing-padding"
25
+ >
26
+
27
+ Nihil hic munitissimus habendi senatus locus, nihil horum? At nos hinc posthac, sitientis piros
28
+ Afros. Magna pars studiorum, prodita quaerimus. Integer legentibus erat a ante historiarum
29
+ dapibus. Praeterea iter est quasdam res quas ex communi. Ullamco laboris nisi ut aliquid ex ea
30
+ commodi consequat. Inmensae subtilitatis, obscuris et malesuada fames. Me non paenitet nullum
31
+ festiviorem excogitasse ad hoc. Cum ceteris in veneratione tui montes, nascetur mus. Etiam
32
+ habebis sem dicantur magna mollis euismod. Quis aute iure reprehenderit in voluptate velit esse.
33
+ Phasellus laoreet lorem vel dolor tempus vehicula. Ambitioni dedisse scripsisse iudicaretur.
34
+ Paullum deliquit, ponderibus modulisque suis ratio utitur. Ab illo tempore, ab est sed
35
+ immemorabili. Nec dubitamus multa iter quae et nos invenerat. Tu quoque, Brute, fili mi, nihil
36
+ timor populi, nihil! Morbi fringilla convallis sapien, id pulvinar odio volutpat. Cras mattis
37
+ iudicium purus sit amet fermentum. Vivamus sagittis lacus vel augue laoreet rutrum faucibus.
38
+ Quisque ut dolor gravida, placerat libero vel, euismod. Unam incolunt Belgae, aliam Aquitani,
39
+ tertiam. Cras mattis iudicium purus sit amet fermentum
40
+ </div>
41
+ <footer>
42
+ <Toolbar
43
+ after={
44
+ <Button
45
+ emphasis="low"
46
+ onClick={[Function]}
47
+ size="m"
48
+ theme="light"
49
+ >
50
+ Close
51
+ </Button>
52
+ }
53
+ />
54
+ </footer>
55
+ </Dialog>
56
+ <AlertDialog
57
+ confirmProps={
58
+ Object {
59
+ "label": "Confirm",
60
+ "onClick": [Function],
61
+ }
62
+ }
63
+ isOpen={false}
64
+ kind="info"
65
+ onClose={[Function]}
66
+ parentElement={
67
+ Object {
68
+ "current": undefined,
69
+ }
70
+ }
71
+ size="tiny"
72
+ title="Default (info)"
73
+ >
74
+ Consequat deserunt officia aute laborum tempor anim sint est.
75
+ </AlertDialog>
76
+ </Fragment>
77
+ `;
78
+
3
79
  exports[`<Dialog> Snapshots and structure should render story DialogWithFocusableElements 1`] = `
4
80
  <Fragment>
5
81
  <Button
@@ -40,7 +40,11 @@ export const DragHandle: Comp<DragHandleProps, HTMLDivElement> = forwardRef((pro
40
40
  {...forwardedProps}
41
41
  className={classNames(className, handleBasicClasses({ prefix: CLASSNAME, theme }))}
42
42
  >
43
- <Icon icon={mdiDragVertical} color={theme === Theme.dark ? ColorPalette.light : undefined} size={Size.xs} />
43
+ <Icon
44
+ icon={mdiDragVertical}
45
+ color={theme === Theme.dark ? ColorPalette.light : ColorPalette.dark}
46
+ size={Size.xs}
47
+ />
44
48
  </div>
45
49
  );
46
50
  });
@@ -69,9 +69,8 @@ describe(`<${Flag.displayName} />`, () => {
69
69
 
70
70
  it('should use the color', () => {
71
71
  const color = ColorPalette.green;
72
- const { wrapper, iconEl } = setup({ icon: mdiAbTesting, color });
72
+ const { wrapper } = setup({ icon: mdiAbTesting, color });
73
73
 
74
- expect(iconEl.prop('color')).toEqual(color);
75
74
  expect(wrapper).toHaveClassName(
76
75
  getBasicClass({
77
76
  prefix: CLASSNAME,
@@ -1,7 +1,7 @@
1
1
  import React, { forwardRef } from 'react';
2
2
  import classNames from 'classnames';
3
3
 
4
- import { ColorPalette, ColorVariant, Icon, Size, Theme } from '@lumx/react';
4
+ import { ColorPalette, Icon, Size, Theme } from '@lumx/react';
5
5
  import { Comp, GenericProps, getRootClassName, handleBasicClasses } from '@lumx/react/utils';
6
6
 
7
7
  export interface FlagProps extends GenericProps {
@@ -38,15 +38,7 @@ export const Flag: Comp<FlagProps, HTMLDivElement> = forwardRef((props, ref) =>
38
38
  className={classNames(className, handleBasicClasses({ prefix: CLASSNAME, color: flagColor }))}
39
39
  ref={ref}
40
40
  >
41
- {icon && (
42
- <Icon
43
- icon={icon}
44
- color={color}
45
- colorVariant={ColorVariant.D2}
46
- size={Size.xxs}
47
- className={`${CLASSNAME}__icon`}
48
- />
49
- )}
41
+ {icon && <Icon icon={icon} size={Size.xxs} className={`${CLASSNAME}__icon`} />}
50
42
  <span className={`${CLASSNAME}__label`}>{label}</span>
51
43
  </div>
52
44
  );
@@ -19,8 +19,6 @@ Array [
19
19
  >
20
20
  <Icon
21
21
  className="lumx-flag__icon"
22
- color="blue"
23
- colorVariant="D2"
24
22
  icon="M12,21.35L10.55,20.03C5.4,15.36 2,12.27 2,8.5C2,5.41 4.42,3 7.5,3C9.24,3 10.91,3.81 12,5.08C13.09,3.81 14.76,3 16.5,3C19.58,3 22,5.41 22,8.5C22,12.27 18.6,15.36 13.45,20.03L12,21.35Z"
25
23
  size="xxs"
26
24
  />
@@ -35,8 +33,6 @@ Array [
35
33
  >
36
34
  <Icon
37
35
  className="lumx-flag__icon"
38
- color="dark"
39
- colorVariant="D2"
40
36
  icon="M12,21.35L10.55,20.03C5.4,15.36 2,12.27 2,8.5C2,5.41 4.42,3 7.5,3C9.24,3 10.91,3.81 12,5.08C13.09,3.81 14.76,3 16.5,3C19.58,3 22,5.41 22,8.5C22,12.27 18.6,15.36 13.45,20.03L12,21.35Z"
41
37
  size="xxs"
42
38
  />
@@ -51,8 +47,6 @@ Array [
51
47
  >
52
48
  <Icon
53
49
  className="lumx-flag__icon"
54
- color="green"
55
- colorVariant="D2"
56
50
  icon="M12,21.35L10.55,20.03C5.4,15.36 2,12.27 2,8.5C2,5.41 4.42,3 7.5,3C9.24,3 10.91,3.81 12,5.08C13.09,3.81 14.76,3 16.5,3C19.58,3 22,5.41 22,8.5C22,12.27 18.6,15.36 13.45,20.03L12,21.35Z"
57
51
  size="xxs"
58
52
  />
@@ -67,8 +61,6 @@ Array [
67
61
  >
68
62
  <Icon
69
63
  className="lumx-flag__icon"
70
- color="primary"
71
- colorVariant="D2"
72
64
  icon="M12,21.35L10.55,20.03C5.4,15.36 2,12.27 2,8.5C2,5.41 4.42,3 7.5,3C9.24,3 10.91,3.81 12,5.08C13.09,3.81 14.76,3 16.5,3C19.58,3 22,5.41 22,8.5C22,12.27 18.6,15.36 13.45,20.03L12,21.35Z"
73
65
  size="xxs"
74
66
  />
@@ -83,8 +75,6 @@ Array [
83
75
  >
84
76
  <Icon
85
77
  className="lumx-flag__icon"
86
- color="red"
87
- colorVariant="D2"
88
78
  icon="M12,21.35L10.55,20.03C5.4,15.36 2,12.27 2,8.5C2,5.41 4.42,3 7.5,3C9.24,3 10.91,3.81 12,5.08C13.09,3.81 14.76,3 16.5,3C19.58,3 22,5.41 22,8.5C22,12.27 18.6,15.36 13.45,20.03L12,21.35Z"
89
79
  size="xxs"
90
80
  />
@@ -99,8 +89,6 @@ Array [
99
89
  >
100
90
  <Icon
101
91
  className="lumx-flag__icon"
102
- color="secondary"
103
- colorVariant="D2"
104
92
  icon="M12,21.35L10.55,20.03C5.4,15.36 2,12.27 2,8.5C2,5.41 4.42,3 7.5,3C9.24,3 10.91,3.81 12,5.08C13.09,3.81 14.76,3 16.5,3C19.58,3 22,5.41 22,8.5C22,12.27 18.6,15.36 13.45,20.03L12,21.35Z"
105
93
  size="xxs"
106
94
  />
@@ -115,8 +103,6 @@ Array [
115
103
  >
116
104
  <Icon
117
105
  className="lumx-flag__icon"
118
- color="yellow"
119
- colorVariant="D2"
120
106
  icon="M12,21.35L10.55,20.03C5.4,15.36 2,12.27 2,8.5C2,5.41 4.42,3 7.5,3C9.24,3 10.91,3.81 12,5.08C13.09,3.81 14.76,3 16.5,3C19.58,3 22,5.41 22,8.5C22,12.27 18.6,15.36 13.45,20.03L12,21.35Z"
121
107
  size="xxs"
122
108
  />
@@ -135,7 +121,6 @@ exports[`<Flag /> Snapshots and structure should render story 'withIcon' 1`] = `
135
121
  >
136
122
  <Icon
137
123
  className="lumx-flag__icon"
138
- colorVariant="D2"
139
124
  icon="M12,21.35L10.55,20.03C5.4,15.36 2,12.27 2,8.5C2,5.41 4.42,3 7.5,3C9.24,3 10.91,3.81 12,5.08C13.09,3.81 14.76,3 16.5,3C19.58,3 22,5.41 22,8.5C22,12.27 18.6,15.36 13.45,20.03L12,21.35Z"
140
125
  size="xxs"
141
126
  />
@@ -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
  });
@@ -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
 
@@ -18,6 +18,7 @@ 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`] = `