Skip to content

Commit faa6ec0

Browse files
committed
Implement onwards content gallery-style AB test
1 parent d2bb4e5 commit faa6ec0

File tree

6 files changed

+366
-28
lines changed

6 files changed

+366
-28
lines changed

dotcom-rendering/src/components/Card/Card.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ export type Props = {
166166
enableHls?: boolean;
167167
isInStarRatingVariant?: boolean;
168168
starRatingSize?: RatingSizeType;
169+
isInOnwardsAbTestVariantStandardCard?: boolean;
169170
};
170171

171172
const starWrapper = (cardHasImage: boolean) => css`
@@ -426,6 +427,7 @@ export const Card = ({
426427
enableHls = false,
427428
isInStarRatingVariant,
428429
starRatingSize = 'small',
430+
isInOnwardsAbTestVariantStandardCard,
429431
}: Props) => {
430432
const hasSublinks = supportingContent && supportingContent.length > 0;
431433
const sublinkPosition = decideSublinkPosition(
@@ -673,7 +675,12 @@ export const Card = ({
673675
*/
674676
const getGapSizes = (): GapSizes => {
675677
if (isOnwardContent && !isGallerySecondaryOnward) {
676-
if (isMoreGalleriesOnwardContent) {
678+
if (
679+
isMoreGalleriesOnwardContent ||
680+
// This is untidy. If we implement the gallery-style redesign for onwards content
681+
// in all articles, we can refactor how we determine gap sizes for onwards cards.
682+
isInOnwardsAbTestVariantStandardCard
683+
) {
677684
return {
678685
row: 'small',
679686
column: 'small',

dotcom-rendering/src/components/MoreGalleries.tsx

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,6 @@ const cardsContainerStyles = css`
5353
${from.desktop} {
5454
${grid.between('centre-column-start', 'right-column-end')}
5555
}
56-
57-
${from.leftCol} {
58-
${grid.between('centre-column-start', 'right-column-end')}
59-
}
6056
`;
6157

6258
const standardCardStyles = css`
Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
import { css } from '@emotion/react';
2+
import {
3+
from,
4+
headlineBold24,
5+
space,
6+
until,
7+
} from '@guardian/source/foundations';
8+
import { StraightLines } from '@guardian/source-development-kitchen/react-components';
9+
import { grid } from '../grid';
10+
import { decideFormat } from '../lib/articleFormat';
11+
import { useApi } from '../lib/useApi';
12+
import { palette } from '../palette';
13+
import type { DCRFrontCard } from '../types/front';
14+
import type { FETrailType } from '../types/trails';
15+
import { Card } from './Card/Card';
16+
import type { Props as CardProps } from './Card/Card';
17+
import { LeftColumn } from './LeftColumn';
18+
import { Placeholder } from './Placeholder';
19+
20+
type OnwardsResponse = {
21+
trails: FETrailType[];
22+
heading: string;
23+
displayname: string;
24+
description: string;
25+
};
26+
27+
const standardCardListStyles = css`
28+
width: 100%;
29+
display: grid;
30+
gap: 20px;
31+
32+
${from.tablet} {
33+
grid-template-columns: repeat(4, 1fr);
34+
padding-top: ${space[2]}px;
35+
}
36+
37+
${from.leftCol} {
38+
&::before {
39+
content: '';
40+
position: absolute;
41+
left: -11px;
42+
bottom: 0;
43+
top: 8px;
44+
width: 1px;
45+
background-color: ${palette('--onward-content-border')};
46+
}
47+
}
48+
`;
49+
50+
const cardsContainerStyles = css`
51+
${grid.column.centre}
52+
position: relative;
53+
54+
${from.desktop} {
55+
${grid.between('centre-column-start', 'right-column-end')}
56+
}
57+
`;
58+
59+
const splashCardStyles = css`
60+
margin-bottom: ${space[4]}px;
61+
${from.leftCol} {
62+
margin-top: ${space[2]}px;
63+
64+
&::before {
65+
content: '';
66+
position: absolute;
67+
left: -11px;
68+
top: 8px;
69+
bottom: 0;
70+
width: 1px;
71+
background-color: ${palette('--onward-content-border')};
72+
}
73+
}
74+
`;
75+
76+
const standardCardStyles = css`
77+
position: relative;
78+
display: flex;
79+
80+
${from.tablet} {
81+
:not(:first-child)::before {
82+
content: '';
83+
position: absolute;
84+
top: 0;
85+
bottom: 0;
86+
left: -11px;
87+
width: 1px;
88+
background: ${palette('--onward-content-border')};
89+
}
90+
}
91+
`;
92+
93+
const containerStyles = css`
94+
display: flex;
95+
flex-direction: column;
96+
padding-bottom: ${space[6]}px;
97+
98+
${from.leftCol} {
99+
flex-direction: row;
100+
padding-right: 80px;
101+
}
102+
`;
103+
104+
const headerStyles = css`
105+
${headlineBold24};
106+
color: ${palette('--carousel-text')};
107+
padding-bottom: ${space[2]}px;
108+
padding-top: ${space[1]}px;
109+
margin-left: 0;
110+
`;
111+
112+
const mobileHeaderStyles = css`
113+
${headerStyles};
114+
${from.tablet} {
115+
padding-left: 4px;
116+
}
117+
${from.leftCol} {
118+
display: none;
119+
}
120+
`;
121+
122+
const convertFETrailToDcrTrail = (
123+
trails: FETrailType[],
124+
discussionApiUrl: string,
125+
): DCRFrontCard[] =>
126+
trails.map((trail) => ({
127+
dataLinkName: 'onwards-content-card',
128+
discussionId: trail.discussion?.discussionId,
129+
discussionApiUrl,
130+
format: decideFormat(trail.format),
131+
headline: trail.headline,
132+
image: {
133+
src: trail.masterImage ?? '',
134+
altText: trail.linkText ?? '',
135+
},
136+
isExternalLink: false,
137+
onwardsSource: 'related-content',
138+
showLivePlayable: false,
139+
showQuotedHeadline: false,
140+
url: trail.url,
141+
webPublicationDate: trail.webPublicationDate,
142+
isImmersive: false,
143+
}));
144+
145+
const getDefaultCardProps = (
146+
trail: DCRFrontCard,
147+
isInOnwardsAbTestVariantStandardCard: boolean,
148+
) => {
149+
const defaultProps: CardProps = {
150+
aspectRatio: '5:4',
151+
avatarUrl: trail.avatarUrl,
152+
branding: trail.branding,
153+
byline: trail.byline,
154+
dataLinkName: `onwards-content-gallery-style ${trail.dataLinkName}`,
155+
discussionApiUrl: trail.discussionApiUrl,
156+
discussionId: trail.discussionId,
157+
format: trail.format,
158+
headlineText: trail.headline,
159+
image: trail.image,
160+
imageLoading: 'lazy',
161+
isCrossword: trail.isCrossword,
162+
isExternalLink: false,
163+
isInOnwardsAbTestVariantStandardCard,
164+
isOnwardContent: true,
165+
kickerText: trail.kickerText,
166+
linkTo: trail.url,
167+
mainMedia: trail.mainMedia,
168+
onwardsSource: 'related-content',
169+
showAge: false,
170+
showByline: trail.showByline,
171+
showClock: false,
172+
showPulsingDot: false,
173+
showQuotedHeadline: trail.showQuotedHeadline,
174+
showTopBarDesktop: false,
175+
showTopBarMobile: false,
176+
snapData: trail.snapData,
177+
starRating: trail.starRating,
178+
trailText: trail.trailText,
179+
webPublicationDate: trail.webPublicationDate,
180+
};
181+
182+
return defaultProps;
183+
};
184+
185+
type Props = {
186+
url: string;
187+
discussionApiUrl: string;
188+
isInOnwardsAbTestVariant?: boolean;
189+
};
190+
191+
/**
192+
* We are running an AB test to use the More Galleries style of onwards content on all articles.
193+
* If the test is successful, this component will likely be removed the MoreGalleries.tsx will
194+
* be generalised so that is can be used for onwards content across all articles.
195+
*/
196+
export const MoreGalleriesStyleOnwardsContent = ({
197+
url,
198+
discussionApiUrl,
199+
isInOnwardsAbTestVariant,
200+
}: Props) => {
201+
const { data, error } = useApi<OnwardsResponse>(url);
202+
203+
if (error) {
204+
// Send the error to Sentry and then prevent the element from rendering
205+
window.guardian.modules.sentry.reportError(error, 'onwards-lower');
206+
return null;
207+
}
208+
209+
if (!data?.trails) {
210+
return (
211+
<Placeholder
212+
heights={
213+
new Map([
214+
['mobile', 900],
215+
['tablet', 600],
216+
['desktop', 900],
217+
])
218+
}
219+
shouldShimmer={false}
220+
backgroundColor={palette('--article-background')}
221+
/>
222+
);
223+
}
224+
225+
const trails: DCRFrontCard[] = convertFETrailToDcrTrail(
226+
data.trails,
227+
discussionApiUrl,
228+
);
229+
230+
const [firstTrail, ...standardTrails] = trails;
231+
if (!firstTrail) return null;
232+
233+
const heading = data.heading || data.displayname;
234+
235+
return (
236+
<div
237+
data-component="onwards-content-gallery-style"
238+
css={containerStyles}
239+
>
240+
<LeftColumn>
241+
<h2 css={headerStyles}>
242+
<span>{heading}</span>
243+
</h2>
244+
</LeftColumn>
245+
<h2 css={mobileHeaderStyles}>
246+
<span>{heading}</span>
247+
</h2>
248+
<div>
249+
<div css={[cardsContainerStyles, splashCardStyles]}>
250+
<Card
251+
{...getDefaultCardProps(firstTrail, false)}
252+
mediaPositionOnDesktop="right"
253+
mediaPositionOnMobile="top"
254+
mediaSize="medium"
255+
headlineSizes={{
256+
desktop: 'small',
257+
tablet: 'small',
258+
mobile: 'small',
259+
}}
260+
/>
261+
</div>
262+
<StraightLines
263+
cssOverrides={[
264+
cardsContainerStyles,
265+
css`
266+
${until.tablet} {
267+
display: none;
268+
}
269+
`,
270+
]}
271+
count={1}
272+
color={palette('--onward-content-border')}
273+
/>
274+
<ul css={[cardsContainerStyles, standardCardListStyles]}>
275+
{standardTrails.slice(0, 4).map((trail) => (
276+
<li key={trail.url} css={standardCardStyles}>
277+
<Card
278+
{...getDefaultCardProps(
279+
trail,
280+
!!isInOnwardsAbTestVariant,
281+
)}
282+
mediaPositionOnDesktop="bottom"
283+
mediaPositionOnMobile="left"
284+
mediaSize="small"
285+
/>
286+
</li>
287+
))}
288+
</ul>
289+
</div>
290+
</div>
291+
);
292+
};

0 commit comments

Comments
 (0)