diff --git a/dotcom-rendering/src/components/ElectionTrackers/ChangeBars.stories.tsx b/dotcom-rendering/src/components/ElectionTrackers/ChangeBars.stories.tsx new file mode 100644 index 00000000000..8408e9a19cb --- /dev/null +++ b/dotcom-rendering/src/components/ElectionTrackers/ChangeBars.stories.tsx @@ -0,0 +1,64 @@ +import { palette as sourcePalette } from '@guardian/source/foundations'; +import type { Meta, StoryObj } from '@storybook/react'; +import { allModes } from '../../../.storybook/modes'; +import { palette } from '../../palette'; +import { ChangeBars } from './ChangeBars'; + +const meta = { + title: 'Components/Election Trackers/Change Bars', + component: ChangeBars, + parameters: { + viewport: { + defaultViewport: 'mobileMedium', + }, + colourSchemeBackground: { + dark: sourcePalette.neutral[20], + }, + chromatic: { + modes: { + 'vertical mobileMedium': allModes['vertical mobileMedium'], + }, + }, + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const UKLocal = { + args: { + changes: [ + { + name: 'Conservative', + abbreviation: 'Con', + change: -635, + colour: palette('--uk-elections-conservative'), + }, + { + name: 'Labour', + abbreviation: 'Lab', + change: -198, + colour: palette('--uk-elections-labour'), + }, + { + name: 'Liberal Democrat', + abbreviation: 'Lib Dem', + change: 146, + colour: palette('--uk-elections-liberal-democrat'), + }, + { + name: 'Reform UK', + abbreviation: 'Reform', + change: 648, + colour: palette('--uk-elections-reform-uk'), + }, + { + name: 'Other', + abbreviation: 'Other', + change: -56, + colour: palette('--uk-elections-others'), + }, + ], + }, +} satisfies Story; diff --git a/dotcom-rendering/src/components/ElectionTrackers/ChangeBars.tsx b/dotcom-rendering/src/components/ElectionTrackers/ChangeBars.tsx new file mode 100644 index 00000000000..52dcd0cf34b --- /dev/null +++ b/dotcom-rendering/src/components/ElectionTrackers/ChangeBars.tsx @@ -0,0 +1,243 @@ +import { + from, + headlineBold17Object, + headlineBold24Object, + headlineBold42Object, + headlineMedium17Object, + headlineMedium20Object, +} from '@guardian/source/foundations'; +import { palette } from '../../palette'; + +type Props = { + changes: Change[]; +}; + +type Change = { + /** + * The name of the value changing. For an election, this could be the name + * of the group or party. It will be used as a React "key" for the element, + * so each value's `name` should be unique relative to the names of other + * changes. + * + * **Examples:** name of a candidate; name of a party. + */ + name: string; + /** + * An abbreviated version of the {@linkcode Change.name}, used when the full + * name will not fit, for example on narrower breakpoints. Note that due to + * the constraints of the design this cannot be longer than about 7 + * characters. + */ + abbreviation: string; + /** + * The change to be represented. Can be positive, negative or zero. + */ + change: number; + /** + * The colour used to represent the value. It expects a CSS `color` value + * (e.g. a hex string). To ensure dark mode support a {@linkcode palette} + * colour can be used; i.e. this property can be set to the return value of + * the {@linkcode palette} function. + */ + colour: string; +}; + +export const ChangeBars = ({ changes }: Props) => { + const maxChange = Math.max(...changes.map((c) => c.change)); + + return ( + + ); +}; + +const ChangeBar = ({ + change, + maxChange, +}: { + change: Change; + maxChange: number; +}) => ( +
  • *']: { + paddingRight: 4, + [from.tablet]: { + paddingRight: 10, + }, + }, + ['&:not(:first-of-type) > *']: { + paddingLeft: 4, + [from.tablet]: { + paddingLeft: 10, + }, + }, + ['&:not(:first-of-type):before']: { + marginLeft: 4, + [from.tablet]: { + marginLeft: 10, + }, + }, + }} + > + + + + +
  • +); + +const Name = ({ + name, + abbreviation, +}: { + name: Change['name']; + abbreviation: Change['abbreviation']; +}) => ( +
    + {name.length > 12 ? abbreviation : name} +
    +); + +const Abbreviation = ({ + abbreviation, +}: { + abbreviation: Change['abbreviation']; +}) => ( +
    + {abbreviation} +
    +); + +const Bar = ({ + change, + maxChange, + colour, +}: { + change: Change['change']; + maxChange: number; + colour: Change['colour']; +}) => ( +
    +
    +
    +); + +const ChangeText = ({ change }: { change: Change['change'] }) => ( +
    +
    + {change > 0 ? '+' : undefined} + {change} +
    +
    +); diff --git a/dotcom-rendering/src/paletteDeclarations.ts b/dotcom-rendering/src/paletteDeclarations.ts index 741830c34e6..78166e307cb 100644 --- a/dotcom-rendering/src/paletteDeclarations.ts +++ b/dotcom-rendering/src/paletteDeclarations.ts @@ -6198,6 +6198,18 @@ const paletteColours = { light: carouselTitleHighlightLight, dark: carouselTitleHighlightDark, }, + '--change-bars-axis': { + light: () => sourcePalette.neutral[46], + dark: () => sourcePalette.neutral[86], + }, + '--change-bars-border': { + light: () => sourcePalette.neutral[86], + dark: () => '#606060', + }, + '--change-bars-text': { + light: () => sourcePalette.neutral[7], + dark: () => sourcePalette.neutral[100], + }, '--click-to-view-background': { light: clickToViewBackgroundLight, dark: clickToViewBackgroundDark,