mirror of
https://github.com/antebudimir/feishin.git
synced 2025-12-31 18:13:31 +00:00
Migrate to Mantine v8 and Design Changes (#961)
* mantine v8 migration * various design changes and improvements
This commit is contained in:
parent
bea55d48a8
commit
c1330d92b2
473 changed files with 12469 additions and 11607 deletions
|
|
@ -12,7 +12,9 @@ export const ImageButton = () => {
|
|||
mr={5}
|
||||
onClick={() => toggleImage()}
|
||||
size="xl"
|
||||
tooltip={showImage ? 'Hide Image' : 'Show Image'}
|
||||
tooltip={{
|
||||
label: showImage ? 'Hide Image' : 'Show Image',
|
||||
}}
|
||||
variant="default"
|
||||
>
|
||||
{showImage ? <CiImageOff size={30} /> : <CiImageOn size={30} />}
|
||||
|
|
|
|||
|
|
@ -9,11 +9,13 @@ export const ReconnectButton = () => {
|
|||
|
||||
return (
|
||||
<RemoteButton
|
||||
$active={!connected}
|
||||
isActive={!connected}
|
||||
mr={5}
|
||||
onClick={() => reconnect()}
|
||||
size="xl"
|
||||
tooltip={connected ? 'Reconnect' : 'Not connected. Reconnect.'}
|
||||
tooltip={{
|
||||
label: connected ? 'Reconnect' : 'Not connected. Reconnect.',
|
||||
}}
|
||||
variant="default"
|
||||
>
|
||||
<RiRestartLine size={30} />
|
||||
|
|
|
|||
24
src/remote/components/buttons/remote-button.module.css
Normal file
24
src/remote/components/buttons/remote-button.module.css
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
.button {
|
||||
svg {
|
||||
display: flex;
|
||||
fill: var(--theme-colors-foreground);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
svg {
|
||||
fill: var(--theme-colors-foreground);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.button.active {
|
||||
svg {
|
||||
fill: var(--primary-color);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
svg {
|
||||
fill: var(--primary-color) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,53 +1,29 @@
|
|||
import { Button, type ButtonProps as MantineButtonProps, Tooltip } from '@mantine/core';
|
||||
import { forwardRef, MouseEvent, ReactNode, Ref } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import clsx from 'clsx';
|
||||
import { forwardRef, ReactNode, Ref } from 'react';
|
||||
|
||||
export interface ButtonProps extends StyledButtonProps {
|
||||
tooltip: string;
|
||||
}
|
||||
import styles from './remote-button.module.css';
|
||||
|
||||
interface StyledButtonProps extends MantineButtonProps {
|
||||
$active?: boolean;
|
||||
import { Button, ButtonProps } from '/@/shared/components/button/button';
|
||||
|
||||
interface RemoteButtonProps extends ButtonProps {
|
||||
children: ReactNode;
|
||||
onClick?: (e: MouseEvent<HTMLButtonElement, MouseEvent>) => void;
|
||||
onMouseDown?: (e: MouseEvent<HTMLButtonElement, MouseEvent>) => void;
|
||||
isActive?: boolean;
|
||||
ref: Ref<HTMLButtonElement>;
|
||||
}
|
||||
|
||||
const StyledButton = styled(Button)<StyledButtonProps>`
|
||||
svg {
|
||||
display: flex;
|
||||
fill: ${({ $active: active }) =>
|
||||
active ? 'var(--primary-color)' : 'var(--playerbar-btn-fg)'};
|
||||
stroke: var(--playerbar-btn-fg);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--playerbar-btn-bg-hover);
|
||||
|
||||
svg {
|
||||
fill: ${({ $active: active }) =>
|
||||
active
|
||||
? 'var(--primary-color) !important'
|
||||
: 'var(--playerbar-btn-fg-hover) !important'};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const RemoteButton = forwardRef<HTMLButtonElement, any>(
|
||||
({ children, tooltip, ...props }: any, ref) => {
|
||||
export const RemoteButton = forwardRef<HTMLButtonElement, RemoteButtonProps>(
|
||||
({ children, isActive, tooltip, ...props }, ref) => {
|
||||
return (
|
||||
<Tooltip
|
||||
label={tooltip}
|
||||
withinPortal
|
||||
<Button
|
||||
className={clsx(styles.button, {
|
||||
[styles.active]: isActive,
|
||||
})}
|
||||
tooltip={tooltip}
|
||||
{...props}
|
||||
ref={ref}
|
||||
>
|
||||
<StyledButton
|
||||
{...props}
|
||||
ref={ref}
|
||||
>
|
||||
{children}
|
||||
</StyledButton>
|
||||
</Tooltip>
|
||||
{children}
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { RiMoonLine, RiSunLine } from 'react-icons/ri';
|
|||
|
||||
import { RemoteButton } from '/@/remote/components/buttons/remote-button';
|
||||
import { useIsDark, useToggleDark } from '/@/remote/store';
|
||||
import { AppTheme } from '/@/shared/types/domain-types';
|
||||
import { AppTheme } from '/@/shared/themes/app-theme-types';
|
||||
|
||||
export const ThemeButton = () => {
|
||||
const isDark = useIsDark();
|
||||
|
|
@ -19,7 +19,9 @@ export const ThemeButton = () => {
|
|||
mr={5}
|
||||
onClick={() => toggleDark()}
|
||||
size="xl"
|
||||
tooltip="Toggle Theme"
|
||||
tooltip={{
|
||||
label: 'Toggle Theme',
|
||||
}}
|
||||
variant="default"
|
||||
>
|
||||
{isDark ? <RiSunLine size={30} /> : <RiMoonLine size={30} />}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Group, Image, Rating, Text, Title, Tooltip } from '@mantine/core';
|
||||
import { Image, Title } from '@mantine/core';
|
||||
import formatDuration from 'format-duration';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { useCallback } from 'react';
|
||||
|
|
@ -17,6 +17,10 @@ import {
|
|||
import { RemoteButton } from '/@/remote/components/buttons/remote-button';
|
||||
import { WrapperSlider } from '/@/remote/components/wrapped-slider';
|
||||
import { useInfo, useSend, useShowImage } from '/@/remote/store';
|
||||
import { Group } from '/@/shared/components/group/group';
|
||||
import { Rating } from '/@/shared/components/rating/rating';
|
||||
import { Text } from '/@/shared/components/text/text';
|
||||
import { Tooltip } from '/@/shared/components/tooltip/tooltip';
|
||||
import { PlayerRepeat, PlayerStatus } from '/@/shared/types/types';
|
||||
|
||||
export const RemoteContainer = () => {
|
||||
|
|
@ -44,7 +48,7 @@ export const RemoteContainer = () => {
|
|||
<Title order={2}>Album: {song.album}</Title>
|
||||
<Title order={2}>Artist: {song.artistName}</Title>
|
||||
</Group>
|
||||
<Group position="apart">
|
||||
<Group justify="space-between">
|
||||
<Title order={3}>Duration: {formatDuration(song.duration)}</Title>
|
||||
{song.releaseDate && (
|
||||
<Title order={3}>
|
||||
|
|
@ -56,13 +60,15 @@ export const RemoteContainer = () => {
|
|||
</>
|
||||
)}
|
||||
<Group
|
||||
gap={0}
|
||||
grow
|
||||
spacing={0}
|
||||
>
|
||||
<RemoteButton
|
||||
disabled={!id}
|
||||
onClick={() => send({ event: 'previous' })}
|
||||
tooltip="Previous track"
|
||||
tooltip={{
|
||||
label: 'Previous track',
|
||||
}}
|
||||
variant="default"
|
||||
>
|
||||
<RiSkipBackFill size={25} />
|
||||
|
|
@ -76,7 +82,9 @@ export const RemoteContainer = () => {
|
|||
send({ event: 'play' });
|
||||
}
|
||||
}}
|
||||
tooltip={id && status === PlayerStatus.PLAYING ? 'Pause' : 'Play'}
|
||||
tooltip={{
|
||||
label: id && status === PlayerStatus.PLAYING ? 'Pause' : 'Play',
|
||||
}}
|
||||
variant="default"
|
||||
>
|
||||
{id && status === PlayerStatus.PLAYING ? (
|
||||
|
|
@ -88,34 +96,40 @@ export const RemoteContainer = () => {
|
|||
<RemoteButton
|
||||
disabled={!id}
|
||||
onClick={() => send({ event: 'next' })}
|
||||
tooltip="Next track"
|
||||
tooltip={{
|
||||
label: 'Next track',
|
||||
}}
|
||||
variant="default"
|
||||
>
|
||||
<RiSkipForwardFill size={25} />
|
||||
</RemoteButton>
|
||||
</Group>
|
||||
<Group
|
||||
gap={0}
|
||||
grow
|
||||
spacing={0}
|
||||
>
|
||||
<RemoteButton
|
||||
$active={shuffle || false}
|
||||
isActive={shuffle || false}
|
||||
onClick={() => send({ event: 'shuffle' })}
|
||||
tooltip={shuffle ? 'Shuffle tracks' : 'Shuffle disabled'}
|
||||
tooltip={{
|
||||
label: shuffle ? 'Shuffle tracks' : 'Shuffle disabled',
|
||||
}}
|
||||
variant="default"
|
||||
>
|
||||
<RiShuffleFill size={25} />
|
||||
</RemoteButton>
|
||||
<RemoteButton
|
||||
$active={repeat !== undefined && repeat !== PlayerRepeat.NONE}
|
||||
isActive={repeat !== undefined && repeat !== PlayerRepeat.NONE}
|
||||
onClick={() => send({ event: 'repeat' })}
|
||||
tooltip={`Repeat ${
|
||||
repeat === PlayerRepeat.ONE
|
||||
? 'One'
|
||||
: repeat === PlayerRepeat.ALL
|
||||
? 'all'
|
||||
: 'none'
|
||||
}`}
|
||||
tooltip={{
|
||||
label: `Repeat ${
|
||||
repeat === PlayerRepeat.ONE
|
||||
? 'One'
|
||||
: repeat === PlayerRepeat.ALL
|
||||
? 'all'
|
||||
: 'none'
|
||||
}`,
|
||||
}}
|
||||
variant="default"
|
||||
>
|
||||
{repeat === undefined || repeat === PlayerRepeat.ONE ? (
|
||||
|
|
@ -125,14 +139,16 @@ export const RemoteContainer = () => {
|
|||
)}
|
||||
</RemoteButton>
|
||||
<RemoteButton
|
||||
$active={song?.userFavorite}
|
||||
disabled={!id}
|
||||
isActive={song?.userFavorite}
|
||||
onClick={() => {
|
||||
if (!id) return;
|
||||
|
||||
send({ event: 'favorite', favorite: !song.userFavorite, id });
|
||||
}}
|
||||
tooltip={song?.userFavorite ? 'Unfavorite' : 'Favorite'}
|
||||
tooltip={{
|
||||
label: song?.userFavorite ? 'Unfavorite' : 'Favorite',
|
||||
}}
|
||||
variant="default"
|
||||
>
|
||||
<RiHeartLine size={25} />
|
||||
|
|
@ -146,7 +162,7 @@ export const RemoteContainer = () => {
|
|||
<Rating
|
||||
onChange={debouncedSetRating}
|
||||
onDoubleClick={() => debouncedSetRating(0)}
|
||||
sx={{ margin: 'auto' }}
|
||||
style={{ margin: 'auto' }}
|
||||
value={song.userRating ?? 0}
|
||||
/>
|
||||
</Tooltip>
|
||||
|
|
@ -169,8 +185,8 @@ export const RemoteContainer = () => {
|
|||
onChangeEnd={(e) => send({ event: 'volume', volume: e })}
|
||||
rightLabel={
|
||||
<Text
|
||||
fw={600}
|
||||
size="xs"
|
||||
weight={600}
|
||||
>
|
||||
{volume ?? 0}
|
||||
</Text>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,4 @@
|
|||
import {
|
||||
AppShell,
|
||||
Container,
|
||||
Flex,
|
||||
Grid,
|
||||
Header,
|
||||
Image,
|
||||
MediaQuery,
|
||||
Skeleton,
|
||||
Title,
|
||||
} from '@mantine/core';
|
||||
import { AppShell, Container, Flex, Grid, Image, Skeleton, Title } from '@mantine/core';
|
||||
|
||||
import { ImageButton } from '/@/remote/components/buttons/image-button';
|
||||
import { ReconnectButton } from '/@/remote/components/buttons/reconnect-button';
|
||||
|
|
@ -20,47 +10,35 @@ export const Shell = () => {
|
|||
const connected = useConnected();
|
||||
|
||||
return (
|
||||
<AppShell
|
||||
header={
|
||||
<Header height={60}>
|
||||
<Grid>
|
||||
<Grid.Col span="auto">
|
||||
<div>
|
||||
<Image
|
||||
fit="contain"
|
||||
height={60}
|
||||
src="/favicon.ico"
|
||||
width={60}
|
||||
/>
|
||||
</div>
|
||||
</Grid.Col>
|
||||
<MediaQuery
|
||||
smallerThan="sm"
|
||||
styles={{ display: 'none' }}
|
||||
>
|
||||
<Grid.Col
|
||||
sm={6}
|
||||
xs={0}
|
||||
>
|
||||
<Title ta="center">Feishin Remote</Title>
|
||||
</Grid.Col>
|
||||
</MediaQuery>
|
||||
<AppShell padding="md">
|
||||
<AppShell.Header>
|
||||
<Grid>
|
||||
<Grid.Col span="auto">
|
||||
<div>
|
||||
<Image
|
||||
fit="contain"
|
||||
height={60}
|
||||
src="/favicon.ico"
|
||||
width={60}
|
||||
/>
|
||||
</div>
|
||||
</Grid.Col>
|
||||
<Grid.Col hiddenFrom="md">
|
||||
<Title ta="center">Feishin Remote</Title>
|
||||
</Grid.Col>
|
||||
|
||||
<Grid.Col span="auto">
|
||||
<Flex
|
||||
direction="row"
|
||||
justify="right"
|
||||
>
|
||||
<ReconnectButton />
|
||||
<ImageButton />
|
||||
<ThemeButton />
|
||||
</Flex>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</Header>
|
||||
}
|
||||
padding="md"
|
||||
>
|
||||
<Grid.Col span="auto">
|
||||
<Flex
|
||||
direction="row"
|
||||
justify="right"
|
||||
>
|
||||
<ReconnectButton />
|
||||
<ImageButton />
|
||||
<ThemeButton />
|
||||
</Flex>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</AppShell.Header>
|
||||
<Container>
|
||||
{connected ? (
|
||||
<RemoteContainer />
|
||||
|
|
|
|||
21
src/remote/components/wrapped-slider.module.css
Normal file
21
src/remote/components/wrapped-slider.module.css
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
.container {
|
||||
display: flex;
|
||||
width: 95%;
|
||||
height: 20px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
display: flex;
|
||||
flex: 6;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.value-wrapper {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-self: flex-end;
|
||||
justify-content: center;
|
||||
max-width: 50px;
|
||||
}
|
||||
|
|
@ -1,40 +1,16 @@
|
|||
import { rem, Slider, SliderProps } from '@mantine/core';
|
||||
import { ReactNode, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const SliderContainer = styled.div`
|
||||
display: flex;
|
||||
width: 95%;
|
||||
height: 20px;
|
||||
margin: 10px 0;
|
||||
`;
|
||||
|
||||
const SliderValueWrapper = styled.div<{ $position: 'left' | 'right' }>`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-self: flex-end;
|
||||
justify-content: center;
|
||||
max-width: 50px;
|
||||
`;
|
||||
|
||||
const SliderWrapper = styled.div`
|
||||
display: flex;
|
||||
flex: 6;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
`;
|
||||
import styles from './wrapped-slider.module.css';
|
||||
|
||||
const PlayerbarSlider = ({ ...props }: SliderProps) => {
|
||||
return (
|
||||
<Slider
|
||||
styles={{
|
||||
bar: {
|
||||
backgroundColor: 'var(--playerbar-slider-track-progress-bg)',
|
||||
transition: 'background-color 0.2s ease',
|
||||
},
|
||||
label: {
|
||||
backgroundColor: 'var(--tooltip-bg)',
|
||||
color: 'var(--tooltip-fg)',
|
||||
fontSize: '1.1rem',
|
||||
fontWeight: 600,
|
||||
padding: '0 1rem',
|
||||
|
|
@ -59,7 +35,6 @@ const PlayerbarSlider = ({ ...props }: SliderProps) => {
|
|||
},
|
||||
track: {
|
||||
'&::before': {
|
||||
backgroundColor: 'var(--playerbar-slider-track-bg)',
|
||||
right: 'calc(0.1rem * -1)',
|
||||
},
|
||||
},
|
||||
|
|
@ -84,9 +59,9 @@ export const WrapperSlider = ({ leftLabel, rightLabel, value, ...props }: Wrappe
|
|||
const [seek, setSeek] = useState(0);
|
||||
|
||||
return (
|
||||
<SliderContainer>
|
||||
{leftLabel && <SliderValueWrapper $position="left">{leftLabel}</SliderValueWrapper>}
|
||||
<SliderWrapper>
|
||||
<div className={styles.container}>
|
||||
{leftLabel && <div className={styles.valueWrapper}>{leftLabel}</div>}
|
||||
<div className={styles.wrapper}>
|
||||
<PlayerbarSlider
|
||||
{...props}
|
||||
min={0}
|
||||
|
|
@ -102,8 +77,8 @@ export const WrapperSlider = ({ leftLabel, rightLabel, value, ...props }: Wrappe
|
|||
value={!isSeeking ? (value ?? 0) : seek}
|
||||
w="100%"
|
||||
/>
|
||||
</SliderWrapper>
|
||||
{rightLabel && <SliderValueWrapper $position="right">{rightLabel}</SliderValueWrapper>}
|
||||
</SliderContainer>
|
||||
</div>
|
||||
{rightLabel && <div className={styles.valueWrapper}>{rightLabel}</div>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue