mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-17 14:10:14 +02:00
adjust feature carousel design
This commit is contained in:
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user