@lumx/react 2.1.9-alpha-thumbnail9 → 2.1.9-alpha-thumbnail13
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/ImageBlock.js +1 -0
- package/esm/_internal/ImageBlock.js.map +1 -1
- package/esm/_internal/Thumbnail2.js +77 -25
- package/esm/_internal/Thumbnail2.js.map +1 -1
- package/esm/_internal/thumbnail.js +1 -1
- package/esm/_internal/types.js +1 -0
- package/esm/_internal/types.js.map +1 -1
- package/esm/index.js +1 -1
- package/package.json +4 -4
- package/src/components/image-block/ImageBlock.tsx +1 -0
- package/src/components/thumbnail/Thumbnail.stories.tsx +39 -0
- package/src/components/thumbnail/Thumbnail.tsx +10 -13
- package/src/components/thumbnail/index.ts +1 -0
- package/src/components/thumbnail/useFocusPointStyle.tsx +55 -0
- package/src/components/thumbnail/useImageLoad.ts +6 -6
- package/src/stories/knobs/focusKnob.ts +1 -1
- package/types.d.ts +1 -0
|
@@ -68,6 +68,7 @@ var ImageBlock = forwardRef(function (props, ref) {
|
|
|
68
68
|
}), fillHeight && "".concat(CLASSNAME, "--fill-height"))
|
|
69
69
|
}), React.createElement(Thumbnail, _extends({}, thumbnailProps, {
|
|
70
70
|
className: classnames("".concat(CLASSNAME, "__image"), thumbnailProps === null || thumbnailProps === void 0 ? void 0 : thumbnailProps.className),
|
|
71
|
+
fillHeight: fillHeight,
|
|
71
72
|
align: align,
|
|
72
73
|
image: image,
|
|
73
74
|
size: size,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ImageBlock.js","sources":["../../../src/components/image-block/ImageBlock.tsx"],"sourcesContent":["import React, { CSSProperties, forwardRef, ReactNode } from 'react';\n\nimport classNames from 'classnames';\n\nimport isObject from 'lodash/isObject';\n\nimport { Alignment, HorizontalAlignment, Size, Theme, Thumbnail } from '@lumx/react';\n\nimport { Comp, GenericProps, getRootClassName, handleBasicClasses, ValueOf } from '@lumx/react/utils';\nimport { ThumbnailProps } from '../thumbnail/Thumbnail';\n\n/**\n * Image block variants.\n */\nexport const ImageBlockCaptionPosition = {\n below: 'below',\n over: 'over',\n} as const;\nexport type ImageBlockCaptionPosition = ValueOf<typeof ImageBlockCaptionPosition>;\n\n/**\n * Image block sizes.\n */\nexport type ImageBlockSize = Extract<Size, 'xl' | 'xxl'>;\n\n/**\n * Defines the props of the component.\n */\nexport interface ImageBlockProps extends GenericProps {\n /** Action toolbar content. */\n actions?: ReactNode;\n /** Alignment. */\n align?: HorizontalAlignment;\n /** Image alternative text. */\n alt: string;\n /** Caption position. */\n captionPosition?: ImageBlockCaptionPosition;\n /** Caption custom CSS style. */\n captionStyle?: CSSProperties;\n /** Image description. Can be either a string, or sanitized html. */\n description?: string | { __html: string };\n /** Whether the image has to fill its container height or not. */\n fillHeight?: boolean;\n /** Image URL. */\n image: string;\n /** Size variant. */\n size?: ImageBlockSize;\n /** Tag content. */\n tags?: ReactNode;\n /** Theme adapting the component to light or dark background. */\n theme?: Theme;\n /** Props to pass to the thumbnail (minus those already set by the ImageBlock props). */\n thumbnailProps?: Omit<ThumbnailProps, 'image' | 'size' | 'theme' | 'align' | 'fillHeight'>;\n /** Image title to display in the caption. */\n title?: string;\n}\n\n/**\n * Component display name.\n */\nconst COMPONENT_NAME = 'ImageBlock';\n\n/**\n * Component default class name and class prefix.\n */\nconst CLASSNAME = getRootClassName(COMPONENT_NAME);\n\n/**\n * Component default props.\n */\nconst DEFAULT_PROPS: Partial<ImageBlockProps> = {\n captionPosition: ImageBlockCaptionPosition.below,\n theme: Theme.light,\n align: Alignment.left,\n};\n\n/**\n * ImageBlock component.\n *\n * @param props Component props.\n * @param ref Component ref.\n * @return React element.\n */\nexport const ImageBlock: Comp<ImageBlockProps, HTMLDivElement> = forwardRef((props, ref) => {\n const {\n actions,\n align,\n alt,\n captionPosition,\n captionStyle,\n className,\n description,\n fillHeight,\n image,\n size,\n tags,\n theme,\n thumbnailProps,\n title,\n ...forwardedProps\n } = props;\n return (\n <figure\n ref={ref}\n {...forwardedProps}\n className={classNames(\n className,\n handleBasicClasses({\n prefix: CLASSNAME,\n captionPosition,\n align,\n size,\n theme,\n }),\n fillHeight && `${CLASSNAME}--fill-height`,\n )}\n >\n <Thumbnail\n {...thumbnailProps}\n className={classNames(`${CLASSNAME}__image`, thumbnailProps?.className)}\n align={align}\n image={image}\n size={size}\n theme={theme}\n alt={(alt || title) as string}\n />\n {(title || description || tags) && (\n <figcaption className={`${CLASSNAME}__wrapper`} style={captionStyle}>\n {(title || description) && (\n <div className={`${CLASSNAME}__caption`}>\n {title && <span className={`${CLASSNAME}__title`}>{title}</span>}\n {/* Add an ` ` when there is description and title. */}\n {title && description && '\\u00A0'}\n {isObject(description) && description.__html ? (\n // eslint-disable-next-line react/no-danger\n <span dangerouslySetInnerHTML={description} className={`${CLASSNAME}__description`} />\n ) : (\n <span className={`${CLASSNAME}__description`}>{description}</span>\n )}\n </div>\n )}\n {tags && <div className={`${CLASSNAME}__tags`}>{tags}</div>}\n </figcaption>\n )}\n {actions && <div className={`${CLASSNAME}__actions`}>{actions}</div>}\n </figure>\n );\n});\nImageBlock.displayName = COMPONENT_NAME;\nImageBlock.className = CLASSNAME;\nImageBlock.defaultProps = DEFAULT_PROPS;\n"],"names":["ImageBlockCaptionPosition","below","over","COMPONENT_NAME","CLASSNAME","getRootClassName","DEFAULT_PROPS","captionPosition","theme","Theme","light","align","Alignment","left","ImageBlock","forwardRef","props","ref","actions","alt","captionStyle","className","description","fillHeight","image","size","tags","thumbnailProps","title","forwardedProps","classNames","handleBasicClasses","prefix","isObject","__html","displayName","defaultProps"],"mappings":";;;;;;;AAWA;;;IAGaA,yBAAyB,GAAG;AACrCC,EAAAA,KAAK,EAAE,OAD8B;AAErCC,EAAAA,IAAI,EAAE;AAF+B;;AA2CzC;;;AAGA,IAAMC,cAAc,GAAG,YAAvB;AAEA;;;;AAGA,IAAMC,SAAS,GAAGC,gBAAgB,CAACF,cAAD,CAAlC;AAEA;;;;AAGA,IAAMG,aAAuC,GAAG;AAC5CC,EAAAA,eAAe,EAAEP,yBAAyB,CAACC,KADC;AAE5CO,EAAAA,KAAK,EAAEC,KAAK,CAACC,KAF+B;AAG5CC,EAAAA,KAAK,EAAEC,SAAS,CAACC;AAH2B,CAAhD;AAMA;;;;;;;;IAOaC,UAAiD,GAAGC,UAAU,CAAC,UAACC,KAAD,EAAQC,GAAR,EAAgB;AAAA,MAEpFC,OAFoF,GAiBpFF,KAjBoF,CAEpFE,OAFoF;AAAA,MAGpFP,KAHoF,GAiBpFK,KAjBoF,CAGpFL,KAHoF;AAAA,MAIpFQ,GAJoF,GAiBpFH,KAjBoF,CAIpFG,GAJoF;AAAA,MAKpFZ,eALoF,GAiBpFS,KAjBoF,CAKpFT,eALoF;AAAA,MAMpFa,YANoF,GAiBpFJ,KAjBoF,CAMpFI,YANoF;AAAA,MAOpFC,SAPoF,GAiBpFL,KAjBoF,CAOpFK,SAPoF;AAAA,MAQpFC,WARoF,GAiBpFN,KAjBoF,CAQpFM,WARoF;AAAA,MASpFC,UAToF,GAiBpFP,KAjBoF,CASpFO,UAToF;AAAA,MAUpFC,KAVoF,GAiBpFR,KAjBoF,CAUpFQ,KAVoF;AAAA,MAWpFC,IAXoF,GAiBpFT,KAjBoF,CAWpFS,IAXoF;AAAA,MAYpFC,IAZoF,GAiBpFV,KAjBoF,CAYpFU,IAZoF;AAAA,MAapFlB,KAboF,GAiBpFQ,KAjBoF,CAapFR,KAboF;AAAA,MAcpFmB,cAdoF,GAiBpFX,KAjBoF,CAcpFW,cAdoF;AAAA,MAepFC,KAfoF,GAiBpFZ,KAjBoF,CAepFY,KAfoF;AAAA,MAgBjFC,cAhBiF,4BAiBpFb,KAjBoF;;AAkBxF,SACI;AACI,IAAA,GAAG,EAAEC;AADT,KAEQY,cAFR;AAGI,IAAA,SAAS,EAAEC,UAAU,CACjBT,SADiB,EAEjBU,kBAAkB,CAAC;AACfC,MAAAA,MAAM,EAAE5B,SADO;AAEfG,MAAAA,eAAe,EAAfA,eAFe;AAGfI,MAAAA,KAAK,EAALA,KAHe;AAIfc,MAAAA,IAAI,EAAJA,IAJe;AAKfjB,MAAAA,KAAK,EAALA;AALe,KAAD,CAFD,EASjBe,UAAU,cAAOnB,SAAP,kBATO;AAHzB,MAeI,oBAAC,SAAD,eACQuB,cADR;AAEI,IAAA,SAAS,EAAEG,UAAU,WAAI1B,SAAJ,cAAwBuB,cAAxB,aAAwBA,cAAxB,uBAAwBA,cAAc,CAAEN,SAAxC,CAFzB;AAGI,IAAA,
|
|
1
|
+
{"version":3,"file":"ImageBlock.js","sources":["../../../src/components/image-block/ImageBlock.tsx"],"sourcesContent":["import React, { CSSProperties, forwardRef, ReactNode } from 'react';\n\nimport classNames from 'classnames';\n\nimport isObject from 'lodash/isObject';\n\nimport { Alignment, HorizontalAlignment, Size, Theme, Thumbnail } from '@lumx/react';\n\nimport { Comp, GenericProps, getRootClassName, handleBasicClasses, ValueOf } from '@lumx/react/utils';\nimport { ThumbnailProps } from '../thumbnail/Thumbnail';\n\n/**\n * Image block variants.\n */\nexport const ImageBlockCaptionPosition = {\n below: 'below',\n over: 'over',\n} as const;\nexport type ImageBlockCaptionPosition = ValueOf<typeof ImageBlockCaptionPosition>;\n\n/**\n * Image block sizes.\n */\nexport type ImageBlockSize = Extract<Size, 'xl' | 'xxl'>;\n\n/**\n * Defines the props of the component.\n */\nexport interface ImageBlockProps extends GenericProps {\n /** Action toolbar content. */\n actions?: ReactNode;\n /** Alignment. */\n align?: HorizontalAlignment;\n /** Image alternative text. */\n alt: string;\n /** Caption position. */\n captionPosition?: ImageBlockCaptionPosition;\n /** Caption custom CSS style. */\n captionStyle?: CSSProperties;\n /** Image description. Can be either a string, or sanitized html. */\n description?: string | { __html: string };\n /** Whether the image has to fill its container height or not. */\n fillHeight?: boolean;\n /** Image URL. */\n image: string;\n /** Size variant. */\n size?: ImageBlockSize;\n /** Tag content. */\n tags?: ReactNode;\n /** Theme adapting the component to light or dark background. */\n theme?: Theme;\n /** Props to pass to the thumbnail (minus those already set by the ImageBlock props). */\n thumbnailProps?: Omit<ThumbnailProps, 'image' | 'size' | 'theme' | 'align' | 'fillHeight'>;\n /** Image title to display in the caption. */\n title?: string;\n}\n\n/**\n * Component display name.\n */\nconst COMPONENT_NAME = 'ImageBlock';\n\n/**\n * Component default class name and class prefix.\n */\nconst CLASSNAME = getRootClassName(COMPONENT_NAME);\n\n/**\n * Component default props.\n */\nconst DEFAULT_PROPS: Partial<ImageBlockProps> = {\n captionPosition: ImageBlockCaptionPosition.below,\n theme: Theme.light,\n align: Alignment.left,\n};\n\n/**\n * ImageBlock component.\n *\n * @param props Component props.\n * @param ref Component ref.\n * @return React element.\n */\nexport const ImageBlock: Comp<ImageBlockProps, HTMLDivElement> = forwardRef((props, ref) => {\n const {\n actions,\n align,\n alt,\n captionPosition,\n captionStyle,\n className,\n description,\n fillHeight,\n image,\n size,\n tags,\n theme,\n thumbnailProps,\n title,\n ...forwardedProps\n } = props;\n return (\n <figure\n ref={ref}\n {...forwardedProps}\n className={classNames(\n className,\n handleBasicClasses({\n prefix: CLASSNAME,\n captionPosition,\n align,\n size,\n theme,\n }),\n fillHeight && `${CLASSNAME}--fill-height`,\n )}\n >\n <Thumbnail\n {...thumbnailProps}\n className={classNames(`${CLASSNAME}__image`, thumbnailProps?.className)}\n fillHeight={fillHeight}\n align={align}\n image={image}\n size={size}\n theme={theme}\n alt={(alt || title) as string}\n />\n {(title || description || tags) && (\n <figcaption className={`${CLASSNAME}__wrapper`} style={captionStyle}>\n {(title || description) && (\n <div className={`${CLASSNAME}__caption`}>\n {title && <span className={`${CLASSNAME}__title`}>{title}</span>}\n {/* Add an ` ` when there is description and title. */}\n {title && description && '\\u00A0'}\n {isObject(description) && description.__html ? (\n // eslint-disable-next-line react/no-danger\n <span dangerouslySetInnerHTML={description} className={`${CLASSNAME}__description`} />\n ) : (\n <span className={`${CLASSNAME}__description`}>{description}</span>\n )}\n </div>\n )}\n {tags && <div className={`${CLASSNAME}__tags`}>{tags}</div>}\n </figcaption>\n )}\n {actions && <div className={`${CLASSNAME}__actions`}>{actions}</div>}\n </figure>\n );\n});\nImageBlock.displayName = COMPONENT_NAME;\nImageBlock.className = CLASSNAME;\nImageBlock.defaultProps = DEFAULT_PROPS;\n"],"names":["ImageBlockCaptionPosition","below","over","COMPONENT_NAME","CLASSNAME","getRootClassName","DEFAULT_PROPS","captionPosition","theme","Theme","light","align","Alignment","left","ImageBlock","forwardRef","props","ref","actions","alt","captionStyle","className","description","fillHeight","image","size","tags","thumbnailProps","title","forwardedProps","classNames","handleBasicClasses","prefix","isObject","__html","displayName","defaultProps"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { d as _slicedToArray, b as _objectWithoutProperties, _ as _objectSpread2, c as _extends } from './_rollupPluginBabelHelpers.js';
|
|
2
|
-
import { Size, Theme
|
|
3
|
-
import React, { useState, useEffect,
|
|
2
|
+
import { AspectRatio, Size, Theme } from './components.js';
|
|
3
|
+
import React, { useState, useEffect, useMemo, forwardRef } from 'react';
|
|
4
4
|
import { g as getRootClassName, c as classnames, h as handleBasicClasses } from './getRootClassName.js';
|
|
5
5
|
import { r as mdiImageBroken } from './mdi.js';
|
|
6
6
|
import { m as mergeRefs } from './mergeRefs.js';
|
|
@@ -22,20 +22,18 @@ function getState(img, event) {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
function useImageLoad(imageURL, imgRef) {
|
|
25
|
-
var
|
|
26
|
-
|
|
27
|
-
var _useState = useState(getState(imgRef === null || imgRef === void 0 ? void 0 : imgRef.current)),
|
|
25
|
+
var _useState = useState(getState(imgRef)),
|
|
28
26
|
_useState2 = _slicedToArray(_useState, 2),
|
|
29
27
|
state = _useState2[0],
|
|
30
28
|
setState = _useState2[1]; // Update state when changing image URL or DOM reference.
|
|
31
29
|
|
|
32
30
|
|
|
33
31
|
useEffect(function () {
|
|
34
|
-
setState(getState(imgRef
|
|
32
|
+
setState(getState(imgRef));
|
|
35
33
|
}, [imageURL, imgRef]); // Listen to `load` and `error` event on image
|
|
36
34
|
|
|
37
35
|
useEffect(function () {
|
|
38
|
-
var img = imgRef
|
|
36
|
+
var img = imgRef;
|
|
39
37
|
if (!img) return undefined;
|
|
40
38
|
|
|
41
39
|
var update = function update(event) {
|
|
@@ -48,10 +46,68 @@ function useImageLoad(imageURL, imgRef) {
|
|
|
48
46
|
img.removeEventListener('load', update);
|
|
49
47
|
img.removeEventListener('error', update);
|
|
50
48
|
};
|
|
51
|
-
}, [imgRef, imgRef === null || imgRef === void 0 ? void 0 :
|
|
49
|
+
}, [imgRef, imgRef === null || imgRef === void 0 ? void 0 : imgRef.src]);
|
|
52
50
|
return state;
|
|
53
51
|
}
|
|
54
52
|
|
|
53
|
+
function shiftPosition(scale, containerSize, imageSize, focusSize, isVertical) {
|
|
54
|
+
if (!focusSize) return 50;
|
|
55
|
+
var focusFactor = (focusSize + 1) / 2;
|
|
56
|
+
var scaledSize = Math.floor(imageSize / scale);
|
|
57
|
+
var focus = Math.floor(focusFactor * scaledSize);
|
|
58
|
+
if (isVertical) focus = scaledSize - focus;
|
|
59
|
+
var containerCenter = Math.floor(containerSize / 2);
|
|
60
|
+
var focusOffset = focus - containerCenter;
|
|
61
|
+
var remainder = scaledSize - focus;
|
|
62
|
+
if (remainder < containerCenter) focusOffset -= containerCenter - remainder;
|
|
63
|
+
if (focusOffset < 0) return 0;
|
|
64
|
+
return Math.min(100, Math.floor(focusOffset * 100 / containerSize));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
var useFocusPointStyle = function useFocusPointStyle(_ref, element, isLoaded) {
|
|
68
|
+
var image = _ref.image,
|
|
69
|
+
aspectRatio = _ref.aspectRatio,
|
|
70
|
+
focusPoint = _ref.focusPoint,
|
|
71
|
+
_ref$imgProps = _ref.imgProps;
|
|
72
|
+
_ref$imgProps = _ref$imgProps === void 0 ? {} : _ref$imgProps;
|
|
73
|
+
var width = _ref$imgProps.width,
|
|
74
|
+
height = _ref$imgProps.height;
|
|
75
|
+
// Get natural image size from imgProps or img element.
|
|
76
|
+
var naturalSize = useMemo(function () {
|
|
77
|
+
if (!image || aspectRatio === AspectRatio.original || !(focusPoint === null || focusPoint === void 0 ? void 0 : focusPoint.x) && !(focusPoint === null || focusPoint === void 0 ? void 0 : focusPoint.y)) return undefined;
|
|
78
|
+
if (typeof width === 'number' && typeof height === 'number') return {
|
|
79
|
+
width: width,
|
|
80
|
+
height: height
|
|
81
|
+
};
|
|
82
|
+
if (element && isLoaded) return {
|
|
83
|
+
width: element.naturalWidth,
|
|
84
|
+
height: element.naturalHeight
|
|
85
|
+
};
|
|
86
|
+
return undefined;
|
|
87
|
+
}, [aspectRatio, element, focusPoint === null || focusPoint === void 0 ? void 0 : focusPoint.x, focusPoint === null || focusPoint === void 0 ? void 0 : focusPoint.y, height, image, isLoaded, width]); // Compute focus point CSS style.
|
|
88
|
+
|
|
89
|
+
return useMemo(function () {
|
|
90
|
+
if (aspectRatio === AspectRatio.original || !(focusPoint === null || focusPoint === void 0 ? void 0 : focusPoint.x) && !(focusPoint === null || focusPoint === void 0 ? void 0 : focusPoint.y)) return {};
|
|
91
|
+
|
|
92
|
+
if (element && naturalSize) {
|
|
93
|
+
var actualWidth = element.offsetWidth;
|
|
94
|
+
var actualHeight = element.offsetHeight;
|
|
95
|
+
var heightScale = actualHeight / naturalSize.height;
|
|
96
|
+
var widthScale = actualWidth / naturalSize.width;
|
|
97
|
+
var x = shiftPosition(heightScale, actualWidth, naturalSize.width, focusPoint === null || focusPoint === void 0 ? void 0 : focusPoint.x);
|
|
98
|
+
var y = shiftPosition(widthScale, actualHeight, naturalSize.height, focusPoint === null || focusPoint === void 0 ? void 0 : focusPoint.y, true);
|
|
99
|
+
return {
|
|
100
|
+
objectPosition: "".concat(x, "% ").concat(y, "%")
|
|
101
|
+
};
|
|
102
|
+
} // Focus point can't be computed yet => We hide the image until it can.
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
visibility: 'hidden'
|
|
107
|
+
};
|
|
108
|
+
}, [aspectRatio, element, focusPoint, naturalSize]);
|
|
109
|
+
};
|
|
110
|
+
|
|
55
111
|
/**
|
|
56
112
|
* Component display name.
|
|
57
113
|
*/
|
|
@@ -70,13 +126,6 @@ var DEFAULT_PROPS = {
|
|
|
70
126
|
loading: 'lazy',
|
|
71
127
|
theme: Theme.light
|
|
72
128
|
};
|
|
73
|
-
|
|
74
|
-
function getObjectPosition(aspectRatio, focusPoint) {
|
|
75
|
-
if (aspectRatio === AspectRatio.original || !(focusPoint === null || focusPoint === void 0 ? void 0 : focusPoint.y) && !(focusPoint === null || focusPoint === void 0 ? void 0 : focusPoint.x)) return undefined;
|
|
76
|
-
var x = Math.round(Math.abs((((focusPoint === null || focusPoint === void 0 ? void 0 : focusPoint.x) || 0) + 1) / 2) * 100);
|
|
77
|
-
var y = Math.round(Math.abs((((focusPoint === null || focusPoint === void 0 ? void 0 : focusPoint.y) || 0) - 1) / 2) * 100);
|
|
78
|
-
return "".concat(x, "% ").concat(y, "%");
|
|
79
|
-
}
|
|
80
129
|
/**
|
|
81
130
|
* Thumbnail component.
|
|
82
131
|
*
|
|
@@ -85,7 +134,6 @@ function getObjectPosition(aspectRatio, focusPoint) {
|
|
|
85
134
|
* @return React element.
|
|
86
135
|
*/
|
|
87
136
|
|
|
88
|
-
|
|
89
137
|
var Thumbnail = forwardRef(function (props, ref) {
|
|
90
138
|
var align = props.align,
|
|
91
139
|
alt = props.alt,
|
|
@@ -109,11 +157,18 @@ var Thumbnail = forwardRef(function (props, ref) {
|
|
|
109
157
|
linkAs = props.linkAs,
|
|
110
158
|
forwardedProps = _objectWithoutProperties(props, ["align", "alt", "aspectRatio", "badge", "className", "crossOrigin", "fallback", "fillHeight", "focusPoint", "image", "imgProps", "imgRef", "isLoading", "loading", "size", "theme", "variant", "linkProps", "linkAs"]);
|
|
111
159
|
|
|
112
|
-
var
|
|
160
|
+
var _useState = useState(),
|
|
161
|
+
_useState2 = _slicedToArray(_useState, 2),
|
|
162
|
+
imgElement = _useState2[0],
|
|
163
|
+
setImgElement = _useState2[1]; // Image loading state.
|
|
164
|
+
|
|
113
165
|
|
|
114
|
-
var loadingState = useImageLoad(image,
|
|
166
|
+
var loadingState = useImageLoad(image, imgElement);
|
|
167
|
+
var isLoaded = loadingState === 'isLoaded';
|
|
115
168
|
var isLoading = isLoadingProp || loadingState === 'isLoading';
|
|
116
|
-
var hasError = loadingState === 'hasError';
|
|
169
|
+
var hasError = loadingState === 'hasError'; // Focus point.
|
|
170
|
+
|
|
171
|
+
var focusPointStyle = useFocusPointStyle(props, imgElement, isLoaded);
|
|
117
172
|
var hasIconErrorFallback = hasError && typeof fallback === 'string';
|
|
118
173
|
var hasCustomErrorFallback = hasError && !hasIconErrorFallback;
|
|
119
174
|
var imageErrorStyle = {};
|
|
@@ -159,11 +214,8 @@ var Thumbnail = forwardRef(function (props, ref) {
|
|
|
159
214
|
}), React.createElement("div", {
|
|
160
215
|
className: "".concat(CLASSNAME, "__background")
|
|
161
216
|
}, React.createElement("img", _extends({}, imgProps, {
|
|
162
|
-
style: _objectSpread2({}, imgProps === null || imgProps === void 0 ? void 0 : imgProps.style, {}, imageErrorStyle, {
|
|
163
|
-
|
|
164
|
-
objectPosition: getObjectPosition(aspectRatio, focusPoint)
|
|
165
|
-
}),
|
|
166
|
-
ref: mergeRefs(imgRef, propImgRef),
|
|
217
|
+
style: _objectSpread2({}, imgProps === null || imgProps === void 0 ? void 0 : imgProps.style, {}, imageErrorStyle, {}, focusPointStyle),
|
|
218
|
+
ref: mergeRefs(setImgElement, propImgRef),
|
|
167
219
|
className: classnames("".concat(CLASSNAME, "__image"), isLoading && "".concat(CLASSNAME, "__image--is-loading")),
|
|
168
220
|
crossOrigin: crossOrigin,
|
|
169
221
|
src: image,
|
|
@@ -183,5 +235,5 @@ Thumbnail.displayName = COMPONENT_NAME;
|
|
|
183
235
|
Thumbnail.className = CLASSNAME;
|
|
184
236
|
Thumbnail.defaultProps = DEFAULT_PROPS;
|
|
185
237
|
|
|
186
|
-
export { Thumbnail as T };
|
|
238
|
+
export { Thumbnail as T, useFocusPointStyle as u };
|
|
187
239
|
//# sourceMappingURL=Thumbnail2.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Thumbnail2.js","sources":["../../../src/components/thumbnail/useImageLoad.ts","../../../src/components/thumbnail/Thumbnail.tsx"],"sourcesContent":["import { RefObject, useEffect, useState } from 'react';\n\nexport type LoadingState = 'isLoading' | 'isLoaded' | 'hasError';\n\nfunction getState(img: HTMLImageElement | null | undefined, event?: Event) {\n // Error event occurred or image loaded empty.\n if (event?.type === 'error' || (img?.complete && (img?.naturalWidth === 0 || img?.naturalHeight === 0))) {\n return 'hasError';\n }\n // Image is undefined or incomplete.\n if (!img || !img.complete) {\n return 'isLoading';\n }\n // Else loaded.\n return 'isLoaded';\n}\n\nexport function useImageLoad(imageURL: string, imgRef?: RefObject<HTMLImageElement>): LoadingState {\n const [state, setState] = useState<LoadingState>(getState(imgRef?.current));\n\n // Update state when changing image URL or DOM reference.\n useEffect(() => {\n setState(getState(imgRef?.current));\n }, [imageURL, imgRef]);\n\n // Listen to `load` and `error` event on image\n useEffect(() => {\n const img = imgRef?.current;\n if (!img) return undefined;\n const update = (event?: Event) => setState(getState(img, event));\n img.addEventListener('load', update);\n img.addEventListener('error', update);\n return () => {\n img.removeEventListener('load', update);\n img.removeEventListener('error', update);\n };\n }, [imgRef, imgRef?.current?.src]);\n\n return state;\n}\n","import React, {\n CSSProperties,\n forwardRef,\n ImgHTMLAttributes,\n KeyboardEventHandler,\n MouseEventHandler,\n ReactElement,\n ReactNode,\n Ref,\n useRef,\n} from 'react';\nimport classNames from 'classnames';\n\nimport { AspectRatio, HorizontalAlignment, Icon, Size, Theme } from '@lumx/react';\n\nimport { Comp, GenericProps, getRootClassName, handleBasicClasses } from '@lumx/react/utils';\n\nimport { mdiImageBroken } from '@lumx/icons';\nimport { mergeRefs } from '@lumx/react/utils/mergeRefs';\nimport { useImageLoad } from '@lumx/react/components/thumbnail/useImageLoad';\nimport { FocusPoint, ThumbnailSize, ThumbnailVariant } from './types';\n\ntype ImgHTMLProps = ImgHTMLAttributes<HTMLImageElement>;\n\n/**\n * Defines the props of the component.\n */\nexport interface ThumbnailProps extends GenericProps {\n /** Alignment of the thumbnail in it's parent (requires flex parent). */\n align?: HorizontalAlignment;\n /** Image alternative text. */\n alt: string;\n /** Image aspect ratio. */\n aspectRatio?: AspectRatio;\n /** Badge. */\n badge?: ReactElement;\n /** Image cross origin resource policy. */\n crossOrigin?: ImgHTMLProps['crossOrigin'];\n /** Fallback icon (SVG path) or react node when image fails to load. */\n fallback?: string | ReactNode;\n /** Whether the thumbnail should fill it's parent size (requires flex parent) or not. */\n fillHeight?: boolean;\n /** Apply relative vertical and horizontal shift (from -1 to 1) on the image position inside the thumbnail. */\n focusPoint?: FocusPoint;\n /** Image URL. */\n image: string;\n /** Props to inject into the native <img> element. */\n imgProps?: ImgHTMLProps;\n /** Reference to the native <img> element. */\n imgRef?: Ref<HTMLImageElement>;\n /** Set to true to force the display of the loading skeleton. */\n isLoading?: boolean;\n /** Size variant of the component. */\n size?: ThumbnailSize;\n /** Image loading mode. */\n loading?: ImgHTMLProps['loading'];\n /** On click callback. */\n onClick?: MouseEventHandler<HTMLDivElement>;\n /** On key press callback. */\n onKeyPress?: KeyboardEventHandler<HTMLDivElement>;\n /** Theme adapting the component to light or dark background. */\n theme?: Theme;\n /** Variant of the component. */\n variant?: ThumbnailVariant;\n /** Props to pass to the link wrapping the thumbnail. */\n linkProps?: React.DetailedHTMLProps<React.AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>;\n /** Custom react component for the link (can be used to inject react router Link). */\n linkAs?: 'a' | any;\n}\n\n/**\n * Component display name.\n */\nconst COMPONENT_NAME = 'Thumbnail';\n\n/**\n * Component default class name and class prefix.\n */\nconst CLASSNAME = getRootClassName(COMPONENT_NAME);\n\n/**\n * Component default props.\n */\nconst DEFAULT_PROPS: Partial<ThumbnailProps> = {\n fallback: mdiImageBroken,\n loading: 'lazy',\n theme: Theme.light,\n};\n\nfunction getObjectPosition(aspectRatio: AspectRatio, focusPoint?: FocusPoint) {\n if (aspectRatio === AspectRatio.original || (!focusPoint?.y && !focusPoint?.x)) return undefined;\n const x = Math.round(Math.abs(((focusPoint?.x || 0) + 1) / 2) * 100);\n const y = Math.round(Math.abs(((focusPoint?.y || 0) - 1) / 2) * 100);\n return `${x}% ${y}%`;\n}\n\n/**\n * Thumbnail component.\n *\n * @param props Component props.\n * @param ref Component ref.\n * @return React element.\n */\nexport const Thumbnail: Comp<ThumbnailProps> = forwardRef((props, ref) => {\n const {\n align,\n alt,\n aspectRatio = AspectRatio.original,\n badge,\n className,\n crossOrigin,\n fallback,\n fillHeight,\n focusPoint,\n image,\n imgProps,\n imgRef: propImgRef,\n isLoading: isLoadingProp,\n loading,\n size,\n theme,\n variant,\n linkProps,\n linkAs,\n ...forwardedProps\n } = props;\n const imgRef = useRef<HTMLImageElement>(null);\n\n // Image loading state.\n const loadingState = useImageLoad(image, imgRef);\n const isLoading = isLoadingProp || loadingState === 'isLoading';\n const hasError = loadingState === 'hasError';\n\n const hasIconErrorFallback = hasError && typeof fallback === 'string';\n const hasCustomErrorFallback = hasError && !hasIconErrorFallback;\n const imageErrorStyle: CSSProperties = {};\n if (hasIconErrorFallback) {\n // Keep the image layout on icon fallback.\n imageErrorStyle.visibility = 'hidden';\n } else if (hasCustomErrorFallback) {\n // Remove the image on custom fallback.\n imageErrorStyle.display = 'none';\n }\n\n const isLink = Boolean(linkProps?.href || linkAs);\n const isButton = !!forwardedProps.onClick;\n const isClickable = isButton || isLink;\n\n let Wrapper: any = 'div';\n const wrapperProps = { ...forwardedProps };\n if (isLink) {\n Wrapper = linkAs || 'a';\n Object.assign(wrapperProps, linkProps);\n } else if (isButton) {\n Wrapper = 'button';\n }\n\n return (\n <Wrapper\n {...wrapperProps}\n ref={ref}\n className={classNames(\n linkProps?.className,\n className,\n handleBasicClasses({\n align,\n aspectRatio,\n prefix: CLASSNAME,\n size,\n theme,\n variant,\n isClickable,\n hasError,\n hasIconErrorFallback,\n hasCustomErrorFallback,\n isLoading,\n hasBadge: !!badge,\n }),\n fillHeight && `${CLASSNAME}--fill-height`,\n )}\n >\n <div className={`${CLASSNAME}__background`}>\n <img\n {...imgProps}\n style={{\n ...imgProps?.style,\n ...imageErrorStyle,\n // Focus point.\n objectPosition: getObjectPosition(aspectRatio, focusPoint),\n }}\n ref={mergeRefs(imgRef, propImgRef)}\n className={classNames(`${CLASSNAME}__image`, isLoading && `${CLASSNAME}__image--is-loading`)}\n crossOrigin={crossOrigin}\n src={image}\n alt={alt}\n loading={loading}\n />\n {!isLoading && hasError && (\n <div className={`${CLASSNAME}__fallback`}>\n {hasIconErrorFallback ? (\n <Icon icon={fallback as string} size={Size.xxs} theme={theme} />\n ) : (\n fallback\n )}\n </div>\n )}\n </div>\n {badge &&\n React.cloneElement(badge, { className: classNames(`${CLASSNAME}__badge`, badge.props.className) })}\n </Wrapper>\n );\n});\nThumbnail.displayName = COMPONENT_NAME;\nThumbnail.className = CLASSNAME;\nThumbnail.defaultProps = DEFAULT_PROPS;\n"],"names":["getState","img","event","type","complete","naturalWidth","naturalHeight","useImageLoad","imageURL","imgRef","useState","current","state","setState","useEffect","undefined","update","addEventListener","removeEventListener","src","COMPONENT_NAME","CLASSNAME","getRootClassName","DEFAULT_PROPS","fallback","mdiImageBroken","loading","theme","Theme","light","getObjectPosition","aspectRatio","focusPoint","AspectRatio","original","y","x","Math","round","abs","Thumbnail","forwardRef","props","ref","align","alt","badge","className","crossOrigin","fillHeight","image","imgProps","propImgRef","isLoadingProp","isLoading","size","variant","linkProps","linkAs","forwardedProps","useRef","loadingState","hasError","hasIconErrorFallback","hasCustomErrorFallback","imageErrorStyle","visibility","display","isLink","Boolean","href","isButton","onClick","isClickable","Wrapper","wrapperProps","Object","assign","classNames","handleBasicClasses","prefix","hasBadge","style","objectPosition","mergeRefs","Size","xxs","React","cloneElement","displayName","defaultProps"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Thumbnail2.js","sources":["../../../src/components/thumbnail/useImageLoad.ts","../../../src/components/thumbnail/useFocusPointStyle.tsx","../../../src/components/thumbnail/Thumbnail.tsx"],"sourcesContent":["import { useEffect, useState } from 'react';\n\nexport type LoadingState = 'isLoading' | 'isLoaded' | 'hasError';\n\nfunction getState(img: HTMLImageElement | null | undefined, event?: Event) {\n // Error event occurred or image loaded empty.\n if (event?.type === 'error' || (img?.complete && (img?.naturalWidth === 0 || img?.naturalHeight === 0))) {\n return 'hasError';\n }\n // Image is undefined or incomplete.\n if (!img || !img.complete) {\n return 'isLoading';\n }\n // Else loaded.\n return 'isLoaded';\n}\n\nexport function useImageLoad(imageURL: string, imgRef?: HTMLImageElement): LoadingState {\n const [state, setState] = useState<LoadingState>(getState(imgRef));\n\n // Update state when changing image URL or DOM reference.\n useEffect(() => {\n setState(getState(imgRef));\n }, [imageURL, imgRef]);\n\n // Listen to `load` and `error` event on image\n useEffect(() => {\n const img = imgRef;\n if (!img) return undefined;\n const update = (event?: Event) => setState(getState(img, event));\n img.addEventListener('load', update);\n img.addEventListener('error', update);\n return () => {\n img.removeEventListener('load', update);\n img.removeEventListener('error', update);\n };\n }, [imgRef, imgRef?.src]);\n\n return state;\n}\n","import { CSSProperties, useMemo } from 'react';\nimport { AspectRatio } from '@lumx/react/components';\nimport { ThumbnailProps } from '@lumx/react/components/thumbnail/Thumbnail';\n\nfunction shiftPosition(\n scale: number,\n containerSize: number,\n imageSize: number,\n focusSize: number | undefined,\n isVertical?: boolean,\n) {\n if (!focusSize) return 50;\n const focusFactor = (focusSize + 1) / 2;\n const scaledSize = Math.floor(imageSize / scale);\n let focus = Math.floor(focusFactor * scaledSize);\n if (isVertical) focus = scaledSize - focus;\n\n const containerCenter = Math.floor(containerSize / 2);\n let focusOffset = focus - containerCenter;\n const remainder = scaledSize - focus;\n if (remainder < containerCenter) focusOffset -= containerCenter - remainder;\n if (focusOffset < 0) return 0;\n\n return Math.min(100, Math.floor((focusOffset * 100) / containerSize));\n}\n\nexport const useFocusPointStyle = (\n { image, aspectRatio, focusPoint, imgProps: { width, height } = {} }: ThumbnailProps,\n element: HTMLImageElement | undefined,\n isLoaded: boolean,\n): CSSProperties => {\n // Get natural image size from imgProps or img element.\n const naturalSize = useMemo(() => {\n if (!image || aspectRatio === AspectRatio.original || (!focusPoint?.x && !focusPoint?.y)) return undefined;\n if (typeof width === 'number' && typeof height === 'number') return { width, height };\n if (element && isLoaded) return { width: element.naturalWidth, height: element.naturalHeight };\n return undefined;\n }, [aspectRatio, element, focusPoint?.x, focusPoint?.y, height, image, isLoaded, width]);\n\n // Compute focus point CSS style.\n return useMemo(() => {\n if (aspectRatio === AspectRatio.original || (!focusPoint?.x && !focusPoint?.y)) return {};\n if (element && naturalSize) {\n const actualWidth = element.offsetWidth;\n const actualHeight = element.offsetHeight;\n const heightScale = actualHeight / naturalSize.height;\n const widthScale = actualWidth / naturalSize.width;\n const x = shiftPosition(heightScale, actualWidth, naturalSize.width, focusPoint?.x);\n const y = shiftPosition(widthScale, actualHeight, naturalSize.height, focusPoint?.y, true);\n return { objectPosition: `${x}% ${y}%` };\n }\n // Focus point can't be computed yet => We hide the image until it can.\n return { visibility: 'hidden' };\n }, [aspectRatio, element, focusPoint, naturalSize]);\n};\n","import React, {\n CSSProperties,\n forwardRef,\n ImgHTMLAttributes,\n KeyboardEventHandler,\n MouseEventHandler,\n ReactElement,\n ReactNode,\n Ref,\n useState,\n} from 'react';\nimport classNames from 'classnames';\n\nimport { AspectRatio, HorizontalAlignment, Icon, Size, Theme } from '@lumx/react';\n\nimport { Comp, GenericProps, getRootClassName, handleBasicClasses } from '@lumx/react/utils';\n\nimport { mdiImageBroken } from '@lumx/icons';\nimport { mergeRefs } from '@lumx/react/utils/mergeRefs';\nimport { useImageLoad } from '@lumx/react/components/thumbnail/useImageLoad';\nimport { useFocusPointStyle } from '@lumx/react/components/thumbnail/useFocusPointStyle';\nimport { FocusPoint, ThumbnailSize, ThumbnailVariant } from './types';\n\ntype ImgHTMLProps = ImgHTMLAttributes<HTMLImageElement>;\n\n/**\n * Defines the props of the component.\n */\nexport interface ThumbnailProps extends GenericProps {\n /** Alignment of the thumbnail in it's parent (requires flex parent). */\n align?: HorizontalAlignment;\n /** Image alternative text. */\n alt: string;\n /** Image aspect ratio. */\n aspectRatio?: AspectRatio;\n /** Badge. */\n badge?: ReactElement;\n /** Image cross origin resource policy. */\n crossOrigin?: ImgHTMLProps['crossOrigin'];\n /** Fallback icon (SVG path) or react node when image fails to load. */\n fallback?: string | ReactNode;\n /** Whether the thumbnail should fill it's parent size (requires flex parent) or not. */\n fillHeight?: boolean;\n /** Apply relative vertical and horizontal shift (from -1 to 1) on the image position inside the thumbnail. */\n focusPoint?: FocusPoint;\n /** Image URL. */\n image: string;\n /** Props to inject into the native <img> element. */\n imgProps?: ImgHTMLProps;\n /** Reference to the native <img> element. */\n imgRef?: Ref<HTMLImageElement>;\n /** Set to true to force the display of the loading skeleton. */\n isLoading?: boolean;\n /** Size variant of the component. */\n size?: ThumbnailSize;\n /** Image loading mode. */\n loading?: ImgHTMLProps['loading'];\n /** On click callback. */\n onClick?: MouseEventHandler<HTMLDivElement>;\n /** On key press callback. */\n onKeyPress?: KeyboardEventHandler<HTMLDivElement>;\n /** Theme adapting the component to light or dark background. */\n theme?: Theme;\n /** Variant of the component. */\n variant?: ThumbnailVariant;\n /** Props to pass to the link wrapping the thumbnail. */\n linkProps?: React.DetailedHTMLProps<React.AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>;\n /** Custom react component for the link (can be used to inject react router Link). */\n linkAs?: 'a' | any;\n}\n\n/**\n * Component display name.\n */\nconst COMPONENT_NAME = 'Thumbnail';\n\n/**\n * Component default class name and class prefix.\n */\nconst CLASSNAME = getRootClassName(COMPONENT_NAME);\n\n/**\n * Component default props.\n */\nconst DEFAULT_PROPS: Partial<ThumbnailProps> = {\n fallback: mdiImageBroken,\n loading: 'lazy',\n theme: Theme.light,\n};\n\n/**\n * Thumbnail component.\n *\n * @param props Component props.\n * @param ref Component ref.\n * @return React element.\n */\nexport const Thumbnail: Comp<ThumbnailProps> = forwardRef((props, ref) => {\n const {\n align,\n alt,\n aspectRatio = AspectRatio.original,\n badge,\n className,\n crossOrigin,\n fallback,\n fillHeight,\n focusPoint,\n image,\n imgProps,\n imgRef: propImgRef,\n isLoading: isLoadingProp,\n loading,\n size,\n theme,\n variant,\n linkProps,\n linkAs,\n ...forwardedProps\n } = props;\n const [imgElement, setImgElement] = useState<HTMLImageElement>();\n\n // Image loading state.\n const loadingState = useImageLoad(image, imgElement);\n const isLoaded = loadingState === 'isLoaded';\n const isLoading = isLoadingProp || loadingState === 'isLoading';\n const hasError = loadingState === 'hasError';\n\n // Focus point.\n const focusPointStyle = useFocusPointStyle(props, imgElement, isLoaded);\n\n const hasIconErrorFallback = hasError && typeof fallback === 'string';\n const hasCustomErrorFallback = hasError && !hasIconErrorFallback;\n const imageErrorStyle: CSSProperties = {};\n if (hasIconErrorFallback) {\n // Keep the image layout on icon fallback.\n imageErrorStyle.visibility = 'hidden';\n } else if (hasCustomErrorFallback) {\n // Remove the image on custom fallback.\n imageErrorStyle.display = 'none';\n }\n\n const isLink = Boolean(linkProps?.href || linkAs);\n const isButton = !!forwardedProps.onClick;\n const isClickable = isButton || isLink;\n\n let Wrapper: any = 'div';\n const wrapperProps = { ...forwardedProps };\n if (isLink) {\n Wrapper = linkAs || 'a';\n Object.assign(wrapperProps, linkProps);\n } else if (isButton) {\n Wrapper = 'button';\n }\n\n return (\n <Wrapper\n {...wrapperProps}\n ref={ref}\n className={classNames(\n linkProps?.className,\n className,\n handleBasicClasses({\n align,\n aspectRatio,\n prefix: CLASSNAME,\n size,\n theme,\n variant,\n isClickable,\n hasError,\n hasIconErrorFallback,\n hasCustomErrorFallback,\n isLoading,\n hasBadge: !!badge,\n }),\n fillHeight && `${CLASSNAME}--fill-height`,\n )}\n >\n <div className={`${CLASSNAME}__background`}>\n <img\n {...imgProps}\n style={{\n ...imgProps?.style,\n ...imageErrorStyle,\n ...focusPointStyle,\n }}\n ref={mergeRefs(setImgElement, propImgRef)}\n className={classNames(`${CLASSNAME}__image`, isLoading && `${CLASSNAME}__image--is-loading`)}\n crossOrigin={crossOrigin}\n src={image}\n alt={alt}\n loading={loading}\n />\n {!isLoading && hasError && (\n <div className={`${CLASSNAME}__fallback`}>\n {hasIconErrorFallback ? (\n <Icon icon={fallback as string} size={Size.xxs} theme={theme} />\n ) : (\n fallback\n )}\n </div>\n )}\n </div>\n {badge &&\n React.cloneElement(badge, { className: classNames(`${CLASSNAME}__badge`, badge.props.className) })}\n </Wrapper>\n );\n});\nThumbnail.displayName = COMPONENT_NAME;\nThumbnail.className = CLASSNAME;\nThumbnail.defaultProps = DEFAULT_PROPS;\n"],"names":["getState","img","event","type","complete","naturalWidth","naturalHeight","useImageLoad","imageURL","imgRef","useState","state","setState","useEffect","undefined","update","addEventListener","removeEventListener","src","shiftPosition","scale","containerSize","imageSize","focusSize","isVertical","focusFactor","scaledSize","Math","floor","focus","containerCenter","focusOffset","remainder","min","useFocusPointStyle","element","isLoaded","image","aspectRatio","focusPoint","imgProps","width","height","naturalSize","useMemo","AspectRatio","original","x","y","actualWidth","offsetWidth","actualHeight","offsetHeight","heightScale","widthScale","objectPosition","visibility","COMPONENT_NAME","CLASSNAME","getRootClassName","DEFAULT_PROPS","fallback","mdiImageBroken","loading","theme","Theme","light","Thumbnail","forwardRef","props","ref","align","alt","badge","className","crossOrigin","fillHeight","propImgRef","isLoadingProp","isLoading","size","variant","linkProps","linkAs","forwardedProps","imgElement","setImgElement","loadingState","hasError","focusPointStyle","hasIconErrorFallback","hasCustomErrorFallback","imageErrorStyle","display","isLink","Boolean","href","isButton","onClick","isClickable","Wrapper","wrapperProps","Object","assign","classNames","handleBasicClasses","prefix","hasBadge","style","mergeRefs","Size","xxs","React","cloneElement","displayName","defaultProps"],"mappings":";;;;;;;;AAIA,SAASA,QAAT,CAAkBC,GAAlB,EAA4DC,KAA5D,EAA2E;AACvE;AACA,MAAI,CAAAA,KAAK,SAAL,IAAAA,KAAK,WAAL,YAAAA,KAAK,CAAEC,IAAP,MAAgB,OAAhB,IAA4B,CAAAF,GAAG,SAAH,IAAAA,GAAG,WAAH,YAAAA,GAAG,CAAEG,QAAL,MAAkB,CAAAH,GAAG,SAAH,IAAAA,GAAG,WAAH,YAAAA,GAAG,CAAEI,YAAL,MAAsB,CAAtB,IAA2B,CAAAJ,GAAG,SAAH,IAAAA,GAAG,WAAH,YAAAA,GAAG,CAAEK,aAAL,MAAuB,CAApE,CAAhC,EAAyG;AACrG,WAAO,UAAP;AACH,GAJsE;;;AAMvE,MAAI,CAACL,GAAD,IAAQ,CAACA,GAAG,CAACG,QAAjB,EAA2B;AACvB,WAAO,WAAP;AACH,GARsE;;;AAUvE,SAAO,UAAP;AACH;;AAEM,SAASG,YAAT,CAAsBC,QAAtB,EAAwCC,MAAxC,EAAiF;AAAA,kBAC1DC,QAAQ,CAAeV,QAAQ,CAACS,MAAD,CAAvB,CADkD;AAAA;AAAA,MAC7EE,KAD6E;AAAA,MACtEC,QADsE;;;AAIpFC,EAAAA,SAAS,CAAC,YAAM;AACZD,IAAAA,QAAQ,CAACZ,QAAQ,CAACS,MAAD,CAAT,CAAR;AACH,GAFQ,EAEN,CAACD,QAAD,EAAWC,MAAX,CAFM,CAAT,CAJoF;;AASpFI,EAAAA,SAAS,CAAC,YAAM;AACZ,QAAMZ,GAAG,GAAGQ,MAAZ;AACA,QAAI,CAACR,GAAL,EAAU,OAAOa,SAAP;;AACV,QAAMC,MAAM,GAAG,SAATA,MAAS,CAACb,KAAD;AAAA,aAAmBU,QAAQ,CAACZ,QAAQ,CAACC,GAAD,EAAMC,KAAN,CAAT,CAA3B;AAAA,KAAf;;AACAD,IAAAA,GAAG,CAACe,gBAAJ,CAAqB,MAArB,EAA6BD,MAA7B;AACAd,IAAAA,GAAG,CAACe,gBAAJ,CAAqB,OAArB,EAA8BD,MAA9B;AACA,WAAO,YAAM;AACTd,MAAAA,GAAG,CAACgB,mBAAJ,CAAwB,MAAxB,EAAgCF,MAAhC;AACAd,MAAAA,GAAG,CAACgB,mBAAJ,CAAwB,OAAxB,EAAiCF,MAAjC;AACH,KAHD;AAIH,GAVQ,EAUN,CAACN,MAAD,EAASA,MAAT,aAASA,MAAT,uBAASA,MAAM,CAAES,GAAjB,CAVM,CAAT;AAYA,SAAOP,KAAP;AACH;;ACnCD,SAASQ,aAAT,CACIC,KADJ,EAEIC,aAFJ,EAGIC,SAHJ,EAIIC,SAJJ,EAKIC,UALJ,EAME;AACE,MAAI,CAACD,SAAL,EAAgB,OAAO,EAAP;AAChB,MAAME,WAAW,GAAG,CAACF,SAAS,GAAG,CAAb,IAAkB,CAAtC;AACA,MAAMG,UAAU,GAAGC,IAAI,CAACC,KAAL,CAAWN,SAAS,GAAGF,KAAvB,CAAnB;AACA,MAAIS,KAAK,GAAGF,IAAI,CAACC,KAAL,CAAWH,WAAW,GAAGC,UAAzB,CAAZ;AACA,MAAIF,UAAJ,EAAgBK,KAAK,GAAGH,UAAU,GAAGG,KAArB;AAEhB,MAAMC,eAAe,GAAGH,IAAI,CAACC,KAAL,CAAWP,aAAa,GAAG,CAA3B,CAAxB;AACA,MAAIU,WAAW,GAAGF,KAAK,GAAGC,eAA1B;AACA,MAAME,SAAS,GAAGN,UAAU,GAAGG,KAA/B;AACA,MAAIG,SAAS,GAAGF,eAAhB,EAAiCC,WAAW,IAAID,eAAe,GAAGE,SAAjC;AACjC,MAAID,WAAW,GAAG,CAAlB,EAAqB,OAAO,CAAP;AAErB,SAAOJ,IAAI,CAACM,GAAL,CAAS,GAAT,EAAcN,IAAI,CAACC,KAAL,CAAYG,WAAW,GAAG,GAAf,GAAsBV,aAAjC,CAAd,CAAP;AACH;;IAEYa,kBAAkB,GAAG,SAArBA,kBAAqB,OAE9BC,OAF8B,EAG9BC,QAH8B,EAId;AAAA,MAHdC,KAGc,QAHdA,KAGc;AAAA,MAHPC,WAGO,QAHPA,WAGO;AAAA,MAHMC,UAGN,QAHMA,UAGN;AAAA,2BAHkBC,QAGlB;AAAA,6CAHgD,EAGhD;AAAA,MAH8BC,KAG9B,iBAH8BA,KAG9B;AAAA,MAHqCC,MAGrC,iBAHqCA,MAGrC;AAChB;AACA,MAAMC,WAAW,GAAGC,OAAO,CAAC,YAAM;AAC9B,QAAI,CAACP,KAAD,IAAUC,WAAW,KAAKO,WAAW,CAACC,QAAtC,IAAmD,EAACP,UAAD,aAACA,UAAD,uBAACA,UAAU,CAAEQ,CAAb,KAAkB,EAACR,UAAD,aAACA,UAAD,uBAACA,UAAU,CAAES,CAAb,CAAzE,EAA0F,OAAOlC,SAAP;AAC1F,QAAI,OAAO2B,KAAP,KAAiB,QAAjB,IAA6B,OAAOC,MAAP,KAAkB,QAAnD,EAA6D,OAAO;AAAED,MAAAA,KAAK,EAALA,KAAF;AAASC,MAAAA,MAAM,EAANA;AAAT,KAAP;AAC7D,QAAIP,OAAO,IAAIC,QAAf,EAAyB,OAAO;AAAEK,MAAAA,KAAK,EAAEN,OAAO,CAAC9B,YAAjB;AAA+BqC,MAAAA,MAAM,EAAEP,OAAO,CAAC7B;AAA/C,KAAP;AACzB,WAAOQ,SAAP;AACH,GAL0B,EAKxB,CAACwB,WAAD,EAAcH,OAAd,EAAuBI,UAAvB,aAAuBA,UAAvB,uBAAuBA,UAAU,CAAEQ,CAAnC,EAAsCR,UAAtC,aAAsCA,UAAtC,uBAAsCA,UAAU,CAAES,CAAlD,EAAqDN,MAArD,EAA6DL,KAA7D,EAAoED,QAApE,EAA8EK,KAA9E,CALwB,CAA3B,CAFgB;;AAUhB,SAAOG,OAAO,CAAC,YAAM;AACjB,QAAIN,WAAW,KAAKO,WAAW,CAACC,QAA5B,IAAyC,EAACP,UAAD,aAACA,UAAD,uBAACA,UAAU,CAAEQ,CAAb,KAAkB,EAACR,UAAD,aAACA,UAAD,uBAACA,UAAU,CAAES,CAAb,CAA/D,EAAgF,OAAO,EAAP;;AAChF,QAAIb,OAAO,IAAIQ,WAAf,EAA4B;AACxB,UAAMM,WAAW,GAAGd,OAAO,CAACe,WAA5B;AACA,UAAMC,YAAY,GAAGhB,OAAO,CAACiB,YAA7B;AACA,UAAMC,WAAW,GAAGF,YAAY,GAAGR,WAAW,CAACD,MAA/C;AACA,UAAMY,UAAU,GAAGL,WAAW,GAAGN,WAAW,CAACF,KAA7C;AACA,UAAMM,CAAC,GAAG5B,aAAa,CAACkC,WAAD,EAAcJ,WAAd,EAA2BN,WAAW,CAACF,KAAvC,EAA8CF,UAA9C,aAA8CA,UAA9C,uBAA8CA,UAAU,CAAEQ,CAA1D,CAAvB;AACA,UAAMC,CAAC,GAAG7B,aAAa,CAACmC,UAAD,EAAaH,YAAb,EAA2BR,WAAW,CAACD,MAAvC,EAA+CH,UAA/C,aAA+CA,UAA/C,uBAA+CA,UAAU,CAAES,CAA3D,EAA8D,IAA9D,CAAvB;AACA,aAAO;AAAEO,QAAAA,cAAc,YAAKR,CAAL,eAAWC,CAAX;AAAhB,OAAP;AACH,KAVgB;;;AAYjB,WAAO;AAAEQ,MAAAA,UAAU,EAAE;AAAd,KAAP;AACH,GAba,EAaX,CAAClB,WAAD,EAAcH,OAAd,EAAuBI,UAAvB,EAAmCI,WAAnC,CAbW,CAAd;AAcH;;ACiBD;;;AAGA,IAAMc,cAAc,GAAG,WAAvB;AAEA;;;;AAGA,IAAMC,SAAS,GAAGC,gBAAgB,CAACF,cAAD,CAAlC;AAEA;;;;AAGA,IAAMG,aAAsC,GAAG;AAC3CC,EAAAA,QAAQ,EAAEC,cADiC;AAE3CC,EAAAA,OAAO,EAAE,MAFkC;AAG3CC,EAAAA,KAAK,EAAEC,KAAK,CAACC;AAH8B,CAA/C;AAMA;;;;;;;;IAOaC,SAA+B,GAAGC,UAAU,CAAC,UAACC,KAAD,EAAQC,GAAR,EAAgB;AAAA,MAElEC,KAFkE,GAsBlEF,KAtBkE,CAElEE,KAFkE;AAAA,MAGlEC,GAHkE,GAsBlEH,KAtBkE,CAGlEG,GAHkE;AAAA,2BAsBlEH,KAtBkE,CAIlE/B,WAJkE;AAAA,MAIlEA,WAJkE,mCAIpDO,WAAW,CAACC,QAJwC;AAAA,MAKlE2B,KALkE,GAsBlEJ,KAtBkE,CAKlEI,KALkE;AAAA,MAMlEC,SANkE,GAsBlEL,KAtBkE,CAMlEK,SANkE;AAAA,MAOlEC,WAPkE,GAsBlEN,KAtBkE,CAOlEM,WAPkE;AAAA,MAQlEd,QARkE,GAsBlEQ,KAtBkE,CAQlER,QARkE;AAAA,MASlEe,UATkE,GAsBlEP,KAtBkE,CASlEO,UATkE;AAAA,MAUlErC,UAVkE,GAsBlE8B,KAtBkE,CAUlE9B,UAVkE;AAAA,MAWlEF,KAXkE,GAsBlEgC,KAtBkE,CAWlEhC,KAXkE;AAAA,MAYlEG,QAZkE,GAsBlE6B,KAtBkE,CAYlE7B,QAZkE;AAAA,MAa1DqC,UAb0D,GAsBlER,KAtBkE,CAalE5D,MAbkE;AAAA,MAcvDqE,aAduD,GAsBlET,KAtBkE,CAclEU,SAdkE;AAAA,MAelEhB,OAfkE,GAsBlEM,KAtBkE,CAelEN,OAfkE;AAAA,MAgBlEiB,IAhBkE,GAsBlEX,KAtBkE,CAgBlEW,IAhBkE;AAAA,MAiBlEhB,KAjBkE,GAsBlEK,KAtBkE,CAiBlEL,KAjBkE;AAAA,MAkBlEiB,OAlBkE,GAsBlEZ,KAtBkE,CAkBlEY,OAlBkE;AAAA,MAmBlEC,SAnBkE,GAsBlEb,KAtBkE,CAmBlEa,SAnBkE;AAAA,MAoBlEC,MApBkE,GAsBlEd,KAtBkE,CAoBlEc,MApBkE;AAAA,MAqB/DC,cArB+D,4BAsBlEf,KAtBkE;;AAAA,kBAuBlC3D,QAAQ,EAvB0B;AAAA;AAAA,MAuB/D2E,UAvB+D;AAAA,MAuBnDC,aAvBmD;;;AA0BtE,MAAMC,YAAY,GAAGhF,YAAY,CAAC8B,KAAD,EAAQgD,UAAR,CAAjC;AACA,MAAMjD,QAAQ,GAAGmD,YAAY,KAAK,UAAlC;AACA,MAAMR,SAAS,GAAGD,aAAa,IAAIS,YAAY,KAAK,WAApD;AACA,MAAMC,QAAQ,GAAGD,YAAY,KAAK,UAAlC,CA7BsE;;AAgCtE,MAAME,eAAe,GAAGvD,kBAAkB,CAACmC,KAAD,EAAQgB,UAAR,EAAoBjD,QAApB,CAA1C;AAEA,MAAMsD,oBAAoB,GAAGF,QAAQ,IAAI,OAAO3B,QAAP,KAAoB,QAA7D;AACA,MAAM8B,sBAAsB,GAAGH,QAAQ,IAAI,CAACE,oBAA5C;AACA,MAAME,eAA8B,GAAG,EAAvC;;AACA,MAAIF,oBAAJ,EAA0B;AACtB;AACAE,IAAAA,eAAe,CAACpC,UAAhB,GAA6B,QAA7B;AACH,GAHD,MAGO,IAAImC,sBAAJ,EAA4B;AAC/B;AACAC,IAAAA,eAAe,CAACC,OAAhB,GAA0B,MAA1B;AACH;;AAED,MAAMC,MAAM,GAAGC,OAAO,CAAC,CAAAb,SAAS,SAAT,IAAAA,SAAS,WAAT,YAAAA,SAAS,CAAEc,IAAX,KAAmBb,MAApB,CAAtB;AACA,MAAMc,QAAQ,GAAG,CAAC,CAACb,cAAc,CAACc,OAAlC;AACA,MAAMC,WAAW,GAAGF,QAAQ,IAAIH,MAAhC;AAEA,MAAIM,OAAY,GAAG,KAAnB;;AACA,MAAMC,YAAY,sBAAQjB,cAAR,CAAlB;;AACA,MAAIU,MAAJ,EAAY;AACRM,IAAAA,OAAO,GAAGjB,MAAM,IAAI,GAApB;AACAmB,IAAAA,MAAM,CAACC,MAAP,CAAcF,YAAd,EAA4BnB,SAA5B;AACH,GAHD,MAGO,IAAIe,QAAJ,EAAc;AACjBG,IAAAA,OAAO,GAAG,QAAV;AACH;;AAED,SACI,oBAAC,OAAD,eACQC,YADR;AAEI,IAAA,GAAG,EAAE/B,GAFT;AAGI,IAAA,SAAS,EAAEkC,UAAU,CACjBtB,SADiB,aACjBA,SADiB,uBACjBA,SAAS,CAAER,SADM,EAEjBA,SAFiB,EAGjB+B,kBAAkB,CAAC;AACflC,MAAAA,KAAK,EAALA,KADe;AAEfjC,MAAAA,WAAW,EAAXA,WAFe;AAGfoE,MAAAA,MAAM,EAAEhD,SAHO;AAIfsB,MAAAA,IAAI,EAAJA,IAJe;AAKfhB,MAAAA,KAAK,EAALA,KALe;AAMfiB,MAAAA,OAAO,EAAPA,OANe;AAOfkB,MAAAA,WAAW,EAAXA,WAPe;AAQfX,MAAAA,QAAQ,EAARA,QARe;AASfE,MAAAA,oBAAoB,EAApBA,oBATe;AAUfC,MAAAA,sBAAsB,EAAtBA,sBAVe;AAWfZ,MAAAA,SAAS,EAATA,SAXe;AAYf4B,MAAAA,QAAQ,EAAE,CAAC,CAAClC;AAZG,KAAD,CAHD,EAiBjBG,UAAU,cAAOlB,SAAP,kBAjBO;AAHzB,MAuBI;AAAK,IAAA,SAAS,YAAKA,SAAL;AAAd,KACI,wCACQlB,QADR;AAEI,IAAA,KAAK,qBACEA,QADF,aACEA,QADF,uBACEA,QAAQ,CAAEoE,KADZ,MAEEhB,eAFF,MAGEH,eAHF,CAFT;AAOI,IAAA,GAAG,EAAEoB,SAAS,CAACvB,aAAD,EAAgBT,UAAhB,CAPlB;AAQI,IAAA,SAAS,EAAE2B,UAAU,WAAI9C,SAAJ,cAAwBqB,SAAS,cAAOrB,SAAP,wBAAjC,CARzB;AASI,IAAA,WAAW,EAAEiB,WATjB;AAUI,IAAA,GAAG,EAAEtC,KAVT;AAWI,IAAA,GAAG,EAAEmC,GAXT;AAYI,IAAA,OAAO,EAAET;AAZb,KADJ,EAeK,CAACgB,SAAD,IAAcS,QAAd,IACG;AAAK,IAAA,SAAS,YAAK9B,SAAL;AAAd,KACKgC,oBAAoB,GACjB,oBAAC,IAAD;AAAM,IAAA,IAAI,EAAE7B,QAAZ;AAAgC,IAAA,IAAI,EAAEiD,IAAI,CAACC,GAA3C;AAAgD,IAAA,KAAK,EAAE/C;AAAvD,IADiB,GAGjBH,QAJR,CAhBR,CAvBJ,EAgDKY,KAAK,IACFuC,KAAK,CAACC,YAAN,CAAmBxC,KAAnB,EAA0B;AAAEC,IAAAA,SAAS,EAAE8B,UAAU,WAAI9C,SAAJ,cAAwBe,KAAK,CAACJ,KAAN,CAAYK,SAApC;AAAvB,GAA1B,CAjDR,CADJ;AAqDH,CA/GwD;AAgHzDP,SAAS,CAAC+C,WAAV,GAAwBzD,cAAxB;AACAU,SAAS,CAACO,SAAV,GAAsBhB,SAAtB;AACAS,SAAS,CAACgD,YAAV,GAAyBvD,aAAzB;;;;"}
|
|
@@ -9,6 +9,6 @@ import 'lodash/kebabCase';
|
|
|
9
9
|
import 'lodash/noop';
|
|
10
10
|
import './mergeRefs.js';
|
|
11
11
|
import './Icon2.js';
|
|
12
|
-
export { T as Thumbnail } from './Thumbnail2.js';
|
|
12
|
+
export { T as Thumbnail, u as useFocusPointStyle } from './Thumbnail2.js';
|
|
13
13
|
export { T as ThumbnailAspectRatio, a as ThumbnailVariant } from './types.js';
|
|
14
14
|
//# sourceMappingURL=thumbnail.js.map
|
package/esm/_internal/types.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sources":["../../../src/components/thumbnail/types.ts"],"sourcesContent":["import React from 'react';\nimport { AspectRatio, Size } from '@lumx/react';\nimport { ValueOf } from '@lumx/react/utils';\n\n/**\n * Focal point using vertical alignment, horizontal alignment or coordinates (from -1 to 1).\n */\nexport type FocusPoint = { x?: number; y?: number };\n\n/**\n * Loading attribute is not yet supported in typescript, so we need\n * to add it in order to avoid a ts error.\n * https://github.com/typescript-cheatsheets/react-typescript-cheatsheet/blob/master/ADVANCED.md#adding-non-standard-attributes\n */\ndeclare module 'react' {\n interface ImgHTMLAttributes<T> extends React.HTMLAttributes<T> {\n loading?: 'eager' | 'lazy';\n }\n}\n\n/**\n * All available aspect ratios.\n * @deprecated\n */\nexport const ThumbnailAspectRatio: Record<string, AspectRatio> = { ...AspectRatio };\n\n/**\n * Thumbnail sizes.\n */\nexport type ThumbnailSize = Extract<Size, 'xxs' | 'xs' | 's' | 'm' | 'l' | 'xl' | 'xxl'>;\n\n/**\n * Thumbnail variants.\n */\nexport const ThumbnailVariant = {\n squared: 'squared',\n rounded: 'rounded',\n} as const;\nexport type ThumbnailVariant = ValueOf<typeof ThumbnailVariant>;\n"],"names":["ThumbnailAspectRatio","AspectRatio","ThumbnailVariant","squared","rounded"],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.js","sources":["../../../src/components/thumbnail/types.ts"],"sourcesContent":["import React from 'react';\nimport { AspectRatio, Size } from '@lumx/react';\nimport { ValueOf } from '@lumx/react/utils';\n\n/**\n * Focal point using vertical alignment, horizontal alignment or coordinates (from -1 to 1).\n */\nexport type FocusPoint = { x?: number; y?: number };\n\n/**\n * Loading attribute is not yet supported in typescript, so we need\n * to add it in order to avoid a ts error.\n * https://github.com/typescript-cheatsheets/react-typescript-cheatsheet/blob/master/ADVANCED.md#adding-non-standard-attributes\n */\ndeclare module 'react' {\n interface ImgHTMLAttributes<T> extends React.HTMLAttributes<T> {\n loading?: 'eager' | 'lazy';\n }\n}\n\n/**\n * All available aspect ratios.\n * @deprecated\n */\nexport const ThumbnailAspectRatio: Record<string, AspectRatio> = { ...AspectRatio };\n\n/**\n * Thumbnail sizes.\n */\nexport type ThumbnailSize = Extract<Size, 'xxs' | 'xs' | 's' | 'm' | 'l' | 'xl' | 'xxl'>;\n\n/**\n * Thumbnail variants.\n */\nexport const ThumbnailVariant = {\n squared: 'squared',\n rounded: 'rounded',\n} as const;\nexport type ThumbnailVariant = ValueOf<typeof ThumbnailVariant>;\n"],"names":["ThumbnailAspectRatio","AspectRatio","ThumbnailVariant","squared","rounded"],"mappings":";;;;AAoBA;;;;IAIaA,oBAAiD,sBAAQC,WAAR;AAE9D;;;;AAKA;;;IAGaC,gBAAgB,GAAG;AAC5BC,EAAAA,OAAO,EAAE,SADmB;AAE5BC,EAAAA,OAAO,EAAE;AAFmB;;;;"}
|
package/esm/index.js
CHANGED
|
@@ -80,7 +80,7 @@ export { S as Switch } from './_internal/Switch2.js';
|
|
|
80
80
|
export { T as Table, a as TableBody, d as TableCell, c as TableCellVariant, e as TableHeader, f as TableRow, b as ThOrder } from './_internal/TableRow.js';
|
|
81
81
|
export { c as Tab, b as TabList, a as TabListLayout, d as TabPanel, T as TabProvider } from './_internal/TabPanel.js';
|
|
82
82
|
export { T as TextField } from './_internal/TextField.js';
|
|
83
|
-
export { T as Thumbnail } from './_internal/Thumbnail2.js';
|
|
83
|
+
export { T as Thumbnail, u as useFocusPointStyle } from './_internal/Thumbnail2.js';
|
|
84
84
|
export { T as ThumbnailAspectRatio, a as ThumbnailVariant } from './_internal/types.js';
|
|
85
85
|
export { T as Toolbar } from './_internal/Toolbar2.js';
|
|
86
86
|
export { T as Tooltip } from './_internal/Tooltip2.js';
|
package/package.json
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
},
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"@juggle/resize-observer": "^3.2.0",
|
|
10
|
-
"@lumx/core": "^2.1.9-alpha-
|
|
11
|
-
"@lumx/icons": "^2.1.9-alpha-
|
|
10
|
+
"@lumx/core": "^2.1.9-alpha-thumbnail13",
|
|
11
|
+
"@lumx/icons": "^2.1.9-alpha-thumbnail13",
|
|
12
12
|
"@popperjs/core": "^2.5.4",
|
|
13
13
|
"body-scroll-lock": "^3.1.5",
|
|
14
14
|
"classnames": "^2.2.6",
|
|
@@ -120,6 +120,6 @@
|
|
|
120
120
|
"build:storybook": "cd storybook && ./build"
|
|
121
121
|
},
|
|
122
122
|
"sideEffects": false,
|
|
123
|
-
"version": "2.1.9-alpha-
|
|
124
|
-
"gitHead": "
|
|
123
|
+
"version": "2.1.9-alpha-thumbnail13",
|
|
124
|
+
"gitHead": "18619b44fecc3b26cbbed5ff531b538b4d7ca550"
|
|
125
125
|
}
|
|
@@ -118,6 +118,7 @@ export const ImageBlock: Comp<ImageBlockProps, HTMLDivElement> = forwardRef((pro
|
|
|
118
118
|
<Thumbnail
|
|
119
119
|
{...thumbnailProps}
|
|
120
120
|
className={classNames(`${CLASSNAME}__image`, thumbnailProps?.className)}
|
|
121
|
+
fillHeight={fillHeight}
|
|
121
122
|
align={align}
|
|
122
123
|
image={image}
|
|
123
124
|
size={size}
|
|
@@ -22,6 +22,10 @@ import classNames from 'classnames';
|
|
|
22
22
|
|
|
23
23
|
export default { title: 'LumX components/thumbnail/Thumbnail' };
|
|
24
24
|
|
|
25
|
+
const Resizable = ({ initialSize: { width, height }, children }: any) => (
|
|
26
|
+
<div style={{ border: '1px solid red', overflow: 'hidden', resize: 'both', width, height }}>{children}</div>
|
|
27
|
+
);
|
|
28
|
+
|
|
25
29
|
/** Default thumbnail props (editable via knobs) */
|
|
26
30
|
export const Default = ({ theme }: any) => {
|
|
27
31
|
const alt = text('Alternative text', 'Image alt text');
|
|
@@ -79,6 +83,41 @@ export const WithBadge = () => {
|
|
|
79
83
|
);
|
|
80
84
|
};
|
|
81
85
|
|
|
86
|
+
export const FocusPoint = () => {
|
|
87
|
+
const focusPoint = { x: focusKnob('Focus X ', -0.2), y: focusKnob('Focus Y', -0.3) };
|
|
88
|
+
const aspectRatio = enumKnob('Aspect ratio', [undefined, ...Object.values(AspectRatio)], AspectRatio.free);
|
|
89
|
+
const fillHeight = aspectRatio === AspectRatio.free;
|
|
90
|
+
return (
|
|
91
|
+
<>
|
|
92
|
+
<small>Focus point will delay the display of the image if the original image size is not accessible.</small>
|
|
93
|
+
|
|
94
|
+
<Resizable initialSize={{ height: 200, width: 300 }}>
|
|
95
|
+
<Thumbnail
|
|
96
|
+
alt="Image"
|
|
97
|
+
image={IMAGES.portrait1}
|
|
98
|
+
aspectRatio={aspectRatio}
|
|
99
|
+
fillHeight={fillHeight}
|
|
100
|
+
focusPoint={focusPoint}
|
|
101
|
+
style={{ width: '100%' }}
|
|
102
|
+
/>
|
|
103
|
+
</Resizable>
|
|
104
|
+
|
|
105
|
+
<small>Providing the width & height in imgProps should avoid the delay shown above</small>
|
|
106
|
+
<Resizable initialSize={{ height: 200, width: 300 }}>
|
|
107
|
+
<Thumbnail
|
|
108
|
+
alt="Image"
|
|
109
|
+
image={IMAGES.portrait2}
|
|
110
|
+
imgProps={IMAGE_SIZES.portrait2}
|
|
111
|
+
fillHeight={fillHeight}
|
|
112
|
+
aspectRatio={aspectRatio}
|
|
113
|
+
focusPoint={focusPoint}
|
|
114
|
+
style={{ width: '100%' }}
|
|
115
|
+
/>
|
|
116
|
+
</Resizable>
|
|
117
|
+
</>
|
|
118
|
+
);
|
|
119
|
+
};
|
|
120
|
+
|
|
82
121
|
export const Clickable = () => (
|
|
83
122
|
<Thumbnail alt="Click me" image={imageKnob()} size={sizeKnob('Size', Size.xxl)} onClick={action('onClick')} />
|
|
84
123
|
);
|
|
@@ -7,7 +7,7 @@ import React, {
|
|
|
7
7
|
ReactElement,
|
|
8
8
|
ReactNode,
|
|
9
9
|
Ref,
|
|
10
|
-
|
|
10
|
+
useState,
|
|
11
11
|
} from 'react';
|
|
12
12
|
import classNames from 'classnames';
|
|
13
13
|
|
|
@@ -18,6 +18,7 @@ import { Comp, GenericProps, getRootClassName, handleBasicClasses } from '@lumx/
|
|
|
18
18
|
import { mdiImageBroken } from '@lumx/icons';
|
|
19
19
|
import { mergeRefs } from '@lumx/react/utils/mergeRefs';
|
|
20
20
|
import { useImageLoad } from '@lumx/react/components/thumbnail/useImageLoad';
|
|
21
|
+
import { useFocusPointStyle } from '@lumx/react/components/thumbnail/useFocusPointStyle';
|
|
21
22
|
import { FocusPoint, ThumbnailSize, ThumbnailVariant } from './types';
|
|
22
23
|
|
|
23
24
|
type ImgHTMLProps = ImgHTMLAttributes<HTMLImageElement>;
|
|
@@ -87,13 +88,6 @@ const DEFAULT_PROPS: Partial<ThumbnailProps> = {
|
|
|
87
88
|
theme: Theme.light,
|
|
88
89
|
};
|
|
89
90
|
|
|
90
|
-
function getObjectPosition(aspectRatio: AspectRatio, focusPoint?: FocusPoint) {
|
|
91
|
-
if (aspectRatio === AspectRatio.original || (!focusPoint?.y && !focusPoint?.x)) return undefined;
|
|
92
|
-
const x = Math.round(Math.abs(((focusPoint?.x || 0) + 1) / 2) * 100);
|
|
93
|
-
const y = Math.round(Math.abs(((focusPoint?.y || 0) - 1) / 2) * 100);
|
|
94
|
-
return `${x}% ${y}%`;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
91
|
/**
|
|
98
92
|
* Thumbnail component.
|
|
99
93
|
*
|
|
@@ -124,13 +118,17 @@ export const Thumbnail: Comp<ThumbnailProps> = forwardRef((props, ref) => {
|
|
|
124
118
|
linkAs,
|
|
125
119
|
...forwardedProps
|
|
126
120
|
} = props;
|
|
127
|
-
const
|
|
121
|
+
const [imgElement, setImgElement] = useState<HTMLImageElement>();
|
|
128
122
|
|
|
129
123
|
// Image loading state.
|
|
130
|
-
const loadingState = useImageLoad(image,
|
|
124
|
+
const loadingState = useImageLoad(image, imgElement);
|
|
125
|
+
const isLoaded = loadingState === 'isLoaded';
|
|
131
126
|
const isLoading = isLoadingProp || loadingState === 'isLoading';
|
|
132
127
|
const hasError = loadingState === 'hasError';
|
|
133
128
|
|
|
129
|
+
// Focus point.
|
|
130
|
+
const focusPointStyle = useFocusPointStyle(props, imgElement, isLoaded);
|
|
131
|
+
|
|
134
132
|
const hasIconErrorFallback = hasError && typeof fallback === 'string';
|
|
135
133
|
const hasCustomErrorFallback = hasError && !hasIconErrorFallback;
|
|
136
134
|
const imageErrorStyle: CSSProperties = {};
|
|
@@ -185,10 +183,9 @@ export const Thumbnail: Comp<ThumbnailProps> = forwardRef((props, ref) => {
|
|
|
185
183
|
style={{
|
|
186
184
|
...imgProps?.style,
|
|
187
185
|
...imageErrorStyle,
|
|
188
|
-
|
|
189
|
-
objectPosition: getObjectPosition(aspectRatio, focusPoint),
|
|
186
|
+
...focusPointStyle,
|
|
190
187
|
}}
|
|
191
|
-
ref={mergeRefs(
|
|
188
|
+
ref={mergeRefs(setImgElement, propImgRef)}
|
|
192
189
|
className={classNames(`${CLASSNAME}__image`, isLoading && `${CLASSNAME}__image--is-loading`)}
|
|
193
190
|
crossOrigin={crossOrigin}
|
|
194
191
|
src={image}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { CSSProperties, useMemo } from 'react';
|
|
2
|
+
import { AspectRatio } from '@lumx/react/components';
|
|
3
|
+
import { ThumbnailProps } from '@lumx/react/components/thumbnail/Thumbnail';
|
|
4
|
+
|
|
5
|
+
function shiftPosition(
|
|
6
|
+
scale: number,
|
|
7
|
+
containerSize: number,
|
|
8
|
+
imageSize: number,
|
|
9
|
+
focusSize: number | undefined,
|
|
10
|
+
isVertical?: boolean,
|
|
11
|
+
) {
|
|
12
|
+
if (!focusSize) return 50;
|
|
13
|
+
const focusFactor = (focusSize + 1) / 2;
|
|
14
|
+
const scaledSize = Math.floor(imageSize / scale);
|
|
15
|
+
let focus = Math.floor(focusFactor * scaledSize);
|
|
16
|
+
if (isVertical) focus = scaledSize - focus;
|
|
17
|
+
|
|
18
|
+
const containerCenter = Math.floor(containerSize / 2);
|
|
19
|
+
let focusOffset = focus - containerCenter;
|
|
20
|
+
const remainder = scaledSize - focus;
|
|
21
|
+
if (remainder < containerCenter) focusOffset -= containerCenter - remainder;
|
|
22
|
+
if (focusOffset < 0) return 0;
|
|
23
|
+
|
|
24
|
+
return Math.min(100, Math.floor((focusOffset * 100) / containerSize));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const useFocusPointStyle = (
|
|
28
|
+
{ image, aspectRatio, focusPoint, imgProps: { width, height } = {} }: ThumbnailProps,
|
|
29
|
+
element: HTMLImageElement | undefined,
|
|
30
|
+
isLoaded: boolean,
|
|
31
|
+
): CSSProperties => {
|
|
32
|
+
// Get natural image size from imgProps or img element.
|
|
33
|
+
const naturalSize = useMemo(() => {
|
|
34
|
+
if (!image || aspectRatio === AspectRatio.original || (!focusPoint?.x && !focusPoint?.y)) return undefined;
|
|
35
|
+
if (typeof width === 'number' && typeof height === 'number') return { width, height };
|
|
36
|
+
if (element && isLoaded) return { width: element.naturalWidth, height: element.naturalHeight };
|
|
37
|
+
return undefined;
|
|
38
|
+
}, [aspectRatio, element, focusPoint?.x, focusPoint?.y, height, image, isLoaded, width]);
|
|
39
|
+
|
|
40
|
+
// Compute focus point CSS style.
|
|
41
|
+
return useMemo(() => {
|
|
42
|
+
if (aspectRatio === AspectRatio.original || (!focusPoint?.x && !focusPoint?.y)) return {};
|
|
43
|
+
if (element && naturalSize) {
|
|
44
|
+
const actualWidth = element.offsetWidth;
|
|
45
|
+
const actualHeight = element.offsetHeight;
|
|
46
|
+
const heightScale = actualHeight / naturalSize.height;
|
|
47
|
+
const widthScale = actualWidth / naturalSize.width;
|
|
48
|
+
const x = shiftPosition(heightScale, actualWidth, naturalSize.width, focusPoint?.x);
|
|
49
|
+
const y = shiftPosition(widthScale, actualHeight, naturalSize.height, focusPoint?.y, true);
|
|
50
|
+
return { objectPosition: `${x}% ${y}%` };
|
|
51
|
+
}
|
|
52
|
+
// Focus point can't be computed yet => We hide the image until it can.
|
|
53
|
+
return { visibility: 'hidden' };
|
|
54
|
+
}, [aspectRatio, element, focusPoint, naturalSize]);
|
|
55
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
2
|
|
|
3
3
|
export type LoadingState = 'isLoading' | 'isLoaded' | 'hasError';
|
|
4
4
|
|
|
@@ -15,17 +15,17 @@ function getState(img: HTMLImageElement | null | undefined, event?: Event) {
|
|
|
15
15
|
return 'isLoaded';
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
export function useImageLoad(imageURL: string, imgRef?:
|
|
19
|
-
const [state, setState] = useState<LoadingState>(getState(imgRef
|
|
18
|
+
export function useImageLoad(imageURL: string, imgRef?: HTMLImageElement): LoadingState {
|
|
19
|
+
const [state, setState] = useState<LoadingState>(getState(imgRef));
|
|
20
20
|
|
|
21
21
|
// Update state when changing image URL or DOM reference.
|
|
22
22
|
useEffect(() => {
|
|
23
|
-
setState(getState(imgRef
|
|
23
|
+
setState(getState(imgRef));
|
|
24
24
|
}, [imageURL, imgRef]);
|
|
25
25
|
|
|
26
26
|
// Listen to `load` and `error` event on image
|
|
27
27
|
useEffect(() => {
|
|
28
|
-
const img = imgRef
|
|
28
|
+
const img = imgRef;
|
|
29
29
|
if (!img) return undefined;
|
|
30
30
|
const update = (event?: Event) => setState(getState(img, event));
|
|
31
31
|
img.addEventListener('load', update);
|
|
@@ -34,7 +34,7 @@ export function useImageLoad(imageURL: string, imgRef?: RefObject<HTMLImageEleme
|
|
|
34
34
|
img.removeEventListener('load', update);
|
|
35
35
|
img.removeEventListener('error', update);
|
|
36
36
|
};
|
|
37
|
-
}, [imgRef, imgRef?.
|
|
37
|
+
}, [imgRef, imgRef?.src]);
|
|
38
38
|
|
|
39
39
|
return state;
|
|
40
40
|
}
|
|
@@ -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 });
|
package/types.d.ts
CHANGED
|
@@ -2433,6 +2433,7 @@ export interface TextFieldProps extends GenericProps {
|
|
|
2433
2433
|
* @return React element.
|
|
2434
2434
|
*/
|
|
2435
2435
|
export declare const TextField: Comp<TextFieldProps, HTMLDivElement>;
|
|
2436
|
+
export declare const useFocusPointStyle: ({ image, aspectRatio, focusPoint, imgProps: { width, height } }: ThumbnailProps, element: HTMLImageElement | undefined, isLoaded: boolean) => CSSProperties;
|
|
2436
2437
|
/**
|
|
2437
2438
|
* Defines the props of the component.
|
|
2438
2439
|
*/
|