183 lines
5.8 KiB
TypeScript
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>
|
|
);
|
|
}
|
|
}
|