Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add theme overrides for ChoiceCard component #1070

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
63ca558
Adding colour theme as a prop to the React components in source
oliverabrahams Jan 9, 2024
8ac6e69
Updated one choice card in group to have a red background in hover
oliverabrahams Jan 9, 2024
6f149af
added override for theme in choice card component
oliverabrahams Jan 9, 2024
8f29a49
updated Dark mode
oliverabrahams Jan 10, 2024
2fcdcc7
Updated the colours to match
oliverabrahams Jan 10, 2024
2fc6169
Updated stories and combined theme func
oliverabrahams Jan 10, 2024
55aba06
remove comment
oliverabrahams Jan 10, 2024
931ca4e
added space
oliverabrahams Jan 10, 2024
804ea61
deprecating old theme whilst updating new theme
oliverabrahams Jan 10, 2024
c449fcc
refactor
oliverabrahams Jan 10, 2024
3d54141
More testing of stories
oliverabrahams Jan 11, 2024
7a69c34
updated types
oliverabrahams Jan 11, 2024
3149d6f
updated function
oliverabrahams Jan 11, 2024
d3c9f41
updated transform to expect only old theme
oliverabrahams Jan 11, 2024
1b2e108
Added test and comments
oliverabrahams Jan 11, 2024
270048a
Updated types and theme
oliverabrahams Jan 11, 2024
ac2593e
Updated stories
oliverabrahams Jan 11, 2024
92f44ab
Updated themes
oliverabrahams Jan 11, 2024
fb9fa2f
fix comment
oliverabrahams Jan 11, 2024
fdaae67
removed dark theme from theme.ts and as an option for the ChoiceCard …
oliverabrahams Jan 11, 2024
4c6cee5
Update libs/@guardian/source-react-components/src/choice-card/theme.ts
oliverabrahams Jan 11, 2024
c081859
removed dark theme from theme.ts and as an option for the ChoiceCard …
oliverabrahams Jan 11, 2024
9c159c7
made the examples use the palette colours
oliverabrahams Jan 11, 2024
9e27b7e
moved theming story elsewhere
oliverabrahams Jan 11, 2024
dc6d487
changed title
oliverabrahams Jan 11, 2024
35477dd
Deleted Story
oliverabrahams Jan 11, 2024
5a2bc82
simplified function
oliverabrahams Jan 11, 2024
668b61a
added Type
oliverabrahams Jan 11, 2024
a1f85af
Added Theming story
oliverabrahams Jan 11, 2024
a39431e
Added Theming story
oliverabrahams Jan 11, 2024
4e57759
Updated UnSelected to Unselected
oliverabrahams Jan 11, 2024
336e79c
:'(
oliverabrahams Jan 11, 2024
a57cded
updated deprecated message for the old theme
oliverabrahams Jan 15, 2024
546e2cf
Update to `theme` prop proposal (#1085)
sndrs Jan 16, 2024
6280453
updated storybook to use the palette
oliverabrahams Jan 17, 2024
07d91b4
turn off turbosnap
oliverabrahams Jan 17, 2024
55ca60f
turn turbosnap back on.
oliverabrahams Jan 17, 2024
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
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import type { Story } from '@storybook/react';
import { palette } from '@guardian/source-foundations';
import type { Meta, StoryFn } from '@storybook/react';
import { SvgCamera } from '../../vendor/icons/SvgCamera';
import { ChoiceCard } from './ChoiceCard';
import type { ChoiceCardProps } from './ChoiceCard';

export default {
const meta: Meta<typeof ChoiceCard> = {
title: 'ChoiceCard',
component: ChoiceCard,
args: {
Expand Down Expand Up @@ -34,33 +35,45 @@ export default {
},
};

const Template: Story<ChoiceCardProps> = (args: ChoiceCardProps) => (
export default meta;

const Template: StoryFn<ChoiceCardProps> = (args: ChoiceCardProps) => (
<ChoiceCard {...args} />
);

// *****************************************************************************

export const DefaultDefaultTheme = Template.bind({});
export const DefaultDefaultTheme: StoryFn<ChoiceCardProps> = Template.bind({});

// *****************************************************************************

export const CheckedDefaultTheme = Template.bind({});
export const CheckedDefaultTheme: StoryFn<ChoiceCardProps> = Template.bind({});
CheckedDefaultTheme.args = {
checked: true,
};

// *****************************************************************************

export const ErrorDefaultTheme = Template.bind({});
export const ErrorDefaultTheme: StoryFn<ChoiceCardProps> = Template.bind({});
ErrorDefaultTheme.args = {
error: true,
};

// *****************************************************************************

export const IconDefaultTheme = Template.bind({});
export const IconDefaultTheme: StoryFn<ChoiceCardProps> = Template.bind({});
IconDefaultTheme.args = {
label: 'Camera',
// @ts-expect-error - Storybook maps 'JSX element' to <em>Option 1</em>
icon: 'JSX element',
};

// *****************************************************************************

export const CustomTheme: StoryFn<ChoiceCardProps> = Template.bind({});
CustomTheme.args = {
theme: {
backgroundUnselected: palette.neutral[0],
textUnselected: palette.lifestyle[500],
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,22 @@ import {
tick,
tickAnimation,
} from './styles';
import { choiceCardTheme as defaultTheme } from './theme';

export type ChoiceCardTheme = {
textUnselected: string;
textSelected: string;
textHover: string;
textError: string;
borderUnselected: string;
borderSelected: string;
borderHover: string;
borderError: string;
backgroundUnselected: string;
backgroundHover: string;
backgroundSelected: string;
backgroundTick: string;
};

export interface ChoiceCardProps
extends InputHTMLAttributes<HTMLInputElement>,
Expand Down Expand Up @@ -49,6 +65,10 @@ export interface ChoiceCardProps
* The type of input you want
*/
type?: 'radio' | 'checkbox';
/**
* A component level theme to override the colour palette of the choice card component
*/
theme?: Partial<ChoiceCardTheme>;
}

/**
Expand All @@ -70,6 +90,7 @@ export const ChoiceCard = ({
cssOverrides,
error,
onChange,
theme = {},
type = 'radio',
...props
}: ChoiceCardProps): EmotionJSX.Element => {
Expand All @@ -81,21 +102,54 @@ export const ChoiceCard = ({
return !!defaultChecked;
};

/** Transforms an old shaped `ThemeProvider` theme to ChoiceCardTheme */
const transformOldProviderTheme = (
providerTheme: Theme['choiceCard'],
): Partial<ChoiceCardTheme> => {
const transformedTheme: Partial<ChoiceCardTheme> = {};

if (providerTheme?.textLabel) {
transformedTheme.textUnselected = providerTheme.textLabel;
}
if (providerTheme?.textChecked) {
transformedTheme.textSelected = providerTheme.textChecked;
}
if (providerTheme?.border) {
transformedTheme.borderUnselected = providerTheme.border;
}
if (providerTheme?.borderChecked) {
transformedTheme.borderSelected = providerTheme.borderChecked;
}
if (providerTheme?.backgroundChecked) {
transformedTheme.backgroundSelected = providerTheme.backgroundChecked;
}
return { ...transformedTheme, ...providerTheme };
};

const combineThemes = (
providerTheme: Theme['choiceCard'],
): ChoiceCardTheme => {
return {
...defaultTheme,
...transformOldProviderTheme(providerTheme),
...theme,
};
};
// prevent the animation firing if a Choice Card has been checked by default
const [userChanged, setUserChanged] = useState(false);

return (
<>
<input
css={(theme: Theme) => [
input(theme.choiceCard),
css={(providerTheme: Theme) => [
input(combineThemes(providerTheme.choiceCard)),
userChanged ? tickAnimation : '',
cssOverrides,
]}
id={id}
value={value}
aria-invalid={!!error}
defaultChecked={defaultChecked != null ? defaultChecked : undefined}
defaultChecked={defaultChecked ?? undefined}
checked={checked != null ? isChecked() : undefined}
onChange={(event) => {
if (onChange) {
Expand All @@ -107,17 +161,21 @@ export const ChoiceCard = ({
{...props}
/>
<label
css={(theme: Theme) => [
choiceCard(theme.choiceCard),
error ? errorChoiceCard(theme.choiceCard) : '',
css={(providerTheme: Theme) => [
choiceCard(combineThemes(providerTheme.choiceCard)),
error ? errorChoiceCard(combineThemes(providerTheme.choiceCard)) : '',
]}
htmlFor={id}
>
<div css={[contentWrapper, !iconSvg ? contentWrapperLabelOnly : '']}>
{iconSvg ? iconSvg : ''}
<div>{labelContent}</div>
</div>
<span css={(theme: Theme) => [tick(theme.choiceCard)]} />
<span
css={(providerTheme: Theme) => [
tick(combineThemes(providerTheme.choiceCard)),
]}
/>
</label>
</>
);
Expand Down
38 changes: 17 additions & 21 deletions libs/@guardian/source-react-components/src/choice-card/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import {
visuallyHidden,
width,
} from '@guardian/source-foundations';
import type { ChoiceCardTheme } from './ChoiceCard';
import type { ChoiceCardColumns } from './ChoiceCardGroup';
import { choiceCardThemeDefault } from './theme';

export const fieldset = css`
${resets.fieldset};
Expand Down Expand Up @@ -61,9 +61,7 @@ export const gridColumns: { [key in ChoiceCardColumns]: SerializedStyles } = {
5: gridColumnsStyle(5),
};

export const input = (
choiceCard = choiceCardThemeDefault.choiceCard,
): SerializedStyles => css`
export const input = (theme: ChoiceCardTheme): SerializedStyles => css`
${visuallyHidden};

&:focus + label {
Expand All @@ -73,11 +71,11 @@ export const input = (
}

&:checked + label {
box-shadow: inset 0 0 0 2px ${choiceCard.borderChecked};
background-color: ${choiceCard.backgroundChecked};
box-shadow: inset 0 0 0 2px ${theme.borderSelected};
background-color: ${theme.backgroundSelected};

& > * {
color: ${choiceCard.textChecked};
color: ${theme.textSelected};

/* last child is the tick */
&:last-child {
Expand Down Expand Up @@ -133,19 +131,18 @@ export const tickAnimation = css`
}
`;

export const choiceCard = (
choiceCard = choiceCardThemeDefault.choiceCard,
): SerializedStyles => css`
export const choiceCard = (theme: ChoiceCardTheme): SerializedStyles => css`
flex: 1;
display: flex;
justify-content: center;
min-height: ${height.inputMedium}px;
margin: 0 0 ${space[2]}px 0;
box-shadow: inset 0 0 0 1px ${choiceCard.border};
box-shadow: inset 0 0 0 1px ${theme.borderUnselected};
border-radius: 4px;
position: relative;
cursor: pointer;
color: ${choiceCard.textLabel};
background-color: ${theme.backgroundUnselected};
color: ${theme.textUnselected};

${from.mobileLandscape} {
margin: 0 ${space[2]}px 0 0;
Expand All @@ -155,8 +152,9 @@ export const choiceCard = (
}

&:hover {
box-shadow: inset 0 0 0 2px ${choiceCard.borderHover};
color: ${choiceCard.textHover};
box-shadow: inset 0 0 0 2px ${theme.borderHover};
background-color: ${theme.backgroundHover};
color: ${theme.textHover};
}
`;

Expand Down Expand Up @@ -205,9 +203,7 @@ export const contentWrapperLabelOnly = css`

// TODO: most of this is duplicated in the checkbox component
// We should extract it into its own module somewhere
export const tick = (
choiceCard = choiceCardThemeDefault.choiceCard,
): SerializedStyles => css`
export const tick = (theme: ChoiceCardTheme): SerializedStyles => css`
/* overall positional properties */
position: absolute;
top: 50%;
Expand All @@ -222,7 +218,7 @@ export const tick = (
&:before {
position: absolute;
display: block;
background-color: ${choiceCard.backgroundTick};
background-color: ${theme.backgroundTick};
transition: all ${transitions.short} ease-in-out;
content: '';
}
Expand All @@ -247,11 +243,11 @@ export const tick = (
`;

export const errorChoiceCard = (
choiceCard = choiceCardThemeDefault.choiceCard,
theme: ChoiceCardTheme,
): SerializedStyles => css`
box-shadow: inset 0 0 0 2px ${choiceCard.borderError};
box-shadow: inset 0 0 0 2px ${theme.borderError};

& > * {
color: ${choiceCard.textError};
color: ${theme.textError};
}
`;
21 changes: 19 additions & 2 deletions libs/@guardian/source-react-components/src/choice-card/theme.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { palette } from '@guardian/source-foundations';
import { userFeedbackThemeDefault } from '../user-feedback/theme';
import type { ChoiceCardTheme } from './ChoiceCard';

/** @deprecated Use `choiceCardTheme` and component `theme` prop instead of emotion's `ThemeProvider` */
export const choiceCardThemeDefault = {
choiceCard: {
textLabel: palette.neutral[46],
Expand All @@ -12,10 +14,25 @@ export const choiceCardThemeDefault = {
backgroundChecked: '#E3F6FF',
backgroundTick: palette.brand[500],
borderChecked: palette.brand[500],
textHover: palette.brand[400],
textHover: palette.brand[500],
borderHover: palette.brand[500],
textError: palette.error[400],
borderError: palette.error[400],
},
...userFeedbackThemeDefault,
};
} as const;

export const choiceCardTheme: ChoiceCardTheme = {
textUnselected: palette.neutral[46],
textSelected: palette.brand[400],
textHover: palette.brand[500],
textError: palette.error[400],
borderUnselected: palette.neutral[46],
borderSelected: palette.brand[500],
borderHover: palette.brand[500],
borderError: palette.error[400],
backgroundUnselected: 'transparent',
backgroundHover: 'transparent',
backgroundSelected: '#E3F6FF',
backgroundTick: palette.brand[500],
} as const;