update feature carousel animation

This commit is contained in:
jeffvli
2025-11-22 20:38:25 -08:00
parent 0a25df39ca
commit b24faa1e08
2 changed files with 64 additions and 24 deletions
@@ -1,7 +1,7 @@
import type { MouseEvent } from 'react'; import type { MouseEvent } from 'react';
import { AnimatePresence, motion } from 'motion/react'; import { AnimatePresence, motion } from 'motion/react';
import { useMemo, useState } from 'react'; import { useMemo, useRef, useState } from 'react';
import { generatePath, Link } from 'react-router'; import { generatePath, Link } from 'react-router';
import styles from './feature-carousel.module.css'; import styles from './feature-carousel.module.css';
@@ -19,23 +19,50 @@ import { TextTitle } from '/@/shared/components/text-title/text-title';
import { Text } from '/@/shared/components/text/text'; import { Text } from '/@/shared/components/text/text';
import { Album, LibraryItem } from '/@/shared/types/domain-types'; import { Album, LibraryItem } from '/@/shared/types/domain-types';
const fadeVariants = { const containerVariants = {
center: { animate: (custom: { isNext: boolean }) => ({
transition: {
delayChildren: 0.1,
staggerChildren: 0.3,
staggerDirection: custom.isNext ? 1 : -1,
},
}),
exit: (custom: { isNext: boolean }) => ({
transition: {
staggerChildren: 0.3,
staggerDirection: custom.isNext ? 1 : -1,
},
}),
initial: (custom: { isNext: boolean }) => ({
transition: {
staggerChildren: 0.3,
staggerDirection: custom.isNext ? -1 : 1,
},
}),
};
const itemVariants = {
animate: {
opacity: 1, opacity: 1,
scale: 1,
transition: { transition: {
duration: 0.4, duration: 0.4,
ease: 'easeInOut' as const, ease: 'easeOut' as const,
}, },
}, y: 0,
enter: {
opacity: 0,
}, },
exit: { exit: {
opacity: 0, opacity: 0,
scale: 0.8,
transition: { transition: {
duration: 0.4, duration: 0.3,
ease: 'easeInOut' as const, ease: 'easeIn' as const,
}, },
y: -20,
},
initial: {
opacity: 0,
y: 20,
}, },
}; };
@@ -90,16 +117,11 @@ const CarouselItem = ({ album }: CarouselItemProps) => {
</TextTitle> </TextTitle>
</div> </div>
<div <div className={styles.imageSection}>
className={styles.imageSection}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
}}
>
<ItemCard <ItemCard
controls={controls} controls={controls}
data={album} data={album}
enableNavigation={false}
itemType={LibraryItem.ALBUM} itemType={LibraryItem.ALBUM}
rows={[]} rows={[]}
type="poster" type="poster"
@@ -144,6 +166,7 @@ const CarouselItem = ({ album }: CarouselItemProps) => {
export const FeatureCarousel = ({ data }: FeatureCarouselProps) => { export const FeatureCarousel = ({ data }: FeatureCarouselProps) => {
const [startIndex, setStartIndex] = useState(0); const [startIndex, setStartIndex] = useState(0);
const directionRef = useRef<{ isNext: boolean }>({ isNext: true });
const { const {
is2xl, is2xl,
is3xl, is3xl,
@@ -180,6 +203,7 @@ export const FeatureCarousel = ({ data }: FeatureCarouselProps) => {
e?.preventDefault(); e?.preventDefault();
e?.stopPropagation(); e?.stopPropagation();
if (!data) return; if (!data) return;
directionRef.current = { isNext: true };
setStartIndex((prev) => (prev + itemsPerRow) % data.length); setStartIndex((prev) => (prev + itemsPerRow) % data.length);
}; };
@@ -187,6 +211,7 @@ export const FeatureCarousel = ({ data }: FeatureCarouselProps) => {
e?.preventDefault(); e?.preventDefault();
e?.stopPropagation(); e?.stopPropagation();
if (!data) return; if (!data) return;
directionRef.current = { isNext: false };
setStartIndex((prev) => (prev - itemsPerRow + data.length) % data.length); setStartIndex((prev) => (prev - itemsPerRow + data.length) % data.length);
}; };
@@ -198,16 +223,22 @@ export const FeatureCarousel = ({ data }: FeatureCarouselProps) => {
<div className={styles.carouselContainer} ref={containerRef}> <div className={styles.carouselContainer} ref={containerRef}>
<AnimatePresence initial={false} mode="popLayout"> <AnimatePresence initial={false} mode="popLayout">
<motion.div <motion.div
animate="center" animate="animate"
className={styles.carousel} className={styles.carousel}
custom={directionRef.current}
exit="exit" exit="exit"
initial="enter" initial="initial"
key={`carousel-${startIndex}`} key={`carousel-${startIndex}`}
style={{ '--items-per-row': itemsPerRow } as React.CSSProperties} style={{ '--items-per-row': itemsPerRow } as React.CSSProperties}
variants={fadeVariants} variants={containerVariants}
> >
{visibleItems.map((album) => ( {visibleItems.map((album, index) => (
<CarouselItem album={album} key={`item-${album.id}-${startIndex}`} /> <motion.div
key={`item-${album.id}-${startIndex}-${index}`}
variants={itemVariants}
>
<CarouselItem album={album} />
</motion.div>
))} ))}
</motion.div> </motion.div>
</AnimatePresence> </AnimatePresence>
@@ -41,6 +41,7 @@ export interface ItemCardProps {
data: Album | AlbumArtist | Artist | Playlist | Song | undefined; data: Album | AlbumArtist | Artist | Playlist | Song | undefined;
enableDrag?: boolean; enableDrag?: boolean;
enableExpansion?: boolean; enableExpansion?: boolean;
enableNavigation?: boolean;
internalState?: ItemListStateActions; internalState?: ItemListStateActions;
isRound?: boolean; isRound?: boolean;
itemType: LibraryItem; itemType: LibraryItem;
@@ -54,6 +55,7 @@ export const ItemCard = ({
data, data,
enableDrag, enableDrag,
enableExpansion, enableExpansion,
enableNavigation,
internalState, internalState,
isRound, isRound,
itemType, itemType,
@@ -72,6 +74,7 @@ export const ItemCard = ({
data={data} data={data}
enableDrag={enableDrag} enableDrag={enableDrag}
enableExpansion={enableExpansion} enableExpansion={enableExpansion}
enableNavigation={enableNavigation}
imageUrl={imageUrl} imageUrl={imageUrl}
internalState={internalState} internalState={internalState}
isRound={isRound} isRound={isRound}
@@ -87,6 +90,7 @@ export const ItemCard = ({
data={data} data={data}
enableDrag={enableDrag} enableDrag={enableDrag}
enableExpansion={enableExpansion} enableExpansion={enableExpansion}
enableNavigation={enableNavigation}
imageUrl={imageUrl} imageUrl={imageUrl}
internalState={internalState} internalState={internalState}
isRound={isRound} isRound={isRound}
@@ -103,6 +107,7 @@ export const ItemCard = ({
data={data} data={data}
enableDrag={enableDrag} enableDrag={enableDrag}
enableExpansion={enableExpansion} enableExpansion={enableExpansion}
enableNavigation={enableNavigation}
imageUrl={imageUrl} imageUrl={imageUrl}
internalState={internalState} internalState={internalState}
isRound={isRound} isRound={isRound}
@@ -117,6 +122,7 @@ export const ItemCard = ({
export interface ItemCardDerivativeProps extends Omit<ItemCardProps, 'type'> { export interface ItemCardDerivativeProps extends Omit<ItemCardProps, 'type'> {
controls?: ItemControls; controls?: ItemControls;
enableExpansion?: boolean; enableExpansion?: boolean;
enableNavigation?: boolean;
imageUrl: string | undefined; imageUrl: string | undefined;
internalState?: ItemListStateActions; internalState?: ItemListStateActions;
rows: DataRow[]; rows: DataRow[];
@@ -126,6 +132,7 @@ const CompactItemCard = ({
controls, controls,
data, data,
enableExpansion, enableExpansion,
enableNavigation,
imageUrl, imageUrl,
internalState, internalState,
isRound, isRound,
@@ -270,7 +277,7 @@ const CompactItemCard = ({
[styles.selected]: isSelected, [styles.selected]: isSelected,
})} })}
> >
{navigationPath && !internalState ? ( {enableNavigation && navigationPath && !internalState ? (
<Link <Link
className={imageContainerClassName} className={imageContainerClassName}
draggable={false} draggable={false}
@@ -329,6 +336,7 @@ const DefaultItemCard = ({
controls, controls,
data, data,
enableExpansion, enableExpansion,
enableNavigation,
imageUrl, imageUrl,
internalState, internalState,
isRound, isRound,
@@ -457,7 +465,7 @@ const DefaultItemCard = ({
[styles.selected]: isSelected, [styles.selected]: isSelected,
})} })}
> >
{navigationPath && !internalState ? ( {enableNavigation && navigationPath && !internalState ? (
<Link <Link
className={imageContainerClassName} className={imageContainerClassName}
draggable={false} draggable={false}
@@ -532,6 +540,7 @@ const PosterItemCard = ({
data, data,
enableDrag, enableDrag,
enableExpansion, enableExpansion,
enableNavigation,
imageUrl, imageUrl,
internalState, internalState,
isRound, isRound,
@@ -708,7 +717,7 @@ const PosterItemCard = ({
})} })}
ref={ref} ref={ref}
> >
{navigationPath && !internalState ? ( {enableNavigation && navigationPath && !internalState ? (
<Link <Link
className={imageContainerClassName} className={imageContainerClassName}
draggable={false} draggable={false}