import * as React from 'react';
import { Contrast } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';
const STORAGE_KEY = 'pig-demo:contrast';
/**
* `localStorage` is not always readable. In a cross-origin iframe with third-
* party storage blocked, and in Safari's private mode, the accessor itself
* THROWS rather than returning null — so every access has to be wrapped, not
* just null-checked. Unreadable storage means "off", never a crash.
*/
function readStored(): boolean {
try {
return window.localStorage.getItem(STORAGE_KEY) === 'high';
} catch {
return false;
}
}
function writeStored(high: boolean): void {
try {
window.localStorage.setItem(STORAGE_KEY, high ? 'high' : 'normal');
} catch {
/* Preference is session-only in this context. The toggle still works. */
}
}
/**
* One value shared by every mounted toggle, not one `useState` each.
*
* This control renders in three places — the desktop bar, the mobile sheet and
* the Play tab — and at most two are ever visible at once. With local state the
* hidden one keeps a stale `aria-pressed`, so a visitor who toggles on the
* board and then opens the menu is told the setting is off while the page is
* plainly showing it on.
*/
let high = false;
let initialised = false;
const listeners = new Set<() => void>();
function applyToRoot(next: boolean): void {
const root = document.documentElement;
// Removed rather than set to "normal": the CSS keys off
// `:root[data-contrast='high']`, and a leftover attribute makes the DOM lie
// about which palette is actually applied.
if (next) root.setAttribute('data-contrast', 'high');
else root.removeAttribute('data-contrast');
}
function setHigh(next: boolean): void {
high = next;
applyToRoot(next);
writeStored(next);
for (const listener of listeners) listener();
}
function subscribe(listener: () => void): () => void {
if (!initialised) {
initialised = true;
high = readStored();
applyToRoot(high);
}
listeners.add(listener);
return () => {
listeners.delete(listener);
};
}
function useContrast(): boolean {
// The server snapshot is `false` so a prerendered page never claims a
// preference it cannot know; the real value lands on the first subscribe.
return React.useSyncExternalStore(
subscribe,
() => high,
() => false,
);
}
export interface ContrastToggleProps {
className?: string;
/**
* Print "High contrast" beside the icon. This is the form the Play tab and
* the mobile sheet use: a colour-blind visitor looking at the board finds the
* mode by its name, not by guessing what a half-filled circle means.
*/
labelled?: boolean;
}
export function ContrastToggle({ className, labelled = false }: ContrastToggleProps) {
const isHigh = useContrast();
const ariaLabel = isHigh ? 'High contrast tiles on. Turn off.' : 'High contrast tiles off. Turn on.';
if (labelled) {
return (
);
}
return (
);
}
/** The Play tab's form of the toggle. Same store, same state, with its name on it. */
export function LabelledContrastToggle({ className }: { className?: string }) {
return ;
}