Skip to content

Commit

Permalink
feat(Video + Markdown): update react player + put videos in markdown
Browse files Browse the repository at this point in the history
Updates the react-player package to the newest version + makes the video component the default render component for iframed Vimeos + Youtubes
  • Loading branch information
dreamwasp authored Oct 15, 2024
1 parent 89afde6 commit 4170f40
Show file tree
Hide file tree
Showing 12 changed files with 164 additions and 69 deletions.
2 changes: 1 addition & 1 deletion packages/gamut/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"react-aria-tabpanel": "^4.4.0",
"react-focus-on": "^3.5.1",
"react-hook-form": "^7.21.2",
"react-player": "^2.3.1",
"react-player": "^2.16.0",
"react-select": "^5.2.2",
"react-truncate-markup": "^5.1.2",
"react-use": "^15.3.8",
Expand Down
71 changes: 54 additions & 17 deletions packages/gamut/src/Markdown/__tests__/Markdown.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ import * as React from 'react';

import { Markdown } from '../index';

const mockTitle = 'a fake youtube';

jest.mock('react-player', () => ({
__esModule: true,
default: () => <iframe title={mockTitle} />,
}));

const basicMarkdown = `
# Heading 1
Expand Down Expand Up @@ -36,12 +43,35 @@ const youtubeMarkdown = `
<iframe src="https://www.youtube.com/embed/KvgrQIK1yPY" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
`;

const vimeoMarkdown = `
<iframe src="https://player.vimeo.com/video/188237476?badge=0&amp;autopause=0&amp;player_id=0&amp;app_id=58479" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
`;

const videoMarkdown = `
<video src="/example.webm" title="video" />
`;

const videoSourceMarkdown = `
<video controls poster="/images/spaceghost.gif">
<source src="movie.mp4" type="video/mp4">
<source src="movie.ogg" type="video/ogg">
</video>
`;

const checkboxMarkdown = `
- [ ] checkbox
- [x] default checked checkbox
- [ ] third checkbox
`;

const table = `
| Tables | Are | Cool |
|----------|:-------------:|------:|
| col 1 is | left-aligned | $1600 |
| col 2 is | centered | $12 |
| col 3 is | right-aligned | $1 |
`;

const renderView = setupRtl(Markdown);

describe('<Markdown />', () => {
Expand All @@ -60,27 +90,13 @@ describe('<Markdown />', () => {
});

it('Renders custom tables in markdown', () => {
const table = `
| Tables | Are | Cool |
|----------|:-------------:|------:|
| col 1 is | left-aligned | $1600 |
| col 2 is | centered | $12 |
| col 3 is | right-aligned | $1 |
`;
renderView({ text: table });
expect(document.querySelectorAll('div.tableWrapper table').length).toEqual(
1
);
});

it('Skips rendering custom tables in markdown when skipProcessing.table is true', () => {
const table = `
| Tables | Are | Cool |
|----------|:-------------:|------:|
| col 1 is | left-aligned | $1600 |
| col 2 is | centered | $12 |
| col 3 is | right-aligned | $1 |
`;
renderView({
skipDefaultOverrides: { table: true },
text: table,
Expand All @@ -91,9 +107,28 @@ describe('<Markdown />', () => {
);
});

it('Wraps youtube iframes in a flexible container', () => {
it('Renders YouTube iframes using the Video component', () => {
renderView({ text: youtubeMarkdown });
screen.getByTitle(mockTitle);
});

it('Renders Vimeo iframes using the Video component', () => {
renderView({ text: vimeoMarkdown });
screen.getByTitle(mockTitle);
});

it('Renders video tags using the Video component if they have an src', () => {
renderView({ text: videoMarkdown });
screen.getByTitle(mockTitle);
});
it('Renders video tags using the Video component if they have an src', () => {
renderView({ text: videoSourceMarkdown });
expect(screen.queryByTitle(mockTitle)).toBeNull();
});

it('Renders YouTube iframes using the Video component', () => {
renderView({ text: youtubeMarkdown });
screen.getByTestId('yt-iframe');
screen.getByTitle(mockTitle);
});

it('Wraps the markdown in a div by default (block)', () => {
Expand Down Expand Up @@ -288,6 +323,7 @@ var test = true;
isCodeBlock?: boolean;
isWebBrowser?: boolean;
};

const renderedProps: jest.Mock<RenderedProps> = jest.fn();

beforeEach(() => {
Expand All @@ -296,9 +332,10 @@ var test = true;
<TestComponent name="my name" isCodeBlock="true" isWebBrowser />
`;

const TestComponent = (props: any) => {
renderedProps(props);
return <strong {...props}>attr-testing-component</strong>;
return <strong>attr-testing-component</strong>;
};

const overrides = {
Expand Down
6 changes: 6 additions & 0 deletions packages/gamut/src/Markdown/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
MarkdownAnchorProps,
} from './libs/overrides/MarkdownAnchor';
import { Table } from './libs/overrides/Table';
import { MarkdownVideo } from './libs/overrides/Video';
import { createPreprocessingInstructions } from './libs/preprocessing';
import { defaultSanitizationConfig } from './libs/sanitizationConfig';
// eslint-disable-next-line gamut/no-css-standalone
Expand All @@ -40,6 +41,7 @@ export type SkipDefaultOverridesSettings = {
details?: boolean;
iframe?: boolean;
table?: boolean;
video?: boolean;
};

export type MarkdownProps = {
Expand Down Expand Up @@ -114,6 +116,10 @@ export class Markdown extends PureComponent<MarkdownProps> {
component: Table,
allowedAttributes: ['style'],
}),
!skipDefaultOverrides.video &&
createTagOverride('video', {
component: MarkdownVideo,
}),
!skipDefaultOverrides.details &&
createTagOverride('details', {
component: Details,
Expand Down
26 changes: 9 additions & 17 deletions packages/gamut/src/Markdown/libs/overrides/Iframe/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/* eslint-disable jsx-a11y/iframe-has-title */
import { FunctionComponent, HTMLAttributes } from 'react';

// eslint-disable-next-line gamut/no-css-standalone
import styles from './styles.module.scss';
import { Video } from '../../../../Video';

export interface IframeProps extends HTMLAttributes<HTMLIFrameElement> {
allow?: string;
src?: string;
title?: string;
width?: number;
Expand All @@ -19,23 +19,15 @@ export const Iframe: FunctionComponent<IframeProps> = (props) => {
props.src &&
[YOUTUBE_PATTERN, VIMEO_PATTERN].some((pattern) => pattern.test(props.src!))
) {
const { width = 16, height = 9 } = props;
const ratioPadding = (
(Math.round(height) / Math.round(width)) *
100
).toFixed(2);
const wrapperStyles = {
paddingBottom: `${ratioPadding}%`,
};
return (
<div
className={styles.youtubeVideoWrapper}
data-testid="yt-iframe"
style={wrapperStyles}
>
<iframe {...props} />
</div>
<Video
height={props?.height}
width={props?.width}
videoUrl={props?.src}
videoTitle={props?.title}
/>
);
}

return <iframe {...props} />;
};

This file was deleted.

30 changes: 30 additions & 0 deletions packages/gamut/src/Markdown/libs/overrides/Video/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/* eslint-disable jsx-a11y/iframe-has-title */
import { DetailedHTMLProps, VideoHTMLAttributes } from 'react';

import { Video } from '../../../../Video';
// eslint-disable-next-line gamut/no-css-standalone

export type MarkdownVideoProps = DetailedHTMLProps<
VideoHTMLAttributes<HTMLVideoElement>,
HTMLVideoElement
>;

export const MarkdownVideo: React.FC<MarkdownVideoProps> = (props) => {
if (props?.src) {
// Sanitize the props to pass to the Video component
const videoProps = {
autoplay: props?.autoPlay,
controls: props?.controls,
height: Number(props?.height),
loop: props?.loop,
muted: props?.muted,
videoTitle: props?.title,
videoUrl: props?.src,
width: Number(props?.width),
};

return <Video {...videoProps} />;
}
// eslint-disable-next-line jsx-a11y/media-has-caption
return <video {...props} />;
};
12 changes: 11 additions & 1 deletion packages/gamut/src/Markdown/libs/sanitizationConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,17 @@ export const defaultSanitizationConfig = {
source: ['src', 'type'],
img: ['src', 'alt', 'height', 'width', 'title', 'aria-label', 'style'],
input: ['checked', 'type'],
video: ['width', 'height', 'align', 'style', 'controls'],
video: [
'align',
'autoPlay',
'controls',
'height',
'loop',
'muted',
'src',
'style',
'width',
],
iframe: [
'src',
'width',
Expand Down
45 changes: 31 additions & 14 deletions packages/gamut/src/Video/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,47 +27,64 @@ const OverlayPlayButton = ({ videoTitle }: { videoTitle?: string }) => {
export type ReactPlayerWithWrapper = ReactPlayer & { wrapper: HTMLElement };

export type VideoProps = {
videoUrl: string;
videoTitle?: string;
placeholderImage?: string | boolean;
autoplay?: boolean;
className?: string;
controls?: boolean;
height?: number;
loop?: boolean;
muted?: boolean;
className?: string;
onReady?: (player: ReactPlayerWithWrapper) => void;
onPlay?: () => void;
onReady?: (player: ReactPlayerWithWrapper) => void;
placeholderImage?: string | boolean;
videoTitle?: string;
videoUrl: string;
width?: number;
};

export const Video: React.FC<VideoProps> = ({
videoUrl,
videoTitle,
placeholderImage,
autoplay,
className,
controls,
height,
loop,
muted,
className,
onReady,
onPlay,
onReady,
placeholderImage,
videoTitle,
videoUrl,
width,
}) => {
const [loading, setLoading] = useState(true);
const isMounted = useIsMounted();

const config = {
youtube: {
playerVars: { color: 'white' },
},
vimeo: {
title: videoTitle,
},
};

return (
<div
className={cx(styles.videoWrapper, loading && styles.loading, className)}
>
{isMounted ? (
<ReactPlayer
url={videoUrl}
light={placeholderImage}
title={videoTitle}
playing={autoplay}
className={styles.iframe}
config={config}
controls={controls === undefined ? true : controls}
height={height}
light={placeholderImage}
loop={loop}
muted={muted}
playIcon={<OverlayPlayButton videoTitle={videoTitle} />}
playing={autoplay}
title={videoTitle}
url={videoUrl}
width={width}
onReady={(player: ReactPlayerWithWrapper) => {
onReady?.(player);
setLoading(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ import { Video } from '@codecademy/gamut';
export const VideoWithPlaceholder = () => {
return (
<Video
placeholder="https://i.ytimg.com/vi/1y_kfWUCFDQ/maxresdefault.jpg"
videoUrl="https://www.youtube.com/watch?v=Yl8yy5tpVIM"
videoTitle="Workout with Rick Sanchez"
videoUrl="https://player.vimeo.com/video/188237476"
videoTitle="A Dream Within a Dream"
placeholderImage="https://placekitten.com/400/300"
autoplay
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Tip: Some videos might have settings where you cannot share/embed them, make sur
</Story>
</Canvas>

## Here is an example with a Viemo URL
## Here is an example with a Vimeo URL

<Canvas>
<Story name="Vimeo video URL">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -347,4 +347,18 @@ Use the `printf()` function.

### Iframes

<iframe src="https://player.vimeo.com/video/145702525?byline=0&portrait=0&badge=0" width="640" height="360" frameborder="0" allow="autoplay; fullscreen" allowfullscreen></iframe>
Vimeo and Youtube video iframes will be rendered by our Video component, otherwise they'll render the original code.

<iframe src="https://player.vimeo.com/video/188237476?badge=0&amp;autopause=0&amp;player_id=0&amp;app_id=58479" frameborder="0" allow="autoplay; fullscreen; picture-in-picture; clipboard-write" style="position:absolute;top:0;left:0;width:100%;height:100%;" title="Studio Ghibli in Real Life"></iframe>

<br/>

<iframe width="1094" height="842" src="https://www.youtube.com/embed/zhDwjnYZiCo" title="Ghibli Coffee Shop ☕️ Music to put you in a better mood 🌿 lofi hip hop - lofi songs | study / relax" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>


### Video

`video`s with an `src` will be rendered by our Video component, otherwise they'll render as stated.

<video src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm" title="video" />

Loading

0 comments on commit 4170f40

Please sign in to comment.