diff --git a/packages/modules/src/modules/banners/momentTemplate/MomentTemplateBanner.stories.tsx b/packages/modules/src/modules/banners/momentTemplate/MomentTemplateBanner.stories.tsx index 9b65f690e..e8828a425 100644 --- a/packages/modules/src/modules/banners/momentTemplate/MomentTemplateBanner.stories.tsx +++ b/packages/modules/src/modules/banners/momentTemplate/MomentTemplateBanner.stories.tsx @@ -52,15 +52,19 @@ const GlobalNYBanner = bannerWrapper( mainUrl: 'https://media.guim.co.uk/a1087c3f7e6da4f1e97947acccdd7f0d15f327d4/0_0_142_124/140.png', altText: 'Guardian logo being held up by supporters of the Guardian', + tabletPosition: 'end', + desktopPosition: 'end', }, }), 'global-new-year-banner', ); -const GlobalNYTemplate: Story = (props: BannerProps) => ; +const GlobalNewYearTemplate: Story = (props: BannerProps) => ( + +); -export const GlobalNY = GlobalNYTemplate.bind({}); -GlobalNY.args = { +export const GlobalNewYear = GlobalNewYearTemplate.bind({}); +GlobalNewYear.args = { ...props, content: { heading: 'As 2022 begins, will you support us?', @@ -85,7 +89,7 @@ GlobalNY.args = { }, }, mobileContent: { - heading: 'As 2022 begins, will you support us?', + heading: 'Please support us in 2022', messageText: 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus', paragraphs: [ @@ -108,7 +112,7 @@ GlobalNY.args = { numArticles: 50, }; -const AusElectionBanner = bannerWrapper( +const WithImageBanner = bannerWrapper( getMomentTemplateBanner({ backgroundColour: '#e4e4e3', primaryCtaSettings: { @@ -171,28 +175,79 @@ const AusElectionBanner = bannerWrapper( 'https://i.guim.co.uk/img/media/4fa98ca4b70ee9b21b74d16f2586b5d6c513297f/0_319_2836_1837/2000.png?quality=85&s=3ef36bd5ab569f310b0f975372f54d29', altText: 'Head shots of Anthony Albanese, leader of the Australian Labor Party, and Scott Morrison, current Prime Minister and leader of the Liberal Party of Australia, who are running for the office of Prime Minister in the Australian federal election, to be held on 21 May 2022.', + tabletPosition: 'center', + desktopPosition: 'center', }, }), 'aus-moment-banner', ); -const AusElectionTemplate: Story = (props: BannerProps) => ( - +const WithImageTemplate: Story = (props: BannerProps) => ( + ); -export const AusElection = AusElectionTemplate.bind({}); -AusElection.args = { - ...GlobalNY.args, +export const WithImage = WithImageTemplate.bind({}); +WithImage.args = { + ...props, content: { - ...GlobalNY.args.content, + heading: 'As 2022 begins, will you support us?', + messageText: + 'Fearless, investigative reporting shapes a fairer world. At the Guardian, our independence allows us to chase the truth wherever it takes us. We have no shareholders. No vested interests. Just the determination and passion to bring readers quality reporting, including groundbreaking investigations. We do not shy away. And we provide all this for free, for everyone.', + paragraphs: [ + 'Fearless, investigative reporting shapes a fairer world. At the Guardian, our independence allows us to chase the truth wherever it takes us. We have no shareholders. No vested interests. Just the determination and passion to bring readers quality reporting, including groundbreaking investigations.', + 'We do not shy away. And we provide all this for free, for everyone.', + ], + highlightedText: + 'Show your support today from just %%CURRENCY_SYMBOL%%1, or sustain us long term with a little more. Thank you.', + cta: { + text: 'Support the Guardian', + baseUrl: 'https://support.theguardian.com/contribute', + }, secondaryCta: { type: SecondaryCtaType.ContributionsReminder, }, }, mobileContent: { - ...GlobalNY.args.mobileContent, + heading: 'Please support us in 2022', + messageText: + 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus', + paragraphs: [ + 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus', + ], + highlightedText: + 'Show your support today from just %%CURRENCY_SYMBOL%%1, or sustain us long term with a little more. Thank you.', + cta: { + text: 'Support us', + baseUrl: 'https://support.theguardian.com/contribute', + }, secondaryCta: { type: SecondaryCtaType.ContributionsReminder, }, }, + numArticles: 50, +}; + +export const WithImageNoArticleCount = WithImageTemplate.bind({}); +WithImageNoArticleCount.args = { + ...WithImage.args, + numArticles: 0, +}; + +export const WithImageNoSupportCtaIcons = WithImageTemplate.bind({}); +WithImageNoSupportCtaIcons.args = { + ...WithImage.args, + content: { + ...WithImage.args.content, + cta: { + baseUrl: 'https://theguardian.com', + text: 'The Guardian', + }, + }, + mobileContent: { + ...WithImage.args.mobileContent, + cta: { + baseUrl: 'https://theguardian.com', + text: 'The Guardian', + }, + }, }; diff --git a/packages/modules/src/modules/banners/momentTemplate/MomentTemplateBanner.tsx b/packages/modules/src/modules/banners/momentTemplate/MomentTemplateBanner.tsx index ef1482e8f..69992b642 100644 --- a/packages/modules/src/modules/banners/momentTemplate/MomentTemplateBanner.tsx +++ b/packages/modules/src/modules/banners/momentTemplate/MomentTemplateBanner.tsx @@ -2,6 +2,7 @@ import React, { useState, useEffect, useRef } from 'react'; import { css } from '@emotion/react'; import { neutral, space } from '@guardian/src-foundations'; import { Container, Hide } from '@guardian/src-layout'; +import { Button } from '@guardian/src-button'; import { BannerRenderProps } from '../common/types'; import { MomentTemplateBannerHeader } from './components/MomentTemplateBannerHeader'; import { MomentTemplateBannerArticleCount } from './components/MomentTemplateBannerArticleCount'; @@ -37,6 +38,42 @@ export function getMomentTemplateBanner( setIsReminderActive(!isReminderActive); }; + const bannerRef = useRef(null); + + useEffect(() => { + if (bannerRef.current != null) { + const closeButtonElements: HTMLElement[] = [ + ...bannerRef.current.querySelectorAll('[data-close-button]'), + ]; + + if (closeButtonElements.length > 1) { + const firstFocussableElement = closeButtonElements[0]; + const lastFocussableElement = + closeButtonElements[closeButtonElements.length - 1]; + + firstFocussableElement.focus(); + + bannerRef.current.addEventListener('keydown', e => { + const isTabPressed = e.key === 'Tab' || e.keyCode === 9; + if (!isTabPressed) { + return; + } + if (e.shiftKey) { + if (document.activeElement === firstFocussableElement) { + lastFocussableElement.focus(); + e.preventDefault(); + } + } else { + if (document.activeElement === lastFocussableElement) { + firstFocussableElement.focus(); + e.preventDefault(); + } + } + }); + } + } + }, [bannerRef.current]); + const mobileReminderRef = useRef(null); useEffect(() => { @@ -46,60 +83,9 @@ export function getMomentTemplateBanner( }, [mobileReminderRef.current, isReminderActive]); return ( -
- -
- - - -
- - {hasVisual && ( -
- {templateSettings.imageSettings && ( - - )} - {templateSettings.alternativeVisual} -
- )} - -
- - - - - -
-
- +
-
- - - -
- {hasVisual && (
{templateSettings.imageSettings && ( @@ -111,6 +97,13 @@ export function getMomentTemplateBanner(
)} +
+ +
+
- {separateArticleCount && numArticles !== undefined && numArticles > 5 && ( + {separateArticleCount && numArticles != null && numArticles > 5 && (
- -
- {content.mobileContent.secondaryCta?.type === - SecondaryCtaType.ContributionsReminder && - isReminderActive && ( + {content.mainContent.secondaryCta?.type === + SecondaryCtaType.ContributionsReminder && + isReminderActive && ( + + + + )} + + {content.mobileContent.secondaryCta?.type === + SecondaryCtaType.ContributionsReminder && + isReminderActive && ( + +
- )} -
-
+
+
+ )} - - {content.mainContent.secondaryCta?.type === - SecondaryCtaType.ContributionsReminder && - isReminderActive && ( - - )} - +
); } - return MomentTemplateBanner; } @@ -218,40 +218,13 @@ const styles = { justify-content: flex-end; } `, - mobileStickyHeaderContainer: (background: string, hasReminderCta: boolean) => css` - background: ${background}; - position: sticky; - top: 0px; - z-index: 100; - border-top: 1px solid ${neutral[0]}; - padding-top: ${space[2]}px; - - ${hasReminderCta - ? ` - border-bottom: 1px solid ${neutral[0]}; - padding-bottom: ${space[2]}px; - ` - : ''} - - ${from.tablet} { - display: none; - } - `, - visualContainer: css` + desktopVisualContainer: css` display: none; - ${from.mobileMedium} { + ${from.mobileLandscape} { display: block; } ${from.tablet} { - display: none; - } - `, - desktopVisualContainer: css` - display: none; - - ${from.tablet} { - display: block; width: 238px; margin-left: ${space[3]}px; } @@ -278,21 +251,8 @@ const styles = { width: 780px; } `, - headerContainer: css` - display: flex; - align-items: center; - - ${from.mobileMedium} { - margin-top: ${space[2]}px; - } - `, desktopHeaderContainer: css` - display: none; margin-top: ${space[2]}px; - - ${from.tablet} { - display: block; - } `, articleCountContainer: css` margin-top: ${space[4]}px; @@ -313,12 +273,14 @@ const styles = { margin-top: ${space[6]}px; } `, - mobileCloseButtonContainer: css` - margin-left: ${space[3]}px; - `, closeButtonContainer: css` position: absolute; top: ${space[2]}px; right: ${space[4]}px; `, + hiddenCloseButton: css` + position: absolute; + left: -1000px; + font-size: 0.001rem; + `, }; diff --git a/packages/modules/src/modules/banners/momentTemplate/buttonStyles.ts b/packages/modules/src/modules/banners/momentTemplate/buttonStyles.ts index 3f6943c79..714067ed2 100644 --- a/packages/modules/src/modules/banners/momentTemplate/buttonStyles.ts +++ b/packages/modules/src/modules/banners/momentTemplate/buttonStyles.ts @@ -8,6 +8,10 @@ export function buttonStyles(settings: CtaSettings): SerializedStyles { &:hover { ${toCssString(settings.hover)} } + + &:focus { + outline: 5px solid #0077b6; + } `; } diff --git a/packages/modules/src/modules/banners/momentTemplate/components/MomentTemplateBannerArticleCountOptOut.tsx b/packages/modules/src/modules/banners/momentTemplate/components/MomentTemplateBannerArticleCountOptOut.tsx index 14099f146..bcfc8a5de 100644 --- a/packages/modules/src/modules/banners/momentTemplate/components/MomentTemplateBannerArticleCountOptOut.tsx +++ b/packages/modules/src/modules/banners/momentTemplate/components/MomentTemplateBannerArticleCountOptOut.tsx @@ -158,7 +158,8 @@ const styles = { font-style: inherit; color: inherit; &:focus { - outline: none !important; + outline: 5px solid #0077b6; + border-radius: 4px; } `, overlayContainer: css` diff --git a/packages/modules/src/modules/banners/momentTemplate/components/MomentTemplateBannerBody.tsx b/packages/modules/src/modules/banners/momentTemplate/components/MomentTemplateBannerBody.tsx index 3848fee70..082bee57d 100644 --- a/packages/modules/src/modules/banners/momentTemplate/components/MomentTemplateBannerBody.tsx +++ b/packages/modules/src/modules/banners/momentTemplate/components/MomentTemplateBannerBody.tsx @@ -12,13 +12,13 @@ import { BannerRenderedContent } from '../../common/types'; // ---- Component ---- // interface MomentTemplateBannerBodyProps { - mainContent: BannerRenderedContent; + content: BannerRenderedContent; mobileContent: BannerRenderedContent; highlightedTextSettings: HighlightedTextSettings; } export function MomentTemplateBannerBody({ - mainContent, + content, mobileContent, highlightedTextSettings, }: MomentTemplateBannerBodyProps): JSX.Element { @@ -26,6 +26,9 @@ export function MomentTemplateBannerBody({ return (
+ + {createBannerBodyCopy(content.paragraphs, content.highlightedText, styles)} + {createBannerBodyCopy( mobileContent.paragraphs, @@ -33,10 +36,6 @@ export function MomentTemplateBannerBody({ styles, )} - - - {createBannerBodyCopy(mainContent.paragraphs, mainContent.highlightedText, styles)} -
); } diff --git a/packages/modules/src/modules/banners/momentTemplate/components/MomentTemplateBannerCloseButton.tsx b/packages/modules/src/modules/banners/momentTemplate/components/MomentTemplateBannerCloseButton.tsx index c36fcc0a4..038391f8a 100644 --- a/packages/modules/src/modules/banners/momentTemplate/components/MomentTemplateBannerCloseButton.tsx +++ b/packages/modules/src/modules/banners/momentTemplate/components/MomentTemplateBannerCloseButton.tsx @@ -27,12 +27,14 @@ export function MomentTemplateBannerCloseButton({
); @@ -57,4 +59,7 @@ const styles = { margin-right: ${space[2]}px; } `, + button: css` + font-size: 0.001rem; + `, }; diff --git a/packages/modules/src/modules/banners/momentTemplate/components/MomentTemplateBannerCtas.tsx b/packages/modules/src/modules/banners/momentTemplate/components/MomentTemplateBannerCtas.tsx index 9e304d85a..ffffff8f5 100644 --- a/packages/modules/src/modules/banners/momentTemplate/components/MomentTemplateBannerCtas.tsx +++ b/packages/modules/src/modules/banners/momentTemplate/components/MomentTemplateBannerCtas.tsx @@ -4,15 +4,16 @@ import { neutral, space } from '@guardian/src-foundations'; import { Hide } from '@guardian/src-layout'; import { Button, LinkButton } from '@guardian/src-button'; import { SecondaryCtaType } from '@sdc/shared/types'; -import { BannerRenderedContent } from '../../common/types'; +import { BannerRenderedContent, BannerEnrichedCta } from '../../common/types'; import { buttonStyles } from '../buttonStyles'; import { CtaSettings } from '../settings'; import { from } from '@guardian/src-foundations/mq'; +import { isSupportUrl } from '@sdc/shared/dist/lib'; // ---- Component ---- // interface MomentTemplateBannerCtasProps { - mainContent: BannerRenderedContent; + content: BannerRenderedContent; mobileContent: BannerRenderedContent; onPrimaryCtaClick: () => void; onSecondaryCtaClick: () => void; @@ -22,7 +23,7 @@ interface MomentTemplateBannerCtasProps { } export function MomentTemplateBannerCtas({ - mainContent, + content, mobileContent, onPrimaryCtaClick, onSecondaryCtaClick, @@ -30,6 +31,12 @@ export function MomentTemplateBannerCtas({ primaryCtaSettings, secondaryCtaSettings, }: MomentTemplateBannerCtasProps): JSX.Element { + const checkForSupportCtaLink = (cta: BannerEnrichedCta | null): boolean => { + if (!cta || !cta.ctaUrl) { + return false; + } + return isSupportUrl(cta.ctaUrl); + }; return (
@@ -74,32 +81,31 @@ export function MomentTemplateBannerCtas({
- {mainContent.primaryCta && ( + {content.primaryCta && ( - {mainContent.primaryCta.ctaText} + {content.primaryCta.ctaText} )} - {mainContent.secondaryCta?.type === SecondaryCtaType.Custom && ( + {content.secondaryCta?.type === SecondaryCtaType.Custom && ( - {mainContent.secondaryCta.cta.ctaText} + {content.secondaryCta.cta.ctaText} )} - {mainContent.secondaryCta?.type === - SecondaryCtaType.ContributionsReminder && ( + {content.secondaryCta?.type === SecondaryCtaType.ContributionsReminder && (
); @@ -201,6 +211,7 @@ function PaymentCards() { const styles = { container: css` padding-bottom: ${space[5]}px; + padding-left: ${space[2]}px; ${from.tablet} { padding-bottom: ${space[6]}px; @@ -229,5 +240,9 @@ const styles = { `, reminderCta: css` color: ${neutral[0]}; + &:focus { + outline: 5px solid #0077b6; + border-radius: 10px; + } `, }; diff --git a/packages/modules/src/modules/banners/momentTemplate/components/MomentTemplateBannerHeader.tsx b/packages/modules/src/modules/banners/momentTemplate/components/MomentTemplateBannerHeader.tsx index c932af062..987b6a3c3 100644 --- a/packages/modules/src/modules/banners/momentTemplate/components/MomentTemplateBannerHeader.tsx +++ b/packages/modules/src/modules/banners/momentTemplate/components/MomentTemplateBannerHeader.tsx @@ -35,6 +35,10 @@ const styles = { position: relative; `, header: css` + width: calc(100% - 50px); + ${from.tablet} { + width: 100%; + } h2 { ${headline.xsmall({ fontWeight: 'bold' })} margin: 0; diff --git a/packages/modules/src/modules/banners/momentTemplate/components/MomentTemplateBannerVisual.tsx b/packages/modules/src/modules/banners/momentTemplate/components/MomentTemplateBannerVisual.tsx index 651e2a129..96511ed0a 100644 --- a/packages/modules/src/modules/banners/momentTemplate/components/MomentTemplateBannerVisual.tsx +++ b/packages/modules/src/modules/banners/momentTemplate/components/MomentTemplateBannerVisual.tsx @@ -37,7 +37,12 @@ export function MomentTemplateBannerVisual({ } return ( -
+
); @@ -45,7 +50,7 @@ export function MomentTemplateBannerVisual({ // ---- Styles ---- // -const container = css` +const container = (tabletPosition: string, desktopPosition: string) => css` height: 140px; display: flex; justify-content: center; @@ -59,6 +64,10 @@ const container = css` ${from.tablet} { height: 100%; width: 100%; - align-items: center; + align-items: ${tabletPosition}; + } + + ${from.desktop} { + align-items: ${desktopPosition}; } `; diff --git a/packages/modules/src/modules/banners/momentTemplate/components/MomentTemplateSignInCta.tsx b/packages/modules/src/modules/banners/momentTemplate/components/MomentTemplateSignInCta.tsx new file mode 100644 index 000000000..22cf1cf60 --- /dev/null +++ b/packages/modules/src/modules/banners/momentTemplate/components/MomentTemplateSignInCta.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import { css } from '@emotion/react'; +import { space } from '@guardian/src-foundations'; +import { Link } from '@guardian/src-link'; + +/* +Existing SignIn URLs + +ContributionsWithSignIn +'https://profile.theguardian.com/signin??utm_source=gdnwb&utm_medium=banner&utm_campaign=SigninContributionsBanner_Existing&CMP_TU=mrtn&CMP_BUNIT=subs' + +DigitalSubscriptionsBanner +'https://profile.theguardian.com/signin?utm_source=gdnwb&utm_medium=banner&utm_campaign=SubsBanner_Existing&CMP_TU=mrtn&CMP_BUNIT=subs' + +GuardianWeeklyBanner +'https://profile.theguardian.com/signin?utm_source=gdnwb&utm_medium=banner&utm_campaign=SubsBanner_gWeekly&CMP_TU=mrtn&CMP_BUNIT=subs' + +... Which gives us a more general sign-in URL of +`https://profile.theguardian.com/signin?utm_source=gdnwb&utm_medium=banner&utm_campaign=${BANNER_TEMPLATE_AND_OR_POSSIBLY_CAMPAIGN_STRING}&CMP_TU=mrtn&CMP_BUNIT=subs` +*/ +// const signInUrl = +// 'https://profile.theguardian.com/signin?utm_source=gdnwb&utm_medium=banner&utm_campaign=SigninContributionsBanner_Existing&CMP_TU=mrtn&CMP_BUNIT=subs'; + +const boldText = css` + font-family: inherit; + font-size: inherit; + font-weight: 700; + &:focus { + outline: 5px solid #0077b6; + border-radius: 4px; + } +`; + +const signInCta = css` + margin: ${space[4]}px 0; +`; + +type MomentTemplateSignInCtaProps = { + onSignInClick?: () => void; + signInUrlTrackingValue: string; +}; + +export const MomentTemplateSignInCta: React.FC = ({ + onSignInClick, + signInUrlTrackingValue, +}) => { + const signInUrl = `https://profile.theguardian.com/signin?utm_source=gdnwb&utm_medium=banner&utm_campaign=${signInUrlTrackingValue}&CMP_TU=mrtn&CMP_BUNIT=subs`; + + return ( +

+ Already a supporter?{' '} + + Sign in + {' '} + and we promise to ask you less. +

+ ); +}; diff --git a/packages/shared/src/types/props/shared.ts b/packages/shared/src/types/props/shared.ts index 0eafcfbb0..02dceaaee 100644 --- a/packages/shared/src/types/props/shared.ts +++ b/packages/shared/src/types/props/shared.ts @@ -139,6 +139,8 @@ export interface Image { leftColUrl?: string; wideUrl?: string; altText: string; + tabletPosition?: string; + desktopPosition?: string; } export const imageSchema = z.object({ @@ -149,6 +151,8 @@ export const imageSchema = z.object({ leftColUrl: z.string().optional(), wideUrl: z.string().optional(), altText: z.string(), + tabletPosition: z.string().optional(), + desktopPosition: z.string().optional(), }); export interface BylineWithImage {