Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | 1x 42x 42x 42x 1x 42x 42x 42x 42x 42x 42x 1x 1x 42x 42x 42x 42x 1x 39x 39x 39x 39x 39x 39x 39x 39x 39x 39x 39x | import { useTranslation } from 'react-i18next';
interface RatingWithCountProps {
score: number | null | undefined;
count?: number | null;
className?: string;
suffix?: string;
}
function normalizeCount(count: number | null | undefined): number | null {
return typeof count === 'number' && Number.isFinite(count) ? Math.max(0, count) : null;
}
export function formatRatingWithCountText({
score,
count,
suffix,
}: RatingWithCountProps): string {
const normalizedCount = normalizeCount(count);
if (score == null || !Number.isFinite(score)) {
return '';
}
const countText = normalizedCount == null ? '' : ` (${normalizedCount})`;
const suffixText = suffix ? ` ${suffix}` : '';
return `★ ${score.toFixed(1)}${countText}${suffixText}`;
}
export function RatingWithCount(props: RatingWithCountProps) {
const { t } = useTranslation();
const text = formatRatingWithCountText(props);
if (!text) return null;
const ariaDetail = text.replace(/★\s*/u, '').trim();
return (
<span
className={`rating-with-count ${props.className ?? ''}`.trim()}
aria-label={t('rating.aria_label', { detail: ariaDetail })}
>
{text}
</span>
);
}
|