fix: Improve mobile layout for flashcards, splitting action rows
All checks were successful
Automated Container Build / build-and-push (push) Successful in 52s

This commit is contained in:
Elijah 2026-06-28 12:43:32 -07:00
parent 2457bfcb39
commit 454db7fb1d
3 changed files with 126 additions and 88 deletions

View file

@ -110,7 +110,7 @@ export default function DeckStudyPage() {
</div> </div>
{/* View toggle & Share */} {/* View toggle & Share */}
<div className="flex items-center gap-2 md:gap-4"> <div className="flex items-center justify-between w-full md:w-auto gap-2 md:gap-4 self-end md:self-auto">
{view === "study" && ( {view === "study" && (
<button <button
onClick={handleRestart} onClick={handleRestart}
@ -122,29 +122,32 @@ export default function DeckStudyPage() {
</svg> </svg>
</button> </button>
)} )}
<ShareMenu targetType="DECK" contentId={deck.id} classSlug={classSlug} />
<div className="flex bg-bg-surface-alt rounded-lg p-0.5 border border-border-light"> <div className="flex items-center gap-2 md:gap-4 ml-auto">
<button <ShareMenu targetType="DECK" contentId={deck.id} classSlug={classSlug} />
onClick={() => setView("study")}
className={`px-4 py-2 rounded-md text-sm font-medium transition-all duration-200 cursor-pointer ${ <div className="flex bg-bg-surface-alt rounded-lg p-0.5 border border-border-light">
view === "study" <button
? "bg-bg-surface text-text-heading shadow-sm" onClick={() => setView("study")}
: "text-text-muted hover:text-text-heading" className={`px-4 py-2 rounded-md text-sm font-medium transition-all duration-200 cursor-pointer ${
}`} view === "study"
> ? "bg-bg-surface text-text-heading shadow-sm"
Study : "text-text-muted hover:text-text-heading"
</button> }`}
<button >
onClick={() => setView("manage")} Study
className={`px-4 py-2 rounded-md text-sm font-medium transition-all duration-200 cursor-pointer ${ </button>
view === "manage" <button
? "bg-bg-surface text-text-heading shadow-sm" onClick={() => setView("manage")}
: "text-text-muted hover:text-text-heading" className={`px-4 py-2 rounded-md text-sm font-medium transition-all duration-200 cursor-pointer ${
}`} view === "manage"
> ? "bg-bg-surface text-text-heading shadow-sm"
Manage : "text-text-muted hover:text-text-heading"
</button> }`}
>
Manage
</button>
</div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -62,7 +62,7 @@ export function SharedViewer({ type, data }: SharedViewerProps) {
)} )}
</div> </div>
<div className="flex items-center gap-2 md:gap-4 self-end md:self-auto"> <div className="flex items-center justify-between w-full md:w-auto gap-2 md:gap-4 self-end md:self-auto">
{view === "study" && ( {view === "study" && (
<button <button
onClick={() => setRestartKey(k => k + 1)} onClick={() => setRestartKey(k => k + 1)}
@ -75,30 +75,32 @@ export function SharedViewer({ type, data }: SharedViewerProps) {
</button> </button>
)} )}
{type === "flashcards" && ( <div className="flex items-center gap-2 md:gap-4 ml-auto">
<div className="flex bg-bg-surface-alt rounded-lg p-0.5 border border-border-light"> {type === "flashcards" && (
<button <div className="flex bg-bg-surface-alt rounded-lg p-0.5 border border-border-light">
onClick={() => setView("study")} <button
className={`px-4 py-2 rounded-md text-sm font-medium transition-all duration-200 cursor-pointer ${ onClick={() => setView("study")}
view === "study" className={`px-4 py-2 rounded-md text-sm font-medium transition-all duration-200 cursor-pointer ${
? "bg-bg-surface text-text-heading shadow-sm" view === "study"
: "text-text-muted hover:text-text-heading" ? "bg-bg-surface text-text-heading shadow-sm"
}`} : "text-text-muted hover:text-text-heading"
> }`}
Study >
</button> Study
<button </button>
onClick={() => setView("list")} <button
className={`px-4 py-2 rounded-md text-sm font-medium transition-all duration-200 cursor-pointer ${ onClick={() => setView("list")}
view === "list" className={`px-4 py-2 rounded-md text-sm font-medium transition-all duration-200 cursor-pointer ${
? "bg-bg-surface text-text-heading shadow-sm" view === "list"
: "text-text-muted hover:text-text-heading" ? "bg-bg-surface text-text-heading shadow-sm"
}`} : "text-text-muted hover:text-text-heading"
> }`}
List >
</button> List
</div> </button>
)} </div>
)}
</div>
</div> </div>
</div> </div>

View file

@ -398,36 +398,15 @@ export function FlashcardViewer({ cards, deckId, isShared = false, initialProgre
)} )}
</div> </div>
<div className="relative w-full mt-6"> <div className="w-full mt-6">
{currentIndex > 0 && (
<button {/* Primary Action Row (Grading) */}
onClick={() => { <div className="flex items-center justify-center gap-4 min-h-[52px] mb-4 relative z-20">
const prevIndex = currentIndex - 1;
setCurrentIndex(prevIndex);
setIsFlipped(false);
setHasFlippedOnce(false);
const prevId = order[prevIndex];
const newResults = { ...results };
delete newResults[prevId];
setResults(newResults);
saveProgress(order, prevIndex, newResults, isShuffled ? "SHUFFLED" : "SEQUENTIAL");
}}
className="absolute left-0 top-0 p-2 rounded-full text-text-muted hover:bg-bg-surface-alt hover:text-text-heading transition-all duration-200 cursor-pointer z-10"
title="Previous Card"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 19l-7-7m0 0l7-7m-7 7h18" />
</svg>
</button>
)}
<div className="flex items-center justify-center gap-4 min-h-[52px]">
{hasFlippedOnce && ( {hasFlippedOnce && (
<div className="flex items-center gap-4 animate-fade-in"> <div className="flex items-center gap-2 md:gap-4 animate-fade-in">
<button <button
onClick={() => gradeCard("missed")} onClick={() => gradeCard("missed")}
className="flex items-center gap-2 px-6 py-3 rounded-xl border-2 border-error/30 text-error font-medium hover:bg-error-bg transition-all duration-200 cursor-pointer" className="flex items-center gap-2 px-6 py-3 rounded-xl border-2 border-error/30 text-error font-medium hover:bg-error-bg transition-all duration-200 cursor-pointer bg-bg-base/80 backdrop-blur"
> >
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
@ -436,7 +415,7 @@ export function FlashcardViewer({ cards, deckId, isShared = false, initialProgre
</button> </button>
<button <button
onClick={() => gradeCard("correct")} onClick={() => gradeCard("correct")}
className="flex items-center gap-2 px-6 py-3 rounded-xl border-2 border-success/30 text-success font-medium hover:bg-success-bg transition-all duration-200 cursor-pointer" className="flex items-center gap-2 px-6 py-3 rounded-xl border-2 border-success/30 text-success font-medium hover:bg-success-bg transition-all duration-200 cursor-pointer bg-bg-base/80 backdrop-blur"
> >
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
@ -447,24 +426,78 @@ export function FlashcardViewer({ cards, deckId, isShared = false, initialProgre
)} )}
</div> </div>
<button {/* Secondary Action Row (Navigation/Settings) */}
onClick={toggleShuffle} <div className={`flex items-center justify-between w-full px-2 transition-all duration-300 relative z-10 ${!hasFlippedOnce ? '-mt-[68px]' : ''}`}>
className={`absolute right-0 top-1 p-2 rounded-full hover:bg-bg-surface-alt transition-all duration-200 cursor-pointer z-10 ${ {currentIndex > 0 ? (
isShuffled ? "text-primary" : "text-text-muted hover:text-text-heading" <button
}`} onClick={() => {
title={isShuffled ? "Turn shuffle off" : "Turn shuffle on"} const prevIndex = currentIndex - 1;
> setCurrentIndex(prevIndex);
<svg className={`w-5 h-5 ${isShuffled ? "stroke-[2.5]" : "stroke-2"}`} fill="none" stroke="currentColor" viewBox="0 0 24 24"> setIsFlipped(false);
<path strokeLinecap="round" strokeLinejoin="round" d="M16 3h5v5M4 20L21 3M21 16v5h-5M15 15l6 6M4 4l5 5" /> setHasFlippedOnce(false);
</svg>
</button> const prevId = order[prevIndex];
const newResults = { ...results };
delete newResults[prevId];
setResults(newResults);
saveProgress(order, prevIndex, newResults, isShuffled ? "SHUFFLED" : "SEQUENTIAL");
}}
className="p-2 rounded-full text-text-muted hover:bg-bg-surface-alt hover:text-text-heading transition-all duration-200 cursor-pointer bg-bg-base/50"
title="Previous Card"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 19l-7-7m0 0l7-7m-7 7h18" />
</svg>
</button>
) : <div className="w-9 h-9" />}
<button
onClick={() => {
if (isShuffled) {
const sortedOrder = [...order].sort((a, b) => {
const idxA = cards.findIndex(c => c.id === a);
const idxB = cards.findIndex(c => c.id === b);
return idxA - idxB;
});
setOrder(sortedOrder);
setCurrentIndex(0);
setIsFlipped(false);
setHasFlippedOnce(false);
setResults({});
setIsShuffled(false);
setToastMessage("Card shuffle off");
saveProgress(sortedOrder, 0, {}, "SEQUENTIAL");
} else {
const remainingCards = order.slice(currentIndex);
const shuffledRemaining = [...remainingCards].sort(() => Math.random() - 0.5);
const newOrder = [...order.slice(0, currentIndex), ...shuffledRemaining];
setOrder(newOrder);
setIsShuffled(true);
setToastMessage("Card shuffle on");
saveProgress(newOrder, currentIndex, results, "SHUFFLED");
}
}}
className={`p-2 rounded-full transition-all duration-200 cursor-pointer bg-bg-base/50 ${
isShuffled
? "text-primary hover:bg-primary/10"
: "text-text-muted hover:bg-bg-surface-alt hover:text-text-heading"
}`}
title={isShuffled ? "Turn off shuffle" : "Shuffle remaining"}
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M19.5 7.5L15 3m4.5 4.5l-4.5 4.5M19.5 7.5h-5.25a6 6 0 00-5.454 3.486m0 0L5.25 21m0 0h-1.5m1.5 0l-3-3m3 3l3-3m11.25-6l-4.5-4.5M19.5 16.5l-4.5 4.5M19.5 16.5h-5.25a6 6 0 01-5.454-3.486m0 0L5.25 3m0 0h-1.5m1.5 0l-3 3m3-3l3 3" />
</svg>
</button>
</div>
</div>
<div className="text-center mt-4 text-xs text-text-muted"> <div className="text-center mt-4 text-xs text-text-muted">
{hasFlippedOnce {hasFlippedOnce
? "← Missed · Got It →" ? "← Missed · Got It →"
: "Space to flip"} : "Space to flip"}
</div> </div>
</div>
</div> </div>
)} )}
</div> </div>