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