Skip to content

Commit d8eab33

Browse files
NewsletterSignupCard — static card + feature flag swap-in (#15728)
NewsletterSignupCard — static presentational shell #15674 NewsletterSignupCard — feature flag + swap in EmailSignUpWrapper #15680
1 parent d8beb4e commit d8eab33

10 files changed

Lines changed: 271 additions & 2 deletions

dotcom-rendering/src/components/EmailSignUpWrapper.island.tsx

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { EmailSignup } from './EmailSignup';
55
import { InlineSkipToWrapper } from './InlineSkipToWrapper';
66
import { Island } from './Island';
77
import { NewsletterPrivacyMessage } from './NewsletterPrivacyMessage';
8+
import { NewsletterSignupCardContainer } from './NewsletterSignupCardContainer';
89
import { Placeholder } from './Placeholder';
910
import { SecureSignup } from './SecureSignup.island';
1011

@@ -22,11 +23,15 @@ interface EmailSignUpWrapperProps extends EmailSignUpProps {
2223
listId: number;
2324
identityName: string;
2425
successDescription: string;
26+
/** Illustration image URL (square crop) for the NewsletterSignupCard variant */
27+
illustrationSquare?: string;
2528
idApiUrl: string;
2629
/** You should only set this to true if the privacy message will be shown elsewhere on the page */
2730
hidePrivacyMessage?: boolean;
2831
/** Feature flag to enable hiding newsletter signup for already subscribed users */
2932
hideNewsletterSignupComponentForSubscribers?: boolean;
33+
/** Feature flag to show the new NewsletterSignupCard design instead of EmailSignup */
34+
showNewNewsletterSignupCard?: boolean;
3035
}
3136

3237
/**
@@ -44,14 +49,47 @@ export const EmailSignUpWrapper = ({
4449
listId,
4550
idApiUrl,
4651
hideNewsletterSignupComponentForSubscribers = false,
52+
showNewNewsletterSignupCard = false,
4753
...emailSignUpProps
4854
}: EmailSignUpWrapperProps) => {
55+
const shouldCheckSubscription =
56+
hideNewsletterSignupComponentForSubscribers &&
57+
!showNewNewsletterSignupCard;
4958
const isSubscribed = useNewsletterSubscription(
5059
listId,
5160
idApiUrl,
52-
hideNewsletterSignupComponentForSubscribers,
61+
shouldCheckSubscription,
5362
);
5463

64+
// When the new card design is enabled, always show it regardless of subscription status
65+
if (showNewNewsletterSignupCard) {
66+
return (
67+
<InlineSkipToWrapper
68+
id={`EmailSignup-skip-link-${index}`}
69+
blockDescription="newsletter promotion"
70+
>
71+
<NewsletterSignupCardContainer
72+
name={emailSignUpProps.name}
73+
frequency={emailSignUpProps.frequency}
74+
description={emailSignUpProps.description}
75+
illustrationSquare={emailSignUpProps.illustrationSquare}
76+
>
77+
<Island priority="feature" defer={{ until: 'visible' }}>
78+
<SecureSignup
79+
newsletterId={emailSignUpProps.identityName}
80+
successDescription={
81+
emailSignUpProps.successDescription
82+
}
83+
/>
84+
</Island>
85+
{!emailSignUpProps.hidePrivacyMessage && (
86+
<NewsletterPrivacyMessage />
87+
)}
88+
</NewsletterSignupCardContainer>
89+
</InlineSkipToWrapper>
90+
);
91+
}
92+
5593
// Show placeholder while subscription status is being determined
5694
// This prevents layout shift in both subscribed and non-subscribed cases
5795
if (isSubscribed === undefined) {
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { allModes } from '../../.storybook/modes';
2+
import preview from '../../.storybook/preview';
3+
import { NewsletterSignupCard } from './NewsletterSignupCard';
4+
import { Section } from './Section';
5+
6+
const meta = preview.meta({
7+
component: NewsletterSignupCard,
8+
title: 'Components/Newsletter Signup Card',
9+
parameters: {
10+
chromatic: {
11+
modes: {
12+
'vertical mobile': allModes['vertical mobile'],
13+
'vertical tablet': allModes['vertical tablet'],
14+
},
15+
},
16+
},
17+
decorators: [
18+
(Story) => (
19+
<Section
20+
title="NewsletterSignupCard"
21+
showTopBorder={true}
22+
padContent={false}
23+
centralBorder="partial"
24+
>
25+
<Story />
26+
</Section>
27+
),
28+
],
29+
});
30+
31+
export const Default = meta.story({
32+
args: {
33+
name: 'Saturday Edition',
34+
description:
35+
"An exclusive roundup of the week's best Guardian journalism from the editor-in-chief, Katharine Viner, free to your inbox every Saturday.",
36+
frequency: 'Weekly',
37+
illustrationSquare:
38+
'https://i.guim.co.uk/img/uploads/2023/11/01/SaturdayEdition_-_5-3.jpg?width=220&dpr=2&s=none&crop=5%3A3',
39+
children: <></>,
40+
},
41+
});
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import { css } from '@emotion/react';
2+
import {
3+
headlineMedium20,
4+
space,
5+
textSans14,
6+
textSans15,
7+
} from '@guardian/source/foundations';
8+
import { SvgNewsletterFilled } from '@guardian/source/react-components';
9+
import { palette as themePalette } from '../palette';
10+
11+
export type NewsletterSignupCardProps = {
12+
name: string;
13+
frequency: string;
14+
description: string;
15+
illustrationSquare?: string;
16+
children?: React.ReactNode;
17+
};
18+
19+
const containerStyles = css`
20+
background-color: ${themePalette('--newsletter-card-background')};
21+
margin-bottom: ${space[6]}px;
22+
padding: ${space[2]}px ${space[2]}px ${space[4]}px ${space[2]}px;
23+
`;
24+
25+
const dividerStyles = css`
26+
clear: left;
27+
border: none;
28+
border-top: 1px solid ${themePalette('--newsletter-card-divider')};
29+
margin: ${space[6]}px 0 ${space[2]}px;
30+
`;
31+
32+
const headerStyles = css`
33+
display: flex;
34+
flex-direction: row;
35+
justify-content: space-between;
36+
align-items: flex-start;
37+
gap: ${space[2]}px;
38+
margin-bottom: ${space[1]}px;
39+
`;
40+
41+
const titleAndMetaStyles = css`
42+
display: flex;
43+
flex-direction: column;
44+
`;
45+
46+
const titleStyles = css`
47+
${headlineMedium20};
48+
margin-bottom: ${space[2]}px;
49+
color: ${themePalette('--newsletter-card-title')};
50+
`;
51+
52+
const frequencyTagStyles = css`
53+
display: flex;
54+
align-items: center;
55+
color: ${themePalette('--newsletter-card-frequency-tag')};
56+
${textSans15};
57+
margin-left: -1px;
58+
margin-top: -1px;
59+
margin-bottom: ${space[1]}px;
60+
61+
svg {
62+
fill: currentColor;
63+
height: 20px;
64+
width: 20px;
65+
}
66+
`;
67+
68+
const descriptionStyles = css`
69+
${textSans14};
70+
line-height: 1.15;
71+
margin-bottom: ${space[1]}px;
72+
clear: both;
73+
color: ${themePalette('--newsletter-card-description')};
74+
`;
75+
76+
const illustrationStyles = css`
77+
flex-shrink: 0;
78+
width: 100px;
79+
height: 100px;
80+
border-radius: 50%;
81+
object-fit: cover;
82+
`;
83+
84+
const NewsletterSignupHeader = (props: {
85+
frequency: string;
86+
name: string;
87+
description: string;
88+
illustrationSquare?: string;
89+
}) => (
90+
<div css={headerStyles}>
91+
<div css={titleAndMetaStyles}>
92+
<div css={frequencyTagStyles}>
93+
<SvgNewsletterFilled />
94+
Newsletter | {props.frequency}
95+
</div>
96+
<p css={titleStyles}>
97+
Sign up to <span>{props.name}</span>
98+
</p>
99+
<p css={descriptionStyles}>{props.description}</p>
100+
</div>
101+
{!!props.illustrationSquare && (
102+
<img
103+
css={illustrationStyles}
104+
src={props.illustrationSquare}
105+
alt=""
106+
loading="lazy"
107+
decoding="async"
108+
/>
109+
)}
110+
</div>
111+
);
112+
113+
export const NewsletterSignupCard = ({
114+
name,
115+
frequency,
116+
description,
117+
illustrationSquare,
118+
children,
119+
}: NewsletterSignupCardProps) => (
120+
<>
121+
<hr css={dividerStyles} />
122+
<aside css={containerStyles} aria-label="newsletter promotion">
123+
<NewsletterSignupHeader
124+
frequency={frequency}
125+
name={name}
126+
description={description}
127+
illustrationSquare={illustrationSquare}
128+
/>
129+
{children}
130+
</aside>
131+
</>
132+
);
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import type { NewsletterSignupCardProps } from './NewsletterSignupCard';
2+
import { NewsletterSignupCard } from './NewsletterSignupCard';
3+
4+
type Props = NewsletterSignupCardProps;
5+
6+
export const NewsletterSignupCardContainer = ({
7+
name,
8+
frequency,
9+
description,
10+
illustrationSquare,
11+
children,
12+
}: Props) => (
13+
<NewsletterSignupCard
14+
name={name}
15+
frequency={frequency}
16+
description={description}
17+
illustrationSquare={illustrationSquare}
18+
>
19+
{children}
20+
</NewsletterSignupCard>
21+
);

dotcom-rendering/src/frontend/schemas/feArticle.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,9 @@
421421
},
422422
"illustrationCard": {
423423
"type": "string"
424+
},
425+
"illustrationSquare": {
426+
"type": "string"
424427
}
425428
},
426429
"required": [
@@ -3067,6 +3070,9 @@
30673070
},
30683071
"illustrationCard": {
30693072
"type": "string"
3073+
},
3074+
"illustrationSquare": {
3075+
"type": "string"
30703076
}
30713077
},
30723078
"required": [

dotcom-rendering/src/lib/renderElement.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -571,7 +571,7 @@ export const renderElement = ({
571571
caption={element.caption}
572572
/>
573573
);
574-
case 'model.dotcomrendering.pageElements.NewsletterSignupBlockElement':
574+
case 'model.dotcomrendering.pageElements.NewsletterSignupBlockElement': {
575575
const emailSignUpProps = {
576576
index,
577577
listId: element.newsletter.listId,
@@ -581,16 +581,20 @@ export const renderElement = ({
581581
frequency: element.newsletter.frequency,
582582
successDescription: element.newsletter.successDescription,
583583
theme: element.newsletter.theme,
584+
illustrationSquare: element.newsletter.illustrationSquare,
584585
idApiUrl: idApiUrl ?? '',
585586
hideNewsletterSignupComponentForSubscribers:
586587
!!switches.hideNewsletterSignupComponentForSubscribers,
588+
showNewNewsletterSignupCard:
589+
!!switches.showNewNewsletterSignupCard,
587590
};
588591
if (isListElement || isTimeline) return null;
589592
return (
590593
<Island priority="feature" defer={{ until: 'visible' }}>
591594
<EmailSignUpWrapper {...emailSignUpProps} />
592595
</Island>
593596
);
597+
}
594598
case 'model.dotcomrendering.pageElements.AdPlaceholderBlockElement':
595599
return renderAds && <AdPlaceholder />;
596600
case 'model.dotcomrendering.pageElements.NumberedTitleBlockElement':

dotcom-rendering/src/model/block-schema.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2549,6 +2549,9 @@
25492549
},
25502550
"illustrationCard": {
25512551
"type": "string"
2552+
},
2553+
"illustrationSquare": {
2554+
"type": "string"
25522555
}
25532556
},
25542557
"required": [

dotcom-rendering/src/model/newsletter-page-schema.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@
3838
},
3939
"illustrationCard": {
4040
"type": "string"
41+
},
42+
"illustrationSquare": {
43+
"type": "string"
4144
}
4245
},
4346
"required": [

dotcom-rendering/src/paletteDeclarations.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7605,6 +7605,26 @@ const paletteColours = {
76057605
light: navSearchBarText,
76067606
dark: navSearchBarText,
76077607
},
7608+
'--newsletter-card-background': {
7609+
light: () => '#F3F7FF',
7610+
dark: () => sourcePalette.brand[100],
7611+
},
7612+
'--newsletter-card-description': {
7613+
light: () => sourcePalette.neutral[20],
7614+
dark: () => sourcePalette.neutral[86],
7615+
},
7616+
'--newsletter-card-divider': {
7617+
light: () => sourcePalette.neutral[73],
7618+
dark: () => sourcePalette.neutral[46],
7619+
},
7620+
'--newsletter-card-frequency-tag': {
7621+
light: () => sourcePalette.neutral[38],
7622+
dark: () => sourcePalette.neutral[73],
7623+
},
7624+
'--newsletter-card-title': {
7625+
light: () => sourcePalette.neutral[7],
7626+
dark: () => sourcePalette.neutral[100],
7627+
},
76087628
'--numbered-list-heading': {
76097629
light: numberedListHeadingLight,
76107630
dark: numberedListHeadingDark,

dotcom-rendering/src/types/content.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1203,6 +1203,7 @@ export type Newsletter = {
12031203
group: string;
12041204
regionFocus?: string;
12051205
illustrationCard?: string;
1206+
illustrationSquare?: string;
12061207
};
12071208

12081209
export type NewsletterLayout = {

0 commit comments

Comments
 (0)