fix(a11y): Batch E part 1 — labels, valid nesting, touch targets

- EmptyState: render <Link> as the button (a <button> inside <Link> was invalid
  and double-focusable); AboutPage <a href> -> <Link> (no full reload)
- aria-label on icon-only controls: settings Back, conversation Refresh/Send,
  crew chat Send
- Crew invite Accept/Decline raised to 44px touch targets
This commit is contained in:
SkateSpots 2026-09-26 18:46:51 +07:00
parent 0468ed1c1b
commit 09e1612db7
6 changed files with 13 additions and 9 deletions

View file

@ -431,6 +431,7 @@ export function CrewChat({
type="submit"
size="icon"
disabled={isPending || !draft.trim()}
aria-label="Send message"
className="bg-teal-500 hover:bg-teal-600 text-black shrink-0"
>
{isPending ? <Loader2 className="h-4 w-4 animate-spin" /> : <Send className="h-4 w-4" />}

View file

@ -118,7 +118,7 @@ export function CrewInviteCard({
size="sm"
onClick={handleAccept}
disabled={busy}
className="h-8 flex-1 gap-1.5 bg-teal-500 font-bold text-black hover:bg-teal-600"
className="min-h-11 flex-1 gap-1.5 bg-teal-500 font-bold text-black hover:bg-teal-600"
>
{accepting ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Check className="h-3.5 w-3.5" />}
Accept
@ -128,7 +128,7 @@ export function CrewInviteCard({
variant="outline"
onClick={handleDecline}
disabled={busy}
className="h-8 gap-1.5 border-white/15 text-gray-400 hover:bg-white/10 hover:text-white"
className="min-h-11 gap-1.5 border-white/15 text-gray-400 hover:bg-white/10 hover:text-white"
>
{declining ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <X className="h-3.5 w-3.5" />}
Decline

View file

@ -63,11 +63,11 @@ function ActionButton({ action }: { action: EmptyStateAction }) {
);
if (action.to) {
// Single interactive element — a <button> nested in a <Link> was invalid
// and double-focusable for assistive tech.
return (
<Link to={action.to} className="w-full sm:w-auto">
<button type="button" className={classes}>
{inner}
</button>
<Link to={action.to} className={classes}>
{inner}
</Link>
);
}

View file

@ -156,11 +156,11 @@ Version: v0.1.0`;
<p className="text-gray-300 mb-4 text-sm">
SkateSpots is open source and community-driven. Contribute code, share spots, or help others get started.
</p>
<a href="/onboarding">
<Link to="/onboarding">
<Button className="gap-2 bg-[#ccff00] text-black hover:bg-[#ccff00]/90 font-bold uppercase w-full">
Get Started
</Button>
</a>
</Link>
</CardContent>
</Card>
</div>

View file

@ -217,7 +217,7 @@ export default function ConversationPage() {
{/* Header */}
<div className="flex items-center gap-2 sm:gap-3 pb-3 sm:pb-4 border-b border-gray-800 flex-shrink-0">
<Link to="/messages">
<Button variant="ghost" size="icon" className="text-gray-400 hover:text-white">
<Button variant="ghost" size="icon" aria-label="Back" className="text-gray-400 hover:text-white">
<ArrowLeft className="h-5 w-5" />
</Button>
</Link>
@ -239,6 +239,7 @@ export default function ConversationPage() {
size="icon"
onClick={() => refetch()}
disabled={isRefetching}
aria-label="Refresh"
className="text-gray-400 hover:text-white"
>
<RefreshCw className={cn("h-4 w-4", isRefetching && "animate-spin")} />
@ -303,6 +304,7 @@ export default function ConversationPage() {
<Button
onClick={handleSend}
disabled={!newMessage.trim() || isSending}
aria-label="Send message"
className="bg-[#ff0040] hover:bg-[#ff0040]/90 text-white h-[44px] w-[44px] sm:h-auto sm:w-auto p-0 sm:px-4 flex-shrink-0"
>
{isSending ? (

View file

@ -76,6 +76,7 @@ function BackHeader({ title, onBack }: { title: string; onBack: () => void }) {
<div className="flex items-center gap-3 mb-5">
<button
onClick={onBack}
aria-label="Back"
className="flex items-center justify-center w-8 h-8 rounded-xl bg-white/5 hover:bg-white/10 active:scale-95 transition-all"
>
<ChevronLeft className="h-4 w-4 text-white" />