add item card skeleton

This commit is contained in:
jeffvli
2025-09-26 10:44:03 -07:00
parent 126ab38475
commit e4574b0260
2 changed files with 124 additions and 57 deletions
@@ -67,7 +67,6 @@
max-width: 100%; max-width: 100%;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
line-height: 1.6;
white-space: nowrap; white-space: nowrap;
a { a {
+124 -56
View File
@@ -8,6 +8,7 @@ import styles from './item-card.module.css';
import { AppRoute } from '/@/renderer/router/routes'; import { AppRoute } from '/@/renderer/router/routes';
import { Image } from '/@/shared/components/image/image'; import { Image } from '/@/shared/components/image/image';
import { Separator } from '/@/shared/components/separator/separator'; import { Separator } from '/@/shared/components/separator/separator';
import { Skeleton } from '/@/shared/components/skeleton/skeleton';
import { Text } from '/@/shared/components/text/text'; import { Text } from '/@/shared/components/text/text';
import { import {
Album, Album,
@@ -31,8 +32,9 @@ type DataRow = {
}; };
interface ItemCardProps { interface ItemCardProps {
data: Album | AlbumArtist | Artist | Playlist | Song; data: Album | AlbumArtist | Artist | Playlist | Song | undefined;
isRound?: boolean; isRound?: boolean;
itemType: LibraryItem;
onClick?: () => void; onClick?: () => void;
onItemExpand?: () => void; onItemExpand?: () => void;
onItemSelect?: () => void; onItemSelect?: () => void;
@@ -43,17 +45,18 @@ interface ItemCardProps {
export const ItemCard = ({ export const ItemCard = ({
data, data,
isRound, isRound,
itemType,
onClick, onClick,
onItemExpand, onItemExpand,
onItemSelect, onItemSelect,
type = 'poster', type = 'poster',
withControls, withControls,
}: ItemCardProps) => { }: ItemCardProps) => {
const imageUrl = getImageUrl(data);
const rows = getDataRows(data);
const [showControls, setShowControls] = useState(false); const [showControls, setShowControls] = useState(false);
const imageUrl = getImageUrl(data);
const rows = getDataRows(itemType);
switch (type) { switch (type) {
case 'compact': case 'compact':
return ( return (
@@ -61,6 +64,7 @@ export const ItemCard = ({
data={data} data={data}
imageUrl={imageUrl} imageUrl={imageUrl}
isRound={isRound} isRound={isRound}
itemType={itemType}
onClick={onClick} onClick={onClick}
onItemExpand={onItemExpand} onItemExpand={onItemExpand}
onItemSelect={onItemSelect} onItemSelect={onItemSelect}
@@ -76,6 +80,7 @@ export const ItemCard = ({
data={data} data={data}
imageUrl={imageUrl} imageUrl={imageUrl}
isRound={isRound} isRound={isRound}
itemType={itemType}
onClick={onClick} onClick={onClick}
onItemExpand={onItemExpand} onItemExpand={onItemExpand}
onItemSelect={onItemSelect} onItemSelect={onItemSelect}
@@ -92,6 +97,7 @@ export const ItemCard = ({
data={data} data={data}
imageUrl={imageUrl} imageUrl={imageUrl}
isRound={isRound} isRound={isRound}
itemType={itemType}
onClick={onClick} onClick={onClick}
onItemExpand={onItemExpand} onItemExpand={onItemExpand}
onItemSelect={onItemSelect} onItemSelect={onItemSelect}
@@ -123,24 +129,41 @@ const CompactItemCard = ({
showControls, showControls,
withControls, withControls,
}: ItemCardDerivativeProps) => { }: ItemCardDerivativeProps) => {
if (data) {
return (
<div className={clsx(styles.container, styles.compact)}>
<div
className={clsx(styles.imageContainer, { [styles.isRound]: isRound })}
onClick={onClick}
onMouseEnter={() => withControls && setShowControls(true)}
onMouseLeave={() => withControls && setShowControls(false)}
>
<Image
className={clsx(styles.image, { [styles.isRound]: isRound })}
src={imageUrl}
/>
<AnimatePresence>
{withControls && showControls && <ItemCardControls type="compact" />}
</AnimatePresence>
<div className={clsx(styles.detailContainer, styles.compact)}>
{rows.map((row) => (
<ItemCardRow data={data!} key={row.id} row={row} type="compact" />
))}
</div>
</div>
</div>
);
}
return ( return (
<div className={clsx(styles.container, styles.compact)}> <div className={clsx(styles.container, styles.compact)}>
<div <div className={clsx(styles.imageContainer, { [styles.isRound]: isRound })}>
className={clsx(styles.imageContainer, { [styles.isRound]: isRound })} <Skeleton className={styles.image} enableAnimation={false} />
onClick={onClick}
onMouseEnter={() => withControls && setShowControls(true)}
onMouseLeave={() => withControls && setShowControls(false)}
>
<Image
className={clsx(styles.image, { [styles.isRound]: isRound })}
src={imageUrl}
/>
<AnimatePresence>
{withControls && showControls && <ItemCardControls type="compact" />}
</AnimatePresence>
<div className={clsx(styles.detailContainer, styles.compact)}> <div className={clsx(styles.detailContainer, styles.compact)}>
{rows.map((row) => ( {rows.map((row) => (
<ItemCardRow data={data} key={row.id} row={row} type="compact" /> <div className={styles.row} key={row.id}>
&nbsp;
</div>
))} ))}
</div> </div>
</div> </div>
@@ -160,28 +183,43 @@ const DefaultItemCard = ({
showControls, showControls,
withControls, withControls,
}: ItemCardDerivativeProps) => { }: ItemCardDerivativeProps) => {
if (data) {
return (
<div className={clsx(styles.container)}>
<div
className={clsx(styles.imageContainer, { [styles.isRound]: isRound })}
onClick={onClick}
onDoubleClick={onItemExpand}
onMouseEnter={() => withControls && setShowControls(true)}
onMouseLeave={() => withControls && setShowControls(false)}
>
<Image
className={clsx(styles.image, { [styles.isRound]: isRound })}
src={imageUrl}
/>
<AnimatePresence>
{withControls && showControls && <ItemCardControls type="default" />}
</AnimatePresence>
</div>
<div className={styles.detailContainer}>
{rows.map((row) => (
<ItemCardRow data={data!} key={row.id} row={row} type="default" />
))}
</div>
</div>
);
}
return ( return (
<div className={clsx(styles.container)}> <div className={clsx(styles.container)}>
<div <div className={clsx(styles.imageContainer, { [styles.isRound]: isRound })}>
className={clsx(styles.imageContainer, { [styles.isRound]: isRound })} <Skeleton className={styles.image} enableAnimation={false} />
onClick={onClick}
onDoubleClick={onItemExpand}
onMouseEnter={() => withControls && setShowControls(true)}
onMouseLeave={() => withControls && setShowControls(false)}
>
<Image
className={clsx(styles.image, { [styles.isRound]: isRound })}
src={imageUrl}
/>
<AnimatePresence>
{withControls && showControls && <ItemCardControls type="default" />}
</AnimatePresence>
</div> </div>
<div className={styles.detailContainer}> <div className={styles.detailContainer}>
{rows.map((row) => ( {rows.map((row) => (
<Fragment key={row.id}> <div className={styles.row} key={row.id}>
<ItemCardRow data={data} row={row} type="default" /> &nbsp;
</Fragment> </div>
))} ))}
</div> </div>
</div> </div>
@@ -200,35 +238,48 @@ const PosterItemCard = ({
showControls, showControls,
withControls, withControls,
}: ItemCardDerivativeProps) => { }: ItemCardDerivativeProps) => {
if (data) {
return (
<div className={clsx(styles.container, styles.poster)}>
<div
className={clsx(styles.imageContainer, { [styles.isRound]: isRound })}
onClick={onClick}
onMouseEnter={() => withControls && setShowControls(true)}
onMouseLeave={() => withControls && setShowControls(false)}
>
<Image
className={clsx(styles.image, { [styles.isRound]: isRound })}
src={imageUrl}
/>
<AnimatePresence>
{withControls && showControls && <ItemCardControls type="poster" />}
</AnimatePresence>
</div>
<div className={styles.detailContainer}>
{rows.map((row) => (
<ItemCardRow data={data!} key={row.id} row={row} type="poster" />
))}
</div>
</div>
);
}
return ( return (
<div className={clsx(styles.container, styles.poster)}> <div className={clsx(styles.container, styles.poster)}>
<div <div className={clsx(styles.imageContainer, { [styles.isRound]: isRound })}>
className={clsx(styles.imageContainer, { [styles.isRound]: isRound })} <Skeleton className={clsx(styles.image, { [styles.isRound]: isRound })} />
onClick={onClick}
onMouseEnter={() => withControls && setShowControls(true)}
onMouseLeave={() => withControls && setShowControls(false)}
>
<Image
className={clsx(styles.image, { [styles.isRound]: isRound })}
src={imageUrl}
/>
<AnimatePresence>
{withControls && showControls && <ItemCardControls type="poster" />}
</AnimatePresence>
</div> </div>
<div className={styles.detailContainer}> <div className={styles.detailContainer}>
{rows.map((row) => ( {rows.map((row) => (
<Fragment key={row.id}> <ItemCardRow data={undefined} key={row.id} row={row} type="poster" />
<ItemCardRow data={data} row={row} type="poster" />
</Fragment>
))} ))}
</div> </div>
</div> </div>
); );
}; };
const getDataRows = (data: Album | AlbumArtist | Artist | Playlist | Song): DataRow[] => { const getDataRows = (itemType: LibraryItem): DataRow[] => {
switch (data.itemType) { switch (itemType) {
case LibraryItem.ALBUM: case LibraryItem.ALBUM:
return [ return [
{ {
@@ -274,11 +325,13 @@ const getDataRows = (data: Album | AlbumArtist | Artist | Playlist | Song): Data
return [{ format: (data) => (data as Playlist).name, id: 'name' }]; return [{ format: (data) => (data as Playlist).name, id: 'name' }];
case LibraryItem.SONG: case LibraryItem.SONG:
return [{ format: (data) => (data as Song).name, id: 'name' }]; return [{ format: (data) => (data as Song).name, id: 'name' }];
default:
return [];
} }
}; };
const getImageUrl = (data: Album | AlbumArtist | Artist | Playlist | Song) => { const getImageUrl = (data: Album | AlbumArtist | Artist | Playlist | Song | undefined) => {
if ('imageUrl' in data) { if (data && 'imageUrl' in data) {
return data.imageUrl || undefined; return data.imageUrl || undefined;
} }
@@ -290,10 +343,25 @@ const ItemCardRow = ({
row, row,
type, type,
}: { }: {
data: Album | AlbumArtist | Artist | Playlist | Song; data: Album | AlbumArtist | Artist | Playlist | Song | undefined;
row: DataRow; row: DataRow;
type?: 'compact' | 'default' | 'poster'; type?: 'compact' | 'default' | 'poster';
}) => { }) => {
if (!data) {
return (
<div
className={clsx(styles.row, {
[styles.compact]: type === 'compact',
[styles.default]: type === 'default',
[styles.muted]: row.isMuted,
[styles.poster]: type === 'poster',
})}
>
&nbsp;
</div>
);
}
return ( return (
<Text <Text
className={clsx(styles.row, { className={clsx(styles.row, {