pl-fe: trying to fix the media modal stuff, i hate webdev

Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
This commit is contained in:
nicole mikołajczyk
2026-02-06 14:57:48 +01:00
parent b5a388fb75
commit 3f26e7ba19
7 changed files with 140 additions and 98 deletions

View File

@ -217,7 +217,7 @@ const ModalRoot: React.FC<IModalRoot> = ({ children, onCancel, onClose, type, mo
<div
role='dialog'
className='⁂-modal-root__modal'
className='⁂-modal-root__container'
>
{children}
</div>

View File

@ -85,7 +85,7 @@ const ModalRoot: React.FC = () => {
<Base onClose={onClickClose} type={type} modalIndex={index}>
{(Component && !!type) && (
<Suspense fallback={renderLoading(type)}>
<Component {...props} onClose={onClickClose} />
<Component key={index} {...props} onClose={onClickClose} />
</Suspense>
)}
</Base>

View File

@ -12,7 +12,6 @@ import StatusActionBar from 'pl-fe/components/status-action-bar';
import HStack from 'pl-fe/components/ui/hstack';
import Icon from 'pl-fe/components/ui/icon';
import IconButton from 'pl-fe/components/ui/icon-button';
import Stack from 'pl-fe/components/ui/stack';
import Audio from 'pl-fe/features/audio';
import PlaceholderStatus from 'pl-fe/features/placeholder/components/placeholder-status';
import Thread from 'pl-fe/features/status/components/thread';
@ -179,7 +178,7 @@ const MediaModal: React.FC<MediaModalProps & BaseModalProps> = (props) => {
setZoomedIn((prev) => !prev);
}, []);
const content = useMemo(() => media.map((attachment, i) => {
const content = useMemo(() => media.map((attachment, idx) => {
let width: number | undefined, height: number | undefined;
if (attachment.type === 'image' || attachment.type === 'gifv' || attachment.type === 'video') {
width = (attachment.meta?.original?.width);
@ -195,18 +194,18 @@ const MediaModal: React.FC<MediaModalProps & BaseModalProps> = (props) => {
if (attachment.type === 'image') {
return (
<ZoomableImage
blurhash={attachment.blurhash || undefined}
src={attachment.url}
blurhash={attachment.blurhash || undefined}
width={width!}
height={height!}
alt={attachment.description}
key={attachment.url}
lang={props.lang}
key={attachment.url}
onClick={toggleNavigation}
onDoubleClick={handleZoomClick}
onClose={onClose}
onZoomChange={setZoomedIn}
zoomedIn={zoomedIn && i === index}
zoomedIn={zoomedIn && idx === index}
/>
);
} else if (attachment.type === 'video') {
@ -219,7 +218,7 @@ const MediaModal: React.FC<MediaModalProps & BaseModalProps> = (props) => {
height={height}
startTime={time}
detailed
autoFocus={i === index}
autoFocus={idx === index}
link={link}
alt={attachment.description}
key={attachment.url}
@ -293,27 +292,27 @@ const MediaModal: React.FC<MediaModalProps & BaseModalProps> = (props) => {
};
return (
<div className='⁂-media-modal media-modal pointer-events-auto fixed inset-0 z-[9999] h-full bg-gray-900/90'>
<div className={clsx('⁂-media-modal', { '⁂-media-modal--fullscreen': isFullScreen })} role='presentation'>
<div
className='absolute inset-0'
role='presentation'
{...bind()}
onClick={handleClickOutside}
className='⁂-media-modal__content'
ref={handleRef}
>
<Stack
{...bind()}
onClick={handleClickOutside}
className={
clsx('⁂-media-modal__content fixed inset-0 h-full grow touch-pan-y transition-all', {
'xl:pr-96': !isFullScreen,
'xl:pr-0': isFullScreen,
})
}
justifyContent='between'
ref={handleRef}
<animated.div
style={wrapperStyles}
className='⁂-media-modal__closer'
role='presentation'
onClick={() => onClose()}
>
{content}
</animated.div>
<div className='⁂-media-modal__navigation'>
<HStack
alignItems='center'
justifyContent='between'
className={clsx('flex-[0_0_60px] p-4 transition-opacity', navigationHiddenClassName)}
className={clsx('pointer-events-auto z-10 flex-[0_0_60px] p-4 transition-opacity', navigationHiddenClassName)}
>
<IconButton
title={intl.formatMessage(messages.close)}
@ -357,13 +356,9 @@ const MediaModal: React.FC<MediaModalProps & BaseModalProps> = (props) => {
)}
</HStack>
</HStack>
{/* Height based on height of top/bottom bars */}
<div
className='relative h-[calc(100vh-120px)] w-full grow'
>
{hasMultipleImages && (
<div className={clsx('absolute left-5 top-[calc(50%-0.625rem)] z-10 flex h-fit items-center transition-opacity', navigationHiddenClassName)}>
{hasMultipleImages && (
<HStack className='z-10 mx-5' justifyContent='between'>
<div className={clsx('pointer-events-auto z-10 flex h-fit items-center transition-opacity', navigationHiddenClassName)}>
<button
tabIndex={0}
className='flex size-10 items-center justify-center rounded-full bg-gray-900 text-white'
@ -373,19 +368,7 @@ const MediaModal: React.FC<MediaModalProps & BaseModalProps> = (props) => {
<Icon src={require('@phosphor-icons/core/regular/arrow-left.svg')} className='size-5' />
</button>
</div>
)}
<animated.div
style={wrapperStyles}
className='media-modal__closer'
role='presentation'
onClick={() => onClose()}
>
{content}
</animated.div>
{hasMultipleImages && (
<div className={clsx('absolute right-5 top-[calc(50%-0.625rem)] z-10 flex h-fit items-center transition-opacity', navigationHiddenClassName)}>
<div className={clsx('pointer-events-auto z-10 flex h-fit items-center transition-opacity', navigationHiddenClassName)}>
<button
tabIndex={0}
className='flex size-10 items-center justify-center rounded-full bg-gray-900 text-white'
@ -395,13 +378,12 @@ const MediaModal: React.FC<MediaModalProps & BaseModalProps> = (props) => {
<Icon src={require('@phosphor-icons/core/regular/arrow-right.svg')} className='size-5' />
</button>
</div>
)}
</div>
{status && (
</HStack>
)}
{status ? (
<HStack
justifyContent='center'
className={clsx('flex-[0_0_60px] transition-opacity', navigationHiddenClassName)}
className={clsx('pointer-events-auto flex-[0_0_60px] transition-opacity', navigationHiddenClassName)}
>
<StatusActionBar
status={status}
@ -409,26 +391,26 @@ const MediaModal: React.FC<MediaModalProps & BaseModalProps> = (props) => {
expandable
/>
</HStack>
)}
</Stack>
{status && (
<div
className={
clsx('-right-96 hidden bg-white transition-all xl:fixed xl:inset-y-0 xl:right-0 xl:flex xl:w-96 xl:flex-col', {
'xl:!-right-96': isFullScreen,
})
}
>
<Thread
status={status}
withMedia={false}
itemClassName='px-4'
isModal
/>
</div>
)}
) : <span />}
</div>
</div>
{status && (
<div
className={
clsx('-right-96 hidden bg-white transition-all xl:fixed xl:inset-y-0 xl:right-0 xl:flex xl:w-96 xl:flex-col', {
'xl:!-right-96': isFullScreen,
})
}
>
<Thread
status={status}
withMedia={false}
itemClassName='px-4'
isModal
/>
</div>
)}
</div>
);
};

View File

@ -1,6 +1,4 @@
.media-modal {
touch-action: pan-y;
.-media-modal {
.audio-player.detailed,
.extended-video-player {
@apply flex items-center justify-center;
@ -17,23 +15,4 @@
@apply max-w-full max-h-[80%];
}
}
&__closer {
display: flex;
position: absolute;
top: 0;
inset-inline-start: 0;
inset-inline-end: 0;
bottom: 0;
> div {
flex-shrink: 0;
overflow: auto;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
}
}
}
}

View File

@ -152,7 +152,7 @@
}
}
.-modal-root__modal .-compose-form__select-buttons {
.-modal-root__container .-compose-form__select-buttons {
padding-top: 0.25rem;
}

View File

@ -270,25 +270,38 @@ body {
@apply fixed inset-0 bg-gray-500/90 black:bg-gray-900/90 dark:bg-gray-700/90;
}
&__modal {
@apply mx-auto relative pointer-events-none flex items-center;
min-height: calc(min(100vh - 3.5rem, 100dvh));
&__container {
position: fixed;
top: 0;
inset-inline-start: 0;
width: 100%;
height: 100%;
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
align-content: space-around;
pointer-events: none;
user-select: none;
overscroll-behavior: none;
min-height: 100dvh;
z-index: 9999;
}
&[data-modal-type='DROPDOWN_MENU'] &__overlay {
@apply opacity-60;
}
&[data-modal-type='MEDIA'], &[data-modal-type='DROPDOWN_MENU'] {
.-modal-root__modal {
&[data-modal-type='DROPDOWN_MENU'] {
.-modal-root__container {
margin-top: 0!important;
margin-bottom: 0!important;
}
}
&:not([data-modal-type='MEDIA']) &__modal {
&:not([data-modal-type='MEDIA']) &__container {
display: flex;
height: 100%;
padding: 0 1rem;
@apply md:p-0;
}

View File

@ -1,3 +1,5 @@
@use 'variables';
.-crypto-donate-modal {
@apply max-w-sm;
}
@ -6,6 +8,72 @@
@apply max-w-4xl;
}
.-media-modal {
pointer-events: auto;
user-select: text;
display: flex;
max-width: 100vw;
width: 100%;
height: 100%;
position: relative;
touch-action: pan-y;
@apply bg-gray-900/90;
&__content {
@apply flex flex-col justify-between fixed inset-0 h-full grow touch-pan-y transition-all xl:pr-96;
}
&__closer {
display: flex;
position: absolute;
top: 0;
inset-inline-start: 0;
inset-inline-end: 0;
bottom: 0;
@media (min-width: variables.$breakpoint-xl) {
inset-inline-end: 24rem;
}
> div {
flex-shrink: 0;
overflow: auto;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
}
}
&__navigation {
display: flex;
flex-direction: column;
justify-content: space-between;
position: absolute;
top: 0;
inset-inline-start: 0;
inset-inline-end: 0;
bottom: 0;
pointer-events: none;
transition: opacity .3s linear;
will-change: opacity;
&--hidden {
opacity: 0;
}
}
&--fullscreen {
.-media-modal__content {
@apply xl:pr-0;
}
.-media-modal__closer {
inset-inline-end: 0;
}
}
}
.-zoomable-image {
position: relative;
width: 100%;