Skip to content

Commit 91aecdc

Browse files
committed
Update: VideoThumbnail components & docs
1 parent 0f90707 commit 91aecdc

File tree

5 files changed

+219
-0
lines changed

5 files changed

+219
-0
lines changed

src/classes/VideoThumbnail.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"Thumbnail":"Polaris-VideoThumbnail__Thumbnail","WithPlayer":"Polaris-VideoThumbnail__WithPlayer","PlayButton":"Polaris-VideoThumbnail__PlayButton","PlayIcon":"Polaris-VideoThumbnail__PlayIcon","Timestamp":"Polaris-VideoThumbnail__Timestamp","withProgress":"Polaris-VideoThumbnail--withProgress","Progress":"Polaris-VideoThumbnail__Progress","Indicator":"Polaris-VideoThumbnail__Indicator","ProgressBar":"Polaris-VideoThumbnail__ProgressBar","Label":"Polaris-VideoThumbnail__Label"}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { Meta, Story, Canvas, ArgsTable } from "@storybook/addon-docs";
2+
import { VideoThumbnail, MediaCard } from "@/polaris-vue";
3+
import dedent from "ts-dedent";
4+
5+
<Meta
6+
title="Components / Images and Icons / Video thumbnail"
7+
component={VideoThumbnail}
8+
argTypes={{
9+
title: {
10+
table: { disable: true },
11+
},
12+
description: {
13+
table: { disable: true },
14+
},
15+
click: {
16+
description: 'Callback on click or keypress of thumbnail. Use to trigger render of the video player in your chosen format, for example within a modal or fullscreen container.',
17+
table: {
18+
type: {
19+
summary: '() => void',
20+
},
21+
},
22+
control: { disable: true },
23+
},
24+
'before-start-playing': {
25+
description: 'Callback on mouse enter, focus, or touch start of thumbnail. Use to trigger video preload.',
26+
table: {
27+
type: {
28+
summary: '() => void',
29+
},
30+
},
31+
control: { disable: true },
32+
},
33+
}}
34+
/>
35+
36+
export const Template = (args) => ({
37+
components: { VideoThumbnail, MediaCard },
38+
setup() {
39+
return { args };
40+
},
41+
template: `
42+
<MediaCard
43+
title="Turn your side-project into a business"
44+
description="In this course, you'll learn how the Kular family turned their mom's recipe book into a global business."
45+
:primaryAction="{ content: 'Learn more', onAction: () => {} }"
46+
:popoverActions="[{content: 'Dismiss', onAction: () => {}}]"
47+
>
48+
<VideoThumbnail v-bind="args" />
49+
</MediaCard>
50+
`,
51+
});
52+
53+
# Video thumbnail
54+
55+
Video thumbnails are a clickable placeholder image. When clicked, it opens a video player within a modal or full screen.
56+
57+
<Canvas>
58+
<Story
59+
name="Video thumbnail"
60+
height="250px"
61+
inline={false}
62+
args={{
63+
videoLength: 80,
64+
showVideoProgress: false,
65+
videoProgress: 0,
66+
thumbnailUrl: 'https://burst.shopifycdn.com/photos/business-woman-smiling-in-office.jpg?width=1850',
67+
}}
68+
parameters={{
69+
docs: {
70+
source: {
71+
code: dedent`
72+
<MediaCard
73+
title="Turn your side-project into a business"
74+
description="In this course, you'll learn how the Kular family turned their mom's recipe book into a global business."
75+
:primaryAction="{ content: 'Learn more', onAction: () => {} }"
76+
:popoverActions="[{content: 'Dismiss', onAction: () => {}}]"
77+
>
78+
<VideoThumbnail
79+
:videoLength="80"
80+
thumbnailUrl="https://burst.shopifycdn.com/photos/business-woman-smiling-in-office.jpg?width=1850"
81+
/>
82+
</MediaCard>
83+
`,
84+
},
85+
},
86+
}}
87+
>
88+
{Template.bind({})}
89+
</Story>
90+
</Canvas>
91+
92+
<ArgsTable story="Video thumbnail" />
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
<template lang="pug">
2+
div(
3+
:class="styles.Thumbnail",
4+
:style="{ backgroundImage: `url(${thumbnailUrl})` }",
5+
)
6+
button(
7+
type="button",
8+
:class="styles.PlayButton",
9+
:aria-label="buttonLabel",
10+
@click="$emit('click')",
11+
@mouseenter="$emit('before-start-playing')",
12+
@focus="$emit('before-start-playing')",
13+
@touchstart="$emit('before-start-playing')",
14+
)
15+
Icon(:class="styles.PlayIcon", :source="PlayIcon")
16+
p(v-if="videoLength", :class="className")
17+
| {{ secondsToTimestamp(videoLength) }}
18+
div(v-if="showVideoProgress", :class="styles.Progress")
19+
progress(
20+
:class="styles.ProgressBar",
21+
:value="progressValuePercents",
22+
max="100",
23+
)
24+
div(
25+
:class="styles.Indicator",
26+
:style="{ transform: `scaleX(${progressValue})`}",
27+
)
28+
span(:class="styles.Label") {{ progressValuePercents }}%
29+
</template>
30+
31+
<script setup lang="ts">
32+
import { ref, computed, inject } from 'vue';
33+
import { classNames } from 'polaris/polaris-react/src/utilities/css';
34+
import { secondsToTimeComponents, secondsToTimestamp, secondsToDurationTranslationKey } from 'polaris/polaris-react/src/utilities/duration';
35+
import PlayIcon from 'polaris/polaris-react/src/components/VideoThumbnail/illustrations/play.svg';
36+
import { UseI18n } from '@/use';
37+
import { Icon } from '@/components';
38+
import styles from '@/classes/VideoThumbnail.json';
39+
40+
interface VideoThumbnailProps {
41+
/** URL source for thumbnail image. */
42+
thumbnailUrl: string;
43+
/**
44+
* Length of video in seconds.
45+
* @default 0
46+
*/
47+
videoLength?: number;
48+
/**
49+
* Video progress in seconds. Displays a progress bar at the bottom of the thumbnail. Only renders when videoLength is also set.
50+
* @default 0
51+
*/
52+
videoProgress?: number;
53+
/**
54+
* Indicate whether to allow video progress to be displayed
55+
* @default false
56+
*/
57+
showVideoProgress?: boolean;
58+
/** Custom ARIA label for play button.
59+
* @default 'Play video of length {human readable duration}'
60+
*/
61+
accessibilityLabel?: string;
62+
}
63+
64+
const props = withDefaults(defineProps<VideoThumbnailProps>(), {
65+
videoLength: 0,
66+
videoProgress: 0,
67+
showVideoProgress: false,
68+
});
69+
70+
const i18n = UseI18n();
71+
72+
const buttonLabel = computed(() => {
73+
if (props.accessibilityLabel) {
74+
return props.accessibilityLabel;
75+
}
76+
77+
if (props.videoLength) {
78+
const { hours, minutes, seconds } = secondsToTimeComponents(props.videoLength);
79+
80+
const translationKey = i18n.translate(secondsToDurationTranslationKey(props.videoLength), {
81+
hourCount: hours,
82+
minuteCount: minutes,
83+
secondCount: seconds,
84+
});
85+
86+
return i18n.translate(
87+
'Polaris.VideoThumbnail.playButtonA11yLabel.defaultWithDuration',
88+
{ duration: translationKey },
89+
);
90+
}
91+
92+
return i18n.translate('Polaris.VideoThumbnail.playButtonA11yLabel.default');
93+
});
94+
95+
const progressValue = computed(() => calculateProgress(props.videoLength, props.videoProgress));
96+
const progressValuePercents = computed(() => Math.round(progressValue.value * 100));
97+
98+
const className = computed(() => {
99+
return classNames(
100+
styles.Timestamp,
101+
props.showVideoProgress && styles.withProgress,
102+
);
103+
});
104+
105+
function calculateProgress(videoLength: number, videoProgress: number) {
106+
// if (videoProgress > videoLength && process.env.NODE_ENV === 'development') {
107+
// // eslint-disable-next-line no-console
108+
// console.warn(
109+
// 'Value passed to the video progress should not exceed video length. Resetting progress to 100%.',
110+
// );
111+
// }
112+
113+
if (videoProgress > 0 && videoLength > 0) {
114+
const progress = parseFloat((videoProgress / videoLength).toFixed(2));
115+
return progress > 1 ? 1 : progress;
116+
}
117+
118+
return 0;
119+
}
120+
</script>
121+
122+
<style lang="scss">
123+
@import 'polaris/polaris-react/src/components/VideoThumbnail/VideoThumbnail.scss';
124+
</style>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default as VideoThumbnail } from './VideoThumbnail.vue';

src/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,4 +85,5 @@ export * from './TrapFocus';
8585
export * from './Truncate';
8686
export * from './UnstyledButton';
8787
export * from './UnstyledLink';
88+
export * from './VideoThumbnail';
8889
export * from './VisuallyHidden';

0 commit comments

Comments
 (0)