diff --git a/web/app/components/base/theme-selector.tsx b/web/app/components/base/theme-selector.tsx new file mode 100644 index 0000000000..8dfe1d2602 --- /dev/null +++ b/web/app/components/base/theme-selector.tsx @@ -0,0 +1,97 @@ +'use client' + +import { useState } from 'react' +import { + RiCheckLine, + RiComputerLine, + RiMoonLine, + RiSunLine, +} from '@remixicon/react' +import { useTranslation } from 'react-i18next' +import { useTheme } from 'next-themes' +import ActionButton from '@/app/components/base/action-button' +import { + PortalToFollowElem, + PortalToFollowElemContent, + PortalToFollowElemTrigger, +} from '@/app/components/base/portal-to-follow-elem' + +export type Theme = 'light' | 'dark' | 'system' + +export default function ThemeSelector() { + const { t } = useTranslation() + const { theme, setTheme } = useTheme() + const [open, setOpen] = useState(false) + + const handleThemeChange = (newTheme: Theme) => { + setTheme(newTheme) + setOpen(false) + } + + const getCurrentIcon = () => { + switch (theme) { + case 'light': return + case 'dark': return + default: return + } + } + + return ( + + setOpen(!open)} + > + + {getCurrentIcon()} + + + +
+ + + +
+
+
+ ) +} diff --git a/web/app/components/base/theme-switcher.tsx b/web/app/components/base/theme-switcher.tsx new file mode 100644 index 0000000000..902d064a66 --- /dev/null +++ b/web/app/components/base/theme-switcher.tsx @@ -0,0 +1,58 @@ +'use client' +import { + RiComputerLine, + RiMoonLine, + RiSunLine, +} from '@remixicon/react' +import { useTheme } from 'next-themes' +import cn from '@/utils/classnames' + +export type Theme = 'light' | 'dark' | 'system' + +export default function ThemeSwitcher() { + const { theme, setTheme } = useTheme() + + const handleThemeChange = (newTheme: Theme) => { + setTheme(newTheme) + } + + return ( +
+
handleThemeChange('system')} + > +
+ +
+
+
+
handleThemeChange('light')} + > +
+ +
+
+
+
handleThemeChange('dark')} + > +
+ +
+
+
+ ) +} diff --git a/web/app/components/header/account-dropdown/index.tsx b/web/app/components/header/account-dropdown/index.tsx index 66b61d7ec1..88bee4dd51 100644 --- a/web/app/components/header/account-dropdown/index.tsx +++ b/web/app/components/header/account-dropdown/index.tsx @@ -14,6 +14,7 @@ import { RiMap2Line, RiSettings3Line, RiStarLine, + RiTShirt2Line, } from '@remixicon/react' import Link from 'next/link' import { Menu, MenuButton, MenuItem, MenuItems, Transition } from '@headlessui/react' @@ -25,6 +26,7 @@ import Compliance from './compliance' import PremiumBadge from '@/app/components/base/premium-badge' import I18n from '@/context/i18n' import Avatar from '@/app/components/base/avatar' +import ThemeSwitcher from '@/app/components/base/theme-switcher' import { logout } from '@/service/common' import AppContext, { useAppContext } from '@/context/app-context' import { useProviderContext } from '@/context/provider-context' @@ -83,8 +85,8 @@ export default function AppSelector() { @@ -189,6 +191,15 @@ export default function AppSelector() { ) } + +
+
+ +
{t('common.theme.theme')}
+ +
+
+
handleLogout()}>
= ({
+
+
+
{t('common.theme.theme')}
+ +
+
+
{data?.privacy_policy && ( diff --git a/web/app/layout.tsx b/web/app/layout.tsx index 6b5618f217..2143c4b0a3 100644 --- a/web/app/layout.tsx +++ b/web/app/layout.tsx @@ -60,8 +60,7 @@ const LocaleLayout = async ({ diff --git a/web/app/signin/_header.tsx b/web/app/signin/_header.tsx index c7b1e67092..d2e3236d1d 100644 --- a/web/app/signin/_header.tsx +++ b/web/app/signin/_header.tsx @@ -2,6 +2,8 @@ import React from 'react' import { useContext } from 'use-context-selector' import Select from '@/app/components/base/select/locale' +import ThemeSelector from '@/app/components/base/theme-selector' +import Divider from '@/app/components/base/divider' import { languages } from '@/i18n/language' import type { Locale } from '@/i18n' import I18n from '@/context/i18n' @@ -10,17 +12,22 @@ import LogoSite from '@/app/components/base/logo/logo-site' const Header = () => { const { locale, setLocaleOnClient } = useContext(I18n) - return
- - item.supported)} + onChange={(value) => { + setLocaleOnClient(value as Locale) + }} + /> + + +
+
+ ) } export default Header diff --git a/web/i18n/en-US/common.ts b/web/i18n/en-US/common.ts index 0811ba73ff..66949f7276 100644 --- a/web/i18n/en-US/common.ts +++ b/web/i18n/en-US/common.ts @@ -1,4 +1,10 @@ const translation = { + theme: { + theme: 'Theme', + light: 'light', + dark: 'dark', + auto: 'system', + }, api: { success: 'Success', actionSuccess: 'Action succeeded', diff --git a/web/i18n/ja-JP/common.ts b/web/i18n/ja-JP/common.ts index 8a47801f58..1c31359485 100644 --- a/web/i18n/ja-JP/common.ts +++ b/web/i18n/ja-JP/common.ts @@ -1,4 +1,10 @@ const translation = { + theme: { + theme: 'テーマ', + light: '明るい', + dark: '暗い', + auto: 'システム', + }, api: { success: '成功', actionSuccess: 'アクションが成功しました', diff --git a/web/i18n/zh-Hans/common.ts b/web/i18n/zh-Hans/common.ts index 9c822885e0..c2d43e21a0 100644 --- a/web/i18n/zh-Hans/common.ts +++ b/web/i18n/zh-Hans/common.ts @@ -1,4 +1,10 @@ const translation = { + theme: { + theme: '主题', + light: '浅色', + dark: '深色', + auto: '自动', + }, api: { success: '成功', actionSuccess: '操作成功',