feat: replace menu with new design (#4012)

Co-authored-by: Alex Yang <himself65@outlook.com>
This commit is contained in:
Qi
2023-09-06 12:36:43 +08:00
committed by GitHub
parent ef3d3a34e2
commit d8c9f10bc1
51 changed files with 611 additions and 739 deletions

View File

@@ -1,138 +1,69 @@
import {
Menu,
MenuItem,
type MenuProps,
MenuTrigger,
styled,
} from '@affine/component';
import { LOCALES, useI18N } from '@affine/i18n';
import { assertExists } from '@blocksuite/global/utils';
import type { ButtonProps } from '@toeverything/components/button';
import { useCallback, useEffect, useState } from 'react';
export const StyledListItem = styled(MenuItem)(() => ({
height: '38px',
textTransform: 'capitalize',
}));
interface LanguageMenuContentProps {
currentLanguage: string;
currentLanguageIndex: number;
}
import { LOCALES } from '@affine/i18n';
import { useI18N } from '@affine/i18n';
import { Menu, MenuItem, MenuTrigger } from '@toeverything/components/menu';
import type { ReactElement } from 'react';
import { useCallback, useMemo, useRef } from 'react';
// Fixme: keyboard focus should be supported by Menu component
const LanguageMenuContent = ({
currentLanguage,
currentLanguageIndex,
}: LanguageMenuContentProps) => {
const i18n = useI18N();
const changeLanguage = useCallback(
(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]);
onSelect,
}: {
currentLanguage?: string;
onSelect: (value: string) => void;
}) => {
return (
<>
{LOCALES.map((option, optionIndex) => {
{LOCALES.map(option => {
return (
<StyledListItem
<MenuItem
key={option.name}
active={option.tag === currentLanguage}
userFocused={optionIndex == focusedOptionIndex}
selected={currentLanguage === option.originalName}
title={option.name}
onClick={() => {
changeLanguage(option.tag);
}}
onSelect={() => onSelect(option.tag)}
>
{option.originalName}
</StyledListItem>
</MenuItem>
);
})}
</>
);
};
interface LanguageMenuProps extends Omit<MenuProps, 'children'> {
triggerProps?: ButtonProps;
}
export const LanguageMenu = ({
triggerProps,
...menuProps
}: LanguageMenuProps) => {
export const LanguageMenu = () => {
const i18n = useI18N();
const currentLanguageIndex = LOCALES.findIndex(
item => item.tag === i18n.language
const ref = useRef(null);
const currentLanguage = useMemo(
() => LOCALES.find(item => item.tag === i18n.language),
[i18n.language]
);
const onSelect = useCallback(
(event: string) => {
return i18n.changeLanguage(event);
},
[i18n]
);
const currentLanguage = LOCALES[currentLanguageIndex];
assertExists(currentLanguage, 'currentLanguage should exist');
return (
<Menu
content={
<LanguageMenuContent
currentLanguage={currentLanguage.tag}
currentLanguageIndex={currentLanguageIndex}
/>
items={
(
<LanguageMenuContent
currentLanguage={currentLanguage?.originalName}
onSelect={onSelect}
/>
) as ReactElement
}
placement="bottom-end"
trigger="click"
disablePortal={true}
{...menuProps}
portalOptions={{
container: ref.current,
}}
>
<MenuTrigger
ref={ref}
data-testid="language-menu-button"
style={{ textTransform: 'capitalize' }}
{...triggerProps}
style={{ textTransform: 'capitalize', fontWeight: 600 }}
block={true}
>
{currentLanguage.originalName}
{currentLanguage?.originalName || ''}
</MenuTrigger>
</Menu>
);

View File

@@ -0,0 +1,5 @@
import { style } from '@vanilla-extract/css';
export const hoveredLanguageItem = style({
background: 'var(--affine-hover-color)',
});

View File

@@ -1,4 +1,3 @@
import { Menu, MenuItem } from '@affine/component';
import {
InviteModal,
type InviteModalProps,
@@ -12,6 +11,7 @@ import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { MoreVerticalIcon } from '@blocksuite/icons';
import { Avatar } from '@toeverything/components/avatar';
import { Button, IconButton } from '@toeverything/components/button';
import { Menu, MenuItem } from '@toeverything/components/menu';
import { Tooltip } from '@toeverything/components/tooltip';
import clsx from 'clsx';
import { useSetAtom } from 'jotai/react';
@@ -190,14 +190,11 @@ const MemberItem = ({
: 'Pending'}
</div>
<Menu
content={
items={
<MenuItem data-member-id={member.id} onClick={handleRevoke}>
{operationButtonInfo.leaveOrRevokeText}
</MenuItem>
}
placement="bottom"
disablePortal={true}
trigger="click"
>
<IconButton
disabled={!operationButtonInfo.show}

View File

@@ -1,6 +1,6 @@
import { Menu, MenuItem, MenuTrigger } from '@affine/component';
import { Menu, MenuItem, MenuTrigger } from '@toeverything/components/menu';
import dayjs from 'dayjs';
import { useCallback } from 'react';
import { useCallback, useRef } from 'react';
import {
dateFormatOptions,
@@ -23,10 +23,8 @@ const DateFormatMenuContent = ({
return (
<MenuItem
key={option}
active={currentOption === option}
onClick={() => {
onSelect(option);
}}
selected={currentOption === option}
onSelect={() => onSelect(option)}
>
{dayjs(new Date()).format(option)}
</MenuItem>
@@ -37,6 +35,7 @@ const DateFormatMenuContent = ({
};
export const DateFormatSetting = () => {
const ref = useRef(null);
const [appearanceSettings, setAppSettings] = useAppSetting();
const handleSelect = useCallback(
(option: DateFormats) => {
@@ -47,17 +46,15 @@ export const DateFormatSetting = () => {
return (
<Menu
content={
items={
<DateFormatMenuContent
onSelect={handleSelect}
currentOption={appearanceSettings.dateFormat}
/>
}
placement="bottom-end"
trigger="click"
disablePortal={true}
portalOptions={{ container: ref.current }}
>
<MenuTrigger data-testid="date-format-menu-trigger">
<MenuTrigger ref={ref} data-testid="date-format-menu-trigger" block>
{dayjs(new Date()).format(appearanceSettings.dateFormat)}
</MenuTrigger>
</Menu>

View File

@@ -113,17 +113,7 @@ export const AppearanceSettings = () => {
desc={t['com.affine.settings.appearance.language-description']()}
>
<div className={settingWrapper}>
<LanguageMenu
triggerContainerStyle={{ width: '100%' }}
triggerProps={{
style: {
width: '100%',
justifyContent: 'space-between',
fontWeight: 600,
padding: '0 10px',
},
}}
/>
<LanguageMenu />
</div>
</SettingRow>
{environment.isDesktop ? (