Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,21 @@
<link rel="icon" type="dynamic-favicon" href="/favicon_purple.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Harper Fabric</title>
<!-- Set theme class before first paint to prevent flash of wrong theme -->
<script>
(function() {
Comment thread
dawsontoth marked this conversation as resolved.
try {
const t = JSON.parse(localStorage.getItem('Theme') || '"system"');
if (
t === 'dark'
|| (t === 'system'
&& window.matchMedia('(prefers-color-scheme: dark)').matches)
) {
document.documentElement.classList.add('dark');
}
} catch (e) {}
})();
</script>
</head>

<body>
Expand Down
5 changes: 3 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -12,12 +13,12 @@ export function App() {
useDatadog();
useGTM();
return (
<>
<ThemeProvider>
<QueryClientProvider client={queryClient}>
<AppRouted />
{!import.meta.env.VITE_DISABLE_DEVTOOLS && <ReactQueryDevtools buttonPosition="bottom-right" />}
</QueryClientProvider>
<Toaster richColors />
</>
</ThemeProvider>
);
}
10 changes: 10 additions & 0 deletions src/components/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -177,6 +178,9 @@ function AnonymousNav() {
</Link>
</NavigationMenuLink>
</NavigationMenuItem>
<NavigationMenuItem>
<ThemeToggle />
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuLink asChild>
<Link to="/sign-in" className="flex-row items-center" activeProps={activeLinkProps}>
Expand Down Expand Up @@ -216,6 +220,9 @@ function DesktopNav({ menuItems }: { menuItems: Array<MenuGroup | MenuItem> }) {
)
: <DesktopNavItem key={menuItem.text} menuItem={menuItem} />
)}
<NavigationMenuItem>
<ThemeToggle />
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>
</div>
Expand Down Expand Up @@ -278,6 +285,9 @@ function MobileNav({ menuItems }: { menuItems: Array<MenuGroup | MenuItem> }) {
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`}
>
<div className="flex justify-end px-3 pt-2">
<ThemeToggle />
</div>
{menuItems.map(menuItem =>
isMenuGroup(menuItem)
? !!menuItem.items.length && (
Expand Down
31 changes: 31 additions & 0 deletions src/components/ThemeToggle.tsx
Original file line number Diff line number Diff line change
@@ -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: <SunIcon className="size-4" />, label: 'Light' },
{ value: 'system', icon: <MonitorIcon className="size-4" />, label: 'System' },
{ value: 'dark', icon: <MoonIcon className="size-4" />, label: 'Dark' },
];

export function ThemeToggle() {
const [theme, setTheme] = useTheme();
return (
<div className="flex items-center bg-muted dark:bg-black rounded-xl p-0.5 gap-0.5">
{OPTIONS.map(({ value, icon, label }) => (
<button
key={value}
type="button"
title={label}
onClick={() => setTheme(value)}
className={`p-1.5 rounded-lg transition-colors ${
theme === value
? 'bg-background text-foreground shadow-xs'
: 'text-muted-foreground hover:text-foreground'
}`}
>
{icon}
</button>
))}
</div>
);
}
2 changes: 1 addition & 1 deletion src/features/auth/AuthLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Outlet } from '@tanstack/react-router';
export function AuthLayout() {
return (
<>
<header className="fixed top-0 z-40 w-full h-20 p-4 bg-white border-b border-border dark:bg-black-dark dark:border-black md:px-12">
<header className="fixed top-0 z-40 w-full h-20 p-4 bg-gradient-to-r from-violet-100 to-white border-b border-violet-200 dark:from-purple-950 dark:to-zinc-900 dark:border-purple-950 md:px-12">
<Navbar />
</header>
<div className="pt-20 h-screen grid grid-cols-1 md:grid-cols-2">
Expand Down
2 changes: 1 addition & 1 deletion src/features/layouts/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export function Dashboard() {

return (
<>
<header className="fixed top-0 z-40 w-full h-20 p-4 bg-white border-b border-border dark:bg-black-dark dark:border-black md:px-12">
<header className="fixed top-0 z-40 w-full h-20 p-4 bg-gradient-to-r from-violet-100 to-white border-b border-violet-200 dark:from-purple-950 dark:to-zinc-900 dark:border-purple-950 md:px-12">
<Navbar />
</header>
<main>
Expand Down
24 changes: 24 additions & 0 deletions src/hooks/useTheme.tsx
Original file line number Diff line number Diff line change
@@ -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<Theme>(LocalStorageKeys.Theme, 'system');
const systemTheme = useSystemTheme();

useEffect(() => {
const isDark = theme === 'dark' || (theme === 'system' && systemTheme === 'dark');
document.documentElement.classList.toggle('dark', isDark);
}, [theme, systemTheme]);

return <ThemeContext.Provider value={[theme, setTheme]}>{children}</ThemeContext.Provider>;
}

export function useTheme() {
return useContext(ThemeContext);
}
16 changes: 6 additions & 10 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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%);
Expand Down Expand Up @@ -100,9 +100,8 @@

}

@media (prefers-color-scheme: dark) {
:root {
--background: var(--color-black-dark); /* Dark Black: #111111 */
.dark {
--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);
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
}
1 change: 1 addition & 0 deletions src/lib/storage/localStorageKeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ export const enum LocalStorageKeys {
'ApplicationChatWidth' = 'ApplicationChatWidth',
'ChatAlwaysApprovedTools' = 'ChatAlwaysApprovedTools',
'SavedClusterState' = 'SavedClusterState',
'Theme' = 'Theme',
}