Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dotcom-rendering/fixtures/generated/match-report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ export const matchReport: MatchReportType = {
shotsOff: 6,
corners: 10,
fouls: 4,
colours: '#ffffff',
colours: '#01009a',
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed this temporarily until we fix the colour contrast

crest: 'https://sport.guim.co.uk/football/crests/120/7699.png',
},
awayTeam: {
Expand Down
2 changes: 1 addition & 1 deletion dotcom-rendering/src/components/FootballMatchStat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const labelCss = css`
${textSansBold14};
grid-area: label;
justify-self: center;
color: ${palette('--football-match-stat-name')};
color: ${palette('--football-match-stat-text')};
${from.desktop} {
${textSansBold15};
}
Expand Down
55 changes: 55 additions & 0 deletions dotcom-rendering/src/components/Lineups.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { css } from '@emotion/react';
import type { Meta, StoryObj } from '@storybook/react-webpack5';
import { allModes } from '../../.storybook/modes';
import { matchReport } from '../../fixtures/generated/match-report';
import { grid } from '../grid';
import { palette } from '../palette';
import { type TeamType } from '../types/sport';
import { Lineups as LineupsComponent } from './Lineups';

const homeTeam: TeamType = matchReport.homeTeam;

const awayTeam: TeamType = matchReport.awayTeam;

const meta = {
title: 'Components/Lineups',
component: LineupsComponent,
parameters: {
chromatic: {
modes: {
'light leftCol': allModes['light leftCol'],
},
},
},
decorators: [
(Story) => (
<div
css={css`
background-color: ${palette(
'--football-match-info-background',
)};
${grid.paddedContainer}
`}
>
<div
css={css`
${grid.column.centre}
`}
>
<Story />
</div>
</div>
),
],
} satisfies Meta<typeof LineupsComponent>;

export default meta;

type Story = StoryObj<typeof meta>;

export const LineupsStory = {
args: {
home: homeTeam,
away: awayTeam,
},
} satisfies Story;
224 changes: 224 additions & 0 deletions dotcom-rendering/src/components/Lineups.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
import { css } from '@emotion/react';
import {
from,
palette as sourcePalette,
space,
textSans14,
textSans15,
textSansBold12,
textSansBold14,
} from '@guardian/source/foundations';
import type { FootballTeam } from '../footballMatch';
import { palette } from '../palette';
import Union from '../static/icons/Union.svg';
import type { EventType } from '../types/sport';

type Props = {
home: FootballTeam;
away: FootballTeam;
};

const lineupSectionId = 'lineups';
const substitutesSectionId = 'substitutes';

export const Lineups = ({ home, away }: Props) => {
return (
<section css={sectionStyles} aria-label="Team Lineups and Substitutes">
<section
css={playerListSectionGridStyles}
aria-labelledby={lineupSectionId}
>
<Title text="Lineups" id={lineupSectionId} />
<PlayerList team={home} isSubstitute={false} isHome={true} />
<PlayerList team={away} isSubstitute={false} isHome={false} />
</section>
<section
css={playerListSectionGridStyles}
aria-labelledby={substitutesSectionId}
>
<Title text="Substitutes" id={substitutesSectionId} />
<PlayerList team={home} isSubstitute={true} isHome={true} />
<PlayerList team={away} isSubstitute={true} isHome={false} />
</section>
</section>
);
};

const Event = ({
type,
time,
}: {
type: 'substitution' | 'dismissal' | 'booking';
time: string;
}) => {
switch (type) {
case 'dismissal':
return (
<span
css={rectangle(BackgroundRed)}
aria-label="Red Card"
aria-hidden="false"
/>
);
case 'booking':
return (
<span
css={rectangle(BackgroundYellow)}
aria-label="Yellow Card"
aria-hidden="false"
/>
);
case 'substitution':
return (
<span
aria-label={`Substitution in ${time} minute`}
css={substitute}
>
<Union />
{time}
</span>
);
}
};

const Title = ({ text, id }: { text: string; id: string }) => (
<h3
id={id}
css={css`
border-bottom: 1px solid ${palette('--football-match-stat-border')};
color: ${palette('--football-match-stat-text')};
grid-column: home-start / away-end;
padding-bottom: ${space[1]}px;
${textSansBold14}
`}
>
{text}
</h3>
);

const PlayerList = ({
team,
isSubstitute,
isHome,
}: {
team: FootballTeam;
isSubstitute: boolean;
isHome: boolean;
}) => {
return (
<ul css={isHome ? homeStyles : awayStyles}>
{team.players
.filter((player) => player.substitute === isSubstitute)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we filtering the substitutes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are 2 different sections one for lineups (Players who are not substitutes) and the other for substitutes. So because the PlayerList is used for both of them, here we're checking if the component is to be used for the lineups section or the substitute section (depending the value of isSubstitute)

.map((player) => (
<li key={player.id} css={listItem}>
<strong css={shirtNumber(team.colours)}>
{player.shirtNumber}
</strong>
<span css={playerName}>
{player.name.charAt(0).toUpperCase()}.{' '}
{player.lastName}
</span>
{player.events.map((event: EventType) => (
<Event
key={event.eventTime + event.eventType}
type={event.eventType}
time={event.eventTime}
/>
))}
</li>
))}
</ul>
);
};

const sectionStyles = css`
border: 1px solid ${palette('--football-match-stat-border')};
margin: ${space[2]}px;
border-radius: 6px;

padding-top: 6px;

background-color: ${palette('--football-match-info-background')};
`;

const playerListSectionGridStyles = css`
display: grid;
grid-template-columns: [home-start] 1fr [home-end away-start] 1fr [away-end];
column-gap: 20px;

padding: 0px 10px 10px 10px;
`;

const homeStyles = css`
grid-column: home-start / home-end;
`;

const awayStyles = css`
grid-column: away-start / away-end;
position: relative;
&::before {
content: '';
position: absolute;
left: -10px;
top: 0;
bottom: 0;
width: 1px;
background-color: ${palette('--football-match-stat-border')};
}
`;

const shirtNumber = (color: string) => css`
display: inline-block;
width: ${space[5]}px;
${textSansBold14}
color: ${color};
`;

const listItem = css`
padding-top: ${space[2]}px;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: ${space[1]}px;

&:last-child {
padding-bottom: ${space[0]}px;
}
`;

const playerName = css`
${textSans14}
${from.tablet} {
${textSans15}
}
color: ${palette('--football-match-stat-text')};
`;

const BackgroundRed = sourcePalette.news[400];

const BackgroundYellow = sourcePalette.brandAlt[300];

const rectangle = (color: string) => css`
display: inline-block;
width: ${space[3]}px;
height: ${space[4]}px;
border-radius: ${space[0]}px;
margin-left: ${space[1]}px;
background-color: ${color};
`;

const substitute = css`
${textSansBold12}
border: 1px solid rgba(18, 18, 18, 0.20);
border-radius: ${space[8]}px;
padding: 0.5px ${space[1]}px 1.5px ${space[1]}px;
display: flex;
align-items: center;
color: ${palette('--football-match-stat-text')};
opacity: 0.6;
gap: ${space[0]}px;

svg {
fill: ${palette('--football-match-substitution-icon')};
}
`;
10 changes: 9 additions & 1 deletion dotcom-rendering/src/paletteDeclarations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7051,6 +7051,10 @@ const paletteColours = {
light: () => sourcePalette.neutral[93],
dark: () => sourcePalette.neutral[38],
},
'--football-match-info-background': {
light: () => sourcePalette.neutral[100],
dark: () => sourcePalette.neutral[10],
},
'--football-match-list-error': {
light: () => sourcePalette.error[400],
dark: () => sourcePalette.error[500],
Expand All @@ -7071,10 +7075,14 @@ const paletteColours = {
light: () => '#00679E', // replace with Source's `calculateHoverColour` when available
dark: () => '#00A1E6',
},
'--football-match-stat-name': {
'--football-match-stat-text': {
light: () => sourcePalette.neutral[7],
dark: () => sourcePalette.neutral[86],
},
'--football-match-substitution-icon': {
light: () => sourcePalette.neutral[46],
dark: () => sourcePalette.neutral[60],
},
'--football-score-border': {
light: () => sourcePalette.neutral[7],
dark: () => sourcePalette.neutral[7],
Expand Down
3 changes: 3 additions & 0 deletions dotcom-rendering/src/static/icons/Union.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading