Remove container query requirement for carousel sizing
This commit is contained in:
parent
63c5a83911
commit
f2690b262f
1 changed files with 130 additions and 148 deletions
|
@ -1,21 +1,35 @@
|
||||||
import { useCallback, ReactNode, useRef, useState, isValidElement } from 'react';
|
import { isValidElement, ReactNode, useCallback, useMemo, useRef, useState } from 'react';
|
||||||
import { Box, Group } from '@mantine/core';
|
import { Group, Stack } from '@mantine/core';
|
||||||
import { useElementSize } from '@mantine/hooks';
|
import throttle from 'lodash/throttle';
|
||||||
import { AnimatePresence } from 'framer-motion';
|
|
||||||
import { RiArrowLeftSLine, RiArrowRightSLine } from 'react-icons/ri';
|
import { RiArrowLeftSLine, RiArrowRightSLine } from 'react-icons/ri';
|
||||||
import { Virtual, SwiperOptions } from 'swiper';
|
import styled from 'styled-components';
|
||||||
|
import { SwiperOptions, Virtual } from 'swiper';
|
||||||
|
import 'swiper/css';
|
||||||
import { Swiper, SwiperSlide } from 'swiper/react';
|
import { Swiper, SwiperSlide } from 'swiper/react';
|
||||||
import { Swiper as SwiperCore } from 'swiper/types';
|
import { Swiper as SwiperCore } from 'swiper/types';
|
||||||
import { PosterCard } from '/@/renderer/components/card/poster-card';
|
|
||||||
import { Album, AlbumArtist, Artist, LibraryItem, RelatedArtist } from '/@/renderer/api/types';
|
import { Album, AlbumArtist, Artist, LibraryItem, RelatedArtist } from '/@/renderer/api/types';
|
||||||
import { CardRoute, CardRow } from '/@/renderer/types';
|
|
||||||
import { TextTitle } from '/@/renderer/components/text-title';
|
|
||||||
import { Button } from '/@/renderer/components/button';
|
import { Button } from '/@/renderer/components/button';
|
||||||
import { usePlayButtonBehavior } from '/@/renderer/store';
|
import { PosterCard } from '/@/renderer/components/card/poster-card';
|
||||||
import { useCreateFavorite, useDeleteFavorite } from '/@/renderer/features/shared';
|
import { TextTitle } from '/@/renderer/components/text-title';
|
||||||
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
||||||
import { MotionStack } from '/@/renderer/components/motion';
|
import { useCreateFavorite, useDeleteFavorite } from '/@/renderer/features/shared';
|
||||||
import 'swiper/css';
|
import { usePlayButtonBehavior } from '/@/renderer/store';
|
||||||
|
import { CardRoute, CardRow } from '/@/renderer/types';
|
||||||
|
|
||||||
|
const getSlidesPerView = (windowWidth: number) => {
|
||||||
|
if (windowWidth < 400) return 2;
|
||||||
|
if (windowWidth < 700) return 3;
|
||||||
|
if (windowWidth < 900) return 4;
|
||||||
|
if (windowWidth < 1100) return 5;
|
||||||
|
if (windowWidth < 1300) return 6;
|
||||||
|
if (windowWidth < 1500) return 7;
|
||||||
|
if (windowWidth < 1920) return 8;
|
||||||
|
return 10;
|
||||||
|
};
|
||||||
|
|
||||||
|
const CarouselContainer = styled(Stack)`
|
||||||
|
container-type: inline-size;
|
||||||
|
`;
|
||||||
|
|
||||||
interface TitleProps {
|
interface TitleProps {
|
||||||
handleNext?: () => void;
|
handleNext?: () => void;
|
||||||
|
@ -81,15 +95,6 @@ interface SwiperGridCarouselProps {
|
||||||
uniqueId: string;
|
uniqueId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const variants = {
|
|
||||||
hidden: {
|
|
||||||
opacity: 0,
|
|
||||||
},
|
|
||||||
show: {
|
|
||||||
opacity: 1,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SwiperGridCarousel = ({
|
export const SwiperGridCarousel = ({
|
||||||
cardRows,
|
cardRows,
|
||||||
data,
|
data,
|
||||||
|
@ -100,15 +105,12 @@ export const SwiperGridCarousel = ({
|
||||||
isLoading,
|
isLoading,
|
||||||
uniqueId,
|
uniqueId,
|
||||||
}: SwiperGridCarouselProps) => {
|
}: SwiperGridCarouselProps) => {
|
||||||
const { ref, width } = useElementSize();
|
|
||||||
const swiperRef = useRef<SwiperCore | any>(null);
|
const swiperRef = useRef<SwiperCore | any>(null);
|
||||||
const playButtonBehavior = usePlayButtonBehavior();
|
const playButtonBehavior = usePlayButtonBehavior();
|
||||||
const handlePlayQueueAdd = usePlayQueueAdd();
|
const handlePlayQueueAdd = usePlayQueueAdd();
|
||||||
|
|
||||||
const slidesPerView = width > 1500 ? 9 : width > 1200 ? 6 : width > 768 ? 5 : width > 600 ? 3 : 2;
|
|
||||||
|
|
||||||
const [pagination, setPagination] = useState({
|
const [pagination, setPagination] = useState({
|
||||||
hasNextPage: (data?.length || 0) > Math.round(slidesPerView),
|
hasNextPage: (data?.length || 0) > Math.round(3),
|
||||||
hasPreviousPage: false,
|
hasPreviousPage: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -139,37 +141,35 @@ export const SwiperGridCarousel = ({
|
||||||
[createFavoriteMutation, deleteFavoriteMutation],
|
[createFavoriteMutation, deleteFavoriteMutation],
|
||||||
);
|
);
|
||||||
|
|
||||||
const slides = data
|
const slides = useMemo(() => {
|
||||||
? data.map((el) => (
|
if (!data) return [];
|
||||||
<PosterCard
|
|
||||||
controls={{
|
return data.map((el) => (
|
||||||
cardRows,
|
<PosterCard
|
||||||
handleFavorite,
|
controls={{
|
||||||
handlePlayQueueAdd,
|
cardRows,
|
||||||
itemType,
|
handleFavorite,
|
||||||
playButtonBehavior,
|
handlePlayQueueAdd,
|
||||||
route,
|
itemType,
|
||||||
}}
|
playButtonBehavior,
|
||||||
data={el}
|
route,
|
||||||
isLoading={isLoading}
|
}}
|
||||||
uniqueId={uniqueId}
|
data={el}
|
||||||
/>
|
isLoading={isLoading}
|
||||||
))
|
uniqueId={uniqueId}
|
||||||
: Array.from(Array(10).keys()).map((el) => (
|
/>
|
||||||
<PosterCard
|
));
|
||||||
controls={{
|
}, [
|
||||||
cardRows,
|
cardRows,
|
||||||
handleFavorite,
|
data,
|
||||||
handlePlayQueueAdd,
|
handleFavorite,
|
||||||
itemType,
|
handlePlayQueueAdd,
|
||||||
playButtonBehavior,
|
isLoading,
|
||||||
route,
|
itemType,
|
||||||
}}
|
playButtonBehavior,
|
||||||
data={el}
|
route,
|
||||||
isLoading={isLoading}
|
uniqueId,
|
||||||
uniqueId={uniqueId}
|
]);
|
||||||
/>
|
|
||||||
));
|
|
||||||
|
|
||||||
const handleNext = useCallback(() => {
|
const handleNext = useCallback(() => {
|
||||||
const activeIndex = swiperRef?.current?.activeIndex || 0;
|
const activeIndex = swiperRef?.current?.activeIndex || 0;
|
||||||
|
@ -183,109 +183,91 @@ export const SwiperGridCarousel = ({
|
||||||
swiperRef?.current?.slideTo(activeIndex - slidesPerView);
|
swiperRef?.current?.slideTo(activeIndex - slidesPerView);
|
||||||
}, [swiperProps?.slidesPerView]);
|
}, [swiperProps?.slidesPerView]);
|
||||||
|
|
||||||
const handleOnSlideChange = useCallback(
|
const handleOnSlideChange = useCallback((e: SwiperCore) => {
|
||||||
(e: SwiperCore) => {
|
const { slides, isEnd, isBeginning, params } = e;
|
||||||
const { slides, isEnd, isBeginning } = e;
|
if (isEnd || isBeginning) return;
|
||||||
if (isEnd || isBeginning) return;
|
|
||||||
|
|
||||||
setPagination({
|
setPagination({
|
||||||
hasNextPage: slidesPerView < slides.length,
|
hasNextPage: (params?.slidesPerView || 3) < slides.length,
|
||||||
hasPreviousPage: slidesPerView < slides.length,
|
hasPreviousPage: (params?.slidesPerView || 3) < slides.length,
|
||||||
});
|
});
|
||||||
},
|
}, []);
|
||||||
[slidesPerView],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleOnZoomChange = useCallback(
|
const handleOnZoomChange = useCallback((e: SwiperCore) => {
|
||||||
(e: SwiperCore) => {
|
const { slides, isEnd, isBeginning, params } = e;
|
||||||
const { slides, isEnd, isBeginning } = e;
|
if (isEnd || isBeginning) return;
|
||||||
if (isEnd || isBeginning) return;
|
|
||||||
|
|
||||||
setPagination({
|
setPagination({
|
||||||
hasNextPage: slidesPerView < slides.length,
|
hasNextPage: (params.slidesPerView || 3) < slides.length,
|
||||||
hasPreviousPage: slidesPerView < slides.length,
|
hasPreviousPage: (params.slidesPerView || 3) < slides.length,
|
||||||
});
|
});
|
||||||
},
|
}, []);
|
||||||
[slidesPerView],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleOnReachEnd = useCallback(
|
const handleOnReachEnd = useCallback((e: SwiperCore) => {
|
||||||
(e: SwiperCore) => {
|
const { slides, params } = e;
|
||||||
const { slides } = e;
|
|
||||||
|
|
||||||
setPagination({
|
setPagination({
|
||||||
hasNextPage: false,
|
hasNextPage: false,
|
||||||
hasPreviousPage: slidesPerView < slides.length,
|
hasPreviousPage: (params.slidesPerView || 3) < slides.length,
|
||||||
});
|
});
|
||||||
},
|
}, []);
|
||||||
[slidesPerView],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleOnReachBeginning = useCallback(
|
const handleOnReachBeginning = useCallback((e: SwiperCore) => {
|
||||||
(e: SwiperCore) => {
|
const { slides, params } = e;
|
||||||
const { slides } = e;
|
|
||||||
|
|
||||||
setPagination({
|
setPagination({
|
||||||
hasNextPage: slidesPerView < slides.length,
|
hasNextPage: (params.slidesPerView || 3) < slides.length,
|
||||||
hasPreviousPage: false,
|
hasPreviousPage: false,
|
||||||
});
|
});
|
||||||
},
|
}, []);
|
||||||
[slidesPerView],
|
|
||||||
);
|
const handleOnResize = throttle((e: SwiperCore) => {
|
||||||
|
const { width } = e;
|
||||||
|
const slidesPerView = getSlidesPerView(width);
|
||||||
|
e.params.slidesPerView = slidesPerView;
|
||||||
|
}, 200);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AnimatePresence
|
<CarouselContainer
|
||||||
initial
|
className="grid-carousel"
|
||||||
mode="sync"
|
spacing="md"
|
||||||
>
|
>
|
||||||
<Box
|
{title ? (
|
||||||
ref={ref}
|
<Title
|
||||||
className="grid-carousel"
|
{...title}
|
||||||
|
handleNext={handleNext}
|
||||||
|
handlePrev={handlePrev}
|
||||||
|
pagination={pagination}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
<Swiper
|
||||||
|
ref={swiperRef}
|
||||||
|
resizeObserver
|
||||||
|
modules={[Virtual]}
|
||||||
|
slidesPerView={4}
|
||||||
|
spaceBetween={20}
|
||||||
|
style={{ height: '100%', width: '100%' }}
|
||||||
|
onBeforeInit={(swiper) => {
|
||||||
|
swiperRef.current = swiper;
|
||||||
|
}}
|
||||||
|
onReachBeginning={handleOnReachBeginning}
|
||||||
|
onReachEnd={handleOnReachEnd}
|
||||||
|
onResize={handleOnResize}
|
||||||
|
onSlideChange={handleOnSlideChange}
|
||||||
|
onZoomChange={handleOnZoomChange}
|
||||||
|
{...swiperProps}
|
||||||
>
|
>
|
||||||
{width ? (
|
{slides.map((slideContent, index) => {
|
||||||
<MotionStack
|
return (
|
||||||
animate="show"
|
<SwiperSlide
|
||||||
initial="hidden"
|
key={`${uniqueId}-${slideContent?.props?.data?.id}-${index}`}
|
||||||
spacing="md"
|
virtualIndex={index}
|
||||||
variants={variants}
|
|
||||||
>
|
|
||||||
{title && (
|
|
||||||
<Title
|
|
||||||
{...title}
|
|
||||||
handleNext={handleNext}
|
|
||||||
handlePrev={handlePrev}
|
|
||||||
pagination={pagination}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Swiper
|
|
||||||
ref={swiperRef}
|
|
||||||
modules={[Virtual]}
|
|
||||||
slidesPerView={swiperProps?.slidesPerView || slidesPerView || 5}
|
|
||||||
spaceBetween={20}
|
|
||||||
style={{ height: '100%', width: '100%' }}
|
|
||||||
onBeforeInit={(swiper) => {
|
|
||||||
swiperRef.current = swiper;
|
|
||||||
}}
|
|
||||||
onReachBeginning={handleOnReachBeginning}
|
|
||||||
onReachEnd={handleOnReachEnd}
|
|
||||||
onSlideChange={handleOnSlideChange}
|
|
||||||
onZoomChange={handleOnZoomChange}
|
|
||||||
{...swiperProps}
|
|
||||||
>
|
>
|
||||||
{slides.map((slideContent, index) => {
|
{slideContent}
|
||||||
return (
|
</SwiperSlide>
|
||||||
<SwiperSlide
|
);
|
||||||
key={`${uniqueId}-${slideContent?.props?.data?.id}-${index}`}
|
})}
|
||||||
virtualIndex={index}
|
</Swiper>
|
||||||
>
|
</CarouselContainer>
|
||||||
{slideContent}
|
|
||||||
</SwiperSlide>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Swiper>
|
|
||||||
</MotionStack>
|
|
||||||
) : null}
|
|
||||||
</Box>
|
|
||||||
</AnimatePresence>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
Reference in a new issue