remove item callbacks from list - move to item component

This commit is contained in:
jeffvli
2025-10-07 11:36:15 -07:00
parent 545ea25e43
commit d9e8625b15
11 changed files with 580 additions and 350 deletions
@@ -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;
}
}
}
+38 -51
View File
@@ -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}>