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