providers.tsx 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. "use client";
  2. import { createContext, useContext, useEffect, useState } from "react";
  3. import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
  4. type Theme = "dark" | "light" | "system";
  5. type ThemeProviderProps = {
  6. children: React.ReactNode;
  7. defaultTheme?: Theme;
  8. storageKey?: string;
  9. };
  10. type ThemeProviderState = {
  11. theme: Theme;
  12. setTheme: (theme: Theme) => void;
  13. };
  14. const initialState: ThemeProviderState = {
  15. theme: "system",
  16. setTheme: () => null,
  17. };
  18. const ThemeProviderContext = createContext<ThemeProviderState>(initialState);
  19. export function ThemeProvider({
  20. children,
  21. defaultTheme = "system",
  22. storageKey = "vtorio-theme",
  23. ...props
  24. }: ThemeProviderProps) {
  25. const [theme, setTheme] = useState<Theme>(defaultTheme);
  26. const [mounted, setMounted] = useState(false);
  27. // Handle hydration mismatch
  28. useEffect(() => {
  29. setMounted(true);
  30. const stored = localStorage.getItem(storageKey) as Theme;
  31. if (stored) {
  32. setTheme(stored);
  33. }
  34. }, [storageKey]);
  35. useEffect(() => {
  36. if (!mounted) return;
  37. const root = window.document.documentElement;
  38. root.classList.remove("light", "dark");
  39. if (theme === "system") {
  40. const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
  41. .matches
  42. ? "dark"
  43. : "light";
  44. root.classList.add(systemTheme);
  45. return;
  46. }
  47. root.classList.add(theme);
  48. }, [theme, mounted]);
  49. const value = {
  50. theme,
  51. setTheme: (theme: Theme) => {
  52. localStorage.setItem(storageKey, theme);
  53. setTheme(theme);
  54. },
  55. };
  56. // Prevent hydration mismatch
  57. if (!mounted) {
  58. return <>{children}</>;
  59. }
  60. return (
  61. <ThemeProviderContext.Provider {...props} value={value}>
  62. {children}
  63. </ThemeProviderContext.Provider>
  64. );
  65. }
  66. export const useTheme = () => {
  67. const context = useContext(ThemeProviderContext);
  68. if (context === undefined)
  69. throw new Error("useTheme must be used within a ThemeProvider");
  70. return context;
  71. };
  72. // Create a client
  73. const queryClient = new QueryClient();
  74. export function Providers({ children }: { children: React.ReactNode }) {
  75. return (
  76. <QueryClientProvider client={queryClient}>
  77. <ThemeProvider>
  78. {children}
  79. </ThemeProvider>
  80. </QueryClientProvider>
  81. );
  82. }