Skip to content

Commit 50705e3

Browse files
authored
Add video support to Hero component (#1042)
1 parent 2b5b693 commit 50705e3

16 files changed

+491
-42
lines changed

.changeset/calm-donuts-live.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
'@primer/react-brand': patch
3+
---
4+
5+
Add `Hero.Video` slot to `Hero` for inserting custom videos.
6+
7+
```jsx
8+
<Hero>
9+
<Hero.Heading>Your super sweet hero heading</Hero.Heading>
10+
<Hero.Video>
11+
<VideoPlayer title="Your custom video">
12+
<VideoPlayer.Source src="./example.mp4" type="video/mp4" />
13+
<VideoPlayer.Track src="./example.vtt" default />
14+
</VideoPlayer>
15+
</Hero.Video>
16+
</Hero>
17+
```
18+
19+
Refer to Storybook for more usage examples.

apps/docs/content/components/Hero/react.mdx

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,36 @@ import {Hero} from '@primer/react-brand'
7676
</>
7777
```
7878

79+
### Video
80+
81+
`Hero` includes support for inserting videos.
82+
83+
Use [VideoPlayer](/components/VideoPlayer), a native `<video>` element or a YouTube embed as valid `children`. Similarly to `Hero.Image`, use the `position` prop to alternate between various layouts.
84+
85+
```jsx live
86+
<Hero>
87+
<Hero.Label>Label</Hero.Label>
88+
<Hero.Heading>This is my super sweet hero heading</Hero.Heading>
89+
<Hero.Description>
90+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. In sapien sit
91+
ullamcorper id. Aliquam luctus sed turpis felis nam pulvinar risus
92+
elementum.
93+
</Hero.Description>
94+
<Hero.PrimaryAction href="#">Primary action</Hero.PrimaryAction>
95+
<Hero.Video>
96+
<iframe
97+
src="https://www.youtube.com/embed/fHwtrOcLAnI"
98+
title="YouTube video player"
99+
frameBorder="0"
100+
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
101+
referrerPolicy="strict-origin-when-cross-origin"
102+
allowFullScreen
103+
style={{width: '100%', height: 'auto', aspectRatio: '16/10'}}
104+
></iframe>
105+
</Hero.Video>
106+
</Hero>
107+
```
108+
79109
### Alignment
80110

81111
`Hero` text alignment can be adjusted by changing the `align` prop.
@@ -158,3 +188,9 @@ Forwards all props from the [Button component](/components/Button).
158188
| `position` | <PropTableValues values={['inline-end', 'block-end']} addLineBreaks /> | `'block-end'` | Controls positioning of the foreground image |
159189

160190
Forwards all props from the [Image component](/components/Image), including `src`, `alt`, and `aspectRatio`.
191+
192+
### Hero.Video
193+
194+
| name | type | default | description |
195+
| ---------- | ---------------------------------------------------------------------- | ------------- | ----------------------------------------------- |
196+
| `position` | <PropTableValues values={['inline-end', 'block-end']} addLineBreaks /> | `'block-end'` | Controls positioning of the child video element |

apps/docs/scripts/components-with-animation.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ export const supportedComponents = [
1010
'Label',
1111
'Pillar',
1212
'SectionIntro',
13-
'Stack',
1413
'Statistic',
14+
'Stack',
1515
'Testimonial',
1616
'Text',
1717
'Timeline',

apps/next-docs/content/components/Hero/react.mdx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,35 @@ import {Hero} from '@primer/react-brand'
6060
</Stack>
6161
```
6262

63+
### Video
64+
65+
`Hero` includes support for inserting videos.
66+
67+
Use [VideoPlayer](/components/VideoPlayer), a native `<video>` element or a YouTube embed as valid `children`. Similarly to `Hero.Image`, use the `position` prop to alternate between various layouts.
68+
69+
```jsx live
70+
<Hero>
71+
<Hero.Label>Label</Hero.Label>
72+
<Hero.Heading>This is my super sweet hero heading</Hero.Heading>
73+
<Hero.Description>
74+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. In sapien sit ullamcorper id. Aliquam luctus sed turpis
75+
felis nam pulvinar risus elementum.
76+
</Hero.Description>
77+
<Hero.PrimaryAction href="#">Primary action</Hero.PrimaryAction>
78+
<Hero.Video>
79+
<iframe
80+
src="https://www.youtube.com/embed/fHwtrOcLAnI"
81+
title="YouTube video player"
82+
frameBorder="0"
83+
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
84+
referrerPolicy="strict-origin-when-cross-origin"
85+
allowFullScreen
86+
style={{width: '100%', height: 'auto', aspectRatio: '16/10'}}
87+
></iframe>
88+
</Hero.Video>
89+
</Hero>
90+
```
91+
6392
### Alignment
6493

6594
`Hero` text alignment can be adjusted by changing the `align` prop.
@@ -142,3 +171,9 @@ Forwards all props from the [Button component](/components/Button).
142171
| `position` | <HeroImagePositionProp /> | `'block-end'` | Controls positioning of the foreground image |
143172

144173
Forwards all props from the [Image component](/components/Image), including `src`, `alt`, and `aspectRatio`.
174+
175+
### Hero.Video
176+
177+
| name | type | default | description |
178+
| ---------- | ------------------------- | ------------- | ----------------------------------------------- |
179+
| `position` | <HeroImagePositionProp /> | `'block-end'` | Controls positioning of the child video element |

package-lock.json

Lines changed: 9 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/e2e/scripts/playwright/playwright.generate-tests.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,9 @@
134134
'components-statistic-features--animations', // animation only
135135
'components-riverstoryscroll-features--video-narrow', // video makes this too flakey
136136
'components-riverstoryscroll-features--video', // video makes this too flakey
137+
'components-hero-features--with-native-block-end-default', // for being non-deterministic due to video buffering
138+
'components-hero-features--with-youtube-video-block-end-default', // for loading a remote video
139+
'components-hero-features--with-youtube-video-inline-end', // for loading a remote video
137140
]
138141

139142
const categorisedStories = Object.keys((stories as typeof StoryIndex).entries).reduce((acc, key) => {

packages/react/src/Hero/Hero.features.stories.tsx

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@ import {StoryFn, Meta} from '@storybook/react'
33
import {INITIAL_VIEWPORTS} from '@storybook/addon-viewport'
44
import {HeartFillIcon, PlayIcon, StarFillIcon} from '@primer/octicons-react'
55
import placeholderImage from '../fixtures/images/placeholder.png'
6+
import posterImage from '../fixtures/images/example-poster.png'
67

78
import {Hero} from '.'
89
import {ActionMenu} from '../ActionMenu'
910
import {Grid} from '../Grid'
1011
import {EyebrowBanner} from '../EyebrowBanner'
12+
import {VideoPlayer} from '../VideoPlayer'
13+
14+
import styles from './Hero.stories.module.css'
1115

1216
export default {
1317
title: 'Components/Hero/Features',
@@ -114,6 +118,129 @@ export const WithImageInlineEnd: StoryFn<typeof Hero> = _args => (
114118
)
115119
WithImageInlineEnd.storyName = 'With an image (right)'
116120

121+
export const WithVideoBlockEndDefault: StoryFn<typeof Hero> = _args => (
122+
<Grid>
123+
<Grid.Column>
124+
<Hero>
125+
<Hero.Label>Label</Hero.Label>
126+
<Hero.Heading>This is my super sweet hero heading</Hero.Heading>
127+
<Hero.Description>
128+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. In sapien sit ullamcorper id. Aliquam luctus sed
129+
turpis felis nam pulvinar risus elementum.
130+
</Hero.Description>
131+
<Hero.PrimaryAction href="#">Primary action</Hero.PrimaryAction>
132+
<Hero.Video>
133+
<VideoPlayer title="GitHub media player" poster={posterImage}>
134+
<VideoPlayer.Source src="./example.mp4" type="video/mp4" />
135+
<VideoPlayer.Track src="./example.vtt" default />
136+
</VideoPlayer>
137+
</Hero.Video>
138+
</Hero>
139+
</Grid.Column>
140+
</Grid>
141+
)
142+
WithVideoBlockEndDefault.storyName = 'With a VideoPlayer (bottom)'
143+
144+
export const WithNativeBlockEndDefault: StoryFn<typeof Hero> = _args => (
145+
<Grid>
146+
<Grid.Column>
147+
<Hero>
148+
<Hero.Label>Label</Hero.Label>
149+
<Hero.Heading>This is my super sweet hero heading</Hero.Heading>
150+
<Hero.Description>
151+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. In sapien sit ullamcorper id. Aliquam luctus sed
152+
turpis felis nam pulvinar risus elementum.
153+
</Hero.Description>
154+
<Hero.PrimaryAction href="#">Primary action</Hero.PrimaryAction>
155+
<Hero.Video>
156+
<video title="Example title" controls poster={posterImage} className={styles.customVideo}>
157+
<source src="./example.mp4" type="video/mp4" />
158+
<track src="./example.vtt" kind="captions" srcLang="en" label="English" default />
159+
Your browser does not support the video tag.
160+
</video>
161+
</Hero.Video>
162+
</Hero>
163+
</Grid.Column>
164+
</Grid>
165+
)
166+
WithNativeBlockEndDefault.storyName = 'With a HTML video (bottom)'
167+
168+
export const WithYoutubeVideoBlockEndDefault: StoryFn<typeof Hero> = _args => (
169+
<Grid>
170+
<Grid.Column>
171+
<Hero>
172+
<Hero.Label>Label</Hero.Label>
173+
<Hero.Heading>This is my super sweet hero heading</Hero.Heading>
174+
<Hero.Description>
175+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. In sapien sit ullamcorper id. Aliquam luctus sed
176+
turpis felis nam pulvinar risus elementum.
177+
</Hero.Description>
178+
<Hero.PrimaryAction href="#">Primary action</Hero.PrimaryAction>
179+
<Hero.Video>
180+
<iframe
181+
src="https://www.youtube.com/embed/EPyyyB23NUU"
182+
title="YouTube video player"
183+
frameBorder="0"
184+
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
185+
referrerPolicy="strict-origin-when-cross-origin"
186+
allowFullScreen
187+
className={styles.customVideo}
188+
></iframe>
189+
</Hero.Video>
190+
</Hero>
191+
</Grid.Column>
192+
</Grid>
193+
)
194+
WithYoutubeVideoBlockEndDefault.storyName = 'With a YouTube video (bottom)'
195+
196+
export const WithVideoInlineEnd: StoryFn<typeof Hero> = _args => (
197+
<Grid>
198+
<Grid.Column>
199+
<Hero align="center">
200+
<Hero.Heading>This is my super sweet hero heading</Hero.Heading>
201+
<Hero.Description>
202+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. In sapien sit ullamcorper id. Aliquam luctus sed
203+
turpis felis nam pulvinar risus elementum.
204+
</Hero.Description>
205+
<Hero.PrimaryAction href="#">Primary action</Hero.PrimaryAction>
206+
<Hero.Video position="inline-end">
207+
<VideoPlayer title="GitHub media player" poster={posterImage}>
208+
<VideoPlayer.Source src="./example.mp4" type="video/mp4" />
209+
<VideoPlayer.Track src="./example.vtt" default />
210+
</VideoPlayer>
211+
</Hero.Video>
212+
</Hero>
213+
</Grid.Column>
214+
</Grid>
215+
)
216+
WithVideoInlineEnd.storyName = 'With a VideoPlayer (right)'
217+
218+
export const WithYoutubeVideoInlineEnd: StoryFn<typeof Hero> = _args => (
219+
<Grid>
220+
<Grid.Column>
221+
<Hero align="center">
222+
<Hero.Heading>This is my super sweet hero heading</Hero.Heading>
223+
<Hero.Description>
224+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. In sapien sit ullamcorper id. Aliquam luctus sed
225+
turpis felis nam pulvinar risus elementum.
226+
</Hero.Description>
227+
<Hero.PrimaryAction href="#">Primary action</Hero.PrimaryAction>
228+
<Hero.Video position="inline-end">
229+
<iframe
230+
src="https://www.youtube.com/embed/EPyyyB23NUU"
231+
title="YouTube video player"
232+
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
233+
referrerPolicy="strict-origin-when-cross-origin"
234+
allowFullScreen
235+
style={{width: '100%', height: 'auto', aspectRatio: '16/9'}}
236+
></iframe>
237+
</Hero.Video>
238+
</Hero>
239+
</Grid.Column>
240+
</Grid>
241+
)
242+
WithYoutubeVideoInlineEnd.storyName = 'With a YouTube video (right)'
243+
117244
export const WithoutDescription: StoryFn<typeof Hero> = _args => (
118245
<Hero>
119246
<Hero.Label>Label</Hero.Label>

packages/react/src/Hero/Hero.module.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@
4545
width: 100%;
4646
}
4747

48+
.Hero-video video,
49+
.Hero-video iframe {
50+
width: 100%;
51+
border-radius: var(--brand-borderRadius-large);
52+
}
53+
4854
.Hero-label {
4955
margin-block-end: var(--base-size-24);
5056
}

packages/react/src/Hero/Hero.module.css.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ declare const styles: {
1414
readonly "Hero-image": string;
1515
readonly "Hero-label": string;
1616
readonly "Hero-trailing": string;
17+
readonly "Hero-video": string;
1718
};
1819
export = styles;
1920

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.customVideo {
2+
width: 100%;
3+
height: 100%;
4+
aspect-ratio: 16 / 9;
5+
}

0 commit comments

Comments
 (0)