48 lines
1.6 KiB
TypeScript
48 lines
1.6 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
|
|
import { useTheme, resolveTheme } from "./theme-provider";
|
|
|
|
// The themes whose *surfaces* are dark (see app/globals.css): "dark" and
|
|
// "ocean". Anything that used to compare `resolvedTheme === "dark"` to pick
|
|
// a color variant (group card fills/strokes, markdown widget color modes,
|
|
// ...) uses this instead, so Ocean is treated exactly like Dark.
|
|
const DARK_SURFACES = new Set(["dark", "ocean"]);
|
|
|
|
function useMounted(): boolean {
|
|
const [mounted, setMounted] = useState(false);
|
|
useEffect(() => {
|
|
setMounted(true);
|
|
}, []);
|
|
return mounted;
|
|
}
|
|
|
|
/**
|
|
* True when the active theme has dark surfaces (Dark or Ocean).
|
|
*
|
|
* Reactive to UI theme switches: the value is derived from the provider's
|
|
* theme state (the single source of truth), so the moment `setTheme` fires,
|
|
* every consumer re-renders with the new variant in the same commit -- no
|
|
* stale card colors, no manual refresh needed.
|
|
*
|
|
* Hydration-safe: until mounted we always report "light", matching the
|
|
* server render (the no-FOUC script in app/layout.tsx covers the CSS side
|
|
* of that first frame).
|
|
*/
|
|
export function isDarkTheme(): boolean {
|
|
const mounted = useMounted();
|
|
const { theme } = useTheme();
|
|
if (!mounted) return false;
|
|
return DARK_SURFACES.has(resolveTheme(theme));
|
|
}
|
|
|
|
/**
|
|
* The `data-color-mode` value the markdown editor/preview widgets expect.
|
|
* They only understand light/dark, so the light "sunset" look reports as
|
|
* light and the dark "ocean" look reports as dark.
|
|
*/
|
|
export function colorModeFromTheme(): "light" | "dark" {
|
|
return isDarkTheme() ? "dark" : "light";
|
|
}
|