Public Access
Library rows: the status badge is a menu that saves at once. Game page: status and rating in the top card, saved on click. Status changes and hiding a game show an Undo toast. Other game actions moved into a "..." menu; split and unlink ask first. New sorts: name, last played, time to beat; "last synced" is gone. Page numbers and 44 px touch targets on phones.
43 lines
1.8 KiB
React
43 lines
1.8 KiB
React
import {useTranslation} from 'react-i18next';
|
|
import {Star} from 'lucide-react';
|
|
|
|
import {cn} from '@/lib/utils';
|
|
|
|
/**
|
|
* Five star buttons. Clicking the chosen star again sends 0, so the parent decides what that means.
|
|
*
|
|
* @param value 0 for no rating
|
|
* @param labelledBy id of the element that names the group
|
|
* @param readOnly shows the stars as one image, without buttons
|
|
*/
|
|
export default function RatingStars({value, onChange, labelledBy, disabled, readOnly}) {
|
|
const {t} = useTranslation();
|
|
if (readOnly) {
|
|
return (
|
|
<span role="img" aria-label={t('BACKLOG.RATING_STARS', {count: value})} className="inline-flex gap-0.5">
|
|
{[1, 2, 3, 4, 5].map((n) => (
|
|
<Star key={n} className={cn('size-3.5', n <= value ? 'fill-amber-400 text-amber-400' : 'text-muted-foreground/40')}/>
|
|
))}
|
|
</span>
|
|
);
|
|
}
|
|
return (
|
|
<div role="group" aria-labelledby={labelledBy} className="flex h-9 items-center gap-1">
|
|
{[1, 2, 3, 4, 5].map((n) => (
|
|
<button
|
|
key={n}
|
|
type="button"
|
|
disabled={disabled}
|
|
aria-label={t('BACKLOG.RATING_STARS', {count: n})}
|
|
aria-pressed={value === n}
|
|
onClick={() => onChange(value === n ? 0 : n)}
|
|
className="rounded-sm p-0.5 pointer-coarse:size-11 pointer-coarse:p-2.5 outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50"
|
|
>
|
|
<Star className={cn('size-6 transition-colors',
|
|
n <= value ? 'fill-amber-400 text-amber-400' : 'text-muted-foreground/40 hover:text-amber-400')}/>
|
|
</button>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|