"use client" import * as React from "react" import { Slot } from "@radix-ui/react-slot" import { cn } from "../../../lib/utils" interface DivProps extends React.ComponentProps<"div"> { asChild?: boolean } function getDataState(swapped: boolean) { return swapped ? "on" : "off" } interface StoreState { swapped: boolean } interface Store { subscribe: (callback: () => void) => () => void getState: () => StoreState setState: (key: K, value: StoreState[K]) => void notify: () => void } const StoreContext = React.createContext(null) function useStore( selector: (state: StoreState) => T, ogStore?: Store | null, ): T { const contextStore = React.useContext(StoreContext) const store = ogStore ?? contextStore if (!store) { throw new Error(`\`useStore\` must be used within \`Swap\``) } const getSnapshot = React.useCallback( () => selector(store.getState()), [store, selector], ) return React.useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot) } interface SwapProps extends DivProps { swapped?: boolean defaultSwapped?: boolean onSwappedChange?: (swapped: boolean) => void activationMode?: "click" | "hover" animation?: "fade" | "rotate" | "flip" | "scale" disabled?: boolean } function Swap(props: SwapProps) { const { swapped: swappedProp, defaultSwapped, onSwappedChange, activationMode = "click", animation = "fade", disabled, asChild, className, onClick: onClickProp, onMouseEnter: onMouseEnterProp, onMouseLeave: onMouseLeaveProp, onKeyDown: onKeyDownProp, ...rootProps } = props const listenersRef = React.useRef void>>(new Set()) const stateRef = React.useRef({ swapped: swappedProp ?? defaultSwapped ?? false, }) const propsRef = React.useRef({ activationMode, animation, disabled, onSwappedChange, onClick: onClickProp, onMouseEnter: onMouseEnterProp, onMouseLeave: onMouseLeaveProp, onKeyDown: onKeyDownProp, }) React.useEffect(() => { propsRef.current = { activationMode, animation, disabled, onSwappedChange, onClick: onClickProp, onMouseEnter: onMouseEnterProp, onMouseLeave: onMouseLeaveProp, onKeyDown: onKeyDownProp, } }, [ activationMode, animation, disabled, onSwappedChange, onClickProp, onMouseEnterProp, onMouseLeaveProp, onKeyDownProp, ]) const isClickMode = activationMode === "click" const store = React.useMemo(() => { return { subscribe: (cb) => { listenersRef.current.add(cb) return () => listenersRef.current.delete(cb) }, getState: () => stateRef.current, setState: (key, value) => { if (Object.is(stateRef.current[key], value)) return if (key === "swapped" && typeof value === "boolean") { stateRef.current.swapped = value propsRef.current.onSwappedChange?.(value) } else { stateRef.current[key] = value } store.notify() }, notify: () => { for (const cb of listenersRef.current) { cb() } }, } }, []) const swapped = useStore((state) => state.swapped, store) React.useEffect(() => { if (swappedProp !== undefined) { store.setState("swapped", swappedProp) } }, [swappedProp, store]) const onToggle = React.useCallback(() => { if (propsRef.current.disabled) return store.setState("swapped", !store.getState().swapped) }, [store]) const onClick = React.useCallback( (event: React.MouseEvent) => { propsRef.current.onClick?.(event) if (event.defaultPrevented || propsRef.current.activationMode !== "click") return onToggle() }, [onToggle], ) const onMouseEnter = React.useCallback( (event: React.MouseEvent) => { propsRef.current.onMouseEnter?.(event) if ( event.defaultPrevented || propsRef.current.activationMode !== "hover" || propsRef.current.disabled ) return store.setState("swapped", true) }, [store], ) const onMouseLeave = React.useCallback( (event: React.MouseEvent) => { propsRef.current.onMouseLeave?.(event) if ( event.defaultPrevented || propsRef.current.activationMode !== "hover" || propsRef.current.disabled ) return store.setState("swapped", false) }, [store], ) const onKeyDown = React.useCallback( (event: React.KeyboardEvent) => { propsRef.current.onKeyDown?.(event) if ( event.defaultPrevented || propsRef.current.activationMode !== "click" || propsRef.current.disabled ) return if (event.key === "Enter" || event.key === " ") { event.preventDefault() onToggle() } }, [onToggle], ) const RootPrimitive = asChild ? Slot : "div" return ( ) } function SwapOn(props: DivProps) { const { asChild, className, ...onProps } = props const swapped = useStore((state) => state.swapped) const OnPrimitive = asChild ? Slot : "div" return ( ) } function SwapOff(props: DivProps) { const { asChild, className, ...offProps } = props const swapped = useStore((state) => state.swapped) const OffPrimitive = asChild ? Slot : "div" return ( ) } export { Swap, SwapOff, SwapOn, useStore as useSwap } export type { SwapProps }