diff --git a/dotcom-rendering/src/components/Distribution.tsx b/dotcom-rendering/src/components/Distribution.tsx index 12ddf8f1d5e..a9a56361659 100644 --- a/dotcom-rendering/src/components/Distribution.tsx +++ b/dotcom-rendering/src/components/Distribution.tsx @@ -1,6 +1,6 @@ import { css } from '@emotion/react'; import { headlineBold34, text } from '@guardian/source/foundations'; -import { isLight } from '../lib/isLight'; +import { isLight } from '../lib/colour'; type Props = { left: BarType; diff --git a/dotcom-rendering/src/components/Doughnut.tsx b/dotcom-rendering/src/components/Doughnut.tsx index adaf192517a..127a5a34d90 100644 --- a/dotcom-rendering/src/components/Doughnut.tsx +++ b/dotcom-rendering/src/components/Doughnut.tsx @@ -5,7 +5,7 @@ import { text, textSans15, } from '@guardian/source/foundations'; -import { isLight } from '../lib/isLight'; +import { isLight } from '../lib/colour'; type Props = { sections: SectionType[]; diff --git a/dotcom-rendering/src/components/FootballMatchStat.stories.tsx b/dotcom-rendering/src/components/FootballMatchStat.stories.tsx index 339ecc24a57..3dfca357907 100644 --- a/dotcom-rendering/src/components/FootballMatchStat.stories.tsx +++ b/dotcom-rendering/src/components/FootballMatchStat.stories.tsx @@ -1,6 +1,8 @@ import { css } from '@emotion/react'; import { space } from '@guardian/source/foundations'; import type { Meta, StoryObj } from '@storybook/react-webpack5'; +import { splitTheme } from '../../.storybook/decorators/splitThemeDecorator'; +import { ArticleDesign, ArticleDisplay, Pillar } from '../lib/articleFormat'; import { palette } from '../palette'; import { FootballMatchStat } from './FootballMatchStat'; @@ -20,6 +22,13 @@ const meta = { ), + splitTheme([ + { + design: ArticleDesign.Standard, + display: ArticleDisplay.Standard, + theme: Pillar.News, + }, + ]), ], parameters: { viewport: { @@ -31,6 +40,97 @@ const meta = { export default meta; type Story = StoryObj; +const teams = [ + { + home: { name: 'Wolves', colour: '#faa01b' }, + away: { name: 'Man Utd', colour: '#b00101' }, + }, + { + home: { name: 'Fulham', colour: '#ffffff' }, + away: { name: 'C Palace', colour: '#af1f17' }, + }, + { + home: { name: 'Brighton', colour: '#2a449a' }, + away: { name: 'West Ham', colour: '#7c1e42' }, + }, + { + home: { name: 'Leeds', colour: '#f5f5f5' }, + away: { name: 'Liverpool', colour: '#ce070c' }, + }, + { + home: { name: 'AFC Bournemouth', colour: '#c80000' }, + away: { name: 'Chelsea', colour: '#005ca4' }, + }, + { + home: { name: 'Everton', colour: '#00349a' }, + away: { name: 'Nottm Forest', colour: '#db1812' }, + }, + { + home: { name: 'Man City', colour: '#5cbfeb' }, + away: { name: 'Sunderland', colour: '#d51022' }, + }, + { + home: { name: 'Newcastle', colour: '#383838' }, + away: { name: 'Burnley', colour: '#570e30' }, + }, + { + home: { name: 'Spurs', colour: '#ffffff' }, + away: { name: 'Brentford', colour: '#c4040f' }, + }, + { + home: { name: 'Aston Villa', colour: '#720e44' }, + away: { name: 'Arsenal', colour: '#c40007' }, + }, + { + home: { name: 'Birmingham', colour: '#01009a' }, + away: { name: 'Norwich', colour: '#ffe400' }, + }, + { + home: { name: 'Derby', colour: '#ffffff' }, + away: { name: 'Watford', colour: '#fef502' }, + }, + { + home: { name: 'Leicester', colour: '#4b2cd3' }, + away: { name: 'Stoke', colour: '#cc0617' }, + }, + { + home: { name: 'Oxford Utd', colour: '#fec726' }, + away: { name: 'Middlesbrough', colour: '#e70101' }, + }, + { + home: { name: 'Portsmouth', colour: '#0077ac' }, + away: { name: 'Millwall', colour: '#1a2791' }, + }, + { + home: { name: 'QPR', colour: '#1f539f' }, + away: { name: 'Hull', colour: '#f2b100' }, + }, + { + home: { name: 'Bristol City', colour: '#c70c23' }, + away: { name: 'Swansea', colour: '#ffffff' }, + }, + { + home: { name: 'Charlton', colour: '#d4222b' }, + away: { name: 'Southampton', colour: '#d71921' }, + }, + { + home: { name: 'Coventry', colour: '#b1d0ff' }, + away: { name: 'West Brom', colour: '#00246a' }, + }, + { + home: { name: 'Celtic', colour: '#559861' }, + away: { name: 'Dundee', colour: '#000033' }, + }, + { + home: { name: 'Falkirk', colour: '#002341' }, + away: { name: 'Motherwell', colour: '#f0c650' }, + }, + { + home: { name: 'Dundee Utd', colour: '#ff6c00' }, + away: { name: 'Rangers', colour: '#195091' }, + }, +]; + export const Default = { args: { label: 'Goal Attempts', @@ -77,3 +177,35 @@ export const LargeNumbersOnDesktop = { largeNumbersOnDesktop: true, }, } satisfies Story; + +export const TeamColours = { + render: (args) => ( +
+ {teams.map((match, index) => ( + + ))} +
+ ), + args: { + ...ShownAsPercentage.args, + }, +} satisfies Story; diff --git a/dotcom-rendering/src/components/FootballMatchStat.tsx b/dotcom-rendering/src/components/FootballMatchStat.tsx index 5f4d350b965..776b40d5ebb 100644 --- a/dotcom-rendering/src/components/FootballMatchStat.tsx +++ b/dotcom-rendering/src/components/FootballMatchStat.tsx @@ -1,6 +1,7 @@ import { css } from '@emotion/react'; import { from, + palette as sourcePalette, space, textSansBold14, textSansBold15, @@ -8,6 +9,7 @@ import { textSansBold28, visuallyHidden, } from '@guardian/source/foundations'; +import { getContrast } from '../lib/colour'; import { palette } from '../palette'; const containerCss = css` @@ -62,6 +64,24 @@ const largeNumberCss = css` } `; +const numberLightContrastCss = css` + @media (prefers-color-scheme: light) { + color: ${palette('--football-match-stat-text')}; + } + [data-color-scheme='light'] & { + color: ${palette('--football-match-stat-text')}; + } +`; + +const numberDarkContrastCss = css` + @media (prefers-color-scheme: dark) { + color: ${palette('--football-match-stat-text')}; + } + [data-color-scheme='dark'] & { + color: ${palette('--football-match-stat-text')}; + } +`; + const awayStatCss = css` grid-area: away-stat; justify-self: end; @@ -80,6 +100,24 @@ const barCss = css` border-radius: 8px; `; +const barLightContrastCss = css` + @media (prefers-color-scheme: light) { + border: 1px solid ${palette('--football-match-stat-border')}; + } + [data-color-scheme='light'] & { + border: 1px solid ${palette('--football-match-stat-border')}; + } +`; + +const barDarkContrastCss = css` + @media (prefers-color-scheme: dark) { + border: 1px solid ${palette('--football-match-stat-border')}; + } + [data-color-scheme='dark'] & { + border: 1px solid ${palette('--football-match-stat-border')}; + } +`; + type MatchStatistic = { teamName: string; teamColour: string; @@ -109,12 +147,41 @@ export const FootballMatchStat = ({ const homePercentage = (home.value / (home.value + away.value)) * 100; const awayPercentage = (away.value / (home.value + away.value)) * 100; + const minimumContrast = 3.1; // https://www.w3.org/TR/WCAG21/#contrast-minimum + + const backgroundLight = sourcePalette.neutral[97]; + const backgroundDark = sourcePalette.neutral[10]; + + const homeNeedsContrastWhenLight = + getContrast(home.teamColour, backgroundLight) < minimumContrast; + const awayNeedsContrastWhenLight = + getContrast(away.teamColour, backgroundLight) < minimumContrast; + const homeNeedsContrastWhenDark = + getContrast(home.teamColour, backgroundDark) < minimumContrast; + const awayNeedsContrastWhenDark = + getContrast(away.teamColour, backgroundDark) < minimumContrast; + + /** + * If either team colour lacks sufficient contrast we adjust both numbers + * so we don't appear to be favouring one team over the other. For the chart + * we keep the team colour and apply a contrasting border colour. + */ + const numbersNeedContrastWhenLight = + homeNeedsContrastWhenLight || awayNeedsContrastWhenLight; + const numbersNeedContrastWhenDark = + homeNeedsContrastWhenDark || awayNeedsContrastWhenDark; + return (
{label} @@ -146,14 +215,22 @@ export const FootballMatchStat = ({