mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-11 23:26:30 +08:00
feat(core): added code to handle keyboard inputs (#4006)
Co-authored-by: Alex Yang <himself65@outlook.com>
This commit is contained in:
@@ -5,11 +5,10 @@ import {
|
||||
MenuTrigger,
|
||||
styled,
|
||||
} from '@affine/component';
|
||||
import { LOCALES } from '@affine/i18n';
|
||||
import { useI18N } from '@affine/i18n';
|
||||
import { LOCALES, useI18N } from '@affine/i18n';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import type { ButtonProps } from '@toeverything/components/button';
|
||||
import type { ReactElement } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
export const StyledListItem = styled(MenuItem)(() => ({
|
||||
height: '38px',
|
||||
@@ -17,30 +16,78 @@ export const StyledListItem = styled(MenuItem)(() => ({
|
||||
}));
|
||||
|
||||
interface LanguageMenuContentProps {
|
||||
currentLanguage?: string;
|
||||
currentLanguage: string;
|
||||
currentLanguageIndex: number;
|
||||
}
|
||||
|
||||
const LanguageMenuContent = ({ currentLanguage }: LanguageMenuContentProps) => {
|
||||
const LanguageMenuContent = ({
|
||||
currentLanguage,
|
||||
currentLanguageIndex,
|
||||
}: LanguageMenuContentProps) => {
|
||||
const i18n = useI18N();
|
||||
const changeLanguage = useCallback(
|
||||
(event: string) => {
|
||||
return i18n.changeLanguage(event);
|
||||
(targetLanguage: string) => {
|
||||
console.assert(
|
||||
LOCALES.some(item => item.tag === targetLanguage),
|
||||
'targetLanguage should be one of the LOCALES'
|
||||
);
|
||||
i18n.changeLanguage(targetLanguage).catch(err => {
|
||||
console.error('Failed to change language', err);
|
||||
});
|
||||
},
|
||||
[i18n]
|
||||
);
|
||||
|
||||
const [focusedOptionIndex, setFocusedOptionIndex] = useState(
|
||||
currentLanguageIndex ?? 0
|
||||
);
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(event: KeyboardEvent) => {
|
||||
switch (event.key) {
|
||||
case 'ArrowUp':
|
||||
event.preventDefault();
|
||||
setFocusedOptionIndex(prevIndex =>
|
||||
prevIndex > 0 ? prevIndex - 1 : 0
|
||||
);
|
||||
break;
|
||||
case 'ArrowDown':
|
||||
event.preventDefault();
|
||||
setFocusedOptionIndex(prevIndex =>
|
||||
prevIndex < LOCALES.length - 1 ? prevIndex + 1 : LOCALES.length
|
||||
);
|
||||
break;
|
||||
case 'Enter':
|
||||
if (focusedOptionIndex !== -1) {
|
||||
const selectedOption = LOCALES[focusedOptionIndex];
|
||||
changeLanguage(selectedOption.tag);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
[changeLanguage, focusedOptionIndex]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener('keydown', handleKeyDown);
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleKeyDown);
|
||||
};
|
||||
}, [handleKeyDown]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{LOCALES.map(option => {
|
||||
{LOCALES.map((option, optionIndex) => {
|
||||
return (
|
||||
<StyledListItem
|
||||
key={option.name}
|
||||
active={currentLanguage === option.originalName}
|
||||
active={option.tag === currentLanguage}
|
||||
userFocused={optionIndex == focusedOptionIndex}
|
||||
title={option.name}
|
||||
onClick={() => {
|
||||
changeLanguage(option.tag).catch(err => {
|
||||
throw new Error('Failed to change language', err);
|
||||
});
|
||||
changeLanguage(option.tag);
|
||||
}}
|
||||
>
|
||||
{option.originalName}
|
||||
@@ -61,16 +108,19 @@ export const LanguageMenu = ({
|
||||
}: LanguageMenuProps) => {
|
||||
const i18n = useI18N();
|
||||
|
||||
const currentLanguage = LOCALES.find(item => item.tag === i18n.language);
|
||||
const currentLanguageIndex = LOCALES.findIndex(
|
||||
item => item.tag === i18n.language
|
||||
);
|
||||
const currentLanguage = LOCALES[currentLanguageIndex];
|
||||
assertExists(currentLanguage, 'currentLanguage should exist');
|
||||
|
||||
return (
|
||||
<Menu
|
||||
content={
|
||||
(
|
||||
<LanguageMenuContent
|
||||
currentLanguage={currentLanguage?.originalName}
|
||||
/>
|
||||
) as ReactElement
|
||||
<LanguageMenuContent
|
||||
currentLanguage={currentLanguage.tag}
|
||||
currentLanguageIndex={currentLanguageIndex}
|
||||
/>
|
||||
}
|
||||
placement="bottom-end"
|
||||
trigger="click"
|
||||
@@ -82,7 +132,7 @@ export const LanguageMenu = ({
|
||||
style={{ textTransform: 'capitalize' }}
|
||||
{...triggerProps}
|
||||
>
|
||||
{currentLanguage?.originalName}
|
||||
{currentLanguage.originalName}
|
||||
</MenuTrigger>
|
||||
</Menu>
|
||||
);
|
||||
|
||||
@@ -15,6 +15,7 @@ export type IconMenuProps = PropsWithChildren<{
|
||||
disabled?: boolean;
|
||||
active?: boolean;
|
||||
disableHover?: boolean;
|
||||
userFocused?: boolean;
|
||||
gap?: string;
|
||||
fontSize?: string;
|
||||
}> &
|
||||
|
||||
@@ -62,11 +62,13 @@ export const StyledMenuItem = styled('button')<{
|
||||
disabled?: boolean;
|
||||
active?: boolean;
|
||||
disableHover?: boolean;
|
||||
userFocused?: boolean;
|
||||
}>(({
|
||||
isDir = false,
|
||||
disabled = false,
|
||||
active = false,
|
||||
disableHover = false,
|
||||
userFocused = false,
|
||||
}) => {
|
||||
return {
|
||||
width: '100%',
|
||||
@@ -99,7 +101,11 @@ export const StyledMenuItem = styled('button')<{
|
||||
: {
|
||||
backgroundColor: 'var(--affine-hover-color)',
|
||||
},
|
||||
|
||||
...(userFocused && !disabled
|
||||
? {
|
||||
backgroundColor: 'var(--affine-hover-color)',
|
||||
}
|
||||
: {}),
|
||||
...(active && !disabled
|
||||
? {
|
||||
backgroundColor: 'var(--affine-hover-color)',
|
||||
|
||||
@@ -20,6 +20,39 @@ test('Open settings modal', async ({ page }) => {
|
||||
await expect(modal).toBeVisible();
|
||||
});
|
||||
|
||||
test('change language using keyboard', async ({ page }) => {
|
||||
await openHomePage(page);
|
||||
await waitEditorLoad(page);
|
||||
await openSettingModal(page);
|
||||
|
||||
const locator = page.getByTestId('language-menu-button');
|
||||
const oldName = await locator.textContent();
|
||||
await locator.click();
|
||||
await page.waitForTimeout(200);
|
||||
await page.keyboard.press('ArrowDown', {
|
||||
delay: 50,
|
||||
});
|
||||
await page.keyboard.press('Enter', {
|
||||
delay: 50,
|
||||
});
|
||||
{
|
||||
const newName = await locator.textContent();
|
||||
expect(oldName).not.toBe(newName);
|
||||
}
|
||||
await locator.click();
|
||||
await page.waitForTimeout(200);
|
||||
await page.keyboard.press('ArrowUp', {
|
||||
delay: 50,
|
||||
});
|
||||
await page.keyboard.press('Enter', {
|
||||
delay: 50,
|
||||
});
|
||||
{
|
||||
const newName = await locator.textContent();
|
||||
expect(oldName).toBe(newName);
|
||||
}
|
||||
});
|
||||
|
||||
test('Change theme', async ({ page }) => {
|
||||
await openHomePage(page);
|
||||
await waitEditorLoad(page);
|
||||
|
||||
Reference in New Issue
Block a user