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>
52 lines
1.6 KiB
TypeScript
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);
|
|
}
|