mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-07 12:30:12 +02:00
redesign album detail page
This commit is contained in:
@@ -28,6 +28,7 @@ interface HeaderPlayButtonProps {
|
||||
itemType: LibraryItem;
|
||||
listQuery?: Record<string, any>;
|
||||
songs?: Song[];
|
||||
variant?: 'default' | 'filled';
|
||||
}
|
||||
|
||||
interface TitleProps {
|
||||
@@ -40,6 +41,7 @@ const HeaderPlayButton = ({
|
||||
itemType,
|
||||
listQuery,
|
||||
songs,
|
||||
variant = 'filled',
|
||||
...props
|
||||
}: HeaderPlayButtonProps) => {
|
||||
const serverId = useCurrentServerId();
|
||||
@@ -73,14 +75,19 @@ const HeaderPlayButton = ({
|
||||
|
||||
return (
|
||||
<div className={styles.playButtonContainer}>
|
||||
<PlayButton className={className} onClick={openPlayTypeModal} {...props} />
|
||||
<PlayButton
|
||||
className={className}
|
||||
onClick={openPlayTypeModal}
|
||||
variant={variant}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Title = ({ children }: TitleProps) => {
|
||||
return (
|
||||
<TextTitle fw={700} order={1} overflow="hidden">
|
||||
<TextTitle fw={700} order={2} overflow="hidden">
|
||||
{children}
|
||||
</TextTitle>
|
||||
);
|
||||
|
||||
@@ -22,10 +22,8 @@
|
||||
width: 250px !important;
|
||||
height: 250px;
|
||||
}
|
||||
}
|
||||
|
||||
@container (min-width: 768px) {
|
||||
.library-header {
|
||||
@container (min-width: $mantine-breakpoint-sm) {
|
||||
grid-template-areas: 'image info';
|
||||
grid-template-rows: auto;
|
||||
grid-template-columns: 225px minmax(0, 1fr);
|
||||
@@ -45,10 +43,8 @@
|
||||
height: 225px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@container (min-width: 1200px) {
|
||||
.library-header {
|
||||
@container (min-width: $mantine-breakpoint-lg) {
|
||||
grid-template-columns: 250px minmax(0, 1fr);
|
||||
|
||||
.image {
|
||||
@@ -70,6 +66,10 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
filter: drop-shadow(0 0 8px rgb(0 0 0 / 50%));
|
||||
|
||||
@container (min-width: $mantine-breakpoint-sm) {
|
||||
align-items: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
.metadata-section {
|
||||
@@ -81,25 +81,12 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
|
||||
& > div {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
@container (min-width: 768px) {
|
||||
.image-section {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.metadata-section,
|
||||
.metadata-section > div:first-of-type,
|
||||
.metadata-section > div:last-of-type {
|
||||
@container (min-width: $mantine-breakpoint-sm) {
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
justify-content: flex-end;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
@@ -146,3 +133,18 @@
|
||||
color: var(--theme-colors-foreground);
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.library-header-menu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--theme-spacing-sm);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
|
||||
@container (min-width: $mantine-breakpoint-sm) {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,16 @@ import { Link } from 'react-router';
|
||||
|
||||
import styles from './library-header.module.css';
|
||||
|
||||
import {
|
||||
WidePlayButton,
|
||||
WideShuffleButton,
|
||||
} from '/@/renderer/features/shared/components/play-button';
|
||||
import { useGeneralSettings } from '/@/renderer/store';
|
||||
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
|
||||
import { Center } from '/@/shared/components/center/center';
|
||||
import { Group } from '/@/shared/components/group/group';
|
||||
import { Image } from '/@/shared/components/image/image';
|
||||
import { Rating } from '/@/shared/components/rating/rating';
|
||||
import { Text } from '/@/shared/components/text/text';
|
||||
import { LibraryItem } from '/@/shared/types/domain-types';
|
||||
|
||||
@@ -117,6 +124,7 @@ export const LibraryHeader = forwardRef(
|
||||
{title && (
|
||||
<div className={styles.metadataSection}>
|
||||
<Text
|
||||
className={styles.itemType}
|
||||
component={Link}
|
||||
fw={600}
|
||||
isLink
|
||||
@@ -127,7 +135,7 @@ export const LibraryHeader = forwardRef(
|
||||
{itemTypeString()}
|
||||
</Text>
|
||||
<h1 className={styles.title}>
|
||||
<AutoTextSize maxFontSizePx={80} minFontSizePx={36} mode="box">
|
||||
<AutoTextSize maxFontSizePx={80} minFontSizePx={32} mode="box">
|
||||
{title}
|
||||
</AutoTextSize>
|
||||
</h1>
|
||||
@@ -138,3 +146,54 @@ export const LibraryHeader = forwardRef(
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
interface LibraryHeaderMenuProps {
|
||||
favorite?: boolean;
|
||||
onFavorite?: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
onMore?: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
onPlay?: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
onRating?: (rating: number) => void;
|
||||
onShuffle?: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
rating?: number;
|
||||
}
|
||||
|
||||
export const LibraryHeaderMenu = ({
|
||||
favorite,
|
||||
onFavorite,
|
||||
onMore,
|
||||
onPlay,
|
||||
onRating,
|
||||
onShuffle,
|
||||
rating,
|
||||
}: LibraryHeaderMenuProps) => {
|
||||
return (
|
||||
<div className={styles.libraryHeaderMenu}>
|
||||
<Group wrap="nowrap">
|
||||
{onPlay && <WidePlayButton onClick={onPlay} />}
|
||||
{onShuffle && <WideShuffleButton onClick={onShuffle} />}
|
||||
</Group>
|
||||
<Group gap="sm" wrap="nowrap">
|
||||
{onRating && <Rating onChange={onRating} size="lg" value={rating || 0} />}
|
||||
{onFavorite && (
|
||||
<ActionIcon
|
||||
icon="favorite"
|
||||
iconProps={{
|
||||
fill: favorite ? 'primary' : undefined,
|
||||
}}
|
||||
onClick={onFavorite}
|
||||
size="lg"
|
||||
variant="transparent"
|
||||
/>
|
||||
)}
|
||||
{onMore && (
|
||||
<ActionIcon
|
||||
icon="ellipsisHorizontal"
|
||||
onClick={onMore}
|
||||
size="lg"
|
||||
variant="transparent"
|
||||
/>
|
||||
)}
|
||||
</Group>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -14,3 +14,80 @@
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.button.unthemed {
|
||||
@mixin light {
|
||||
color: white;
|
||||
background: black;
|
||||
|
||||
svg {
|
||||
color: white;
|
||||
fill: white;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: lighten(black, 10%);
|
||||
}
|
||||
}
|
||||
|
||||
@mixin dark {
|
||||
color: black;
|
||||
background: white;
|
||||
|
||||
svg {
|
||||
color: black;
|
||||
fill: black;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: darken(white, 20%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wide-button {
|
||||
padding-right: var(--theme-spacing-xl);
|
||||
padding-left: var(--theme-spacing-xl);
|
||||
background: white;
|
||||
border-radius: var(--theme-radius-xl);
|
||||
transition: background-color 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.wide-button.unthemed {
|
||||
@mixin light {
|
||||
background: black;
|
||||
|
||||
svg {
|
||||
color: white;
|
||||
fill: white;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: lighten(black, 10%);
|
||||
}
|
||||
}
|
||||
|
||||
@mixin dark {
|
||||
background: white;
|
||||
|
||||
svg {
|
||||
color: black;
|
||||
fill: black;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: darken(white, 20%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wide-button-label {
|
||||
font-size: var(--theme-font-size-md);
|
||||
font-weight: 600;
|
||||
color: black;
|
||||
|
||||
svg {
|
||||
color: black;
|
||||
fill: black;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,69 @@
|
||||
import clsx from 'clsx';
|
||||
import { t } from 'i18next';
|
||||
|
||||
import styles from './play-button.module.css';
|
||||
|
||||
import { ActionIcon, ActionIconProps } from '/@/shared/components/action-icon/action-icon';
|
||||
import { Button, ButtonProps } from '/@/shared/components/button/button';
|
||||
import { Group } from '/@/shared/components/group/group';
|
||||
import { Icon } from '/@/shared/components/icon/icon';
|
||||
|
||||
export interface PlayButtonProps extends ActionIconProps {
|
||||
size?: number | string;
|
||||
}
|
||||
|
||||
export const PlayButton = ({ className, ...props }: PlayButtonProps) => {
|
||||
export const PlayButton = ({ className, variant = 'filled', ...props }: PlayButtonProps) => {
|
||||
return (
|
||||
<ActionIcon
|
||||
className={clsx(styles.button, className)}
|
||||
className={clsx(styles.button, className, {
|
||||
[styles.unthemed]: variant !== 'filled',
|
||||
})}
|
||||
icon="mediaPlay"
|
||||
iconProps={{
|
||||
size: 'xl',
|
||||
}}
|
||||
variant="filled"
|
||||
variant={variant}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
interface WidePlayButtonProps extends ButtonProps {}
|
||||
|
||||
export const WidePlayButton = ({
|
||||
className,
|
||||
variant = 'default',
|
||||
...props
|
||||
}: WidePlayButtonProps) => {
|
||||
return (
|
||||
<Button
|
||||
className={clsx(styles.wideButton, className, {
|
||||
[styles.unthemed]: variant !== 'filled',
|
||||
})}
|
||||
classNames={{
|
||||
label: styles.wideButtonLabel,
|
||||
root: styles.wideButton,
|
||||
}}
|
||||
variant="subtle"
|
||||
{...props}
|
||||
>
|
||||
{props.children || (
|
||||
<Group gap="sm" wrap="nowrap">
|
||||
<Icon fill="default" icon="mediaPlay" size="lg" />
|
||||
{t('player.play', { postProcess: 'sentenceCase' })}
|
||||
</Group>
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export const WideShuffleButton = ({ ...props }: WidePlayButtonProps) => {
|
||||
return (
|
||||
<WidePlayButton {...props}>
|
||||
<Group gap="sm" wrap="nowrap">
|
||||
<Icon fill="default" icon="mediaShuffle" size="lg" />
|
||||
{t('action.shuffle', { postProcess: 'sentenceCase' })}
|
||||
</Group>
|
||||
</WidePlayButton>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user