@lumx/react 2.1.7 → 2.1.9-alpha-thumbnail2
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/DragHandle.js +1 -1
- package/esm/_internal/DragHandle.js.map +1 -1
- package/esm/_internal/Flag2.js +3 -1
- package/esm/_internal/Flag2.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/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 -2
- package/esm/_internal/user-block.js.map +1 -1
- package/esm/index.js +2 -3
- package/esm/index.js.map +1 -1
- package/package.json +4 -4
- package/src/components/drag-handle/DragHandle.tsx +1 -5
- package/src/components/flag/Flag.test.tsx +2 -1
- package/src/components/flag/Flag.tsx +10 -2
- package/src/components/flag/__snapshots__/Flag.test.tsx.snap +15 -0
- package/src/components/message/Message.tsx +2 -2
- package/src/components/thumbnail/Thumbnail.stories.tsx +343 -59
- package/src/components/thumbnail/Thumbnail.test.tsx +6 -6
- package/src/components/thumbnail/Thumbnail.tsx +35 -34
- package/src/components/thumbnail/useFocusPoint.ts +18 -10
- package/src/components/thumbnail/useImageLoad.ts +23 -22
- package/src/hooks/useOnResize.ts +6 -0
- package/src/stories/knobs/image.ts +35 -3
- package/types.d.ts +2 -0
- package/esm/_internal/clamp.js +0 -22
- package/esm/_internal/clamp.js.map +0 -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
|
}
|
package/src/hooks/useOnResize.ts
CHANGED
|
@@ -23,6 +23,12 @@ export function useOnResize(element: HTMLElement | Falsy, update: RefObject<Call
|
|
|
23
23
|
return;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
// Do not update on first resize.
|
|
27
|
+
if (previousSize.current && previousSize.current.height <= 1) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Do not update if size hasn't really changed.
|
|
26
32
|
const { width, height } = entry.contentRect;
|
|
27
33
|
if (previousSize.current?.width === width && previousSize.current?.height === height) {
|
|
28
34
|
return;
|
|
@@ -6,18 +6,19 @@ const avatar3 = '/demo-assets/avatar3.jpg';
|
|
|
6
6
|
const avatar4 = '/demo-assets/avatar4.jpg';
|
|
7
7
|
const landscape1 = '/demo-assets/landscape1.jpg';
|
|
8
8
|
const landscape2 = '/demo-assets/landscape2.jpg';
|
|
9
|
+
const landscape1s200 = '/demo-assets/landscape1-s200.jpg';
|
|
9
10
|
const landscape3 = '/demo-assets/landscape3.jpg';
|
|
10
11
|
const portrait1 = '/demo-assets/portrait1.jpg';
|
|
12
|
+
const portrait1s200 = '/demo-assets/portrait1-s200.jpg';
|
|
11
13
|
const portrait2 = '/demo-assets/portrait2.jpg';
|
|
12
14
|
const portrait3 = '/demo-assets/portrait3.jpg';
|
|
13
15
|
const square1 = '/demo-assets/square1.jpg';
|
|
14
16
|
const square2 = '/demo-assets/square2.jpg';
|
|
15
17
|
|
|
16
18
|
export const AVATAR_IMAGES = { avatar1, avatar2, avatar3, avatar4 };
|
|
17
|
-
|
|
18
19
|
export const SQUARE_IMAGES = { square1, square2 };
|
|
19
|
-
export const LANDSCAPE_IMAGES = { landscape1, landscape2, landscape3 };
|
|
20
|
-
export const PORTRAIT_IMAGES = { portrait1, portrait2, portrait3 };
|
|
20
|
+
export const LANDSCAPE_IMAGES = { landscape1, landscape1s200, landscape2, landscape3 };
|
|
21
|
+
export const PORTRAIT_IMAGES = { portrait1, portrait1s200, portrait2, portrait3 };
|
|
21
22
|
|
|
22
23
|
export const IMAGES = { ...LANDSCAPE_IMAGES, ...PORTRAIT_IMAGES, ...SQUARE_IMAGES, ...AVATAR_IMAGES };
|
|
23
24
|
|
|
@@ -35,3 +36,34 @@ export const squareImageKnob = (name = 'Image', value = Object.values(SQUARE_IMA
|
|
|
35
36
|
|
|
36
37
|
export const imageKnob = (name = 'Image', value = Object.values(IMAGES)[0], groupId?: string) =>
|
|
37
38
|
select(name, IMAGES, value, groupId);
|
|
39
|
+
|
|
40
|
+
type Size = { width: number; height: number };
|
|
41
|
+
|
|
42
|
+
export const AVATAR_IMAGE_SIZES: Record<keyof typeof AVATAR_IMAGES, Size> = {
|
|
43
|
+
avatar1: { width: 128, height: 128 },
|
|
44
|
+
avatar2: { width: 150, height: 150 },
|
|
45
|
+
avatar3: { width: 128, height: 128 },
|
|
46
|
+
avatar4: { width: 128, height: 128 },
|
|
47
|
+
};
|
|
48
|
+
export const SQUARE_IMAGE_SIZES: Record<keyof typeof SQUARE_IMAGES, Size> = {
|
|
49
|
+
square1: { width: 72, height: 72 },
|
|
50
|
+
square2: { width: 300, height: 300 },
|
|
51
|
+
};
|
|
52
|
+
export const LANDSCAPE_IMAGE_SIZES: Record<keyof typeof LANDSCAPE_IMAGES, Size> = {
|
|
53
|
+
landscape1: { width: 800, height: 546 },
|
|
54
|
+
landscape1s200: { width: 200, height: 150 },
|
|
55
|
+
landscape2: { width: 800, height: 600 },
|
|
56
|
+
landscape3: { width: 640, height: 480 },
|
|
57
|
+
};
|
|
58
|
+
export const PORTRAIT_IMAGE_SIZES: Record<keyof typeof PORTRAIT_IMAGES, Size> = {
|
|
59
|
+
portrait1: { width: 275, height: 500 },
|
|
60
|
+
portrait1s200: { width: 200, height: 364 },
|
|
61
|
+
portrait2: { width: 350, height: 500 },
|
|
62
|
+
portrait3: { width: 300, height: 500 },
|
|
63
|
+
};
|
|
64
|
+
export const IMAGE_SIZES: Record<keyof typeof IMAGES, Size> = {
|
|
65
|
+
...LANDSCAPE_IMAGE_SIZES,
|
|
66
|
+
...PORTRAIT_IMAGE_SIZES,
|
|
67
|
+
...SQUARE_IMAGE_SIZES,
|
|
68
|
+
...AVATAR_IMAGE_SIZES,
|
|
69
|
+
};
|
package/types.d.ts
CHANGED
|
@@ -1237,6 +1237,8 @@ export interface ThumbnailProps extends GenericProps {
|
|
|
1237
1237
|
imgProps?: ImgHTMLProps;
|
|
1238
1238
|
/** Reference to the native <img> element. */
|
|
1239
1239
|
imgRef?: Ref<HTMLImageElement>;
|
|
1240
|
+
/** Set to true to force the display of the loading skeleton. */
|
|
1241
|
+
isLoading?: boolean;
|
|
1240
1242
|
/** Size variant of the component. */
|
|
1241
1243
|
size?: ThumbnailSize;
|
|
1242
1244
|
/** Image loading mode. */
|
package/esm/_internal/clamp.js
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Clamp value in range.
|
|
3
|
-
*
|
|
4
|
-
* @param value Value to clamp.
|
|
5
|
-
* @param min Minimum value.
|
|
6
|
-
* @param max Maximum value.
|
|
7
|
-
* @return Clamped value.
|
|
8
|
-
*/
|
|
9
|
-
var clamp = function clamp(value, min, max) {
|
|
10
|
-
if (value < min) {
|
|
11
|
-
return min;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
if (value > max) {
|
|
15
|
-
return max;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
return value;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export { clamp as c };
|
|
22
|
-
//# sourceMappingURL=clamp.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"clamp.js","sources":["../../../src/utils/clamp.ts"],"sourcesContent":["/**\n * Clamp value in range.\n *\n * @param value Value to clamp.\n * @param min Minimum value.\n * @param max Maximum value.\n * @return Clamped value.\n */\nexport const clamp = (value: number, min: number, max: number): number => {\n if (value < min) {\n return min;\n }\n if (value > max) {\n return max;\n }\n return value;\n};\n"],"names":["clamp","value","min","max"],"mappings":"AAAA;;;;;;;;IAQaA,KAAK,GAAG,SAARA,KAAQ,CAACC,KAAD,EAAgBC,GAAhB,EAA6BC,GAA7B,EAAqD;AACtE,MAAIF,KAAK,GAAGC,GAAZ,EAAiB;AACb,WAAOA,GAAP;AACH;;AACD,MAAID,KAAK,GAAGE,GAAZ,EAAiB;AACb,WAAOA,GAAP;AACH;;AACD,SAAOF,KAAP;AACH;;;;"}
|