nudge-components-library
Version:
A library of nudge UI components
108 lines (105 loc) • 6.3 kB
JavaScript
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
import { memo, useContext, useState, useRef, useEffect, useCallback } from 'react';
import { FiStar } from '../../node_modules/react-icons/fi/index.js';
import '../../styles/tokens.css.js';
import '../../styles/globals.css.js';
import { ThemeContext } from '../../theme/ThemeContext.js';
import styles from './Rating.module.css.js';
const Star = memo(({ index, currentRating, hoverRating, disabled, handleRating, setHoverRating, ariaLabel, onFocus, }) => {
const isFilled = index <= (hoverRating ?? currentRating);
const theme = useContext(ThemeContext);
return (jsx("span", { className: `${styles.star} ${isFilled ? styles.filled : ""}`, onClick: () => !disabled && handleRating(index), onKeyDown: (e) => {
if (!disabled && (e.key === "Enter" || e.key === " ")) {
handleRating(index);
}
}, onMouseEnter: () => !disabled && setHoverRating(index), onMouseLeave: () => !disabled && setHoverRating(null), onTouchStart: () => !disabled && setHoverRating(index), onTouchEnd: () => !disabled && setHoverRating(null), onTouchCancel: () => !disabled && setHoverRating(null), onFocus: onFocus, role: "button", tabIndex: disabled ? -1 : 0, "aria-label": `${ariaLabel} - ${index} star${index > 1 ? "s" : ""}`, style: {
cursor: disabled ? "not-allowed" : "pointer",
transition: "transform 0.2s ease-in-out, color 0.2s ease-in-out",
...(theme.rating?.star || {}),
}, children: jsx(FiStar, { style: {
stroke: isFilled ? "var(--filled-star-color)" : "var(--star-color)",
fill: isFilled ? "var(--filled-star-color)" : "transparent",
strokeWidth: theme.rating?.star?.strokeWidth,
transition: "fill 0.2s ease-in-out, stroke 0.2s ease-in-out",
} }) }, index));
});
function Rating({ ratingLabel, rating, defaultRating = 0, max = 5, onChange, disabled = false, nudgeText, id, ariaLabel = ratingLabel || "", onFocus, onBlur, nudgeVisible = true, nudgePosition = "bottom", renderNudge, }) {
const theme = useContext(ThemeContext);
const [currentRating, setCurrentRating] = useState(defaultRating);
const [hoverRating, setHoverRating] = useState(null);
const containerRef = useRef(null);
// focus/blur tracking
const isFocusingWithin = useRef(false);
const blurTimeout = useRef(null);
const handleFocusCapture = (e) => {
if (!isFocusingWithin.current) {
isFocusingWithin.current = true;
onFocus?.(e);
}
if (blurTimeout.current)
clearTimeout(blurTimeout.current);
};
const handleBlurCapture = (e) => {
blurTimeout.current = setTimeout(() => {
if (isFocusingWithin.current) {
isFocusingWithin.current = false;
onBlur?.(e);
}
}, 100);
};
useEffect(() => {
if (rating !== undefined) {
setCurrentRating(rating);
}
}, [rating]);
const handleRating = useCallback((newRating) => {
if (disabled)
return;
setCurrentRating(newRating);
onChange?.(newRating);
}, [disabled, onChange]);
useEffect(() => {
const handleTouchStart = (event) => {
const targetNode = event.target;
const container = containerRef.current;
if (!container)
return;
const touchedInside = container.contains(targetNode);
if (touchedInside) {
if (!isFocusingWithin.current) {
isFocusingWithin.current = true;
onFocus?.({
target: container,
});
}
}
else {
if (isFocusingWithin.current) {
isFocusingWithin.current = false;
onBlur?.({
target: container,
});
}
}
};
document.addEventListener("touchstart", handleTouchStart);
return () => {
document.removeEventListener("touchstart", handleTouchStart);
};
}, [onFocus, onBlur]);
const nudgeId = id ? `${id}-nudge` : undefined;
const nudgeElement = nudgeVisible ? (renderNudge ? (jsx("div", { id: nudgeId, style: theme.rating?.nudgeText, children: renderNudge(currentRating) })) : (nudgeText && (jsx("div", { id: nudgeId, style: theme.rating?.nudgeText, children: jsx("span", { children: nudgeText }) })))) : null;
const stars = Array.from({ length: max }, (_, i) => {
const index = max - i;
return (jsx(Star, { index: index, currentRating: currentRating, hoverRating: hoverRating, disabled: disabled, handleRating: handleRating, setHoverRating: setHoverRating, ariaLabel: ariaLabel, onFocus: onFocus }, index));
});
const content = nudgeVisible && (nudgePosition === "left" || nudgePosition === "right") ? (jsxs("div", { style: { display: "flex", alignItems: "center" }, children: [nudgePosition === "left" && (jsx("div", { style: theme.rating?.nudgeLeft, children: nudgeElement })), jsx("div", { className: styles.ratingContainer, children: stars }), nudgePosition === "right" && (jsx("div", { style: theme.rating?.nudgeRight, children: nudgeElement }))] })) : (jsxs(Fragment, { children: [nudgeVisible && nudgePosition === "top" && (jsx("div", { style: theme.rating?.nudgeTop, children: nudgeElement })), jsx("div", { className: styles.ratingContainer, children: stars }), nudgeVisible && nudgePosition === "bottom" && (jsx("div", { style: theme.rating?.nudgeBottom, children: nudgeElement }))] }));
return (jsxs("div", { style: {
...theme.rating?.wrapper,
...(disabled ? theme.rating?.disabled || {} : {}),
"--star-color": theme.rating?.star?.color,
"--filled-star-color": theme.rating?.filledStar?.color,
}, ref: containerRef, onFocusCapture: handleFocusCapture, onBlurCapture: handleBlurCapture, children: [ratingLabel && (jsx("div", { style: theme.rating?.ratingLabel, children: ratingLabel })), jsx("div", { style: theme.rating?.container, children: content })] }));
}
export { Rating };
//# sourceMappingURL=Rating.js.map