redesign PlayButtonGroup to match card styles

This commit is contained in:
jeffvli
2025-11-29 04:02:47 -08:00
parent a99a02c94c
commit 93ba99e36f
4 changed files with 61 additions and 112 deletions
@@ -12,7 +12,7 @@ import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys'; import { queryKeys } from '/@/renderer/api/query-keys';
import { useGenreList } from '/@/renderer/features/genres/api/genres-api'; import { useGenreList } from '/@/renderer/features/genres/api/genres-api';
import { usePlayer } from '/@/renderer/features/player/context/player-context'; import { usePlayer } from '/@/renderer/features/player/context/player-context';
import { PlayButton } from '/@/renderer/features/shared/components/play-button'; import { PlayButtonGroup } from '/@/renderer/features/shared/components/play-button-group';
import { useCurrentServer } from '/@/renderer/store'; import { useCurrentServer } from '/@/renderer/store';
import { Checkbox } from '/@/shared/components/checkbox/checkbox'; import { Checkbox } from '/@/shared/components/checkbox/checkbox';
import { Divider } from '/@/shared/components/divider/divider'; import { Divider } from '/@/shared/components/divider/divider';
@@ -101,16 +101,6 @@ export const ShuffleAllContextModal = () => {
closeAllModals(); closeAllModals();
}; };
const isLoadingNext =
isFetching &&
(fetchTypeRef.current === Play.NEXT || fetchTypeRef.current === Play.NEXT_SHUFFLE);
const isLoadingLast =
isFetching &&
(fetchTypeRef.current === Play.LAST || fetchTypeRef.current === Play.LAST_SHUFFLE);
const isLoadingNow = isFetching && fetchTypeRef.current === Play.NOW;
return ( return (
<Stack gap="md"> <Stack gap="md">
<NumberInput <NumberInput
@@ -166,60 +156,10 @@ export const ShuffleAllContextModal = () => {
/> />
)} )}
<Divider /> <Divider />
<Group align="center" gap="md" justify="center" w="100%"> <PlayButtonGroup
<PlayButton loading={(isFetching && fetchTypeRef.current) || false}
icon="mediaPlayNext" onPlay={handlePlay}
isSecondary
loading={isLoadingNext}
onClick={() => handlePlay(Play.NEXT)}
onLongPress={() => handlePlay(Play.NEXT_SHUFFLE)}
/> />
<PlayButton
fill
loading={isLoadingNow}
onClick={() => handlePlay(Play.NOW)}
onLongPress={() => handlePlay(Play.SHUFFLE)}
/>
<PlayButton
icon="mediaPlayLast"
isSecondary
loading={isLoadingLast}
onClick={() => handlePlay(Play.LAST)}
onLongPress={() => handlePlay(Play.LAST_SHUFFLE)}
/>
</Group>
{/* <Group grow>
<Button
disabled={!limit || isFetching}
leftSection={<Icon icon="mediaPlayNext" />}
loading={fetchTypeRef.current === Play.NEXT && isFetching}
onClick={() => handlePlay(Play.NEXT)}
type="submit"
variant="default"
>
{t('player.addNext', { postProcess: 'sentenceCase' })}
</Button>
<Button
disabled={!limit || isFetching}
leftSection={<Icon icon="mediaPlayLast" />}
loading={fetchTypeRef.current === Play.LAST && isFetching}
onClick={() => handlePlay(Play.LAST)}
type="submit"
variant="default"
>
{t('player.addLast', { postProcess: 'sentenceCase' })}
</Button>
</Group>
<Button
disabled={!limit || isFetching}
leftSection={<Icon icon="mediaPlay" />}
loading={fetchTypeRef.current === Play.NOW && isFetching}
onClick={() => handlePlay(Play.NOW)}
type="submit"
variant="filled"
>
{t('player.play', { postProcess: 'sentenceCase' })}
</Button> */}
</Stack> </Stack>
); );
}; };
@@ -1,6 +1,5 @@
import { closeAllModals, openModal } from '@mantine/modals'; import { closeAllModals, openModal } from '@mantine/modals';
import { CSSProperties, memo, ReactNode, useCallback } from 'react'; import { CSSProperties, memo, ReactNode, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import styles from './library-header-bar.module.css'; import styles from './library-header-bar.module.css';
@@ -53,7 +52,6 @@ const HeaderPlayButton = ({
...props ...props
}: HeaderPlayButtonProps) => { }: HeaderPlayButtonProps) => {
const serverId = useCurrentServerId(); const serverId = useCurrentServerId();
const { t } = useTranslation();
const player = usePlayer(); const player = usePlayer();
const handlePlay = useCallback( const handlePlay = useCallback(
@@ -77,9 +75,16 @@ const HeaderPlayButton = ({
openModal({ openModal({
children: <PlayButtonGroup onPlay={handlePlay} />, children: <PlayButtonGroup onPlay={handlePlay} />,
size: 'xs', size: 'xs',
title: t('player.play', { postProcess: 'titleCase' }), styles: {
body: {
padding: 'var(--theme-spacing-md)',
},
header: {
display: 'none',
},
},
}); });
}, [serverId, handlePlay, t]); }, [serverId, handlePlay]);
return ( return (
<div className={styles.playButtonContainer}> <div className={styles.playButtonContainer}>
@@ -1,58 +1,56 @@
import i18n from '/@/i18n/i18n'; import i18n from '/@/i18n/i18n';
import { ActionIcon } from '/@/shared/components/action-icon/action-icon'; import { PlayButton } from '/@/renderer/features/shared/components/play-button';
import { Group } from '/@/shared/components/group/group'; 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 { Play } from '/@/shared/types/types'; import { Play } from '/@/shared/types/types';
const playButtons: { icon: AppIconSelection; label: string; type: Play }[] = [ const playButtons: { icon: AppIconSelection; label: string; secondary: boolean; type: Play }[] = [
{
icon: 'mediaPlay',
label: i18n.t('player.play', { postProcess: 'sentenceCase' }),
type: Play.NOW,
},
{ {
icon: 'mediaPlayNext', icon: 'mediaPlayNext',
label: i18n.t('player.addNext', { postProcess: 'sentenceCase' }), label: i18n.t('player.addNext', { postProcess: 'sentenceCase' }),
secondary: true,
type: Play.NEXT, type: Play.NEXT,
}, },
{
icon: 'mediaPlay',
label: i18n.t('player.play', { postProcess: 'sentenceCase' }),
secondary: false,
type: Play.NOW,
},
{ {
icon: 'mediaPlayLast', icon: 'mediaPlayLast',
label: i18n.t('player.addLast', { postProcess: 'sentenceCase' }), label: i18n.t('player.addLast', { postProcess: 'sentenceCase' }),
secondary: true,
type: Play.LAST, type: Play.LAST,
}, },
{
icon: 'mediaShuffle',
label: i18n.t('player.shuffle', { postProcess: 'sentenceCase' }),
type: Play.SHUFFLE,
},
]; ];
const LONG_PRESS_PLAY_BEHAVIOR = {
[Play.LAST]: Play.LAST_SHUFFLE,
[Play.NEXT]: Play.NEXT_SHUFFLE,
[Play.NOW]: Play.SHUFFLE,
};
interface PlayButtonGroupProps { interface PlayButtonGroupProps {
loading?: boolean | Play;
onPlay: (type: Play) => void; onPlay: (type: Play) => void;
} }
export const PlayButtonGroup = ({ onPlay }: PlayButtonGroupProps) => { export const PlayButtonGroup = ({ loading, onPlay }: PlayButtonGroupProps) => {
return ( return (
<Group grow> <Group align="center" gap="md" justify="center">
{playButtons.map((button) => ( {playButtons.map((button) => (
<ActionIcon <Tooltip key={button.type} label={button.label} openDelay={2000}>
<PlayButton
fill={button.type === Play.NOW}
icon={button.icon} icon={button.icon}
iconProps={{ isSecondary={button.secondary}
size: 'xl', loading={loading === button.type}
}}
key={button.type}
onClick={() => onPlay(button.type)} onClick={() => onPlay(button.type)}
styles={{ onLongPress={() => onPlay(LONG_PRESS_PLAY_BEHAVIOR[button.type])}
root: {
padding: 'var(--mantine-spacing-lg)',
},
}}
tooltip={{
label: button.label,
openDelay: 0,
}}
variant="default"
/> />
</Tooltip>
))} ))}
</Group> </Group>
); );
@@ -1,6 +1,6 @@
import clsx from 'clsx'; import clsx from 'clsx';
import { t } from 'i18next'; import { t } from 'i18next';
import { memo } from 'react'; import { forwardRef, memo } from 'react';
import styles from './play-button.module.css'; import styles from './play-button.module.css';
@@ -85,8 +85,9 @@ interface PlayButtonProps {
onLongPress?: (e: React.MouseEvent<HTMLButtonElement>) => void; onLongPress?: (e: React.MouseEvent<HTMLButtonElement>) => void;
} }
export const PlayButton = memo( const PlayButtonBase = forwardRef<HTMLButtonElement, PlayButtonProps>(
({ (
{
classNames, classNames,
fill, fill,
icon = 'mediaPlay', icon = 'mediaPlay',
@@ -94,7 +95,9 @@ export const PlayButton = memo(
loading, loading,
onClick, onClick,
onLongPress, onLongPress,
}: PlayButtonProps) => { }: PlayButtonProps,
ref,
) => {
const clickHandlers = usePlayButtonClick({ const clickHandlers = usePlayButtonClick({
loading, loading,
onClick, onClick,
@@ -107,6 +110,7 @@ export const PlayButton = memo(
[styles.fill]: fill, [styles.fill]: fill,
[styles.secondary]: isSecondary, [styles.secondary]: isSecondary,
})} })}
ref={ref}
{...clickHandlers.handlers} {...clickHandlers.handlers}
{...clickHandlers.props} {...clickHandlers.props}
> >
@@ -116,4 +120,6 @@ export const PlayButton = memo(
}, },
); );
export const PlayButton = memo(PlayButtonBase);
PlayButton.displayName = 'PlayButton'; PlayButton.displayName = 'PlayButton';