adjust feature carousel design

This commit is contained in:
jeffvli
2025-11-29 04:21:43 -08:00
parent 61bfca90d1
commit a708162b15
4 changed files with 103 additions and 32 deletions
@@ -23,7 +23,7 @@
width: 100%;
min-height: 440px;
overflow: hidden;
border-radius: var(--theme-radius-md);
border-radius: var(--theme-radius-lg);
isolation: isolate;
}
@@ -56,16 +56,17 @@
.title-section {
display: flex;
flex-shrink: 0;
align-items: center;
align-items: flex-start;
justify-content: center;
width: 100%;
height: 60px;
min-height: 60px;
max-height: 60px;
text-align: center;
text-align: left;
}
.image-section {
position: relative;
display: flex;
flex-shrink: 0;
align-items: center;
@@ -76,6 +77,22 @@
max-height: 250px;
}
.play-button-overlay {
position: absolute;
top: 50%;
left: 50%;
z-index: 20;
pointer-events: none;
opacity: 0;
transform: translate(-50%, -50%);
transition: opacity 0.3s ease;
}
.image-section:hover .play-button-overlay {
pointer-events: auto;
opacity: 1;
}
.metadata-section {
display: flex;
flex-shrink: 0;
@@ -102,17 +119,44 @@
transform: scale(0.98);
}
.album-image {
.album-image-container {
position: relative;
width: 100%;
max-width: 180px;
height: auto;
overflow: hidden;
border-radius: var(--theme-radius-lg);
box-shadow: 0 8px 24px rgb(0 0 0 / 60%);
transition: box-shadow 0.3s ease;
filter: drop-shadow(0 10px 30px rgb(0 0 0 / 50%)) drop-shadow(0 4px 12px rgb(0 0 0 / 40%));
transition: filter 0.3s ease;
}
.image-link:hover .album-image {
box-shadow: 0 12px 32px rgb(0 0 0 / 40%);
.album-image-container::before {
position: absolute;
top: 0;
left: 0;
z-index: 10;
width: 100%;
height: 100%;
pointer-events: none;
content: '';
background-color: rgb(0 0 0 / 0%);
border-radius: var(--theme-radius-lg);
transition: background-color 0.3s ease;
}
.image-section:hover .album-image-container::before {
background-color: rgb(0 0 0 / 40%);
}
.album-image {
width: 100%;
height: auto;
object-fit: cover;
border-radius: var(--theme-radius-lg);
}
.carousel-item:hover .album-image-container,
.carousel-link:hover .album-image-container {
filter: drop-shadow(0 16px 40px rgb(0 0 0 / 60%)) drop-shadow(0 6px 16px rgb(0 0 0 / 50%));
}
.artist-link {
@@ -195,7 +239,7 @@
max-height: 80px;
}
.album-image {
.album-image-container {
max-width: 160px;
}
}
@@ -231,7 +275,7 @@
max-height: 110px;
}
.album-image {
.album-image-container {
max-width: 200px;
}
}
@@ -263,7 +307,7 @@
max-height: 120px;
}
.album-image {
.album-image-container {
max-width: 220px;
}
}
@@ -295,7 +339,7 @@
max-height: 130px;
}
.album-image {
.album-image-container {
max-width: 240px;
}
}
@@ -6,18 +6,21 @@ import { generatePath, Link } from 'react-router';
import styles from './feature-carousel.module.css';
import { ItemCard } from '/@/renderer/components/item-card/item-card';
import { useDefaultItemListControls } from '/@/renderer/components/item-list/helpers/item-list-controls';
import { usePlayer } from '/@/renderer/features/player/context/player-context';
import { BackgroundOverlay } from '/@/renderer/features/shared/components/library-background-overlay';
import { PlayButtonGroup } from '/@/renderer/features/shared/components/play-button-group';
import { useContainerQuery, useFastAverageColor } from '/@/renderer/hooks';
import { AppRoute } from '/@/renderer/router/routes';
import { useCurrentServer } from '/@/renderer/store';
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
import { Badge } from '/@/shared/components/badge/badge';
import { Group } from '/@/shared/components/group/group';
import { Image } from '/@/shared/components/image/image';
import { Stack } from '/@/shared/components/stack/stack';
import { TextTitle } from '/@/shared/components/text-title/text-title';
import { Text } from '/@/shared/components/text/text';
import { Album, LibraryItem } from '/@/shared/types/domain-types';
import { Play } from '/@/shared/types/types';
const containerVariants = {
animate: (custom: { isNext: boolean }) => ({
@@ -98,11 +101,17 @@ const CarouselItem = ({ album }: CarouselItemProps) => {
srcLoaded: true,
});
const controls = useDefaultItemListControls();
const server = useCurrentServer();
const { addToQueueByFetch } = usePlayer();
const handlePlay = (type: Play) => {
if (!server?.id) return;
addToQueueByFetch(server.id, [album.id], LibraryItem.ALBUM, type);
};
return (
<div className={styles.carouselItem}>
<BackgroundOverlay backgroundColor={backgroundColor} opacity={1} />
<BackgroundOverlay backgroundColor={backgroundColor} opacity={0.7} />
<Link
className={styles.carouselLink}
state={{ item: album }}
@@ -112,21 +121,20 @@ const CarouselItem = ({ album }: CarouselItemProps) => {
>
<div className={styles.content}>
<div className={styles.titleSection}>
<TextTitle className={styles.title} fw={700} lineClamp={2} order={3}>
<TextTitle className={styles.title} fw={800} lineClamp={2} order={3}>
{album.name}
</TextTitle>
</div>
<div className={styles.imageSection}>
<ItemCard
controls={controls}
data={album}
enableNavigation={false}
itemType={LibraryItem.ALBUM}
rows={[]}
type="poster"
withControls
<Image
className={styles.albumImage}
containerClassName={styles.albumImageContainer}
src={album.imageUrl || undefined}
/>
<div className={styles.playButtonOverlay}>
<PlayButtonGroup onPlay={handlePlay} />
</div>
</div>
<div className={styles.metadataSection}>
@@ -136,18 +144,26 @@ const CarouselItem = ({ album }: CarouselItemProps) => {
className={styles.artist}
fw={600}
key={`artist-${artist.id}`}
size="md"
size="xl"
>
{artist.name}
</Text>
))}
<Group gap="sm" justify="center" wrap="wrap">
{album.genres?.slice(0, 2).map((genre) => (
<Badge key={`genre-${genre.id}`} size="sm">
<Badge
key={`genre-${genre.id}`}
size="lg"
variant="transparent"
>
{genre.name}
</Badge>
))}
{album.releaseYear && <Badge size="sm">{album.releaseYear}</Badge>}
{album.releaseYear && (
<Badge size="lg" variant="transparent">
{album.releaseYear}
</Badge>
)}
</Group>
</Stack>
</div>
@@ -0,0 +1,10 @@
.play-button-group {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
gap: var(--theme-spacing-sm);
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
}
@@ -1,6 +1,7 @@
import styles from './play-button-group.module.css';
import i18n from '/@/i18n/i18n';
import { PlayButton } from '/@/renderer/features/shared/components/play-button';
import { Group } from '/@/shared/components/group/group';
import { AppIconSelection } from '/@/shared/components/icon/icon';
import { Tooltip } from '/@/shared/components/tooltip/tooltip';
import { Play } from '/@/shared/types/types';
@@ -39,7 +40,7 @@ interface PlayButtonGroupProps {
export const PlayButtonGroup = ({ loading, onPlay }: PlayButtonGroupProps) => {
return (
<Group align="center" gap="md" justify="center">
<div className={styles.playButtonGroup}>
{playButtons.map((button) => (
<Tooltip key={button.type} label={button.label} openDelay={2000}>
<PlayButton
@@ -52,6 +53,6 @@ export const PlayButtonGroup = ({ loading, onPlay }: PlayButtonGroupProps) => {
/>
</Tooltip>
))}
</Group>
</div>
);
};