← back to Nineoh Guide
apps/web/components/EpisodeControls.tsx
60 lines
"use client";
import { useEffect, useState } from "react";
/**
* Sort + density for the episode guide (standing rule). Density toggles whether
* recaps show (comfortable) or hide for fast title scanning (compact), via a CSS
* var + localStorage. Sort flips season order through a query param (server-ordered).
*/
const KEY = "nineoh.epDensity.v1";
export function EpisodeControls({ order }: { order: string }) {
const [compact, setCompact] = useState(false);
useEffect(() => {
const c = localStorage.getItem(KEY) === "compact";
setCompact(c);
document.documentElement.style.setProperty("--ep-recaps", c ? "none" : "block");
}, []);
const setDensity = (c: boolean) => {
setCompact(c);
document.documentElement.style.setProperty("--ep-recaps", c ? "none" : "block");
localStorage.setItem(KEY, c ? "compact" : "comfortable");
};
const onSort = (e: React.ChangeEvent<HTMLSelectElement>) => {
const url = new URL(window.location.href);
url.searchParams.set("order", e.target.value);
window.location.href = url.toString();
};
return (
<div style={{ display: "flex", gap: 14, alignItems: "center", flexWrap: "wrap", margin: "10px 0 4px", fontSize: 13 }}>
<label>
Sort{" "}
<select defaultValue={order} onChange={onSort} aria-label="Sort episodes">
<option value="asc">Season 1 → 5</option>
<option value="desc">Season 5 → 1</option>
</select>
</label>
<span style={{ display: "inline-flex", border: "1px solid var(--line)", borderRadius: 8, overflow: "hidden" }}>
<button
onClick={() => setDensity(false)}
style={{ border: "none", borderRadius: 0, background: !compact ? "var(--gold-2)" : "transparent" }}
aria-pressed={!compact}
>
Comfortable
</button>
<button
onClick={() => setDensity(true)}
style={{ border: "none", borderRadius: 0, background: compact ? "var(--gold-2)" : "transparent" }}
aria-pressed={compact}
>
Compact
</button>
</span>
</div>
);
}