UNPKG

@lobehub/ui

Version:

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

1 lines 10.9 kB
{"version":3,"file":"atoms.mjs","names":["useControlledState"],"sources":["../../../src/base-ui/Switch/atoms.tsx"],"sourcesContent":["'use client';\n\nimport { Switch } from '@base-ui/react/switch';\nimport { cx } from 'antd-style';\nimport { animate, motionValue } from 'motion';\nimport type { CSSProperties, KeyboardEvent, MouseEvent, PointerEvent } from 'react';\nimport { createContext, use, useEffect, useMemo, useRef, useState } from 'react';\nimport useControlledState from 'use-merge-value';\n\nimport { rootVariants, styles, thumbVariants } from './style';\nimport type {\n SwitchChangeEventHandler,\n SwitchContextType,\n SwitchIconPosition,\n SwitchIconProps,\n SwitchRootProps,\n SwitchSize,\n SwitchThumbProps,\n} from './type';\n\nconst THUMB_METRICS: Record<\n SwitchSize,\n { checkedX: number; pressedCheckedX: number; pressedWidth: number; width: number }\n> = {\n default: { checkedX: 14, pressedCheckedX: 10, pressedWidth: 22, width: 18 },\n small: { checkedX: 12, pressedCheckedX: 8, pressedWidth: 16, width: 12 },\n};\n\nconst THUMB_SPRING = { damping: 24, stiffness: 360, type: 'spring' as const };\n\nconst SwitchContext = createContext<SwitchContextType | null>(null);\n\nexport const useSwitchContext = () => {\n const context = use(SwitchContext);\n if (!context) {\n throw new Error('useSwitchContext must be used within a SwitchRoot');\n }\n return context;\n};\n\ntype SwitchRootInternalProps = Omit<SwitchRootProps, 'onCheckedChange' | 'onClick'> & {\n onCheckedChange?: SwitchChangeEventHandler;\n onClick?: SwitchChangeEventHandler;\n};\n\nexport const SwitchRoot = ({\n checked,\n className,\n defaultChecked,\n onCheckedChange,\n onClick,\n onKeyDown,\n onKeyUp,\n onPointerCancel,\n onPointerDown,\n onPointerLeave,\n onPointerUp,\n size = 'default',\n children,\n disabled,\n readOnly,\n required,\n inputRef,\n id,\n name,\n ...rest\n}: SwitchRootInternalProps) => {\n const [isPressed, setIsPressed] = useState(false);\n const lastEventRef = useRef<MouseEvent<HTMLButtonElement> | KeyboardEvent<HTMLButtonElement>>(\n null,\n );\n\n const [isChecked, setIsChecked] = useControlledState(defaultChecked ?? false, {\n defaultValue: defaultChecked,\n onChange: (value: boolean) => {\n if (lastEventRef.current) {\n onCheckedChange?.(value, lastEventRef.current);\n }\n },\n value: checked,\n });\n\n const baseClassName = rootVariants({ size });\n\n const contextValue = useMemo(\n () => ({\n isChecked: Boolean(isChecked),\n isPressed,\n setIsChecked: (value: boolean) => setIsChecked(value),\n setIsPressed,\n }),\n [isChecked, isPressed, setIsChecked],\n );\n\n const handleClick = (event: MouseEvent<HTMLButtonElement>) => {\n lastEventRef.current = event;\n onClick?.(!isChecked, event);\n };\n\n const isInteractive = !disabled && !readOnly;\n\n const handleKeyDown = (event: KeyboardEvent<HTMLButtonElement>) => {\n if (event.key === 'Enter' || event.key === ' ') {\n lastEventRef.current = event;\n }\n if (event.key === ' ' && isInteractive) {\n setIsPressed(true);\n }\n onKeyDown?.(event);\n };\n\n const handleKeyUp = (event: KeyboardEvent<HTMLButtonElement>) => {\n if (event.key === ' ') {\n setIsPressed(false);\n }\n onKeyUp?.(event);\n };\n\n const handlePointerDown = (event: PointerEvent<HTMLButtonElement>) => {\n if (isInteractive) {\n setIsPressed(true);\n }\n onPointerDown?.(event);\n };\n\n const handlePointerUp = (event: PointerEvent<HTMLButtonElement>) => {\n setIsPressed(false);\n onPointerUp?.(event);\n };\n\n const handlePointerCancel = (event: PointerEvent<HTMLButtonElement>) => {\n setIsPressed(false);\n onPointerCancel?.(event);\n };\n\n const handlePointerLeave = (event: PointerEvent<HTMLButtonElement>) => {\n setIsPressed(false);\n onPointerLeave?.(event);\n };\n\n return (\n <SwitchContext value={contextValue}>\n <Switch.Root\n nativeButton\n checked={isChecked}\n defaultChecked={defaultChecked}\n disabled={disabled}\n id={id}\n inputRef={inputRef}\n name={name}\n readOnly={readOnly}\n required={required}\n render={\n <button\n {...rest}\n className={cx(baseClassName, className)}\n onClick={handleClick}\n onKeyDown={handleKeyDown}\n onKeyUp={handleKeyUp}\n onPointerCancel={handlePointerCancel}\n onPointerDown={handlePointerDown}\n onPointerLeave={handlePointerLeave}\n onPointerUp={handlePointerUp}\n />\n }\n onCheckedChange={setIsChecked}\n >\n {children}\n </Switch.Root>\n </SwitchContext>\n );\n};\n\nSwitchRoot.displayName = 'SwitchRoot';\n\nexport const SwitchThumb = ({\n className,\n size = 'default',\n style,\n children,\n ...rest\n}: SwitchThumbProps) => {\n const { isChecked, isPressed } = useSwitchContext();\n const ref = useRef<HTMLSpanElement>(null);\n const baseClassName = thumbVariants({ size });\n\n const metrics = THUMB_METRICS[size];\n const targetX = isChecked ? (isPressed ? metrics.pressedCheckedX : metrics.checkedX) : 0;\n const targetWidth = isPressed ? metrics.pressedWidth : metrics.width;\n\n const [values] = useState(() => ({\n width: motionValue(targetWidth),\n x: motionValue(targetX),\n }));\n\n // animated keys stay referentially stable so React never rewrites mid-flight inline values\n const [initialStyle] = useState<CSSProperties>(\n () => ({ '--switch-x': `${targetX}px`, 'width': targetWidth }) as CSSProperties,\n );\n\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n const unsubscribeX = values.x.on('change', (x) => {\n el.style.setProperty('--switch-x', `${x}px`);\n });\n const unsubscribeWidth = values.width.on('change', (width) => {\n el.style.setProperty('width', `${width}px`);\n });\n return () => {\n unsubscribeX();\n unsubscribeWidth();\n };\n }, [values]);\n\n useEffect(() => {\n const reduceMotion = window.matchMedia?.('(prefers-reduced-motion: reduce)').matches ?? false;\n const transition = reduceMotion ? { duration: 0 } : THUMB_SPRING;\n const animations = [\n animate(values.x, targetX, transition),\n animate(values.width, targetWidth, transition),\n ];\n return () => {\n for (const animation of animations) animation.stop();\n };\n }, [values, targetX, targetWidth]);\n\n return (\n <Switch.Thumb\n render={\n <span\n {...rest}\n className={cx(baseClassName, className)}\n ref={ref}\n style={{ ...initialStyle, ...style }}\n >\n {children}\n </span>\n }\n />\n );\n};\n\nSwitchThumb.displayName = 'SwitchThumb';\n\nconst getIconPositionClass = (position: SwitchIconPosition, size: 'default' | 'small') => {\n if (position === 'thumb') return styles.iconThumb;\n if (position === 'left') return size === 'small' ? styles.iconLeftSmall : styles.iconLeft;\n return size === 'small' ? styles.iconRightSmall : styles.iconRight;\n};\n\nexport const SwitchIcon = ({\n children,\n className,\n position,\n size = 'default',\n ...rest\n}: SwitchIconProps) => {\n const positionClass = getIconPositionClass(position, size);\n\n return (\n <span className={cx(styles.icon, positionClass, className)} {...rest}>\n {children}\n </span>\n );\n};\n\nSwitchIcon.displayName = 'SwitchIcon';\n\nexport { styles as switchStyles } from './style';\n"],"mappings":"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"}