2022-02-18 08:20:47 +00:00
|
|
|
import PropTypes from "prop-types";
|
2022-03-13 09:43:39 +00:00
|
|
|
import { useEffect, useMemo, useState } from "react";
|
|
|
|
import styled from "styled-components";
|
|
|
|
import { nanoid } from "nanoid";
|
|
|
|
|
|
|
|
const Container = styled.div.attrs({
|
|
|
|
className: "inline-flex items-center gap-1 cursor-pointer select-none",
|
|
|
|
})``;
|
|
|
|
|
|
|
|
const Checkbox = styled.input.attrs({
|
|
|
|
type: "checkbox",
|
|
|
|
className: `h-0 w-0 hidden`,
|
|
|
|
})``;
|
|
|
|
|
|
|
|
const Label = styled.label.attrs({
|
|
|
|
className: "cursor-pointer inline-flex items-center gap-2",
|
|
|
|
})`
|
|
|
|
&:active .toggle-pin {
|
|
|
|
width: 20px;
|
|
|
|
}
|
|
|
|
`;
|
|
|
|
|
|
|
|
const Toggle = styled.span.attrs({
|
2022-03-15 14:54:34 +00:00
|
|
|
className: `flex flex-row items-center justify-between shrink-0
|
2022-03-13 09:43:39 +00:00
|
|
|
w-[44px] h-[22px] bg-white rounded-full
|
|
|
|
border border-palette-200 relative cursor-pointer`,
|
|
|
|
})`
|
|
|
|
&:active .toggle-pin {
|
|
|
|
width: 20px;
|
|
|
|
}
|
|
|
|
`;
|
|
|
|
|
|
|
|
const TogglePin = styled.span.attrs(({ $checked }) => ({
|
|
|
|
className: `toggle-pin
|
|
|
|
absolute top-[2px] w-4 h-4 rounded-full
|
|
|
|
transition-[width_left] active:w-5
|
|
|
|
|
|
|
|
${$checked ? "checked bg-primary" : "bg-palette-200"}`,
|
|
|
|
}))`
|
|
|
|
left: 2px;
|
|
|
|
|
|
|
|
&.checked {
|
|
|
|
left: calc(100% - 2px);
|
|
|
|
transform: translateX(-100%);
|
|
|
|
}
|
|
|
|
`;
|
|
|
|
|
2022-03-15 14:54:34 +00:00
|
|
|
export const Switch = ({ children, defaultChecked, labelClassName, onChange, ...props }) => {
|
2022-03-13 09:43:39 +00:00
|
|
|
const id = useMemo(nanoid, [onChange]);
|
|
|
|
const [checked, setChecked] = useState(defaultChecked);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
onChange(checked);
|
|
|
|
}, [checked, onChange]);
|
2022-02-17 11:53:32 +00:00
|
|
|
|
|
|
|
return (
|
2022-03-13 09:43:39 +00:00
|
|
|
<Container {...props}>
|
|
|
|
<Checkbox checked={checked} onChange={(ev) => setChecked(ev.target.checked)} id={id} />
|
2022-03-15 14:54:34 +00:00
|
|
|
<Label htmlFor={id} className={labelClassName}>
|
2022-03-13 09:43:39 +00:00
|
|
|
<Toggle>
|
|
|
|
<TogglePin $checked={checked} />
|
|
|
|
</Toggle>
|
2022-03-16 13:07:56 +00:00
|
|
|
<div className="-mt-0.5">{children}</div>
|
2022-03-13 09:43:39 +00:00
|
|
|
</Label>
|
|
|
|
</Container>
|
2022-02-18 08:20:47 +00:00
|
|
|
);
|
|
|
|
};
|
2022-02-17 11:53:32 +00:00
|
|
|
|
|
|
|
Switch.propTypes = {
|
|
|
|
/**
|
2022-03-13 09:43:39 +00:00
|
|
|
* Should the checkbox be checked by default?
|
|
|
|
*/
|
|
|
|
defaultChecked: PropTypes.bool,
|
|
|
|
/**
|
|
|
|
* Element to be rendered as the switch label
|
2022-02-17 11:53:32 +00:00
|
|
|
*/
|
2022-03-16 08:04:45 +00:00
|
|
|
children: PropTypes.oneOfType([PropTypes.element, PropTypes.string]),
|
2022-03-15 14:54:34 +00:00
|
|
|
/**
|
|
|
|
* Pass additional CSS classes to the `label` element.
|
|
|
|
*/
|
|
|
|
labelClassName: PropTypes.string,
|
2022-02-17 11:53:32 +00:00
|
|
|
/**
|
|
|
|
* Function to execute on change
|
|
|
|
*/
|
2022-03-13 09:43:39 +00:00
|
|
|
onChange: PropTypes.func.isRequired,
|
2022-02-18 08:20:47 +00:00
|
|
|
};
|
2022-02-17 11:53:32 +00:00
|
|
|
|
|
|
|
Switch.defaultProps = {
|
2022-03-13 09:43:39 +00:00
|
|
|
defaultChecked: false,
|
2022-03-15 14:54:34 +00:00
|
|
|
labelClassName: "",
|
2022-02-18 08:20:47 +00:00
|
|
|
};
|