From d5cf428c89fc641ff3733502db5c5da29d12cbd7 Mon Sep 17 00:00:00 2001 From: arsam Date: Fri, 18 Sep 2026 23:20:23 +0330 Subject: [PATCH] Import source: web/src/lib/ui Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality. --- web/src/lib/ui.tsx | 101 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 web/src/lib/ui.tsx diff --git a/web/src/lib/ui.tsx b/web/src/lib/ui.tsx new file mode 100644 index 0000000..d920308 --- /dev/null +++ b/web/src/lib/ui.tsx @@ -0,0 +1,101 @@ +import { + createContext, + useContext, + useEffect, + useMemo, + useState, + type ReactNode, +} from "react"; +import { useReducedMotion } from "motion/react"; +import { STRINGS, type Lang } from "./i18n"; + +export type ViewMode = "internal" | "public"; +export type Window = "24h" | "7d"; +export type Theme = "dark" | "light"; + +interface UIState { + lang: Lang; + dir: "ltr" | "rtl"; + mode: ViewMode; + window: Window; + theme: Theme; + reduced: boolean; + t: (typeof STRINGS)["en"]; + setLang: (l: Lang) => void; + setMode: (m: ViewMode) => void; + setWindow: (w: Window) => void; + setTheme: (t: Theme) => void; +} + +const UIContext = createContext(null); + +export function UIProvider({ children }: { children: ReactNode }) { + const [lang, setLang] = useState( + () => (localStorage.getItem("dash.lang") as Lang) || "en", + ); + const [mode, setMode] = useState( + () => (localStorage.getItem("dash.mode") as ViewMode) || "internal", + ); + const [window, setWindow] = useState("24h"); + const [theme, setTheme] = useState( + () => (localStorage.getItem("dash.theme") as Theme) || "dark", + ); + const reduced = useReducedMotion() ?? false; + + const dir = lang === "fa" ? "rtl" : "ltr"; + + useEffect(() => { + document.documentElement.dir = dir; + document.documentElement.lang = lang; + localStorage.setItem("dash.lang", lang); + }, [lang, dir]); + + useEffect(() => { + document.documentElement.dataset.theme = theme; + localStorage.setItem("dash.theme", theme); + }, [theme]); + + useEffect(() => { + localStorage.setItem("dash.mode", mode); + }, [mode]); + + const value = useMemo( + () => ({ + lang, + dir, + mode, + window, + theme, + reduced, + t: STRINGS[lang], + setLang, + setMode, + setWindow, + setTheme, + }), + [lang, dir, mode, window, theme, reduced], + ); + + return {children}; +} + +export function useUI(): UIState { + const ctx = useContext(UIContext); + if (!ctx) throw new Error("useUI outside UIProvider"); + return ctx; +} + +/** Spring presets — physical, interruptible; inert under reduced motion. */ +export const springs = { + arc: { type: "spring", stiffness: 48, damping: 16, mass: 1.2 } as const, + snappy: { type: "spring", stiffness: 360, damping: 28 } as const, + gentle: { type: "spring", stiffness: 120, damping: 22 } as const, + layout: { type: "spring", stiffness: 230, damping: 26 } as const, +}; + +export const STATE_COLOR: Record = { + up: "var(--color-up)", + degraded: "var(--color-warn)", + down: "var(--color-down)", + unknown: "var(--color-idle)", +};