/ components / SlideNav.tsx
SlideNav.tsx
1 import { MessageSquarePlus } from 'lucide-react'; 2 3 interface Props { 4 current: number; 5 total: number; 6 reviewedCount?: number; 7 prevTitle: string | null; 8 nextTitle: string | null; 9 onPrev: () => void; 10 onNext: () => void; 11 commentCount?: number; 12 onSubmitReview?: () => void; 13 } 14 15 // Bottom navigation bar. The previous version was a thin row of muted 16 // text controls — so quiet that users overlooked it and missed how to 17 // advance through the deck. This rebuild keeps the editorial register 18 // (no shadcn Button chrome, no rounded pills) but gives the bar real 19 // presence: a subtle paper-tinted background, a solid border-top, more 20 // vertical padding, and the actual section titles on the prev/next 21 // buttons so the user can see what's coming next at a glance. The 22 // reading-progress hairline has moved to the very top of the page in 23 // ReviewPage; this bar no longer carries it. 24 export function SlideNav({ 25 current, 26 total, 27 reviewedCount = 0, 28 prevTitle, 29 nextTitle, 30 onPrev, 31 onNext, 32 commentCount = 0, 33 onSubmitReview, 34 }: Props) { 35 const isOverview = current === 0; 36 const atEnd = current >= total; 37 38 // On the very last slide, the right-hand button transforms from 39 // "Next" into the submit-review CTA. The user has just finished 40 // reading; the most prominent affordance on the page should now 41 // be the conclusion of the flow, not a dead disabled button. When 42 // this transformation happens, the center submit-review link 43 // hides so the right button is the unique submit affordance. 44 const showSubmitAsRight = atEnd && !!onSubmitReview; 45 const showSubmitInCenter = !showSubmitAsRight && !!onSubmitReview; 46 47 const nextLeadIn = isOverview ? 'Start reading' : 'Next'; 48 49 return ( 50 <div className="border-t border-border bg-muted/30 shrink-0"> 51 <div className="flex items-stretch justify-between gap-6 px-10 py-5"> 52 {/* ── Previous ── Quieter weight than Next so the eye is 53 pulled forward. Disabled (faded) when on the overview. */} 54 <button 55 onClick={onPrev} 56 disabled={isOverview} 57 className="group flex items-baseline gap-3 text-left max-w-[36%] min-w-0 disabled:opacity-30 disabled:cursor-default" 58 title="Previous (←)" 59 > 60 <span className="slide-meta shrink-0 group-hover:text-foreground transition-colors"> 61 ← 62 </span> 63 <span className="flex flex-col gap-0.5 min-w-0"> 64 <span className="slide-meta">Previous</span> 65 <span className="font-serif text-base text-foreground/60 group-hover:text-foreground transition-colors truncate"> 66 {prevTitle ?? 'Start of review'} 67 </span> 68 </span> 69 </button> 70 71 {/* ── Center ── Counter + (when not at the end) the submit 72 review affordance. On the last slide the submit moves 73 to the right column, so this column shrinks back to 74 just the counter. */} 75 <div className="flex flex-col items-center justify-center gap-1 shrink-0"> 76 <span className="slide-meta tabular-nums"> 77 {isOverview 78 ? 'Overview' 79 : `Section ${current.toString().padStart(2, '0')} of ${total 80 .toString() 81 .padStart(2, '0')}`} 82 {reviewedCount > 0 && ` · ${reviewedCount} reviewed`} 83 </span> 84 {showSubmitInCenter && ( 85 <button 86 onClick={onSubmitReview} 87 className="inline-flex items-center gap-1.5 slide-meta hover:text-foreground transition-colors" 88 > 89 <MessageSquarePlus className="h-3 w-3" /> 90 Submit review 91 {commentCount > 0 && ( 92 <span className="text-[var(--ring)] tabular-nums">({commentCount})</span> 93 )} 94 </button> 95 )} 96 </div> 97 98 {/* ── Right ── On the last slide, this becomes the 99 submit-review CTA — closing the reading loop with a 100 clear "you're done, click here" guide. Otherwise it's 101 the standard Next button with the upcoming section 102 title in heavy foreground type. */} 103 {showSubmitAsRight ? ( 104 <button 105 onClick={onSubmitReview} 106 className="group flex items-baseline gap-3 text-right max-w-[36%] min-w-0 justify-end" 107 title="Submit review" 108 > 109 <span className="flex flex-col gap-0.5 min-w-0 items-end"> 110 <span className="slide-meta">End of review</span> 111 <span className="font-serif text-base text-foreground group-hover:opacity-80 transition-opacity truncate"> 112 Submit review 113 {commentCount > 0 && ( 114 <span className="text-[var(--ring)] tabular-nums"> ({commentCount})</span> 115 )} 116 </span> 117 </span> 118 <span className="slide-meta shrink-0 text-foreground group-hover:opacity-80 transition-opacity"> 119 → 120 </span> 121 </button> 122 ) : ( 123 <button 124 onClick={onNext} 125 disabled={atEnd} 126 className="group flex items-baseline gap-3 text-right max-w-[36%] min-w-0 disabled:opacity-30 disabled:cursor-default justify-end" 127 title="Next (→)" 128 > 129 <span className="flex flex-col gap-0.5 min-w-0 items-end"> 130 <span className="slide-meta">{nextLeadIn}</span> 131 <span className="font-serif text-base text-foreground group-hover:opacity-80 transition-opacity truncate"> 132 {nextTitle ?? '—'} 133 </span> 134 </span> 135 <span className="slide-meta shrink-0 text-foreground group-hover:opacity-80 transition-opacity"> 136 → 137 </span> 138 </button> 139 )} 140 </div> 141 </div> 142 ); 143 }