From e9ffa0ad726777d3ee80ab9c32db98f045c0dd0a Mon Sep 17 00:00:00 2001 From: "nic.long" Date: Tue, 8 Sep 2020 12:17:19 +0100 Subject: [PATCH] =?UTF-8?q?Youtube=20Atom=20(n=C3=A9e=20MediaAtom)=20(#46)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a YoutubeAtom, a subset of the CAPI MediaAtom. As part of this some tooling changes have been introduced to support the @guardian/types library. --- .storybook/main.js | 32 +++++ .storybook/preview-head.html | 158 +++++++++++++++++++++++- .babelrc.js => babel.config.js | 1 + package.json | 10 +- src/MediaAtom.stories.tsx | 12 -- src/MediaAtom.test.tsx | 13 -- src/MediaAtom.tsx | 7 -- src/YoutubeAtom/MaintainAspectRatio.tsx | 33 +++++ src/YoutubeAtom/YoutubeAtom.stories.tsx | 26 ++++ src/YoutubeAtom/YoutubeAtom.test.tsx | 27 ++++ src/YoutubeAtom/YoutubeAtom.tsx | 79 ++++++++++++ src/YoutubeAtom/YoutubeMeta.tsx | 98 +++++++++++++++ src/YoutubeAtom/YoutubeOverlay.tsx | 52 ++++++++ src/YoutubeAtom/pillarPalette.ts | 31 +++++ src/index.ts | 4 +- src/types.ts | 30 ++++- yarn.lock | 29 ++++- 17 files changed, 600 insertions(+), 42 deletions(-) rename .babelrc.js => babel.config.js (94%) delete mode 100644 src/MediaAtom.stories.tsx delete mode 100644 src/MediaAtom.test.tsx delete mode 100644 src/MediaAtom.tsx create mode 100644 src/YoutubeAtom/MaintainAspectRatio.tsx create mode 100644 src/YoutubeAtom/YoutubeAtom.stories.tsx create mode 100644 src/YoutubeAtom/YoutubeAtom.test.tsx create mode 100644 src/YoutubeAtom/YoutubeAtom.tsx create mode 100644 src/YoutubeAtom/YoutubeMeta.tsx create mode 100644 src/YoutubeAtom/YoutubeOverlay.tsx create mode 100644 src/YoutubeAtom/pillarPalette.ts diff --git a/.storybook/main.js b/.storybook/main.js index c2f3665e..702b9c3f 100644 --- a/.storybook/main.js +++ b/.storybook/main.js @@ -1,4 +1,36 @@ +const path = require('path'); + module.exports = { stories: ['../**/*.stories.tsx'], addons: ['@storybook/preset-typescript'], + webpackFinal: async (config, { configType }) => { + config.module.rules.push({ + test: /\.tsx?$/, + use: [ + { + loader: 'babel-loader', + options: { + presets: [ + '@babel/preset-env', + '@babel/preset-typescript', + '@babel/preset-react', + ], + plugins: [ + 'const-enum', + [ + 'emotion', + { + sourceMap: false, + }, + ], + ], + }, + }, + ], + include: path.resolve(__dirname, '../'), + }); + + // Return the altered config + return config; + }, }; diff --git a/.storybook/preview-head.html b/.storybook/preview-head.html index 8b137891..542a84b7 100644 --- a/.storybook/preview-head.html +++ b/.storybook/preview-head.html @@ -1 +1,157 @@ - + diff --git a/.babelrc.js b/babel.config.js similarity index 94% rename from .babelrc.js rename to babel.config.js index 7f8c58e6..dfc31a90 100644 --- a/.babelrc.js +++ b/babel.config.js @@ -5,6 +5,7 @@ module.exports = { '@babel/preset-react', ], plugins: [ + 'const-enum', [ 'emotion', { diff --git a/package.json b/package.json index 00e380a8..ca2f2a05 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@babel/preset-typescript": "^7.9.0", "@guardian/src-foundations": "^1.0.1", "@guardian/src-icons": "^1.9.1", + "@guardian/types": "^0.4.4", "@storybook/addons": "^5.3.19", "@storybook/preset-typescript": "^3.0.0", "@storybook/react": "^5.3.19", @@ -37,6 +38,7 @@ "@typescript-eslint/eslint-plugin": "^3.0.1", "@typescript-eslint/parser": "^3.0.1", "babel-loader": "^8.1.0", + "babel-plugin-const-enum": "^1.0.1", "babel-plugin-emotion": "^10.0.33", "chromatic": "^5.0.0", "emotion": "^10.0.27", @@ -83,9 +85,9 @@ "react-dom": "^16.13.1" }, "jest": { - "testEnvironment": "jest-environment-jsdom-sixteen", - "moduleNameMapper": { - "^@guardian/src-foundations/(.*)$": "@guardian/src-foundations/$1/cjs" - } + "testEnvironment": "jest-environment-jsdom-sixteen", + "transformIgnorePatterns": [ + "node_modules/(?!(@guardian/src-foundations|@guardian/types)/)" + ] } } diff --git a/src/MediaAtom.stories.tsx b/src/MediaAtom.stories.tsx deleted file mode 100644 index 557a7c09..00000000 --- a/src/MediaAtom.stories.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; - -import { MediaAtom } from './MediaAtom'; - -export default { - title: 'MediaAtom', - component: MediaAtom, -}; - -export const DefaultStory = (): JSX.Element => { - return ; -}; diff --git a/src/MediaAtom.test.tsx b/src/MediaAtom.test.tsx deleted file mode 100644 index cde01cc2..00000000 --- a/src/MediaAtom.test.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react'; -import '@testing-library/jest-dom/extend-expect'; -import { render } from '@testing-library/react'; - -import { MediaAtom } from './MediaAtom'; - -describe('MediaAtom', () => { - it('should render', () => { - const { getByText } = render(); - - expect(getByText('MediaAtom')).toBeInTheDocument(); - }); -}); diff --git a/src/MediaAtom.tsx b/src/MediaAtom.tsx deleted file mode 100644 index bcf84295..00000000 --- a/src/MediaAtom.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import React from 'react'; - -import { MediaAtomType } from './types'; - -export const MediaAtom = ({ id }: MediaAtomType): JSX.Element => ( -
MediaAtom
-); diff --git a/src/YoutubeAtom/MaintainAspectRatio.tsx b/src/YoutubeAtom/MaintainAspectRatio.tsx new file mode 100644 index 00000000..6663009d --- /dev/null +++ b/src/YoutubeAtom/MaintainAspectRatio.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { css } from 'emotion'; + +type Props = { + height: number; + width: number; + children: React.ReactNode; +}; + +export const MaintainAspectRatio = ({ + height, + width, + children, +}: Props): JSX.Element => ( + /* https://css-tricks.com/aspect-ratio-boxes/ */ +
+ {children} +
+); diff --git a/src/YoutubeAtom/YoutubeAtom.stories.tsx b/src/YoutubeAtom/YoutubeAtom.stories.tsx new file mode 100644 index 00000000..e28e085f --- /dev/null +++ b/src/YoutubeAtom/YoutubeAtom.stories.tsx @@ -0,0 +1,26 @@ +import React from 'react'; + +import { YoutubeAtom } from './YoutubeAtom'; +import { Pillar, Display, Design } from '@guardian/types/Format'; + +export default { + title: 'YoutubeAtom', + component: YoutubeAtom, +}; + +export const DefaultStory = (): JSX.Element => { + return ( + + ); +}; diff --git a/src/YoutubeAtom/YoutubeAtom.test.tsx b/src/YoutubeAtom/YoutubeAtom.test.tsx new file mode 100644 index 00000000..6ab3d8f5 --- /dev/null +++ b/src/YoutubeAtom/YoutubeAtom.test.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import '@testing-library/jest-dom/extend-expect'; +import { render } from '@testing-library/react'; +import { Pillar, Display, Design } from '@guardian/types/Format'; +import { YoutubeAtom } from './YoutubeAtom'; + +describe('YoutubeAtom', () => { + it('should render', () => { + const atom = ( + + ); + const { getByTitle } = render(atom); + + expect(getByTitle('My Youtube video!')).toBeInTheDocument(); + }); +}); diff --git a/src/YoutubeAtom/YoutubeAtom.tsx b/src/YoutubeAtom/YoutubeAtom.tsx new file mode 100644 index 00000000..43186238 --- /dev/null +++ b/src/YoutubeAtom/YoutubeAtom.tsx @@ -0,0 +1,79 @@ +import React from 'react'; + +import { YoutubeOverlay } from './YoutubeOverlay'; +import { MaintainAspectRatio } from './MaintainAspectRatio'; +import { YoutubeAtomType } from '../types'; + +type EmbedConfig = { + adsConfig: { + adTagParameters: { + iu: string; + cust_params: string; + }; + }; +}; + +interface AdTargeting { + adUnit: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + customParams: { [key: string]: any }; +} + +const buildEmbedConfig = (adTargeting: AdTargeting): EmbedConfig => { + return { + adsConfig: { + adTagParameters: { + iu: `${adTargeting.adUnit || ''}`, + cust_params: encodeURIComponent( + constructQuery(adTargeting.customParams), + ), + }, + }, + }; +}; + +const constructQuery = (query: { [key: string]: any }): string => + Object.keys(query) + .map((param: any) => { + const value = query[param]; + const queryValue = Array.isArray(value) + ? value.map((v) => encodeURIComponent(v)).join(',') + : encodeURIComponent(value); + return `${param}=${queryValue}`; + }) + .join('&'); + +// Note, this is a subset of the CAPI MediaAtom essentially. +export const YoutubeAtom = ({ + format, + videoMeta, + overlayImage, + adTargeting, + height = 259, + width = 460, + title = 'YouTube video player', + duration, +}: YoutubeAtomType): JSX.Element => { + const embedConfig = + adTargeting && JSON.stringify(buildEmbedConfig(adTargeting)); + + return ( +
+ + {overlayImage && ( + + )} +