aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
192 lines (189 loc) • 6.21 kB
JavaScript
'use client';
import { jsx, jsxs } from 'react/jsx-runtime';
import { cn } from '../../lib/utilsComprehensive.js';
import { forwardRef, useState, useCallback } 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 '../../primitives/motion/MotionFramer.js';
const StarIcon = ({
filled,
halfFilled
}) => jsxs("svg", {
viewBox: "0 0 24 24",
fill: "none",
xmlns: "http://www.w3.org/2000/svg",
className: "glass-w-full glass-h-full",
children: [jsx("defs", {
children: halfFilled && jsxs("linearGradient", {
id: "halfFill",
children: [jsx("stop", {
offset: "50%",
stopColor: "currentColor",
stopOpacity: "1"
}), jsx("stop", {
offset: "50%",
stopColor: "currentColor",
stopOpacity: "0.2"
})]
})
}), jsx("path", {
d: "M12 2L15.09 8.26L22 9.27L17 14.14L18.18 21.02L12 17.77L5.82 21.02L7 14.14L2 9.27L8.91 8.26L12 2Z",
fill: halfFilled ? "url(#halfFill)" : filled ? "currentColor" : "none",
stroke: "currentColor",
strokeWidth: "1.5",
strokeLinecap: "round",
strokeLinejoin: "round",
opacity: filled || halfFilled ? 1 : 0.3
})]
});
const GlassRating = /*#__PURE__*/forwardRef(({
value = 0,
max = 5,
size = "md",
readOnly = false,
disabled = false,
allowHalf = false,
showValue = false,
icon,
emptyIcon,
variant = "primary",
onChange,
onHoverChange,
elevation = "level1",
glass = true,
labels,
className,
...props
}, ref) => {
const [hoverValue, setHoverValue] = useState(null);
const sizeClasses = {
sm: "w-4 h-4",
md: "w-6 h-6",
lg: "w-8 h-8",
xl: "w-10 h-10"
};
const variantClasses = {
primary: "text-blue-500",
secondary: "text-purple-500",
success: "text-green-500",
warning: "text-yellow-500",
danger: "text-red-500"
};
const textSizeClasses = {
sm: "glass-text-sm",
md: "glass-text-base",
lg: "glass-text-lg",
xl: "glass-text-xl"
};
const handleClick = useCallback((index, isHalf = false) => {
if (readOnly || disabled) return;
const newValue = isHalf ? index + 0.5 : index + 1;
onChange?.(newValue);
}, [readOnly, disabled, onChange]);
const handleMouseMove = useCallback((event, index) => {
if (readOnly || disabled || !allowHalf) return;
const {
left,
width
} = event.currentTarget.getBoundingClientRect();
const percent = (event.clientX - left) / width;
const newHoverValue = percent < 0.5 ? index + 0.5 : index + 1;
if (newHoverValue !== hoverValue) {
setHoverValue(newHoverValue);
onHoverChange?.(newHoverValue);
}
}, [readOnly, disabled, allowHalf, hoverValue, onHoverChange]);
const handleMouseEnter = useCallback(index => {
if (readOnly || disabled) return;
const newValue = index + 1;
setHoverValue(newValue);
onHoverChange?.(newValue);
}, [readOnly, disabled, onHoverChange]);
const handleMouseLeave = useCallback(() => {
if (readOnly || disabled) return;
setHoverValue(null);
onHoverChange?.(null);
}, [readOnly, disabled, onHoverChange]);
const handleKeyDown = useCallback((event, index) => {
if (readOnly || disabled) return;
switch (event.key) {
case "ArrowLeft":
case "ArrowDown":
event.preventDefault();
if (index > 0) {
onChange?.(index);
}
break;
case "ArrowRight":
case "ArrowUp":
event.preventDefault();
if (index < max - 1) {
onChange?.(index + 2);
}
break;
case "Enter":
case " ":
event.preventDefault();
onChange?.(index + 1);
break;
}
}, [readOnly, disabled, max, onChange]);
const displayValue = hoverValue ?? value;
const stars = Array.from({
length: max
}, (_, index) => {
const isFilled = index < Math.floor(displayValue);
const isHalfFilled = allowHalf && index === Math.floor(displayValue) && displayValue % 1 !== 0;
const label = labels?.[index] || `${index + 1} star${index !== 0 ? "s" : ""}`;
return jsx("button", {
"data-glass-component": true,
type: "button",
onClick: () => handleClick(index),
onMouseMove: e => allowHalf && handleMouseMove(e, index),
onMouseEnter: () => handleMouseEnter(index),
onKeyDown: e => handleKeyDown(e, index),
disabled: disabled || readOnly,
"aria-label": label,
"aria-pressed": index < value,
className: cn(sizeClasses[size], variantClasses[variant], "transition-all duration-200 ease-out", !readOnly && !disabled && "cursor-pointer hover:scale-110 active:scale-95", readOnly && "cursor-default", disabled && "opacity-50 cursor-not-allowed", "focus:outline-none glass-focus glass-touch-target glass-contrast-guard"),
children: icon && isFilled ? icon : emptyIcon && !isFilled && !isHalfFilled ? emptyIcon : jsx(StarIcon, {
filled: isFilled,
halfFilled: isHalfFilled
})
}, index);
});
const content = jsxs("div", {
className: cn("flex items-center gap-1", disabled && "pointer-events-none"),
onMouseLeave: handleMouseLeave,
role: "radiogroup",
"aria-label": "Rating",
"aria-required": !readOnly,
"aria-readonly": readOnly,
children: [stars, showValue && jsxs("span", {
className: cn("glass-text-secondary ml-2", textSizeClasses[size]),
"aria-live": "polite",
children: [displayValue.toFixed(allowHalf && displayValue % 1 !== 0 ? 1 : 0), " ", "/ ", max]
})]
});
if (!glass) {
return jsx("div", {
ref: ref,
className: cn("inline-flex", className),
...props,
children: content
});
}
return jsx(OptimizedGlassCore, {
ref: ref,
elevation: elevation,
className: cn("inline-flex glass-p-2 glass-radius-lg", className),
...props,
children: content
});
});
GlassRating.displayName = "GlassRating";
export { GlassRating, GlassRating as default };
//# sourceMappingURL=GlassRating.js.map