UNPKG

aura-glass

Version:

A comprehensive glassmorphism design system for React applications with 142+ production-ready components

172 lines (169 loc) 6.27 kB
'use client'; import { jsx, jsxs } from 'react/jsx-runtime'; import { cn } from '../../lib/utilsComprehensive.js'; import React, { forwardRef } from 'react'; import { useMotionPreferenceContext } from '../../contexts/MotionPreferenceContext.js'; import '../../primitives/GlassCore.js'; import '../../primitives/glass/GlassAdvanced.js'; import { OptimizedGlassCore } from '../../primitives/OptimizedGlassCore.js'; import '../../primitives/glass/OptimizedGlassAdvanced.js'; import '../../primitives/MotionNative.js'; import '../../primitives/motion/MotionFramer.js'; import { useA11yId, announceToScreenReader, createFormFieldA11y } from '../../utils/a11y.js'; const GlassSwitch = /*#__PURE__*/forwardRef(({ checked, defaultChecked = false, onChange, onCheckedChange, size = "md", variant = "default", label, description, labelPosition = "right", disabled = false, loading = false, icons, focusRing = true, thumbContent, error, required = false, respectMotionPreference = true, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, className, id, ...props }, ref) => { const { prefersReducedMotion, isMotionSafe } = useMotionPreferenceContext(); // Generate unique IDs for accessibility const switchId = useA11yId("glass-switch"); const finalId = id || switchId; const labelId = label ? useA11yId("glass-switch-label") : undefined; const descriptionId = description ? useA11yId("glass-switch-description") : undefined; const errorId = error ? useA11yId("glass-switch-error") : undefined; const [internalChecked, setInternalChecked] = React.useState(defaultChecked); const isChecked = checked !== undefined ? checked : internalChecked; const isInvalid = !!error; // Create accessibility attributes const a11yProps = createFormFieldA11y({ id: finalId, label: !ariaLabelledBy && !labelId ? ariaLabel || label : undefined, description: description, error: error, required: required, invalid: isInvalid, disabled: disabled || loading, labelId: ariaLabelledBy || labelId, descriptionId: ariaDescribedBy || descriptionId, errorId: errorId }); // Announce error state changes React.useEffect(() => { if (error) { announceToScreenReader(`Switch error: ${error}`, "assertive"); } }, [error]); const handleToggle = event => { if (disabled || loading) return; const newChecked = !isChecked; if (checked === undefined) { setInternalChecked(newChecked); } onChange?.(newChecked); onCheckedChange?.(newChecked); props?.onClick?.(event); // Announce state change announceToScreenReader(`Switch ${newChecked ? "on" : "off"}`, "polite"); }; const sizeConfig = { sm: { track: "h-5 w-9", thumb: "h-4 w-4", translate: "translate-x-4", text: "glass-text-xs", gap: "glass-gap-2" }, md: { track: "h-6 w-11", thumb: "h-5 w-5", translate: "translate-x-5", text: "glass-text-sm", gap: "glass-gap-3" }, lg: { track: "h-7 w-14", thumb: "h-6 w-6", translate: "translate-x-7", text: "glass-text-base", gap: "glass-gap-4" } }; const config = sizeConfig?.[size]; const switchElement = jsx(OptimizedGlassCore, { as: "button", ref: ref, role: "switch", type: "button", elevation: isChecked ? "level2" : "level1", intensity: isChecked ? "medium" : "subtle", depth: 2, tint: isChecked ? "primary" : "neutral", border: "subtle", animation: isMotionSafe && respectMotionPreference ? "shimmer" : "none", performanceMode: "medium", liftOnHover: !disabled, press: true, className: cn("glass-switch relative inline-flex shrink-0 cursor-pointer glass-radius-full transition-all duration-200", config.track, disabled && "opacity-50 cursor-not-allowed", loading && "cursor-wait", focusRing && "glass-focus", error && "ring-2 ring-destructive/50", className), onClick: handleToggle, "aria-checked": !!isChecked, "aria-invalid": isInvalid || undefined, "aria-required": required || undefined, disabled: disabled || loading, id: finalId, ...a11yProps, ...props, children: jsx("div", { className: cn("pointer-events-none relative flex items-center justify-center glass-radius-full transition-all duration-200 shadow-lg", config.thumb, isChecked ? config.translate : "translate-x-0.5") }) }); const labelElement = label && jsx("label", { htmlFor: finalId, id: labelId, className: cn("glass-switch-label cursor-pointer font-medium text-foreground", config.text, disabled && "cursor-not-allowed opacity-70", required && 'after:content-["*"] after:glass-ml-1 after:text-destructive'), children: label }); const descriptionElement = description && jsx("p", { id: descriptionId, className: cn("glass-switch-description glass-text-secondary", size === "sm" ? "glass-text-xs" : "glass-text-sm", disabled && "opacity-70"), children: description }); const errorElement = error && jsx("p", { id: errorId, className: cn("glass-switch-error text-destructive glass-mt-1", size === "sm" ? "glass-text-xs" : "glass-text-sm"), role: "alert", "aria-live": "polite", children: error }); // Render based on label position if (!label && !description && !error) { return switchElement; } const containerClass = cn("glass-switch-container flex items-start", config.gap, labelPosition === "left" && "flex-row-reverse", labelPosition === "right" && "flex-row", labelPosition === "top" && "flex-col", labelPosition === "bottom" && "flex-col-reverse"); return jsxs("div", { className: containerClass, children: [(labelPosition === "left" || labelPosition === "right") && jsx("div", { className: "glass-flex glass-items-center", children: switchElement }), (labelPosition === "top" || labelPosition === "bottom") && switchElement, jsxs("div", { className: "glass-gap-1", children: [labelElement, descriptionElement, errorElement] })] }); }); GlassSwitch.displayName = "GlassSwitch"; export { GlassSwitch, GlassSwitch as default }; //# sourceMappingURL=GlassSwitch.js.map