Skip to content
This repository has been archived by the owner on Dec 6, 2022. It is now read-only.

Commit

Permalink
Merge dd94fa2 into ff694d3
Browse files Browse the repository at this point in the history
  • Loading branch information
marjisound committed Oct 14, 2021
2 parents ff694d3 + dd94fa2 commit 4e65d21
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 131 deletions.
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { css } from '@emotion/react';
import { ArticlePillar, ArticleSpecial } from '@guardian/libs';
import { neutral } from '@guardian/src-foundations';
import { useState } from 'react';
import type { Story } from '../../../../../../lib/@types/storybook-emotion-10-fixes';
import {
asChromaticStory,
asPlayground,
} from '../../../../../../lib/story-intents';
import type { ToggleSwitchProps } from './ToggleSwitch';
import { ToggleSwitch } from './ToggleSwitch';
import type { ToggleSwitchProps } from './ToggleSwitch';

export default {
title: 'Kitchen/source-react-components-development-kitchen/ToggleSwitch',
Expand Down Expand Up @@ -35,65 +35,64 @@ asPlayground(Playground);

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

export const NoLabel = Template.bind({});
asChromaticStory(NoLabel);
export const SlimNoLabel = Template.bind({});
SlimNoLabel.args = {
size: 'slim',
};
asChromaticStory(SlimNoLabel);

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

export const MediumNoLabel = Template.bind({});
MediumNoLabel.args = {
size: 'medium',
};
asChromaticStory(MediumNoLabel);

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

export const WithLabel = Template.bind({});
WithLabel.args = {
export const SlimWithLabel = Template.bind({});
SlimWithLabel.args = {
label: 'Get alerts on this story',
size: 'slim',
};
asChromaticStory(WithLabel);
asChromaticStory(SlimWithLabel);

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

export const DefaultChecked = Template.bind({});
DefaultChecked.args = {
export const MediumWithLabel = Template.bind({});
MediumWithLabel.args = {
label: 'Get alerts on this story',
defaultChecked: true,
size: 'medium',
};
asChromaticStory(DefaultChecked);
asChromaticStory(MediumWithLabel);

// *****************************************************************************
const pillars = [
{ pillar: ArticlePillar.News, label: 'News' },
{ pillar: ArticlePillar.Sport, label: 'Sport' },
{ pillar: ArticlePillar.Culture, label: 'Culture' },
{ pillar: ArticlePillar.Lifestyle, label: 'Lifestyle' },
{ pillar: ArticlePillar.Opinion, label: 'Opinion' },
{ pillar: ArticleSpecial.SpecialReport, label: 'SpecialReport' },
{ pillar: ArticleSpecial.Labs, label: 'Labs' },
];
const RowTemplate: Story<ToggleSwitchProps> = (
args: Partial<ToggleSwitchProps>,
) => (
<div
css={css`
display: flex;
flex-direction: row;
justify-content: space-between;
width: 800px;
`}
>
{pillars.map((pillar) => (
<Template
key={pillar.pillar}
{...args}
theme={pillar.pillar}
label={pillar.label}
/>
))}
</div>
);

export const AllPillarsUnchecked = RowTemplate.bind({});
asChromaticStory(AllPillarsUnchecked);
const stylesForDarkBackground = css`
background: rgb(139, 0, 0);
button[aria-checked='false'] {
background-color: rgba(255, 255, 255, 0.5);
}
label {
color: ${neutral[100]};
}
`;

export const SlimLabelCssOverrideForDarkBackground = Template.bind({});
SlimLabelCssOverrideForDarkBackground.args = {
label: 'Get alerts on this story',
size: 'slim',
cssOverrides: stylesForDarkBackground,
};
asChromaticStory(SlimLabelCssOverrideForDarkBackground);

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

export const AllPillarsChecked = RowTemplate.bind({});
AllPillarsChecked.args = {
defaultChecked: true,
export const MediumLabelCssOverrideForDarkBackground = Template.bind({});
MediumLabelCssOverrideForDarkBackground.args = {
label: 'Get alerts on this story',
size: 'medium',
cssOverrides: stylesForDarkBackground,
};
asChromaticStory(AllPillarsChecked);
asChromaticStory(MediumLabelCssOverrideForDarkBackground);
Original file line number Diff line number Diff line change
@@ -1,30 +1,22 @@
import { css } from '@emotion/react';
import type { EmotionJSX } from '@emotion/react/types/jsx-namespace';
import type { ArticleTheme } from '@guardian/libs';
import { ArticlePillar } from '@guardian/libs';
import { textSans } from '@guardian/src-foundations/typography';
import type { Props } from '@guardian/src-helpers';
import { decideBackground, toggleSwitchStyles } from './styles';
import {
labelStyles,
mediumStyles,
slimStyles,
toggleSwitchStyles,
} from './styles';

export type Size = 'medium' | 'slim';

export interface ToggleSwitchProps extends Props {
/**
* A theme object denoting the style of the button using the enums
* available from [@guardian/libs](https://github.com/guardian/libs/blob/main/src/format.ts).
*
* For example:
*
* ```ts
* Pillar.News,
* ```
*/
theme?: ArticleTheme;
/**
* Whether the ToggleSwitch is checked. This is necessary when using the
* [controlled approach](https://reactjs.org/docs/forms.html#controlled-components)
* (recommended) to form state management.
*
* _Note: if you pass the `checked` prop, you MUST also pass an `onClick`
* handler, or the field will be rendered as read-only._
* Note: if you pass the `checked` prop, you MUST also pass an `onClick`
* handler, or the field will be rendered as read-only.
*/
checked?: boolean;
/**
Expand All @@ -37,6 +29,11 @@ export interface ToggleSwitchProps extends Props {
*
*/
label?: string;
/**
* slim or medium toggle would be different in size and colors
*
*/
size?: Size;
/**
* A callback function called when the component is opened or closed.
* Receives the click event as an argument.
Expand All @@ -54,17 +51,14 @@ export interface ToggleSwitchProps extends Props {
*
*/

const labelStyles = css`
${textSans.small()};
`;

export const ToggleSwitch = ({
theme = ArticlePillar.News,
checked,
label,
defaultChecked,
cssOverrides,
size = 'medium',
onClick = () => undefined,
...props
}: ToggleSwitchProps): EmotionJSX.Element => {
const isChecked = (): boolean => {
if (checked != undefined) {
Expand All @@ -74,19 +68,26 @@ export const ToggleSwitch = ({
return !!defaultChecked;
};

const background = decideBackground(theme);
const isSlim = size === 'slim';

return (
<div css={[toggleSwitchStyles(background), cssOverrides]}>
<div
css={[
toggleSwitchStyles,
isSlim ? slimStyles : mediumStyles,
cssOverrides,
]}
{...props}
>
<button
role="switch"
aria-checked={isChecked()}
aria-labelledby="notify"
onClick={onClick}
></button>
<span css={labelStyles} id="notify">
<label css={labelStyles} id="notify">
{label}
</span>
</label>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -1,84 +1,93 @@
import type { SerializedStyles } from '@emotion/react';
import { css } from '@emotion/react';
import type { ArticleTheme } from '@guardian/libs';
import { ArticlePillar, ArticleSpecial } from '@guardian/libs';
import {
culture,
lifestyle,
news,
opinion,
sport,
success,
} from '@guardian/src-foundations';
import { neutral, success } from '@guardian/src-foundations';
import { textSans } from '@guardian/src-foundations/typography';

export const defaultTheme = ArticlePillar.News;

export const decideBackground = (theme: ArticleTheme): SerializedStyles => {
switch (theme) {
case ArticlePillar.News:
case ArticleSpecial.Labs:
case ArticleSpecial.SpecialReport:
return css`
background: ${news[200]}40;
`;
case ArticlePillar.Culture:
return css`
background-color: ${culture[200]}40;
`;
case ArticlePillar.Lifestyle:
return css`
background-color: ${lifestyle[200]}40;
`;
case ArticlePillar.Sport:
return css`
background-color: ${sport[100]}40;
`;
case ArticlePillar.Opinion:
return css`
background-color: ${opinion[200]}40;
`;
}
};

export const toggleSwitchStyles = (
background: SerializedStyles,
): SerializedStyles => css`
export const toggleSwitchStyles = css`
button {
border: none;
margin: 0 8px 0 0;
margin: 8px;
padding: 0;
width: 3.75em;
height: 1.9rem;
display: inline-block;
vertical-align: middle;
text-align: center;
border-radius: 1.2rem;
position: relative;
transition: background 0.15s ease-in-out;
transition: background-color 150ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
}
button:after {
content: '';
position: absolute;
height: 1.6rem;
width: 1.6rem;
border-radius: 50%;
top: 0.15rem;
background: #fff;
transition: left 0.15s ease-in-out;
will-change: left;
transition: left 0.15s ease-in-out;
}
`;

export const mediumStyles = css`
button {
width: 3.188rem;
height: 1.938rem;
border-radius: 15.5px;
}
button:after {
height: 1.688rem;
width: 1.688rem;
margin: 2px;
top: 0;
left: 0;
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.306272);
}
button[aria-checked='false'] {
${background}
background-color: rgba(153, 153, 153, 0.5);
}
button[aria-checked='true'] {
background: ${success[500]};
}
button[aria-checked='true']:after {
left: 20px;
background: ${neutral[100]};
}
`;
export const slimStyles = css`
button {
width: 1.625rem;
height: 0.75rem;
border-radius: 6px;
}
button:after {
height: 1.125rem;
width: 1.125rem;
top: -3px;
box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.35);
}
button[aria-checked='false'] {
background: rgb(112, 112, 112, 0.5);
}
button[aria-checked='false']:after {
left: 0.1em;
left: -2px;
}
button[aria-checked='true'] {
background: ${success[500]};
background: rgba(88, 208, 139, 0.65);
}
button[aria-checked='true']:after {
left: 1.4rem;
left: 8px;
background: ${success[500]};
}
`;

export const labelStyles = css`
${textSans.small()};
display: inline-block;
transform: translateY(1px);
color: ${neutral[7]};
`;

0 comments on commit 4e65d21

Please sign in to comment.