feat(core): added code to handle keyboard inputs (#4006)

Co-authored-by: Alex Yang <himself65@outlook.com>
This commit is contained in:
Priyansh Gupta
2023-08-31 23:45:55 +05:30
committed by GitHub
parent 6be176b4e3
commit 18d5a99af5
4 changed files with 111 additions and 21 deletions
@@ -5,11 +5,10 @@ import {
MenuTrigger, MenuTrigger,
styled, styled,
} from '@affine/component'; } from '@affine/component';
import { LOCALES } from '@affine/i18n'; import { LOCALES, useI18N } from '@affine/i18n';
import { useI18N } from '@affine/i18n'; import { assertExists } from '@blocksuite/global/utils';
import type { ButtonProps } from '@toeverything/components/button'; import type { ButtonProps } from '@toeverything/components/button';
import type { ReactElement } from 'react'; import { useCallback, useEffect, useState } from 'react';
import { useCallback } from 'react';
export const StyledListItem = styled(MenuItem)(() => ({ export const StyledListItem = styled(MenuItem)(() => ({
height: '38px', height: '38px',
@@ -17,30 +16,78 @@ export const StyledListItem = styled(MenuItem)(() => ({
})); }));
interface LanguageMenuContentProps { interface LanguageMenuContentProps {
currentLanguage?: string; currentLanguage: string;
currentLanguageIndex: number;
} }
const LanguageMenuContent = ({ currentLanguage }: LanguageMenuContentProps) => { const LanguageMenuContent = ({
currentLanguage,
currentLanguageIndex,
}: LanguageMenuContentProps) => {
const i18n = useI18N(); const i18n = useI18N();
const changeLanguage = useCallback( const changeLanguage = useCallback(
(event: string) => { (targetLanguage: string) => {
return i18n.changeLanguage(event); 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] [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 ( return (
<> <>
{LOCALES.map(option => { {LOCALES.map((option, optionIndex) => {
return ( return (
<StyledListItem <StyledListItem
key={option.name} key={option.name}
active={currentLanguage === option.originalName} active={option.tag === currentLanguage}
userFocused={optionIndex == focusedOptionIndex}
title={option.name} title={option.name}
onClick={() => { onClick={() => {
changeLanguage(option.tag).catch(err => { changeLanguage(option.tag);
throw new Error('Failed to change language', err);
});
}} }}
> >
{option.originalName} {option.originalName}
@@ -61,16 +108,19 @@ export const LanguageMenu = ({
}: LanguageMenuProps) => { }: LanguageMenuProps) => {
const i18n = useI18N(); 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 ( return (
<Menu <Menu
content={ content={
( <LanguageMenuContent
<LanguageMenuContent currentLanguage={currentLanguage.tag}
currentLanguage={currentLanguage?.originalName} currentLanguageIndex={currentLanguageIndex}
/> />
) as ReactElement
} }
placement="bottom-end" placement="bottom-end"
trigger="click" trigger="click"
@@ -82,7 +132,7 @@ export const LanguageMenu = ({
style={{ textTransform: 'capitalize' }} style={{ textTransform: 'capitalize' }}
{...triggerProps} {...triggerProps}
> >
{currentLanguage?.originalName} {currentLanguage.originalName}
</MenuTrigger> </MenuTrigger>
</Menu> </Menu>
); );
@@ -15,6 +15,7 @@ export type IconMenuProps = PropsWithChildren<{
disabled?: boolean; disabled?: boolean;
active?: boolean; active?: boolean;
disableHover?: boolean; disableHover?: boolean;
userFocused?: boolean;
gap?: string; gap?: string;
fontSize?: string; fontSize?: string;
}> & }> &
+7 -1
View File
@@ -62,11 +62,13 @@ export const StyledMenuItem = styled('button')<{
disabled?: boolean; disabled?: boolean;
active?: boolean; active?: boolean;
disableHover?: boolean; disableHover?: boolean;
userFocused?: boolean;
}>(({ }>(({
isDir = false, isDir = false,
disabled = false, disabled = false,
active = false, active = false,
disableHover = false, disableHover = false,
userFocused = false,
}) => { }) => {
return { return {
width: '100%', width: '100%',
@@ -99,7 +101,11 @@ export const StyledMenuItem = styled('button')<{
: { : {
backgroundColor: 'var(--affine-hover-color)', backgroundColor: 'var(--affine-hover-color)',
}, },
...(userFocused && !disabled
? {
backgroundColor: 'var(--affine-hover-color)',
}
: {}),
...(active && !disabled ...(active && !disabled
? { ? {
backgroundColor: 'var(--affine-hover-color)', backgroundColor: 'var(--affine-hover-color)',
+33
View File
@@ -20,6 +20,39 @@ test('Open settings modal', async ({ page }) => {
await expect(modal).toBeVisible(); 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 }) => { test('Change theme', async ({ page }) => {
await openHomePage(page); await openHomePage(page);
await waitEditorLoad(page); await waitEditorLoad(page);