Migrate to Mantine v8 and Design Changes (#961)

* mantine v8 migration

* various design changes and improvements
This commit is contained in:
Jeff
2025-06-24 00:04:36 -07:00
committed by GitHub
parent bea55d48a8
commit c1330d92b2
473 changed files with 12469 additions and 11607 deletions
@@ -1,13 +1,15 @@
import { Center, Stack } from '@mantine/core';
import { RiAlbumFill, RiPlayListFill, RiUserVoiceFill } from 'react-icons/ri';
import clsx from 'clsx';
import { useState } from 'react';
import { generatePath, useNavigate } from 'react-router-dom';
import { SimpleImg } from 'react-simple-img';
import { ListChildComponentProps } from 'react-window';
import styled from 'styled-components';
import { CardRows } from '/@/renderer/components/card';
import { Skeleton } from '/@/renderer/components/skeleton';
import styles from './poster-card.module.css';
import { CardRows } from '/@/renderer/components/card/card-rows';
import { GridCardControls } from '/@/renderer/components/virtual-grid/grid-card/grid-card-controls';
import { Image } from '/@/shared/components/image/image';
import { Skeleton } from '/@/shared/components/skeleton/skeleton';
import { Stack } from '/@/shared/components/stack/stack';
import {
Album,
AlbumArtist,
@@ -39,93 +41,6 @@ interface BaseGridCardProps {
listChildProps: Omit<ListChildComponentProps, 'data' | 'style'>;
}
const PosterCardContainer = styled.div<{ $isHidden?: boolean; $itemGap: number }>`
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
margin: ${({ $itemGap }) => $itemGap}px;
overflow: hidden;
pointer-events: auto;
opacity: ${({ $isHidden }) => ($isHidden ? 0 : 1)};
.card-controls {
opacity: 0;
}
`;
const LinkContainer = styled.div`
cursor: pointer;
`;
const ImageContainer = styled.div<{ $isFavorite?: boolean }>`
position: relative;
display: flex;
align-items: center;
aspect-ratio: 1/1;
overflow: hidden;
background: var(--card-default-bg);
border-radius: var(--card-poster-radius);
&::before {
position: absolute;
top: 0;
left: 0;
z-index: 1;
width: 100%;
height: 100%;
content: '';
user-select: none;
background: linear-gradient(0deg, rgb(0 0 0 / 100%) 35%, rgb(0 0 0 / 0%) 100%);
opacity: 0;
transition: all 0.2s ease-in-out;
}
${(props) =>
props.$isFavorite &&
`
&::after {
position: absolute;
top: -50px;
left: -50px;
width: 80px;
height: 80px;
background-color: var(--primary-color);
box-shadow: 0 0 10px 8px rgba(0, 0, 0, 80%);
transform: rotate(-45deg);
content: '';
pointer-events: none;
}
`}
&:hover {
&::before {
opacity: 0.5;
}
}
&:hover .card-controls {
opacity: 1;
}
`;
const Image = styled(SimpleImg)`
width: 100%;
max-width: 100%;
height: 100% !important;
max-height: 100%;
border: 0;
img {
height: 100%;
object-fit: var(--image-fit);
}
`;
const DetailContainer = styled.div`
margin-top: 0.5rem;
`;
export const PosterCard = ({
columnIndex,
controls,
@@ -135,6 +50,8 @@ export const PosterCard = ({
}: BaseGridCardProps) => {
const navigate = useNavigate();
const [isHovered, setIsHovered] = useState(false);
if (data) {
const path = generatePath(
controls.route.route as string,
@@ -146,97 +63,68 @@ export const PosterCard = ({
}, {}),
);
let Placeholder = RiAlbumFill;
switch (controls.itemType) {
case LibraryItem.ALBUM:
Placeholder = RiAlbumFill;
break;
case LibraryItem.ALBUM_ARTIST:
Placeholder = RiUserVoiceFill;
break;
case LibraryItem.ARTIST:
Placeholder = RiUserVoiceFill;
break;
case LibraryItem.PLAYLIST:
Placeholder = RiPlayListFill;
break;
default:
Placeholder = RiAlbumFill;
break;
}
return (
<PosterCardContainer
$itemGap={controls.itemGap}
<div
className={styles.container}
key={`card-${columnIndex}-${listChildProps.index}`}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
style={{
margin: controls.itemGap,
}}
>
<LinkContainer onClick={() => navigate(path)}>
<ImageContainer $isFavorite={data?.userFavorite}>
{data?.imageUrl ? (
<Image
importance="auto"
placeholder={data?.imagePlaceholderUrl || 'var(--card-default-bg)'}
src={data?.imageUrl}
/>
) : (
<Center
sx={{
background: 'var(--placeholder-bg)',
borderRadius: 'var(--card-default-radius)',
height: '100%',
width: '100%',
}}
>
<Placeholder
color="var(--placeholder-fg)"
size={35}
/>
</Center>
)}
<div
className={styles.linkContainer}
onClick={() => navigate(path)}
>
<div
className={`${styles.imageContainer} ${data?.userFavorite ? styles.isFavorite : ''}`}
>
<Image
className={styles.image}
src={data?.imageUrl}
/>
<GridCardControls
handleFavorite={controls.handleFavorite}
handlePlayQueueAdd={controls.handlePlayQueueAdd}
isHovered={isHovered}
itemData={data}
itemType={controls.itemType}
resetInfiniteLoaderCache={controls.resetInfiniteLoaderCache}
/>
</ImageContainer>
</LinkContainer>
<DetailContainer>
</div>
</div>
<div className={styles.detailContainer}>
<CardRows
data={data}
rows={controls.cardRows}
/>
</DetailContainer>
</PosterCardContainer>
</div>
</div>
);
}
return (
<PosterCardContainer
$isHidden={isHidden}
$itemGap={controls.itemGap}
<div
className={clsx(styles.container, isHidden && styles.hidden)}
key={`card-${columnIndex}-${listChildProps.index}`}
style={{
margin: controls.itemGap,
}}
>
<Skeleton
radius="sm"
visible
>
<ImageContainer />
</Skeleton>
<DetailContainer>
<Stack spacing="sm">
<div className={styles.imageContainer}>
<Skeleton className={styles.image} />
</div>
<div className={styles.detailContainer}>
<Stack gap="xs">
{(controls?.cardRows || []).map((row, index) => (
<Skeleton
height={14}
className={styles.row}
key={`${index}-${columnIndex}-${row.arrayProperty}`}
radius="sm"
visible
/>
))}
</Stack>
</DetailContainer>
</PosterCardContainer>
</div>
</div>
);
};