perf+style: grid subscription hygiene, a11y, shadcn-style consistency
Perf / a11y (high-impact review items) - Timeline arrow-key handler binds once per (viewMode, currentSection) and reads fresh state via navStateRef instead of an 8-element dep array of new-each-render values. - usePhotosQuery collapses 14 individual Zustand selectors into one useShallow selector returning the params object. - PhotoThumbnail no longer subscribes to the search query directly; Timeline subscribes once and passes it down as a prop. - PhotoThumbnail gains role="button", tabIndex, aria-label, aria-pressed, Enter/Space key handlers and a focus-visible ring. Timeline marked role="grid"; RightSidebar marked role="region". Style consistency - Swap clsx for cn (tailwind-merge aware) across 17 files so conflicting utility classes collapse correctly. - New Badge primitive (ui/badge.tsx) with default/neutral/overlay/ outline variants; adopted in ColorsView, RatedView, TagsView for the repeated count overlay pill. - Fix palette drift: text-amber-400 -> text-star, text-green-* -> text-pick, text-red-* -> text-reject (5 files). - Button gains an xs size (h-6 px-1.5 text-[11px]) for the repeated compact-button pattern. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -10,7 +10,7 @@ import {
|
||||
ShoppingBasket,
|
||||
Trash2,
|
||||
} from 'lucide-react'
|
||||
import clsx from 'clsx'
|
||||
import { cn } from '@/lib/utils'
|
||||
import {
|
||||
useQuery,
|
||||
useMutation,
|
||||
@@ -367,13 +367,13 @@ export function PhotoInfoPanel({ photoId, darkTheme = false }: PhotoInfoPanelPro
|
||||
|
||||
// Single themable input class so the same component reads against either
|
||||
// the surface (grid sidebar) or a darker preview overlay.
|
||||
const inputClass = clsx(
|
||||
const inputClass = cn(
|
||||
'w-full rounded border px-2 py-1 text-sm focus:outline-none',
|
||||
darkTheme
|
||||
? 'border-white/15 bg-black/40 text-white placeholder-white/40 focus:border-primary'
|
||||
: 'border-border bg-bg text-text placeholder-text-faint focus:border-primary'
|
||||
)
|
||||
const monoInputClass = clsx(
|
||||
const monoInputClass = cn(
|
||||
'w-full rounded border px-2 py-1 font-mono text-xs focus:outline-none',
|
||||
darkTheme
|
||||
? 'border-white/15 bg-black/40 text-white placeholder-white/40 focus:border-primary'
|
||||
@@ -382,7 +382,7 @@ export function PhotoInfoPanel({ photoId, darkTheme = false }: PhotoInfoPanelPro
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
className={cn(
|
||||
'flex h-full flex-col transition-opacity duration-150',
|
||||
isPlaceholderData && 'opacity-70'
|
||||
)}
|
||||
@@ -436,7 +436,7 @@ export function PhotoInfoPanel({ photoId, darkTheme = false }: PhotoInfoPanelPro
|
||||
onBlur={commitNotes}
|
||||
placeholder="Add notes…"
|
||||
rows={3}
|
||||
className={clsx(inputClass, 'resize-none')}
|
||||
className={cn(inputClass, 'resize-none')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -454,7 +454,7 @@ export function PhotoInfoPanel({ photoId, darkTheme = false }: PhotoInfoPanelPro
|
||||
title={`Set rating to ${value}`}
|
||||
>
|
||||
<Star
|
||||
className={clsx(
|
||||
className={cn(
|
||||
'h-5 w-5 transition-colors',
|
||||
value <= rating
|
||||
? 'fill-star text-star'
|
||||
@@ -478,7 +478,7 @@ export function PhotoInfoPanel({ photoId, darkTheme = false }: PhotoInfoPanelPro
|
||||
onClick={() =>
|
||||
updateMutation.mutate({ color_label: active ? null : value })
|
||||
}
|
||||
className={clsx(
|
||||
className={cn(
|
||||
'h-5 w-5 rounded-full ring-offset-2 ring-offset-surface transition-all',
|
||||
className,
|
||||
active ? 'ring-2 ring-primary' : 'opacity-60 hover:opacity-100'
|
||||
@@ -509,7 +509,7 @@ export function PhotoInfoPanel({ photoId, darkTheme = false }: PhotoInfoPanelPro
|
||||
heapMutation.mutate({ remove: isInActiveHeap })
|
||||
}}
|
||||
disabled={!activeHeap || heapMutation.isPending}
|
||||
className={clsx(
|
||||
className={cn(
|
||||
'flex items-center gap-1 rounded px-2 py-1 text-sm transition-colors disabled:cursor-not-allowed disabled:opacity-50',
|
||||
isInActiveHeap
|
||||
? 'bg-pick/20 text-pick'
|
||||
@@ -528,7 +528,7 @@ export function PhotoInfoPanel({ photoId, darkTheme = false }: PhotoInfoPanelPro
|
||||
</button>
|
||||
<button
|
||||
onClick={() => updateMutation.mutate({ is_discarded: !isDiscarded })}
|
||||
className={clsx(
|
||||
className={cn(
|
||||
'flex items-center gap-1 rounded px-2 py-1 text-sm transition-colors',
|
||||
isDiscarded
|
||||
? 'bg-reject/20 text-reject'
|
||||
|
||||
Reference in New Issue
Block a user