UNPKG

aura-glass

Version:

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

197 lines (194 loc) 7.65 kB
'use client'; import { jsxs, jsx } from 'react/jsx-runtime'; import { cn } from '../../lib/utilsComprehensive.js'; import { Minus, Check } from 'lucide-react'; import React, { forwardRef, useState } from 'react'; 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 { MotionFramer } from '../../primitives/motion/MotionFramer.js'; import { useA11yId, announceToScreenReader, createFormFieldA11y } from '../../utils/a11y.js'; import { useMotionPreferenceContext } from '../../contexts/MotionPreferenceContext.js'; const GlassCheckbox = /*#__PURE__*/forwardRef(({ checked, indeterminate = false, onCheckedChange, onChange, size = "md", variant = "default", label, description, labelPosition = "right", disabled = false, loading = false, focusRing = true, checkIcon, indeterminateIcon, error, respectMotionPreference = true, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, className, id, required, defaultChecked, ...props }, ref) => { const { prefersReducedMotion, isMotionSafe } = useMotionPreferenceContext(); const shouldAnimate = isMotionSafe && !prefersReducedMotion; // Generate unique IDs for accessibility const checkboxId = useA11yId("glass-checkbox"); const finalId = id || checkboxId; const labelId = label ? useA11yId("glass-checkbox-label") : undefined; const descriptionId = description ? useA11yId("glass-checkbox-description") : undefined; const errorId = error ? useA11yId("glass-checkbox-error") : undefined; const isInvalid = !!error; const isRequired = required || false; // Create accessibility attributes const a11yProps = createFormFieldA11y({ id: finalId, label: !ariaLabelledBy && !labelId ? ariaLabel || label : undefined, description: description, error: error, required: isRequired, invalid: isInvalid, disabled: disabled || loading, labelId: ariaLabelledBy || labelId, descriptionId: ariaDescribedBy || descriptionId, errorId: errorId }); // Announce error state changes React.useEffect(() => { if (error) { announceToScreenReader(`Checkbox error: ${error}`, "assertive"); } }, [error]); const handleChange = event => { if (disabled || loading) return; const newChecked = event.target.checked; onCheckedChange?.(newChecked); onChange?.(event); }; const sizeConfig = { sm: { box: "h-4 w-4", icon: "h-3 w-3", text: "glass-text-xs", gap: "glass-gap-2" }, md: { box: "h-5 w-5", icon: "h-4 w-4", text: "glass-text-sm", gap: "glass-gap-3" }, lg: { box: "h-6 w-6", icon: "h-5 w-5", text: "glass-text-base", gap: "glass-gap-4" } }; const config = sizeConfig[size]; const isCheckedOrIndeterminate = checked || indeterminate; const isControlled = typeof checked === "boolean"; const [internalChecked, setInternalChecked] = useState(defaultChecked ?? false); const currentChecked = isControlled ? checked : internalChecked; React.useEffect(() => { if (!isControlled && defaultChecked !== undefined) { setInternalChecked(defaultChecked); } }, [defaultChecked, isControlled]); const checkboxElement = jsxs("div", { className: 'relative glass-inline-flex glass-items-center', children: [jsx("input", { ref: ref, type: "checkbox", ...a11yProps, checked: !!currentChecked, onChange: handleChange, disabled: disabled || loading, className: 'sr-only glass-touch-target glass-contrast-guard', ...props }), jsx(OptimizedGlassCore, { elevation: isCheckedOrIndeterminate ? "level2" : "level1", intensity: "medium", depth: 2, tint: isCheckedOrIndeterminate ? "primary" : "neutral", border: "subtle", animation: shouldAnimate && respectMotionPreference ? "float" : "none", performanceMode: "medium", liftOnHover: true, press: true, className: cn("glass-checkbox-box relative inline-flex items-center justify-center", "cursor-pointer group glass-radius-lg transition-all duration-200", // Size config.box, // Disabled state disabled && "opacity-50 cursor-not-allowed", // Loading state loading && "cursor-wait", // Focus styles focusRing && ["focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2", "focus-within:ring-primary focus-within:ring-offset-background"], // Error state error && !isCheckedOrIndeterminate && "ring-2 ring-destructive/50"), role: "presentation", "aria-hidden": "true", "data-state": loading ? "loading" : indeterminate ? "indeterminate" : currentChecked ? "checked" : "unchecked", children: jsxs(MotionFramer, { preset: shouldAnimate && respectMotionPreference ? "scaleIn" : "none", className: cn("flex items-center justify-center", config.icon, isCheckedOrIndeterminate ? "opacity-100" : "opacity-0"), children: [loading && jsx("div", { className: cn(shouldAnimate && respectMotionPreference ? "animate-spin" : "", "glass-radius-full border-2 border-current border-t-transparent", size === "sm" ? "w-2.5 h-2.5" : size === "md" ? "w-3 h-3" : "w-4 h-4") }), !loading && indeterminate && (indeterminateIcon || jsx(Minus, { className: config.icon, strokeWidth: 3 })), !loading && !indeterminate && checked && (checkIcon || jsx(Check, { className: config.icon, strokeWidth: 3 }))] }) })] }); const labelElement = label && jsx("label", { id: labelId, htmlFor: finalId, className: cn("glass-checkbox-label cursor-pointer font-medium text-foreground", config.text, disabled && "cursor-not-allowed opacity-70", isRequired && 'after:content-["*"] after:glass-ml-1 after:text-destructive'), children: label }); const descriptionElement = description && jsx("p", { id: descriptionId, className: cn("glass-checkbox-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-checkbox-error text-destructive", 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 checkboxElement; } const containerClass = cn("glass-checkbox-container flex", config.gap, labelPosition === "left" && "flex-row-reverse items-start", labelPosition === "right" && "flex-row items-start", labelPosition === "top" && "flex-col", labelPosition === "bottom" && "flex-col-reverse", className); return jsxs("div", { className: containerClass, children: [(labelPosition === "left" || labelPosition === "right") && jsx("div", { className: 'glass-flex glass-items-start pt-0.5', children: checkboxElement }), (labelPosition === "top" || labelPosition === "bottom") && checkboxElement, jsxs("div", { className: "glass-gap-1 glass-min-w-0 glass-flex-1", children: [labelElement, descriptionElement, errorElement] })] }); }); GlassCheckbox.displayName = "GlassCheckbox"; export { GlassCheckbox, GlassCheckbox as default }; //# sourceMappingURL=GlassCheckbox.js.map