diff --git a/packages/frontend/core/src/mobile/dialogs/setting/dropdown-select.css.ts b/packages/frontend/core/src/mobile/dialogs/setting/dropdown-select.css.ts index 7a55719be663f..18fed66372985 100644 --- a/packages/frontend/core/src/mobile/dialogs/setting/dropdown-select.css.ts +++ b/packages/frontend/core/src/mobile/dialogs/setting/dropdown-select.css.ts @@ -6,6 +6,7 @@ export const root = style({ display: 'flex', alignItems: 'center', gap: 8, + position: 'relative', }); export const label = style([ bodyRegular, @@ -15,3 +16,11 @@ export const icon = style({ fontSize: 24, color: cssVarV2('icon/primary'), }); +export const nativeSelect = style({ + position: 'absolute', + top: 0, + left: 0, + width: '100%', + height: '100%', + opacity: 0, +}); diff --git a/packages/frontend/core/src/mobile/dialogs/setting/dropdown-select.tsx b/packages/frontend/core/src/mobile/dialogs/setting/dropdown-select.tsx index a73f66b389c09..d94ea530aded5 100644 --- a/packages/frontend/core/src/mobile/dialogs/setting/dropdown-select.tsx +++ b/packages/frontend/core/src/mobile/dialogs/setting/dropdown-select.tsx @@ -5,7 +5,9 @@ import { type CSSProperties, type HTMLAttributes, type ReactNode, + useCallback, useMemo, + useRef, } from 'react'; import * as styles from './dropdown-select.css'; @@ -28,6 +30,7 @@ export interface SettingDropdownSelectProps< ) => void; emitValue?: E; menuOptions?: Omit; + native?: boolean; } export const SettingDropdownSelect = < @@ -40,12 +43,26 @@ export const SettingDropdownSelect = < onChange, className, menuOptions, + native = true, ...attrs }: SettingDropdownSelectProps) => { const selectedItem = useMemo( () => options.find(opt => opt.value === value), [options, value] ); + + if (native) { + return ( + + ); + } + return ( ( @@ -76,3 +93,59 @@ export const SettingDropdownSelect = < ); }; + +export const NativeSettingDropdownSelect = < + V extends string = string, + E extends boolean | undefined = true, +>({ + options = [], + value, + emitValue = true, + onChange, + className, + ...attrs +}: Omit, 'native'>) => { + const nativeSelectRef = useRef(null); + const selectedItem = useMemo( + () => options.find(opt => opt.value === value), + [options, value] + ); + + const handleChange = useCallback( + (e: React.ChangeEvent) => { + const value = e.target.value; + const opt = options.find(opt => opt.value === value); + if (emitValue) { + onChange?.(e.target.value as any); + } else { + onChange?.(opt as any); + } + }, + [emitValue, onChange, options] + ); + + return ( +
+ {selectedItem?.label ?? ''} + + + +
+ ); +}; diff --git a/tests/affine-mobile/e2e/settings.spec.ts b/tests/affine-mobile/e2e/settings.spec.ts index 5110ecf89cbaa..86d1d3c642157 100644 --- a/tests/affine-mobile/e2e/settings.spec.ts +++ b/tests/affine-mobile/e2e/settings.spec.ts @@ -13,48 +13,15 @@ test('can open settings', async ({ page }) => { test('can change theme', async ({ page }) => { await openSettings(page); - await page + const select = page .getByTestId('setting-row') .filter({ hasText: 'Color mode', }) - .getByTestId('dropdown-select-trigger') - .click(); + .getByTestId('native-dropdown-select-trigger'); - await expect( - page.getByRole('dialog').filter({ - has: page.getByRole('menuitem', { name: 'Light' }), - }) - ).toBeVisible(); - - await page.getByRole('menuitem', { name: 'Dark' }).click(); + await select.selectOption('light'); + await select.selectOption('dark'); await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark'); }); - -test('can close change theme popover by clicking outside', async ({ page }) => { - await openSettings(page); - await page - .getByTestId('setting-row') - .filter({ - hasText: 'Color mode', - }) - .getByTestId('dropdown-select-trigger') - .click(); - - const themePopover = page.getByRole('dialog').filter({ - has: page.getByRole('menuitem', { name: 'Light' }), - }); - - await expect(themePopover).toBeVisible(); - - // get a mouse position that is 10px offset to the top of theme popover - // and click - const mousePosition = await themePopover.boundingBox(); - if (!mousePosition) { - throw new Error('theme popover is not visible'); - } - await page.mouse.click(mousePosition.x, mousePosition.y - 10); - - await expect(themePopover).not.toBeVisible(); -});