aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
197 lines (194 loc) • 7.65 kB
JavaScript
'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