UNPKG

@lobehub/ui

Version:

Lobe UI is an open-source UI component library for building AIGC web apps

184 lines (183 loc) 5.62 kB
"use client"; import { rootVariants, styles, thumbVariants } from "./style.mjs"; import { createContext, use, useEffect, useMemo, useRef, useState } from "react"; import { jsx } from "react/jsx-runtime"; import { cx } from "antd-style"; import useMergeState from "use-merge-value"; import { Switch } from "@base-ui/react/switch"; import { animate, motionValue } from "motion"; //#region src/base-ui/Switch/atoms.tsx const THUMB_METRICS = { default: { checkedX: 14, pressedCheckedX: 10, pressedWidth: 22, width: 18 }, small: { checkedX: 12, pressedCheckedX: 8, pressedWidth: 16, width: 12 } }; const THUMB_SPRING = { damping: 24, stiffness: 360, type: "spring" }; const SwitchContext = createContext(null); const useSwitchContext = () => { const context = use(SwitchContext); if (!context) throw new Error("useSwitchContext must be used within a SwitchRoot"); return context; }; const SwitchRoot = ({ checked, className, defaultChecked, onCheckedChange, onClick, onKeyDown, onKeyUp, onPointerCancel, onPointerDown, onPointerLeave, onPointerUp, size = "default", children, disabled, readOnly, required, inputRef, id, name, ...rest }) => { const [isPressed, setIsPressed] = useState(false); const lastEventRef = useRef(null); const [isChecked, setIsChecked] = useMergeState(defaultChecked ?? false, { defaultValue: defaultChecked, onChange: (value) => { if (lastEventRef.current) onCheckedChange?.(value, lastEventRef.current); }, value: checked }); const baseClassName = rootVariants({ size }); const contextValue = useMemo(() => ({ isChecked: Boolean(isChecked), isPressed, setIsChecked: (value) => setIsChecked(value), setIsPressed }), [ isChecked, isPressed, setIsChecked ]); const handleClick = (event) => { lastEventRef.current = event; onClick?.(!isChecked, event); }; const isInteractive = !disabled && !readOnly; const handleKeyDown = (event) => { if (event.key === "Enter" || event.key === " ") lastEventRef.current = event; if (event.key === " " && isInteractive) setIsPressed(true); onKeyDown?.(event); }; const handleKeyUp = (event) => { if (event.key === " ") setIsPressed(false); onKeyUp?.(event); }; const handlePointerDown = (event) => { if (isInteractive) setIsPressed(true); onPointerDown?.(event); }; const handlePointerUp = (event) => { setIsPressed(false); onPointerUp?.(event); }; const handlePointerCancel = (event) => { setIsPressed(false); onPointerCancel?.(event); }; const handlePointerLeave = (event) => { setIsPressed(false); onPointerLeave?.(event); }; return /* @__PURE__ */ jsx(SwitchContext, { value: contextValue, children: /* @__PURE__ */ jsx(Switch.Root, { nativeButton: true, checked: isChecked, defaultChecked, disabled, id, inputRef, name, readOnly, required, render: /* @__PURE__ */ jsx("button", { ...rest, className: cx(baseClassName, className), onClick: handleClick, onKeyDown: handleKeyDown, onKeyUp: handleKeyUp, onPointerCancel: handlePointerCancel, onPointerDown: handlePointerDown, onPointerLeave: handlePointerLeave, onPointerUp: handlePointerUp }), onCheckedChange: setIsChecked, children }) }); }; SwitchRoot.displayName = "SwitchRoot"; const SwitchThumb = ({ className, size = "default", style, children, ...rest }) => { const { isChecked, isPressed } = useSwitchContext(); const ref = useRef(null); const baseClassName = thumbVariants({ size }); const metrics = THUMB_METRICS[size]; const targetX = isChecked ? isPressed ? metrics.pressedCheckedX : metrics.checkedX : 0; const targetWidth = isPressed ? metrics.pressedWidth : metrics.width; const [values] = useState(() => ({ width: motionValue(targetWidth), x: motionValue(targetX) })); const [initialStyle] = useState(() => ({ "--switch-x": `${targetX}px`, "width": targetWidth })); useEffect(() => { const el = ref.current; if (!el) return; const unsubscribeX = values.x.on("change", (x) => { el.style.setProperty("--switch-x", `${x}px`); }); const unsubscribeWidth = values.width.on("change", (width) => { el.style.setProperty("width", `${width}px`); }); return () => { unsubscribeX(); unsubscribeWidth(); }; }, [values]); useEffect(() => { const transition = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches ?? false ? { duration: 0 } : THUMB_SPRING; const animations = [animate(values.x, targetX, transition), animate(values.width, targetWidth, transition)]; return () => { for (const animation of animations) animation.stop(); }; }, [ values, targetX, targetWidth ]); return /* @__PURE__ */ jsx(Switch.Thumb, { render: /* @__PURE__ */ jsx("span", { ...rest, className: cx(baseClassName, className), ref, style: { ...initialStyle, ...style }, children }) }); }; SwitchThumb.displayName = "SwitchThumb"; const getIconPositionClass = (position, size) => { if (position === "thumb") return styles.iconThumb; if (position === "left") return size === "small" ? styles.iconLeftSmall : styles.iconLeft; return size === "small" ? styles.iconRightSmall : styles.iconRight; }; const SwitchIcon = ({ children, className, position, size = "default", ...rest }) => { const positionClass = getIconPositionClass(position, size); return /* @__PURE__ */ jsx("span", { className: cx(styles.icon, positionClass, className), ...rest, children }); }; SwitchIcon.displayName = "SwitchIcon"; //#endregion export { SwitchIcon, SwitchRoot, SwitchThumb, styles as switchStyles, useSwitchContext }; //# sourceMappingURL=atoms.mjs.map