Skip to content

Commit cf383e2

Browse files
committed
Add Lineups component
1 parent 42be578 commit cf383e2

File tree

4 files changed

+285
-1
lines changed

4 files changed

+285
-1
lines changed

dotcom-rendering/fixtures/generated/match-report.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,8 @@ export const matchReport: MatchReportType = {
274274
shotsOff: 6,
275275
corners: 10,
276276
fouls: 4,
277-
colours: '#ffffff',
277+
colours: '#01009a',
278+
// colours: '#fff', // #fef502
278279
crest: 'https://sport.guim.co.uk/football/crests/120/7699.png',
279280
},
280281
awayTeam: {
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import type { Meta, StoryObj } from '@storybook/react-webpack5';
2+
import { allModes } from '../../.storybook/modes';
3+
import { matchReport } from '../../fixtures/generated/match-report';
4+
import { type TeamType } from '../types/sport';
5+
import { Lineups as LineupsComponent } from './Lineups';
6+
7+
const homeTeam: TeamType = matchReport.homeTeam;
8+
9+
const awayTeam: TeamType = matchReport.awayTeam;
10+
11+
const meta = {
12+
title: 'Components/Lineups',
13+
component: LineupsComponent,
14+
parameters: {
15+
chromatic: {
16+
modes: {
17+
'light leftCol': allModes['light leftCol'],
18+
},
19+
},
20+
},
21+
} satisfies Meta<typeof LineupsComponent>;
22+
23+
export default meta;
24+
25+
type Story = StoryObj<typeof meta>;
26+
27+
export const LineupsStory = {
28+
args: {
29+
home: homeTeam,
30+
away: awayTeam,
31+
},
32+
} satisfies Story;
Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
import { css } from '@emotion/react';
2+
import {
3+
palette as sourcePalette,
4+
space,
5+
textSans15,
6+
textSansBold14,
7+
} from '@guardian/source/foundations';
8+
import { type FootballTeam } from '../footballMatch';
9+
import { palette } from '../palette';
10+
import Union from '../static/icons/Union.svg';
11+
import type { EventType } from '../types/sport';
12+
13+
type Props = {
14+
home: FootballTeam;
15+
away: FootballTeam;
16+
};
17+
18+
const sectionStyles = css`
19+
border: 1px solid ${sourcePalette.neutral[86]};
20+
margin: ${space[2]}px;
21+
border-radius: 6px;
22+
`;
23+
24+
const lineupSectionGridStyles = css`
25+
display: grid;
26+
grid-template-columns: [home-start] 1fr [home-end away-start] 1fr [away-end];
27+
column-gap: 20px;
28+
29+
padding: 6px 10px;
30+
`;
31+
32+
const homeStyles = css`
33+
grid-column: home-start / home-end;
34+
`;
35+
36+
const awayStyles = css`
37+
grid-column: away-start / away-end;
38+
position: relative;
39+
&::before {
40+
content: '';
41+
position: absolute;
42+
left: -10px;
43+
top: 0;
44+
bottom: 0;
45+
width: 1px;
46+
background-color: ${sourcePalette.neutral[86]};
47+
}
48+
`;
49+
50+
const shirtNumber = (color: string) => css`
51+
display: inline-block;
52+
width: 20px;
53+
${textSansBold14}
54+
color: ${color};
55+
`;
56+
57+
const listItem = css`
58+
padding-top: ${space[2]}px;
59+
display: flex;
60+
align-items: center;
61+
`;
62+
63+
const playerName = css`
64+
${textSans15}
65+
color: ${palette('--match-nav-text')};
66+
`;
67+
68+
export const Lineups = ({ home, away }: Props) => {
69+
return (
70+
<div>
71+
<section css={sectionStyles}>
72+
<section css={lineupSectionGridStyles}>
73+
<h3
74+
css={css`
75+
border-bottom: 1px solid
76+
${sourcePalette.neutral[86]};
77+
grid-column: home-start / away-end;
78+
padding-bottom: ${space[1]}px;
79+
${textSansBold14}
80+
`}
81+
>
82+
Lineups
83+
</h3>
84+
<ul css={homeStyles}>
85+
{home.players
86+
.filter((player) => !player.substitute)
87+
.map((player) => (
88+
<li key={player.id} css={listItem}>
89+
<strong css={shirtNumber(home.colours)}>
90+
{player.shirtNumber}
91+
</strong>
92+
<span css={playerName}>
93+
{player.name.charAt(0).toUpperCase()}.{' '}
94+
{player.lastName}
95+
</span>
96+
{player.events.map((event: EventType) => (
97+
<Event
98+
key={
99+
event.eventTime +
100+
event.eventType
101+
}
102+
type={event.eventType}
103+
time={event.eventTime}
104+
/>
105+
))}
106+
</li>
107+
))}
108+
</ul>
109+
<ul css={awayStyles}>
110+
{away.players
111+
.filter((player) => !player.substitute)
112+
.map((player) => (
113+
<li key={player.id} css={listItem}>
114+
<strong css={shirtNumber(away.colours)}>
115+
{player.shirtNumber}
116+
</strong>
117+
<span css={playerName}>
118+
{player.name.charAt(0).toUpperCase()}.{' '}
119+
{player.lastName}
120+
</span>
121+
{player.events.map((event: EventType) => (
122+
<Event
123+
key={
124+
event.eventTime +
125+
event.eventType
126+
}
127+
type={event.eventType}
128+
time={event.eventTime}
129+
/>
130+
))}
131+
</li>
132+
))}
133+
</ul>
134+
</section>
135+
<section css={lineupSectionGridStyles}>
136+
<h3
137+
css={css`
138+
border-bottom: 1px solid
139+
${sourcePalette.neutral[86]};
140+
grid-column: home-start / away-end;
141+
padding-bottom: ${space[1]}px;
142+
padding-top: ${space[3]}px;
143+
${textSansBold14}
144+
`}
145+
>
146+
Substitutes
147+
</h3>
148+
<ul css={homeStyles}>
149+
{home.players
150+
.filter((player) => player.substitute)
151+
.map((player) => (
152+
<li key={player.id} css={listItem}>
153+
<strong css={shirtNumber(home.colours)}>
154+
{player.shirtNumber}
155+
</strong>
156+
<span css={playerName}>
157+
{player.name.charAt(0).toUpperCase()}.{' '}
158+
{player.lastName}
159+
</span>
160+
{player.events.map((event: EventType) => (
161+
<Event
162+
key={
163+
event.eventTime +
164+
event.eventType
165+
}
166+
type={event.eventType}
167+
time={event.eventTime}
168+
/>
169+
))}
170+
</li>
171+
))}
172+
</ul>
173+
<ul css={awayStyles}>
174+
{away.players
175+
.filter((player) => player.substitute)
176+
.map((player) => (
177+
<li key={player.id} css={listItem}>
178+
<strong css={shirtNumber(away.colours)}>
179+
{player.shirtNumber}
180+
</strong>
181+
<span css={playerName}>
182+
{player.name.charAt(0).toUpperCase()}.{' '}
183+
{player.lastName}
184+
</span>
185+
{player.events.map((event: EventType) => (
186+
<Event
187+
key={
188+
event.eventTime +
189+
event.eventType
190+
}
191+
type={event.eventType}
192+
time={event.eventTime}
193+
/>
194+
))}
195+
</li>
196+
))}
197+
</ul>
198+
</section>
199+
</section>
200+
</div>
201+
);
202+
};
203+
204+
const BackgroundRed = '#cc2b12';
205+
206+
const BackgroundYellow = '#FFD900';
207+
208+
const rectangle = (color: string) => css`
209+
display: inline-block;
210+
width: ${space[3]}px;
211+
height: ${space[4]}px;
212+
border-radius: ${space[0]}px;
213+
margin-left: ${space[1]}px;
214+
background-color: ${color};
215+
`;
216+
217+
const Event = ({
218+
type,
219+
time,
220+
}: {
221+
type: 'substitution' | 'dismissal' | 'booking';
222+
time: string;
223+
}) => {
224+
switch (type) {
225+
case 'dismissal':
226+
return (
227+
<span
228+
css={rectangle(BackgroundRed)}
229+
aria-label="Red Card"
230+
aria-hidden="false"
231+
/>
232+
);
233+
case 'booking':
234+
return (
235+
<span
236+
css={rectangle(BackgroundYellow)}
237+
aria-label="Yellow Card"
238+
aria-hidden="false"
239+
/>
240+
);
241+
case 'substitution':
242+
return (
243+
<span>
244+
<Union />
245+
</span>
246+
);
247+
}
248+
};
Lines changed: 3 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)