fix image carousel, still needs a bit of work though

This commit is contained in:
2026-01-24 17:03:53 +00:00
parent 66f481a0b9
commit 5d7d8db061
2 changed files with 35 additions and 62 deletions

View File

@ -1,6 +1,4 @@
import { animated, useSpring } from '@react-spring/web';
import { Link } from '@tanstack/react-router';
import { useDrag } from '@use-gesture/react';
import clsx from 'clsx';
import React, { type RefCallback, useCallback, useEffect, useMemo, useState } from 'react';
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
@ -26,8 +24,6 @@ import { makeGetStatus } from 'pl-fe/selectors';
import type { MediaAttachment } from 'pl-api';
import type { BaseModalProps } from 'pl-fe/features/ui/components/modal-root';
const MIN_SWIPE_DISTANCE = 400;
const messages = defineMessages({
close: { id: 'lightbox.close', defaultMessage: 'Close' },
expand: { id: 'lightbox.expand', defaultMessage: 'Expand' },
@ -67,12 +63,8 @@ const MediaModal: React.FC<MediaModalProps & BaseModalProps> = (props) => {
const [navigationHidden, setNavigationHidden] = useState(false);
const [isFullScreen, setIsFullScreen] = useState(!status);
const [wrapperStyles, api] = useSpring(() => ({
x: `-${index * 100}%`,
}));
const handleChangeIndex = useCallback(
(newIndex: number, animate = false) => {
(newIndex: number) => {
if (newIndex < 0) {
newIndex = media.length + newIndex;
} else if (newIndex >= media.length) {
@ -80,17 +72,18 @@ const MediaModal: React.FC<MediaModalProps & BaseModalProps> = (props) => {
}
setIndex(newIndex);
setZoomedIn(false);
if (animate) {
void api.start({ x: `calc(-${newIndex * 100}% + 0px)` });
}
},
[api, media.length],
[media.length],
);
const handlePrevClick = useCallback(() => {
handleChangeIndex(index - 1, true);
const handlePrevClick = useCallback((e: React.MouseEvent) => {
e.stopPropagation();
handleChangeIndex(index - 1);
}, [handleChangeIndex, index]);
const handleNextClick = useCallback(() => {
handleChangeIndex(index + 1, true);
const handleNextClick = useCallback((e: React.MouseEvent) => {
e.stopPropagation();
handleChangeIndex(index + 1);
}, [handleChangeIndex, index]);
const [viewportDimensions, setViewportDimensions] = useState<{
@ -111,44 +104,20 @@ const MediaModal: React.FC<MediaModalProps & BaseModalProps> = (props) => {
const navigationHiddenClassName = navigationHidden ? 'pointer-events-none opacity-0' : '';
const handleKeyDown = (e: KeyboardEvent) => {
const handleKeyDown = useCallback((e: KeyboardEvent) => {
switch (e.key) {
case 'ArrowLeft':
handlePrevClick();
handleChangeIndex(index - 1);
e.preventDefault();
e.stopPropagation();
break;
case 'ArrowRight':
handleNextClick();
handleChangeIndex(index + 1);
e.preventDefault();
e.stopPropagation();
break;
}
};
const bind = useDrag(
({ active, movement: [mx], direction: [xDir], cancel }) => {
// Disable swipe when zoomed in.
if (zoomedIn) {
return;
}
// If dragging and swipe distance is enough, change the index.
if (
active &&
Math.abs(mx) > Math.min(window.innerWidth / 4, MIN_SWIPE_DISTANCE)
) {
handleChangeIndex(index - xDir);
cancel();
}
// Set the x position via calc to ensure proper centering regardless of screen size.
const x = active ? mx : 0;
void api.start({
x: `calc(-${index * 100}% + ${x}px)`,
});
},
{ pointer: { capture: false } },
);
}, [handleChangeIndex, index]);
const handleDownload = () => {
const mediaItem = hasMultipleImages ? media[index as number] : media[0];
@ -289,10 +258,9 @@ const MediaModal: React.FC<MediaModalProps & BaseModalProps> = (props) => {
role='presentation'
>
<Stack
{...bind()}
onClick={handleClickOutside}
className={
clsx('⁂-media-modal__content fixed inset-0 h-full grow touch-pan-y transition-all', {
clsx('⁂-media-modal__content fixed inset-0 h-full grow transition-all', {
'xl:pr-96': !isFullScreen,
'xl:pr-0': isFullScreen,
})
@ -365,14 +333,18 @@ const MediaModal: React.FC<MediaModalProps & BaseModalProps> = (props) => {
</div>
)}
<animated.div
style={wrapperStyles}
className='media-modal__closer'
role='presentation'
onClick={() => onClose()}
>
{content}
</animated.div>
<div className='absolute inset-0 overflow-hidden'>
<div
style={{
width: `${media.length * 100}%`,
transform: `translateX(-${index * (100 / media.length)}%)`,
}}
className='media-modal__closer transition-transform duration-300 ease-out'
role='presentation'
>
{content}
</div>
</div>
{hasMultipleImages && (
<div className={clsx('absolute inset-y-0 right-5 z-10 flex items-center transition-opacity', navigationHiddenClassName)}>

View File

@ -1,5 +1,5 @@
.media-modal {
touch-action: pan-y;
touch-action: none;
.audio-player.detailed,
.extended-video-player {
@ -19,20 +19,21 @@
}
&__closer {
display: flex;
position: absolute;
top: 0;
inset-inline-start: 0;
inset-inline-end: 0;
bottom: 0;
left: 0;
display: flex;
flex-wrap: nowrap;
height: 100%;
> div {
flex-shrink: 0;
overflow: auto;
flex: 1 0 0;
min-width: 0;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
overflow: hidden;
}
}
}