A mark (a lambda on an ultramarine tile with the flap of an envelope), the favicon, home-screen icons and an install manifest. Every colour is a variable defined once per theme, and the Tailwind scales the screens already use read them, so dark (the default) and light are a change of values and not of classes. Geist is served by the app itself. Ctrl+K opens a command menu for folders, actions, accounts and appearance, and typing something that matches no command searches the mail; c writes a message and / goes to the search field. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
38 lines
1.3 KiB
TypeScript
38 lines
1.3 KiB
TypeScript
"use client";
|
|
|
|
import React from "react";
|
|
import { Laptop, Moon, Sun } from "lucide-react";
|
|
import { useTranslations } from "../i18n/provider";
|
|
import { useTheme, type ThemeChoice } from "../lib/theme";
|
|
|
|
/** Dark, light or follow the system: three buttons, one click each. */
|
|
export function ThemeToggle({ className = "" }: { className?: string }) {
|
|
const t = useTranslations();
|
|
const theme = useTheme();
|
|
const options: [ThemeChoice, React.ComponentType<{ className?: string }>, string][] = [
|
|
["dark", Moon, t("appearance.dark")],
|
|
["light", Sun, t("appearance.light")],
|
|
["system", Laptop, t("appearance.system")],
|
|
];
|
|
return (
|
|
<div role="radiogroup" aria-label={t("appearance.title")} className={`flex rounded-lg p-0.5 shadow-edge ${className}`}>
|
|
{options.map(([value, Icon, label]) => (
|
|
<button
|
|
key={value}
|
|
type="button"
|
|
role="radio"
|
|
aria-checked={theme.choice === value}
|
|
aria-label={label}
|
|
title={label}
|
|
onClick={() => theme.set(value)}
|
|
className={`flex h-7 w-8 items-center justify-center rounded-md transition-colors ${
|
|
theme.choice === value ? "bg-indigo-500/20 text-indigo-300" : "text-slate-400 hover:text-slate-100"
|
|
}`}
|
|
>
|
|
<Icon className="h-3.5 w-3.5" />
|
|
</button>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|