diff --git a/tenant-dashboard/src/app/globals.css b/tenant-dashboard/src/app/globals.css index 238c7bc..859f9c3 100644 --- a/tenant-dashboard/src/app/globals.css +++ b/tenant-dashboard/src/app/globals.css @@ -54,7 +54,19 @@ @apply border-border; } body { - @apply bg-background text-foreground; + @apply bg-background text-foreground transition-colors duration-300; + } + + /* Smooth theme transitions */ + *, *::before, *::after { + transition-property: background-color, border-color, color, fill, stroke; + transition-duration: 150ms; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + } + + /* Disable transitions during theme changes to prevent flash */ + .theme-transitioning * { + transition: none !important; } } diff --git a/tenant-dashboard/src/app/providers.tsx b/tenant-dashboard/src/app/providers.tsx index 988807a..7679d19 100644 --- a/tenant-dashboard/src/app/providers.tsx +++ b/tenant-dashboard/src/app/providers.tsx @@ -9,6 +9,7 @@ import { PerformanceProvider } from "@/components/performance/PerformanceProvide import { AuthProvider } from "@/lib/auth/auth-context"; import { WebSocketProvider } from "@/lib/realtime/WebSocketProvider"; import { QueryProvider } from "@/components/providers/query-provider"; +import { ThemeProvider } from "@/providers/ThemeProvider"; interface ProvidersProps { children: React.ReactNode; @@ -24,12 +25,13 @@ export function Providers({ children }: ProvidersProps) { console.error("Root error boundary triggered:", error, errorInfo); }} > - - - - {/* Temporarily disabled: */} - - {children} + + + + + {/* Temporarily disabled: */} + + {children} - {/* PWA Components */} - - - - {/* Temporarily disabled: */} - - - + {/* PWA Components */} + + + + {/* Temporarily disabled: */} + + + + ); } \ No newline at end of file diff --git a/tenant-dashboard/src/components/layout/Header.tsx b/tenant-dashboard/src/components/layout/Header.tsx index 587fa2d..6b83e97 100644 --- a/tenant-dashboard/src/components/layout/Header.tsx +++ b/tenant-dashboard/src/components/layout/Header.tsx @@ -2,6 +2,7 @@ import React from "react"; import Image from "next/image"; +import Link from "next/link"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { @@ -19,14 +20,13 @@ import { User, LogOut, HelpCircle, - Moon, - Sun, Menu, ChevronDown, } from "lucide-react"; import { cn, getInitials } from "@/lib/utils"; import { CommandPalette, useCommandPalette } from "@/components/design-system"; import { useResponsive, responsive } from "@/hooks/useResponsive"; +import { ThemeToggle } from "@/components/ui/ThemeToggle"; interface HeaderProps { onMenuToggle?: () => void; @@ -34,7 +34,6 @@ interface HeaderProps { } export function Header({ onMenuToggle, className }: HeaderProps) { - const [theme, setTheme] = React.useState<"light" | "dark">("light"); const { open, setOpen, CommandPalette: CommandPaletteComponent } = useCommandPalette(); const { isMobile, isTablet } = useResponsive(); const [notifications] = React.useState([ @@ -77,11 +76,6 @@ export function Header({ onMenuToggle, className }: HeaderProps) { avatar: null, }; - const toggleTheme = () => { - setTheme(theme === "light" ? "dark" : "light"); - // In real app, this would update the theme context - document.documentElement.classList.toggle("dark"); - }; return (
{/* Theme Toggle */} - + {/* Notifications */} @@ -302,13 +284,17 @@ export function Header({ onMenuToggle, className }: HeaderProps) { - - - Profile Settings + + + + Profile Settings + - - - Account Settings + + + + Settings + diff --git a/tenant-dashboard/src/components/settings/AppearanceSettings.tsx b/tenant-dashboard/src/components/settings/AppearanceSettings.tsx new file mode 100644 index 0000000..c586637 --- /dev/null +++ b/tenant-dashboard/src/components/settings/AppearanceSettings.tsx @@ -0,0 +1,78 @@ +'use client'; + +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Separator } from '@/components/ui/separator'; +import { ThemeToggleInline } from '@/components/ui/ThemeToggle'; +import { Palette, Monitor } from 'lucide-react'; + +export function AppearanceSettings() { + return ( +
+ + + + + Theme + + + Choose how the interface appears to you. The system option will follow your device's theme preference. + + + + + + + + + + + + Display + + + Additional display preferences and accessibility options. + + + +
+
+
+
High contrast mode
+
+ Increases color contrast for better readability +
+
+
+ Coming soon +
+
+ +
+
+
Compact mode
+
+ Reduces spacing for a more compact interface +
+
+
+ Coming soon +
+
+ +
+
+
Motion preference
+
+ Reduce motion and animations +
+
+
+ Coming soon +
+
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/tenant-dashboard/src/components/ui/ThemeToggle.tsx b/tenant-dashboard/src/components/ui/ThemeToggle.tsx new file mode 100644 index 0000000..74efff8 --- /dev/null +++ b/tenant-dashboard/src/components/ui/ThemeToggle.tsx @@ -0,0 +1,145 @@ +'use client'; + +import * as React from 'react'; +import { useTheme } from 'next-themes'; +import { Moon, Sun, Monitor, Check } from 'lucide-react'; + +import { Button } from '@/components/ui/button'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; + +export function ThemeToggle() { + const { theme, setTheme, themes } = useTheme(); + const [mounted, setMounted] = React.useState(false); + + // useEffect only runs on the client, so now we can safely show the UI + React.useEffect(() => { + setMounted(true); + }, []); + + if (!mounted) { + return ( + + ); + } + + const getThemeIcon = (themeName: string) => { + switch (themeName) { + case 'dark': + return ; + case 'light': + return ; + case 'system': + return ; + default: + return ; + } + }; + + const getThemeLabel = (themeName: string) => { + switch (themeName) { + case 'dark': + return 'Dark'; + case 'light': + return 'Light'; + case 'system': + return 'System'; + default: + return 'System'; + } + }; + + return ( + + + + + + {themes.map((themeName) => ( + setTheme(themeName)} + className="flex items-center justify-between cursor-pointer" + > +
+ {getThemeIcon(themeName)} + {getThemeLabel(themeName)} +
+ {theme === themeName && } +
+ ))} +
+
+ ); +} + +export function ThemeToggleInline() { + const { theme, setTheme } = useTheme(); + const [mounted, setMounted] = React.useState(false); + + React.useEffect(() => { + setMounted(true); + }, []); + + if (!mounted) { + return ( +
+ + Loading theme... +
+ ); + } + + const themes = [ + { value: 'light', label: 'Light', icon: Sun }, + { value: 'dark', label: 'Dark', icon: Moon }, + { value: 'system', label: 'System', icon: Monitor }, + ]; + + return ( +
+
+

Theme Preference

+

+ Choose how the interface looks. Select System to use your device preference. +

+
+ +
+ {themes.map(({ value, label, icon: Icon }) => ( + + ))} +
+
+ ); +} \ No newline at end of file diff --git a/tenant-dashboard/src/providers/ThemeProvider.tsx b/tenant-dashboard/src/providers/ThemeProvider.tsx new file mode 100644 index 0000000..46acc66 --- /dev/null +++ b/tenant-dashboard/src/providers/ThemeProvider.tsx @@ -0,0 +1,19 @@ +'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 ( + + {children} + + ); +} \ No newline at end of file