From 56fccccbdc6b19894c1b7274025710d4a9055e01 Mon Sep 17 00:00:00 2001 From: Chloe Rice Date: Mon, 24 Feb 2020 11:49:03 -0500 Subject: [PATCH] requested changes for easier diff viewing/copying --- UNRELEASED.md | 3 +- locales/en.json | 2 +- src/components/VideoCard/README.md | 85 +++---- src/components/VideoCard/VideoCard.scss | 61 +++-- src/components/VideoCard/VideoCard.tsx | 153 +++++------- src/components/VideoCard/index.ts | 2 +- .../VideoCard/tests/VideoCard.test.tsx | 232 ++++-------------- src/components/VideoThumbnail/README.md | 28 +-- .../VideoThumbnail/VideoThumbnail.scss | 13 +- .../VideoThumbnail/VideoThumbnail.tsx | 79 +++--- src/components/VideoThumbnail/index.ts | 2 +- .../time.test.ts => tests/utilities.test.ts} | 2 +- .../{utilities/time.ts => utilities.ts} | 17 +- src/components/index.ts | 4 +- 14 files changed, 250 insertions(+), 433 deletions(-) rename src/components/VideoThumbnail/{utilities/time.test.ts => tests/utilities.test.ts} (96%) rename src/components/VideoThumbnail/{utilities/time.ts => utilities.ts} (59%) diff --git a/UNRELEASED.md b/UNRELEASED.md index 42f9781fa0c..ec3b6726bd2 100644 --- a/UNRELEASED.md +++ b/UNRELEASED.md @@ -4,7 +4,7 @@ ### New components -- Added new components `VideoCard` and `VideoThumbnail` ([#2725](https://github.com/Shopify/polaris-react/pull/2725)) +Added new components [`VideoCard`](https://polaris.shopify.com/components/structure/video-card) and [`VideoThumbnail`](https://polaris.shopify.com/components/images-and-icons/video-thumbnail) ([#2725](https://github.com/Shopify/polaris-react/pull/2725)) ### Enhancements @@ -12,7 +12,6 @@ - Added high contrast border to `Button` ([#2712](https://github.com/Shopify/polaris-react/pull/2712)) - Added styled placeholder image to `Avatar` when initials are blank ([#2693](https://github.com/Shopify/polaris-react/pull/2693)) - Added a `preferInputActivator` prop to `Popover` to allow better positioning of the overlay ([#2754](https://github.com/Shopify/polaris-react/pull/2754)) -- Added new components `VideoCard` and `VideoThumbnail` ([#2725](https://github.com/Shopify/polaris-react/pull/2725)) ### Bug fixes diff --git a/locales/en.json b/locales/en.json index 4611af86b8e..992b9b50fdc 100644 --- a/locales/en.json +++ b/locales/en.json @@ -233,7 +233,7 @@ }, "VideoThumbnail": { "playButtonDefault": "Play video", - "playButtonWithTime": "Play video of length ", + "playButtonWithTime": "Play video of length {length}", "hoursLabel": "{hours} hours", "minutesLabel": "{minutes} minutes", "secondsLabel": "{seconds} seconds" diff --git a/src/components/VideoCard/README.md b/src/components/VideoCard/README.md index 4438c6986dd..39588d2b06f 100644 --- a/src/components/VideoCard/README.md +++ b/src/components/VideoCard/README.md @@ -8,24 +8,13 @@ keywords: - new features - video card - feature card - - video card heading - - video card body content - - video card text - - video card cta - - video card call to action - - video card button - - video card with secondary cta - - video card with secondary button - - dismissible video card - card with thumbnail - thumbnail card --- # Video card -Video cards are used to surface contextual videos and provide layouts and controls introduced -in the contextual learning system. This component makes presenting educational videos to merchants -more consistent, while providing the flexibility to configure a video player and its behaviour outside of the component. +Video cards provide a consistent layout for contextual learning system content. Use to wrap thumbnails of educational videos about Shopify admin features in context. --- @@ -33,21 +22,18 @@ more consistent, while providing the flexibility to configure a video player and Video cards should: -- Clearly articulate the benefit of the feature and what it does +- Clearly articulate the benefit of the feature and what it does in the context of where it is managed - Provide merchants with a clear call to action - Show targeted content toward specific user types where possible to maximize relevance and impact -- Use a thumbnail that helps to communicate the subject of the video or merchant benefit -- Can be displayed higher on the page in Empty States, but should be positioned at the bottom of the page - in Non-Empty States to avoid getting in the way of a merchant task -- Video Cards should generally be dismissable -- Suggested Duration: Keep contextual learning videos generally short (1-5 mins) +- Use a video player with a thumbnail that helps to communicate the subject of the video or merchant benefit +- Be positioned at the bottom of the page to avoid getting in the way of a merchant task, unless used within an empty state +- Be dismissable --- ## Content guidelines -- Do not use video cards as advertisements for your feature. Instead they should educate the merchant about how to - accomplish tasks related to the section they’re in. +- Do not use video cards as advertisements for your feature. Instead they should educate the merchant about how to accomplish tasks related to the section they’re in. ### Title @@ -57,19 +43,17 @@ Video card titles should follow the content guidelines for [headings and subhead Body content should be: -- Actionable: start sentences with imperative verbs when telling merchants - what actions are available to them (especially something new). Don’t use - permissive language like “you can”. +- Actionable: start sentences with imperative verbs when telling merchants what actions are available to them, especially something new. Don’t use permissive language like “you can”. #### Do -Get performance data for all your sales channels. +Get performance data for all of your sales channels. #### Don’t -Now you can get performance data for all your sales channels. +Now you can get performance data for all of your sales channels. @@ -146,27 +130,20 @@ Add a menu item - Follow the 16:9 ratio, otherwise the image may appear cropped - If the thumbnail shows a person, avoid cropping the person’s head off -### Character Limits - -- Single Line Headline approx 14 characters -- Two Line Headline approx 28 characters -- Body 50 characters -- CTA 14 characters - --- ## Examples -### Portrait video card +### Basic video card -Use to help merchants know what clear, single action to take in the section the video card is displayed. +Use to surface educational information about a feature or opportunity in the context of where it is managed. ```jsx {}, }, ]} @@ -183,24 +160,21 @@ Use to help merchants know what clear, single action to take in the section the ### Video card with secondary action -Use to let merchants know about a feature or opportunity where there are two distinct actions they can take on the information. +Use when there are two distinct actions merchants can take on the information in the video. ```jsx {}, - }, - { - content: 'Click here', - onAction: () => {}, - }, - ]} + primaryAction={{ + content: 'Learn about getting started', + onAction: () => {}, + }} + secondaryAction={{ + content: 'Click here', + onAction: () => {}, + }} description="Discover how Shopify can power up your entrepreneurial journey." popoverActions={[{content: 'Dismiss', onAction: () => {}}]} - portrait > ``` -### Landscape video card +### Portrait video card -Use when you would like to surface a video card but not take up too much screen space or push critical content below the fold. +Use when vertical screen space is not limited or when the video card comprises the page's primary content. For example, in an empty state. ```jsx {}, - }, - ]} + primaryAction={{ + content: 'Learn about getting started', + onAction: () => {}, + }} description="Discover how Shopify can power up your entrepreneurial journey." popoverActions={[{content: 'Dismiss', onAction: () => {}}]} > diff --git a/src/components/VideoCard/VideoCard.scss b/src/components/VideoCard/VideoCard.scss index a367ab1b8f6..2b1675b0d0b 100644 --- a/src/components/VideoCard/VideoCard.scss +++ b/src/components/VideoCard/VideoCard.scss @@ -1,54 +1,63 @@ -$breakpoint: 804px; +@import '../../styles/common'; -.Container { +$portrait-breakpoint: 804px; + +.VideoCard { height: 100%; + width: 100%; display: flex; flex-flow: row wrap; - @media screen and (max-width: $breakpoint) { + &.portrait { flex-flow: column nowrap; } -} -.PortraitContainer { - flex-flow: column nowrap; + @include breakpoint-before($portrait-breakpoint, inclusive) { + flex-flow: column nowrap; + } } -.InfoContainer { - position: relative; - display: flex; - justify-content: space-between; - flex-direction: column; +.VideoContainer { + &:not(.portrait) { + flex-basis: 40%; + } } -.PortraitInfoContainer { +.InfoContainer { position: relative; -} -.VideoContainer { - flex-basis: 40%; + &:not(.portrait) { + flex-basis: 60%; + } } .Popover { position: absolute; z-index: z-index(overlay); - top: 0; - right: 0; + top: spacing(); + right: spacing(); } -.InfoWrapper { - position: relative; - flex-basis: 60%; +.Heading { + margin-right: spacing(extra-loose); } -.ContentIndented { - padding-right: spacing(); +.PrimaryAction { + margin-right: spacing(tight); } -.Description { - padding-top: spacing(tight); +.SecondaryAction { + margin-left: -spacing(tight); } -.PrimaryAction { - padding-top: spacing(); +.ActionContainer { + padding-top: spacing(tight); + + &.portrait { + padding-top: spacing(extra-loose); + } + + @include breakpoint-before($portrait-breakpoint, inclusive) { + padding-top: spacing(extra-loose); + } } diff --git a/src/components/VideoCard/VideoCard.tsx b/src/components/VideoCard/VideoCard.tsx index 5d5e31a0f0b..e5374c2165e 100644 --- a/src/components/VideoCard/VideoCard.tsx +++ b/src/components/VideoCard/VideoCard.tsx @@ -1,31 +1,44 @@ import React, {useState, useCallback} from 'react'; import {HorizontalDotsMinor} from '@shopify/polaris-icons'; + import {classNames} from '../../utilities/css'; import {useI18n} from '../../utilities/i18n'; import {Action, ActionListItemDescriptor} from '../../types'; import {Card} from '../Card'; -import {Button} from '../Button'; +import {Button, buttonFrom} from '../Button'; import {Heading} from '../Heading'; import {Popover} from '../Popover'; import {ActionList} from '../ActionList'; -import {Stack} from '../Stack'; import {ButtonGroup} from '../ButtonGroup'; +import {Stack} from '../Stack'; + import styles from './VideoCard.scss'; interface VideoCardProps { + /** describe prop here */ children?: React.ReactNode; + /** Heading content */ title: string; - primaryActions: Action[]; + /** Body content */ description: string; + /** Main call to action, rendered as a basic button */ + primaryAction: Action; + /** Secondary call to action, rendered as a plain button */ + secondaryAction?: Action; + /** Action list items to render in ellipsis popover */ popoverActions?: ActionListItemDescriptor[]; + /** Whether or not card content should be laid out vertically + * @default false + */ portrait?: boolean; } export function VideoCard({ title, children, - primaryActions, + primaryAction, + secondaryAction, description, popoverActions = [], portrait = false, @@ -52,106 +65,74 @@ export function VideoCard({ popoverActions.length > 0 ? (
) : null; - const primaryActionsMarkup = primaryActions.map( - ( - { - id = '', - content = '', - accessibilityLabel = '', - url = '', - onAction = noop, - external = true, - onMouseEnter = noop, - onTouchStart = noop, - }, - index, - ) => { - const secondaryButton = index !== 0; - return ( - - ); - }, + const primaryActionMarkup = ( +
{buttonFrom(primaryAction)}
+ ); + + const secondaryActionMarkup = secondaryAction ? ( +
+ {buttonFrom(secondaryAction, {plain: true})} +
+ ) : null; + + const actionClassName = classNames( + styles.ActionContainer, + portrait && styles.portrait, + ); + + const actionMarkup = ( +
+ + {primaryActionMarkup} + {secondaryActionMarkup} + +
+ ); + + const videoCardClassName = classNames( + styles.VideoCard, + portrait && styles.portrait, ); + + const videoContainerClassName = classNames( + styles.VideoContainer, + portrait && styles.portrait, + ); + + const infoContainerClassName = classNames( + styles.InfoContainer, + portrait && styles.portrait, + ); + return ( -
-
- {children} -
-
+
+
{children}
+
-
- {popoverActionsMarkup} - -
-
- {title} -
-
-

{description}

-
- - {primaryActions.length > 0 ? ( - - {primaryActionsMarkup} - - ) : null} - -
-
-
+ {popoverActionsMarkup} + +
+ {title} +
+

{description}

+ {actionMarkup} +
); } - -function noop() {} diff --git a/src/components/VideoCard/index.ts b/src/components/VideoCard/index.ts index 8a17800f1e6..aeae19f5af3 100644 --- a/src/components/VideoCard/index.ts +++ b/src/components/VideoCard/index.ts @@ -1 +1 @@ -export {VideoCard} from './VideoCard'; +export * from './VideoCard'; diff --git a/src/components/VideoCard/tests/VideoCard.test.tsx b/src/components/VideoCard/tests/VideoCard.test.tsx index be509c2976f..a65ce0c68d1 100644 --- a/src/components/VideoCard/tests/VideoCard.test.tsx +++ b/src/components/VideoCard/tests/VideoCard.test.tsx @@ -1,230 +1,86 @@ import React from 'react'; import {Heading, Popover, Button, ActionList} from 'components'; -// eslint-disable-next-line no-restricted-imports -import {mountWithAppProvider, trigger} from 'test-utilities/legacy'; import {mountWithApp} from 'test-utilities'; import {VideoCard} from '../VideoCard'; -function createVideoCard() { - return { - title: '', - primaryActions: createPrimaryAction(), - description: '', - popoverActions: createPopoverActions(2), - }; -} - -function createPrimaryAction() { - return [ - { - content: '', - onAction: () => {}, - url: '', - external: true, - accessibilityLabel: '', - }, - ]; -} - -function createPopoverActions(num: number) { - return new Array(num).fill({}).map(createPopoverAction); -} - -function createPopoverAction() { - return { - content: '', +const mockProps = { + title: 'test title', + description: 'test description', + primaryAction: { + content: 'test primary action', onAction: () => {}, - }; -} + }, +}; describe('', () => { - it('renders title and checks contents', () => { - const titleText = 'Getting Started'; - const videoCard = mountWithApp( - , - ); - expect(videoCard.find(Heading)).not.toBeNull(); - expect(videoCard.find(Heading)!).toContainReactText(titleText); + it('renders the title as a Heading', () => { + const title = 'Getting Started'; + const videoCard = mountWithApp(); + + expect(videoCard).toContainReactComponent(Heading, {children: title}); }); - it('renders description and checks contents', () => { - const descriptionLabel = - 'Discover how Shopify can power up your entrepreneurial journey.'; + it('renders the description as a paragraph', () => { + const description = 'test'; const videoCard = mountWithApp( - , - ); - expect(videoCard.find('p', {className: 'Description'})).not.toBeNull(); - expect(videoCard.find('p', {className: 'Description'})).toContainReactText( - descriptionLabel, + , ); + + expect(videoCard).toContainReactComponent('p', {children: description}); }); - it('renders CTA and checks contents', () => { - const mainCTALabel = 'Learn about getting started and more'; - const mainCTAUrl = ''; - const accesssibilityLabel = 'button1'; + it('renders a Button with the primaryAction', () => { + const primaryAction = {content: 'test primary action'}; const videoCard = mountWithApp( - , + , ); - expect(videoCard.find(Button)).not.toBeNull(); + expect(videoCard).toContainReactComponent(Button, { - url: mainCTAUrl, - external: true, - accessibilityLabel: accesssibilityLabel, + children: primaryAction.content, }); - expect( - videoCard.find(Button, { - children: mainCTALabel, - })!, - ).toContainReactText(mainCTALabel); }); - it('renders CTA and plain button and checks contents', () => { - const secondaryCTALabel = 'Additional Info'; - const secondaryCTAUrl = ''; - const accesssibilityLabel = 'button2'; + it('renders secondaryAction as a plain Button', () => { + const secondaryAction = {content: 'test'}; const videoCard = mountWithApp( - , + , ); - expect(videoCard.find(Button)).not.toBeNull(); + expect(videoCard).toContainReactComponent(Button, { - url: secondaryCTAUrl, - external: true, - accessibilityLabel: accesssibilityLabel, + children: secondaryAction.content, }); - expect( - videoCard.find(Button, { - children: secondaryCTALabel, - })!, - ).toContainReactText(secondaryCTALabel); }); - it('calls the onAction callback when the CTA is clicked', () => { - const spy = jest.fn(); - const videoCard = mountWithAppProvider( - , + it('renders a Popover and ActionList when popoverActions are provided', () => { + const actions = [{content: 'Dismiss'}]; + const videoCard = mountWithApp( + , ); - videoCard - .find('button') - .last() - .simulate('click'); - expect(spy).toHaveBeenCalled(); - }); - it('calls the onAction callback when the plain button is clicked', () => { - const spy = jest.fn(); - const videoCard = mountWithAppProvider( - , - ); + expect(videoCard).toContainReactComponentTimes(Popover, 1); - videoCard - .find('button') - .last() - .simulate('click'); - expect(spy).toHaveBeenCalled(); - }); + const popoverActivator = videoCard.find(Popover)!.find(Button); + popoverActivator!.trigger('onClick'); - it('toggles the popover menu when clicked and calls the first action item', () => { - const spyDismiss = jest.fn(); - const spyFeedback = jest.fn(); - const videoCard = mountWithAppProvider( - , - ); - trigger(videoCard.find(Popover)!.find(Button)!, 'onClick'); - expect(videoCard.find(Popover)!.prop('active')).toBe(true); - - trigger(videoCard.find(Popover)!.find(Button)!, 'onClick'); - expect(videoCard.find(Popover)!.prop('active')).toBe(false); - - const actionList = videoCard.find(ActionList)!; - expect(actionList.prop('items')).toHaveLength(2); - - actionList - .find('button') - .first() - .simulate('click'); - expect(spyDismiss).toHaveBeenCalled(); - actionList - .find('button') - .last() - .simulate('click'); - expect(spyFeedback).toHaveBeenCalled(); + expect(videoCard).toContainReactComponent(ActionList, { + items: actions, + }); }); - it('does not render popover menu if secondary actions are empty', () => { + it('does not render a Popover if popoverActions are empty', () => { const videoCard = mountWithApp( - , + , ); + expect(videoCard).not.toContainReactComponent(Popover); }); it('renders in landscape mode by default', () => { - const videoCard = mountWithApp(); - expect(videoCard.find('div', {className: 'PortraitContainer'})).toBeNull(); - }); + const videoCard = mountWithApp(); - it('renders in portrait mode if specified', () => { - const videoCard = mountWithApp( - , - ); - expect( - videoCard.find('div', {className: 'Container PortraitContainer'}), - ).not.toBeNull(); + expect(videoCard.find('div')).toContainReactComponentTimes('div', 0, { + className: 'portrait', + }); }); }); diff --git a/src/components/VideoThumbnail/README.md b/src/components/VideoThumbnail/README.md index c1cb2782fc5..133b7591acf 100644 --- a/src/components/VideoThumbnail/README.md +++ b/src/components/VideoThumbnail/README.md @@ -1,25 +1,20 @@ --- name: Video thumbnail -category: Structure +category: Images and icons keywords: - - Videothumbnail - - actionable + - video + - VideoThumbnail - updates - new features - video thumbnail - feature thumbnail - - video thumbnail url - - video thumbnail length - - video thumbnail accessibility label - - video thumbnail onClick + - education + - contextual learning system --- # Video thumbnail -The Video thumbnail is used to surface a thumbnail image, play button icon, and timestamp for a -contextual video in the contextual learning system. This component is used when an implementer decides -that they would like to have a thumbnail visible to the user, and only when the user clicks the play button, -then they will surface their video player of choice in the format that they desire (modal, separate div, etc.). +Video thumbnails provide consistent controls for video cards in the contextual learning system. When clicked, conditionally present a video player in the format of your choice. For example, within a modal or a full screen container. --- @@ -64,7 +59,7 @@ Omit the timestamp ## Examples -### Default video thumbnail +### Basic video thumbnail ```jsx -Images included in video thumbnails are implemented as decorative images so that they’re skipped by screen readers. +Images included in video thumbnails are implemented as decorative background images so that they’re skipped by screen readers. -The play button is screen-reader friendly. When tabbed onto by a screen-reader, a video of length 1:20 is read as “Play video of length 1 minute and 20 seconds”, and if no time is provided then the defaulted accessibilityLabel reads “Play video”. +The play button is keyboard accessible and the `aria-label` includes the video length when provided. For example, a video of length 1:20 is read as “Play video of length 1 minute and 20 seconds”. If no video length is provided, the default label reads “Play video”. diff --git a/src/components/VideoThumbnail/VideoThumbnail.scss b/src/components/VideoThumbnail/VideoThumbnail.scss index 5dabb1827b4..25f6067d447 100644 --- a/src/components/VideoThumbnail/VideoThumbnail.scss +++ b/src/components/VideoThumbnail/VideoThumbnail.scss @@ -4,7 +4,7 @@ $start-button-size: 60px; .Thumbnail { position: relative; // Accomodating 16:9 responsive block for video - padding-bottom: 56.25%; + padding-bottom: 9 / 16 * 100%; background-size: cover; background-position: center center; background-repeat: no-repeat; @@ -41,7 +41,8 @@ $start-button-size: 60px; } &:focus { - box-shadow: inset 0.5rem 0.5rem 0 color('indigo'); + outline: none; + @include state(focused); } } @@ -61,9 +62,9 @@ $start-button-size: 60px; padding: 0 spacing(extra-tight); margin-bottom: spacing(tight); margin-left: spacing(tight); - border-radius: border-radius(); - color: color('sky', 'lighter'); - background-color: color('ink'); - opacity: 0.6; + border-radius: var(--p-border-radius-base, border-radius()); + color: var(--p-text, color('sky', 'lighter')); + background-color: var(--p-surface, color('ink')); + opacity: 0.8; text-align: center; } diff --git a/src/components/VideoThumbnail/VideoThumbnail.tsx b/src/components/VideoThumbnail/VideoThumbnail.tsx index 7cc2b7cc051..8e2b9256af8 100644 --- a/src/components/VideoThumbnail/VideoThumbnail.tsx +++ b/src/components/VideoThumbnail/VideoThumbnail.tsx @@ -1,13 +1,17 @@ -import React, {useCallback} from 'react'; +import React from 'react'; import {useI18n} from '../../utilities/i18n'; import {PlayIcon} from './illustrations'; -import {secondsToFormatPretty} from './utilities/time'; +import {secondsToFormatPretty} from './utilities'; + import styles from './VideoThumbnail.scss'; export interface VideoThumbnailProps { thumbnailUrl: string; videoLength?: number; + /** Custom ARIA label for play button. + * @default 'Play video' + */ accessibilityLabel?: string; onClick(): void; onBeforeStartPlaying?(): void; @@ -16,54 +20,53 @@ export interface VideoThumbnailProps { export const VideoThumbnail = ({ thumbnailUrl, videoLength, + /** Custom ARIA label for play button. + * @default 'Play video' + */ accessibilityLabel, onClick, - onBeforeStartPlaying = noop, + onBeforeStartPlaying, }: VideoThumbnailProps) => { const i18n = useI18n(); - const videoLengthParsed = videoLength - ? secondsToFormatPretty(videoLength) + const videoLengthParsed = secondsToFormatPretty(videoLength); + const length = videoLengthParsed + ? `${ + videoLengthParsed!.hours + ? i18n.translate('Polaris.VideoThumbnail.hoursLabel', { + hours: videoLengthParsed!.hours, + }) + : '' + } ${ + videoLengthParsed!.minutes + ? i18n.translate('Polaris.VideoThumbnail.minutesLabel', { + minutes: videoLengthParsed!.minutes, + }) + : '' + } ${ + videoLengthParsed!.seconds + ? i18n.translate('Polaris.VideoThumbnail.secondsLabel', { + seconds: videoLengthParsed!.seconds, + }) + : '' + }` : null; - const getTimeLabel = useCallback(() => { - return `${ - videoLengthParsed && videoLengthParsed!.hours - ? i18n.translate('Polaris.VideoThumbnail.hoursLabel', { - hours: videoLengthParsed!.hours, - }) - : '' - } ${ - videoLengthParsed && videoLengthParsed!.minutes - ? i18n.translate('Polaris.VideoThumbnail.minutesLabel', { - minutes: videoLengthParsed!.minutes, - }) - : '' - } ${ - videoLengthParsed && videoLengthParsed!.seconds - ? i18n.translate('Polaris.VideoThumbnail.secondsLabel', { - seconds: videoLengthParsed!.seconds, - }) - : '' - }`; - }, [i18n, videoLengthParsed]); + const defaultLabel = length + ? i18n.translate('Polaris.VideoThumbnail.playButtonWithTime', { + length, + }) + : i18n.translate('Polaris.VideoThumbnail.playButtonDefault'); + + const buttonLabel = accessibilityLabel ? accessibilityLabel : defaultLabel; - const buttonLabel = - accessibilityLabel || - (videoLengthParsed - ? `${i18n.translate( - 'Polaris.VideoThumbnail.playButtonWithTime', - )}${getTimeLabel()}` - : i18n.translate('Polaris.VideoThumbnail.playButtonDefault')); - const videoLengthMarkup = videoLength ? ( + const videoLengthMarkup = videoLengthParsed ? (

{videoLengthParsed!.timeLabel}

) : null; return (