Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
/* eslint-disable complexity */
'use client';
import Image from 'next/image';
import { memo, useEffect, useState } from 'react';
import Fancybox from '@/shared/ui/Fancybox/Fancybox';
import { memo, useEffect, useState, useRef } from 'react';
import { useClientTranslation } from '@/shared/i18n';
import { ReaderModal } from '@/entities/Gallery/ui/ReaderModal';
import cls from './styles.module.scss';

export type GalleryCategoriesWithModalSliderProps = {
Expand All @@ -19,17 +19,36 @@ export type GalleryCategoriesWithModalSliderProps = {
export const GalleryCategoriesWithModalSlider = memo(
({ sources, cover }: GalleryCategoriesWithModalSliderProps) => {
useClientTranslation('picture-galleries');

const [isModalOpen, setIsModalOpen] = useState(false);
const [pageIndex, setPageIndex] = useState(0);
const [isMobile, setIsMobile] = useState(false);

const touchStartX = useRef(0);
const touchEndX = useRef(0);

useEffect(() => {
const checkMobile = () => {
setIsMobile(window.innerWidth <= 768);
};

checkMobile();
window.addEventListener('resize', checkMobile);

return () => window.removeEventListener('resize', checkMobile);
}, []);

const filteredSources = sources.filter((src) => src !== cover.url);

const sortedSources = [...filteredSources].sort((a, b) => {
const numA = parseInt(a.match(/\d+/)?.[0] || '', 10);
const numB = parseInt(b.match(/\d+/)?.[0] || '', 10);
return numA - numB;
});

const allImages = [cover.url, ...sortedSources];
const maxPageIndex = Math.ceil(sortedSources.length / 2);

const maxPageIndex = isMobile ? sortedSources.length : Math.ceil(sortedSources.length / 2);

const changePage = (direction: 'next' | 'prev') => {
setPageIndex((prev) =>
Expand All @@ -39,30 +58,69 @@ export const GalleryCategoriesWithModalSlider = memo(

useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === 'ArrowLeft' && pageIndex > 0) changePage('prev');
else if (event.key === 'ArrowRight' && pageIndex < maxPageIndex) changePage('next');
if (event.key === 'ArrowLeft' && pageIndex > 0) {
changePage('prev');
}

if (event.key === 'ArrowRight' && pageIndex < maxPageIndex) {
changePage('next');
}
};

window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [pageIndex, maxPageIndex]);

const openCurrentInFancybox = () => {
const indexToOpen = pageIndex === 0 ? 0 : 1 + (pageIndex - 1) * 2;
const el = document.getElementById(
`fancybox-image-${indexToOpen}`,
) as HTMLAnchorElement;
if (el) el.click();
useEffect(() => {
if (isModalOpen) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = '';
}
}, [isModalOpen]);

const openModal = () => {
setIsModalOpen(true);
};

// SWIPE
const handleTouchStart = (e: React.TouchEvent) => {
touchStartX.current = e.touches[0].clientX;
};

const handleTouchMove = (e: React.TouchEvent) => {
touchEndX.current = e.touches[0].clientX;
};

const handleTouchEnd = () => {
if (!isMobile) return;

const delta = touchStartX.current - touchEndX.current;

if (Math.abs(delta) < 50) return;

if (delta > 0 && pageIndex < maxPageIndex) {
changePage('next');
}

if (delta < 0 && pageIndex > 0) {
changePage('prev');
}
};

const renderImages = () => {
const renderImages = (isModal = false) => {
return allImages.map((src, idx) => {
let visible = false;

if (pageIndex === 0) {
visible = idx === 0;
} else if (isMobile) {
visible = idx === pageIndex;
} else {
const start = (pageIndex - 1) * 2;
const leftIdx = 1 + start;
const rightIdx = 2 + start;

visible = idx === leftIdx || idx === rightIdx;
}

Expand All @@ -72,83 +130,93 @@ export const GalleryCategoriesWithModalSlider = memo(
key={idx}
style={{ display: visible ? 'flex' : 'none' }}
>
<a
id={`fancybox-image-${idx}`}
href={src}
data-fancybox={cover.name}
data-index={idx}
>
<Image
src={src}
width={250}
height={292}
className={cls.coverImage}
priority={true}
alt={idx === 0 ? cover.name : `Page ${idx}`}
style={{ width: '100%', height: 'auto' }}
/>
</a>
<Image
src={src}
width={isModal ? 600 : 250}
height={isModal ? 800 : 292}
className={isModal ? cls.modalImage : cls.coverImage}
priority
alt={idx === 0 ? cover.name : `Page ${idx}`}
style={{ width: '100%', height: 'auto' }}
/>
</div>
);
});
};

return (
<div style={{ cursor: 'pointer' }}>
<Fancybox>
{/* GALLERY */}
<div
className={cls.galleryContainer}
style={{ minHeight: '80vh' }}
>
<div
className={cls.galleryContainer}
style={{ minHeight: '80vh' }}
className={`${cls.zoomButtonWrapper} ${
pageIndex === 0 ? cls.coverZoomPosition : ''
}`}
>
{/* Zoom Button */}
<div
className={`${cls.zoomButtonWrapper} ${
pageIndex === 0 ? cls.coverZoomPosition : ''
}`}
<button
className={cls.zoomButton}
onClick={openModal}
>
<button
className={cls.zoomButton}
onClick={openCurrentInFancybox}
>
<Image
src="/images/ZoomPlus.png"
width={20}
height={20}
alt="Zoom"
style={{ width: 'auto', height: 'auto' }}
/>
</button>
</div>
<Image
src="/images/ZoomPlus.png"
width={20}
height={20}
alt="Zoom"
/>
</button>
</div>

<div
className={cls.cover}
onClick={openModal}
>
{renderImages(false)}
</div>
</div>

{/* Viewer */}
<div className={cls.cover}>
{/* MODAL */}
<ReaderModal
open={isModalOpen}
onClose={() => setIsModalOpen(false)}
>
<div className={cls.modalViewer}>
<div
className={cls.cover}
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
onTouchEnd={handleTouchEnd}
>
<span
onClick={pageIndex > 0 ? () => changePage('prev') : undefined}
className={`${cls.navSymbol} ${pageIndex === 0 ? cls.disabled : ''}`}
role="button"
tabIndex={pageIndex > 0 ? 0 : -1}
aria-label="Previous"
onClick={(e) => {
e.stopPropagation();
if (pageIndex > 0) changePage('prev');
}}
className={`${cls.navSymbol} ${
pageIndex === 0 ? cls.disabled : ''
}`}
>
{'<'}
</span>

{renderImages()}
{renderImages(true)}

<span
onClick={
pageIndex < maxPageIndex ? () => changePage('next') : undefined
}
onClick={(e) => {
e.stopPropagation();
if (pageIndex < maxPageIndex) changePage('next');
}}
className={`${cls.navSymbol} ${
pageIndex === maxPageIndex ? cls.disabled : ''
}`}
role="button"
tabIndex={pageIndex < maxPageIndex ? 0 : -1}
aria-label="Next"
>
{'>'}
</span>
</div>

{/* Page slider (βœ… stays close to cover) */}
{/* Slider */}
<div className={cls.sliderContainer}>
<button
className={cls.arrowButton}
Expand All @@ -158,27 +226,29 @@ export const GalleryCategoriesWithModalSlider = memo(
{'<'}
</button>

<span className={cls.pageNumber}>{pageIndex * 2 || 1}</span>
<span className={cls.pageNumber}>
{pageIndex === 0
? 1
: isMobile
? pageIndex + 1
: (pageIndex - 1) * 2 + 2}
</span>

<input
type="range"
min={0}
max={maxPageIndex}
value={pageIndex}
onChange={(event) => setPageIndex(parseInt(event.target.value))}
onChange={(e) => setPageIndex(parseInt(e.target.value))}
className={cls.pageSlider}
style={
{
'--percent':
maxPageIndex === 0
? '0%'
: `${(pageIndex / maxPageIndex) * 100}%`,
} as React.CSSProperties
}
/>

<span className={cls.pageNumber}>
{Math.min(sortedSources.length, (pageIndex || 0) * 2 + 1)}
{pageIndex === 0
? 1
: isMobile
? pageIndex + 1
: Math.min(sortedSources.length, (pageIndex - 1) * 2 + 3)}
</span>

<button
Expand All @@ -190,7 +260,7 @@ export const GalleryCategoriesWithModalSlider = memo(
</button>
</div>
</div>
</Fancybox>
</ReaderModal>
</div>
);
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
.dialog {
position: fixed;
inset: 0;

width: 100%;
height: 100%;

margin: 0;
padding: 0;
border: none;

background: transparent;
}

.dialog::backdrop {
background: rgba(0, 0, 0, 0.7);
}

.content {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);

display: flex;
justify-content: center;
align-items: center;
}

Loading
Loading