diff --git a/packages/react/src/presets/video/minimal-skin.css b/packages/react/src/presets/video/minimal-skin.css index bf7e4140..97d5faf9 100644 --- a/packages/react/src/presets/video/minimal-skin.css +++ b/packages/react/src/presets/video/minimal-skin.css @@ -346,7 +346,9 @@ /* Icon button variant */ .media-minimal-skin .media-button--icon { display: grid; - padding: 0.625rem; + width: 2.375rem; + padding: 0; + aspect-ratio: 1; background: transparent; color: oklch(1 0 0); } @@ -367,6 +369,15 @@ } } +/* Playback rate button */ +.media-minimal-skin .media-button--playback-rate { + padding: 0; +} +.media-minimal-skin .media-button--playback-rate span { + width: 4ch; + font-variant-numeric: tabular-nums; +} + /* ========================================================================== Icons ========================================================================== */ @@ -375,6 +386,7 @@ position: relative; } .media-minimal-skin .media-icon { + flex-shrink: 0; grid-area: 1 / 1; width: 18px; height: 18px; diff --git a/packages/react/src/presets/video/minimal-skin.tailwind.tsx b/packages/react/src/presets/video/minimal-skin.tailwind.tsx index 24fe3753..9608c618 100644 --- a/packages/react/src/presets/video/minimal-skin.tailwind.tsx +++ b/packages/react/src/presets/video/minimal-skin.tailwind.tsx @@ -25,6 +25,7 @@ import { FullscreenButton } from '@/ui/fullscreen-button'; import { MuteButton } from '@/ui/mute-button'; import { PiPButton } from '@/ui/pip-button'; import { PlayButton } from '@/ui/play-button'; +import { PlaybackRateButton } from '@/ui/playback-rate-button'; import { SeekButton } from '@/ui/seek-button'; import { Time } from '@/ui/time'; import type { MinimalVideoSkinProps } from './minimal-skin'; @@ -53,14 +54,14 @@ const Button = forwardRef & { varian type="button" className={cn( // Shared - 'shrink-0 border-none cursor-pointer select-none', + 'items-center justify-center shrink-0 border-none cursor-pointer select-none', 'outline-2 outline-transparent -outline-offset-2', 'transition-[background-color,color,outline-offset] duration-150 ease-out', 'disabled:cursor-not-allowed disabled:opacity-50 disabled:grayscale', // Variant variant === 'icon' ? cn( - 'grid p-2.5 bg-transparent rounded-md', + 'grid w-[2.375rem] aspect-square bg-transparent rounded-md', 'text-white', 'hover:text-white/80 hover:no-underline', 'focus-visible:text-white/80', @@ -68,7 +69,7 @@ const Button = forwardRef & { varian 'aria-expanded:text-white/80' ) : cn( - 'flex items-center justify-center py-2 px-4 bg-white rounded-lg', + 'flex py-2 px-4 bg-white rounded-lg', 'text-black font-medium', 'focus-visible:outline-white focus-visible:outline-offset-2' ), @@ -297,6 +298,14 @@ export function MinimalVideoSkinTailwind(props: MinimalVideoSkinProps): ReactNod + ( + + )} + /> + ( + )} + /> + ( + )} + /> + ( + )} + /> + ( + * )} + * /> + * ``` + */ +export const PlaybackRateButton = forwardRef(function PlaybackRateButton( + componentProps: PlaybackRateButtonProps, + forwardedRef: ForwardedRef +) { + const { render, className, style, label, disabled, ...elementProps } = componentProps; + + // FIXME: Replace with actual playback rate state + const [rate, setRate] = useState(1); + + const { getButtonProps, buttonRef } = useButton({ + displayName: 'PlaybackRateButton', + onActivate: () => + setRate((currentRate) => { + // FIXME: Replace with actual toggle logic + const rates: PlaybackRateButtonState['rate'][] = [...RATES]; + const currentIndex = rates.indexOf(currentRate); + const nextIndex = (currentIndex + 1) % rates.length; + return rates[nextIndex] ?? 1; + }), + isDisabled: () => disabled ?? false, + }); + + if (!DEBUG) return null; + + return renderElement( + 'button', + { render, className, style }, + { + state: { rate }, // FIXME: Replace with actual toggle logic + stateAttrMap: PlaybackRateButtonDataAttrs, + ref: [forwardedRef, buttonRef], + props: [elementProps, getButtonProps()], + } + ); +}); + +export namespace PlaybackRateButton { + export type Props = PlaybackRateButtonProps; + export type State = PlaybackRateButtonState; +}