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