@lumx/react 3.7.5 → 3.7.6-alpha.0
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/index.d.ts +76 -12
- package/index.js +1466 -718
- package/index.js.map +1 -1
- package/package.json +3 -3
- package/src/components/image-block/ImageBlock.tsx +13 -42
- package/src/components/image-block/ImageCaption.tsx +73 -0
- package/src/components/image-block/constants.ts +11 -0
- package/src/components/image-lightbox/ImageLightbox.stories.tsx +163 -0
- package/src/components/image-lightbox/ImageLightbox.test.tsx +252 -0
- package/src/components/image-lightbox/ImageLightbox.tsx +72 -0
- package/src/components/image-lightbox/constants.ts +11 -0
- package/src/components/image-lightbox/index.ts +2 -0
- package/src/components/image-lightbox/internal/ImageSlide.tsx +99 -0
- package/src/components/image-lightbox/internal/ImageSlideshow.tsx +158 -0
- package/src/components/image-lightbox/internal/useAnimateScroll.ts +55 -0
- package/src/components/image-lightbox/internal/usePointerZoom.ts +148 -0
- package/src/components/image-lightbox/types.ts +49 -0
- package/src/components/image-lightbox/useImageLightbox.tsx +122 -0
- package/src/components/lightbox/Lightbox.tsx +13 -12
- package/src/components/thumbnail/useFocusPointStyle.tsx +3 -4
- package/src/hooks/useElementSizeDependentOfWindowSize.ts +32 -0
- package/src/hooks/useImageSize.ts +17 -0
- package/src/index.ts +1 -0
- package/src/utils/findImage.tsx +3 -0
- package/src/utils/getPrefersReducedMotion.ts +6 -0
- package/src/utils/startViewTransition.ts +54 -0
- package/src/utils/type.ts +15 -0
- package/src/utils/unref.ts +6 -0
- package/src/hooks/useOnResize.ts +0 -41
|
@@ -4,7 +4,7 @@ import classNames from 'classnames';
|
|
|
4
4
|
import { createPortal } from 'react-dom';
|
|
5
5
|
|
|
6
6
|
import { mdiClose } from '@lumx/icons';
|
|
7
|
-
import {
|
|
7
|
+
import { IconButton, IconButtonProps } from '@lumx/react';
|
|
8
8
|
import { DIALOG_TRANSITION_DURATION, DOCUMENT } from '@lumx/react/constants';
|
|
9
9
|
import { Comp, GenericProps, HasTheme } from '@lumx/react/utils/type';
|
|
10
10
|
import { getRootClassName, handleBasicClasses } from '@lumx/react/utils/className';
|
|
@@ -145,17 +145,18 @@ export const Lightbox: Comp<LightboxProps, HTMLDivElement> = forwardRef((props,
|
|
|
145
145
|
style={{ zIndex }}
|
|
146
146
|
>
|
|
147
147
|
{closeButtonProps && (
|
|
148
|
-
<
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
148
|
+
<div className={`${CLASSNAME}__close`}>
|
|
149
|
+
<IconButton
|
|
150
|
+
{...closeButtonProps}
|
|
151
|
+
ref={closeButtonRef}
|
|
152
|
+
emphasis="low"
|
|
153
|
+
hasBackground
|
|
154
|
+
icon={mdiClose}
|
|
155
|
+
theme="dark"
|
|
156
|
+
type="button"
|
|
157
|
+
onClick={onClose}
|
|
158
|
+
/>
|
|
159
|
+
</div>
|
|
159
160
|
)}
|
|
160
161
|
<ClickAwayProvider callback={!preventAutoClose && onClose} childrenRefs={clickAwayRefs}>
|
|
161
162
|
<div ref={childrenRef} className={`${CLASSNAME}__wrapper`} role="presentation">
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { CSSProperties, useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import { AspectRatio } from '@lumx/react/components';
|
|
3
3
|
import { ThumbnailProps } from '@lumx/react/components/thumbnail/Thumbnail';
|
|
4
|
+
import { RectSize } from '@lumx/react/utils/type';
|
|
4
5
|
|
|
5
6
|
// Calculate shift to center the focus point in the container.
|
|
6
7
|
export function shiftPosition({
|
|
@@ -24,8 +25,6 @@ export function shiftPosition({
|
|
|
24
25
|
return Math.floor(Math.max(Math.min(shift, 1), 0) * 100);
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
type Size = { width: number; height: number };
|
|
28
|
-
|
|
29
28
|
// Compute CSS properties to apply the focus point.
|
|
30
29
|
export const useFocusPointStyle = (
|
|
31
30
|
{ image, aspectRatio, focusPoint, imgProps: { width, height } = {} }: ThumbnailProps,
|
|
@@ -33,7 +32,7 @@ export const useFocusPointStyle = (
|
|
|
33
32
|
isLoaded: boolean,
|
|
34
33
|
): CSSProperties => {
|
|
35
34
|
// Get natural image size from imgProps or img element.
|
|
36
|
-
const imageSize:
|
|
35
|
+
const imageSize: RectSize | undefined = useMemo(() => {
|
|
37
36
|
// Focus point is not applicable => exit early
|
|
38
37
|
if (!image || aspectRatio === AspectRatio.original || (!focusPoint?.x && !focusPoint?.y)) return undefined;
|
|
39
38
|
if (typeof width === 'number' && typeof height === 'number') return { width, height };
|
|
@@ -42,7 +41,7 @@ export const useFocusPointStyle = (
|
|
|
42
41
|
}, [aspectRatio, element, focusPoint?.x, focusPoint?.y, height, image, isLoaded, width]);
|
|
43
42
|
|
|
44
43
|
// Get container size (dependant on imageSize).
|
|
45
|
-
const [containerSize, setContainerSize] = useState<
|
|
44
|
+
const [containerSize, setContainerSize] = useState<RectSize | undefined>(undefined);
|
|
46
45
|
useEffect(
|
|
47
46
|
function updateContainerSize() {
|
|
48
47
|
const cWidth = element?.offsetWidth;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import { RectSize } from '@lumx/react/utils/type';
|
|
4
|
+
import throttle from 'lodash/throttle';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Observe element size (only works if it's size depends on the window size).
|
|
8
|
+
*
|
|
9
|
+
* (Not using ResizeObserver for better browser backward compat)
|
|
10
|
+
*
|
|
11
|
+
* @param elementRef Element to observe
|
|
12
|
+
* @return the size and a manual update callback
|
|
13
|
+
*/
|
|
14
|
+
export function useElementSizeDependentOfWindowSize(
|
|
15
|
+
elementRef: React.RefObject<HTMLElement>,
|
|
16
|
+
): [RectSize | null, () => void] {
|
|
17
|
+
const [size, setSize] = React.useState<null | RectSize>(null);
|
|
18
|
+
const updateSize = React.useMemo(
|
|
19
|
+
() =>
|
|
20
|
+
throttle(() => {
|
|
21
|
+
const newSize = elementRef?.current?.getBoundingClientRect();
|
|
22
|
+
if (newSize) setSize(newSize);
|
|
23
|
+
}, 10),
|
|
24
|
+
[elementRef],
|
|
25
|
+
);
|
|
26
|
+
React.useEffect(() => {
|
|
27
|
+
updateSize();
|
|
28
|
+
window.addEventListener('resize', updateSize);
|
|
29
|
+
return () => window.removeEventListener('resize', updateSize);
|
|
30
|
+
}, [updateSize]);
|
|
31
|
+
return [size, updateSize];
|
|
32
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { RectSize } from '@lumx/react/utils/type';
|
|
3
|
+
|
|
4
|
+
/** Get natural image size after load. */
|
|
5
|
+
export function useImageSize(imgRef: React.RefObject<HTMLImageElement>, getInitialSize?: () => RectSize | null) {
|
|
6
|
+
const [imageSize, setImageSize] = React.useState<null | RectSize>(getInitialSize || null);
|
|
7
|
+
React.useEffect(() => {
|
|
8
|
+
const { current: img } = imgRef;
|
|
9
|
+
if (!img) {
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
const onLoad = () => setImageSize({ width: img.naturalWidth, height: img.naturalHeight });
|
|
13
|
+
img.addEventListener('load', onLoad);
|
|
14
|
+
return () => img.removeEventListener('load', onLoad);
|
|
15
|
+
}, [imgRef]);
|
|
16
|
+
return imageSize;
|
|
17
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -25,6 +25,7 @@ export * from './components/grid';
|
|
|
25
25
|
export * from './components/grid-column';
|
|
26
26
|
export * from './components/icon';
|
|
27
27
|
export * from './components/image-block';
|
|
28
|
+
export * from './components/image-lightbox';
|
|
28
29
|
export * from './components/inline-list';
|
|
29
30
|
export * from './components/input-helper';
|
|
30
31
|
export * from './components/input-label';
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import ReactDOM from 'react-dom';
|
|
2
|
+
import { getPrefersReducedMotion } from '@lumx/react/utils/getPrefersReducedMotion';
|
|
3
|
+
import { MaybeElementOrRef } from '@lumx/react/utils/type';
|
|
4
|
+
import { unref } from '@lumx/react/utils/unref';
|
|
5
|
+
|
|
6
|
+
function setTransitionViewName(elementRef: MaybeElementOrRef<HTMLElement>, name: string | null | undefined) {
|
|
7
|
+
const element = unref(elementRef) as any;
|
|
8
|
+
if (element) element.style.viewTransitionName = name;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Wrapper around the `document.startViewTransition` handling browser incompatibilities, react DOM flush and
|
|
13
|
+
* user preference.
|
|
14
|
+
*
|
|
15
|
+
* @param changes callback containing the changes to apply within the view transition.
|
|
16
|
+
* @param setViewTransitionName set the `viewTransitionName` style on a `source` & `target` to morph these elements.
|
|
17
|
+
*/
|
|
18
|
+
export async function startViewTransition({
|
|
19
|
+
changes,
|
|
20
|
+
viewTransitionName,
|
|
21
|
+
}: {
|
|
22
|
+
changes: () => void;
|
|
23
|
+
viewTransitionName: {
|
|
24
|
+
source: MaybeElementOrRef<HTMLElement>;
|
|
25
|
+
target: MaybeElementOrRef<HTMLElement>;
|
|
26
|
+
name: string;
|
|
27
|
+
};
|
|
28
|
+
}) {
|
|
29
|
+
const start = (document as any)?.startViewTransition?.bind(document);
|
|
30
|
+
const prefersReducedMotion = getPrefersReducedMotion();
|
|
31
|
+
const { flushSync } = ReactDOM as any;
|
|
32
|
+
if (prefersReducedMotion || !start || !flushSync || !viewTransitionName?.source || !viewTransitionName?.target) {
|
|
33
|
+
// Skip, apply changes without a transition
|
|
34
|
+
changes();
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Set transition name on source element
|
|
39
|
+
setTransitionViewName(viewTransitionName.source, viewTransitionName.name);
|
|
40
|
+
|
|
41
|
+
// Start view transition, apply changes & flush to DOM
|
|
42
|
+
await start(() => {
|
|
43
|
+
// Un-set transition name on source element
|
|
44
|
+
setTransitionViewName(viewTransitionName.source, null);
|
|
45
|
+
|
|
46
|
+
flushSync(changes);
|
|
47
|
+
|
|
48
|
+
// Set transition name on target element
|
|
49
|
+
setTransitionViewName(viewTransitionName.target, viewTransitionName.name);
|
|
50
|
+
}).updateCallbackDone;
|
|
51
|
+
|
|
52
|
+
// Un-set transition name on target element
|
|
53
|
+
setTransitionViewName(viewTransitionName.target, null);
|
|
54
|
+
}
|
package/src/utils/type.ts
CHANGED
|
@@ -139,3 +139,18 @@ export type ComponentRef<C> = C extends keyof JSX.IntrinsicElements
|
|
|
139
139
|
: C extends React.JSXElementConstructor<{ ref?: infer R }>
|
|
140
140
|
? R
|
|
141
141
|
: never;
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Rectangle size
|
|
145
|
+
*/
|
|
146
|
+
export type RectSize = { width: number; height: number };
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Maybe a HTMLElement or a React ref of a HTMLElement
|
|
150
|
+
*/
|
|
151
|
+
export type MaybeElementOrRef<E extends HTMLElement> = E | React.RefObject<E | null> | null | undefined;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* A point coordinate in 2D space
|
|
155
|
+
*/
|
|
156
|
+
export type Point = { x: number; y: number };
|
package/src/hooks/useOnResize.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { Callback, Falsy } from '@lumx/react/utils/type';
|
|
2
|
-
import { MutableRefObject, RefObject, useEffect, useRef } from 'react';
|
|
3
|
-
import { WINDOW } from '@lumx/react/constants';
|
|
4
|
-
import { ResizeObserver as Polyfill } from '@juggle/resize-observer';
|
|
5
|
-
|
|
6
|
-
const ResizeObserver: typeof Polyfill = (WINDOW as any)?.ResizeObserver || Polyfill;
|
|
7
|
-
|
|
8
|
-
export function useOnResize(element: HTMLElement | Falsy, update: RefObject<Callback>): void {
|
|
9
|
-
const observerRef = useRef(null) as MutableRefObject<Polyfill | null>;
|
|
10
|
-
const previousSize = useRef<{ width: number; height: number }>();
|
|
11
|
-
|
|
12
|
-
useEffect(() => {
|
|
13
|
-
if (!element || !update) {
|
|
14
|
-
return undefined;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
previousSize.current = undefined;
|
|
18
|
-
const observer =
|
|
19
|
-
observerRef.current ||
|
|
20
|
-
new ResizeObserver(([entry]) => {
|
|
21
|
-
const updateFunction = update.current;
|
|
22
|
-
if (!updateFunction) {
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const { width, height } = entry.contentRect;
|
|
27
|
-
if (previousSize.current?.width === width && previousSize.current?.height === height) {
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
window.requestAnimationFrame(() => updateFunction());
|
|
32
|
-
previousSize.current = entry.contentRect;
|
|
33
|
-
});
|
|
34
|
-
if (!observerRef.current) observerRef.current = observer;
|
|
35
|
-
|
|
36
|
-
observer.observe(element);
|
|
37
|
-
return () => {
|
|
38
|
-
observer.unobserve(element);
|
|
39
|
-
};
|
|
40
|
-
}, [element, update]);
|
|
41
|
-
}
|