Skip to content

Commit 09eaa30

Browse files
authored
Add A Nav For Football Competitions (#14170)
When major competitions are running we'd like to introduce additional navigation to the football data pages that relate to that competition: fixtures, live matches, results and tables. This will allow users to navigate between different pieces of information about said competition. This change introduces a `FootballCompetitionNav` component to be displayed only when the `pageId` matches the given competition. For now this is hardcoded to the Women's Euro 2025 competition as a first implementation. We will make it generic and configurable for other competitions in a later change.
1 parent d3d4c4f commit 09eaa30

File tree

7 files changed

+413
-152
lines changed

7 files changed

+413
-152
lines changed

dotcom-rendering/.storybook/modes.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,8 @@ export const allModes = {
6565
globalColourScheme: 'light',
6666
viewport: breakpoints.desktop,
6767
},
68+
'light leftCol': {
69+
globalColourScheme: 'light',
70+
viewport: breakpoints.leftCol,
71+
},
6872
};
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import type { Meta, StoryObj } from '@storybook/react';
2+
import { allModes } from '../../.storybook/modes';
3+
import { FootballCompetitionNav } from './FootballCompetitionNav';
4+
5+
const meta = {
6+
component: FootballCompetitionNav,
7+
title: 'Components/Football Competition Nav',
8+
argTypes: {
9+
selected: {
10+
options: ['fixtures', 'tables', 'none'],
11+
control: { type: 'select' },
12+
},
13+
},
14+
parameters: {
15+
chromatic: {
16+
modes: {
17+
'light mobileMedium': allModes['light mobileMedium'],
18+
'light desktop': allModes['light desktop'],
19+
'light leftCol': allModes['light leftCol'],
20+
},
21+
},
22+
},
23+
} satisfies Meta<typeof FootballCompetitionNav>;
24+
25+
export default meta;
26+
27+
type Story = StoryObj<typeof meta>;
28+
29+
export const WomensEuro2025 = {
30+
args: {
31+
selected: 'fixtures',
32+
pageId: 'football/women-s-euro-2025/table',
33+
},
34+
} satisfies Story;
35+
36+
export const OtherCompetition = {
37+
args: {
38+
selected: 'none',
39+
pageId: 'football/premierleague/table',
40+
},
41+
} satisfies Story;
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
import { css } from '@emotion/react';
2+
import {
3+
from,
4+
headlineBold15Object,
5+
headlineBold17Object,
6+
headlineBold24Object,
7+
headlineBold42Object,
8+
headlineMedium15Object,
9+
headlineMedium17Object,
10+
palette,
11+
} from '@guardian/source/foundations';
12+
import { grid } from '../grid';
13+
14+
type Props = {
15+
selected: 'fixtures' | 'tables' | 'none';
16+
pageId: string;
17+
};
18+
19+
export const FootballCompetitionNav = ({ selected, pageId }: Props) =>
20+
pageId.includes('women-s-euro-2025') ? (
21+
<nav css={nav}>
22+
<a
23+
href="https://www.theguardian.com/football/women-s-euro-2025"
24+
css={largeLinkStyles}
25+
>
26+
Women's Euro 2025
27+
</a>
28+
<ul css={list}>
29+
<li
30+
css={listItem}
31+
style={selected === 'fixtures' ? selectedStyles : undefined}
32+
>
33+
<a
34+
href="https://www.theguardian.com/football/women-s-euro-2025/fixtures"
35+
css={smallLink}
36+
>
37+
Fixtures
38+
</a>
39+
</li>
40+
<li
41+
css={listItem}
42+
style={selected === 'tables' ? selectedStyles : undefined}
43+
>
44+
<a
45+
href="https://www.theguardian.com/football/women-s-euro-2025/overview"
46+
css={smallLink}
47+
>
48+
Tables
49+
</a>
50+
</li>
51+
<li css={listItem}>
52+
<a
53+
href="https://www.theguardian.com/p/x2e3za"
54+
css={smallLink}
55+
>
56+
Top scorers
57+
</a>
58+
</li>
59+
<li css={listItem}>
60+
<a
61+
href="https://www.theguardian.com/p/x27nz8"
62+
css={smallLink}
63+
>
64+
Players guide
65+
</a>
66+
</li>
67+
<li css={listItem}>
68+
<a
69+
href="https://www.theguardian.com/football/women-s-euro-2025"
70+
css={lastSmallLink}
71+
>
72+
Full coverage
73+
</a>
74+
</li>
75+
</ul>
76+
</nav>
77+
) : null;
78+
79+
const nav = css({
80+
backgroundColor: palette.news[400],
81+
'&': css(grid.paddedContainer),
82+
alignContent: 'space-between',
83+
height: 116,
84+
[from.tablet]: {
85+
height: 140,
86+
},
87+
[from.desktop]: {
88+
height: 150,
89+
},
90+
});
91+
92+
const largeLinkStyles = css({
93+
...headlineBold24Object,
94+
color: palette.neutral[100],
95+
textDecoration: 'none',
96+
'&': css(grid.column.centre),
97+
[from.tablet]: headlineBold42Object,
98+
[from.leftCol]: css(grid.between('left-column-start', 'right-column-end')),
99+
});
100+
101+
const list = css({
102+
display: 'flex',
103+
flexWrap: 'wrap',
104+
'&': css(grid.column.all),
105+
position: 'relative',
106+
'--top-border-gap': '1.55rem',
107+
[from.mobileLandscape]: {
108+
paddingLeft: 10,
109+
},
110+
[from.tablet]: {
111+
'--top-border-gap': '3rem',
112+
},
113+
backgroundImage: `linear-gradient(
114+
#d84d4d 0,
115+
#d84d4d 1px,
116+
transparent 1px,
117+
transparent var(--top-border-gap),
118+
#d84d4d var(--top-border-gap),
119+
#d84d4d calc(var(--top-border-gap) + 1px),
120+
transparent 1px,
121+
transparent var(--top-border-gap)
122+
)`,
123+
});
124+
125+
const selectedStyles = {
126+
'--selected-height': '4px',
127+
'--selected-opacity': '1',
128+
};
129+
130+
const listItem = css({
131+
position: 'relative',
132+
'&::before': {
133+
content: '""',
134+
display: 'block',
135+
position: 'absolute',
136+
left: 0,
137+
top: 0,
138+
width: '100%',
139+
height: 'var(--selected-height, 0)',
140+
opacity: 'var(--selected-opacity, 0)',
141+
backgroundColor: palette.neutral[100],
142+
transition: 'height 0.3s ease-in-out, opacity 0.05s 0.1s linear',
143+
},
144+
'&:hover': selectedStyles,
145+
[from.leftCol]: {
146+
flexBasis: 160,
147+
},
148+
});
149+
150+
const smallLink = css({
151+
...headlineBold15Object,
152+
padding: '4px 10px 6px',
153+
display: 'block',
154+
lineHeight: 1,
155+
color: palette.neutral[100],
156+
textDecoration: 'none',
157+
'&::after': {
158+
content: '""',
159+
display: 'block',
160+
position: 'absolute',
161+
top: 0,
162+
right: 0,
163+
width: 1,
164+
height: '1.3rem',
165+
backgroundColor: '#d84d4d',
166+
},
167+
[from.tablet]: headlineBold17Object,
168+
[from.leftCol]: {
169+
padding: '9px 10px 10px',
170+
},
171+
});
172+
173+
const lastSmallLink = css(smallLink, {
174+
...headlineMedium15Object,
175+
lineHeight: 1,
176+
[from.tablet]: headlineMedium17Object,
177+
});

dotcom-rendering/src/components/FootballMatchesPage.stories.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { StoryObj } from '@storybook/react';
22
import { fn } from '@storybook/test';
33
import { initialDays, regions } from '../../fixtures/manual/footballData';
4+
import { WomensEuro2025 } from './FootballCompetitionNav.stories';
45
import { FootballMatchesPage } from './FootballMatchesPage';
56

67
const meta = {
@@ -38,3 +39,10 @@ export const Fixtures = {
3839
kind: 'FootballFixtures',
3940
},
4041
} satisfies Story;
42+
43+
export const WithCompetitionNav = {
44+
args: {
45+
...Fixtures.args,
46+
pageId: WomensEuro2025.args.pageId,
47+
},
48+
} satisfies Story;

0 commit comments

Comments
 (0)