Files
ihasvpn/web/src/components/ThemeSwitch.tsx
T
jcoffey 6c1a13d1fc Padlock-on-a-shield mark, dark and light themes, history note
A new mark: a padlock on a shield with the W as its keyhole, in the
console's teal on a dark shield. It replaces the placeholder W tile in the
sidebar, on the sign-in and setup pages, and as the favicon, with app icons
and a web manifest. The SVG sources, a single-colour variant, a wordmark
and a social preview image live in docs/brand.

The console now has a theme choice: dark by default, light, or follow the
system, switched from the sidebar or the Account page and remembered per
browser. The light palette is a first-class theme rather than a media
query, so both are laid out the same way.

The README opens with the mark and gains a History section about the 2025
WGX installer this replaces.
2026-09-12 20:48:53 -07:00

32 lines
1.2 KiB
TypeScript

import { useState } from "react";
import { Monitor, Moon, Sun } from "lucide-react";
import { getThemeChoice, setThemeChoice, type ThemeChoice } from "../theme";
const options: { value: ThemeChoice; label: string; icon: typeof Moon }[] = [
{ value: "dark", label: "Dark", icon: Moon },
{ value: "light", label: "Light", icon: Sun },
{ value: "system", label: "System", icon: Monitor },
];
// Dark / Light / System. `compact` shows icons only, for the sidebar.
export function ThemeSwitch({ compact = false }: { compact?: boolean }) {
const [choice, setChoice] = useState<ThemeChoice>(getThemeChoice);
const pick = (v: ThemeChoice) => {
setThemeChoice(v);
setChoice(v);
};
return (
<div className="segmented" role="radiogroup" aria-label="Theme">
{options.map((o) => {
const Icon = o.icon;
return (
<button key={o.value} role="radio" aria-checked={choice === o.value} aria-label={o.label} title={o.label} className={choice === o.value ? "active" : ""} onClick={() => pick(o.value)}>
<Icon size={14} style={{ verticalAlign: -2 }} />
{!compact && <span style={{ marginLeft: 6 }}>{o.label}</span>}
</button>
);
})}
</div>
);
}