← 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>
  );
}