Files
ui/src/material/provider.tsx
T
enricobuehlerandClaude Opus 4.8 ccf93a85a0 chore: import @unom/ui from @played/ui
Copy of the played-ui component library, rebranded to the @unom scope
for publishing as @unom/ui.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 14:09:25 +02:00

52 lines
1.6 KiB
TypeScript

"use client";
import {
createContext,
type ReactNode,
useContext,
useEffect,
useMemo,
useState,
} from "react";
import { defaultMaterialTheme } from "./defaults";
import { mergeMaterialTheme } from "./merge";
import type { MaterialTheme, PartialMaterialTheme } from "./types";
const MaterialContext = createContext<MaterialTheme>(defaultMaterialTheme);
export type MaterialProviderProps = {
theme: PartialMaterialTheme;
children: ReactNode;
};
export function MaterialProvider({ theme, children }: MaterialProviderProps) {
const parent = useContext(MaterialContext);
// Enable material only AFTER mount. The material renders extra DOM (the
// `.material-fx` layer + `data-material`), so if the provider value differed
// between server and client it would cause a hydration mismatch (#418).
// Provider trees aren't always applied identically during SSR (e.g. the
// app shell/header), so to be safe-by-construction we provide the inherited
// value (default = OFF) during SSR and the first hydration render, then flip
// to the merged theme one tick later — exactly like the auth session bridge.
const [mounted, setMounted] = useState(false);
useEffect(() => setMounted(true), []);
const merged = useMemo(
() => (mounted ? mergeMaterialTheme(parent, theme) : parent),
[mounted, parent, theme],
);
return (
<MaterialContext.Provider value={merged}>
{children}
</MaterialContext.Provider>
);
}
export function useMaterial<K extends keyof MaterialTheme>(
key: K,
): MaterialTheme[K] {
return useContext(MaterialContext)[key];
}
export function useMaterialTheme(): MaterialTheme {
return useContext(MaterialContext);
}