Skip to content

Commit 9c85604

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

File tree

6 files changed

+367
-28
lines changed

6 files changed

+367
-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: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
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: ${space[5]}px;
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: ${space[1]}px;
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, the plan is that this component will be removed and MoreGalleries.tsx will
194+
* be generalised so that is can be used for onwards content across all articles. If the
195+
* test is not successful, this component will be removed.
196+
*/
197+
export const MoreGalleriesStyleOnwardsContent = ({
198+
url,
199+
discussionApiUrl,
200+
isInOnwardsAbTestVariant,
201+
}: Props) => {
202+
const { data, error } = useApi<OnwardsResponse>(url);
203+
204+
if (error) {
205+
// Send the error to Sentry and then prevent the element from rendering
206+
window.guardian.modules.sentry.reportError(error, 'onwards-lower');
207+
return null;
208+
}
209+
210+
if (!data?.trails) {
211+
return (
212+
<Placeholder
213+
heights={
214+
new Map([
215+
['mobile', 900],
216+
['tablet', 600],
217+
['desktop', 900],
218+
])
219+
}
220+
shouldShimmer={false}
221+
backgroundColor={palette('--article-background')}
222+
/>
223+
);
224+
}
225+
226+
const trails: DCRFrontCard[] = convertFETrailToDcrTrail(
227+
data.trails,
228+
discussionApiUrl,
229+
);
230+
231+
const [firstTrail, ...standardTrails] = trails;
232+
if (!firstTrail) return null;
233+
234+
const heading = data.heading || data.displayname;
235+
236+
return (
237+
<div
238+
data-component="onwards-content-gallery-style"
239+
css={containerStyles}
240+
>
241+
<LeftColumn>
242+
<h2 css={headerStyles}>
243+
<span>{heading}</span>
244+
</h2>
245+
</LeftColumn>
246+
<h2 css={mobileHeaderStyles}>
247+
<span>{heading}</span>
248+
</h2>
249+
<div>
250+
<div css={[cardsContainerStyles, splashCardStyles]}>
251+
<Card
252+
{...getDefaultCardProps(firstTrail, false)}
253+
mediaPositionOnDesktop="right"
254+
mediaPositionOnMobile="top"
255+
mediaSize="medium"
256+
headlineSizes={{
257+
desktop: 'small',
258+
tablet: 'small',
259+
mobile: 'small',
260+
}}
261+
/>
262+
</div>
263+
<StraightLines
264+
cssOverrides={[
265+
cardsContainerStyles,
266+
css`
267+
${until.tablet} {
268+
display: none;
269+
}
270+
`,
271+
]}
272+
count={1}
273+
color={palette('--onward-content-border')}
274+
/>
275+
<ul css={[cardsContainerStyles, standardCardListStyles]}>
276+
{standardTrails.slice(0, 4).map((trail) => (
277+
<li key={trail.url} css={standardCardStyles}>
278+
<Card
279+
{...getDefaultCardProps(
280+
trail,
281+
!!isInOnwardsAbTestVariant,
282+
)}
283+
mediaPositionOnDesktop="bottom"
284+
mediaPositionOnMobile="left"
285+
mediaSize="small"
286+
/>
287+
</li>
288+
))}
289+
</ul>
290+
</div>
291+
</div>
292+
);
293+
};

0 commit comments

Comments
 (0)