mirror of
https://github.com/antebudimir/feishin.git
synced 2026-01-01 10:23:33 +00:00
fix and update remote design
This commit is contained in:
parent
ad533a1d9c
commit
6689e84f67
24 changed files with 326 additions and 453 deletions
|
|
@ -1,23 +1,21 @@
|
|||
import { CiImageOff, CiImageOn } from 'react-icons/ci';
|
||||
|
||||
import { RemoteButton } from '/@/remote/components/buttons/remote-button';
|
||||
import { useShowImage, useToggleShowImage } from '/@/remote/store';
|
||||
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
|
||||
|
||||
export const ImageButton = () => {
|
||||
const showImage = useShowImage();
|
||||
const toggleImage = useToggleShowImage();
|
||||
|
||||
return (
|
||||
<RemoteButton
|
||||
mr={5}
|
||||
<ActionIcon
|
||||
onClick={() => toggleImage()}
|
||||
size="xl"
|
||||
tooltip={{
|
||||
label: showImage ? 'Hide Image' : 'Show Image',
|
||||
}}
|
||||
variant="default"
|
||||
>
|
||||
{showImage ? <CiImageOff size={30} /> : <CiImageOn size={30} />}
|
||||
</RemoteButton>
|
||||
</ActionIcon>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,24 +1,24 @@
|
|||
import { RiRestartLine } from 'react-icons/ri';
|
||||
|
||||
import { RemoteButton } from '/@/remote/components/buttons/remote-button';
|
||||
import { useConnected, useReconnect } from '/@/remote/store';
|
||||
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
|
||||
|
||||
export const ReconnectButton = () => {
|
||||
const connected = useConnected();
|
||||
const reconnect = useReconnect();
|
||||
|
||||
return (
|
||||
<RemoteButton
|
||||
isActive={!connected}
|
||||
mr={5}
|
||||
<ActionIcon
|
||||
onClick={() => reconnect()}
|
||||
size="xl"
|
||||
tooltip={{
|
||||
label: connected ? 'Reconnect' : 'Not connected. Reconnect.',
|
||||
}}
|
||||
variant="default"
|
||||
>
|
||||
<RiRestartLine size={30} />
|
||||
</RemoteButton>
|
||||
<RiRestartLine
|
||||
color={connected ? 'var(--theme-colors-primary)' : 'var(--theme-colors-foreground)'}
|
||||
size={30}
|
||||
/>
|
||||
</ActionIcon>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
.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,29 +0,0 @@
|
|||
import clsx from 'clsx';
|
||||
import { forwardRef, ReactNode, Ref } from 'react';
|
||||
|
||||
import styles from './remote-button.module.css';
|
||||
|
||||
import { Button, ButtonProps } from '/@/shared/components/button/button';
|
||||
|
||||
interface RemoteButtonProps extends ButtonProps {
|
||||
children: ReactNode;
|
||||
isActive?: boolean;
|
||||
ref: Ref<HTMLButtonElement>;
|
||||
}
|
||||
|
||||
export const RemoteButton = forwardRef<HTMLButtonElement, RemoteButtonProps>(
|
||||
({ children, isActive, tooltip, ...props }, ref) => {
|
||||
return (
|
||||
<Button
|
||||
className={clsx(styles.button, {
|
||||
[styles.active]: isActive,
|
||||
})}
|
||||
tooltip={tooltip}
|
||||
{...props}
|
||||
ref={ref}
|
||||
>
|
||||
{children}
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
@ -1,30 +1,34 @@
|
|||
import { useEffect } from 'react';
|
||||
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/themes/app-theme-types';
|
||||
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
|
||||
import { Icon } from '/@/shared/components/icon/icon';
|
||||
|
||||
export const ThemeButton = () => {
|
||||
const isDark = useIsDark();
|
||||
const toggleDark = useToggleDark();
|
||||
|
||||
useEffect(() => {
|
||||
const targetTheme: AppTheme = isDark ? AppTheme.DEFAULT_DARK : AppTheme.DEFAULT_LIGHT;
|
||||
document.body.setAttribute('data-theme', targetTheme);
|
||||
}, [isDark]);
|
||||
const handleToggleTheme = () => {
|
||||
toggleDark();
|
||||
};
|
||||
|
||||
return (
|
||||
<RemoteButton
|
||||
mr={5}
|
||||
onClick={() => toggleDark()}
|
||||
size="xl"
|
||||
<ActionIcon
|
||||
onClick={handleToggleTheme}
|
||||
tooltip={{
|
||||
label: 'Toggle Theme',
|
||||
}}
|
||||
variant="default"
|
||||
>
|
||||
{isDark ? <RiSunLine size={30} /> : <RiMoonLine size={30} />}
|
||||
</RemoteButton>
|
||||
{isDark ? (
|
||||
<Icon
|
||||
icon="themeLight"
|
||||
size={30}
|
||||
/>
|
||||
) : (
|
||||
<Icon
|
||||
icon="themeDark"
|
||||
size={30}
|
||||
/>
|
||||
)}
|
||||
</ActionIcon>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
7
src/remote/components/player-image.module.css
Normal file
7
src/remote/components/player-image.module.css
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
.container {
|
||||
width: 100%;
|
||||
height: 40vh;
|
||||
aspect-ratio: 1/1;
|
||||
object-fit: var(--theme-image-fit);
|
||||
border-radius: var(--theme-radius-md);
|
||||
}
|
||||
18
src/remote/components/player-image.tsx
Normal file
18
src/remote/components/player-image.tsx
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import styles from './player-image.module.css';
|
||||
|
||||
import { useSend } from '/@/remote/store';
|
||||
|
||||
interface PlayerImageProps {
|
||||
src?: null | string;
|
||||
}
|
||||
export const PlayerImage = ({ src }: PlayerImageProps) => {
|
||||
const send = useSend();
|
||||
|
||||
return (
|
||||
<img
|
||||
className={styles.container}
|
||||
onError={() => send({ event: 'proxy' })}
|
||||
src={src?.replaceAll(/&(size|width|height=\d+)/g, '')}
|
||||
/>
|
||||
);
|
||||
};
|
||||
0
src/remote/components/remote-container.module.css
Normal file
0
src/remote/components/remote-container.module.css
Normal file
|
|
@ -1,24 +1,16 @@
|
|||
import { Image, Title } from '@mantine/core';
|
||||
import formatDuration from 'format-duration';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { useCallback } from 'react';
|
||||
import {
|
||||
RiHeartLine,
|
||||
RiPauseFill,
|
||||
RiPlayFill,
|
||||
RiRepeat2Line,
|
||||
RiRepeatOneLine,
|
||||
RiShuffleFill,
|
||||
RiSkipBackFill,
|
||||
RiSkipForwardFill,
|
||||
RiVolumeUpFill,
|
||||
} from 'react-icons/ri';
|
||||
import { RiPauseFill, RiPlayFill, RiVolumeUpFill } from 'react-icons/ri';
|
||||
|
||||
import { RemoteButton } from '/@/remote/components/buttons/remote-button';
|
||||
import { WrapperSlider } from '/@/remote/components/wrapped-slider';
|
||||
import { PlayerImage } from '/@/remote/components/player-image';
|
||||
import { WrappedSlider } from '/@/remote/components/wrapped-slider';
|
||||
import { useInfo, useSend, useShowImage } from '/@/remote/store';
|
||||
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
|
||||
import { Flex } from '/@/shared/components/flex/flex';
|
||||
import { Group } from '/@/shared/components/group/group';
|
||||
import { Rating } from '/@/shared/components/rating/rating';
|
||||
import { Stack } from '/@/shared/components/stack/stack';
|
||||
import { Text } from '/@/shared/components/text/text';
|
||||
import { Tooltip } from '/@/shared/components/tooltip/tooltip';
|
||||
import { PlayerRepeat, PlayerStatus } from '/@/shared/types/types';
|
||||
|
|
@ -40,40 +32,115 @@ export const RemoteContainer = () => {
|
|||
const debouncedSetRating = debounce(setRating, 400);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack
|
||||
gap="md"
|
||||
h="100dvh"
|
||||
w="100%"
|
||||
>
|
||||
{showImage && (
|
||||
<Flex
|
||||
align="center"
|
||||
justify="center"
|
||||
w="100%"
|
||||
>
|
||||
<PlayerImage src={song?.imageUrl} />
|
||||
</Flex>
|
||||
)}
|
||||
{id && (
|
||||
<>
|
||||
<Title order={1}>{song.name}</Title>
|
||||
<Group align="flex-end">
|
||||
<Title order={2}>Album: {song.album}</Title>
|
||||
<Title order={2}>Artist: {song.artistName}</Title>
|
||||
</Group>
|
||||
<Stack gap="xs">
|
||||
<Text
|
||||
fw={700}
|
||||
size="xl"
|
||||
style={{
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
{song.name}
|
||||
</Text>
|
||||
<Text
|
||||
isMuted
|
||||
style={{
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
{song.album}
|
||||
</Text>
|
||||
<Text
|
||||
isMuted
|
||||
style={{
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
{song.artistName}
|
||||
</Text>
|
||||
<Group justify="space-between">
|
||||
<Title order={3}>Duration: {formatDuration(song.duration)}</Title>
|
||||
{song.releaseDate && (
|
||||
<Title order={3}>
|
||||
Released: {new Date(song.releaseDate).toLocaleDateString()}
|
||||
</Title>
|
||||
<Text isMuted>{new Date(song.releaseDate).toLocaleDateString()}</Text>
|
||||
)}
|
||||
<Title order={3}>Plays: {song.playCount}</Title>
|
||||
<Text isMuted>Plays: {song.playCount}</Text>
|
||||
</Group>
|
||||
</>
|
||||
</Stack>
|
||||
)}
|
||||
<Group
|
||||
gap={0}
|
||||
grow
|
||||
>
|
||||
<RemoteButton
|
||||
<ActionIcon
|
||||
disabled={!id}
|
||||
icon="favorite"
|
||||
iconProps={{
|
||||
fill: song?.userFavorite ? 'primary' : 'default',
|
||||
}}
|
||||
onClick={() => {
|
||||
if (!id) return;
|
||||
|
||||
send({ event: 'favorite', favorite: !song.userFavorite, id });
|
||||
}}
|
||||
tooltip={{
|
||||
label: song?.userFavorite ? 'Unfavorite' : 'Favorite',
|
||||
}}
|
||||
variant="transparent"
|
||||
/>
|
||||
{(song?.serverType === 'navidrome' || song?.serverType === 'subsonic') && (
|
||||
<div style={{ margin: 'auto' }}>
|
||||
<Tooltip
|
||||
label="Double click to clear"
|
||||
openDelay={1000}
|
||||
>
|
||||
<Rating
|
||||
onChange={debouncedSetRating}
|
||||
onDoubleClick={() => debouncedSetRating(0)}
|
||||
style={{ margin: 'auto' }}
|
||||
value={song.userRating ?? 0}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</Group>
|
||||
<Group
|
||||
gap="xs"
|
||||
grow
|
||||
>
|
||||
<ActionIcon
|
||||
disabled={!id}
|
||||
icon="mediaPrevious"
|
||||
iconProps={{
|
||||
fill: 'default',
|
||||
size: 'lg',
|
||||
}}
|
||||
onClick={() => send({ event: 'previous' })}
|
||||
tooltip={{
|
||||
label: 'Previous track',
|
||||
}}
|
||||
variant="default"
|
||||
>
|
||||
<RiSkipBackFill size={25} />
|
||||
</RemoteButton>
|
||||
<RemoteButton
|
||||
/>
|
||||
<ActionIcon
|
||||
disabled={!id}
|
||||
onClick={() => {
|
||||
if (status === PlayerStatus.PLAYING) {
|
||||
|
|
@ -92,34 +159,50 @@ export const RemoteContainer = () => {
|
|||
) : (
|
||||
<RiPlayFill size={25} />
|
||||
)}
|
||||
</RemoteButton>
|
||||
<RemoteButton
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
disabled={!id}
|
||||
icon="mediaNext"
|
||||
iconProps={{
|
||||
fill: 'default',
|
||||
size: 'lg',
|
||||
}}
|
||||
onClick={() => send({ event: 'next' })}
|
||||
tooltip={{
|
||||
label: 'Next track',
|
||||
}}
|
||||
variant="default"
|
||||
>
|
||||
<RiSkipForwardFill size={25} />
|
||||
</RemoteButton>
|
||||
/>
|
||||
</Group>
|
||||
<Group
|
||||
gap={0}
|
||||
gap="xs"
|
||||
grow
|
||||
>
|
||||
<RemoteButton
|
||||
isActive={shuffle || false}
|
||||
<ActionIcon
|
||||
icon="mediaShuffle"
|
||||
iconProps={{
|
||||
fill: shuffle ? 'primary' : 'default',
|
||||
size: 'lg',
|
||||
}}
|
||||
onClick={() => send({ event: 'shuffle' })}
|
||||
tooltip={{
|
||||
label: shuffle ? 'Shuffle tracks' : 'Shuffle disabled',
|
||||
}}
|
||||
variant="default"
|
||||
>
|
||||
<RiShuffleFill size={25} />
|
||||
</RemoteButton>
|
||||
<RemoteButton
|
||||
isActive={repeat !== undefined && repeat !== PlayerRepeat.NONE}
|
||||
/>
|
||||
<ActionIcon
|
||||
icon={
|
||||
repeat === undefined || repeat === PlayerRepeat.ONE
|
||||
? 'mediaRepeatOne'
|
||||
: 'mediaRepeat'
|
||||
}
|
||||
iconProps={{
|
||||
fill:
|
||||
repeat !== undefined && repeat !== PlayerRepeat.NONE
|
||||
? 'primary'
|
||||
: 'default',
|
||||
size: 'lg',
|
||||
}}
|
||||
onClick={() => send({ event: 'repeat' })}
|
||||
tooltip={{
|
||||
label: `Repeat ${
|
||||
|
|
@ -131,74 +214,34 @@ export const RemoteContainer = () => {
|
|||
}`,
|
||||
}}
|
||||
variant="default"
|
||||
>
|
||||
{repeat === undefined || repeat === PlayerRepeat.ONE ? (
|
||||
<RiRepeatOneLine size={25} />
|
||||
) : (
|
||||
<RiRepeat2Line size={25} />
|
||||
)}
|
||||
</RemoteButton>
|
||||
<RemoteButton
|
||||
disabled={!id}
|
||||
isActive={song?.userFavorite}
|
||||
onClick={() => {
|
||||
if (!id) return;
|
||||
|
||||
send({ event: 'favorite', favorite: !song.userFavorite, id });
|
||||
}}
|
||||
tooltip={{
|
||||
label: song?.userFavorite ? 'Unfavorite' : 'Favorite',
|
||||
}}
|
||||
variant="default"
|
||||
>
|
||||
<RiHeartLine size={25} />
|
||||
</RemoteButton>
|
||||
{(song?.serverType === 'navidrome' || song?.serverType === 'subsonic') && (
|
||||
<div style={{ margin: 'auto' }}>
|
||||
<Tooltip
|
||||
label="Double click to clear"
|
||||
openDelay={1000}
|
||||
>
|
||||
<Rating
|
||||
onChange={debouncedSetRating}
|
||||
onDoubleClick={() => debouncedSetRating(0)}
|
||||
style={{ margin: 'auto' }}
|
||||
value={song.userRating ?? 0}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</Group>
|
||||
{id && position !== undefined && (
|
||||
<WrapperSlider
|
||||
label={(value) => formatDuration(value * 1e3)}
|
||||
leftLabel={formatDuration(position * 1e3)}
|
||||
max={song.duration / 1e3}
|
||||
onChangeEnd={(e) => send({ event: 'position', position: e })}
|
||||
rightLabel={formatDuration(song.duration)}
|
||||
value={position}
|
||||
<Stack gap="lg">
|
||||
{id && position !== undefined && (
|
||||
<WrappedSlider
|
||||
label={(value) => formatDuration(value * 1e3)}
|
||||
leftLabel={formatDuration(position * 1e3)}
|
||||
max={song.duration / 1e3}
|
||||
onChangeEnd={(e) => send({ event: 'position', position: e })}
|
||||
rightLabel={formatDuration(song.duration)}
|
||||
value={position}
|
||||
/>
|
||||
)}
|
||||
<WrappedSlider
|
||||
leftLabel={<RiVolumeUpFill size={20} />}
|
||||
max={100}
|
||||
onChangeEnd={(e) => send({ event: 'volume', volume: e })}
|
||||
rightLabel={
|
||||
<Text
|
||||
fw={600}
|
||||
size="xs"
|
||||
>
|
||||
{volume ?? 0}
|
||||
</Text>
|
||||
}
|
||||
value={volume ?? 0}
|
||||
/>
|
||||
)}
|
||||
<WrapperSlider
|
||||
leftLabel={<RiVolumeUpFill size={20} />}
|
||||
max={100}
|
||||
onChangeEnd={(e) => send({ event: 'volume', volume: e })}
|
||||
rightLabel={
|
||||
<Text
|
||||
fw={600}
|
||||
size="xs"
|
||||
>
|
||||
{volume ?? 0}
|
||||
</Text>
|
||||
}
|
||||
value={volume ?? 0}
|
||||
/>
|
||||
{showImage && (
|
||||
<Image
|
||||
onError={() => send({ event: 'proxy' })}
|
||||
src={song?.imageUrl?.replaceAll(/&(size|width|height=\d+)/g, '')}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,54 +1,71 @@
|
|||
import { AppShell, Container, Flex, Grid, Image, Skeleton, Title } from '@mantine/core';
|
||||
import { AppShell, Flex, Grid, Image } from '@mantine/core';
|
||||
|
||||
import { ImageButton } from '/@/remote/components/buttons/image-button';
|
||||
import { ReconnectButton } from '/@/remote/components/buttons/reconnect-button';
|
||||
import { ThemeButton } from '/@/remote/components/buttons/theme-button';
|
||||
import { RemoteContainer } from '/@/remote/components/remote-container';
|
||||
import { useConnected } from '/@/remote/store';
|
||||
import { Center } from '/@/shared/components/center/center';
|
||||
import { Group } from '/@/shared/components/group/group';
|
||||
import { Spinner } from '/@/shared/components/spinner/spinner';
|
||||
|
||||
export const Shell = () => {
|
||||
const connected = useConnected();
|
||||
|
||||
return (
|
||||
<AppShell padding="md">
|
||||
<AppShell.Header>
|
||||
<Grid>
|
||||
<Grid.Col span="auto">
|
||||
<div>
|
||||
<AppShell
|
||||
h="100vh"
|
||||
padding="md"
|
||||
w="100vw"
|
||||
>
|
||||
<AppShell.Header style={{ background: 'var(--theme-colors-surface)' }}>
|
||||
<Grid
|
||||
px="md"
|
||||
py="sm"
|
||||
>
|
||||
<Grid.Col span={4}>
|
||||
<Flex
|
||||
align="center"
|
||||
direction="row"
|
||||
h="100%"
|
||||
justify="flex-start"
|
||||
style={{
|
||||
justifySelf: 'flex-start',
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
fit="contain"
|
||||
height={60}
|
||||
height={32}
|
||||
src="/favicon.ico"
|
||||
width={60}
|
||||
width={32}
|
||||
/>
|
||||
</div>
|
||||
</Flex>
|
||||
</Grid.Col>
|
||||
<Grid.Col hiddenFrom="md">
|
||||
<Title ta="center">Feishin Remote</Title>
|
||||
</Grid.Col>
|
||||
|
||||
<Grid.Col span="auto">
|
||||
<Flex
|
||||
direction="row"
|
||||
justify="right"
|
||||
<Grid.Col span={8}>
|
||||
<Group
|
||||
gap="sm"
|
||||
justify="flex-end"
|
||||
wrap="nowrap"
|
||||
>
|
||||
<ReconnectButton />
|
||||
<ImageButton />
|
||||
<ThemeButton />
|
||||
</Flex>
|
||||
</Group>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</AppShell.Header>
|
||||
<Container>
|
||||
<AppShell.Main pt="60px">
|
||||
{connected ? (
|
||||
<RemoteContainer />
|
||||
) : (
|
||||
<Skeleton
|
||||
height={300}
|
||||
width="100%"
|
||||
/>
|
||||
<Center
|
||||
h="100vh"
|
||||
w="100vw"
|
||||
>
|
||||
<Spinner />
|
||||
</Center>
|
||||
)}
|
||||
</Container>
|
||||
</AppShell.Main>
|
||||
</AppShell>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
.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,7 +1,8 @@
|
|||
import { rem, Slider, SliderProps } from '@mantine/core';
|
||||
import { ReactNode, useState } from 'react';
|
||||
|
||||
import styles from './wrapped-slider.module.css';
|
||||
import { Group } from '/@/shared/components/group/group';
|
||||
import { Text } from '/@/shared/components/text/text';
|
||||
|
||||
const PlayerbarSlider = ({ ...props }: SliderProps) => {
|
||||
return (
|
||||
|
|
@ -37,6 +38,7 @@ const PlayerbarSlider = ({ ...props }: SliderProps) => {
|
|||
'&::before': {
|
||||
right: 'calc(0.1rem * -1)',
|
||||
},
|
||||
height: '1rem',
|
||||
},
|
||||
}}
|
||||
{...props}
|
||||
|
|
@ -54,31 +56,32 @@ export interface WrappedProps extends Omit<SliderProps, 'onChangeEnd'> {
|
|||
value: number;
|
||||
}
|
||||
|
||||
export const WrapperSlider = ({ leftLabel, rightLabel, value, ...props }: WrappedProps) => {
|
||||
export const WrappedSlider = ({ leftLabel, rightLabel, value, ...props }: WrappedProps) => {
|
||||
const [isSeeking, setIsSeeking] = useState(false);
|
||||
const [seek, setSeek] = useState(0);
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
{leftLabel && <div className={styles.valueWrapper}>{leftLabel}</div>}
|
||||
<div className={styles.wrapper}>
|
||||
<PlayerbarSlider
|
||||
{...props}
|
||||
min={0}
|
||||
onChange={(e) => {
|
||||
setIsSeeking(true);
|
||||
setSeek(e);
|
||||
}}
|
||||
onChangeEnd={(e) => {
|
||||
props.onChangeEnd(e);
|
||||
setIsSeeking(false);
|
||||
}}
|
||||
size={6}
|
||||
value={!isSeeking ? (value ?? 0) : seek}
|
||||
w="100%"
|
||||
/>
|
||||
</div>
|
||||
{rightLabel && <div className={styles.valueWrapper}>{rightLabel}</div>}
|
||||
</div>
|
||||
<Group
|
||||
align="center"
|
||||
wrap="nowrap"
|
||||
>
|
||||
{leftLabel && <Text size="sm">{leftLabel}</Text>}
|
||||
<PlayerbarSlider
|
||||
{...props}
|
||||
min={0}
|
||||
onChange={(e) => {
|
||||
setIsSeeking(true);
|
||||
setSeek(e);
|
||||
}}
|
||||
onChangeEnd={(e) => {
|
||||
props.onChangeEnd(e);
|
||||
setIsSeeking(false);
|
||||
}}
|
||||
size={6}
|
||||
value={!isSeeking ? (value ?? 0) : seek}
|
||||
w="100%"
|
||||
/>
|
||||
{rightLabel && <Text size="sm">{rightLabel}</Text>}
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue