SkateSpots/src/components/DeckRating.tsx
SkateSpots 09735375c2 feat(i18n): Spanish (Castellano) — infra + Map/Spots, Marketplace, Lab (batch 1)
- First-visit language auto-detect (Spanish devices land in es; stored choice wins).
- Sasquatch AI replies in the app language (skate trick names kept English).
- i18n safety net: scripts/check-i18n.mjs wired into npm test — fails on any t()
  key missing from en/es or any en/es key drift.
- ~1,680 new keys (2,297 total) across mapUi / spotUi / marketUi / labUi and
  ~65 files wired to t(): Map, spots (detail/create/edit/chat), marketplace,
  trades, offers, escrow, shipping, The Lab, /become/* forms, coaches, skaters.
- Policy: only skate TRICK names stay English; user-generated content untouched.
2026-10-01 23:57:20 +07:00

65 lines
2.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { cn } from '@/lib/utils';
import { useTranslation } from '@/contexts/language';
/** A little skateboard-deck glyph for the rating. */
function Deck({ className }: { className?: string }) {
return (
<svg viewBox="0 0 24 10" className={className} fill="currentColor" aria-hidden>
<rect x="1" y="2.2" width="22" height="3.6" rx="1.8" />
<circle cx="7" cy="7.6" r="1.4" />
<circle cx="17" cy="7.6" r="1.4" />
</svg>
);
}
interface DeckRatingProps {
/** Current rating, 0 = none, 1–10 = decks. */
value: number;
/** Omit for a read-only display. */
onChange?: (value: number) => void;
/** `sm` for inline rows, `md` for dialogs. */
size?: 'sm' | 'md';
/** When true, only render the filled decks (for display). */
compact?: boolean;
className?: string;
}
/**
* A 1–10 skateboard-deck rating. 1 deck = bad, 10 decks = best-in-class.
* Interactive by default; read-only when no `onChange` is given.
*/
export function DeckRating({ value, onChange, size = 'md', compact = false, className }: DeckRatingProps) {
const { t } = useTranslation();
const readOnly = !onChange;
const count = readOnly && compact ? Math.max(value, 0) : 10;
const deckCls = size === 'sm' ? 'h-3 w-6' : 'h-3.5 w-7';
return (
<span className={cn('inline-flex items-center gap-0.5', className)} title={t('marketUi.deckRating.title', { value })}>
{Array.from({ length: count }).map((_, i) => {
const n = i + 1;
const filled = n <= value;
if (readOnly) {
return <Deck key={n} className={cn(deckCls, 'text-[#ccff00]')} />;
}
return (
<button
key={n}
type="button"
onClick={() => onChange?.(n === value ? 0 : n)}
aria-label={t('marketUi.deckRating.aria', { n })}
aria-pressed={filled}
className={cn(deckCls, 'transition-transform hover:scale-110 active:scale-95', filled ? 'text-[#ccff00]' : 'text-gray-600')}
>
<Deck className="h-full w-full" />
</button>
);
})}
{(value > 0 || !readOnly) && (
<span className="ml-1 text-[11px] font-bold text-gray-400 tabular-nums">
{value > 0 ? `${value}/10` : ''}
</span>
)}
</span>
);
}