"use client"; import { useEffect } from "react"; import { useSignal, isThemeParamsDark, setMiniAppBackgroundColor, setMiniAppHeaderColor, } from "@telegram-apps/sdk-react"; /** * Resolves color scheme when Telegram theme is not available (SSR or non-TWA). * Uses --tg-color-scheme (if set by Telegram) then prefers-color-scheme. */ export function getFallbackScheme(): "dark" | "light" { if (typeof window === "undefined") return "dark"; try { const cssScheme = getComputedStyle(document.documentElement) .getPropertyValue("--tg-color-scheme") .trim(); if (cssScheme === "light" || cssScheme === "dark") return cssScheme; } catch { // ignore } if (window.matchMedia("(prefers-color-scheme: dark)").matches) return "dark"; return "light"; } /** * Ensure --surface differs from --bg so cards/cells are visible. * iOS OLED sends secondary_bg === bg (#000000) while section_bg differs; * PC desktop sends section_bg === bg (#17212b) while secondary_bg differs. * When the CSS-resolved --surface equals --bg, override with whichever * Telegram color provides contrast, or a synthesized lighter fallback. */ export function fixSurfaceContrast(): void { const root = document.documentElement; const cs = getComputedStyle(root); const bg = cs.getPropertyValue("--bg").trim(); const surface = cs.getPropertyValue("--surface").trim(); if (!bg || !surface || bg !== surface) return; const sectionBg = cs.getPropertyValue("--tg-theme-section-bg-color").trim(); if (sectionBg && sectionBg !== bg) { root.style.setProperty("--surface", sectionBg); return; } const secondaryBg = cs.getPropertyValue("--tg-theme-secondary-bg-color").trim(); if (secondaryBg && secondaryBg !== bg) { root.style.setProperty("--surface", secondaryBg); return; } root.style.setProperty("--surface", `color-mix(in srgb, ${bg}, white 8%)`); } /** * Applies theme: sets data-theme, forces reflow, fixes surface contrast, * then Mini App background/header. * Shared by TelegramProvider (initial + delayed) and useTelegramTheme. * @param scheme - If provided, use it; otherwise resolve via getFallbackScheme(). */ export function applyTheme(scheme?: "dark" | "light"): void { const resolved = scheme ?? getFallbackScheme(); document.documentElement.setAttribute("data-theme", resolved); void document.documentElement.offsetHeight; // force reflow so WebView repaints fixSurfaceContrast(); if (setMiniAppBackgroundColor.isAvailable()) { setMiniAppBackgroundColor("bg_color"); } if (setMiniAppHeaderColor.isAvailable()) { setMiniAppHeaderColor("bg_color"); } } /** * Maps Telegram theme params to data-theme and Mini App background/header. * Subscribes to theme changes via SDK signals. * Ported from webapp/js/theme.js applyTheme / initTheme. */ export function useTelegramTheme(): "dark" | "light" { const signalDark = useSignal(isThemeParamsDark); const isDark = typeof signalDark === "boolean" ? signalDark : getFallbackScheme() === "dark"; useEffect(() => { applyTheme(isDark ? "dark" : "light"); }, [isDark]); return isDark ? "dark" : "light"; }