diff --git a/web/app/components/base/select/locale-signin.tsx b/web/app/components/base/select/locale-signin.tsx deleted file mode 100644 index 046a76a5d4..0000000000 --- a/web/app/components/base/select/locale-signin.tsx +++ /dev/null @@ -1,64 +0,0 @@ -'use client' -import { Menu, MenuButton, MenuItem, MenuItems, Transition } from '@headlessui/react' -import { GlobeAltIcon } from '@heroicons/react/24/outline' -import { Fragment } from 'react' - -type ISelectProps = { - items: Array<{ value: string, name: string }> - value?: string - className?: string - onChange?: (value: string) => void -} - -export default function LocaleSigninSelect({ - items, - value, - onChange, -}: ISelectProps) { - const item = items.filter(item => item.value === value)[0] - - return ( -
- -
- - -
- - -
- {items.map((item) => { - return ( - - - - ) - })} - -
- -
-
-
-
- ) -} diff --git a/web/app/components/base/select/__tests__/locale-signin.spec.tsx b/web/app/signin/__tests__/_locale-menu.spec.tsx similarity index 69% rename from web/app/components/base/select/__tests__/locale-signin.spec.tsx rename to web/app/signin/__tests__/_locale-menu.spec.tsx index 1eca2b1aed..e43587033f 100644 --- a/web/app/components/base/select/__tests__/locale-signin.spec.tsx +++ b/web/app/signin/__tests__/_locale-menu.spec.tsx @@ -1,6 +1,6 @@ import { render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import LocaleSigninSelect from '../locale-signin' +import LocaleMenu from '../_locale-menu' const localeItems = [ { value: 'en-US', name: 'English (US)' }, @@ -8,16 +8,15 @@ const localeItems = [ { value: 'ja-JP', name: '日本語' }, ] -describe('LocaleSigninSelect', () => { +describe('LocaleMenu', () => { beforeEach(() => { vi.clearAllMocks() }) - // Rendering behavior for selected value and fallback state. describe('Rendering', () => { it('should render selected locale name when value matches an item', () => { render( - { it('should render trigger without selected label when value is not found', () => { render( - { }) }) - // Menu interactions and callback behavior. describe('User Interactions', () => { it('should call onChange with selected locale value when clicking an option', async () => { const user = userEvent.setup() const onChange = vi.fn() render( - { ) await user.click(screen.getByRole('button', { name: /english \(us\)/i })) - await user.click(screen.getByRole('menuitem', { name: '日本語' })) + await user.click(screen.getByRole('menuitemradio', { name: '日本語' })) - expect(onChange).toHaveBeenCalledWith('ja-JP') + expect(onChange).toHaveBeenCalledWith('ja-JP', expect.anything()) }) it('should render all locale options when menu is opened', async () => { const user = userEvent.setup() render( - { await user.click(screen.getByRole('button', { name: /english \(us\)/i })) - expect(screen.getByRole('menuitem', { name: 'English (US)' })).toBeInTheDocument() - expect(screen.getByRole('menuitem', { name: '简体中文' })).toBeInTheDocument() - expect(screen.getByRole('menuitem', { name: '日本語' })).toBeInTheDocument() + expect(screen.getByRole('menuitemradio', { name: 'English (US)' })).toBeInTheDocument() + expect(screen.getByRole('menuitemradio', { name: '简体中文' })).toBeInTheDocument() + expect(screen.getByRole('menuitemradio', { name: '日本語' })).toBeInTheDocument() }) }) - // Edge behavior for missing callback and empty data. describe('Edge Cases', () => { it('should not throw when onChange is undefined and option is selected', async () => { const user = userEvent.setup() render( - , ) await user.click(screen.getByRole('button', { name: /english \(us\)/i })) - await user.click(screen.getByRole('menuitem', { name: '简体中文' })) - // No assertion needed — test verifies no exception is thrown during selection without onChange. + await user.click(screen.getByRole('menuitemradio', { name: '简体中文' })) + + expect(screen.getByRole('menuitemradio', { name: '简体中文' })).toBeInTheDocument() }) it('should render no options when items are empty', async () => { const user = userEvent.setup() render( - { ) await user.click(screen.getByRole('button')) - expect(screen.queryAllByRole('menuitem')).toHaveLength(0) + + expect(screen.queryAllByRole('menuitemradio')).toHaveLength(0) }) }) }) diff --git a/web/app/signin/_header.tsx b/web/app/signin/_header.tsx index 0b424d45c2..5b638b4208 100644 --- a/web/app/signin/_header.tsx +++ b/web/app/signin/_header.tsx @@ -2,12 +2,12 @@ import type { Locale } from '@/i18n-config' import { useSuspenseQuery } from '@tanstack/react-query' import Divider from '@/app/components/base/divider' -import LocaleSigninSelect from '@/app/components/base/select/locale-signin' import { useLocale } from '@/context/i18n' import { setLocaleOnClient } from '@/i18n-config' import { languages } from '@/i18n-config/language' import dynamic from '@/next/dynamic' import { systemFeaturesQueryOptions } from '@/service/system-features' +import LocaleMenu from './_locale-menu' // Avoid rendering the logo and theme selector on the server const DifyLogo = dynamic(() => import('@/app/components/base/logo/dify-logo'), { @@ -35,7 +35,7 @@ const Header = () => { ) : }
- item.supported)} onChange={(value) => { diff --git a/web/app/signin/_locale-menu.tsx b/web/app/signin/_locale-menu.tsx new file mode 100644 index 0000000000..be63286b37 --- /dev/null +++ b/web/app/signin/_locale-menu.tsx @@ -0,0 +1,64 @@ +'use client' + +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, + DropdownMenuRadioItemIndicator, + DropdownMenuTrigger, +} from '@langgenius/dify-ui/dropdown-menu' + +type LocaleMenuProps = { + items: Array<{ value: string, name: string }> + value?: string + onChange?: (value: string) => void +} + +export default function LocaleMenu({ + items, + value, + onChange, +}: LocaleMenuProps) { + const selectedItem = items.find(item => item.value === value) + + return ( +
+ +
+
+ + )} + > + +
+
+ + + {items.map(item => ( + + {item.name} + + + ))} + + +
+
+ ) +}