Skip to content

Commit

Permalink
perf: switch button
Browse files Browse the repository at this point in the history
  • Loading branch information
521xueweihan committed Aug 2, 2024
1 parent d6af00c commit 3b77f91
Show file tree
Hide file tree
Showing 7 changed files with 153 additions and 140 deletions.
77 changes: 0 additions & 77 deletions src/components/ThemeSwitch.tsx

This file was deleted.

2 changes: 1 addition & 1 deletion src/components/buttons/HeaderBtn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const HeaderBtn = (props: HeaderBtnProps) => {

return (
<Button
className='font-normal text-current hover:bg-transparent dark:text-gray-400 dark:hover:bg-gray-700'
className='font-normal text-current hover:bg-transparent dark:text-gray-300 dark:hover:bg-gray-700'
variant='ghost'
onClick={debounce(() => {
router.push(pathname);
Expand Down
109 changes: 56 additions & 53 deletions src/components/buttons/LanguageSwitcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,78 +2,88 @@
import { useRouter } from 'next/router';
import { useEffect, useRef, useState } from 'react';

const LanguageSwitcher = () => {
type LanguageSwitchProps = {
type?: string;
};

const LanguageSwitcher = (props: LanguageSwitchProps) => {
const router = useRouter();
const { locale, asPath } = router;
const [selectedLocale, setSelectedLocale] = useState(locale);
const [isOpen, setIsOpen] = useState(false);
const [isHovered, setIsHovered] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);
const timeoutRef = useRef<NodeJS.Timeout | null>(null);

useEffect(() => {
const storedLocale = localStorage.getItem('locale');
if (storedLocale && storedLocale !== locale) {
if (storedLocale) {
setSelectedLocale(storedLocale);
router.push(asPath, asPath, { locale: storedLocale });
} else {
const systemLocale = navigator.language.toLowerCase().startsWith('zh')
? 'zh'
: 'en';
setSelectedLocale(systemLocale);
localStorage.setItem('locale', systemLocale);
router.push(asPath, asPath, { locale: systemLocale });
}
}, []);

useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (
dropdownRef.current &&
!dropdownRef.current.contains(event.target as Node)
) {
setIsOpen(false);
}
};

document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [dropdownRef]);

const changeLanguage = (language: string) => {
localStorage.setItem('locale', language);
setSelectedLocale(language);
setIsOpen(false); // 关闭下拉菜单
setIsHovered(false);
router.push(asPath, asPath, { locale: language });
};

if (props.type === 'text') {
const isChinese = locale == 'en' ? false : true;
const buttonText = isChinese ? 'English' : '简体中文';
return (
<span onClick={() => changeLanguage(isChinese ? 'en' : 'zh')}>
{buttonText}
</span>
);
}

const handleMouseEnter = () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
setIsHovered(true);
};

const handleMouseLeave = () => {
timeoutRef.current = setTimeout(() => {
setIsHovered(false);
}, 300);
};

return (
<div className='relative inline-block text-left' ref={dropdownRef}>
<div
className='relative inline-block text-left'
ref={dropdownRef}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
<div>
<button
type='button'
className='inline-flex w-full justify-center rounded-md border border-gray-300 bg-white px-2 py-1.5 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2'
className='inline-flex h-8 w-full items-center justify-center rounded-md border border-gray-300 bg-white px-2 text-sm font-medium text-gray-700 shadow-sm transition-colors duration-200 hover:bg-gray-50 dark:border-gray-600 dark:bg-gray-900 dark:text-gray-300 dark:hover:bg-gray-700'
id='options-menu'
aria-haspopup='true'
aria-expanded={isOpen ? 'true' : 'false'}
onClick={() => setIsOpen(!isOpen)}
aria-expanded={isHovered ? 'true' : 'false'}
>
{selectedLocale === 'zh'
? '中文'
: selectedLocale === 'en'
? 'English'
: '日本語'}
<svg
className='-mr-1 ml-2 h-5 w-5'
xmlns='http://www.w3.org/2000/svg'
viewBox='0 0 20 20'
fill='currentColor'
aria-hidden='true'
>
<path
fillRule='evenodd'
d='M5.23 7.21a.75.75 0 011.06.02L10 10.939l3.71-3.71a.75.75 0 111.06 1.06l-4.25 4.25a.75.75 0 01-1.06 0l-4.25-4.25a.75.75 0 01.02-1.06z'
clipRule='evenodd'
/>
</svg>
{selectedLocale === 'en' ? 'EN' : '中文'}
</button>
</div>

{isOpen && (
<div className='absolute right-0 mt-2 w-28 origin-top-right rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none'>
{isHovered && (
<div
className='absolute right-0 mt-2 w-[110px] origin-top-right rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:bg-gray-800 dark:ring-gray-600'
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
<div
className='py-1'
role='menu'
Expand All @@ -82,25 +92,18 @@ const LanguageSwitcher = () => {
>
<button
onClick={() => changeLanguage('zh')}
className='block w-full px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-100'
className='block w-full px-4 py-2 text-left text-sm text-gray-700 transition-colors duration-200 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700'
role='menuitem'
>
🇨🇳 中文
</button>
<button
onClick={() => changeLanguage('en')}
className='block w-full px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-100'
className='block w-full px-4 py-2 text-left text-sm text-gray-700 transition-colors duration-200 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700'
role='menuitem'
>
🇺🇸 English
</button>
<button
onClick={() => changeLanguage('ja')}
className='block w-full px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-100'
role='menuitem'
>
🇯🇵 日本語
</button>
</div>
</div>
)}
Expand Down
81 changes: 81 additions & 0 deletions src/components/buttons/ThemeSwitcher.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { useEffect } from 'react';

import { toggleTheme, updateTheme } from '@/lib/theme';
import { useLoginContext } from '@/hooks/useLoginContext';

import { TranslationFunction } from '@/types/utils';

type ThemeSwitchProps = {
t?: TranslationFunction;
type?: string;
};

const ThemeSwitcher: React.FC<ThemeSwitchProps> = (props: ThemeSwitchProps) => {
const { theme, changeTheme } = useLoginContext();

useEffect(() => {
updateTheme();
}, []);

const onToggle = () => {
const newTheme = toggleTheme();
changeTheme(newTheme);
};

if (props.type === 'text' && props.t) {
return (
<span onClick={onToggle}>
{theme === 'light' ? props.t('theme.dark') : props.t('theme.light')}
</span>
);
}

return (
<button
type='button'
onClick={onToggle}
className='rounded-md p-2 transition-colors hover:bg-gray-200 dark:hover:bg-gray-700'
aria-label='Toggle theme'
>
{theme == 'light' ? (
<svg
className='h-4 w-4'
data-testid='geist-icon'
fill='none'
shapeRendering='geometricPrecision'
stroke='currentColor'
strokeLinecap='round'
strokeLinejoin='round'
strokeWidth='1.5'
viewBox='0 0 24 24'
>
<circle cx='12' cy='12' r='5'></circle>
<path d='M12 1v2'></path>
<path d='M12 21v2'></path>
<path d='M4.22 4.22l1.42 1.42'></path>
<path d='M18.36 18.36l1.42 1.42'></path>
<path d='M1 12h2'></path>
<path d='M21 12h2'></path>
<path d='M4.22 19.78l1.42-1.42'></path>
<path d='M18.36 5.64l1.42-1.42'></path>
</svg>
) : (
<svg
className='h-4 w-4'
data-testid='geist-icon'
fill='none'
shapeRendering='geometricPrecision'
stroke='currentColor'
strokeLinecap='round'
strokeLinejoin='round'
strokeWidth='1.5'
viewBox='0 0 24 24'
>
<path d='M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z'></path>
</svg>
)}
</button>
);
};

export default ThemeSwitcher;
8 changes: 6 additions & 2 deletions src/components/dropdown/AvatarWithDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import { useEffect, useState } from 'react';

import { useLoginContext } from '@/hooks/useLoginContext';

import LanguageSwitcher from '@/components/buttons/LanguageSwitcher';
import ThemeSwitcher from '@/components/buttons/ThemeSwitcher';
import CustomLink from '@/components/links/CustomLink';
import ThemeSwitch from '@/components/ThemeSwitch';

import { DEFAULT_AVATAR } from '@/utils/constants';

Expand Down Expand Up @@ -63,7 +64,10 @@ const AvatarWithDropdown = ({ t, className }: Props) => {
</CustomLink>

<div className='px-4 leading-8 active:bg-gray-100 dark:active:bg-gray-700'>
<ThemeSwitch type='text' t={t} />
<ThemeSwitcher type='text' t={t} />
</div>
<div className='px-4 leading-8 active:bg-gray-100 dark:active:bg-gray-700'>
<LanguageSwitcher type='text' />
</div>
<div
className='px-4 leading-8 active:bg-gray-100 dark:active:bg-gray-700'
Expand Down
6 changes: 4 additions & 2 deletions src/components/layout/ErrorPage.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import Link from 'next/link';
import { IoIosArrowForward } from 'react-icons/io';

import ThemeSwitch from '@/components/ThemeSwitch';
import LanguageSwitcher from '@/components/buttons/LanguageSwitcher';
import ThemeSwitcher from '@/components/buttons/ThemeSwitcher';

type Props = {
httpCode: number;
Expand All @@ -12,7 +13,8 @@ const ErrorPage = ({ t, httpCode }: Props) => {
return (
<main>
<div className='hidden'>
<ThemeSwitch />
<ThemeSwitcher />
<LanguageSwitcher />
</div>
<section className='min-h-screen bg-white dark:bg-gray-800'>
<div className='flex flex-col items-center justify-center'>
Expand Down
Loading

0 comments on commit 3b77f91

Please sign in to comment.