Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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/media-playlist-narrow-spacing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@primer/react-brand': patch
---

Adjusted the spacing in the `MediaPlaylist` component on narrow viewports
5 changes: 5 additions & 0 deletions .changeset/pillar-icon-radius.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@primer/react-brand': patch
---

Increased the `border-radius` of icons in the `Pillar` component to `12px`
5 changes: 5 additions & 0 deletions .changeset/statistic-color-consistency.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@primer/react-brand': patch
---

Updated the `Statistic` social proof example to use consistent text colors
5 changes: 5 additions & 0 deletions .changeset/tiles-tablet-columns.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@primer/react-brand': patch
---

Adjusted the `Tiles` grid to use three columns on tablet viewports
5 changes: 5 additions & 0 deletions .changeset/video-player-autoplay-controls.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@primer/react-brand': patch
---

Added autoplay controls support to the `VideoPlayer` component
42 changes: 27 additions & 15 deletions apps/next-docs/content/components/VideoPlayer/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,17 @@ The audio description can be provided as part of the existing soundtrack, or via
</VideoPlayer>
```

## Autoplay with bottom controls

Autoplaying videos hide the large center play button overlay. Use `controlsPosition="bottom"` to place the controls bar at the bottom of the video.

```jsx live
<VideoPlayer title="GitHub media player" autoPlay muted loop controlsPosition="bottom">
<VideoPlayer.Source src="/example.mp4" type="video/mp4" />
<VideoPlayer.Track src="/example.vtt" default />
</VideoPlayer>
```

## Minimal

```jsx live
Expand Down Expand Up @@ -200,21 +211,22 @@ Below is a detailed description of each property and method available in the `us

`VideoPlayer` provides a React alternative to the native HTML `<video />`.

| Name | Type | Default | Required | Description |
| :----------------------- | :------------------------------------- | :-----: | :------: | :-------------------------------------------------------------- |
| `children` | `React.ReactNode`, `React.ReactNode[]` | | `true` | Takes `<VideoPlayer.Source>` and `<VideoPlayer.Track>` elements |
| `title` | `string` | | `true` | Sets the title of the video |
| `visuallyHiddenTitle` | `boolean` | | `false` | Visually hides the title of the video in the upper-left corner |
| `showBranding` | `boolean` | `true` | `false` | Displays GitHub branding next to video title |
| `poster` | `string` | | `false` | Sets an image as the starting frame for the video element |
| `className` | `string` | | `false` | Sets a custom class on the `<video>` element |
| `showControlsWhenPaused` | `boolean` | `true` | `false` | Show the video controls when paused |
| `showPlayPauseButton` | `boolean` | `true` | `false` | Show the play button control |
| `showSeekControl` | `boolean` | `true` | `false` | Show the seek control |
| `showCCButton` | `boolean` | `true` | `false` | Show the closed caption toggle control |
| `showMuteButton` | `boolean` | `true` | `false` | Show the mute button control |
| `showVolumeControl` | `boolean` | `true` | `false` | Show the volume control |
| `showFullScreenButton` | `boolean` | `true` | `false` | Show the full screen button control |
| Name | Type | Default | Required | Description |
| :----------------------- | :------------------------------------- | :------: | :------: | :-------------------------------------------------------------- |
| `children` | `React.ReactNode`, `React.ReactNode[]` | | `true` | Takes `<VideoPlayer.Source>` and `<VideoPlayer.Track>` elements |
| `title` | `string` | | `true` | Sets the title of the video |
| `visuallyHiddenTitle` | `boolean` | | `false` | Visually hides the title of the video in the upper-left corner |
| `showBranding` | `boolean` | `true` | `false` | Displays GitHub branding next to video title |
| `poster` | `string` | | `false` | Sets an image as the starting frame for the video element |
| `className` | `string` | | `false` | Sets a custom class on the `<video>` element |
| `controlsPosition` | `'inline' \| 'bottom'` | `inline` | `false` | Sets the controls bar below the video or at the bottom |
| `showControlsWhenPaused` | `boolean` | `true` | `false` | Show the video controls when paused |
| `showPlayPauseButton` | `boolean` | `true` | `false` | Show the play button control |
| `showSeekControl` | `boolean` | `true` | `false` | Show the seek control |
| `showCCButton` | `boolean` | `true` | `false` | Show the closed caption toggle control |
| `showMuteButton` | `boolean` | `true` | `false` | Show the mute button control |
| `showVolumeControl` | `boolean` | `true` | `false` | Show the volume control |
| `showFullScreenButton` | `boolean` | `true` | `false` | Show the full screen button control |

The component API supports all standard HTML attribute props, while providing some additional behavior as described above.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ const skipTestLookup = [
'components-statistic-features--animations', // animation only
'components-riverstoryscroll-features--video-narrow', // video makes this too flakey
'components-riverstoryscroll-features--video', // video makes this too flakey
'components-videoplayer-features--autoplay-with-bottom-controls', // autoplay video makes this too flakey
'components-hero-features-images-and-videos--with-native-block-end-default', // for being non-deterministic due to video buffering
'components-hero-features-images-and-videos--with-youtube-video-block-end-default', // for loading a remote video
'components-hero-features-images-and-videos--with-youtube-video-inline-end', // for loading a remote video
Expand Down
10 changes: 10 additions & 0 deletions packages/react/src/MediaPlaylist/MediaPlaylist.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
margin-inline: auto;
padding: 0;
}

.MediaPlaylist__panelContent :is(h1, h2, h3) + p {
margin-block-start: var(--base-size-12);
}
}

.MediaPlaylist__layout {
Expand Down Expand Up @@ -240,6 +244,12 @@
gap: var(--base-size-12);
}

@media screen and (max-width: 63.24rem) {
.MediaPlaylist__mediaPanels {
padding-block-end: var(--base-size-24);
}
}

@media screen and (min-width: 38.625rem) {
.MediaPlaylist {
border-inline: var(--brand-borderWidth-thin) solid var(--brand-color-border-muted);
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions packages/react/src/Pillar/Pillar.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@
margin-bottom: var(--base-size-48);
}

.Pillar__icon--with-background {
border-radius: var(--base-size-12);
}

.Pillar__icon--with-background > svg {
width: 100%;
height: 100%;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ export const SocialProof: Story = {
style={{flex: 1, display: 'flex', alignItems: 'center'}}
>
<Statistic>
<Statistic.Heading weight="normal" size="800" style={{color: 'var(--brand-color-accent-primary)'}}>
<Statistic.Heading weight="normal" size="800" style={{color: 'var(--brand-color-text-emphasized)'}}>
39%
</Statistic.Heading>
<Statistic.Description font="monospace" size="100">
Expand Down
6 changes: 6 additions & 0 deletions packages/react/src/Tiles/Tiles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@
}
}

@media screen and (min-width: 34rem) and (max-width: 47.99rem) {
.Tiles-grid {
--tiles-columns: 3;
}
}

@media screen and (min-width: 63.25rem) {
.Tiles--layout-default .Tiles-grid {
--tiles-columns: 6;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@ export const Minimal = () => (
</VideoPlayer>
)

export const AutoplayWithBottomControls = () => (
<VideoPlayer title="GitHub media player" autoPlay muted loop controlsPosition="bottom">
<VideoPlayer.Source src="./example.mp4" type="video/mp4" />
<VideoPlayer.Track src="./example.vtt" default />
</VideoPlayer>
)

const MyVideoPlayer = () => {
const {isPlaying, togglePlaying, seek} = useVideo()

Expand Down
12 changes: 12 additions & 0 deletions packages/react/src/VideoPlayer/VideoPlayer.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@
flex-direction: column;
}

.VideoPlayer__container--controlsBottom {
position: relative;
}

.VideoPlayer__overlayContainer {
flex: 1;
position: relative;
Expand Down Expand Up @@ -149,11 +153,19 @@
border-bottom-right-radius: var(--brand-borderRadius-medium);
border-bottom-left-radius: var(--brand-borderRadius-medium);
width: 100%;
background: var(--brand-videoPlayer-controls-bgColor);
padding: var(--base-size-12) var(--base-size-16);
pointer-events: all;
opacity: 1;
}

.VideoPlayer__controlsBar--bottom {
position: absolute;
bottom: 0;
left: 0;
z-index: 2;
}

.VideoPlayer__controlsBar__row1,
.VideoPlayer__controlsBar__row2 {
display: flex;
Expand Down
2 changes: 2 additions & 0 deletions packages/react/src/VideoPlayer/VideoPlayer.module.css.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ declare const styles: {
readonly "VideoPlayer__ccText": string;
readonly "VideoPlayer__closedCaption": string;
readonly "VideoPlayer__container": string;
readonly "VideoPlayer__container--controlsBottom": string;
readonly "VideoPlayer__container--fullscreen": string;
readonly "VideoPlayer__container--small": string;
readonly "VideoPlayer__controlTextColor": string;
readonly "VideoPlayer__controls": string;
readonly "VideoPlayer__controlsBar": string;
readonly "VideoPlayer__controlsBar--bottom": string;
readonly "VideoPlayer__controlsBar__row1": string;
readonly "VideoPlayer__controlsBar__row2": string;
readonly "VideoPlayer__iconControl": string;
Expand Down
9 changes: 9 additions & 0 deletions packages/react/src/VideoPlayer/VideoPlayer.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ const meta = {
title: 'GitHub media player',
visuallyHiddenTitle: false,
showBranding: true,
controlsPosition: 'inline',
showControlsWhenPaused: true,
showPlayPauseButton: true,
showSeekControl: true,
showCCButton: true,
Expand All @@ -33,6 +35,13 @@ const meta = {
showBranding: {
type: 'boolean',
},
controlsPosition: {
control: 'select',
options: ['inline', 'bottom'],
},
showControlsWhenPaused: {
type: 'boolean',
},
showPlayPauseButton: {
type: 'boolean',
},
Expand Down
55 changes: 55 additions & 0 deletions packages/react/src/VideoPlayer/VideoPlayer.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,61 @@ describe('VideoPlayer', () => {
expect(getByRole('button', {name: 'Play video'})).toBeInTheDocument()
})

it('does not render controls when paused and showControlsWhenPaused is false', () => {
const {queryByRole} = render(
<VideoPlayer poster="/example-poster.jpg" title="test video" showControlsWhenPaused={false}>
<VideoPlayer.Source src="/example.mp4" />
<VideoPlayer.Track src="/example.vtt" default kind="subtitles" srcLang="en" label="English" />
</VideoPlayer>,
)

expect(queryByRole('button', {name: 'Play video'})).not.toBeInTheDocument()
expect(queryByRole('button', {name: 'Play'})).toBeInTheDocument()
})

it('does not render the overlay play button when autoplaying', () => {
const {getByRole, queryByRole} = render(
<VideoPlayer poster="/example-poster.jpg" title="test video" autoPlay muted>
<VideoPlayer.Source src="/example.mp4" />
<VideoPlayer.Track src="/example.vtt" default kind="subtitles" srcLang="en" label="English" />
</VideoPlayer>,
)

expect(queryByRole('button', {name: 'Play'})).not.toBeInTheDocument()
expect(getByRole('button', {name: 'Play video'})).toBeInTheDocument()
})

it('syncs the mute control with an initially muted video', async () => {
const {getByRole, getByTitle} = render(
<VideoPlayer poster="/example-poster.jpg" title="test video" muted>
<VideoPlayer.Source src="/example.mp4" />
<VideoPlayer.Track src="/example.vtt" default kind="subtitles" srcLang="en" label="English" />
</VideoPlayer>,
)

const video = getByTitle('test video') as HTMLVideoElement

await waitFor(() => {
expect(getByRole('button', {name: 'Unmute'})).toBeInTheDocument()
})

await userEvent.click(getByRole('button', {name: 'Unmute'}))

expect(video.muted).toBe(false)
})

it('supports positioning the controls bar at the bottom of the player', () => {
const {container} = render(
<VideoPlayer poster="/example-poster.jpg" title="test video" controlsPosition="bottom">
<VideoPlayer.Source src="/example.mp4" />
<VideoPlayer.Track src="/example.vtt" default kind="subtitles" srcLang="en" label="English" />
</VideoPlayer>,
)

expect(container.querySelector('.VideoPlayer__container--controlsBottom')).toBeInTheDocument()
expect(container.querySelector('.VideoPlayer__controlsBar--bottom')).toBeInTheDocument()
})

it('renders the enable/disable closed caption button without errors', () => {
const {getByRole} = render(
<VideoPlayer poster="/example-poster.jpg" title="test video">
Expand Down
42 changes: 29 additions & 13 deletions packages/react/src/VideoPlayer/VideoPlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ type VideoPlayerProps = {
visuallyHiddenTitle?: boolean
showBranding?: boolean
animate?: AnimateProps
controlsPosition?: 'inline' | 'bottom'
showControlsWhenPaused?: boolean
showPlayPauseButton?: boolean
showSeekControl?: boolean
showCCButton?: boolean
Expand All @@ -49,6 +51,9 @@ const Root = forwardRef<HTMLVideoElement, VideoPlayerProps>(
showBranding = true,
children,
className,
autoPlay,
controlsPosition = 'inline',
showControlsWhenPaused = true,
showPlayPauseButton = true,
showSeekControl = true,
showCCButton = true,
Expand Down Expand Up @@ -85,15 +90,24 @@ const Root = forwardRef<HTMLVideoElement, VideoPlayerProps>(

const showControlsRow1 = showPlayPauseButton || showSeekControl
const showControlsRow2 = showCCButton || showMuteButton || showVolumeControl || showFullScreenButton
const showControlsBar = showControlsRow1 || showControlsRow2
const showControlsBar = (showControlsRow1 || showControlsRow2) && (isPlaying || showControlsWhenPaused)
const showPlayButtonOverlay = !autoPlay
Comment thread
danielguillan marked this conversation as resolved.
Outdated

return (
<div data-video-player-container className={styles.VideoPlayer__container} ref={fullscreenRef}>
<div
data-video-player-container
className={clsx(
styles.VideoPlayer__container,
controlsPosition === 'bottom' && styles['VideoPlayer__container--controlsBottom'],
)}
ref={fullscreenRef}
>
<div className={styles.VideoPlayer__overlayContainer}>
<video
ref={setVideoElementRef}
title={title}
controls={false}
autoPlay={autoPlay}
className={clsx(styles.VideoPlayer, className)}
{...rest}
>
Expand All @@ -110,21 +124,23 @@ const Root = forwardRef<HTMLVideoElement, VideoPlayerProps>(
)}
</div>
) : null}
<button
className={clsx(
styles.VideoPlayer__playButtonOverlay,
isPlaying && styles['VideoPlayer__playButtonOverlay--transparent'],
)}
onClick={togglePlaying}
aria-label={isPlaying ? 'Pause' : 'Play'}
>
{!isPlaying && <PlayIcon />}
</button>
{showPlayButtonOverlay && (
<button
className={clsx(
styles.VideoPlayer__playButtonOverlay,
isPlaying && styles['VideoPlayer__playButtonOverlay--transparent'],
)}
onClick={togglePlaying}
aria-label={isPlaying ? 'Pause' : 'Play'}
>
{!isPlaying && <PlayIcon />}
</button>
)}
</div>

{ccEnabled && <Captions />}
{showControlsBar && (
<ControlsBar>
<ControlsBar className={clsx(controlsPosition === 'bottom' && styles['VideoPlayer__controlsBar--bottom'])}>
{showControlsRow1 && (
<div className={styles['VideoPlayer__controlsBar__row1']}>
{showPlayPauseButton && <PlayPauseButton />}
Expand Down
Loading
Loading