@lumx/react 2.1.8 → 2.1.9-alpha-thumbnail4

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 (64) hide show
  1. package/esm/_internal/Avatar2.js +1 -5
  2. package/esm/_internal/Avatar2.js.map +1 -1
  3. package/esm/_internal/DragHandle.js +1 -1
  4. package/esm/_internal/DragHandle.js.map +1 -1
  5. package/esm/_internal/Flag2.js +1 -3
  6. package/esm/_internal/Flag2.js.map +1 -1
  7. package/esm/_internal/Icon2.js +9 -1
  8. package/esm/_internal/Icon2.js.map +1 -1
  9. package/esm/_internal/ImageBlock.js +0 -1
  10. package/esm/_internal/ImageBlock.js.map +1 -1
  11. package/esm/_internal/List2.js.map +1 -1
  12. package/esm/_internal/Message2.js +2 -2
  13. package/esm/_internal/Message2.js.map +1 -1
  14. package/esm/_internal/Slider2.js +21 -2
  15. package/esm/_internal/Slider2.js.map +1 -1
  16. package/esm/_internal/Thumbnail2.js +61 -764
  17. package/esm/_internal/Thumbnail2.js.map +1 -1
  18. package/esm/_internal/UserBlock.js +14 -45
  19. package/esm/_internal/UserBlock.js.map +1 -1
  20. package/esm/_internal/avatar.js +0 -3
  21. package/esm/_internal/avatar.js.map +1 -1
  22. package/esm/_internal/comment-block.js +0 -3
  23. package/esm/_internal/comment-block.js.map +1 -1
  24. package/esm/_internal/image-block.js +0 -3
  25. package/esm/_internal/image-block.js.map +1 -1
  26. package/esm/_internal/link-preview.js +0 -3
  27. package/esm/_internal/link-preview.js.map +1 -1
  28. package/esm/_internal/mdi.js +2 -2
  29. package/esm/_internal/mdi.js.map +1 -1
  30. package/esm/_internal/mosaic.js +0 -3
  31. package/esm/_internal/mosaic.js.map +1 -1
  32. package/esm/_internal/post-block.js +0 -3
  33. package/esm/_internal/post-block.js.map +1 -1
  34. package/esm/_internal/slider.js +1 -2
  35. package/esm/_internal/slider.js.map +1 -1
  36. package/esm/_internal/thumbnail.js +0 -3
  37. package/esm/_internal/thumbnail.js.map +1 -1
  38. package/esm/_internal/user-block.js +0 -4
  39. package/esm/_internal/user-block.js.map +1 -1
  40. package/esm/index.js +1 -3
  41. package/esm/index.js.map +1 -1
  42. package/package.json +4 -4
  43. package/src/components/avatar/Avatar.tsx +0 -8
  44. package/src/components/drag-handle/DragHandle.tsx +5 -1
  45. package/src/components/flag/Flag.test.tsx +1 -2
  46. package/src/components/flag/Flag.tsx +2 -10
  47. package/src/components/flag/__snapshots__/Flag.test.tsx.snap +0 -15
  48. package/src/components/icon/Icon.tsx +10 -1
  49. package/src/components/image-block/ImageBlock.tsx +0 -1
  50. package/src/components/message/Message.tsx +2 -2
  51. package/src/components/thumbnail/Thumbnail.stories.tsx +399 -59
  52. package/src/components/thumbnail/Thumbnail.test.tsx +6 -6
  53. package/src/components/thumbnail/Thumbnail.tsx +35 -34
  54. package/src/components/thumbnail/__snapshots__/Thumbnail.test.tsx.snap +6 -53
  55. package/src/components/thumbnail/useFocusPoint.ts +18 -10
  56. package/src/components/thumbnail/useImageLoad.ts +23 -22
  57. package/src/components/user-block/UserBlock.stories.tsx +4 -30
  58. package/src/components/user-block/UserBlock.tsx +16 -41
  59. package/src/components/user-block/__snapshots__/UserBlock.test.tsx.snap +145 -244
  60. package/src/hooks/useOnResize.ts +6 -0
  61. package/src/stories/knobs/image.ts +35 -3
  62. package/types.d.ts +2 -8
  63. package/esm/_internal/clamp.js +0 -22
  64. package/esm/_internal/clamp.js.map +0 -1
@@ -7,7 +7,6 @@ import React, {
7
7
  ReactNode,
8
8
  Ref,
9
9
  useRef,
10
- useState,
11
10
  } from 'react';
12
11
  import classNames from 'classnames';
13
12
 
@@ -15,10 +14,8 @@ import { AspectRatio, HorizontalAlignment, Icon, Size, Theme } from '@lumx/react
15
14
 
16
15
  import { Comp, GenericProps, getRootClassName, handleBasicClasses } from '@lumx/react/utils';
17
16
 
18
- import { mdiImageBrokenVariant } from '@lumx/icons';
19
- import { isInternetExplorer } from '@lumx/react/utils/isInternetExplorer';
17
+ import { mdiImageBroken } from '@lumx/icons';
20
18
  import { mergeRefs } from '@lumx/react/utils/mergeRefs';
21
- import { useFocusPoint } from '@lumx/react/components/thumbnail/useFocusPoint';
22
19
  import { useImageLoad } from '@lumx/react/components/thumbnail/useImageLoad';
23
20
  import { FocusPoint, ThumbnailSize, ThumbnailVariant } from './types';
24
21
 
@@ -50,6 +47,8 @@ export interface ThumbnailProps extends GenericProps {
50
47
  imgProps?: ImgHTMLProps;
51
48
  /** Reference to the native <img> element. */
52
49
  imgRef?: Ref<HTMLImageElement>;
50
+ /** Set to true to force the display of the loading skeleton. */
51
+ isLoading?: boolean;
53
52
  /** Size variant of the component. */
54
53
  size?: ThumbnailSize;
55
54
  /** Image loading mode. */
@@ -82,11 +81,18 @@ const CLASSNAME = getRootClassName(COMPONENT_NAME);
82
81
  * Component default props.
83
82
  */
84
83
  const DEFAULT_PROPS: Partial<ThumbnailProps> = {
85
- fallback: mdiImageBrokenVariant,
84
+ fallback: mdiImageBroken,
86
85
  loading: 'lazy',
87
86
  theme: Theme.light,
88
87
  };
89
88
 
89
+ function getObjectPosition(aspectRatio: AspectRatio, focusPoint?: FocusPoint) {
90
+ if (aspectRatio === AspectRatio.original || (!focusPoint?.y && !focusPoint?.x)) return undefined;
91
+ const x = Math.round(Math.abs(((focusPoint?.x || 0) + 1) / 2) * 100);
92
+ const y = Math.round(Math.abs(((focusPoint?.y || 0) - 1) / 2) * 100);
93
+ return `${x}% ${y}%`;
94
+ }
95
+
90
96
  /**
91
97
  * Thumbnail component.
92
98
  *
@@ -98,7 +104,7 @@ export const Thumbnail: Comp<ThumbnailProps> = forwardRef((props, ref) => {
98
104
  const {
99
105
  align,
100
106
  alt,
101
- aspectRatio,
107
+ aspectRatio = AspectRatio.original,
102
108
  badge,
103
109
  className,
104
110
  crossOrigin,
@@ -108,22 +114,22 @@ export const Thumbnail: Comp<ThumbnailProps> = forwardRef((props, ref) => {
108
114
  image,
109
115
  imgProps,
110
116
  imgRef: propImgRef,
117
+ isLoading: isLoadingProp,
111
118
  loading,
112
119
  size,
113
120
  theme,
114
121
  variant,
115
122
  linkProps,
116
123
  linkAs,
124
+ showSkeletonLoading = true,
117
125
  ...forwardedProps
118
126
  } = props;
119
127
  const imgRef = useRef<HTMLImageElement>(null);
120
128
 
121
129
  // Image loading state.
122
- const loadingState = useImageLoad(imgRef);
130
+ const loadingState = useImageLoad(image, imgRef);
131
+ const isLoading = isLoadingProp || loadingState === 'isLoading';
123
132
  const hasError = loadingState === 'hasError';
124
- const isLoading = loadingState === 'isLoading';
125
-
126
- const [wrapper, setWrapper] = useState<HTMLElement>();
127
133
 
128
134
  const isLink = Boolean(linkProps?.href || linkAs);
129
135
  const isButton = !!forwardedProps.onClick;
@@ -138,13 +144,10 @@ export const Thumbnail: Comp<ThumbnailProps> = forwardRef((props, ref) => {
138
144
  Wrapper = 'button';
139
145
  }
140
146
 
141
- // Update img style according to focus point and aspect ratio.
142
- const style = useFocusPoint({ image, focusPoint, aspectRatio, imgRef, loadingState, wrapper });
143
-
144
147
  return (
145
148
  <Wrapper
146
149
  {...wrapperProps}
147
- ref={mergeRefs(setWrapper, ref) as any}
150
+ ref={ref}
148
151
  className={classNames(
149
152
  linkProps?.className,
150
153
  className,
@@ -156,42 +159,40 @@ export const Thumbnail: Comp<ThumbnailProps> = forwardRef((props, ref) => {
156
159
  theme,
157
160
  variant,
158
161
  isClickable,
162
+ hasError,
163
+ isLoading: showSkeletonLoading && isLoading,
159
164
  hasBadge: !!badge,
160
165
  }),
161
- isLoading && wrapper?.getBoundingClientRect()?.height && 'lumx-color-background-dark-L6',
162
166
  fillHeight && `${CLASSNAME}--fill-height`,
163
167
  )}
164
168
  >
165
- <div
166
- className={`${CLASSNAME}__background`}
167
- style={{
168
- ...style?.wrapper,
169
- // Remove from layout if image not loaded correctly (use fallback)
170
- display: hasError ? 'none' : undefined,
171
- // Hide while loading.
172
- visibility: isLoading ? 'hidden' : undefined,
173
- }}
174
- >
169
+ <div className={`${CLASSNAME}__background`}>
175
170
  <img
176
171
  {...imgProps}
177
172
  style={{
178
173
  ...imgProps?.style,
179
- ...style?.image,
174
+ // Hide on error.
175
+ visibility: hasError ? 'hidden' : undefined,
176
+ // Focus point.
177
+ objectPosition: getObjectPosition(aspectRatio, focusPoint),
180
178
  }}
181
179
  ref={mergeRefs(imgRef, propImgRef)}
182
- className={style?.image ? `${CLASSNAME}__focused-image` : `${CLASSNAME}__image`}
183
- crossOrigin={crossOrigin && !isInternetExplorer() ? crossOrigin : undefined}
180
+ className={classNames(`${CLASSNAME}__image`, isLoading && `${CLASSNAME}__image--is-loading`)}
181
+ crossOrigin={crossOrigin}
184
182
  src={image}
185
183
  alt={alt}
186
184
  loading={loading}
187
185
  />
186
+ {!isLoading && hasError && (
187
+ <div className={`${CLASSNAME}__fallback`}>
188
+ {typeof fallback === 'string' ? (
189
+ <Icon icon={fallback} size={Size.xxs} theme={theme} />
190
+ ) : (
191
+ fallback
192
+ )}
193
+ </div>
194
+ )}
188
195
  </div>
189
- {hasError &&
190
- (typeof fallback === 'string' ? (
191
- <Icon className={`${CLASSNAME}__fallback`} icon={fallback} size={size || Size.m} theme={theme} />
192
- ) : (
193
- <div className={`${CLASSNAME}__fallback`}>{fallback}</div>
194
- ))}
195
196
  {badge &&
196
197
  React.cloneElement(badge, { className: classNames(`${CLASSNAME}__badge`, badge.props.className) })}
197
198
  </Wrapper>
@@ -1,9 +1,12 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
3
  exports[`<Thumbnail> Snapshots and structure should render story 'Clickable' 1`] = `
4
- <button
5
- className="lumx-thumbnail lumx-thumbnail--size-xxl lumx-thumbnail--theme-light lumx-thumbnail--is-clickable"
4
+ <div
5
+ className="lumx-thumbnail lumx-thumbnail--size-xxl lumx-thumbnail--theme-light"
6
6
  onClick={[Function]}
7
+ onKeyPress={[Function]}
8
+ role="button"
9
+ tabIndex={0}
7
10
  >
8
11
  <div
9
12
  className="lumx-thumbnail__background"
@@ -22,57 +25,7 @@ exports[`<Thumbnail> Snapshots and structure should render story 'Clickable' 1`]
22
25
  style={Object {}}
23
26
  />
24
27
  </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="/demo-assets/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="/demo-assets/landscape1.jpg"
72
- style={Object {}}
73
- />
74
- </div>
75
- </a>
28
+ </div>
76
29
  `;
77
30
 
78
31
  exports[`<Thumbnail> Snapshots and structure should render story 'CustomFallback' 1`] = `
@@ -32,15 +32,19 @@ type Sizes = {
32
32
 
33
33
  function calculateSizes(
34
34
  imageElement?: HTMLImageElement | null | undefined,
35
+ imageWidthProp?: number,
36
+ imageHeightProp?: number,
35
37
  parentElement?: HTMLElement | null,
36
38
  aspectRatio?: AspectRatio,
37
39
  ): Sizes | undefined {
38
- if (!imageElement || !parentElement || !aspectRatio || aspectRatio === AspectRatio.original) return undefined;
39
- const { naturalWidth: imgWidth, naturalHeight: imgHeight } = imageElement || { naturalWidth: 0, naturalHeight: 0 };
40
- const { width: containerWidth, height: containerHeight } = parentElement?.getBoundingClientRect() || {
41
- width: 0,
42
- height: 0,
43
- };
40
+ const imgWidth = imageElement?.naturalWidth || imageWidthProp;
41
+ const imgHeight = imageElement?.naturalHeight || imageHeightProp;
42
+ if (!imgHeight || !imgWidth || !parentElement || !aspectRatio || aspectRatio === AspectRatio.original) {
43
+ return undefined;
44
+ }
45
+ const rect = parentElement?.getBoundingClientRect();
46
+ const containerWidth = Math.ceil(rect?.width || 0);
47
+ const containerHeight = Math.ceil(rect?.height || 0);
44
48
  return { imgWidth, imgHeight, containerWidth, containerHeight, aspectRatio };
45
49
  }
46
50
 
@@ -90,6 +94,7 @@ function calculateImageStyle(sizes: Sizes, point: Required<FocusPoint>): Styles
90
94
  // Minimize image while still filling space
91
95
  if (sizes.imgWidth > sizes.containerWidth && sizes.imgHeight > sizes.containerHeight) {
92
96
  image[widthRatio > heightRatio ? 'maxHeight' : 'maxWidth'] = '100%';
97
+ image[widthRatio > heightRatio ? 'maxWidth' : 'maxHeight'] = 'none';
93
98
  }
94
99
 
95
100
  if (widthRatio > heightRatio) {
@@ -109,13 +114,15 @@ function calculateImageStyle(sizes: Sizes, point: Required<FocusPoint>): Styles
109
114
  */
110
115
  export const useFocusPoint = (options: {
111
116
  image: string;
117
+ imageWidthProp?: number;
118
+ imageHeightProp?: number;
112
119
  focusPoint?: FocusPoint;
113
120
  aspectRatio?: AspectRatio;
114
121
  imgRef: RefObject<HTMLImageElement>;
115
122
  loadingState: LoadingState;
116
123
  wrapper?: HTMLElement;
117
124
  }): Styles | undefined => {
118
- const { image, aspectRatio, focusPoint, imgRef, loadingState, wrapper } = options;
125
+ const { image, imageWidthProp, imageHeightProp, aspectRatio, focusPoint, imgRef, loadingState, wrapper } = options;
119
126
 
120
127
  const point = parseFocusPoint(focusPoint);
121
128
 
@@ -128,12 +135,13 @@ export const useFocusPoint = (options: {
128
135
  const update = useMemo(
129
136
  () => {
130
137
  const updateFunction = () => {
131
- const sizes = calculateSizes(imgRef?.current, wrapper, aspectRatio);
138
+ const sizes = calculateSizes(imgRef?.current, imageWidthProp, imageHeightProp, wrapper, aspectRatio);
132
139
  if (!sizes || (isEqual(sizes, previousSizes.current) && isEqual(point, previousPoint.current))) {
133
140
  // Nothing changed.
134
141
  return;
135
142
  }
136
- setStyle(calculateImageStyle(sizes, point));
143
+ const newStyle = calculateImageStyle(sizes, point);
144
+ setStyle(newStyle);
137
145
  previousPoint.current = point;
138
146
  previousSizes.current = sizes;
139
147
  };
@@ -141,7 +149,7 @@ export const useFocusPoint = (options: {
141
149
  return updateFunction;
142
150
  },
143
151
  // eslint-disable-next-line react-hooks/exhaustive-deps
144
- [...Object.values(point), imgRef, wrapper, aspectRatio],
152
+ [...Object.values(point), wrapper, aspectRatio],
145
153
  );
146
154
 
147
155
  // Update on image loaded.
@@ -1,39 +1,40 @@
1
- import { RefObject, useCallback, useEffect, useState } from 'react';
1
+ import { RefObject, useEffect, useState } from 'react';
2
2
 
3
3
  export type LoadingState = 'isLoading' | 'isLoaded' | 'hasError';
4
4
 
5
- export function useImageLoad(imgRef?: RefObject<HTMLImageElement>): LoadingState {
6
- const [state, setState] = useState<LoadingState>('isLoading');
7
-
8
- const update = useCallback(
9
- (event?: any) => {
10
- const img = imgRef?.current;
11
- if (!img || !img.complete) {
12
- setState('isLoading');
13
- return;
14
- }
5
+ function getState(img: HTMLImageElement | null | undefined, event?: Event) {
6
+ // Error event occurred or image loaded empty.
7
+ if (event?.type === 'error' || (img?.complete && (img?.naturalWidth === 0 || img?.naturalHeight === 0))) {
8
+ return 'hasError';
9
+ }
10
+ // Image is undefined or incomplete.
11
+ if (!img || !img.complete) {
12
+ return 'isLoading';
13
+ }
14
+ // Else loaded.
15
+ return 'isLoaded';
16
+ }
15
17
 
16
- if (event?.type === 'error' || (img.complete && img?.naturalWidth === 0)) {
17
- setState('hasError');
18
- return;
19
- }
18
+ export function useImageLoad(imageURL: string, imgRef?: RefObject<HTMLImageElement>): LoadingState {
19
+ const [state, setState] = useState<LoadingState>(getState(imgRef?.current));
20
20
 
21
- setState('isLoaded');
22
- },
23
- [imgRef],
24
- );
21
+ // Update state when changing image URL or DOM reference.
22
+ useEffect(() => {
23
+ setState(getState(imgRef?.current));
24
+ }, [imageURL, imgRef]);
25
25
 
26
+ // Listen to `load` and `error` event on image
26
27
  useEffect(() => {
27
28
  const img = imgRef?.current;
28
29
  if (!img) return undefined;
29
-
30
- update();
30
+ const update = (event?: Event) => setState(getState(img, event));
31
31
  img.addEventListener('load', update);
32
32
  img.addEventListener('error', update);
33
33
  return () => {
34
34
  img.removeEventListener('load', update);
35
35
  img.removeEventListener('error', update);
36
36
  };
37
- }, [update, imgRef, imgRef?.current?.src]);
37
+ }, [imgRef, imgRef?.current?.src]);
38
+
38
39
  return state;
39
40
  }
@@ -6,12 +6,11 @@ import { UserBlock } from './UserBlock';
6
6
 
7
7
  export default { title: 'LumX components/user-block/UserBlock' };
8
8
 
9
- export const Sizes = ({ theme }: any) => {
9
+ export const Sizes = () => {
10
10
  const logAction = (action: string) => () => console.log(action);
11
11
  return [Size.s, Size.m, Size.l].map((size: any) => (
12
12
  <div className="demo-grid" key={size}>
13
13
  <UserBlock
14
- theme={theme}
15
14
  name="Emmitt O. Lum"
16
15
  fields={['Creative developer', 'Denpasar']}
17
16
  avatarProps={{ image: avatarImageKnob(), alt: 'Avatar' }}
@@ -24,34 +23,11 @@ export const Sizes = ({ theme }: any) => {
24
23
  ));
25
24
  };
26
25
 
27
- export const WithLinks = ({ theme }: any) => {
28
- const logAction = (action: string) => () => console.log(action);
29
- return [Size.s, Size.m, Size.l].map((size: any) => (
30
- <div className="demo-grid" key={size}>
31
- <UserBlock
32
- theme={theme}
33
- name="Emmitt O. Lum"
34
- linkProps={{
35
- href: 'https://www.lumapps.com',
36
- target: '_blank',
37
- }}
38
- fields={['Creative developer', 'Denpasar']}
39
- avatarProps={{ image: avatarImageKnob(), alt: 'Avatar' }}
40
- size={size}
41
- onMouseEnter={logAction('Mouse entered')}
42
- onMouseLeave={logAction('Mouse left')}
43
- onClick={logAction('UserBlock clicked')}
44
- />
45
- </div>
46
- ));
47
- };
48
-
49
- export const WithBadge = ({ theme }: any) => {
26
+ export const WithBadge = () => {
50
27
  const logAction = (action: string) => () => console.log(action);
51
28
  return (
52
29
  <div className="demo-grid">
53
30
  <UserBlock
54
- theme={theme}
55
31
  name="Emmitt O. Lum"
56
32
  fields={['Creative developer', 'Denpasar']}
57
33
  avatarProps={{
@@ -66,19 +42,19 @@ export const WithBadge = ({ theme }: any) => {
66
42
  size={Size.m}
67
43
  onMouseEnter={logAction('Mouse entered')}
68
44
  onMouseLeave={logAction('Mouse left')}
45
+ onClick={logAction('UserBlock clicked')}
69
46
  />
70
47
  </div>
71
48
  );
72
49
  };
73
50
 
74
- export const InList = ({ theme }: any) => {
51
+ export const InList = () => {
75
52
  const logAction = (action: string) => () => console.log(action);
76
53
  return (
77
54
  <div className="demo-grid">
78
55
  <List itemPadding={Size.big}>
79
56
  <ListItem className="lumx-color-background-dark-L6" size={Size.big}>
80
57
  <UserBlock
81
- theme={theme}
82
58
  name="Emmitt O. Lum"
83
59
  fields={['Creative developer', 'Denpasar']}
84
60
  avatarProps={{
@@ -98,7 +74,6 @@ export const InList = ({ theme }: any) => {
98
74
  </ListItem>
99
75
  <ListItem className="lumx-color-background-dark-L6" size={Size.big}>
100
76
  <UserBlock
101
- theme={theme}
102
77
  name="Emmitt O. Lum"
103
78
  fields={['Creative developer', 'Denpasar']}
104
79
  avatarProps={{
@@ -118,7 +93,6 @@ export const InList = ({ theme }: any) => {
118
93
  </ListItem>
119
94
  <ListItem className="lumx-color-background-dark-L6" size={Size.big}>
120
95
  <UserBlock
121
- theme={theme}
122
96
  name="Emmitt O. Lum"
123
97
  fields={['Creative developer', 'Denpasar']}
124
98
  avatarProps={{
@@ -5,8 +5,6 @@ import classNames from 'classnames';
5
5
  import { Avatar, Orientation, Size, Theme } from '@lumx/react';
6
6
 
7
7
  import { Comp, GenericProps, getRootClassName, handleBasicClasses } from '@lumx/react/utils';
8
- import { isEmpty } from 'lodash';
9
- import { renderLink } from '@lumx/react/utils/renderLink';
10
8
  import { AvatarProps } from '../avatar/Avatar';
11
9
 
12
10
  /**
@@ -20,10 +18,6 @@ export type UserBlockSize = Extract<Size, 's' | 'm' | 'l'>;
20
18
  export interface UserBlockProps extends GenericProps {
21
19
  /** Props to pass to the avatar. */
22
20
  avatarProps?: AvatarProps;
23
- /** Props to pass to the link wrapping the avatar thumbnail. */
24
- linkProps?: React.DetailedHTMLProps<React.AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>;
25
- /** Custom react component for the link (can be used to inject react router Link). */
26
- linkAs?: 'a' | any;
27
21
  /** Simple action toolbar content. */
28
22
  simpleAction?: ReactNode;
29
23
  /** Multiple action toolbar content. */
@@ -86,8 +80,6 @@ export const UserBlock: Comp<UserBlockProps, HTMLDivElement> = forwardRef((props
86
80
  simpleAction,
87
81
  size,
88
82
  theme,
89
- linkProps,
90
- linkAs,
91
83
  ...forwardedProps
92
84
  } = props;
93
85
  let componentSize = size;
@@ -99,29 +91,12 @@ export const UserBlock: Comp<UserBlockProps, HTMLDivElement> = forwardRef((props
99
91
 
100
92
  const shouldDisplayActions: boolean = orientation === Orientation.vertical;
101
93
 
102
- const isLink = Boolean(linkProps?.href || linkAs);
103
- const isClickable = !!onClick || isLink;
104
-
105
- const nameBlock: ReactNode = React.useMemo(() => {
106
- if (isEmpty(name)) {
107
- return null;
108
- }
109
- const nameClassName = classNames(
110
- handleBasicClasses({ prefix: `${CLASSNAME}__name`, isClickable }),
111
- isLink && linkProps?.className,
112
- );
113
- if (isLink) {
114
- return renderLink({ ...linkProps, linkAs, className: nameClassName }, name);
115
- }
116
- if (onClick) {
117
- return (
118
- <button onClick={onClick} type="button" className={nameClassName}>
119
- {name}
120
- </button>
121
- );
122
- }
123
- return <span className={nameClassName}>{name}</span>;
124
- }, [isClickable, isLink, linkAs, linkProps, name, onClick]);
94
+ const nameBlock: ReactNode = name && (
95
+ // eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-noninteractive-tabindex,jsx-a11y/no-static-element-interactions
96
+ <span className={`${CLASSNAME}__name`} onClick={onClick} tabIndex={onClick ? 0 : -1}>
97
+ {name}
98
+ </span>
99
+ );
125
100
 
126
101
  const fieldsBlock: ReactNode = fields && componentSize !== Size.s && (
127
102
  <div className={`${CLASSNAME}__fields`}>
@@ -139,21 +114,21 @@ export const UserBlock: Comp<UserBlockProps, HTMLDivElement> = forwardRef((props
139
114
  {...forwardedProps}
140
115
  className={classNames(
141
116
  className,
142
- handleBasicClasses({ prefix: CLASSNAME, orientation, size: componentSize, theme, isClickable }),
117
+ handleBasicClasses({ prefix: CLASSNAME, orientation, size: componentSize, theme }),
143
118
  )}
144
119
  onMouseLeave={onMouseLeave}
145
120
  onMouseEnter={onMouseEnter}
146
121
  >
147
122
  {avatarProps && (
148
- <Avatar
149
- linkAs={linkAs}
150
- linkProps={linkProps}
151
- {...avatarProps}
152
- className={classNames(`${CLASSNAME}__avatar`, avatarProps.className)}
153
- size={componentSize}
154
- onClick={onClick}
155
- theme={theme}
156
- />
123
+ <div className={`${CLASSNAME}__avatar`}>
124
+ <Avatar
125
+ {...avatarProps}
126
+ size={componentSize}
127
+ onClick={onClick}
128
+ tabIndex={onClick ? 0 : -1}
129
+ theme={theme}
130
+ />
131
+ </div>
157
132
  )}
158
133
  {(fields || name) && (
159
134
  <div className={`${CLASSNAME}__wrapper`}>