feishin/src/renderer/features/player/components/player-button.tsx
Malachi Soord 8a3edb71df
feat: add semantic selectors for now-playing media (#1138)
* feat: add semantic selectors for now-playing media

This change adds unique class names to the elements that display the currently playing media information. This makes it easier for extension developers to parse the DOM and understand what media is playing.

The following classes have been added:
- `media-player`: The main player container.
- `player-cover-art`: The cover art of the playing track.
- `song-title`: The title of the playing track.
- `song-artist`: The artist of the playing track.
- `song-album`: The album of the playing track.
- `player-state-playing`/`player-state-paused`: The state of the player.
- `elapsed-time`: The elapsed time of the playing track.
- `total-duration`: The total duration of the playing track.

---------

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
2025-09-23 12:44:22 -07:00

91 lines
3 KiB
TypeScript

import clsx from 'clsx';
import { t } from 'i18next';
import { forwardRef, ReactNode } from 'react';
import styles from './player-button.module.css';
import { ActionIcon, ActionIconProps } from '/@/shared/components/action-icon/action-icon';
import { Tooltip, TooltipProps } from '/@/shared/components/tooltip/tooltip';
import { PlaybackSelectors } from '/@/shared/constants/playback-selectors';
interface PlayerButtonProps extends Omit<ActionIconProps, 'icon' | 'variant'> {
icon: ReactNode;
isActive?: boolean;
tooltip?: Omit<TooltipProps, 'children'>;
variant: 'main' | 'secondary' | 'tertiary';
}
export const PlayerButton = forwardRef<HTMLButtonElement, PlayerButtonProps>(
({ icon, isActive, tooltip, variant, ...rest }: PlayerButtonProps, ref) => {
if (tooltip) {
return (
<Tooltip {...tooltip}>
<ActionIcon
className={clsx({
[styles.active]: isActive,
})}
ref={ref}
{...rest}
onClick={(e) => {
e.stopPropagation();
rest.onClick?.(e);
}}
variant="subtle"
>
{icon}
</ActionIcon>
</Tooltip>
);
}
return (
<ActionIcon
className={clsx(styles.playerButton, styles[variant], {
[styles.active]: isActive,
})}
ref={ref}
{...rest}
onClick={(e) => {
e.stopPropagation();
rest.onClick?.(e);
}}
variant="subtle"
>
{icon}
</ActionIcon>
);
},
);
interface PlayButtonProps extends Omit<ActionIconProps, 'icon' | 'variant'> {
isPaused?: boolean;
}
export const PlayButton = forwardRef<HTMLButtonElement, PlayButtonProps>(
({ isPaused, onClick, ...props }: PlayButtonProps, ref) => {
const playerStateClass = isPaused
? PlaybackSelectors.playerStatePaused
: PlaybackSelectors.playerStatePlaying;
return (
<ActionIcon
className={clsx(styles.main, playerStateClass)}
icon={isPaused ? 'mediaPlay' : 'mediaPause'}
iconProps={{
size: 'lg',
}}
onClick={(e) => {
e.stopPropagation();
onClick?.(e);
}}
ref={ref}
tooltip={{
label: isPaused
? (t('player.play', { postProcess: 'sentenceCase' }) as string)
: (t('player.pause', { postProcess: 'sentenceCase' }) as string),
}}
{...props}
/>
);
},
);