diff --git a/src/components/common/ModeToggle.tsx b/src/components/common/ModeToggle.tsx new file mode 100644 index 0000000..9669ca3 --- /dev/null +++ b/src/components/common/ModeToggle.tsx @@ -0,0 +1,34 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL + */ + +import { useTranslation } from 'react-i18next'; +import { Moon, Sun } from 'lucide-react'; + +import { Button } from '@/components/ui/button'; +import { useUIStore } from '@/stores/uiStore'; + +// Single-click light/dark toggle. Color themes and corner radius live on the +// Appearance settings page instead of a dropdown. +export function ModeToggle() { + const { t } = useTranslation(); + const theme = useUIStore((s) => s.theme); + const setTheme = useUIStore((s) => s.setTheme); + + return ( + + ); +} diff --git a/src/components/common/ThemeSwitcher.tsx b/src/components/common/ThemeSwitcher.tsx deleted file mode 100644 index 02f3993..0000000 --- a/src/components/common/ThemeSwitcher.tsx +++ /dev/null @@ -1,78 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL - */ - -import { useTranslation } from 'react-i18next'; -import { Moon, Palette, Sun, Square, Radius } from 'lucide-react'; - -import { Button } from '@/components/ui/button'; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuLabel, - DropdownMenuRadioGroup, - DropdownMenuRadioItem, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu'; -import { COLOR_THEMES, useUIStore, type ColorTheme, type Radius as RadiusValue, type Theme } from '@/stores/uiStore'; - -export function ThemeSwitcher() { - const { t } = useTranslation(); - const theme = useUIStore((s) => s.theme); - const setTheme = useUIStore((s) => s.setTheme); - const colorTheme = useUIStore((s) => s.colorTheme); - const setColorTheme = useUIStore((s) => s.setColorTheme); - const radius = useUIStore((s) => s.radius); - const setRadius = useUIStore((s) => s.setRadius); - - return ( - - - - - - {t('appearance.mode', 'Mode')} - setTheme(value as Theme)}> - - - {t('appearance.light', 'Light')} - - - - {t('appearance.dark', 'Dark')} - - - - {t('appearance.colorTheme', 'Color theme')} - setColorTheme(value as ColorTheme)}> - {COLOR_THEMES.map((entry) => ( - - - {t(entry.labelKey, entry.fallback)} - - ))} - - - {t('appearance.corners', 'Corners')} - setRadius(value as RadiusValue)}> - - - {t('appearance.rounded', 'Rounded')} - - - - {t('appearance.square', 'Square')} - - - - - ); -} diff --git a/src/components/layout/TopBar.tsx b/src/components/layout/TopBar.tsx index 52cd6f1..f2dd57e 100644 --- a/src/components/layout/TopBar.tsx +++ b/src/components/layout/TopBar.tsx @@ -20,7 +20,7 @@ import { DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import Logo from '@/components/common/Logo'; -import { ThemeSwitcher } from '@/components/common/ThemeSwitcher'; +import { ModeToggle } from '@/components/common/ModeToggle'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; import { EnterpriseUpsell } from '@/components/common/EnterpriseUpsell'; import { findFirstAccessibleLinkInLayout, findFirstVisibleLinkInLayout, visibleLayouts } from '@/lib/layout'; @@ -122,7 +122,7 @@ export function TopBar() { - + diff --git a/src/i18n/en.json b/src/i18n/en.json index 07977b9..d95925a 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -9,6 +9,8 @@ "mode": "Mode", "rounded": "Rounded", "square": "Square", + "switchToDark": "Switch to dark mode", + "switchToLight": "Switch to light mode", "theme": { "forest": "Forest", "ocean": "Ocean", diff --git a/src/pages/LoginPage.tsx b/src/pages/LoginPage.tsx index ae97e41..c56c575 100644 --- a/src/pages/LoginPage.tsx +++ b/src/pages/LoginPage.tsx @@ -10,7 +10,7 @@ import { useTranslation } from 'react-i18next'; import { ArrowRight, Loader2 } from 'lucide-react'; import Logo from '@/components/common/Logo'; -import { ThemeSwitcher } from '@/components/common/ThemeSwitcher'; +import { ModeToggle } from '@/components/common/ModeToggle'; import { useDocumentTitle } from '@/hooks/useDocumentTitle'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; @@ -46,7 +46,7 @@ export default function LoginPage() { return (
- +
diff --git a/src/pages/OAuthCallback.tsx b/src/pages/OAuthCallback.tsx index 028b5b0..2ce6fd7 100644 --- a/src/pages/OAuthCallback.tsx +++ b/src/pages/OAuthCallback.tsx @@ -10,7 +10,7 @@ import { useTranslation } from 'react-i18next'; import { Loader2 } from 'lucide-react'; import { useAuthStore } from '@/stores/authStore'; -import { ThemeSwitcher } from '@/components/common/ThemeSwitcher'; +import { ModeToggle } from '@/components/common/ModeToggle'; import { useDocumentTitle } from '@/hooks/useDocumentTitle'; import { getBasePath } from '@/lib/basePath'; import { exchangeCode, getStoredOAuthData, clearStoredOAuthData, getOAuthRedirectUri } from '@/services/auth/oauth'; @@ -87,7 +87,7 @@ export default function OAuthCallback() { return (
- +

@@ -104,7 +104,7 @@ export default function OAuthCallback() { return (

- +