Files
DECNET/decnet_web/src/components/ThemeLab/ThemeToggle.tsx
anti f2b3393669 chore: relicense to AGPL-3.0-or-later and add SPDX headers
Replaces LICENSE (GPLv3 -> AGPLv3) and prepends
`SPDX-License-Identifier: AGPL-3.0-or-later` to every source file
across decnet/, decnet_web/, tests/, scripts/, and tools/.

Rationale: closes the GPLv3 ASP loophole so any party operating a
modified DECNET as a network service must offer their modified
source. Personal copyright (Samuel Paschuan) + inbound=outbound
contributions make a future unilateral relicense infeasible.

- LICENSE: full AGPL-3.0 text (gnu.org/licenses/agpl-3.0.txt)
- COPYRIGHT: project copyright notice
- tools/add_spdx_headers.py: idempotent header injector
  (shebang- and PEP 263-aware)

Touches 1565 source files (.py, .ts, .tsx, .js, .jsx, .css, .sh).
No behavior change; comments only.
2026-05-22 21:04:16 -04:00

59 lines
1.6 KiB
TypeScript

// SPDX-License-Identifier: AGPL-3.0-or-later
import React, { useEffect, useState } from 'react';
/* Dev-scoped Dark/Light theme toggle for the theme lab.
*
* Flips `document.documentElement.dataset.theme` and persists to
* **sessionStorage** intentionally — the lab is a tab-scoped
* exploration tool. Global persistence (across reloads, all users)
* is the user-facing Config toggle that ships in Task 6. */
export const THEME_SESSION_KEY = 'decnet_theme_lab';
export type Theme = 'dark' | 'light';
export function readLabTheme(): Theme {
try {
const v = sessionStorage.getItem(THEME_SESSION_KEY);
return v === 'light' ? 'light' : 'dark';
} catch {
return 'dark';
}
}
export function applyTheme(theme: Theme): void {
document.documentElement.dataset.theme = theme;
}
const ThemeToggle: React.FC = () => {
const [theme, setTheme] = useState<Theme>(() => readLabTheme());
useEffect(() => {
applyTheme(theme);
try { sessionStorage.setItem(THEME_SESSION_KEY, theme); } catch { /* ignore */ }
}, [theme]);
return (
<div className="lab-theme-toggle" role="group" aria-label="Theme">
<button
type="button"
className={`btn small ${theme === 'dark' ? '' : 'ghost'}`}
onClick={() => setTheme('dark')}
aria-pressed={theme === 'dark'}
>
DARK
</button>
<button
type="button"
className={`btn small ${theme === 'light' ? '' : 'ghost'}`}
onClick={() => setTheme('light')}
aria-pressed={theme === 'light'}
>
LIGHT
</button>
</div>
);
};
export default ThemeToggle;