@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.
- 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 +0 -1
- 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 +21 -2
- package/esm/_internal/Slider2.js.map +1 -1
- package/esm/_internal/Thumbnail2.js +61 -764
- 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 +0 -3
- package/esm/_internal/avatar.js.map +1 -1
- package/esm/_internal/comment-block.js +0 -3
- package/esm/_internal/comment-block.js.map +1 -1
- package/esm/_internal/image-block.js +0 -3
- package/esm/_internal/image-block.js.map +1 -1
- package/esm/_internal/link-preview.js +0 -3
- 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 +0 -3
- package/esm/_internal/mosaic.js.map +1 -1
- package/esm/_internal/post-block.js +0 -3
- package/esm/_internal/post-block.js.map +1 -1
- package/esm/_internal/slider.js +1 -2
- package/esm/_internal/slider.js.map +1 -1
- package/esm/_internal/thumbnail.js +0 -3
- package/esm/_internal/thumbnail.js.map +1 -1
- package/esm/_internal/user-block.js +0 -4
- package/esm/_internal/user-block.js.map +1 -1
- package/esm/index.js +1 -3
- 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 +0 -1
- package/src/components/message/Message.tsx +2 -2
- package/src/components/thumbnail/Thumbnail.stories.tsx +399 -59
- package/src/components/thumbnail/Thumbnail.test.tsx +6 -6
- package/src/components/thumbnail/Thumbnail.tsx +35 -34
- package/src/components/thumbnail/__snapshots__/Thumbnail.test.tsx.snap +6 -53
- package/src/components/thumbnail/useFocusPoint.ts +18 -10
- package/src/components/thumbnail/useImageLoad.ts +23 -22
- 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 +6 -0
- package/src/stories/knobs/image.ts +35 -3
- package/types.d.ts +2 -8
- package/esm/_internal/clamp.js +0 -22
- 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 {
|
|
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:
|
|
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={
|
|
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
|
-
|
|
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={
|
|
183
|
-
crossOrigin={crossOrigin
|
|
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
|
-
<
|
|
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,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
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
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),
|
|
152
|
+
[...Object.values(point), wrapper, aspectRatio],
|
|
145
153
|
);
|
|
146
154
|
|
|
147
155
|
// Update on image loaded.
|
|
@@ -1,39 +1,40 @@
|
|
|
1
|
-
import { RefObject,
|
|
1
|
+
import { RefObject, useEffect, useState } from 'react';
|
|
2
2
|
|
|
3
3
|
export type LoadingState = 'isLoading' | 'isLoaded' | 'hasError';
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
17
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
}, [
|
|
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 = (
|
|
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`}>
|