Przeglądaj źródła

feat(layout): integrate ThemeSynchronizer to dynamically update theme color meta tag based on user preference
style(globals.css): add new color variables for light and dark themes to enhance theming capabilities
chore(layout): remove redundant theme-color meta tags and site manifest link for cleaner code

Mathias 1 miesiąc temu
rodzic
commit
1e46960a49

+ 5 - 5
app/[locale]/layout.tsx

@@ -7,6 +7,7 @@ import { cn } from "@/shared/lib/utils";
 import { getServerUrl } from "@/shared/lib/server-url";
 import { FB_PIXEL_ID } from "@/shared/lib/facebook/fb-pixel";
 import { SiteConfig } from "@/shared/config/site-config";
+import { ThemeSynchronizer } from "@/features/theme/ui/ThemeSynchronizer";
 import { Header } from "@/features/layout/Header";
 import { Footer } from "@/features/layout/Footer";
 import { TailwindIndicator } from "@/components/utils/TailwindIndicator";
@@ -83,7 +84,6 @@ export const metadata: Metadata = {
     ],
     apple: "/apple-touch-icon.png",
   },
-  manifest: "/site.webmanifest",
 };
 
 const inter = Inter({
@@ -114,10 +114,7 @@ export default async function RootLayout({ params, children }: RootLayoutProps)
       <html className="h-full" dir="ltr" lang={locale} suppressHydrationWarning>
         <head>
           <meta charSet="UTF-8" />
-          <meta content="#f3f4f6" media="(prefers-color-scheme: light)" name="theme-color" />
-          <meta content="#18181b" media="(prefers-color-scheme: dark)" name="theme-color" />
           <meta content="width=device-width, initial-scale=1, maximum-scale=1 viewport-fit=cover" name="viewport" />
-          <link href="/site.webmanifest" rel="manifest" />
 
           {/* eslint-disable-next-line @next/next/no-page-custom-font */}
           <link as="style" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="preload" />
@@ -126,6 +123,9 @@ export default async function RootLayout({ params, children }: RootLayoutProps)
           <link href="https://www.workout.cool/fr" hrefLang="fr" rel="alternate" />
           <link href="https://www.workout.cool/en" hrefLang="en" rel="alternate" />
 
+          {/* Balise theme-color unique, synchronisée dynamiquement */}
+          <meta content="#f3f4f6" name="theme-color" />
+
           {/* TODO: maybe add some ads ? */}
           <noscript>
             <Image
@@ -150,7 +150,7 @@ export default async function RootLayout({ params, children }: RootLayoutProps)
           suppressHydrationWarning
         >
           <Providers locale={locale}>
-            {/* <WorkoutSessionsSynchronizer /> */}
+            <ThemeSynchronizer />
             <NextTopLoader color="#FF5722" delay={100} showSpinner={false} />
 
             {/* Main Card Container */}

+ 25 - 0
src/features/theme/ui/ThemeSynchronizer.tsx

@@ -0,0 +1,25 @@
+"use client";
+
+import { useEffect } from "react";
+import { useTheme } from "next-themes";
+
+/**
+ * Synchronizes the <meta name="theme-color"> tag with the current theme (light/dark).
+ * Ensures the browser UI (mobile address bar, etc.) matches the user's selected theme.
+ */
+export function ThemeSynchronizer() {
+  const { resolvedTheme } = useTheme();
+
+  useEffect(() => {
+    const themeColor = resolvedTheme === "dark" ? "#18181b" : "#f3f4f6";
+    let meta = document.querySelector("meta[name=theme-color]");
+    if (!meta) {
+      meta = document.createElement("meta");
+      meta.setAttribute("name", "theme-color");
+      document.head.appendChild(meta);
+    }
+    meta.setAttribute("content", themeColor);
+  }, [resolvedTheme]);
+
+  return null;
+}

+ 43 - 0
src/shared/styles/globals.css

@@ -48,6 +48,49 @@
   --color-accent-pink: #ff7eb6;
   --color-accent-orange: #ffedd5;
   --color-accent-red: #cf0026;
+
+  /* Light theme colors */
+  --color-base-100: oklch(98% 0.005 255);
+  --color-base-200: oklch(96% 0.005 255);
+  --color-base-300: oklch(93% 0.005 255);
+  --color-base-content: oklch(18% 0.01 255);
+  --color-primary-content: oklch(98% 0.005 255);
+  --color-secondary: oklch(90% 0.01 255);
+  --color-secondary-content: oklch(18% 0.01 255);
+  --color-accent-content: oklch(18% 0.01 255);
+  --color-neutral: oklch(95% 0.005 255);
+  --color-neutral-content: oklch(18% 0.01 255);
+  --color-info: oklch(90% 0.01 255);
+  --color-info-content: oklch(18% 0.01 255);
+  --color-success: oklch(90% 0.01 255);
+  --color-success-content: oklch(18% 0.01 255);
+  --color-warning: oklch(90% 0.01 255);
+  --color-warning-content: oklch(18% 0.01 255);
+  --color-error: oklch(90% 0.01 255);
+  --color-error-content: oklch(18% 0.01 255);
+}
+
+.dark {
+  --color-base-100: oklch(25.33% 0.016 252.42);
+  --color-base-200: oklch(23.26% 0.014 253.1);
+  --color-base-300: oklch(21.15% 0.012 254.09);
+  --color-base-content: oklch(97.807% 0.029 256.847);
+  --color-primary: oklch(58% 0.233 277.117);
+  --color-primary-content: oklch(96% 0.018 272.314);
+  --color-secondary: oklch(65% 0.241 354.308);
+  --color-secondary-content: oklch(94% 0.028 342.258);
+  --color-accent: oklch(77% 0.152 181.912);
+  --color-accent-content: oklch(38% 0.063 188.416);
+  --color-neutral: oklch(14% 0.005 285.823);
+  --color-neutral-content: oklch(92% 0.004 286.32);
+  --color-info: oklch(74% 0.16 232.661);
+  --color-info-content: oklch(29% 0.066 243.157);
+  --color-success: oklch(76% 0.177 163.223);
+  --color-success-content: oklch(37% 0.077 168.94);
+  --color-warning: oklch(82% 0.189 84.429);
+  --color-warning-content: oklch(41% 0.112 45.904);
+  --color-error: oklch(71% 0.194 13.428);
+  --color-error-content: oklch(27% 0.105 12.094);
 }
 
 @layer components {