Skip to content

Commit c76bd07

Browse files
Abstract area placement depending on layout
1 parent 7b28aae commit c76bd07

File tree

1 file changed

+219
-78
lines changed

1 file changed

+219
-78
lines changed

dotcom-rendering/src/layouts/StandardLayout.tsx

Lines changed: 219 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { css } from '@emotion/react';
1+
import { css, type SerializedStyles } from '@emotion/react';
22
import { log } from '@guardian/libs';
33
import {
44
from,
@@ -83,17 +83,150 @@ const stretchLines = css`
8383
}
8484
`;
8585

86-
const tabletRow = (row: number) => css`
87-
${from.tablet} {
88-
grid-row: ${row};
89-
}
90-
`;
86+
type LayoutType = 'standard' | 'matchReport' | 'media' | 'labs';
9187

92-
const leftColRow = (row: number) => css`
93-
${from.leftCol} {
94-
grid-row: ${row};
95-
}
96-
`;
88+
type Area =
89+
| 'title'
90+
| 'headline'
91+
| 'standfirst'
92+
| 'main-media'
93+
| 'meta'
94+
| 'body'
95+
| 'right-column'
96+
| 'match-nav'
97+
| 'match-tabs';
98+
99+
type LayoutRows = Partial<
100+
Record<
101+
Area,
102+
{ mobile?: number; tablet?: number; leftCol?: number; desktop?: number }
103+
>
104+
>;
105+
106+
const rowMaps: Record<LayoutType, LayoutRows> = {
107+
standard: {
108+
title: { tablet: 1 },
109+
headline: { tablet: 2, leftCol: 1 },
110+
standfirst: { tablet: 3, leftCol: 2 },
111+
'main-media': { tablet: 4, leftCol: 3 },
112+
meta: { tablet: 5, leftCol: 3 },
113+
},
114+
matchReport: {
115+
'match-nav': { tablet: 1 },
116+
'match-tabs': { tablet: 2 },
117+
title: { tablet: 3, leftCol: 1 },
118+
headline: { tablet: 4, leftCol: 3 },
119+
standfirst: { tablet: 5, leftCol: 4 },
120+
'main-media': { tablet: 6, leftCol: 5 },
121+
meta: { tablet: 7, leftCol: 5 },
122+
},
123+
media: {
124+
title: { mobile: 1, tablet: 1 },
125+
headline: { mobile: 2, tablet: 2, leftCol: 1 },
126+
'main-media': { mobile: 3, tablet: 3, leftCol: 2 },
127+
standfirst: { mobile: 4, tablet: 4, leftCol: 3 },
128+
meta: { mobile: 5, tablet: 5, leftCol: 2 },
129+
},
130+
labs: {
131+
title: { tablet: 1 },
132+
headline: { tablet: 2, leftCol: 1 },
133+
standfirst: { tablet: 3, leftCol: 2 },
134+
'main-media': { tablet: 3 },
135+
meta: { tablet: 4, leftCol: 3 },
136+
'match-nav': { tablet: 1 },
137+
'match-tabs': { tablet: 1 },
138+
},
139+
};
140+
141+
const rowCss = (area: Area, layoutType: LayoutType) => {
142+
const rows = rowMaps[layoutType][area] ?? {};
143+
144+
return css([
145+
rows.mobile != null &&
146+
css`
147+
${until.tablet} {
148+
grid-row: ${rows.mobile};
149+
}
150+
`,
151+
rows.tablet != null &&
152+
css`
153+
${from.tablet} {
154+
grid-row: ${rows.tablet};
155+
}
156+
`,
157+
rows.leftCol != null &&
158+
css`
159+
${from.leftCol} {
160+
grid-row: ${rows.leftCol};
161+
}
162+
`,
163+
rows.desktop != null &&
164+
css`
165+
${from.desktop} {
166+
grid-row: ${rows.desktop};
167+
}
168+
`,
169+
]);
170+
};
171+
interface GridItemProps {
172+
area: Area;
173+
layoutType: LayoutType;
174+
columns?: {
175+
tablet?: 'left' | 'centre' | 'right';
176+
desktop?: 'left' | 'centre' | 'right';
177+
leftCol?: 'left' | 'centre' | 'right';
178+
};
179+
element?: 'div' | 'article' | 'main' | 'aside' | 'section';
180+
customCss?: SerializedStyles;
181+
children: React.ReactNode;
182+
}
183+
184+
const GridItem = ({
185+
area,
186+
layoutType,
187+
columns,
188+
element: Element = 'div',
189+
customCss,
190+
children,
191+
}: GridItemProps) => {
192+
const mobileCol = 'centre';
193+
const tabletCol = columns?.tablet ?? mobileCol;
194+
const leftColCol = columns?.leftCol ?? mobileCol;
195+
const desktopCol = columns?.desktop ?? mobileCol;
196+
197+
return (
198+
<Element
199+
data-gu-name={area}
200+
css={css([
201+
grid.column[mobileCol],
202+
rowCss(area, layoutType),
203+
customCss,
204+
205+
// Override column at breakpoints if specified
206+
columns?.tablet &&
207+
css`
208+
${from.tablet} {
209+
${grid.column[tabletCol]};
210+
}
211+
`,
212+
columns?.desktop &&
213+
css`
214+
${from.desktop} {
215+
${grid.column[desktopCol]};
216+
}
217+
`,
218+
columns?.leftCol &&
219+
css`
220+
${from.leftCol} {
221+
${grid.column[leftColCol]};
222+
}
223+
`,
224+
])}
225+
>
226+
{children}
227+
</Element>
228+
);
229+
};
97230

98231
interface Props {
99232
article: ArticleDeprecated;
@@ -170,6 +303,14 @@ export const StandardLayout = (props: WebProps | AppProps) => {
170303

171304
const renderAds = canRenderAds(article);
172305

306+
const layoutType: LayoutType = isLabs
307+
? 'labs'
308+
: isMatchReport
309+
? 'matchReport'
310+
: isMedia
311+
? 'media'
312+
: 'standard';
313+
173314
return (
174315
<>
175316
{isWeb && (
@@ -239,12 +380,22 @@ export const StandardLayout = (props: WebProps | AppProps) => {
239380
pageId={article.pageId}
240381
pageTags={article.tags}
241382
/>
242-
<article>
383+
<article
384+
css={css`
385+
background-color: ${themePalette(
386+
'--article-background',
387+
)};
388+
`}
389+
>
243390
<div css={css([grid.container, grid.verticalRules])}>
244391
<div className="grid-rule rule-left" />
245392
<div className="grid-rule rule-centre" />
246393
<div className="grid-rule rule-right" />
247-
<aside css={css(grid.column.centre)}>
394+
<GridItem
395+
area="match-nav"
396+
layoutType={layoutType}
397+
element="aside"
398+
>
248399
<div css={maxWidth}>
249400
{isMatchReport && (
250401
<Island
@@ -263,8 +414,12 @@ export const StandardLayout = (props: WebProps | AppProps) => {
263414
</Island>
264415
)}
265416
</div>
266-
</aside>
267-
<aside css={css(grid.column.centre)}>
417+
</GridItem>
418+
<GridItem
419+
area="match-tabs"
420+
layoutType={layoutType}
421+
element="aside"
422+
>
268423
<div css={maxWidth}>
269424
{isMatchReport && (
270425
<Island
@@ -278,13 +433,11 @@ export const StandardLayout = (props: WebProps | AppProps) => {
278433
</Island>
279434
)}
280435
</div>
281-
</aside>
282-
<div
283-
css={css(
284-
grid.column.centre,
285-
tabletRow(4),
286-
leftColRow(3),
287-
)}
436+
</GridItem>
437+
<GridItem
438+
area="main-media"
439+
layoutType={layoutType}
440+
element="div"
288441
>
289442
<div css={!isMedia && maxWidth}>
290443
<MainMedia
@@ -305,20 +458,13 @@ export const StandardLayout = (props: WebProps | AppProps) => {
305458
contentLayout="StandardLayout"
306459
/>
307460
</div>
308-
</div>
461+
</GridItem>
309462
{!isInFootballRedesignVariantGroup && (
310-
<aside
311-
css={[
312-
css(grid.column.centre),
313-
css`
314-
${from.leftCol} {
315-
${grid.column.left};
316-
grid-row: 1;
317-
align-self: start;
318-
}
319-
`,
320-
tabletRow(1),
321-
]}
463+
<GridItem
464+
area="title"
465+
layoutType={layoutType}
466+
columns={{ leftCol: 'left' }}
467+
element="aside"
322468
>
323469
<ArticleTitle
324470
format={format}
@@ -328,7 +474,7 @@ export const StandardLayout = (props: WebProps | AppProps) => {
328474
guardianBaseURL={article.guardianBaseURL}
329475
isMatch={!!footballMatchUrl}
330476
/>
331-
</aside>
477+
</GridItem>
332478
)}
333479

334480
<div css={css(grid.column.centre)}>
@@ -338,12 +484,10 @@ export const StandardLayout = (props: WebProps | AppProps) => {
338484
<Border />
339485
)}
340486
</div>
341-
<div
342-
css={css([
343-
grid.column.centre,
344-
tabletRow(2),
345-
leftColRow(1),
346-
])}
487+
<GridItem
488+
area="headline"
489+
layoutType={layoutType}
490+
element="div"
347491
>
348492
<div css={maxWidth}>
349493
<ArticleHeadline
@@ -357,30 +501,22 @@ export const StandardLayout = (props: WebProps | AppProps) => {
357501
starRating={article.starRating}
358502
/>
359503
</div>
360-
</div>
361-
<div
362-
css={css([
363-
grid.column.centre,
364-
tabletRow(3),
365-
leftColRow(2),
366-
])}
504+
</GridItem>
505+
<GridItem
506+
area="standfirst"
507+
layoutType={layoutType}
508+
element="div"
367509
>
368510
<Standfirst
369511
format={format}
370512
standfirst={article.standfirst}
371513
/>
372-
</div>
373-
<aside
374-
css={[
375-
css(grid.column.centre),
376-
css`
377-
${from.leftCol} {
378-
${grid.column.left};
379-
grid-row: 3;
380-
align-self: start;
381-
}
382-
`,
383-
]}
514+
</GridItem>
515+
<GridItem
516+
area="meta"
517+
layoutType={layoutType}
518+
columns={{ leftCol: 'left' }}
519+
element="aside"
384520
>
385521
<div css={maxWidth}>
386522
<div css={stretchLines}>
@@ -493,8 +629,12 @@ export const StandardLayout = (props: WebProps | AppProps) => {
493629
)}
494630
</div>
495631
)}
496-
</aside>
497-
<div css={css(grid.column.centre)}>
632+
</GridItem>
633+
<GridItem
634+
area="body"
635+
layoutType={layoutType}
636+
element="div"
637+
>
498638
{/* Only show Listen to Article button on App landscape views */}
499639
{isApps && (
500640
<Hide until="leftCol">
@@ -632,20 +772,21 @@ export const StandardLayout = (props: WebProps | AppProps) => {
632772
}
633773
/>
634774
</ArticleContainer>
635-
</div>
636-
<div
637-
css={[
638-
css(grid.column.centre),
639-
css`
640-
display: none;
641-
${from.desktop} {
642-
display: block;
643-
padding-top: 6px;
644-
${grid.column.right};
645-
grid-row: 1 / span 999;
646-
}
647-
`,
648-
]}
775+
</GridItem>
776+
<GridItem
777+
area="right-column"
778+
layoutType={layoutType}
779+
columns={{ desktop: 'right' }}
780+
customCss={css`
781+
display: none;
782+
${from.desktop} {
783+
display: block;
784+
padding-top: 6px;
785+
${grid.column.right};
786+
grid-row: 1 / span 999;
787+
}
788+
`}
789+
element="aside"
649790
>
650791
<Island
651792
priority="feature"
@@ -668,7 +809,7 @@ export const StandardLayout = (props: WebProps | AppProps) => {
668809
}
669810
/>
670811
</Island>
671-
</div>
812+
</GridItem>
672813
</div>
673814
</article>
674815

0 commit comments

Comments
 (0)