feat: Takeout walkthrough, design-system restyle, replacing import
Adds a seven-step in-app guide to exporting subscriptions from Google Takeout, with the real URLs opened in the system browser. Restyles the app onto the supplied design system: slate/sky palette, 9-15px type ladder, outline-first controls, tiered radii, borders for separation and shadows only for elevation. Light and dark are both designed, with a System/Light/Dark control and a pre-paint script so the window does not flash light on a dark machine. Importing now replaces the subscription list rather than merging, per request. Because that can delete downloaded files, a confirmation dialog names exactly what will go first.
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
export type Appearance = "system" | "light" | "dark";
|
||||
export const APPEARANCE_MODES: Appearance[] = ["system", "light", "dark"];
|
||||
|
||||
const KEY = "flighttube.appearance";
|
||||
const media = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
|
||||
function stored(): Appearance {
|
||||
try {
|
||||
const v = localStorage.getItem(KEY);
|
||||
return v === "light" || v === "dark" || v === "system" ? v : "system";
|
||||
} catch {
|
||||
return "system";
|
||||
}
|
||||
}
|
||||
|
||||
const effective = (mode: Appearance) =>
|
||||
mode === "system" ? (media.matches ? "dark" : "light") : mode;
|
||||
|
||||
/**
|
||||
* Three states, not two. "System" keeps following the OS if it changes
|
||||
* mid-session; light and dark are explicit overrides.
|
||||
*/
|
||||
export function useAppearance() {
|
||||
const [mode, setMode] = useState<Appearance>(stored);
|
||||
const [shown, setShown] = useState<"light" | "dark">(() => effective(stored()));
|
||||
|
||||
const apply = useCallback((m: Appearance) => {
|
||||
const next = effective(m);
|
||||
document.documentElement.classList.toggle("dark", next === "dark");
|
||||
setShown(next);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
apply(mode);
|
||||
try {
|
||||
localStorage.setItem(KEY, mode);
|
||||
} catch {
|
||||
/* storage blocked */
|
||||
}
|
||||
}, [mode, apply]);
|
||||
|
||||
useEffect(() => {
|
||||
const onChange = () => {
|
||||
if (mode === "system") apply(mode);
|
||||
};
|
||||
media.addEventListener("change", onChange);
|
||||
return () => media.removeEventListener("change", onChange);
|
||||
}, [mode, apply]);
|
||||
|
||||
return { mode, setMode, shown };
|
||||
}
|
||||
Reference in New Issue
Block a user