Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/bright-parts-compose.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@bazza-ui/react": patch
---

Compose video player component part event handlers so consumers can opt out of internal behavior with preventBaseUIHandler.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,5 @@ tmp/
# Repomix
repomix-output.txt
.osgrep

base-ui/
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
'use client'

import * as React from 'react'
import type { WithPreventableBaseHandlers } from '../../../utils/types.js'
import { useVideoPlayerContext } from '../../contexts/video-player-context.js'
import type { RenderProp } from '../../types.js'
import { mergeElementProps } from '../../utils/merge-element-props.js'
import { CaptionsButtonDataAttributes } from './captions-button.data-attributes.js'

// ============================================================================
// CaptionsButton Props
// ============================================================================

export interface CaptionsButtonProps
extends React.ComponentPropsWithRef<'button'> {
extends WithPreventableBaseHandlers<React.ComponentPropsWithRef<'button'>> {
render?: RenderProp<CaptionsButtonRenderProps, CaptionsButtonState>
}

export interface CaptionsButtonRenderProps {
ref: React.Ref<HTMLButtonElement>
type: 'button'
type: React.ButtonHTMLAttributes<HTMLButtonElement>['type']
'aria-label': string
'aria-pressed': boolean
disabled: boolean
Expand All @@ -39,7 +41,7 @@ export const CaptionsButton = React.forwardRef<
HTMLButtonElement,
CaptionsButtonProps
>(function CaptionsButton(props, forwardedRef) {
const { render, onClick, ...buttonProps } = props
const { render, ...buttonProps } = props
const context = useVideoPlayerContext('CaptionsButton')

const available = context.registeredTracks.length > 0
Expand All @@ -51,10 +53,16 @@ export const CaptionsButton = React.forwardRef<
trackCount: context.registeredTracks.length,
}

const handleClick = React.useCallback(
(event: React.MouseEvent<HTMLButtonElement>) => {
onClick?.(event)
if (!event.defaultPrevented) {
const renderProps = mergeElementProps(
{
ref: forwardedRef,
type: 'button',
'aria-label': active ? 'Disable captions' : 'Enable captions',
'aria-pressed': active,
disabled: !available,
[CaptionsButtonDataAttributes.active]: active || undefined,
[CaptionsButtonDataAttributes.available]: available || undefined,
onClick() {
if (active) {
// Turn off captions
context.setTextTrack(null)
Expand All @@ -65,22 +73,11 @@ export const CaptionsButton = React.forwardRef<
context.setTextTrack(firstTrack.textTrack)
}
}
}
},
},
[onClick, context, active],
buttonProps,
)

const renderProps: CaptionsButtonRenderProps = {
ref: forwardedRef,
type: 'button',
'aria-label': active ? 'Disable captions' : 'Enable captions',
'aria-pressed': active,
disabled: !available,
[CaptionsButtonDataAttributes.active]: active || undefined,
[CaptionsButtonDataAttributes.available]: available || undefined,
onClick: handleClick,
}

if (render) {
return render(renderProps, state)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
'use client'

import * as React from 'react'
import type { WithPreventableBaseHandlers } from '../../../utils/types.js'
import { useVideoPlayerContext } from '../../contexts/video-player-context.js'
import type { RenderProp, TrackInfo } from '../../types.js'
import { mergeElementProps } from '../../utils/merge-element-props.js'
import {
CaptionsMenuDataAttributes,
CaptionsMenuItemDataAttributes,
Expand Down Expand Up @@ -77,42 +79,34 @@ export const CaptionsMenu = React.forwardRef<HTMLDivElement, CaptionsMenuProps>(
// ============================================================================

export interface CaptionsMenuItemProps
extends React.ComponentPropsWithRef<'button'> {
extends WithPreventableBaseHandlers<React.ComponentPropsWithRef<'button'>> {
track: TextTrack | null
}

export const CaptionsMenuItem = React.forwardRef<
HTMLButtonElement,
CaptionsMenuItemProps
>(function CaptionsMenuItem(props, forwardedRef) {
const { track, onClick, children, ...buttonProps } = props
const { track, children, ...buttonProps } = props
const context = useVideoPlayerContext('CaptionsMenuItem')

const isActive = context.activeTextTrack === track

const handleClick = React.useCallback(
(event: React.MouseEvent<HTMLButtonElement>) => {
onClick?.(event)
if (!event.defaultPrevented) {
const elementProps = mergeElementProps(
{
ref: forwardedRef,
type: 'button',
role: 'menuitemradio',
'aria-checked': isActive,
[CaptionsMenuItemDataAttributes.active]: isActive || undefined,
onClick() {
context.setTextTrack(track)
}
},
},
[onClick, context, track],
buttonProps,
)

return (
<button
ref={forwardedRef}
type="button"
role="menuitemradio"
aria-checked={isActive}
{...{ [CaptionsMenuItemDataAttributes.active]: isActive || undefined }}
{...buttonProps}
onClick={handleClick}
>
{children}
</button>
)
return <button {...elementProps}>{children}</button>
})

// ============================================================================
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,28 @@
'use client'

import * as React from 'react'
import type { WithPreventableBaseHandlers } from '../../../utils/types.js'
import { useVideoPlayerContext } from '../../contexts/video-player-context.js'
import type { RenderProp } from '../../types.js'
import { mergeElementProps } from '../../utils/merge-element-props.js'
import { FullscreenButtonDataAttributes } from './fullscreen-button.data-attributes.js'

// ============================================================================
// FullscreenButton Props
// ============================================================================

export interface FullscreenButtonProps
extends Omit<React.ComponentPropsWithRef<'button'>, 'children'> {
extends Omit<
WithPreventableBaseHandlers<React.ComponentPropsWithRef<'button'>>,
'children'
> {
render?: RenderProp<FullscreenButtonRenderProps, FullscreenButtonState>
children?: React.ReactNode
}

export interface FullscreenButtonRenderProps {
ref: React.Ref<HTMLButtonElement>
type: 'button'
type: React.ButtonHTMLAttributes<HTMLButtonElement>['type']
'aria-label': string
'aria-pressed': boolean
disabled: boolean
Expand All @@ -39,7 +44,7 @@ export const FullscreenButton = React.forwardRef<
HTMLButtonElement,
FullscreenButtonProps
>(function FullscreenButton(props, forwardedRef) {
const { render, onClick, children, ...buttonProps } = props
const { render, children, ...buttonProps } = props
const context = useVideoPlayerContext('FullscreenButton')

// Defer to after hydration to avoid mismatch
Expand All @@ -48,32 +53,27 @@ export const FullscreenButton = React.forwardRef<
setSupported('fullscreenEnabled' in document && document.fullscreenEnabled)
}, [])

const handleClick = React.useCallback(
(event: React.MouseEvent<HTMLButtonElement>) => {
onClick?.(event)
if (!event.defaultPrevented) {
context.toggleFullscreen()
}
},
[onClick, context],
)

const state: FullscreenButtonState = {
fullscreen: context.fullscreen,
supported,
}

const renderProps: FullscreenButtonRenderProps = {
ref: forwardedRef,
type: 'button',
'aria-label': context.fullscreen ? 'Exit fullscreen' : 'Enter fullscreen',
'aria-pressed': context.fullscreen,
disabled: !supported,
[FullscreenButtonDataAttributes.fullscreen]:
context.fullscreen || undefined,
[FullscreenButtonDataAttributes.supported]: supported || undefined,
onClick: handleClick,
}
const renderProps = mergeElementProps(
{
ref: forwardedRef,
type: 'button',
'aria-label': context.fullscreen ? 'Exit fullscreen' : 'Enter fullscreen',
'aria-pressed': context.fullscreen,
disabled: !supported,
[FullscreenButtonDataAttributes.fullscreen]:
context.fullscreen || undefined,
[FullscreenButtonDataAttributes.supported]: supported || undefined,
onClick() {
context.toggleFullscreen()
},
},
buttonProps,
)

if (render) {
return render(renderProps, state)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
'use client'

import * as React from 'react'
import type { WithPreventableBaseHandlers } from '../../../utils/types.js'
import { useVideoPlayerContext } from '../../contexts/video-player-context.js'
import type { RenderProp } from '../../types.js'
import { mergeElementProps } from '../../utils/merge-element-props.js'
import { MuteButtonDataAttributes } from './mute-button.data-attributes.js'

// ============================================================================
// MuteButton Props
// ============================================================================

export interface MuteButtonProps extends React.ComponentPropsWithRef<'button'> {
export interface MuteButtonProps
extends WithPreventableBaseHandlers<React.ComponentPropsWithRef<'button'>> {
render?: RenderProp<MuteButtonRenderProps, MuteButtonState>
}

export interface MuteButtonRenderProps {
ref: React.Ref<HTMLButtonElement>
type: 'button'
type: React.ButtonHTMLAttributes<HTMLButtonElement>['type']
'aria-label': string
'aria-pressed': boolean
[MuteButtonDataAttributes.muted]?: boolean
Expand All @@ -37,43 +40,38 @@ export interface MuteButtonState {

export const MuteButton = React.forwardRef<HTMLButtonElement, MuteButtonProps>(
function MuteButton(props, forwardedRef) {
const { render, onClick, ...buttonProps } = props
const { render, ...buttonProps } = props
const context = useVideoPlayerContext('MuteButton')

const state: MuteButtonState = {
muted: context.muted,
volume: context.volume,
}

const handleClick = React.useCallback(
(event: React.MouseEvent<HTMLButtonElement>) => {
onClick?.(event)
if (!event.defaultPrevented) {
context.toggleMute()
}
},
[onClick, context],
)

const volume = context.volume
const muted = context.muted
const volumeOff = volume === 0 || muted
const volumeOn = volume > 0 && !muted
const volumeLow = !muted && volume > 0 && volume < 0.5
const volumeHigh = !muted && volume >= 0.5

const renderProps: MuteButtonRenderProps = {
ref: forwardedRef,
type: 'button',
'aria-label': context.muted ? 'Unmute' : 'Mute',
'aria-pressed': context.muted,
[MuteButtonDataAttributes.muted]: muted || undefined,
[MuteButtonDataAttributes.volumeOff]: volumeOff || undefined,
[MuteButtonDataAttributes.volumeOn]: volumeOn || undefined,
[MuteButtonDataAttributes.volumeLow]: volumeLow || undefined,
[MuteButtonDataAttributes.volumeHigh]: volumeHigh || undefined,
onClick: handleClick,
}
const renderProps = mergeElementProps(
{
ref: forwardedRef,
type: 'button',
'aria-label': context.muted ? 'Unmute' : 'Mute',
'aria-pressed': context.muted,
[MuteButtonDataAttributes.muted]: muted || undefined,
[MuteButtonDataAttributes.volumeOff]: volumeOff || undefined,
[MuteButtonDataAttributes.volumeOn]: volumeOn || undefined,
[MuteButtonDataAttributes.volumeLow]: volumeLow || undefined,
[MuteButtonDataAttributes.volumeHigh]: volumeHigh || undefined,
onClick() {
context.toggleMute()
},
},
buttonProps,
)

if (render) {
return render(renderProps, state)
Expand Down
Loading
Loading