@chepchik/react-rating
Version:
React Rating is a react rating component which supports custom symbols both with inline styles and icon.
100 lines (99 loc) • 4.75 kB
JavaScript
import React, { useState, useEffect } from "react";
import Symbol from "./RatingSymbol";
const Rating = ({ totalSymbols = 5, value, placeholderValue, readonly = false,
// Рейтинг редактируемый
quiet = false,
// Включает обработку событий при касании
// будет ли отображаться при наведении
fractions = 1,
// Поддержка дробных значений (например, 0.5).
direction = "ltr",
// Направление текста
emptySymbol,
// Настройка внешнего вида звездочек
fullSymbol,
// Настройка внешнего вида звездочек
placeholderSymbol = undefined,
// Отображаемое значение при наведении или текущее значение, если наведения нет.
onClick, onHover = () => { }, className, id, style, tabIndex, "aria-label": ariaLabel, }) => {
const [displayValue, setDisplayValue] = useState(value);
const [interacting, setInteracting] = useState(false);
useEffect(() => {
if (value !== displayValue) {
setDisplayValue(value);
}
}, [value]);
useEffect(() => {
if (!interacting && displayValue !== value) {
onHover(undefined);
}
else if (interacting && value === displayValue) {
onHover(displayValue);
}
}, [interacting, displayValue, value, onHover]);
const calculateHoverPercentage = (event) => {
const clientX = "touches" in event
? "changedTouches" in event
? event.changedTouches[0].clientX
: // @ts-ignore
event.touches[0].clientX
: event.clientX;
const targetRect = event.target.getBoundingClientRect();
const delta = direction === "rtl" ? targetRect.right - clientX : clientX - targetRect.left;
return delta < 0 ? 0 : delta / targetRect.width;
};
const calculateDisplayValue = (symbolIndex, event) => {
const percentage = calculateHoverPercentage(event);
const fraction = Math.ceil((percentage % 1) * fractions) / fractions;
const precision = 10 ** 3;
const displayValue = symbolIndex + (Math.floor(percentage) + Math.floor(fraction * precision) / precision);
return displayValue > 0
? displayValue > totalSymbols
? totalSymbols
: displayValue
: 1 / fractions;
};
const handleSymbolMouseMove = (symbolIndex, event) => {
if (readonly)
return;
const value = calculateDisplayValue(symbolIndex, event);
setDisplayValue(value);
setInteracting(true);
};
const handleSymbolClick = (symbolIndex, event) => {
const value = calculateDisplayValue(symbolIndex, event);
onClick(value, event);
};
const handleSymbolEnd = (symbolIndex, event) => {
if (!quiet) {
handleSymbolClick(symbolIndex, event);
event.preventDefault();
}
onMouseLeave();
};
const onMouseLeave = () => {
setDisplayValue(value);
setInteracting(false);
};
const shouldDisplayPlaceholder = placeholderValue !== 0 && value === 0 && !interacting;
const renderedValue = shouldDisplayPlaceholder ? placeholderValue : quiet ? value : displayValue;
const fullSymbols = Math.floor(renderedValue);
const symbolNodes = Array.from({ length: totalSymbols }).map((_, i) => {
const percent = i - fullSymbols < 0 ? 100 : i - fullSymbols === 0 ? (renderedValue - i) * 100 : 0;
const empty = Array.isArray(emptySymbol) ? emptySymbol : [emptySymbol];
const full = Array.isArray(fullSymbol) ? fullSymbol : [fullSymbol];
const placeholder = placeholderSymbol
? Array.isArray(placeholderSymbol)
? placeholderSymbol
: [placeholderSymbol]
: [];
return (React.createElement(Symbol, { key: i, index: i, readonly: readonly, inactiveIcon: empty[i % empty.length], activeIcon: shouldDisplayPlaceholder ? placeholder[i % placeholder.length] : full[i % full.length], percent: percent, direction: direction, ...(!readonly && {
onClick: handleSymbolClick,
onMouseMove: handleSymbolMouseMove,
onTouchMove: handleSymbolMouseMove,
onTouchEnd: handleSymbolEnd,
}) }));
});
return (React.createElement("span", { id: id, style: { ...style, display: "inline-block", direction }, className: className, tabIndex: tabIndex, "aria-label": ariaLabel, ...(!readonly && { onMouseLeave }) }, symbolNodes));
};
export default Rating;