Remove container query requirement for carousel sizing

This commit is contained in:
jeffvli 2023-06-02 01:01:50 -07:00
parent 63c5a83911
commit f2690b262f

View file

@ -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>
); );
}; };