mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-10 04:30:25 +02:00
update feature carousel animation
This commit is contained in:
@@ -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}
|
||||||
|
|||||||
Reference in New Issue
Block a user