mirror of
https://github.com/zoriya/v10.git
synced 2026-08-14 01:49:30 +00:00
162 lines
5.5 KiB
TypeScript
162 lines
5.5 KiB
TypeScript
import {
|
|
button,
|
|
buttonGroup,
|
|
container,
|
|
controls,
|
|
error,
|
|
icon,
|
|
muteIcon,
|
|
playIcon,
|
|
popup,
|
|
slider,
|
|
} from '@videojs/skins/default/tailwind/audio.tailwind';
|
|
import { cn } from '@videojs/utils/style';
|
|
import { type ComponentProps, forwardRef, type ReactNode } from 'react';
|
|
import { PauseIcon, PlayIcon, RestartIcon, VolumeHighIcon, VolumeLowIcon, VolumeOffIcon } from '@/icons';
|
|
import { Container, usePlayer } from '@/player/context';
|
|
import { ErrorDialog } from '@/ui/error-dialog';
|
|
import { Hotkey } from '@/ui/hotkey';
|
|
import { LiveButton } from '@/ui/live-button';
|
|
import { MuteButton } from '@/ui/mute-button';
|
|
import { PlayButton } from '@/ui/play-button';
|
|
import { Popover } from '@/ui/popover';
|
|
import { StatusAnnouncer } from '@/ui/status-announcer';
|
|
import { Tooltip } from '@/ui/tooltip';
|
|
import { VolumeSlider } from '@/ui/volume-slider';
|
|
import type { LiveAudioSkinProps } from './skin';
|
|
|
|
const Button = forwardRef<HTMLButtonElement, ComponentProps<'button'>>(function Button({ className, ...props }, ref) {
|
|
return (
|
|
<button ref={ref} type="button" className={cn(button.base, button.subtle, button.icon, className)} {...props} />
|
|
);
|
|
});
|
|
|
|
const SliderRoot = forwardRef<HTMLDivElement, ComponentProps<'div'>>(function SliderRoot({ className, ...props }, ref) {
|
|
return <div ref={ref} className={cn(slider.root, className)} {...props} />;
|
|
});
|
|
|
|
const SliderTrack = forwardRef<HTMLDivElement, ComponentProps<'div'>>(function SliderTrack(
|
|
{ className, ...props },
|
|
ref
|
|
) {
|
|
return <div ref={ref} className={cn(slider.track, className)} {...props} />;
|
|
});
|
|
|
|
const SliderFill = forwardRef<HTMLDivElement, ComponentProps<'div'> & { type?: 'fill' | 'buffer' }>(function SliderFill(
|
|
{ type = 'fill', className, ...props },
|
|
ref
|
|
) {
|
|
return (
|
|
<div
|
|
ref={ref}
|
|
className={cn(slider.fill.base, type === 'fill' ? slider.fill.fill : slider.fill.buffer, className)}
|
|
{...props}
|
|
/>
|
|
);
|
|
});
|
|
|
|
const SliderThumb = forwardRef<HTMLDivElement, ComponentProps<'div'> & { persistent?: boolean }>(function SliderThumb(
|
|
{ persistent, className, ...props },
|
|
ref
|
|
) {
|
|
return (
|
|
<div
|
|
ref={ref}
|
|
className={cn(slider.thumb.base, persistent ? slider.thumb.persistent : slider.thumb.interactive, className)}
|
|
{...props}
|
|
/>
|
|
);
|
|
});
|
|
|
|
function VolumePopover(): ReactNode {
|
|
const volumeUnsupported = usePlayer((s) => s.volumeAvailability === 'unsupported');
|
|
|
|
const muteButton = (
|
|
<MuteButton className={muteIcon.button} render={<Button />}>
|
|
<VolumeOffIcon className={cn(icon, muteIcon.volumeOff)} />
|
|
<VolumeLowIcon className={cn(icon, muteIcon.volumeLow)} />
|
|
<VolumeHighIcon className={cn(icon, muteIcon.volumeHigh)} />
|
|
</MuteButton>
|
|
);
|
|
|
|
if (volumeUnsupported) return muteButton;
|
|
|
|
return (
|
|
<Popover.Root openOnHover delay={200} closeDelay={100} side="top" boundary="viewport">
|
|
<Popover.Trigger render={muteButton} />
|
|
<Popover.Popup className={cn(popup.popover, popup.volume)}>
|
|
<VolumeSlider.Root orientation="vertical" thumbAlignment="edge" render={<SliderRoot />}>
|
|
<VolumeSlider.Track render={<SliderTrack />}>
|
|
<VolumeSlider.Fill render={<SliderFill />} />
|
|
</VolumeSlider.Track>
|
|
<VolumeSlider.Thumb render={(props) => <SliderThumb persistent {...props} />} />
|
|
</VolumeSlider.Root>
|
|
</Popover.Popup>
|
|
</Popover.Root>
|
|
);
|
|
}
|
|
|
|
export function LiveAudioSkinTailwind(props: LiveAudioSkinProps): ReactNode {
|
|
const { children, className, ...rest } = props;
|
|
|
|
return (
|
|
<Container className={cn(container, className)} {...rest}>
|
|
{children}
|
|
|
|
<ErrorDialog.Root>
|
|
<ErrorDialog.Popup className={error.root}>
|
|
<div className={error.dialog}>
|
|
<div className={error.content}>
|
|
<ErrorDialog.Title className={error.title}>Something went wrong.</ErrorDialog.Title>
|
|
<ErrorDialog.Description className={error.description} />
|
|
</div>
|
|
<div className={error.actions}>
|
|
<ErrorDialog.Close className={cn(button.base, button.subtle)}>OK</ErrorDialog.Close>
|
|
</div>
|
|
</div>
|
|
</ErrorDialog.Popup>
|
|
</ErrorDialog.Root>
|
|
|
|
<div className={controls}>
|
|
<Tooltip.Provider>
|
|
<div className={buttonGroup}>
|
|
<Tooltip.Root side="top" boundary="viewport">
|
|
<Tooltip.Trigger
|
|
render={
|
|
<PlayButton className={playIcon.button} render={<Button />}>
|
|
<RestartIcon className={cn(icon, playIcon.restart)} />
|
|
<PlayIcon className={cn(icon, playIcon.play)} />
|
|
<PauseIcon className={cn(icon, playIcon.pause)} />
|
|
</PlayButton>
|
|
}
|
|
/>
|
|
<Tooltip.Popup className={cn(popup.tooltip)}>
|
|
<Tooltip.Label />
|
|
<Tooltip.Shortcut className={popup.tooltipShortcut} />
|
|
</Tooltip.Popup>
|
|
</Tooltip.Root>
|
|
|
|
<LiveButton className={cn(button.base, button.subtle, button.live)} />
|
|
</div>
|
|
|
|
<div className="grow" aria-hidden="true" />
|
|
|
|
<div className={buttonGroup}>
|
|
<VolumePopover />
|
|
</div>
|
|
</Tooltip.Provider>
|
|
</div>
|
|
|
|
{/* Hotkeys */}
|
|
<Hotkey keys="Space" action="togglePaused" />
|
|
<Hotkey keys="k" action="togglePaused" />
|
|
<Hotkey keys="m" action="toggleMuted" />
|
|
<Hotkey keys="ArrowUp" action="volumeStep" value={0.05} />
|
|
<Hotkey keys="ArrowDown" action="volumeStep" value={-0.05} />
|
|
|
|
{/* Input Feedback */}
|
|
<StatusAnnouncer />
|
|
</Container>
|
|
);
|
|
}
|