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:
parent
2f6c6230f3
commit
9ca5f2a604
16 changed files with 37 additions and 25 deletions
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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')}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ const en = {
|
|||
filter: 'Filter',
|
||||
sort: 'Sort',
|
||||
share: 'Share',
|
||||
more: 'More',
|
||||
copy: 'Copy',
|
||||
copied: 'Copied!',
|
||||
viewAll: 'View All',
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ const es = {
|
|||
filter: 'Filtrar',
|
||||
sort: 'Ordenar',
|
||||
share: 'Compartir',
|
||||
more: 'Más',
|
||||
copy: 'Copiar',
|
||||
copied: '¡Copiado!',
|
||||
viewAll: 'Ver Todo',
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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} />
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in a new issue