Merge pull request #54 from ZLostTK/DarkMode-Theme

feat: Dark mode theme
This commit is contained in:
Developers Digest
2025-09-02 18:37:01 -04:00
committed by GitHub
11 changed files with 521 additions and 12 deletions
+2
View File
@@ -0,0 +1,2 @@
+36
View File
@@ -0,0 +1,36 @@
"use client"
import { useTheme } from "next-themes"
import { useEffect, useState } from "react"
export function ThemeLogo() {
const { theme } = useTheme()
const [mounted, setMounted] = useState(false)
useEffect(() => {
setMounted(true)
}, [])
if (!mounted) {
// Return light theme logo by default to avoid hydration mismatch
return (
<img
src="/firecrawl-logo-with-fire.webp"
alt="Firecrawl"
className="h-8 w-auto"
/>
)
}
const logoSrc = theme === "dark"
? "/firecrawl-logo-with-fire-dark.webp"
: "/firecrawl-logo-with-fire.webp"
return (
<img
src={logoSrc}
alt="Firecrawl"
className="h-8 w-auto"
/>
)
}
+9
View File
@@ -0,0 +1,9 @@
"use client"
import * as React from "react"
import { ThemeProvider as NextThemesProvider } from "next-themes"
import { type ThemeProviderProps } from "next-themes/dist/types"
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
}
+84
View File
@@ -0,0 +1,84 @@
"use client"
import * as React from "react"
import { useTheme } from "next-themes"
import { Button } from "@/components/ui/button"
export function ThemeToggle() {
const { setTheme, theme, resolvedTheme } = useTheme()
const [mounted, setMounted] = React.useState(false)
React.useEffect(() => {
setMounted(true)
}, [])
if (!mounted) {
return null
}
const toggleTheme = () => {
setTheme(resolvedTheme === "light" ? "dark" : "light")
}
return (
<Button
variant="outline"
size="icon"
onClick={toggleTheme}
className="rounded-full fixed bottom-4 right-4 z-[9999] bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-700 shadow-lg transition-all duration-200 hover:scale-110"
>
{resolvedTheme === "light" ? (
<MoonIcon className="h-[1.2rem] w-[1.2rem] transition-all duration-200" />
) : (
<SunIcon className="h-[1.2rem] w-[1.2rem] transition-all duration-200" />
)}
<span className="sr-only">Toggle theme</span>
</Button>
)
}
function SunIcon(props: React.SVGProps<SVGSVGElement>) {
return (
<svg
{...props}
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<circle cx="12" cy="12" r="4" />
<path d="M12 2v2" />
<path d="M12 20v2" />
<path d="m4.93 4.93 1.41 1.41" />
<path d="m17.66 17.66 1.41 1.41" />
<path d="M2 12h2" />
<path d="M20 12h2" />
<path d="m6.34 17.66-1.41 1.41" />
<path d="m19.07 4.93-1.41 1.41" />
</svg>
)
}
function MoonIcon(props: React.SVGProps<SVGSVGElement>) {
return (
<svg
{...props}
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z" />
</svg>
)
}
+357
View File
@@ -138,6 +138,352 @@
--radius: 0.5rem;
}
/* Dark theme overrides */
.dark {
--color-background: hsl(240 10% 3.9%);
--color-foreground: hsl(0 0% 98%);
--color-card: hsl(240 10% 3.9%);
--color-card-foreground: hsl(0 0% 98%);
--color-popover: hsl(240 10% 3.9%);
--color-popover-foreground: hsl(0 0% 98%);
--color-primary: hsl(0 0% 98%);
--color-primary-foreground: hsl(240 5.9% 10%);
--color-secondary: hsl(240 3.7% 15.9%);
--color-secondary-foreground: hsl(0 0% 98%);
--color-muted: hsl(240 3.7% 15.9%);
--color-muted-foreground: hsl(240 5% 64.9%);
--color-accent: hsl(240 3.7% 15.9%);
--color-accent-foreground: hsl(0 0% 98%);
--color-destructive: hsl(0 62.8% 30.6%);
--color-destructive-foreground: hsl(0 0% 98%);
--color-border: hsl(240 3.7% 15.9%);
--color-input: hsl(240 3.7% 15.9%);
--color-ring: hsl(240 4.9% 83.9%);
}
/* Dark mode styles for common elements */
.dark .bg-white {
background-color: hsl(240 10% 3.9%);
}
/* !important */
.dark .text-gray-900,
.dark .text-zinc-900 {
color: hsl(0 0% 98%);
}
/* Dark mode for specific color values */
.dark [class*="text-[#36322F]"] {
color: hsl(0 0% 98%);
}
.dark .text-gray-600,
.dark .text-zinc-500,
.dark .text-zinc-600 {
color: hsl(240 5% 64.9%);
}
.dark .bg-gray-50,
.dark .bg-gray-100 {
background-color: hsl(240 3.7% 15.9%);
}
.dark .border-gray-200,
.dark .border-zinc-300 {
border-color: hsl(240 3.7% 15.9%);
}
.dark .bg-gray-900 {
background-color: hsl(240 10% 3.9%);
}
/* Dark mode for specific Open Lovable elements */
.dark .bg-orange-400\/50,
.dark .bg-orange-300\/30,
.dark .bg-orange-200\/20 {
background-color: hsl(240 10% 3.9%);
}
.dark .bg-yellow-300\/40 {
background-color: hsl(240 10% 3.9%);
}
/* Dark mode for design style buttons */
.dark .p-3.rounded-lg.border.border-gray-200.bg-white {
background-color: hsl(240 3.7% 15.9%);
border-color: hsl(240 3.7% 15.9%);
color: hsl(0 0% 98%);
}
.dark .p-3.rounded-lg.border.border-gray-200.bg-white:hover {
background-color: hsl(240 3.7% 20%);
border-color: hsl(25 95% 53%);
}
.dark .p-3.rounded-lg.border.border-gray-200.bg-white .text-sm.font-medium {
color: hsl(0 0% 98%);
}
.dark .p-3.rounded-lg.border.border-gray-200.bg-white .text-xs.text-gray-500 {
color: hsl(240 5% 64.9%);
}
/* Dark mode for selected/active design style buttons */
.dark .p-3.rounded-lg.border.border-orange-200.bg-orange-50\/50 {
background-color: hsl(25 95% 53%);
border-color: hsl(25 95% 53%);
color: hsl(240 10% 3.9%);
}
.dark .p-3.rounded-lg.border.border-orange-200.bg-orange-50\/50 .text-sm.font-medium {
color: hsl(240 10% 3.9%);
}
.dark .p-3.rounded-lg.border.border-orange-200.bg-orange-50\/50 .text-xs.text-gray-500 {
color: hsl(240 10% 3.9%);
opacity: 0.8;
}
/* Dark mode for selected buttons with orange-400 border and orange-50 background */
.dark .p-3.rounded-lg.border.border-orange-400.bg-orange-50 {
background-color: hsl(240 3.7% 15.9%);
border-color: hsl(25 95% 53%);
border-width: 2px;
color: hsl(0 0% 98%);
box-shadow: 0 0 0 1px hsl(25 95% 53%), 0 4px 12px -1px rgba(0, 0, 0, 0.15);
}
.dark .p-3.rounded-lg.border.border-orange-400.bg-orange-50 .text-sm.font-medium {
color: hsl(0 0% 98%);
font-weight: 600;
}
.dark .p-3.rounded-lg.border.border-orange-400.bg-orange-50 .text-xs.text-gray-500 {
color: hsl(240 5% 64.9%);
font-weight: 500;
}
/* Light mode improvements for selected buttons */
.p-3.rounded-lg.border.border-orange-400.bg-orange-50 {
background-color: hsl(0 0% 98%);
border-color: hsl(25 95% 53%);
border-width: 2px;
color: hsl(240 10% 3.9%);
box-shadow: 0 0 0 1px hsl(25 95% 53%), 0 4px 12px -1px rgba(0, 0, 0, 0.1);
}
.p-3.rounded-lg.border.border-orange-400.bg-orange-50 .text-sm.font-medium {
color: hsl(240 10% 3.9%);
font-weight: 600;
}
.p-3.rounded-lg.border.border-orange-400.bg-orange-50 .text-xs.text-gray-500 {
color: hsl(240 5% 40%);
font-weight: 500;
}
/* Dark mode for hover states on design style buttons */
.dark .p-3.rounded-lg.border.border-gray-200.bg-white:hover {
background-color: hsl(25 95% 53%);
border-color: hsl(25 95% 53%);
color: hsl(240 10% 3.9%);
}
.dark .p-3.rounded-lg.border.border-gray-200.bg-white:hover .text-sm.font-medium {
color: hsl(240 10% 3.9%);
}
.dark .p-3.rounded-lg.border.border-gray-200.bg-white:hover .text-xs.text-gray-500 {
color: hsl(240 10% 3.9%);
opacity: 0.8;
}
/* Dark mode for dropdowns and selectors */
.dark .bg-white {
background-color: hsl(240 10% 3.9%);
}
/* Dark mode for design container */
.dark .bg-white\/80.backdrop-blur-sm.border.border-gray-200.rounded-xl.p-4.shadow-sm {
background-color: hsl(240 10% 3.9% / 0.9);
border-color: hsl(240 3.7% 15.9%);
backdrop-filter: blur(12px);
box-shadow: 0 8px 32px -4px rgba(0, 0, 0, 0.3), 0 2px 8px -2px rgba(0, 0, 0, 0.1);
}
/* Light mode improvements for design container */
.bg-white\/80.backdrop-blur-sm.border.border-gray-200.rounded-xl.p-4.shadow-sm {
background-color: hsl(0 0% 100% / 0.9);
border-color: hsl(240 5.9% 90%);
backdrop-filter: blur(12px);
box-shadow: 0 8px 32px -4px rgba(0, 0, 0, 0.1), 0 2px 8px -2px rgba(0, 0, 0, 0.05);
}
/* Dark mode for loading spinner and text */
.dark .w-16.h-16.border-4.border-orange-200.border-t-orange-500.rounded-full.animate-spin.mx-auto {
border-color: hsl(25 95% 53%);
border-top-color: hsl(25 95% 70%);
}
.dark .text-xl.font-semibold.text-gray-800 {
color: hsl(0 0% 98%);
}
.dark .text-gray-600.text-sm {
color: hsl(240 5% 64.9%);
}
/* Dark mode for file explorer */
.dark .flex-1.overflow-y-auto.p-2.scrollbar-hide {
background-color: hsl(240 10% 3.9%);
}
.dark .text-sm {
color: hsl(0 0% 98%);
}
.dark .flex.items-center.gap-1.py-1.px-2.hover\:bg-gray-100.rounded.cursor-pointer.text-gray-700 {
color: hsl(0 0% 98%);
}
.dark .flex.items-center.gap-1.py-1.px-2.hover\:bg-gray-100.rounded.cursor-pointer.text-gray-700:hover {
background-color: hsl(240 3.7% 15.9%);
}
/* Dark mode for file items */
.dark .text-xs.flex.items-center.gap-1 {
color: hsl(0 0% 98%);
}
.dark .text-xs.flex.items-center.gap-1:hover {
color: hsl(25 95% 70%);
background-color: hsl(240 3.7% 15.9%);
border-radius: 0.375rem;
padding: 0.25rem 0.5rem;
transition: all 0.2s ease;
}
/* Light mode improvements for file items */
.text-xs.flex.items-center.gap-1 {
color: hsl(240 10% 3.9%);
transition: all 0.2s ease;
}
/* Theme toggle button improvements */
.dark .rounded-full.fixed.bottom-4.right-4.z-\[9999\].bg-white.dark\:bg-gray-800.border-gray-300.dark\:border-gray-600.text-gray-700.dark\:text-gray-200.hover\:bg-gray-50.dark\:hover\:bg-gray-700.shadow-lg.transition-all.duration-200.hover\:scale-110 {
background-color: hsl(240 10% 3.9%);
border-color: hsl(240 3.7% 15.9%);
color: hsl(0 0% 98%);
box-shadow: 0 10px 25px -3px rgba(0, 0, 0, 0.3), 0 4px 6px -2px rgba(0, 0, 0, 0.1);
}
.dark .rounded-full.fixed.bottom-4.right-4.z-\[9999\].bg-white.dark\:bg-gray-800.border-gray-300.dark\:border-gray-600.text-gray-700.dark\:text-gray-200.hover\:bg-gray-50.dark\:hover\:bg-gray-700.shadow-lg.transition-all.duration-200.hover\:scale-110:hover {
background-color: hsl(240 3.7% 15.9%);
border-color: hsl(25 95% 53%);
transform: scale(1.1);
}
/* Light mode theme toggle improvements */
.rounded-full.fixed.bottom-4.right-4.z-\[9999\].bg-white.dark\:bg-gray-800.border-gray-300.dark\:border-gray-600.text-gray-700.dark\:text-gray-200.hover\:bg-gray-50.dark\:hover\:bg-gray-700.shadow-lg.transition-all.duration-200.hover\:scale-110 {
box-shadow: 0 10px 25px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
.rounded-full.fixed.bottom-4.right-4.z-\[9999\].bg-white.dark\:bg-gray-800.border-gray-300.dark\:border-gray-600.text-gray-700.dark\:text-gray-200.hover\:bg-gray-50.dark\:hover\:bg-gray-700.shadow-lg.transition-all.duration-200.hover\:scale-110:hover {
border-color: hsl(25 95% 53%);
transform: scale(1.1);
}
.text-xs.flex.items-center.gap-1:hover {
color: hsl(25 95% 53%);
background-color: hsl(25 95% 95%);
border-radius: 0.375rem;
padding: 0.25rem 0.5rem;
}
/* Light mode improvements for loading elements */
.w-16.h-16.border-4.border-orange-200.border-t-orange-500.rounded-full.animate-spin.mx-auto {
border-color: hsl(25 95% 53%);
border-top-color: hsl(25 95% 70%);
box-shadow: 0 0 20px rgba(251, 146, 60, 0.3);
}
.text-xl.font-semibold.text-gray-800 {
color: hsl(240 10% 3.9%);
}
.text-gray-600.text-sm {
color: hsl(240 5% 40%);
}
.dark .text-black {
color: hsl(0 0% 98%);
}
.dark .hover\:bg-gray-50:hover {
background-color: hsl(240 3.7% 15.9%);
}
.dark .hover\:bg-gray-100:hover {
background-color: hsl(240 3.7% 15.9%);
}
/* Dark mode for select elements */
.dark select {
background-color: hsl(240 10% 3.9%);
color: hsl(0 0% 98%);
border-color: hsl(240 3.7% 15.9%);
}
.dark select option {
background-color: hsl(240 10% 3.9%);
color: hsl(0 0% 98%);
}
.dark select option:hover {
background-color: hsl(240 3.7% 15.9%);
}
/* Dark mode for specific Open Lovable elements */
.dark .bg-card {
background-color: hsl(240 10% 3.9%);
}
.dark .border-border {
border-color: hsl(240 3.7% 15.9%);
}
.dark .text-black {
color: hsl(0 0% 98%);
}
.dark .hover\:text-gray-700:hover {
color: hsl(0 0% 98%);
}
/* Dark mode for page background */
.dark .bg-background {
background-color: hsl(240 10% 3.9%);
}
.dark .text-foreground {
color: hsl(0 0% 98%);
}
/* Dark mode for specific backgrounds */
.dark .bg-gray-50 {
background-color: hsl(240 10% 3.9%);
}
.dark .bg-gray-100 {
background-color: hsl(240 10% 3.9%);
}
.dark .bg-gray-900 {
background-color: hsl(240 10% 3.9%);
}
@layer utilities {
/* Hide scrollbar for Chrome, Safari and Opera */
.scrollbar-hide::-webkit-scrollbar {
@@ -169,6 +515,17 @@
background-color: theme('colors.background');
color: theme('colors.foreground');
}
/* Dark mode overrides for common elements */
.dark {
background-color: theme('colors.background');
color: theme('colors.foreground');
}
/* Ensure dark mode applies to all elements */
.dark * {
border-color: theme('colors.border');
}
}
@layer utilities {
+10 -2
View File
@@ -1,6 +1,7 @@
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import { ThemeProvider } from "@/app/components/theme-provider";
const inter = Inter({ subsets: ["latin"] });
@@ -15,9 +16,16 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
<html lang="en">
<html lang="en" suppressHydrationWarning>
<body className={inter.className}>
{children}
<ThemeProvider
attribute="class"
defaultTheme="light"
enableSystem
disableTransitionOnChange
>
{children}
</ThemeProvider>
</body>
</html>
);
+7 -10
View File
@@ -22,6 +22,8 @@ import {
} from '@/lib/icons';
import { motion, AnimatePresence } from 'framer-motion';
import CodeApplicationProgress, { type CodeApplicationState } from '@/components/CodeApplicationProgress';
import { ThemeToggle } from '@/app/components/theme-toggle';
import { ThemeLogo } from '@/app/components/theme-logo';
interface SandboxData {
sandboxId: string;
@@ -2739,6 +2741,9 @@ Focus on the key sections and content, making it clean and modern.`;
return (
<div className="font-sans bg-background text-foreground h-screen flex flex-col">
{/* Theme Toggle */}
<ThemeToggle />
{/* Home Screen Overlay */}
{showHomeScreen && (
<div className={`fixed inset-0 z-50 transition-opacity duration-500 ${homeScreenFading ? 'opacity-0' : 'opacity-100'}`}>
@@ -2786,11 +2791,7 @@ Focus on the key sections and content, making it clean and modern.`;
{/* Header */}
<div className="absolute top-0 left-0 right-0 z-20 px-6 py-4 flex items-center justify-between animate-[fadeIn_0.8s_ease-out]">
<img
src="/firecrawl-logo-with-fire.webp"
alt="Firecrawl"
className="h-8 w-auto"
/>
<ThemeLogo />
<a
href="https://github.com/mendableai/open-lovable"
target="_blank"
@@ -3003,11 +3004,7 @@ Focus on the key sections and content, making it clean and modern.`;
<div className="bg-card px-4 py-4 border-b border-border flex items-center justify-between">
<div className="flex items-center gap-4">
<img
src="/firecrawl-logo-with-fire.webp"
alt="Firecrawl"
className="h-8 w-auto"
/>
<ThemeLogo />
</div>
<div className="flex items-center gap-2">
{/* Model Selector - Left side */}
+1
View File
@@ -19,6 +19,7 @@ const buttonVariants = cva(
size: {
default: "h-10 px-4 py-2",
sm: "h-8 px-3 py-1 text-sm",
icon: "h-8 w-8 p-0",
lg: "h-12 px-6 py-3",
},
},
+1
View File
@@ -34,6 +34,7 @@
"groq-sdk": "^0.29.0",
"lucide-react": "^0.532.0",
"next": "15.4.3",
"next-themes": "^0.4.6",
"react": "19.1.0",
"react-dom": "19.1.0",
"react-icons": "^5.5.0",
+14
View File
@@ -71,6 +71,9 @@ importers:
next:
specifier: 15.4.3
version: 15.4.3(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
next-themes:
specifier: ^0.4.6
version: 0.4.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
react:
specifier: 19.1.0
version: 19.1.0
@@ -1941,6 +1944,12 @@ packages:
resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
engines: {node: '>= 0.6'}
next-themes@0.4.6:
resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==}
peerDependencies:
react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
next@15.4.3:
resolution: {integrity: sha512-uW7Qe6poVasNIE1X382nI29oxSdFJzjQzTgJFLD43MxyPfGKKxCMySllhBpvqr48f58Om+tLMivzRwBpXEytvA==}
engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0}
@@ -4421,6 +4430,11 @@ snapshots:
negotiator@1.0.0: {}
next-themes@0.4.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
dependencies:
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
next@15.4.3(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
dependencies:
'@next/env': 15.4.3
Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB