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:
@@ -26,7 +26,7 @@ import {
|
||||
Upload as UploadIcon,
|
||||
Download as DownloadIcon,
|
||||
} from 'lucide-react'
|
||||
import clsx from 'clsx'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { sourceFolders, photos as photosApi, downloads, type FolderTreeNode } from '../../services/api'
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { toast } from '../ToastContainer'
|
||||
@@ -487,11 +487,11 @@ export function LeftSidebar() {
|
||||
return (
|
||||
<div key={item.id}>
|
||||
<div
|
||||
className={clsx(
|
||||
className={cn(
|
||||
'group flex cursor-pointer items-center gap-1',
|
||||
isSectionHeader
|
||||
? 'mt-2 px-3 py-1 text-[10px] font-semibold uppercase tracking-[0.14em] text-text-muted hover:text-text'
|
||||
: clsx(
|
||||
: cn(
|
||||
// Fixed h-[24px] (not min-h) locks the row height so the
|
||||
// hover-only kebab button can't grow the row vertically.
|
||||
'h-[24px] rounded px-2 text-[12px] leading-none',
|
||||
@@ -577,7 +577,7 @@ export function LeftSidebar() {
|
||||
* without hunting through the kebab menu. */}
|
||||
{item.icon && !isSectionHeader && (
|
||||
<span
|
||||
className={clsx(
|
||||
className={cn(
|
||||
'flex-shrink-0',
|
||||
isSelected
|
||||
? 'text-primary'
|
||||
@@ -619,7 +619,7 @@ export function LeftSidebar() {
|
||||
/>
|
||||
) : (
|
||||
<span
|
||||
className={clsx(
|
||||
className={cn(
|
||||
'flex-1 truncate',
|
||||
item.isHidden && !isSelected && 'italic text-text-muted/80'
|
||||
)}
|
||||
@@ -677,7 +677,7 @@ export function LeftSidebar() {
|
||||
onOpenChange={(o) => setOpenMenuId(o ? item.id : null)}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
className={cn(
|
||||
'relative flex-shrink-0',
|
||||
isMenuOpen ? 'block' : 'hidden group-hover:block'
|
||||
)}
|
||||
@@ -871,7 +871,7 @@ export function LeftSidebar() {
|
||||
return (
|
||||
<div
|
||||
key={sf.id}
|
||||
className={clsx(
|
||||
className={cn(
|
||||
'flex h-[24px] cursor-pointer items-center gap-1 rounded px-2 text-[12px] leading-none',
|
||||
isSelected ? 'bg-primary/20 text-primary' : 'text-text hover:bg-surface-2',
|
||||
)}
|
||||
@@ -881,7 +881,7 @@ export function LeftSidebar() {
|
||||
}
|
||||
>
|
||||
<Users
|
||||
className={clsx(
|
||||
className={cn(
|
||||
'h-3.5 w-3.5 flex-shrink-0',
|
||||
isSelected ? 'text-primary' : 'text-text-muted'
|
||||
)}
|
||||
@@ -922,7 +922,7 @@ export function LeftSidebar() {
|
||||
)}
|
||||
<button
|
||||
onClick={logout}
|
||||
className="rounded p-0.5 text-text-muted hover:bg-surface-2 hover:text-red-400 flex-shrink-0"
|
||||
className="rounded p-0.5 text-text-muted hover:bg-surface-2 hover:text-reject flex-shrink-0"
|
||||
title="Sign out"
|
||||
>
|
||||
<LogOut className="h-3 w-3" />
|
||||
@@ -933,7 +933,7 @@ export function LeftSidebar() {
|
||||
{isAdmin && (
|
||||
<button
|
||||
onClick={() => navigateToSection('settings', {})}
|
||||
className={clsx(
|
||||
className={cn(
|
||||
'flex w-full items-center gap-2 rounded px-2 py-1 text-[12px] hover:bg-surface-2 hover:text-text',
|
||||
currentSection === 'settings' ? 'text-primary' : 'text-text-muted',
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user