fix(audit UI/UX): 44px slabs, missing grid base cols, aria-labels, dark weather panel, contrast, sheet width, stretched-link SpotCard

This commit is contained in:
SkateSpots 2026-10-05 21:47:29 +07:00
parent 2f6c6230f3
commit 9ca5f2a604
16 changed files with 37 additions and 25 deletions

View file

@ -801,7 +801,7 @@ export function Layout({ children, noPadding }: LayoutProps) {
<Sheet open={mobileMenuOpen} onOpenChange={setMobileMenuOpen}>
<SheetTrigger asChild>
<Button variant="ghost" size="icon" className="h-10 w-10 touch-manipulation active:scale-95 transition-transform">
<Button variant="ghost" size="icon" aria-label={t('chromeUi.menu')} className="h-10 w-10 touch-manipulation active:scale-95 transition-transform">
<Menu className="h-5 w-5" />
</Button>
</SheetTrigger>

View file

@ -276,7 +276,7 @@ export function MarketplaceItemCard({ item, compact = false }: MarketplaceItemCa
{user && !isOwnListing && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="h-6 w-6 text-gray-400">
<Button variant="ghost" size="icon" className="min-h-0 min-w-0 h-6 w-6 text-gray-400">
<MoreVertical className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
@ -299,7 +299,7 @@ export function MarketplaceItemCard({ item, compact = false }: MarketplaceItemCa
<Button
variant="ghost"
size="icon"
className="h-6 w-6 text-gray-400"
className="min-h-0 min-w-0 h-6 w-6 text-gray-400"
disabled={isUpdatingStatus}
aria-label={t('marketUi.card.manageListing')}
>

View file

@ -1625,6 +1625,12 @@ Instructions:
onPointerMove={onPointerMove}
onPointerUp={onPointerUp}
onClick={onBubbleClick}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onBubbleClick(); }
}}
role="button"
tabIndex={0}
aria-label={t('sasquatchUi.openGuideAria')}
title={t('sasquatchUi.bubbleTitle')}
>
<div

View file

@ -61,7 +61,10 @@ export function SpotCard({ spot, status, community }: SpotCardProps) {
const isGone = status?.status === 'gone';
return (
<Link to={route}>
<div className="relative">
{/* Stretched link — the whole card navigates, without nesting interactive
elements (buttons, Zap, dropdowns) or dialogs inside an <a>. */}
<Link to={route} className="absolute inset-0 z-0" aria-label={spot.title} />
<Card className={cn(
"group overflow-hidden hover:shadow-2xl transition-all duration-300 border-2 bg-black/90 backdrop-blur",
isShop
@ -211,7 +214,7 @@ export function SpotCard({ spot, status, community }: SpotCardProps) {
)}
</div>
<div className="flex items-center gap-2" onClick={(e) => e.preventDefault()}>
<div className="relative z-10 flex items-center gap-2" onClick={(e) => e.preventDefault()}>
{user && !isShop && (
<Button
size="icon"
@ -277,7 +280,7 @@ export function SpotCard({ spot, status, community }: SpotCardProps) {
targetEventId={spot.id}
targetTitle={spot.title}
/>
</Link>
</div>
);
}

View file

@ -586,7 +586,7 @@ function createPopupContent(
// Weather section (if enabled and available)
if (weatherData) {
const weatherSection = document.createElement('div');
weatherSection.className = 'mb-3 p-2 rounded-lg bg-gray-50 border border-gray-200';
weatherSection.className = 'mb-3 p-2 rounded-lg bg-white/[0.04] border border-white/10';
// Current conditions
const currentWeather = document.createElement('div');

View file

@ -169,7 +169,7 @@ export function VouchCard({
</Button>
)}
{isOwner && (
<p className="text-center text-[11px] text-gray-600">
<p className="text-center text-[11px] text-gray-400">
{t('socialUi.vouch.ownerNote')}
</p>
)}

View file

@ -44,7 +44,7 @@ export function LightningPrivacyTips({ className, defaultExpanded = false }: Lig
{isExpanded && (
<CardContent className="space-y-4 pt-0">
{/* Cashu vs Lightning Comparison */}
<div className="grid gap-3 sm:grid-cols-2">
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
{/* Cashu - Recommended */}
<div className="p-3 rounded-lg bg-green-500/10 border border-green-500/30">
<div className="flex items-center gap-2 mb-2">

View file

@ -26,6 +26,7 @@ const en = {
filter: 'Filter',
sort: 'Sort',
share: 'Share',
more: 'More',
copy: 'Copy',
copied: 'Copied!',
viewAll: 'View All',

View file

@ -27,6 +27,7 @@ const es = {
filter: 'Filtrar',
sort: 'Ordenar',
share: 'Compartir',
more: 'Más',
copy: 'Copiar',
copied: '¡Copiado!',
viewAll: 'Ver Todo',

View file

@ -156,7 +156,7 @@ export default function AppShopPage() {
</span>
</div>
<div className="grid gap-3 sm:grid-cols-2">
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
{PLANNED.slice(0, 2).map(({ icon: Icon, labelKey }) => (
<div
key={labelKey}
@ -195,7 +195,7 @@ export default function AppShopPage() {
</span>
</div>
<div className="mt-3 grid gap-3 sm:grid-cols-2">
<div className="mt-3 grid grid-cols-1 gap-3 sm:grid-cols-2">
{FREE_PRINT.map((item) => (
<div
key={item.titleKey}

View file

@ -432,7 +432,7 @@ export default function BecomeEditorPage() {
{image ? (
<div className="relative w-32 h-32">
<img src={image} alt="profile" className="w-32 h-32 object-cover rounded-lg border-2 border-purple-500/40" />
<Button type="button" variant="destructive" size="icon" className="absolute -top-2 -right-2 h-6 w-6" onClick={() => setImage(null)}>
<Button type="button" variant="destructive" size="icon" className="min-h-0 min-w-0 absolute -top-2 -right-2 h-6 w-6" onClick={() => setImage(null)}>
<X className="h-3 w-3" />
</Button>
</div>

View file

@ -522,7 +522,7 @@ function FilterDrawer({
{t('common.clearAll')}
</button>
)}
<button onClick={onClose} className="w-7 h-7 flex items-center justify-center rounded-full bg-white/5 hover:bg-white/10 transition-colors">
<button onClick={onClose} className="min-h-0 min-w-0 w-7 h-7 flex items-center justify-center rounded-full bg-white/5 hover:bg-white/10 transition-colors">
<X className="h-3.5 w-3.5 text-gray-400" />
</button>
</div>

View file

@ -139,7 +139,7 @@ export default function GlossaryPage() {
</p>
</div>
) : (
<div className="grid gap-3 md:grid-cols-2">
<div className="grid grid-cols-1 gap-3 md:grid-cols-2">
{searchResults.map((t) => (
<div key={`${t.categoryId}-${t.name}`}>
<p className="mb-1 text-[10px] font-bold uppercase tracking-widest text-gray-600">
@ -164,7 +164,7 @@ export default function GlossaryPage() {
<span className="text-xs text-gray-500">{t('settingsUi.glossary.tricksCount', { count: cat.tricks.length })}</span>
</div>
<p className="mb-5 text-sm text-gray-400">{cat.blurb}</p>
<div className="grid gap-3 md:grid-cols-2">
<div className="grid grid-cols-1 gap-3 md:grid-cols-2">
{cat.tricks.map((t) => (
<TrickRow key={t.name} trick={t} />
))}

View file

@ -171,7 +171,7 @@ function MiniStashCard({ item, onDelete }: { item: StashItem; onDelete: () => vo
</Badge>
<button
onClick={onDelete}
className="p-1 rounded hover:bg-red-500/20 text-gray-500 hover:text-red-400 transition-colors"
className="min-h-0 min-w-0 p-1 rounded hover:bg-red-500/20 text-gray-500 hover:text-red-400 transition-colors"
>
<Trash2 className="h-3 w-3" />
</button>
@ -1144,7 +1144,7 @@ export default function MapPage() {
{/* View toggle — compact pill */}
<div className="flex items-center rounded-full border border-white/15 bg-black/50 backdrop-blur-md md:bg-white/4 md:border-white/10 p-0.5 flex-shrink-0">
<button
className={cn('h-8 w-8 rounded-full flex items-center justify-center transition-all touch-manipulation active:scale-90',
className={cn('min-h-0 min-w-0 h-8 w-8 rounded-full flex items-center justify-center transition-all touch-manipulation active:scale-90',
viewMode === 'map' ? 'bg-[#ff0040] shadow-lg shadow-[#ff0040]/30' : 'text-gray-500 hover:text-gray-300')}
onClick={() => setViewMode('map')}
aria-label={t('mapUi.mapViewAria')}
@ -1152,7 +1152,7 @@ export default function MapPage() {
<Map className="h-3.5 w-3.5" />
</button>
<button
className={cn('h-8 w-8 rounded-full flex items-center justify-center transition-all touch-manipulation active:scale-90',
className={cn('min-h-0 min-w-0 h-8 w-8 rounded-full flex items-center justify-center transition-all touch-manipulation active:scale-90',
viewMode === 'list' ? 'bg-[#ff0040] shadow-lg shadow-[#ff0040]/30' : 'text-gray-500 hover:text-gray-300')}
onClick={() => setViewMode('list')}
aria-label={t('mapUi.listViewAria')}
@ -1496,7 +1496,7 @@ export default function MapPage() {
<span className="hidden xs:inline">{t('mapUi.stash')}</span>
</Button>
</SheetTrigger>
<SheetContent side="right" className="w-[340px] sm:w-[400px] p-0 bg-black/95 border-l-2 border-[#ff6600]/30">
<SheetContent side="right" className="w-[min(340px,calc(100vw-2rem))] sm:w-[400px] p-0 bg-black/95 border-l-2 border-[#ff6600]/30">
<SheetHeader className="p-4 pb-0">
<SheetTitle className="flex items-center gap-2 text-white">
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-[#ff6600] to-[#ff0040] flex items-center justify-center">

View file

@ -603,7 +603,7 @@ function LiveIssuesSection() {
if (isLoading) {
return (
<div className="grid gap-4 sm:grid-cols-2">
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<Skeleton className="h-56 w-full rounded-2xl" />
<Skeleton className="h-56 w-full rounded-2xl" />
</div>
@ -615,7 +615,7 @@ function LiveIssuesSection() {
return (
<section className="mb-16">
<SectionHeader emoji="📡" label={t('walletUi.newsletter.sectionLatestDrops')} color="#ccff00" />
<div className="mt-4 grid gap-4 sm:grid-cols-2">
<div className="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">
{issues.map((issue) => (
<LiveIssueCard key={issue.id} issue={issue} />
))}
@ -709,7 +709,7 @@ export default function NewsletterPage() {
{t('walletUi.newsletter.supportUs')}
</a>
</p>
<p className="text-[10px] text-gray-700 uppercase tracking-widest">
<p className="text-[10px] text-gray-400 uppercase tracking-widest">
{t('walletUi.newsletter.freeToSkate')}
</p>
</div>

View file

@ -416,7 +416,7 @@ export default function SpotDetailPage() {
<div className="flex items-center gap-2 flex-wrap">
{isOwner && (
<Link to={`/edit-spot/${spot.pubkey}/${spot.identifier}`}>
<Button variant="outline" size="icon">
<Button variant="outline" size="icon" aria-label={t('common.edit')}>
<Pencil className="h-4 w-4" />
</Button>
</Link>
@ -440,6 +440,7 @@ export default function SpotDetailPage() {
<Button
variant="outline"
size="icon"
aria-label={t('common.save')}
onClick={() => setShowCollectionDialog(true)}
className="hover:bg-[#ccff00]/10 hover:text-[#ccff00] hover:border-[#ccff00]/50"
>
@ -464,7 +465,7 @@ export default function SpotDetailPage() {
spotTitle={spot.title}
/>
)}
<Button variant="outline" size="icon" onClick={handleShare}>
<Button variant="outline" size="icon" onClick={handleShare} aria-label={t('common.share')}>
<Share2 className="h-4 w-4" />
</Button>
<Button variant="outline" size="icon" onClick={handleDirections} aria-label={t('spotUi.getDirections')}>
@ -473,7 +474,7 @@ export default function SpotDetailPage() {
{/* More options menu */}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon">
<Button variant="outline" size="icon" aria-label={t('common.more')}>
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>