Skip to content

Commit

Permalink
Merge pull request #9698 from bbc/frosted-glass-promo
Browse files Browse the repository at this point in the history
Frosted Glass Promos
  • Loading branch information
ryanmccombe authored Nov 25, 2021
2 parents 07c8cb4 + dbefa97 commit adf1477
Show file tree
Hide file tree
Showing 20 changed files with 1,756 additions and 1,986 deletions.
2 changes: 1 addition & 1 deletion puppeteer/bundleRequests.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ describe('Js bundle requests', () => {
.forEach(url => {
expect(url).toMatch(
new RegExp(
`(\\/static\\/js\\/(?:comscore\\/)?(main|framework|commons|shared|${serviceRegex}|.+Page).+?.js)|(\\/static\\/.+?-lib.+?.js)`,
`(\\/static\\/js\\/(?:comscore\\/)?(main|framework|commons|shared|${serviceRegex}|frosted_promo|.+Page).+?.js)|(\\/static\\/.+?-lib.+?.js)`,
'g',
),
);
Expand Down
83 changes: 83 additions & 0 deletions src/app/components/FrostedGlassPromo/FrostedGlassPanel.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import React from 'react';
import styled from '@emotion/styled';
import { node, string } from 'prop-types';

import { GEL_SPACING_DBL } from '@bbc/gel-foundations/spacings';
import useImageColour from '../../hooks/useImageColour';

const BLUR_RADIUS = 15;
const FALLBACK_COLOUR = '#202224';

const Wrapper = styled.div`
position: relative;
overflow: hidden;
`;
const scaleAmount = 1 + BLUR_RADIUS / 100;
const scaleX = `scaleX(${scaleAmount})`;
const scaleY = `scaleY(${-1 * scaleAmount})`;

const Background = styled.div`
display: none;
@supports (filter: blur(${BLUR_RADIUS}px)) {
display: block;
z-index: 1;
position: absolute;
bottom: 0;
top: -${BLUR_RADIUS}px;
left: 0;
right: 0;
background: ${FALLBACK_COLOUR} url('${({ image }) => image}');
background-repeat: no-repeat;
background-size: cover;
background-position: bottom;
transform: ${scaleX} ${scaleY};
filter: blur(${BLUR_RADIUS}px);
}
`;

const Overlay = styled.div`
z-index: 2;
position: absolute;
bottom: 0;
top: 0;
left: 0;
right: 0;
transition: background 0.5s ease-in-out;
background: rgb(${({ colour }) => `${colour.join(',')}`});
${({ isLoading, colour }) =>
!isLoading &&
`
@supports (filter: blur(${BLUR_RADIUS}px)) {
background: rgba(${`${colour.join(',')}, 0.62`});
}
`}
`;

const Children = styled.div`
position: relative;
z-index: 3;
padding-bottom: ${GEL_SPACING_DBL};
`;

const FrostedGlassPanel = ({ image, children }) => {
const { isLoading, colour } = useImageColour(image, {
fallbackColour: FALLBACK_COLOUR,
minimumContrast: 10,
contrastColour: '#ffffff',
});

return (
<Wrapper>
<Background image={image} />
<Overlay colour={colour.rgb} isLoading={isLoading} />
<Children>{children}</Children>
</Wrapper>
);
};

FrostedGlassPanel.propTypes = {
image: string.isRequired,
children: node.isRequired,
};

export default FrostedGlassPanel;
21 changes: 21 additions & 0 deletions src/app/components/FrostedGlassPromo/TimestampFooter.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import styled from '@emotion/styled';

import { getSansRegular } from '@bbc/psammead-styles/font-styles';
import { GEL_GROUP_2_SCREEN_WIDTH_MIN } from '@bbc/gel-foundations/breakpoints';
import { GEL_SPACING, GEL_SPACING_DBL } from '@bbc/gel-foundations/spacings';

import Timestamp from '../../containers/StoryPromo/Timestamp';

const StyledTimestamp = styled(Timestamp)`
${({ service }) => service && getSansRegular(service)}
color: white;
font-size: 0.8125rem;
padding: 0.625rem ${GEL_SPACING} 0 ${GEL_SPACING};
@media (min-width: ${GEL_GROUP_2_SCREEN_WIDTH_MIN}) {
font-size: 0.875rem;
padding: 0.625rem ${GEL_SPACING_DBL} 0 ${GEL_SPACING_DBL};
}
`;

export default StyledTimestamp;
565 changes: 565 additions & 0 deletions src/app/components/FrostedGlassPromo/__snapshots__/index.test.jsx.snap

Large diffs are not rendered by default.

99 changes: 99 additions & 0 deletions src/app/components/FrostedGlassPromo/fixtures.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
export const promoProps = {
children:
'Además de sus decenas de museos tradicionales, Ciudad de México es una galería al aire libre de arte callejero, en la que exponen artistas nacionales y extranjeros. Lo invitamos a un recorrido.',
footer: '4 mayo 2021',
url: '/mundo/internacional-23038380',
image: {
ratio: 52,
src: 'https://ichef.bbci.co.uk/news/400/cpsdevpb/DDFC/test/_63482865_orange2.jpg',
alt: 'Orange 2',
width: 976,
},
};

export const linkPromoFixture = {
item: {
name: 'Man City vs West Ham: Bad weather force Premier League to cancel Sunday match',
summary:
'Man City vs West Ham: Bad weather force Premier League to cancel Sunday match',
indexImage: {
id: '63731494',
subType: 'index',
href: 'http://b.files.bbci.co.uk/C105/test/_63731494__110870927_climategrief5.jpg',
path: '/cpsdevpb/C105/test/_63731494__110870927_climategrief5.jpg',
height: 351,
width: 624,
altText: 'E dey good for your',
caption: 'E dey good for your wellbeing to reconnect wit nature',
copyrightHolder: 'BBC',
type: 'image',
},
uri: 'https://www.bbc.com/pidgin/sport-51434980',
contentType: 'RadioBulletin',
assetTypeCode: 'PRO',
timestamp: 1581941235000,
type: 'link',
},
dir: 'ltr',
displayImage: true,
displaySummary: false,
eventTrackingData: {
block: {
componentName: 'features',
},
},
};

export const cpsPromoFixture = {
item: {
headlines: {
headline: 'El colorido encanto del arte callejero chilango 17',
},
locators: {
assetUri: '/mundo/internacional-23038380',
cpsUrn: 'urn:bbc:content:assetUri:mundo/internacional-23038380',
assetId: '23038380',
},
summary:
'Además de sus decenas de museos tradicionales, Ciudad de México es una galería al aire libre de arte callejero, en la que exponen artistas nacionales y extranjeros. Lo invitamos a un recorrido.',
timestamp: 1462445134000,
language: 'es',
byline: {
name: 'Aled Scourfield',
title: 'BBC News, Wales',
persons: [
{
name: 'Aled Scourfield',
function: 'BBC News, Wales',
},
],
},
cpsType: 'STY',
indexImage: {
id: '63482865',
subType: 'index',
href: 'http://b.files.bbci.co.uk/DDFC/test/_63482865_orange2.jpg',
path: '/cpsdevpb/DDFC/test/_63482865_orange2.jpg',
height: 549,
width: 976,
altText: 'Orange 2',
caption: 'Orange 2',
copyrightHolder: 'BBC',
type: 'image',
},
options: {
isBreakingNews: false,
isFactCheck: false,
},
id: 'urn:bbc:ares::asset:mundo/internacional-23038380',
type: 'cps',
},
dir: 'ltr',
displayImage: true,
displaySummary: false,
eventTrackingData: {
block: {
componentName: 'features',
},
},
};
96 changes: 96 additions & 0 deletions src/app/components/FrostedGlassPromo/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import React, { useContext } from 'react';
import { shape, node, string, number } from 'prop-types';
import styled from '@emotion/styled';
import pick from 'ramda/src/pick';

import { getSerifRegular } from '@bbc/psammead-styles/font-styles';
import { GEL_GROUP_2_SCREEN_WIDTH_MIN } from '@bbc/gel-foundations/breakpoints';
import { GEL_SPACING, GEL_SPACING_DBL } from '@bbc/gel-foundations/spacings';

import useClickTrackerHandler from '#hooks/useClickTrackerHandler';
import { ServiceContext } from '#contexts/ServiceContext';
import FrostedGlassPanel from './FrostedGlassPanel';

import ImageWithPlaceholder from '../../containers/ImageWithPlaceholder';

import withData from './withData';

const Body = styled.div`
${({ service }) => service && getSerifRegular(service)}
color: white;
font-size: 0.9375rem;
line-height: 1.33;
padding: 0.625rem ${GEL_SPACING} 0 ${GEL_SPACING};
@media (min-width: ${GEL_GROUP_2_SCREEN_WIDTH_MIN}) {
font-size: 1rem;
line-height: 1.25;
padding: 0.875rem ${GEL_SPACING_DBL} 0 ${GEL_SPACING_DBL};
}
`;

const Wrapper = styled.a`
display: inline-block;
width: 100%;
max-width: 400px;
text-decoration: none;
&:hover,
&:focus {
${Body} {
text-decoration: underline;
}
}
`;

const FrostedGlassPromo = props => {
const { script, service } = useContext(ServiceContext);
const { image, children, footer, url, eventTrackingData } = props;

const clickTracker = useClickTrackerHandler({
...(eventTrackingData || {}),
url,
});

const onClick = eventTrackingData ? clickTracker : () => {};

return (
<Wrapper href={url} onClick={onClick}>
<ImageWithPlaceholder
darkMode
{...pick(
['src', 'srcset', 'sizes', 'alt', 'ratio', 'width', 'height'],
image,
)}
/>
<FrostedGlassPanel image={image.smallSrc || image.src}>
<Body script={script} service={service}>
{children}
</Body>
{footer}
</FrostedGlassPanel>
</Wrapper>
);
};

FrostedGlassPromo.propTypes = {
children: node.isRequired,
url: string.isRequired,
footer: node,
eventTrackingData: shape({}),
image: shape({
src: string.isRequired,
alt: string.isRequired,
ratio: number.isRequired,
width: number.isRequired,
height: number.isRequired,
smallSrc: string,
srcset: string,
sizes: string,
}).isRequired,
};

FrostedGlassPromo.defaultProps = {
footer: null,
eventTrackingData: null,
};

export default withData(FrostedGlassPromo);
74 changes: 74 additions & 0 deletions src/app/components/FrostedGlassPromo/index.stories.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import React from 'react';
import { withServicesKnob } from '@bbc/psammead-storybook-helpers';
import { withKnobs, text } from '@storybook/addon-knobs';

import { RequestContextProvider } from '#contexts/RequestContext';
import { ServiceContextProvider } from '#contexts/ServiceContext';
import { ToggleContextProvider } from '#contexts/ToggleContext';

import Promo from '.';
import { cpsPromoFixture, linkPromoFixture } from './fixtures';

// eslint-disable-next-line react/prop-types
const Wrappers = ({ service, variant, children }) => {
return (
<ServiceContextProvider service={service} variant={variant}>
<RequestContextProvider isAmp={false} service={service}>
<ToggleContextProvider
toggles={{
eventTracking: { enabled: false },
}}
>
{children}
</ToggleContextProvider>
</RequestContextProvider>
</ServiceContextProvider>
);
};

const Component = props => {
const imageUrl = text(
'Image URL',
'https://ichef.bbci.co.uk/news/976/cpsprodpb/189F/production/_121530360_hi071904982.jpg',
);
const mainBody = text(
'Main Body',
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean quam magna, lacinia ut arcu in, vulputate ultricies lectus. Vestibulum purus ligula, finibus vel ultrices in, pretium non neque. Sed mauris ante, mollis ac metus fermentum, vestibulum malesuada felis. Nullam a congue mauris. Nulla venenatis felis ac eleifend rutrum.',
);
return (
<Wrappers {...props}>
<Promo
image={{ src: imageUrl, alt: '', width: 500, height: 250, ratio: 52 }}
url="#"
>
{mainBody}
</Promo>
</Wrappers>
);
};

const WithCPSPromoData = props => {
return (
<Wrappers {...props}>
<Promo {...cpsPromoFixture} />
</Wrappers>
);
};

const WithLinkPromoData = props => {
return (
<Wrappers {...props}>
<Promo {...linkPromoFixture} />
</Wrappers>
);
};

export default {
title: 'Components/Frosted Glass Promo',
Component,
decorators: [withKnobs, withServicesKnob()],
};

export const Standalone = Component;
export const CPSPromo = WithCPSPromoData;
export const LinkPromo = WithLinkPromoData;
Loading

0 comments on commit adf1477

Please sign in to comment.