Skip to content
Draft
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
119 changes: 61 additions & 58 deletions client/components/checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,86 +8,89 @@ export interface CheckboxProps {
label?: string;
checkboxFill?: string;
maxWidth?: string;
disabled?: boolean;
}

export const Checkbox = (props: CheckboxProps) => (
<label
css={{
display: 'flex',
maxWidth: props.maxWidth,
cursor: 'pointer',
cursor: props.disabled ? 'default' : 'pointer',
userSelect: 'none',
':hover .checkbox': {
boxShadow: `0 0 0 3px ${palette.neutral[93]}`,
},
}}
>
<div
className="checkbox"
css={{
transition: 'all .2s ease-in-out',
border: props.checked
? undefined
: `1px solid ${palette.neutral[60]}`,
minWidth: '18px',
height: '18px',
display: 'inline-block',
margin: '3px',
marginRight: '10px',
background: props.checked
? props.checkboxFill || palette.success[400]
: palette.neutral[100],
position: 'relative',
outline: 0,
':focus': {
boxShadow: `0 0 0 3px ${palette.brandAlt[400]}`,
},
}}
// accessibility props below
role="checkbox"
aria-checked={props.checked}
tabIndex={0}
onKeyPress={() => props.onChange(!props.checked)}
>
<input
type="checkbox"
css={{
position: 'absolute',
zIndex: -999999,
overflow: 'hidden',
opacity: 0,
}}
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
props.onChange(event.target.checked)
}
checked={props.checked}
required={props.required}
tabIndex={-1}
/>
{!props.disabled && (
<div
className="checkbox"
css={{
position: 'absolute',
left: '3px',
top: '5px',
width: '12px',
height: '6px',
transform: 'rotate(-45deg)',
textAlign: 'right',
transition: 'all .2s ease-in-out',
border: props.checked
? undefined
: `1px solid ${palette.neutral[60]}`,
minWidth: '18px',
height: '18px',
display: 'inline-block',
margin: '3px',
marginRight: '10px',
background: props.checked
? props.checkboxFill || palette.success[400]
: palette.neutral[100],
position: 'relative',
outline: 0,
':focus': {
boxShadow: `0 0 0 3px ${palette.brandAlt[400]}`,
},
}}
// accessibility props below
role="checkbox"
aria-checked={props.checked}
tabIndex={0}
onKeyPress={() => props.onChange(!props.checked)}
>
<input
type="checkbox"
css={{
position: 'absolute',
zIndex: -999999,
overflow: 'hidden',
opacity: 0,
}}
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
props.onChange(event.target.checked)
}
checked={props.checked}
required={props.required}
tabIndex={-1}
/>
<div
css={{
width: props.checked ? '12px' : '2px',
position: 'absolute',
left: '3px',
top: '5px',
width: '12px',
height: '6px',
borderColor: palette.neutral[100],
borderWidth: '0 0 2px 2px',
borderStyle: 'solid',
transition: 'all .2s ease-in-out',
transitionDelay: '.1s',
transform: 'rotate(-45deg)',
textAlign: 'right',
}}
/>
>
<div
css={{
width: props.checked ? '12px' : '2px',
height: '6px',
borderColor: palette.neutral[100],
borderWidth: '0 0 2px 2px',
borderStyle: 'solid',
transition: 'all .2s ease-in-out',
transitionDelay: '.1s',
}}
/>
</div>
</div>
</div>
)}
<span>{props.label}</span>
</label>
);
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const consentPreference = (
title: name,
description,
selected: subscribed,
canOnlyUnsubscribe: id === 'holidays',
};
switch (uxType) {
case 'checkbox': {
Expand Down
34 changes: 29 additions & 5 deletions client/components/identity/MarketingCheckbox.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FC } from 'react';
import { FC, useState } from 'react';
import { Checkbox } from '../checkbox';
import { standardSansText } from './sharedStyles';

Expand All @@ -8,14 +8,18 @@ interface MarketingCheckboxProps {
title?: string;
selected?: boolean;
onClick: (id: string) => {};
canOnlyUnsubscribe?: boolean;
}

const getTitle = (title: MarketingCheckboxProps['title']) => (
const getTitle = (
title: MarketingCheckboxProps['title'],
disabledInput: boolean,
) => (
<p
css={[
standardSansText,
{
cursor: 'pointer',
cursor: disabledInput ? 'default' : 'pointer',
lineHeight: '22px',
fontWeight: 'bold',
margin: '0',
Expand All @@ -37,7 +41,21 @@ const getDescription = (description: MarketingCheckboxProps['description']) => (
);

export const MarketingCheckbox: FC<MarketingCheckboxProps> = (props) => {
const { id, description, selected, title, onClick } = props;
const {
id,
description,
selected,
title,
onClick,
canOnlyUnsubscribe = false,
} = props;

const [wasInitiallySelected] = useState(selected);

if (canOnlyUnsubscribe && !wasInitiallySelected) {
return null;
}

return (
<div
onClick={(e) => {
Expand All @@ -49,6 +67,11 @@ export const MarketingCheckbox: FC<MarketingCheckboxProps> = (props) => {
) {
return;
}

if (canOnlyUnsubscribe && !selected) {
return;
}

onClick(id);
}}
css={[
Expand All @@ -63,12 +86,13 @@ export const MarketingCheckbox: FC<MarketingCheckboxProps> = (props) => {
<div css={{ position: 'absolute', left: 0 }}>
<Checkbox
checked={!!selected}
disabled={canOnlyUnsubscribe && !selected}
onChange={(_) => {
return;
}}
/>
</div>
{title && getTitle(title)}
{title && getTitle(title, canOnlyUnsubscribe && !selected)}
{description && getDescription(description)}
</div>
);
Expand Down
22 changes: 20 additions & 2 deletions client/components/identity/MarketingToggle.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FC } from 'react';
import { FC, useState } from 'react';
import { css } from '@emotion/react';
import { ToggleSwitch } from './ToggleSwitch';
import { standardSansText, toggleDescriptionPadding } from './sharedStyles';
Expand All @@ -9,14 +9,28 @@ interface MarketingToggleProps {
title?: string;
selected?: boolean;
onClick: (id: string) => {};
canOnlyUnsubscribe?: boolean;
}

const getDescription = (description: MarketingToggleProps['description']) => (
<p css={[standardSansText, toggleDescriptionPadding]}>{description}</p>
);

export const MarketingToggle: FC<MarketingToggleProps> = (props) => {
const { id, description, selected, title, onClick } = props;
const {
id,
description,
selected,
title,
onClick,
canOnlyUnsubscribe = false,
} = props;

const [wasInitiallySelected] = useState(selected);
const disabled = canOnlyUnsubscribe && !selected;
if (canOnlyUnsubscribe && !wasInitiallySelected) {
return null;
}
return (
<div
css={[
Expand Down Expand Up @@ -44,8 +58,12 @@ export const MarketingToggle: FC<MarketingToggleProps> = (props) => {
id={id}
checked={!!selected}
onClick={() => {
if (canOnlyUnsubscribe && !selected) {
return;
}
onClick(id);
}}
disabled={disabled}
/>
</div>
{description && getDescription(description)}
Expand Down
26 changes: 18 additions & 8 deletions client/components/identity/ToggleSwitch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ interface ToggleSwitchProps extends Props {
* Receives the click event as an argument.
*/
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;

/**
* Whether interaction with the toggle should be disabled.
*/
disabled?: boolean;
}

/**
Expand Down Expand Up @@ -112,14 +117,19 @@ export const ToggleSwitch = ({
return (
<label id={labelId} css={[labelStyles, cssOverrides]} {...props}>
{labelPosition === 'left' && label}
<button
id={id}
css={[buttonStyles(labelPosition), getPlatformStyles(platform)]}
role="switch"
aria-checked={isChecked()}
aria-labelledby={labelId}
onClick={onClick}
></button>
{!props.disabled && (
<button
id={id}
css={[
buttonStyles(labelPosition),
getPlatformStyles(platform),
]}
role="switch"
aria-checked={isChecked()}
aria-labelledby={labelId}
onClick={onClick}
></button>
)}
{labelPosition === 'right' && label}
</label>
);
Expand Down