@lumx/react 2.1.9 → 2.2.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.
- package/esm/_internal/Avatar2.js +7 -2
- package/esm/_internal/Avatar2.js.map +1 -1
- package/esm/_internal/Slider2.js +21 -2
- package/esm/_internal/Slider2.js.map +1 -1
- package/esm/_internal/Thumbnail2.js +181 -782
- package/esm/_internal/Thumbnail2.js.map +1 -1
- package/esm/_internal/Tooltip2.js +0 -5
- package/esm/_internal/Tooltip2.js.map +1 -1
- package/esm/_internal/UserBlock.js +41 -17
- 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 +1 -4
- package/esm/_internal/thumbnail.js.map +1 -1
- package/esm/_internal/types.js +1 -0
- package/esm/_internal/types.js.map +1 -1
- package/esm/_internal/user-block.js +2 -3
- package/esm/_internal/user-block.js.map +1 -1
- package/esm/index.js +2 -4
- package/esm/index.js.map +1 -1
- package/package.json +4 -4
- package/src/components/avatar/Avatar.stories.tsx +30 -53
- package/src/components/avatar/Avatar.tsx +9 -0
- package/src/components/avatar/__snapshots__/Avatar.test.tsx.snap +220 -357
- package/src/components/image-block/__snapshots__/ImageBlock.test.tsx.snap +1 -1
- package/src/components/mosaic/__snapshots__/Mosaic.test.tsx.snap +30 -30
- package/src/components/post-block/__snapshots__/PostBlock.test.tsx.snap +1 -1
- package/src/components/slideshow/__snapshots__/Slideshow.test.tsx.snap +10 -10
- package/src/components/table/__snapshots__/Table.test.tsx.snap +3 -3
- package/src/components/thumbnail/Thumbnail.stories.tsx +428 -52
- package/src/components/thumbnail/Thumbnail.test.tsx +8 -2
- package/src/components/thumbnail/Thumbnail.tsx +84 -47
- package/src/components/thumbnail/__snapshots__/Thumbnail.test.tsx.snap +28 -81
- package/src/components/thumbnail/index.ts +1 -0
- package/src/components/thumbnail/useFocusPointStyle.tsx +89 -0
- package/src/components/thumbnail/useImageLoad.ts +24 -23
- package/src/components/tooltip/Tooltip.stories.tsx +7 -4
- package/src/components/tooltip/useInjectTooltipRef.tsx +1 -3
- package/src/components/user-block/UserBlock.stories.tsx +65 -105
- package/src/components/user-block/UserBlock.test.tsx +6 -0
- package/src/components/user-block/UserBlock.tsx +50 -25
- package/src/components/user-block/__snapshots__/UserBlock.test.tsx.snap +113 -144
- package/src/stories/generated/Badge/Demos.stories.tsx +1 -0
- package/src/stories/generated/Flag/Demos.stories.tsx +6 -0
- package/src/stories/generated/List/Demos.stories.tsx +2 -0
- package/src/stories/generated/Thumbnail/Demos.stories.tsx +1 -0
- package/src/stories/knobs/focusKnob.ts +1 -1
- package/src/stories/knobs/image.ts +35 -3
- package/src/stories/utils/CustomLink.tsx +7 -0
- package/types.d.ts +21 -4
- package/esm/_internal/clamp.js +0 -22
- package/esm/_internal/clamp.js.map +0 -1
- package/src/components/thumbnail/useClickable.ts +0 -26
- package/src/components/thumbnail/useFocusPoint.ts +0 -154
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
export default { title: 'LumX components/list/List Demos' };
|
|
5
5
|
|
|
6
6
|
export { App as Big } from './big';
|
|
7
|
+
export { App as Clickable } from './clickable';
|
|
7
8
|
export { App as Huge } from './huge';
|
|
9
|
+
export { App as Paddings } from './paddings';
|
|
8
10
|
export { App as Regular } from './regular';
|
|
9
11
|
export { App as Tiny } from './tiny';
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
export default { title: 'LumX components/thumbnail/Thumbnail Demos' };
|
|
5
5
|
|
|
6
6
|
export { App as Combined } from './combined';
|
|
7
|
+
export { App as LoadingError } from './loading-error';
|
|
7
8
|
export { App as Ratios } from './ratios';
|
|
8
9
|
export { App as Sizes } from './sizes';
|
|
9
10
|
export { App as Variants } from './variants';
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { number } from '@storybook/addon-knobs';
|
|
2
2
|
|
|
3
|
-
export const focusKnob = (name: string) => number(name,
|
|
3
|
+
export const focusKnob = (name: string, value = 0) => number(name, value, { max: 1, min: -1, range: true, step: 0.01 });
|
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/* eslint-disable jsx-a11y/anchor-has-content,jsx-a11y/anchor-is-valid */
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Example custom link that can be used in linkAs props.
|
|
6
|
+
*/
|
|
7
|
+
export const CustomLink: any = ({ ...props }) => <a href="#" data-custom-link {...props} />;
|
package/types.d.ts
CHANGED
|
@@ -430,6 +430,10 @@ export interface AvatarProps extends GenericProps {
|
|
|
430
430
|
badge?: ReactElement;
|
|
431
431
|
/** Image URL. */
|
|
432
432
|
image: string;
|
|
433
|
+
/** Props to pass to the link wrapping the thumbnail. */
|
|
434
|
+
linkProps?: React.DetailedHTMLProps<React.AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>;
|
|
435
|
+
/** Custom react component for the link (can be used to inject react router Link). */
|
|
436
|
+
linkAs?: "a" | any;
|
|
433
437
|
/** On click callback. */
|
|
434
438
|
onClick?: MouseEventHandler<HTMLDivElement>;
|
|
435
439
|
/** On key press callback. */
|
|
@@ -1233,6 +1237,8 @@ export interface ThumbnailProps extends GenericProps {
|
|
|
1233
1237
|
imgProps?: ImgHTMLProps;
|
|
1234
1238
|
/** Reference to the native <img> element. */
|
|
1235
1239
|
imgRef?: Ref<HTMLImageElement>;
|
|
1240
|
+
/** Set to true to force the display of the loading skeleton. */
|
|
1241
|
+
isLoading?: boolean;
|
|
1236
1242
|
/** Size variant of the component. */
|
|
1237
1243
|
size?: ThumbnailSize;
|
|
1238
1244
|
/** Image loading mode. */
|
|
@@ -1245,6 +1251,10 @@ export interface ThumbnailProps extends GenericProps {
|
|
|
1245
1251
|
theme?: Theme;
|
|
1246
1252
|
/** Variant of the component. */
|
|
1247
1253
|
variant?: ThumbnailVariant;
|
|
1254
|
+
/** Props to pass to the link wrapping the thumbnail. */
|
|
1255
|
+
linkProps?: React.DetailedHTMLProps<React.AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>;
|
|
1256
|
+
/** Custom react component for the link (can be used to inject react router Link). */
|
|
1257
|
+
linkAs?: "a" | any;
|
|
1248
1258
|
}
|
|
1249
1259
|
/**
|
|
1250
1260
|
* Thumbnail component.
|
|
@@ -2427,6 +2437,7 @@ export interface TextFieldProps extends GenericProps {
|
|
|
2427
2437
|
* @return React element.
|
|
2428
2438
|
*/
|
|
2429
2439
|
export declare const TextField: Comp<TextFieldProps, HTMLDivElement>;
|
|
2440
|
+
export declare const useFocusPointStyle: ({ image, aspectRatio, focusPoint, imgProps: { width, height } }: ThumbnailProps, element: HTMLImageElement | undefined, isLoaded: boolean) => CSSProperties;
|
|
2430
2441
|
/**
|
|
2431
2442
|
* Defines the props of the component.
|
|
2432
2443
|
*/
|
|
@@ -2521,16 +2532,22 @@ export declare type UserBlockSize = Extract<Size, "s" | "m" | "l">;
|
|
|
2521
2532
|
export interface UserBlockProps extends GenericProps {
|
|
2522
2533
|
/** Props to pass to the avatar. */
|
|
2523
2534
|
avatarProps?: AvatarProps;
|
|
2524
|
-
/** Simple action toolbar content. */
|
|
2525
|
-
simpleAction?: ReactNode;
|
|
2526
|
-
/** Multiple action toolbar content. */
|
|
2527
|
-
multipleActions?: ReactNode;
|
|
2528
2535
|
/** Additional fields used to describe the user. */
|
|
2529
2536
|
fields?: string[];
|
|
2537
|
+
/** Props to pass to the link wrapping the avatar thumbnail. */
|
|
2538
|
+
linkProps?: React.DetailedHTMLProps<React.AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>;
|
|
2539
|
+
/** Custom react component for the link (can be used to inject react router Link). */
|
|
2540
|
+
linkAs?: "a" | any;
|
|
2541
|
+
/** Multiple action toolbar content. */
|
|
2542
|
+
multipleActions?: ReactNode;
|
|
2530
2543
|
/** User name. */
|
|
2531
2544
|
name?: string;
|
|
2545
|
+
/** Props to pass to the name block. */
|
|
2546
|
+
nameProps?: GenericProps;
|
|
2532
2547
|
/** Orientation. */
|
|
2533
2548
|
orientation?: Orientation;
|
|
2549
|
+
/** Simple action toolbar content. */
|
|
2550
|
+
simpleAction?: ReactNode;
|
|
2534
2551
|
/** Size variant. */
|
|
2535
2552
|
size?: UserBlockSize;
|
|
2536
2553
|
/** Theme adapting the component to light or dark background. */
|
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;;;;"}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { KeyboardEventHandler, MouseEventHandler, useMemo } from 'react';
|
|
2
|
-
|
|
3
|
-
interface Props {
|
|
4
|
-
role: 'button';
|
|
5
|
-
tabIndex: 0;
|
|
6
|
-
onClick: MouseEventHandler;
|
|
7
|
-
onKeyPress: KeyboardEventHandler;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export const useClickable = ({ onClick, onKeyPress }: { onClick?: any; onKeyPress?: any }): Props | undefined => {
|
|
11
|
-
return useMemo(() => {
|
|
12
|
-
if (!onClick) return undefined;
|
|
13
|
-
|
|
14
|
-
return {
|
|
15
|
-
role: 'button',
|
|
16
|
-
tabIndex: 0,
|
|
17
|
-
onClick,
|
|
18
|
-
onKeyPress(event) {
|
|
19
|
-
onKeyPress?.(event);
|
|
20
|
-
if (event.key === 'Enter' || event.key === ' ') {
|
|
21
|
-
onClick?.();
|
|
22
|
-
}
|
|
23
|
-
},
|
|
24
|
-
};
|
|
25
|
-
}, [onClick, onKeyPress]);
|
|
26
|
-
};
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
import { CSSProperties, MutableRefObject, RefObject, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
-
import { useOnResize } from '@lumx/react/hooks/useOnResize';
|
|
3
|
-
import { AspectRatio } from '@lumx/react';
|
|
4
|
-
import { clamp } from '@lumx/react/utils/clamp';
|
|
5
|
-
import { LoadingState } from '@lumx/react/components/thumbnail/useImageLoad';
|
|
6
|
-
import { Callback } from '@lumx/react/utils';
|
|
7
|
-
import { isEqual } from 'lodash';
|
|
8
|
-
import { FocusPoint } from './types';
|
|
9
|
-
|
|
10
|
-
const IMG_STYLES: CSSProperties = {
|
|
11
|
-
bottom: 0,
|
|
12
|
-
left: 0,
|
|
13
|
-
position: 'absolute',
|
|
14
|
-
right: 0,
|
|
15
|
-
top: 0,
|
|
16
|
-
minHeight: '100%',
|
|
17
|
-
minWidth: '100%',
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
const WRAPPER_STYLES: CSSProperties = {
|
|
21
|
-
overflow: 'hidden',
|
|
22
|
-
position: 'relative',
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
type Sizes = {
|
|
26
|
-
imgWidth?: number;
|
|
27
|
-
imgHeight?: number;
|
|
28
|
-
containerWidth?: number;
|
|
29
|
-
containerHeight?: number;
|
|
30
|
-
aspectRatio?: AspectRatio;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
function calculateSizes(
|
|
34
|
-
imageElement?: HTMLImageElement | null | undefined,
|
|
35
|
-
parentElement?: HTMLElement | null,
|
|
36
|
-
aspectRatio?: AspectRatio,
|
|
37
|
-
): 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
|
-
};
|
|
44
|
-
return { imgWidth, imgHeight, containerWidth, containerHeight, aspectRatio };
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const parseFocusPoint = (point: FocusPoint = {}): Required<FocusPoint> => {
|
|
48
|
-
return { x: point.x ? clamp(point.x, -1, 1) : 0, y: point.y ? clamp(point.y, -1, 1) : 0 };
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
function calculateImageShift(
|
|
52
|
-
containerToImageSizeRatio: number,
|
|
53
|
-
containerSize: number,
|
|
54
|
-
imageSize: number,
|
|
55
|
-
focusSize: number,
|
|
56
|
-
toMinus?: boolean,
|
|
57
|
-
) {
|
|
58
|
-
const containerCenter = Math.floor(containerSize / 2); // Container center in px
|
|
59
|
-
const focusFactor = (focusSize + 1) / 2; // Focus point of resize image in px
|
|
60
|
-
const scaledImage = Math.floor(imageSize / containerToImageSizeRatio); // Can't use width() as images may be display:none
|
|
61
|
-
let focus = Math.floor(focusFactor * scaledImage);
|
|
62
|
-
if (toMinus) {
|
|
63
|
-
focus = scaledImage - focus;
|
|
64
|
-
}
|
|
65
|
-
let focusOffset = focus - containerCenter; // Calculate difference between focus point and center
|
|
66
|
-
const remainder = scaledImage - focus; // Reduce offset if necessary so image remains filled
|
|
67
|
-
const containerRemainder = containerSize - containerCenter;
|
|
68
|
-
if (remainder < containerRemainder) {
|
|
69
|
-
focusOffset -= containerRemainder - remainder;
|
|
70
|
-
}
|
|
71
|
-
if (focusOffset < 0) {
|
|
72
|
-
focusOffset = 0;
|
|
73
|
-
}
|
|
74
|
-
return (focusOffset * -100) / containerSize;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
type Styles = { wrapper: CSSProperties; image: CSSProperties };
|
|
78
|
-
|
|
79
|
-
function calculateImageStyle(sizes: Sizes, point: Required<FocusPoint>): Styles | undefined {
|
|
80
|
-
if (!sizes.imgWidth || !sizes.imgHeight || !sizes.containerWidth || !sizes.containerHeight) {
|
|
81
|
-
return undefined;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Which is over by more?
|
|
85
|
-
const widthRatio = sizes.imgWidth / sizes.containerWidth;
|
|
86
|
-
const heightRatio = sizes.imgHeight / sizes.containerHeight;
|
|
87
|
-
|
|
88
|
-
const image: CSSProperties = { ...IMG_STYLES };
|
|
89
|
-
|
|
90
|
-
// Minimize image while still filling space
|
|
91
|
-
if (sizes.imgWidth > sizes.containerWidth && sizes.imgHeight > sizes.containerHeight) {
|
|
92
|
-
image[widthRatio > heightRatio ? 'maxHeight' : 'maxWidth'] = '100%';
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (widthRatio > heightRatio) {
|
|
96
|
-
image.left = `${calculateImageShift(heightRatio, sizes.containerWidth, sizes.imgWidth, point.x)}%`;
|
|
97
|
-
} else if (widthRatio < heightRatio) {
|
|
98
|
-
image.top = `${calculateImageShift(widthRatio, sizes.containerHeight, sizes.imgHeight, point.y, true)}%`;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
return {
|
|
102
|
-
image,
|
|
103
|
-
wrapper: WRAPPER_STYLES,
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Hook that calculate CSS style to shift the image in it's container according to the focus point.
|
|
109
|
-
*/
|
|
110
|
-
export const useFocusPoint = (options: {
|
|
111
|
-
image: string;
|
|
112
|
-
focusPoint?: FocusPoint;
|
|
113
|
-
aspectRatio?: AspectRatio;
|
|
114
|
-
imgRef: RefObject<HTMLImageElement>;
|
|
115
|
-
loadingState: LoadingState;
|
|
116
|
-
wrapper?: HTMLElement;
|
|
117
|
-
}): Styles | undefined => {
|
|
118
|
-
const { image, aspectRatio, focusPoint, imgRef, loadingState, wrapper } = options;
|
|
119
|
-
|
|
120
|
-
const point = parseFocusPoint(focusPoint);
|
|
121
|
-
|
|
122
|
-
const previousPoint = useRef<Required<FocusPoint>>();
|
|
123
|
-
const previousSizes = useRef<Sizes>({});
|
|
124
|
-
const [style, setStyle] = useState<Styles>();
|
|
125
|
-
|
|
126
|
-
// Update style.
|
|
127
|
-
const updateRef = useRef<Callback>(null);
|
|
128
|
-
const update = useMemo(
|
|
129
|
-
() => {
|
|
130
|
-
const updateFunction = () => {
|
|
131
|
-
const sizes = calculateSizes(imgRef?.current, wrapper, aspectRatio);
|
|
132
|
-
if (!sizes || (isEqual(sizes, previousSizes.current) && isEqual(point, previousPoint.current))) {
|
|
133
|
-
// Nothing changed.
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
setStyle(calculateImageStyle(sizes, point));
|
|
137
|
-
previousPoint.current = point;
|
|
138
|
-
previousSizes.current = sizes;
|
|
139
|
-
};
|
|
140
|
-
(updateRef as MutableRefObject<Callback>).current = updateFunction;
|
|
141
|
-
return updateFunction;
|
|
142
|
-
},
|
|
143
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
144
|
-
[...Object.values(point), imgRef, wrapper, aspectRatio],
|
|
145
|
-
);
|
|
146
|
-
|
|
147
|
-
// Update on image loaded.
|
|
148
|
-
useLayoutEffect(update, [image, update, loadingState]);
|
|
149
|
-
|
|
150
|
-
// Update on parent resize.
|
|
151
|
-
useOnResize(wrapper, updateRef);
|
|
152
|
-
|
|
153
|
-
return style;
|
|
154
|
-
};
|