diff --git a/packages/react/components/Switch.tsx b/packages/react/components/Switch.tsx new file mode 100644 index 0000000..a95a522 --- /dev/null +++ b/packages/react/components/Switch.tsx @@ -0,0 +1,84 @@ +import React from "react"; +import { VariantProps, vcn } from "../shared"; + +const switchColors = { + background: { + default: "bg-neutral-200 dark:bg-neutral-700", + hover: "hover:bg-neutral-300 dark:hover:bg-neutral-600", + checked: + "has-[input[type=checkbox]:checked]:bg-neutral-700 dark:has-[input[type=checkbox]:checked]:bg-neutral-300", + checkedHover: + "has-[input[type=checkbox]:checked]:hover:bg-black dark:has-[input[type=checkbox]:checked]:hover:bg-white", + disabled: + "has-[input[type=checkbox]:disabled]:bg-neutral-100 dark:has-[input[type=checkbox]:disabled]:bg-neutral-800", + disabledHover: + "has-[input[type=checkbox]:disabled]:hover:bg-neutral-100 dark:has-[input[type=checkbox]:disabled]:hover:bg-neutral-800", + disabledChecked: + "has-[input[type=checkbox]:disabled:checked]:bg-neutral-300 dark:has-[input[type=checkbox]:disabled:checked]:bg-neutral-700", + disabledCheckedHover: + "has-[input[type=checkbox]:disabled:checked]:hover:bg-neutral-300 dark:has-[input[type=checkbox]:disabled:checked]:hover:bg-neutral-700", + }, + button: { + default: "bg-white dark:bg-black", + }, +}; + +const [switchVariant, resolveSwitchVariantProps] = vcn({ + base: `relative inline-block group/switch rounded-full p-1 has-[input[type=checkbox]:not(:checked)]:pr-5 has-[input[type=checkbox]:checked]:pl-5 ${switchColors.background.default} ${switchColors.background.hover} ${switchColors.background.checked} ${switchColors.background.checkedHover} ${switchColors.background.disabled} ${switchColors.background.disabledHover} ${switchColors.background.disabledChecked} ${switchColors.background.disabledCheckedHover} has-[input[type=checkbox]:disabled]:cursor-not-allowed transition-all duration-200 ease-in-out`, + variants: {}, + defaults: {}, +}); + +interface SwitchProps + extends VariantProps, + Omit< + React.ComponentPropsWithoutRef<"input">, + "type" | "className" | "size" + > {} + +const Switch = React.forwardRef((props, ref) => { + const [variantProps, otherPropsCompressed] = resolveSwitchVariantProps(props); + const { + defaultChecked, + checked: propChecked, + onChange, + ...otherPropsExtracted + } = otherPropsCompressed; + + // internally handles checked, so we can use checked value without prop + const [checked, setChecked] = React.useState(defaultChecked ?? false); + React.useEffect(() => { + if (typeof propChecked === "boolean") { + setChecked(propChecked); + } + }, [propChecked]); + + const internalRef = React.useRef(null); + + return ( +