Skip to content

Commit

Permalink
feat(video-player): migrate caem-video-player features (#12113)
Browse files Browse the repository at this point in the history
### Related Ticket(s)

[ADCMS-6664](https://jsw.ibm.com/browse/ADCMS-6664)

### Description

Migrates features from `caem-video-player` and `caem-video-player-container` mainly `intersection-mode`, which works for scrolling video into view, as well as multiple card in card with video in a carousel.

### Testing instructions

* Browse to Video player > Autoplay to test standalone video player intersection mode
* Browse to Card in Card > With carousel video to test the intersection mode within a `<c4d-carousel>` component

### Changelog

**New**

- `intersection-mode` attribute for `<c4d-video-player>` which will auto play/pause as the video scrolls into / out of the viewport

<!-- React and Web Component deploy previews are enabled by default. -->
<!-- To enable additional available deploy previews, apply the following -->
<!-- labels for the corresponding package: -->
<!-- *** "test: e2e": Codesandbox examples and e2e integration tests -->
<!-- *** "package: services": Services -->
<!-- *** "package: utilities": Utilities -->
<!-- *** "RTL": React / Web Components (RTL) -->
<!-- *** "feature flag": React / Web Components (experimental) -->
  • Loading branch information
m4olivei authored Dec 23, 2024
1 parent 30de2fd commit cb49cbc
Show file tree
Hide file tree
Showing 10 changed files with 611 additions and 93 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright IBM Corp. 2016, 2023
* Copyright IBM Corp. 2016, 2024
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
Expand Down
73 changes: 60 additions & 13 deletions packages/styles/scss/components/video-player/_video-player.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,17 @@
@use '@carbon/styles/scss/spacing' as *;
@use '@carbon/styles/scss/theme' as *;
@use '@carbon/styles/scss/type' as *;
@use '@carbon/styles/scss/utilities/convert' as *;
@use '@carbon/styles/scss/components/button/vars' as *;
$aspect-ratios: ((16, 9), (9, 16), (2, 1), (1, 2), (4, 3), (3, 4), (1, 1));

@mixin video-player {
// Make the video player container a block for purposes of the intersection
// observer.
:host(#{$c4d-prefix}-video-player-container) {
display: block;
}

:host(#{$c4d-prefix}-video-player),
.#{$c4d-prefix}--video-player {
color: var(--#{$c4d-prefix}--video-caption--color, $text-secondary);
Expand All @@ -26,19 +34,6 @@ $aspect-ratios: ((16, 9), (9, 16), (2, 1), (1, 2), (4, 3), (3, 4), (1, 1));
inline-size: 100%;
}

&:focus {
outline: none;
.#{$c4d-prefix}--video-player__video-container {
&::before {
position: absolute;
z-index: 1;
border: 1px solid $focus-inverse;
content: ' ';
inset: $spacing-01;
outline: $spacing-01 solid $focus;
}
}
}
#{$c4d-prefix}-image {
padding-block-start: 0;
}
Expand Down Expand Up @@ -129,6 +124,8 @@ $aspect-ratios: ((16, 9), (9, 16), (2, 1), (1, 2), (4, 3), (3, 4), (1, 1));
&.#{$c4d-prefix}--video-player__aspect-ratio {
&--#{$width}x#{$height} {
@include ratio-base($width, $height, true);

overflow: visible;
}
}
}
Expand Down Expand Up @@ -202,4 +199,54 @@ $aspect-ratios: ((16, 9), (9, 16), (2, 1), (1, 2), (4, 3), (3, 4), (1, 1));
}
}
}

.#{$c4d-prefix}--video-player__toggle-playback {
position: absolute;
z-index: 100;
padding: 0.875rem;
border: 0;
background-color: $overlay;
block-size: $spacing-09;
color: #ffffff;
inline-size: $spacing-09;

&--top-left {
inset-block-start: 0;
inset-inline-start: 0;
}

&--top-right {
inset-block-start: 0;
inset-inline-end: 0;
}

&--bottom-right {
inset-block-end: 0;
inset-inline-end: 0;
}

&--bottom-left {
inset-block-end: 0;
inset-inline-start: 0;
}

/* stylelint-disable-next-line caem/require-color-with-bg */
&:hover {
// Grey 100, more opaque.
background-color: rgba(22, 22, 22, 0.9);
cursor: pointer;
}

&:focus {
outline: 2px solid $focus;
}
}

// Prevent any pointer events from getting through to the slotted player div
// when in intersection mode.
:host(#{$c4d-prefix}-video-player[intersection-mode]) {
::slotted(.#{$c4d-prefix}--video-player__video) {
pointer-events: none;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* @license
*
* Copyright IBM Corp. 2021, 2023
* Copyright IBM Corp. 2021, 2024
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
Expand All @@ -12,16 +12,21 @@ import '../../image/image';
import '../index';
import '../../cta/card-cta-footer';
import '../../cta/video-cta-container';
import '../../carousel/carousel';
import '../../video-player/video-player-container';
import { html } from 'lit';
import { ifDefined } from 'lit/directives/if-defined.js';
import { boolean } from '@storybook/addon-knobs';
import { boolean, select, text } from '@storybook/addon-knobs';
import ArrowRight20 from '@carbon/web-components/es/icons/arrow--right/20.js';

import imgXlg16x9 from '../../../../.storybook/storybook-images/assets/1312/fpo--16x9--1312x738--005.jpg';
import imgMd16x9 from '../../../../.storybook/storybook-images/assets/960/fpo--16x9--960x540--005.jpg';
import imgSm4x3 from '../../../../.storybook/storybook-images/assets/480/fpo--4x3--480x360--005.jpg';

import readme from './README.stories.mdx';
import textNullable from '../../../../.storybook/knob-text-nullable';
import { BUTTON_POSITION } from '../../video-player/defs';
import { enumValsToArray } from '../../../globals/internal/enum-helpers';

export const Default = (args) => {
const { video, eyebrow, heading, defaultSrc, alt, href } =
Expand Down Expand Up @@ -60,23 +65,8 @@ export const Default = (args) => {
`;
};

export default {
title: 'Components/Card in card',
decorators: [
(story) => html`
<div class="cds--grid">
<div class="cds--row">
<div class="cds--col-lg-12 cds--no-gutter">${story()}</div>
</div>
</div>
`,
],
Default.story = {
parameters: {
percy: {
skip: true,
},
...readme.parameters,
hasStoryPadding: true,
knobs: {
'c4d-card-in-card': () => {
const video = boolean('video', false);
Expand Down Expand Up @@ -105,6 +95,93 @@ export default {
};
},
},
},
};

export const WithCarouselVideo = (args) => {
const { videoId, thumbnail, buttonPosition } =
args?.['WithCarouselVideo'] ?? {};

return html`
<c4d-carousel page-size="1">
<c4d-card-in-card href="https://ibm.com">
<c4d-card-eyebrow>Experience // Case Study</c4d-card-eyebrow>
<c4d-card-heading
>Putting innovation in the driver's seat</c4d-card-heading
>
<c4d-card-cta-footer
>Link ${ArrowRight20({ slot: 'icon' })}</c4d-card-cta-footer
>
<c4d-video-player-container
slot="image"
aspect-ratio="16x9"
playing-mode="inline"
video-id="${videoId}"
hide-caption
thumbnail="${thumbnail}"
intersection-mode
button-position="${buttonPosition}"></c4d-video-player-container>
</c4d-card-in-card>
<c4d-card-in-card href="https://ibm.com">
<c4d-card-eyebrow>Experience // Case Study</c4d-card-eyebrow>
<c4d-card-heading
>Putting innovation in the driver's seat</c4d-card-heading
>
<c4d-card-cta-footer
>Link ${ArrowRight20({ slot: 'icon' })}</c4d-card-cta-footer
>
<c4d-video-player-container
slot="image"
aspect-ratio="16x9"
playing-mode="inline"
video-id="${videoId}"
hide-caption
thumbnail="${thumbnail}"
intersection-mode
button-position="${buttonPosition}"></c4d-video-player-container>
</c4d-card-in-card>
</c4d-carousel>
`;
};

WithCarouselVideo.story = {
name: 'With carousel video',
parameters: {
knobs: {
WithCarouselVideo: () => {
return {
video: null,
videoId: text('Video id (videoId):', '0_ibuqxqbe'),
thumbnail: text('Custom thumbnail (thumbnail):', ''),
buttonPosition: select(
'Button position (buttonPosition)',
enumValsToArray(BUTTON_POSITION),
BUTTON_POSITION.BOTTOM_LEFT
),
};
},
},
},
};

export default {
title: 'Components/Card in card',
decorators: [
(story) => html`
<div class="cds--grid">
<div class="cds--row">
<div class="cds--col-lg-12 cds--no-gutter">${story()}</div>
</div>
</div>
`,
],
parameters: {
percy: {
skip: true,
},
...readme.parameters,
hasStoryPadding: true,
propsSet: {
default: {
'c4d-card-in-card': {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ class C4DLightboxVideoPlayerComposite extends ModalRenderMixin(
if (videoId) {
this._loadVideoData?.(videoId);
if (open) {
this._embedMedia?.(videoId, false);
this._embedMedia?.(videoId);
this._handleAriaAndHiddenState();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@
*/

import { html } from 'lit';
import { boolean, text } from '@storybook/addon-knobs';
import { boolean, text, select } from '@storybook/addon-knobs';
import { ifDefined } from 'lit/directives/if-defined.js';
import readme from './README.stories.mdx';
import '../video-player-container';
import '../../lightbox-media-viewer/lightbox-video-player-container';
import { enumValsToArray } from '../../../globals/internal/enum-helpers';
import { BUTTON_POSITION } from '../defs';

export const Default = (args) => {
const { caption, hideCaption, thumbnail, videoId } = args?.VideoPlayer ?? {};
Expand Down Expand Up @@ -118,6 +120,26 @@ export const autoplayMuted = (args) => {
`;
};

export const intersectionMode = (args) => {
const {
aspectRatio,
caption,
hideCaption,
thumbnail,
videoId,
buttonPosition,
} = args?.VideoPlayer ?? {};
return html` <c4d-video-player-container
aspect-ratio="${aspectRatio}"
playing-mode="inline"
video-id=${videoId}
caption=${caption}
?hide-caption=${hideCaption}
thumbnail=${thumbnail}
intersection-mode
button-position="${buttonPosition}"></c4d-video-player-container>`;
};

aspectRatio4x3.story = {
name: 'Aspect ratio 4:3',
parameters: {
Expand Down Expand Up @@ -263,6 +285,49 @@ autoplayMuted.story = {
},
};

intersectionMode.story = {
name: 'Intersection mode',
decorators: [
(story) => html`
<p>
Scroll down ⬇️<br />
To illustrate playback beginning only when the video comes into view,
we've added intentional space to push the video below the fold.
</p>
<div style="margin-top: 120vh;">${story()}</div>
`,
],
parameters: {
knobs: {
VideoPlayer: () => {
return {
aspectRatio: '16x9',
caption: text('Custom caption (caption):', ''),
hideCaption: boolean('Hide caption (hideCaption):', false),
thumbnail: text('Custom thumbnail (thumbnail):', ''),
videoId: '0_ibuqxqbe',
buttonPosition: select(
'Button position (buttonPosition)',
enumValsToArray(BUTTON_POSITION),
BUTTON_POSITION.BOTTOM_RIGHT
),
};
},
},
propsSet: {
default: {
VideoPlayer: {
aspectRatio: '16x9',
caption: '',
hideCaption: false,
thumbnail: '',
videoId: '0_ibuqxqbe',
},
},
},
},
};

export default {
title: 'Components/Video player',
decorators: [
Expand Down
9 changes: 8 additions & 1 deletion packages/web-components/src/components/video-player/defs.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* @license
*
* Copyright IBM Corp. 2020, 2021
* Copyright IBM Corp. 2020, 2024
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
Expand Down Expand Up @@ -35,3 +35,10 @@ export enum VIDEO_PLAYER_PLAYING_MODE {
*/
LIGHTBOX = 'lightbox',
}

export enum BUTTON_POSITION {
TOP_LEFT = 'top-left',
TOP_RIGHT = 'top-right',
BOTTOM_RIGHT = 'bottom-right',
BOTTOM_LEFT = 'bottom-left',
}
Loading

0 comments on commit cb49cbc

Please sign in to comment.