@lumx/react 2.1.9-alpha-thumbnail3 → 2.1.9-prefer-css-color-variables
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/_internal/Avatar2.js +1 -5
- package/esm/_internal/Avatar2.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/Icon2.js +9 -1
- package/esm/_internal/Icon2.js.map +1 -1
- package/esm/_internal/ImageBlock.js +1 -0
- package/esm/_internal/ImageBlock.js.map +1 -1
- 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/Slider2.js +2 -21
- package/esm/_internal/Slider2.js.map +1 -1
- package/esm/_internal/Thumbnail2.js +787 -79
- package/esm/_internal/Thumbnail2.js.map +1 -1
- package/esm/_internal/UserBlock.js +14 -45
- package/esm/_internal/UserBlock.js.map +1 -1
- package/esm/_internal/avatar.js +3 -0
- package/esm/_internal/avatar.js.map +1 -1
- package/esm/_internal/clamp.js +22 -0
- package/esm/_internal/clamp.js.map +1 -0
- package/esm/_internal/comment-block.js +3 -0
- package/esm/_internal/comment-block.js.map +1 -1
- package/esm/_internal/image-block.js +3 -0
- package/esm/_internal/image-block.js.map +1 -1
- package/esm/_internal/link-preview.js +3 -0
- package/esm/_internal/link-preview.js.map +1 -1
- package/esm/_internal/mdi.js +2 -2
- package/esm/_internal/mdi.js.map +1 -1
- package/esm/_internal/mosaic.js +3 -0
- package/esm/_internal/mosaic.js.map +1 -1
- package/esm/_internal/post-block.js +3 -0
- package/esm/_internal/post-block.js.map +1 -1
- package/esm/_internal/slider.js +2 -1
- package/esm/_internal/slider.js.map +1 -1
- package/esm/_internal/thumbnail.js +3 -0
- package/esm/_internal/thumbnail.js.map +1 -1
- package/esm/_internal/user-block.js +2 -1
- package/esm/_internal/user-block.js.map +1 -1
- package/esm/index.js +3 -2
- package/esm/index.js.map +1 -1
- package/package.json +4 -4
- package/src/components/avatar/Avatar.tsx +0 -8
- 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/icon/Icon.tsx +10 -1
- package/src/components/image-block/ImageBlock.tsx +1 -0
- package/src/components/message/Message.tsx +2 -2
- package/src/components/thumbnail/Thumbnail.stories.tsx +42 -403
- package/src/components/thumbnail/Thumbnail.test.tsx +2 -20
- package/src/components/thumbnail/Thumbnail.tsx +45 -71
- package/src/components/thumbnail/__snapshots__/Thumbnail.test.tsx.snap +6 -53
- package/src/components/thumbnail/useFocusPoint.ts +10 -18
- package/src/components/thumbnail/useImageLoad.ts +22 -23
- package/src/components/user-block/UserBlock.stories.tsx +4 -30
- package/src/components/user-block/UserBlock.tsx +16 -41
- package/src/components/user-block/__snapshots__/UserBlock.test.tsx.snap +145 -244
- package/src/hooks/useOnResize.ts +0 -6
- package/src/stories/generated/List/Demos.stories.tsx +2 -0
- package/src/stories/knobs/image.ts +3 -35
- package/types.d.ts +0 -14
|
@@ -7,6 +7,7 @@ import React, {
|
|
|
7
7
|
ReactNode,
|
|
8
8
|
Ref,
|
|
9
9
|
useRef,
|
|
10
|
+
useState,
|
|
10
11
|
} from 'react';
|
|
11
12
|
import classNames from 'classnames';
|
|
12
13
|
|
|
@@ -14,9 +15,12 @@ import { AspectRatio, HorizontalAlignment, Icon, Size, Theme } from '@lumx/react
|
|
|
14
15
|
|
|
15
16
|
import { Comp, GenericProps, getRootClassName, handleBasicClasses } from '@lumx/react/utils';
|
|
16
17
|
|
|
17
|
-
import {
|
|
18
|
+
import { mdiImageBrokenVariant } from '@lumx/icons';
|
|
19
|
+
import { isInternetExplorer } from '@lumx/react/utils/isInternetExplorer';
|
|
18
20
|
import { mergeRefs } from '@lumx/react/utils/mergeRefs';
|
|
21
|
+
import { useFocusPoint } from '@lumx/react/components/thumbnail/useFocusPoint';
|
|
19
22
|
import { useImageLoad } from '@lumx/react/components/thumbnail/useImageLoad';
|
|
23
|
+
import { useClickable } from '@lumx/react/components/thumbnail/useClickable';
|
|
20
24
|
import { FocusPoint, ThumbnailSize, ThumbnailVariant } from './types';
|
|
21
25
|
|
|
22
26
|
type ImgHTMLProps = ImgHTMLAttributes<HTMLImageElement>;
|
|
@@ -47,8 +51,6 @@ export interface ThumbnailProps extends GenericProps {
|
|
|
47
51
|
imgProps?: ImgHTMLProps;
|
|
48
52
|
/** Reference to the native <img> element. */
|
|
49
53
|
imgRef?: Ref<HTMLImageElement>;
|
|
50
|
-
/** Set to true to force the display of the loading skeleton. */
|
|
51
|
-
isLoading?: boolean;
|
|
52
54
|
/** Size variant of the component. */
|
|
53
55
|
size?: ThumbnailSize;
|
|
54
56
|
/** Image loading mode. */
|
|
@@ -61,10 +63,6 @@ export interface ThumbnailProps extends GenericProps {
|
|
|
61
63
|
theme?: Theme;
|
|
62
64
|
/** Variant of the component. */
|
|
63
65
|
variant?: ThumbnailVariant;
|
|
64
|
-
/** Props to pass to the link wrapping the thumbnail. */
|
|
65
|
-
linkProps?: React.DetailedHTMLProps<React.AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>;
|
|
66
|
-
/** Custom react component for the link (can be used to inject react router Link). */
|
|
67
|
-
linkAs?: 'a' | any;
|
|
68
66
|
}
|
|
69
67
|
|
|
70
68
|
/**
|
|
@@ -81,18 +79,11 @@ const CLASSNAME = getRootClassName(COMPONENT_NAME);
|
|
|
81
79
|
* Component default props.
|
|
82
80
|
*/
|
|
83
81
|
const DEFAULT_PROPS: Partial<ThumbnailProps> = {
|
|
84
|
-
fallback:
|
|
82
|
+
fallback: mdiImageBrokenVariant,
|
|
85
83
|
loading: 'lazy',
|
|
86
84
|
theme: Theme.light,
|
|
87
85
|
};
|
|
88
86
|
|
|
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
|
-
|
|
96
87
|
/**
|
|
97
88
|
* Thumbnail component.
|
|
98
89
|
*
|
|
@@ -104,7 +95,7 @@ export const Thumbnail: Comp<ThumbnailProps> = forwardRef((props, ref) => {
|
|
|
104
95
|
const {
|
|
105
96
|
align,
|
|
106
97
|
alt,
|
|
107
|
-
aspectRatio
|
|
98
|
+
aspectRatio,
|
|
108
99
|
badge,
|
|
109
100
|
className,
|
|
110
101
|
crossOrigin,
|
|
@@ -114,88 +105,71 @@ export const Thumbnail: Comp<ThumbnailProps> = forwardRef((props, ref) => {
|
|
|
114
105
|
image,
|
|
115
106
|
imgProps,
|
|
116
107
|
imgRef: propImgRef,
|
|
117
|
-
isLoading: isLoadingProp,
|
|
118
108
|
loading,
|
|
119
109
|
size,
|
|
120
110
|
theme,
|
|
121
111
|
variant,
|
|
122
|
-
linkProps,
|
|
123
|
-
linkAs,
|
|
124
|
-
showSkeletonLoading = true,
|
|
125
112
|
...forwardedProps
|
|
126
113
|
} = props;
|
|
127
114
|
const imgRef = useRef<HTMLImageElement>(null);
|
|
128
115
|
|
|
129
116
|
// Image loading state.
|
|
130
|
-
const loadingState = useImageLoad(
|
|
131
|
-
const isLoading = isLoadingProp || loadingState === 'isLoading';
|
|
117
|
+
const loadingState = useImageLoad(imgRef);
|
|
132
118
|
const hasError = loadingState === 'hasError';
|
|
119
|
+
const isLoading = loadingState === 'isLoading';
|
|
133
120
|
|
|
134
|
-
const
|
|
135
|
-
const
|
|
136
|
-
|
|
121
|
+
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
|
+
};
|
|
137
134
|
|
|
138
|
-
|
|
139
|
-
const
|
|
140
|
-
if (isLink) {
|
|
141
|
-
Wrapper = linkAs || 'a';
|
|
142
|
-
Object.assign(wrapperProps, linkProps);
|
|
143
|
-
} else if (isButton) {
|
|
144
|
-
Wrapper = 'button';
|
|
145
|
-
}
|
|
135
|
+
// Update img style according to focus point and aspect ratio.
|
|
136
|
+
const style = useFocusPoint({ image, focusPoint, aspectRatio, imgRef, loadingState, wrapper });
|
|
146
137
|
|
|
147
138
|
return (
|
|
148
|
-
<
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
theme,
|
|
160
|
-
variant,
|
|
161
|
-
isClickable,
|
|
162
|
-
hasError,
|
|
163
|
-
isLoading: showSkeletonLoading && isLoading,
|
|
164
|
-
hasBadge: !!badge,
|
|
165
|
-
}),
|
|
166
|
-
fillHeight && `${CLASSNAME}--fill-height`,
|
|
167
|
-
)}
|
|
168
|
-
>
|
|
169
|
-
<div className={`${CLASSNAME}__background`}>
|
|
139
|
+
<div {...wrapperProps}>
|
|
140
|
+
<div
|
|
141
|
+
className={`${CLASSNAME}__background`}
|
|
142
|
+
style={{
|
|
143
|
+
...style?.wrapper,
|
|
144
|
+
// Remove from layout if image not loaded correctly (use fallback)
|
|
145
|
+
display: hasError ? 'none' : undefined,
|
|
146
|
+
// Hide while loading.
|
|
147
|
+
visibility: isLoading ? 'hidden' : undefined,
|
|
148
|
+
}}
|
|
149
|
+
>
|
|
170
150
|
<img
|
|
171
151
|
{...imgProps}
|
|
172
152
|
style={{
|
|
173
153
|
...imgProps?.style,
|
|
174
|
-
|
|
175
|
-
visibility: hasError ? 'hidden' : undefined,
|
|
176
|
-
// Focus point.
|
|
177
|
-
objectPosition: getObjectPosition(aspectRatio, focusPoint),
|
|
154
|
+
...style?.image,
|
|
178
155
|
}}
|
|
179
156
|
ref={mergeRefs(imgRef, propImgRef)}
|
|
180
|
-
className={
|
|
181
|
-
crossOrigin={crossOrigin}
|
|
157
|
+
className={style?.image ? `${CLASSNAME}__focused-image` : `${CLASSNAME}__image`}
|
|
158
|
+
crossOrigin={crossOrigin && !isInternetExplorer() ? crossOrigin : undefined}
|
|
182
159
|
src={image}
|
|
183
160
|
alt={alt}
|
|
184
161
|
loading={loading}
|
|
185
162
|
/>
|
|
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
|
-
)}
|
|
195
163
|
</div>
|
|
164
|
+
{hasError &&
|
|
165
|
+
(typeof fallback === 'string' ? (
|
|
166
|
+
<Icon className={`${CLASSNAME}__fallback`} icon={fallback} size={size || Size.m} theme={theme} />
|
|
167
|
+
) : (
|
|
168
|
+
<div className={`${CLASSNAME}__fallback`}>{fallback}</div>
|
|
169
|
+
))}
|
|
196
170
|
{badge &&
|
|
197
171
|
React.cloneElement(badge, { className: classNames(`${CLASSNAME}__badge`, badge.props.className) })}
|
|
198
|
-
</
|
|
172
|
+
</div>
|
|
199
173
|
);
|
|
200
174
|
});
|
|
201
175
|
Thumbnail.displayName = COMPONENT_NAME;
|
|
@@ -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
|
-
<
|
|
5
|
-
className="lumx-thumbnail lumx-thumbnail--size-xxl lumx-thumbnail--theme-light
|
|
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
|
-
</
|
|
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,19 +32,15 @@ type Sizes = {
|
|
|
32
32
|
|
|
33
33
|
function calculateSizes(
|
|
34
34
|
imageElement?: HTMLImageElement | null | undefined,
|
|
35
|
-
imageWidthProp?: number,
|
|
36
|
-
imageHeightProp?: number,
|
|
37
35
|
parentElement?: HTMLElement | null,
|
|
38
36
|
aspectRatio?: AspectRatio,
|
|
39
37
|
): Sizes | undefined {
|
|
40
|
-
|
|
41
|
-
const imgHeight = imageElement
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const containerWidth = Math.ceil(rect?.width || 0);
|
|
47
|
-
const containerHeight = Math.ceil(rect?.height || 0);
|
|
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
|
+
};
|
|
48
44
|
return { imgWidth, imgHeight, containerWidth, containerHeight, aspectRatio };
|
|
49
45
|
}
|
|
50
46
|
|
|
@@ -94,7 +90,6 @@ function calculateImageStyle(sizes: Sizes, point: Required<FocusPoint>): Styles
|
|
|
94
90
|
// Minimize image while still filling space
|
|
95
91
|
if (sizes.imgWidth > sizes.containerWidth && sizes.imgHeight > sizes.containerHeight) {
|
|
96
92
|
image[widthRatio > heightRatio ? 'maxHeight' : 'maxWidth'] = '100%';
|
|
97
|
-
image[widthRatio > heightRatio ? 'maxWidth' : 'maxHeight'] = 'none';
|
|
98
93
|
}
|
|
99
94
|
|
|
100
95
|
if (widthRatio > heightRatio) {
|
|
@@ -114,15 +109,13 @@ function calculateImageStyle(sizes: Sizes, point: Required<FocusPoint>): Styles
|
|
|
114
109
|
*/
|
|
115
110
|
export const useFocusPoint = (options: {
|
|
116
111
|
image: string;
|
|
117
|
-
imageWidthProp?: number;
|
|
118
|
-
imageHeightProp?: number;
|
|
119
112
|
focusPoint?: FocusPoint;
|
|
120
113
|
aspectRatio?: AspectRatio;
|
|
121
114
|
imgRef: RefObject<HTMLImageElement>;
|
|
122
115
|
loadingState: LoadingState;
|
|
123
116
|
wrapper?: HTMLElement;
|
|
124
117
|
}): Styles | undefined => {
|
|
125
|
-
const { image,
|
|
118
|
+
const { image, aspectRatio, focusPoint, imgRef, loadingState, wrapper } = options;
|
|
126
119
|
|
|
127
120
|
const point = parseFocusPoint(focusPoint);
|
|
128
121
|
|
|
@@ -135,13 +128,12 @@ export const useFocusPoint = (options: {
|
|
|
135
128
|
const update = useMemo(
|
|
136
129
|
() => {
|
|
137
130
|
const updateFunction = () => {
|
|
138
|
-
const sizes = calculateSizes(imgRef?.current,
|
|
131
|
+
const sizes = calculateSizes(imgRef?.current, wrapper, aspectRatio);
|
|
139
132
|
if (!sizes || (isEqual(sizes, previousSizes.current) && isEqual(point, previousPoint.current))) {
|
|
140
133
|
// Nothing changed.
|
|
141
134
|
return;
|
|
142
135
|
}
|
|
143
|
-
|
|
144
|
-
setStyle(newStyle);
|
|
136
|
+
setStyle(calculateImageStyle(sizes, point));
|
|
145
137
|
previousPoint.current = point;
|
|
146
138
|
previousSizes.current = sizes;
|
|
147
139
|
};
|
|
@@ -149,7 +141,7 @@ export const useFocusPoint = (options: {
|
|
|
149
141
|
return updateFunction;
|
|
150
142
|
},
|
|
151
143
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
152
|
-
[...Object.values(point), wrapper, aspectRatio],
|
|
144
|
+
[...Object.values(point), imgRef, wrapper, aspectRatio],
|
|
153
145
|
);
|
|
154
146
|
|
|
155
147
|
// Update on image loaded.
|
|
@@ -1,40 +1,39 @@
|
|
|
1
|
-
import { RefObject, useEffect, useState } from 'react';
|
|
1
|
+
import { RefObject, useCallback, useEffect, useState } from 'react';
|
|
2
2
|
|
|
3
3
|
export type LoadingState = 'isLoading' | 'isLoaded' | 'hasError';
|
|
4
4
|
|
|
5
|
-
function
|
|
6
|
-
|
|
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
|
-
}
|
|
5
|
+
export function useImageLoad(imgRef?: RefObject<HTMLImageElement>): LoadingState {
|
|
6
|
+
const [state, setState] = useState<LoadingState>('isLoading');
|
|
17
7
|
|
|
18
|
-
|
|
19
|
-
|
|
8
|
+
const update = useCallback(
|
|
9
|
+
(event?: any) => {
|
|
10
|
+
const img = imgRef?.current;
|
|
11
|
+
if (!img || !img.complete) {
|
|
12
|
+
setState('isLoading');
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
20
15
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
16
|
+
if (event?.type === 'error' || (img.complete && img?.naturalWidth === 0)) {
|
|
17
|
+
setState('hasError');
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
setState('isLoaded');
|
|
22
|
+
},
|
|
23
|
+
[imgRef],
|
|
24
|
+
);
|
|
25
25
|
|
|
26
|
-
// Listen to `load` and `error` event on image
|
|
27
26
|
useEffect(() => {
|
|
28
27
|
const img = imgRef?.current;
|
|
29
28
|
if (!img) return undefined;
|
|
30
|
-
|
|
29
|
+
|
|
30
|
+
update();
|
|
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
|
-
}, [imgRef, imgRef?.current?.src]);
|
|
38
|
-
|
|
37
|
+
}, [update, imgRef, imgRef?.current?.src]);
|
|
39
38
|
return state;
|
|
40
39
|
}
|
|
@@ -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 = (
|
|
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
|
|
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 = (
|
|
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
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
|
117
|
+
handleBasicClasses({ prefix: CLASSNAME, orientation, size: componentSize, theme }),
|
|
143
118
|
)}
|
|
144
119
|
onMouseLeave={onMouseLeave}
|
|
145
120
|
onMouseEnter={onMouseEnter}
|
|
146
121
|
>
|
|
147
122
|
{avatarProps && (
|
|
148
|
-
<
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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`}>
|