p2p/src/hooks/ThemeContext.tsx
Bhanu Prakash Sai Potteri f5bc1fa119 feat: rebuild the UI on Tailwind v4 + shadcn/ui
Migrates Tailwind v3 -> v4 (CSS-first, theme tokens for brand, workflow
states and charts) and adds 23 shadcn/ui components on Radix.

- lib/workflow.ts is now the single source of truth for state name, colour,
  icon and available actions; "Financial Review" is the canonical served
  name, with "Finance Review" kept as an alias so those rows stop falling
  through to the unstyled fallback and dropping out of every count
- record view: TanStack Table with server-side sort/paginate, company column
  with initials avatar, page totals, density and a URL-synced query state
- dashboard: one cached analytics fetch shared by tiles and charts, donut +
  aging + vendor spend, click-to-filter
- detail view: rebuilt against the live detailview payload — restores the PO
  number and payment terms that the old key heuristics silently dropped,
  parses match_summary into a 3-way-match checklist, and separates the
  department's and reviewer's own words from AI-generated content
- form modal on Radix Dialog (focus trap, scroll lock, escape handling)
2026-07-29 19:47:59 +05:30

49 lines
1.5 KiB
TypeScript

import { createContext, useContext, useEffect, useState } from "react";
type Theme = "light" | "dark";
interface ThemeContextValue {
theme: Theme;
toggleTheme: () => void;
}
const ThemeContext = createContext<ThemeContextValue>({
theme: "light",
toggleTheme: () => {},
});
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = useState<Theme>(() => {
const stored = localStorage.getItem("p2p_theme");
if (stored === "dark" || stored === "light") return stored;
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
});
useEffect(() => {
const root = document.documentElement;
root.classList.toggle("dark", theme === "dark");
localStorage.setItem("p2p_theme", theme);
}, [theme]);
// The colour-transition is opt-in per switch rather than a standing global
// rule, so ordinary hovers and mounts don't animate. Skipped entirely when
// the user prefers reduced motion.
const toggleTheme = () => {
const root = document.documentElement;
const animate = !window.matchMedia("(prefers-reduced-motion: reduce)").matches;
if (animate) {
root.classList.add("theming");
window.setTimeout(() => root.classList.remove("theming"), 220);
}
setTheme((t) => (t === "light" ? "dark" : "light"));
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
export const useTheme = () => useContext(ThemeContext);