From 329abee7d5dca4ad57d0de6e0111c6adda1c0d3e Mon Sep 17 00:00:00 2001 From: Dawson Toth Date: Thu, 21 May 2026 17:25:41 -0400 Subject: [PATCH 1/3] feat: Let user pick theme https://harperdb.atlassian.net/browse/STUDIO-218 --- index.html | 15 ++++++++++++++ src/App.tsx | 5 +++-- src/components/Navbar.tsx | 10 ++++++++++ src/components/ThemeToggle.tsx | 31 +++++++++++++++++++++++++++++ src/hooks/useTheme.tsx | 24 ++++++++++++++++++++++ src/index.css | 14 +++++-------- src/lib/storage/localStorageKeys.ts | 1 + 7 files changed, 89 insertions(+), 11 deletions(-) create mode 100644 src/components/ThemeToggle.tsx create mode 100644 src/hooks/useTheme.tsx diff --git a/index.html b/index.html index 802c11ff0..207915e02 100644 --- a/index.html +++ b/index.html @@ -6,6 +6,21 @@ Harper Fabric + + diff --git a/src/App.tsx b/src/App.tsx index dcac1c49d..8f2c204da 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,6 @@ import { AppRouted } from '@/AppRouted'; import { Toaster } from '@/components/ui/sonner'; +import { ThemeProvider } from '@/hooks/useTheme'; import { useDatadog } from '@/integrations/datadog/datadog'; import { useGTM } from '@/integrations/google/gtm'; import { useReo } from '@/integrations/reo/reo'; @@ -12,12 +13,12 @@ export function App() { useDatadog(); useGTM(); return ( - <> + {!import.meta.env.VITE_DISABLE_DEVTOOLS && } - + ); } diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index af45a2b9e..313b5a986 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -1,5 +1,6 @@ import { DiscordLogo } from '@/components/DiscordLogo'; import { MainLogo } from '@/components/MainLogo'; +import { ThemeToggle } from '@/components/ThemeToggle'; import { NavigationMenu } from '@/components/ui/navigation/NavigationMenu'; import { NavigationMenuItem } from '@/components/ui/navigation/NavigationMenuItem'; import { NavigationMenuLink } from '@/components/ui/navigation/NavigationMenuLink'; @@ -177,6 +178,9 @@ function AnonymousNav() { + + + @@ -216,6 +220,9 @@ function DesktopNav({ menuItems }: { menuItems: Array }) { ) : )} + + + @@ -278,6 +285,9 @@ function MobileNav({ menuItems }: { menuItems: Array }) { isMenuOpen ? 'block' : 'hidden' } md:hidden z-50 space-y-1 pb-3 bg-card border-b border-border dark:bg-black-dark dark:border-none absolute left-0 top-full w-full rounded-b-md`} > +
+ +
{menuItems.map(menuItem => isMenuGroup(menuItem) ? !!menuItem.items.length && ( diff --git a/src/components/ThemeToggle.tsx b/src/components/ThemeToggle.tsx new file mode 100644 index 000000000..4b20e1fce --- /dev/null +++ b/src/components/ThemeToggle.tsx @@ -0,0 +1,31 @@ +import { type Theme, useTheme } from '@/hooks/useTheme'; +import { MonitorIcon, MoonIcon, SunIcon } from 'lucide-react'; + +const OPTIONS: Array<{ value: Theme; icon: React.ReactNode; label: string }> = [ + { value: 'light', icon: , label: 'Light' }, + { value: 'system', icon: , label: 'System' }, + { value: 'dark', icon: , label: 'Dark' }, +]; + +export function ThemeToggle() { + const [theme, setTheme] = useTheme(); + return ( +
+ {OPTIONS.map(({ value, icon, label }) => ( + + ))} +
+ ); +} diff --git a/src/hooks/useTheme.tsx b/src/hooks/useTheme.tsx new file mode 100644 index 000000000..fe97ca5de --- /dev/null +++ b/src/hooks/useTheme.tsx @@ -0,0 +1,24 @@ +import { useLocalStorage } from '@/hooks/useLocalStorage'; +import { useSystemTheme } from '@/hooks/useSystemTheme'; +import { LocalStorageKeys } from '@/lib/storage/localStorageKeys'; +import { createContext, ReactNode, useContext, useEffect } from 'react'; + +export type Theme = 'system' | 'light' | 'dark'; + +const ThemeContext = createContext<[Theme, (t: Theme) => void]>(['system', () => {}]); + +export function ThemeProvider({ children }: { children: ReactNode }) { + const [theme, setTheme] = useLocalStorage(LocalStorageKeys.Theme, 'system'); + const systemTheme = useSystemTheme(); + + useEffect(() => { + const isDark = theme === 'dark' || (theme === 'system' && systemTheme === 'dark'); + document.documentElement.classList.toggle('dark', isDark); + }, [theme, systemTheme]); + + return {children}; +} + +export function useTheme() { + return useContext(ThemeContext); +} diff --git a/src/index.css b/src/index.css index 70c2e6199..0307d18eb 100644 --- a/src/index.css +++ b/src/index.css @@ -12,7 +12,7 @@ @plugin "tailwindcss-animate"; -@custom-variant dark (@media (prefers-color-scheme: dark)); +@custom-variant dark (&:where(.dark, .dark *)); :root { --white: hsl(0, 0%, 99.61%); /* #fefefe */ @@ -100,9 +100,8 @@ } -@media (prefers-color-scheme: dark) { - :root { - --background: var(--color-black-dark); /* Dark Black: #111111 */ +.dark { + --background: var(--color-black-dark); /* Dark Black: #111111 */ --foreground: hsl(0 0% 98%); --card: var(--color-grey-700); --card-foreground: var(--color-white); @@ -142,7 +141,6 @@ --chart-tooltip-fg: var(--popover-foreground); --shadow-deep: 0 2px 8px 0 rgba(0,0,0,0.4), 0 1px 2px 0 rgba(0,0,0,0.3); - } } @theme inline { @@ -316,8 +314,6 @@ background-size: cover; } -@media (prefers-color-scheme: light) { - .fabricSignupTextContainer { - filter: brightness(1.3) saturate(0.75); - } +html:not(.dark) .fabricSignupTextContainer { + filter: brightness(1.3) saturate(0.75); } diff --git a/src/lib/storage/localStorageKeys.ts b/src/lib/storage/localStorageKeys.ts index 0c1827ff8..91f02b994 100644 --- a/src/lib/storage/localStorageKeys.ts +++ b/src/lib/storage/localStorageKeys.ts @@ -3,4 +3,5 @@ export const enum LocalStorageKeys { 'ApplicationChatWidth' = 'ApplicationChatWidth', 'ChatAlwaysApprovedTools' = 'ChatAlwaysApprovedTools', 'SavedClusterState' = 'SavedClusterState', + 'Theme' = 'Theme', } From c9b644dfe11e60d76f330941802477d6c5a7b80b Mon Sep 17 00:00:00 2001 From: Dawson Toth Date: Thu, 21 May 2026 17:32:30 -0400 Subject: [PATCH 2/3] feat: Add some more subtle coloring --- src/features/auth/AuthLayout.tsx | 2 +- src/features/layouts/Dashboard.tsx | 2 +- src/index.css | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/features/auth/AuthLayout.tsx b/src/features/auth/AuthLayout.tsx index efd2987d8..740c1628f 100644 --- a/src/features/auth/AuthLayout.tsx +++ b/src/features/auth/AuthLayout.tsx @@ -4,7 +4,7 @@ import { Outlet } from '@tanstack/react-router'; export function AuthLayout() { return ( <> -
+
diff --git a/src/features/layouts/Dashboard.tsx b/src/features/layouts/Dashboard.tsx index a67e6a11b..be279c5cd 100644 --- a/src/features/layouts/Dashboard.tsx +++ b/src/features/layouts/Dashboard.tsx @@ -22,7 +22,7 @@ export function Dashboard() { return ( <> -
+
diff --git a/src/index.css b/src/index.css index 0307d18eb..8cd09e102 100644 --- a/src/index.css +++ b/src/index.css @@ -54,7 +54,7 @@ --purple-gradient: 90deg, var(--purple-500) 100%, var(--purple-500) 100%; --purple-dark-to-light-gradient: 45deg, var(--purple-200) 0%, var(--purple-100) 100%; - --background: hsl(0 0% 97%); /* Light grey page background */ + --background: hsl(250 30% 97%); /* Very light lavender page background */ --foreground: hsl(0 0% 9%); /* Near-black text */ --card: hsl(0 0% 100%); /* White cards */ --card-foreground: hsl(0 0% 9%); @@ -101,7 +101,7 @@ } .dark { - --background: var(--color-black-dark); /* Dark Black: #111111 */ + --background: hsl(250 20% 8%); /* Deep purple-tinted dark background */ --foreground: hsl(0 0% 98%); --card: var(--color-grey-700); --card-foreground: var(--color-white); From 0eea5c3fe8d14ce5d6c2bae9599dd51c0081002b Mon Sep 17 00:00:00 2001 From: Dawson Toth Date: Fri, 22 May 2026 11:59:11 -0400 Subject: [PATCH 3/3] refactor: Const instead of var --- index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.html b/index.html index 207915e02..bec75a1bd 100644 --- a/index.html +++ b/index.html @@ -10,7 +10,7 @@