← back to Nineoh Guide
apps/web/components/NewsControls.tsx
69 lines
"use client";
import { useEffect, useState } from "react";
/**
* Sort + density controls for the news grid (Steve's standing rule: every grid
* gets sort + a density control, both persisted). Density writes a CSS var the
* grid reads; sort navigates with a query param (server does the ordering).
*/
const KEY = "nineoh.newsCols.v1";
export function NewsControls({ sort }: { sort: string }) {
const [cols, setCols] = useState(1);
useEffect(() => {
const saved = Number(localStorage.getItem(KEY));
const c = saved >= 1 && saved <= 4 ? saved : 1;
setCols(c);
document.documentElement.style.setProperty("--news-cols", String(c));
}, []);
const onCols = (c: number) => {
setCols(c);
document.documentElement.style.setProperty("--news-cols", String(c));
localStorage.setItem(KEY, String(c));
};
const onSort = (e: React.ChangeEvent<HTMLSelectElement>) => {
const url = new URL(window.location.href);
url.searchParams.set("sort", e.target.value);
url.searchParams.delete("page");
window.location.href = url.toString();
};
return (
<div
style={{
display: "flex",
gap: 14,
alignItems: "center",
flexWrap: "wrap",
margin: "10px 0 16px",
fontSize: 13,
}}
>
<label>
Sort{" "}
<select defaultValue={sort} onChange={onSort} aria-label="Sort news">
<option value="newest">Newest</option>
<option value="oldest">Oldest</option>
<option value="source">Source A–Z</option>
</select>
</label>
<label style={{ display: "flex", alignItems: "center", gap: 6 }}>
Density
<input
type="range"
min={1}
max={4}
value={cols}
onChange={(e) => onCols(Number(e.target.value))}
aria-label="News density (columns)"
style={{ width: 90 }}
/>
<span style={{ color: "var(--muted)" }}>{cols}-up</span>
</label>
</div>
);
}