69 lines
2.5 KiB
TypeScript
69 lines
2.5 KiB
TypeScript
"use client";
|
|
import { useEffect, useState } from "react";
|
|
import { Infinity, FileText } from "lucide-react";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
const STORAGE_KEY = "pinkudex.infiniteScroll";
|
|
const EVENT_NAME = "pinkudex:infinite-scroll-toggled";
|
|
|
|
export function readInfiniteScrollEnabled(): boolean {
|
|
if (typeof window === "undefined") return true;
|
|
try {
|
|
const raw = localStorage.getItem(STORAGE_KEY);
|
|
if (raw === "0") return false;
|
|
return true;
|
|
} catch { return true; }
|
|
}
|
|
|
|
function writeInfiniteScrollEnabled(value: boolean): void {
|
|
try { localStorage.setItem(STORAGE_KEY, value ? "1" : "0"); } catch { /* ignore */ }
|
|
try { window.dispatchEvent(new CustomEvent(EVENT_NAME, { detail: value })); } catch { /* ignore */ }
|
|
}
|
|
|
|
/**
|
|
* Subscribe to toggle changes from anywhere in the app. Returns the
|
|
* current value. Updates synchronously when the toggle is flipped.
|
|
*/
|
|
export function useInfiniteScrollEnabled(): boolean {
|
|
const [enabled, setEnabled] = useState<boolean>(true);
|
|
useEffect(() => {
|
|
setEnabled(readInfiniteScrollEnabled());
|
|
const onChange = (e: Event) => {
|
|
const next = (e as CustomEvent<boolean>).detail;
|
|
setEnabled(next);
|
|
};
|
|
window.addEventListener(EVENT_NAME, onChange);
|
|
// Cross-tab updates via the storage event.
|
|
const onStorage = (e: StorageEvent) => {
|
|
if (e.key === STORAGE_KEY) setEnabled(readInfiniteScrollEnabled());
|
|
};
|
|
window.addEventListener("storage", onStorage);
|
|
return () => {
|
|
window.removeEventListener(EVENT_NAME, onChange);
|
|
window.removeEventListener("storage", onStorage);
|
|
};
|
|
}, []);
|
|
return enabled;
|
|
}
|
|
|
|
export function InfiniteScrollToggle() {
|
|
const enabled = useInfiniteScrollEnabled();
|
|
return (
|
|
<button
|
|
type="button"
|
|
onClick={() => writeInfiniteScrollEnabled(!enabled)}
|
|
title={enabled ? "Infinite scroll on — click to disable (paginated only)" : "Paginated only — click to enable infinite scroll"}
|
|
className={cn(
|
|
"inline-flex items-center justify-center w-9 h-9 rounded-lg border transition-colors cursor-pointer",
|
|
enabled
|
|
? "border-[var(--color-cyan)]/50 bg-[var(--color-cyan)]/10 text-[var(--color-cyan)]"
|
|
: "border-[var(--color-glass-border)] bg-[var(--color-glass)] text-[var(--color-fg-dim)] hover:text-[var(--color-fg)]",
|
|
)}
|
|
aria-pressed={enabled}
|
|
aria-label={enabled ? "Disable infinite scroll" : "Enable infinite scroll"}
|
|
>
|
|
{enabled ? <Infinity className="w-4 h-4" /> : <FileText className="w-4 h-4" />}
|
|
</button>
|
|
);
|
|
}
|