Files
protocol/packages/website/ts/components/ui/switch.tsx
2019-03-26 16:20:02 -04:00

183 lines
5.8 KiB
TypeScript

import * as React from 'react';
import styled from 'styled-components';
import { Heading } from 'ts/components/text';
import { colors } from 'ts/style/colors';
interface ToggleProps {
className?: string;
name?: string;
value?: string;
isChecked?: boolean;
isDisabled?: boolean;
onToggle?: (e: React.ChangeEvent) => void;
width?: string;
height?: string;
borderWidth?: string;
borderColor?: string;
backgroundColor?: string;
backgroundColorDisabled?: string;
radius?: string;
radiusBackground?: string;
knobRadius?: string;
knobWidth?: string;
knobHeight?: string;
knobGap?: string;
knobColor?: string;
}
const ToggleContainer = styled.label``;
const DEFAULT_TOGGLE_STYLES = {
WIDTH: '48px',
HEIGHT: '24px',
KNOB_WIDTH: '12px',
KNOB_HEIGHT: '12px',
};
export const ToggleBase = styled.span<ToggleProps>`
position: relative;
box-sizing: border-box;
display: inline-grid;
align-items: center;
width: ${p => p.width || DEFAULT_TOGGLE_STYLES.WIDTH};
height: ${p => p.height || DEFAULT_TOGGLE_STYLES.HEIGHT};
vertical-align: text-top;
margin: 0 4px;
input[type='checkbox'] {
position: absolute;
margin-left: -9999px;
visibility: hidden;
// off state
& + label {
display: inline-grid;
box-sizing: border-box;
align-items: center;
outline: none;
user-select: none;
width: ${p => p.width || DEFAULT_TOGGLE_STYLES.WIDTH};
height: ${p => p.height || DEFAULT_TOGGLE_STYLES.HEIGHT};
background-color: ${p => p.knobColor || '#EAEAEA'};
border-radius: ${p => p.radius || '256px'};
cursor: pointer;
transition: background ease-out 0.3s;
&:before {
content: '';
display: block;
position: absolute;
border-radius: ${p => p.radiusBackground || '256px'};
width: calc(${p => p.width || DEFAULT_TOGGLE_STYLES.WIDTH} - 2 * ${p => p.borderWidth || '2px'});
height: calc(${p => p.height || DEFAULT_TOGGLE_STYLES.HEIGHT} - 2 * ${p => p.borderWidth || '2px'});
background-color: ${p => p.backgroundColor || colors.white};
left: ${p => p.borderWidth || '2px'};
}
&:after {
display: block;
position: absolute;
content: '';
width: ${p => p.knobWidth || DEFAULT_TOGGLE_STYLES.KNOB_WIDTH};
height: ${p => p.knobHeight || DEFAULT_TOGGLE_STYLES.KNOB_HEIGHT};
border-radius: ${p => p.knobRadius || '100%'};
background-color: ${p => p.knobColor || '#EAEAEA'};
transition: all ease-out 0.4s;
margin-left: ${p => p.knobGap || '4px'};
}
}
// on state
&:checked {
& + label {
background-color: ${p => p.borderColor || colors.brandLight};
&:before {
background-color: ${p => p.backgroundColor || colors.white};
}
&:after {
margin-left: calc(
100% - ${p => p.knobWidth || DEFAULT_TOGGLE_STYLES.KNOB_WIDTH} - ${p => p.knobGap || '4px'}
);
transition: all ease-out 0.2s;
background-color: ${p => p.knobColor || colors.brandLight};
}
}
&:disabled {
& + label {
background-color: ${p => p.backgroundColorDisabled || '#eee'};
&:after {
box-shadow: none;
}
}
}
}
// disabled
&:disabled {
& + label {
background-color: ${p => p.backgroundColorDisabled || '#eee'};
cursor: default;
&:after {
box-shadow: none;
background-color: ${p => p.backgroundColorDisabled || '#eee'};
}
}
}
}
`;
export const Toggle = React.forwardRef((props: ToggleProps, ref: React.Ref<HTMLInputElement>) => {
const { className, name, isChecked = false, isDisabled = false, value = '', onToggle = () => true } = props;
return (
<ToggleBase className={className} {...props}>
<input
ref={ref}
onChange={onToggle}
type={'checkbox'}
defaultChecked={isChecked}
id={name}
name={name}
value={value}
disabled={isDisabled}
/>
<ToggleContainer htmlFor={name} />
</ToggleBase>
);
});
const SwitchWrapper = styled.div`
display: flex;
justify-content: space-between;
padding: 0.75rem 0;
`;
export interface SwitchProps {
label: string;
isChecked?: boolean;
onToggle: (checked: boolean) => void;
}
export class Switch extends React.Component<SwitchProps> {
public switchRef: React.RefObject<HTMLInputElement> = React.createRef();
constructor(props: SwitchProps) {
super(props);
}
public render(): React.ReactNode {
const onToggle = () => {
this.props.onToggle(this.switchRef.current.checked);
};
return (
<SwitchWrapper>
<Heading isNoMargin={true} asElement="h3" size={'small'}>
{this.props.label}
</Heading>
<Toggle
ref={this.switchRef}
isChecked={this.props.isChecked}
onToggle={onToggle}
knobGap={'6px'}
name={this.props.label}
/>
</SwitchWrapper>
);
}
}