mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-15 04:51:06 +02:00
remove item callbacks from list - move to item component
This commit is contained in:
@@ -138,17 +138,29 @@
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: rgb(255 255 255);
|
||||
stroke: rgb(255 255 255);
|
||||
}
|
||||
}
|
||||
|
||||
.secondary-button.favorite {
|
||||
.user-data {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.rating {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
padding: var(--theme-spacing-md);
|
||||
}
|
||||
|
||||
.secondary-button.options {
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.secondary-button.expand {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,27 @@
|
||||
import clsx from 'clsx';
|
||||
import { motion } from 'motion/react';
|
||||
import { MouseEvent } from 'react';
|
||||
|
||||
import styles from './item-card-controls.module.css';
|
||||
|
||||
import { ItemControls } from '/@/renderer/components/item-list/types';
|
||||
import { animationVariants } from '/@/shared/components/animations/animation-variants';
|
||||
import { AppIcon, Icon } from '/@/shared/components/icon/icon';
|
||||
import { Rating } from '/@/shared/components/rating/rating';
|
||||
import {
|
||||
Album,
|
||||
AlbumArtist,
|
||||
Artist,
|
||||
LibraryItem,
|
||||
Playlist,
|
||||
Song,
|
||||
} from '/@/shared/types/domain-types';
|
||||
import { Play } from '/@/shared/types/types';
|
||||
|
||||
interface ItemCardControlsProps {
|
||||
controls: ItemControls;
|
||||
item: Album | AlbumArtist | Artist | Playlist | Song | undefined;
|
||||
itemType: LibraryItem;
|
||||
type?: 'compact' | 'default' | 'poster';
|
||||
}
|
||||
|
||||
@@ -27,27 +42,79 @@ const containerProps = {
|
||||
animate: 'show',
|
||||
exit: 'hidden',
|
||||
initial: 'hidden',
|
||||
variants: animationVariants.combine(animationVariants.slideInUp, animationVariants.fadeIn),
|
||||
variants: animationVariants.combine(animationVariants.zoomIn, animationVariants.fadeIn),
|
||||
},
|
||||
};
|
||||
|
||||
export const ItemCardControls = ({ type = 'default' }: ItemCardControlsProps) => {
|
||||
export const ItemCardControls = ({
|
||||
controls,
|
||||
item,
|
||||
itemType,
|
||||
type = 'default',
|
||||
}: ItemCardControlsProps) => {
|
||||
return (
|
||||
<motion.div className={clsx(styles.container)} {...containerProps[type]}>
|
||||
<PlayButton />
|
||||
<SecondaryPlayButton className={styles.left} icon="mediaPlayNext" />
|
||||
<SecondaryPlayButton className={styles.right} icon="mediaPlayLast" />
|
||||
<SecondaryButton className={styles.favorite} icon="favorite" />
|
||||
<SecondaryButton className={styles.options} icon="ellipsisHorizontal" />
|
||||
<PlayButton
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
controls.onPlay?.(item, itemType, Play.NOW, e);
|
||||
}}
|
||||
/>
|
||||
<SecondaryPlayButton
|
||||
className={styles.left}
|
||||
icon="mediaPlayNext"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
controls.onPlay?.(item, itemType, Play.NEXT, e);
|
||||
}}
|
||||
/>
|
||||
<SecondaryPlayButton
|
||||
className={styles.right}
|
||||
icon="mediaPlayLast"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
controls.onPlay?.(item, itemType, Play.LAST, e);
|
||||
}}
|
||||
/>
|
||||
<SecondaryButton
|
||||
className={styles.favorite}
|
||||
icon="favorite"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
controls.onFavorite?.(item, itemType, e);
|
||||
}}
|
||||
/>
|
||||
<Rating className={styles.rating} size="xs" />
|
||||
<SecondaryButton
|
||||
className={styles.options}
|
||||
icon="ellipsisHorizontal"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
controls.onMore?.(item, itemType, e);
|
||||
}}
|
||||
/>
|
||||
{controls.onItemExpand && (
|
||||
<SecondaryButton
|
||||
className={styles.expand}
|
||||
icon="arrowDownS"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
controls.onItemExpand?.(item, itemType, e);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
||||
const PlayButton = () => {
|
||||
const PlayButton = ({ onClick }: { onClick?: (e: MouseEvent<HTMLButtonElement>) => void }) => {
|
||||
return (
|
||||
<button
|
||||
className={clsx(styles.playButton, styles.primary)}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onClick?.(e);
|
||||
}}
|
||||
>
|
||||
<Icon icon="mediaPlay" size="lg" />
|
||||
</button>
|
||||
@@ -57,12 +124,20 @@ const PlayButton = () => {
|
||||
const SecondaryPlayButton = ({
|
||||
className,
|
||||
icon,
|
||||
onClick,
|
||||
}: {
|
||||
className?: string;
|
||||
icon: keyof typeof AppIcon;
|
||||
onClick?: (e: MouseEvent<HTMLButtonElement>) => void;
|
||||
}) => {
|
||||
return (
|
||||
<button className={clsx(styles.playButton, styles.secondary, className)}>
|
||||
<button
|
||||
className={clsx(styles.playButton, styles.secondary, className)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onClick?.(e);
|
||||
}}
|
||||
>
|
||||
<Icon icon={icon} size="lg" />
|
||||
</button>
|
||||
);
|
||||
@@ -71,11 +146,18 @@ const SecondaryPlayButton = ({
|
||||
interface SecondaryButtonProps {
|
||||
className?: string;
|
||||
icon: keyof typeof AppIcon;
|
||||
onClick?: (e: MouseEvent<HTMLButtonElement>) => void;
|
||||
}
|
||||
|
||||
const SecondaryButton = ({ className, icon }: SecondaryButtonProps) => {
|
||||
const SecondaryButton = ({ className, icon, onClick }: SecondaryButtonProps) => {
|
||||
return (
|
||||
<button className={clsx(styles.secondaryButton, className)}>
|
||||
<button
|
||||
className={clsx(styles.secondaryButton, className)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onClick?.(e);
|
||||
}}
|
||||
>
|
||||
<Icon icon={icon} size="lg" />
|
||||
</button>
|
||||
);
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
&:hover {
|
||||
&::before {
|
||||
opacity: 0.6;
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,12 @@
|
||||
import clsx from 'clsx';
|
||||
import { AnimatePresence } from 'motion/react';
|
||||
import {
|
||||
Dispatch,
|
||||
Fragment,
|
||||
lazy,
|
||||
memo,
|
||||
MouseEvent,
|
||||
ReactNode,
|
||||
SetStateAction,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { Dispatch, Fragment, memo, ReactNode, SetStateAction, useState } from 'react';
|
||||
import { generatePath, Link } from 'react-router-dom';
|
||||
|
||||
import styles from './item-card.module.css';
|
||||
|
||||
import { ItemCardControls } from '/@/renderer/components/item-card/item-card-controls';
|
||||
import { ItemControls } from '/@/renderer/components/item-list/types';
|
||||
import { AppRoute } from '/@/renderer/router/routes';
|
||||
import { Image } from '/@/shared/components/image/image';
|
||||
import { Separator } from '/@/shared/components/separator/separator';
|
||||
@@ -28,12 +21,6 @@ import {
|
||||
Song,
|
||||
} from '/@/shared/types/domain-types';
|
||||
|
||||
const ItemCardControls = lazy(() =>
|
||||
import('/@/renderer/components/item-card/item-card-controls').then((module) => ({
|
||||
default: module.ItemCardControls,
|
||||
})),
|
||||
);
|
||||
|
||||
type DataRow = {
|
||||
format: (data: Album | AlbumArtist | Artist | Playlist | Song) => ReactNode | string;
|
||||
id: string;
|
||||
@@ -41,27 +28,20 @@ type DataRow = {
|
||||
};
|
||||
|
||||
interface ItemCardProps {
|
||||
controls: ItemControls;
|
||||
data: Album | AlbumArtist | Artist | Playlist | Song | undefined;
|
||||
isRound?: boolean;
|
||||
itemType: LibraryItem;
|
||||
onClick?: (
|
||||
e: MouseEvent<HTMLDivElement>,
|
||||
item: Album | AlbumArtist | Artist | Playlist | Song | undefined,
|
||||
itemType: LibraryItem,
|
||||
) => void;
|
||||
onItemExpand?: () => void;
|
||||
onItemSelect?: () => void;
|
||||
|
||||
type?: 'compact' | 'default' | 'poster';
|
||||
withControls?: boolean;
|
||||
}
|
||||
|
||||
export const ItemCard = ({
|
||||
controls,
|
||||
data,
|
||||
isRound,
|
||||
itemType,
|
||||
onClick,
|
||||
onItemExpand,
|
||||
onItemSelect,
|
||||
type = 'poster',
|
||||
withControls,
|
||||
}: ItemCardProps) => {
|
||||
@@ -74,13 +54,11 @@ export const ItemCard = ({
|
||||
case 'compact':
|
||||
return (
|
||||
<CompactItemCard
|
||||
controls={controls}
|
||||
data={data}
|
||||
imageUrl={imageUrl}
|
||||
isRound={isRound}
|
||||
itemType={itemType}
|
||||
onClick={onClick}
|
||||
onItemExpand={onItemExpand}
|
||||
onItemSelect={onItemSelect}
|
||||
rows={rows}
|
||||
setShowControls={setShowControls}
|
||||
showControls={showControls}
|
||||
@@ -90,13 +68,11 @@ export const ItemCard = ({
|
||||
case 'poster':
|
||||
return (
|
||||
<PosterItemCard
|
||||
controls={controls}
|
||||
data={data}
|
||||
imageUrl={imageUrl}
|
||||
isRound={isRound}
|
||||
itemType={itemType}
|
||||
onClick={onClick}
|
||||
onItemExpand={onItemExpand}
|
||||
onItemSelect={onItemSelect}
|
||||
rows={rows}
|
||||
setShowControls={setShowControls}
|
||||
showControls={showControls}
|
||||
@@ -107,13 +83,11 @@ export const ItemCard = ({
|
||||
default:
|
||||
return (
|
||||
<DefaultItemCard
|
||||
controls={controls}
|
||||
data={data}
|
||||
imageUrl={imageUrl}
|
||||
isRound={isRound}
|
||||
itemType={itemType}
|
||||
onClick={onClick}
|
||||
onItemExpand={onItemExpand}
|
||||
onItemSelect={onItemSelect}
|
||||
rows={rows}
|
||||
setShowControls={setShowControls}
|
||||
showControls={showControls}
|
||||
@@ -124,6 +98,7 @@ export const ItemCard = ({
|
||||
};
|
||||
|
||||
export interface ItemCardDerivativeProps extends Omit<ItemCardProps, 'type'> {
|
||||
controls: ItemControls;
|
||||
imageUrl: string | undefined;
|
||||
rows: DataRow[];
|
||||
setShowControls: Dispatch<SetStateAction<boolean>>;
|
||||
@@ -131,13 +106,11 @@ export interface ItemCardDerivativeProps extends Omit<ItemCardProps, 'type'> {
|
||||
}
|
||||
|
||||
const CompactItemCard = ({
|
||||
controls,
|
||||
data,
|
||||
imageUrl,
|
||||
isRound,
|
||||
itemType,
|
||||
onClick,
|
||||
onItemExpand,
|
||||
onItemSelect,
|
||||
rows,
|
||||
setShowControls,
|
||||
showControls,
|
||||
@@ -148,7 +121,6 @@ const CompactItemCard = ({
|
||||
<div className={clsx(styles.container, styles.compact)}>
|
||||
<div
|
||||
className={clsx(styles.imageContainer, { [styles.isRound]: isRound })}
|
||||
onClick={(e) => onClick?.(e, data, itemType)}
|
||||
onMouseEnter={() => withControls && setShowControls(true)}
|
||||
onMouseLeave={() => withControls && setShowControls(false)}
|
||||
>
|
||||
@@ -157,7 +129,14 @@ const CompactItemCard = ({
|
||||
src={imageUrl}
|
||||
/>
|
||||
<AnimatePresence>
|
||||
{withControls && showControls && <ItemCardControls type="compact" />}
|
||||
{withControls && showControls && (
|
||||
<ItemCardControls
|
||||
controls={controls}
|
||||
item={data}
|
||||
itemType={itemType}
|
||||
type="compact"
|
||||
/>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
<div className={clsx(styles.detailContainer, styles.compact)}>
|
||||
{rows.map((row) => (
|
||||
@@ -186,13 +165,11 @@ const CompactItemCard = ({
|
||||
};
|
||||
|
||||
const DefaultItemCard = ({
|
||||
controls,
|
||||
data,
|
||||
imageUrl,
|
||||
isRound,
|
||||
itemType,
|
||||
onClick,
|
||||
onItemExpand,
|
||||
onItemSelect,
|
||||
rows,
|
||||
setShowControls,
|
||||
showControls,
|
||||
@@ -203,8 +180,6 @@ const DefaultItemCard = ({
|
||||
<div className={clsx(styles.container)}>
|
||||
<div
|
||||
className={clsx(styles.imageContainer, { [styles.isRound]: isRound })}
|
||||
onClick={(e) => onClick?.(e, data, itemType)}
|
||||
onDoubleClick={onItemExpand}
|
||||
onMouseEnter={() => withControls && setShowControls(true)}
|
||||
onMouseLeave={() => withControls && setShowControls(false)}
|
||||
>
|
||||
@@ -213,7 +188,14 @@ const DefaultItemCard = ({
|
||||
src={imageUrl}
|
||||
/>
|
||||
<AnimatePresence>
|
||||
{withControls && showControls && <ItemCardControls type="default" />}
|
||||
{withControls && showControls && (
|
||||
<ItemCardControls
|
||||
controls={controls}
|
||||
item={data}
|
||||
itemType={itemType}
|
||||
type="default"
|
||||
/>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
<div className={styles.detailContainer}>
|
||||
@@ -242,13 +224,11 @@ const DefaultItemCard = ({
|
||||
};
|
||||
|
||||
const PosterItemCard = ({
|
||||
controls,
|
||||
data,
|
||||
imageUrl,
|
||||
isRound,
|
||||
itemType,
|
||||
onClick,
|
||||
onItemExpand,
|
||||
onItemSelect,
|
||||
rows,
|
||||
setShowControls,
|
||||
showControls,
|
||||
@@ -259,7 +239,7 @@ const PosterItemCard = ({
|
||||
<div className={clsx(styles.container, styles.poster)}>
|
||||
<div
|
||||
className={clsx(styles.imageContainer, { [styles.isRound]: isRound })}
|
||||
onClick={(e) => onClick?.(e, data, itemType)}
|
||||
onClick={(e) => controls?.onClick?.(data, itemType, e)}
|
||||
onMouseEnter={() => withControls && setShowControls(true)}
|
||||
onMouseLeave={() => withControls && setShowControls(false)}
|
||||
>
|
||||
@@ -268,7 +248,14 @@ const PosterItemCard = ({
|
||||
src={imageUrl}
|
||||
/>
|
||||
<AnimatePresence>
|
||||
{withControls && showControls && <ItemCardControls type="poster" />}
|
||||
{withControls && showControls && (
|
||||
<ItemCardControls
|
||||
controls={controls}
|
||||
item={data}
|
||||
itemType={itemType}
|
||||
type="poster"
|
||||
/>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
<div className={styles.detailContainer}>
|
||||
|
||||
Reference in New Issue
Block a user