UNPKG

aura-glass

Version:

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

323 lines (320 loc) 11.8 kB
'use client'; import { jsxs, jsx } from 'react/jsx-runtime'; import { cn } from '../../lib/utilsComprehensive.js'; import React, { forwardRef, useState, createContext } 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 { useMotionPreference } from '../../hooks/useMotionPreference.js'; // Context for radio group state const RadioGroupContext = /*#__PURE__*/createContext(null); /** * GlassRadioGroup component * A glassmorphism radio button group */ const GlassRadioGroup = /*#__PURE__*/forwardRef(({ options, value: controlledValue, defaultValue, onValueChange, name, disabled = false, orientation = "vertical", size = "md", variant = "default", className, renderOption, label, description, error, required = false, respectMotionPreference = true, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, "aria-label": ariaLabel }, ref) => { useMotionPreference(); // Generate unique IDs for accessibility const groupId = useA11yId("glass-radio-group"); const labelId = label ? useA11yId("glass-radio-group-label") : undefined; const descriptionId = description ? useA11yId("glass-radio-group-description") : undefined; const errorId = error ? useA11yId("glass-radio-group-error") : undefined; const [internalValue, setInternalValue] = useState(defaultValue || ""); const value = controlledValue !== undefined ? controlledValue : internalValue; const isInvalid = !!error; // Create accessibility attributes const a11yProps = { role: "radiogroup", "aria-labelledby": ariaLabelledBy || labelId || undefined, "aria-label": !ariaLabelledBy && !labelId ? ariaLabel || label : undefined, "aria-describedby": ariaDescribedBy || descriptionId || description && `${groupId}-description` || undefined, "aria-invalid": isInvalid || undefined, "aria-required": required || undefined, "aria-disabled": disabled || undefined }; // Announce error state changes React.useEffect(() => { if (error) { announceToScreenReader(`Radio group error: ${error}`, "assertive"); } }, [error]); const handleValueChange = newValue => { if (controlledValue === undefined) { setInternalValue(newValue); } onValueChange?.(newValue); // Announce selection change const selectedOption = options.find(opt => opt.value === newValue); if (selectedOption) { announceToScreenReader(`Selected ${selectedOption.label}`, "polite"); } }; const contextValue = { value, onValueChange: handleValueChange, name, disabled, size, variant, respectMotionPreference }; return jsxs("div", { "data-glass-component": true, className: cn("relative", className), children: [label && jsx("label", { id: labelId, className: cn("block glass-text-sm font-medium text-foreground mb-3", required && 'after:content-["*"] after:glass-ml-1 after:text-destructive'), children: label }), description && jsx("p", { id: descriptionId, className: 'glass-text-sm glass-text-secondary mb-3', children: description }), jsx(RadioGroupContext.Provider, { value: contextValue, children: jsx("div", { ref: ref, ...a11yProps, className: cn("flex glass-gap-3", { "flex-col": orientation === "vertical", "flex-row flex-wrap": orientation === "horizontal" }, error && "ring-2 ring-destructive/50 glass-radius-lg glass-p-2"), role: "radiogroup", "aria-invalid": isInvalid || undefined, "aria-required": required || undefined, children: options.map(option => jsx(GlassRadioGroupItem, { option: option, isSelected: value === option.value, isDisabled: disabled || option.disabled || false, onSelect: handleValueChange, size: size, variant: variant, name: name, renderOption: renderOption, respectMotionPreference: respectMotionPreference }, option.value)) }) }), error && jsx("p", { id: errorId, className: 'glass-mt-2 glass-text-sm text-destructive', role: "alert", "aria-live": "polite", children: error })] }); }); GlassRadioGroup.displayName = "GlassRadioGroup"; const GlassRadioGroupItem = /*#__PURE__*/forwardRef(({ option, isSelected, isDisabled, onSelect, size = "md", variant = "default", name, className, renderOption, respectMotionPreference = true }, ref) => { const { shouldAnimate } = useMotionPreference(); const itemId = useA11yId("glass-radio-item"); const sizeClasses = { sm: "w-4 h-4", md: "w-5 h-5", lg: "w-6 h-6" }; const textSizeClasses = { sm: "glass-text-sm", md: "glass-text-base", lg: "glass-text-lg" }; // Create accessibility attributes for the radio item const labelElementId = useA11yId("glass-radio-label"); const descriptionElementId = option.description ? useA11yId("glass-radio-description") : undefined; const usesCustomRenderer = typeof renderOption === "function"; const itemA11yProps = createFormFieldA11y({ id: itemId, label: usesCustomRenderer ? option.label : undefined, description: option.description, disabled: isDisabled, labelId: usesCustomRenderer ? undefined : labelElementId, descriptionId: descriptionElementId }); const handleClick = () => { if (!isDisabled) { onSelect(option.value); } }; const handleKeyDown = e => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); handleClick(); } }; if (renderOption) { return jsx("div", { ref: ref, onClick: handleClick, onKeyDown: handleKeyDown, tabIndex: isDisabled ? -1 : 0, role: "radio", "aria-checked": isSelected, "aria-disabled": isDisabled, ...itemA11yProps, children: renderOption(option, isSelected) }); } if (variant === "card") { return jsx(MotionFramer, { preset: shouldAnimate && respectMotionPreference ? isSelected ? "scaleIn" : "fadeIn" : "none", animateOnHover: !isDisabled && shouldAnimate && respectMotionPreference, duration: 200, children: jsxs(OptimizedGlassCore, { elevation: isSelected ? "level3" : "level1", intensity: "medium", depth: 2, tint: "neutral", border: "subtle", animation: "none", performanceMode: "medium", liftOnHover: true, press: true, className: cn("relative cursor-pointer transition-all duration-200 glass-p-4 glass-radius-xl", "focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-primary/40 focus-within:ring-offset-background", { "opacity-50 cursor-not-allowed": isDisabled }, className), ref: ref, onClick: handleClick, "data-selected": isSelected ? "true" : "false", children: [jsx("input", { id: itemId, type: "radio", name: name, value: option.value, checked: isSelected, disabled: isDisabled, onChange: () => onSelect(option.value), className: 'sr-only', ...itemA11yProps }), jsxs("div", { className: "glass-flex glass-items-start glass-gap-3", children: [jsx("div", { className: cn("relative flex items-center justify-center glass-radius-full border-2", "transition-all duration-200 mt-0.5", sizeClasses[size], { "border-white/40 bg-white/10": !isSelected, "border-white bg-white/20": isSelected && !isDisabled, "border-white/20 bg-white/5": isDisabled }), children: isSelected && jsx(MotionFramer, { preset: shouldAnimate && respectMotionPreference ? "scaleIn" : "none", duration: 200, className: 'w-2 h-2 glass-radius-full glass-surface-subtle' }) }), jsxs("div", { className: "glass-flex-1 glass-min-w-0", children: [jsxs("div", { className: "glass-flex glass-items-center glass-gap-2", children: [option.icon && jsx("div", { className: 'glass-flex glass-items-center glass-justify-center text-primary/70', children: option.icon }), jsx("h3", { id: labelElementId, className: cn("font-medium glass-text-primary", textSizeClasses[size]), children: option.label })] }), option.description && jsx("p", { id: descriptionElementId, className: 'text-primary/60 glass-text-sm glass-mt-1 leading-relaxed', children: option.description })] })] })] }) }); } // Default variant return jsxs(OptimizedGlassCore, { ref: ref, elevation: isSelected ? "level2" : "level1", intensity: "subtle", depth: 1, tint: isSelected ? "primary" : "neutral", border: "subtle", animation: shouldAnimate && respectMotionPreference ? "shimmer" : "none", performanceMode: "medium", liftOnHover: !isDisabled, press: true, className: cn("flex items-center glass-gap-3 cursor-pointer group", "transition-all duration-200 glass-p-3 glass-radius-lg", "focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-primary/40 focus-within:ring-offset-background", { "opacity-50 cursor-not-allowed": isDisabled }, className), onClick: handleClick, "data-selected": isSelected ? "true" : "false", children: [jsx("input", { id: itemId, type: "radio", name: name, value: option.value, checked: isSelected, disabled: isDisabled, onChange: () => onSelect(option.value), className: 'sr-only', ...itemA11yProps }), jsx("div", { className: cn("relative flex items-center justify-center glass-radius-full border-2", "transition-all duration-200", sizeClasses[size], { "border-white/40 bg-white/10": !isSelected, "border-white bg-white/20": isSelected && !isDisabled, "border-white/20 bg-white/5": isDisabled }), children: isSelected && jsx(MotionFramer, { preset: shouldAnimate && respectMotionPreference ? "scaleIn" : "none", duration: 200, className: 'w-2 h-2 glass-radius-full glass-surface-subtle' }) }), jsxs("div", { className: "glass-flex-1 glass-min-w-0", children: [jsxs("div", { className: "glass-flex glass-items-center glass-gap-2", children: [option.icon && jsx("div", { className: 'glass-flex glass-items-center glass-justify-center text-primary/70', children: option.icon }), jsx("span", { id: labelElementId, className: cn("glass-text-primary/90", textSizeClasses[size], { "font-medium": isSelected }), children: option.label })] }), option.description && jsx("p", { id: descriptionElementId, className: 'text-primary/60 glass-text-sm glass-mt-1', children: option.description })] })] }); }); GlassRadioGroupItem.displayName = "GlassRadioGroupItem"; export { GlassRadioGroup, GlassRadioGroupItem }; //# sourceMappingURL=GlassRadioGroup.js.map