UNPKG

nudge-components-library

Version:

A library of nudge UI components

108 lines (105 loc) 6.3 kB
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