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