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
79 changes: 79 additions & 0 deletions dotcom-rendering/src/components/FootballMatchStat.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { css } from '@emotion/react';
import { space } from '@guardian/source/foundations';
import type { Meta, StoryObj } from '@storybook/react-webpack5';
import { palette } from '../palette';
import { FootballMatchStat } from './FootballMatchStat';

const meta = {
title: 'Components/Football Match Stat',
component: FootballMatchStat,
decorators: [
(Story) => (
<div
css={css`
padding: ${space[4]}px;
background-color: ${palette(
'--football-live-blog-background',
)};
`}
>
<Story />
</div>
),
],
parameters: {
viewport: {
defaultViewport: 'mobileMedium',
},
},
} satisfies Meta<typeof FootballMatchStat>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Default = {
args: {
label: 'Goal Attempts',
home: {
teamName: 'Manchester United',
teamColour: '#da020e',
value: 7,
},
away: {
teamName: 'Arsenal',
teamColour: '#023474',
value: 4,
},
},
} satisfies Story;

export const ShownAsPercentage = {
args: {
label: 'Possession',
home: {
teamName: 'West Ham',
teamColour: '#722642',
value: 39,
},
away: {
teamName: 'Newcastle',
teamColour: '#383838',
value: 61,
},
showPercentage: true,
},
} satisfies Story;

export const RaisedLabelOnDesktop = {
args: {
...Default.args,
raiseLabelOnDesktop: true,
},
} satisfies Story;

export const LargeNumbersOnDesktop = {
args: {
...Default.args,
largeNumbersOnDesktop: true,
},
} satisfies Story;
165 changes: 165 additions & 0 deletions dotcom-rendering/src/components/FootballMatchStat.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import { css } from '@emotion/react';
import {
from,
space,
textSansBold14,
textSansBold15,
textSansBold20,
textSansBold28,
visuallyHidden,
} from '@guardian/source/foundations';
import { palette } from '../palette';

const containerCss = css`
position: relative;
padding: 5px 10px 10px;
border: 1px solid ${palette('--football-match-stat-border')};
border-radius: 6px;
&::before {
position: absolute;
content: '';
left: 50%;
bottom: 0;
width: 1px;
height: ${space[6]}px;
background-color: ${palette('--football-match-stat-border')};
}
`;

const headerCss = css`
display: grid;
grid-template-columns: auto 1fr auto;
grid-template-areas: 'home-stat label away-stat';
`;

const raiseLabelCss = css`
${from.desktop} {
grid-template-areas:
'label label label'
'home-stat . away-stat';
}
`;

const labelCss = css`
${textSansBold14};
grid-area: label;
justify-self: center;
color: ${palette('--football-match-stat-name')};
${from.desktop} {
${textSansBold15};
}
`;

const numberCss = css`
${textSansBold20};
grid-area: home-stat;
color: var(--match-stat-team-colour);
`;

const largeNumberCss = css`
${from.desktop} {
${textSansBold28}
}
`;

const awayStatCss = css`
grid-area: away-stat;
justify-self: end;
`;

const chartCss = css`
position: relative;
display: flex;
gap: 10px;
`;

const barCss = css`
height: ${space[2]}px;
width: var(--match-stat-percentage);
background-color: var(--match-stat-team-colour);
border-radius: 8px;
`;

type MatchStatistic = {
teamName: string;
teamColour: string;
value: number;
};

type Props = {
label: string;
home: MatchStatistic;
away: MatchStatistic;
showPercentage?: boolean;
raiseLabelOnDesktop?: boolean;
largeNumbersOnDesktop?: boolean;
};

const formatValue = (value: number, showPercentage: boolean) =>
`${value}${showPercentage ? '%' : ''}`;

export const FootballMatchStat = ({
label,
home,
away,
showPercentage = false,
raiseLabelOnDesktop = false,
largeNumbersOnDesktop = false,
}: Props) => {
const homePercentage = (home.value / (home.value + away.value)) * 100;
const awayPercentage = (away.value / (home.value + away.value)) * 100;

return (
<div css={containerCss}>
<div css={[headerCss, raiseLabelOnDesktop && raiseLabelCss]}>
<span css={labelCss}>{label}</span>
<span
css={[numberCss, largeNumbersOnDesktop && largeNumberCss]}
style={{ '--match-stat-team-colour': home.teamColour }}
>
<span
css={css`
${visuallyHidden}
`}
>
{home.teamName}
</span>
{formatValue(home.value, showPercentage)}
</span>
<span
css={[
numberCss,
awayStatCss,
largeNumbersOnDesktop && largeNumberCss,
]}
style={{ '--match-stat-team-colour': away.teamColour }}
>
<span
css={css`
${visuallyHidden}
`}
>
{away.teamName}
</span>
{formatValue(away.value, showPercentage)}
</span>
</div>
<div aria-hidden="true" css={chartCss}>
<div
css={barCss}
style={{
'--match-stat-percentage': `${homePercentage}%`,
'--match-stat-team-colour': home.teamColour,
}}
></div>
<div
css={barCss}
style={{
'--match-stat-percentage': `${awayPercentage}%`,
'--match-stat-team-colour': away.teamColour,
}}
></div>
</div>
</div>
);
};
75 changes: 75 additions & 0 deletions dotcom-rendering/src/components/FootballMiniMatchStats.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { css } from '@emotion/react';
import { breakpoints, from } from '@guardian/source/foundations';
import type { Meta, StoryObj } from '@storybook/react-webpack5';
import { palette } from '../palette';
import { FootballMiniMatchStats as FootballMiniMatchStatsComponent } from './FootballMiniMatchStats';

const gridCss = css`
background-color: ${palette('--football-live-blog-background')};
/**
* Extremely simplified live blog grid layout as we're only interested in
* the 240px wide left column added at the desktop breakpoint.
* dotcom-rendering/src/layouts/LiveLayout.tsx
*/
${from.desktop} {
display: grid;
grid-column-gap: 20px;
grid-template-columns: 240px 1fr;
}
`;

const containerCss = css`
padding: 10px;
${from.desktop} {
padding-left: 20px;
padding-right: 0;
}
`;

const meta = {
title: 'Components/Football Mini Match Stats',
component: FootballMiniMatchStatsComponent,
decorators: [
(Story) => (
<div css={gridCss}>
<div css={containerCss}>
<Story />
</div>
</div>
),
],
parameters: {
chromatic: {
viewports: [
breakpoints.mobileMedium,
breakpoints.tablet,
breakpoints.wide,
],
},
},
} satisfies Meta<typeof FootballMiniMatchStatsComponent>;

export default meta;
type Story = StoryObj<typeof meta>;

export const FootballMiniMatchStats = {
args: {
homeTeam: {
name: 'Manchester United',
colour: '#da020e',
},
awayTeam: {
name: 'Arsenal',
colour: '#023474',
},
stats: [
{
label: 'Possession',
homeValue: 39,
awayValue: 61,
showPercentage: true,
},
{ label: 'Goal Attempts', homeValue: 7, awayValue: 4 },
],
},
} satisfies Story;
Loading
Loading