Skip to content

Commit 2ff5ef9

Browse files
JimmFlyhimself65
andauthored
feat: move theme switch and language switch to editor option menu (#2025)
Co-authored-by: himself65 <[email protected]>
1 parent 903b6ea commit 2ff5ef9

File tree

12 files changed

+346
-192
lines changed

12 files changed

+346
-192
lines changed

apps/electron/tests/basic.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ test('app theme', async () => {
5151
await page.screenshot({
5252
path: resolve(testResultDir, 'affine-light-theme-electron.png'),
5353
});
54+
await page.getByTestId('editor-option-menu').click();
5455
await page.getByTestId('change-theme-dark').click();
5556
await page.waitForTimeout(50);
5657
{

apps/web/src/components/blocksuite/workspace-header/header-right-items/EditorOptionMenu.tsx

Lines changed: 94 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
usePageMetaHelper,
1515
} from '@toeverything/hooks/use-block-suite-page-meta';
1616
import { useAtom } from 'jotai';
17+
import { useRouter } from 'next/router';
1718
import { useState } from 'react';
1819

1920
import { workspacePreferredModeAtom } from '../../../../atoms';
@@ -22,10 +23,41 @@ import { useCurrentPageId } from '../../../../hooks/current/use-current-page-id'
2223
import { useCurrentWorkspace } from '../../../../hooks/current/use-current-workspace';
2324
import { toast } from '../../../../utils';
2425
import { Export, MoveToTrash } from '../../../affine/operation-menu-items';
25-
26-
export const EditorOptionMenu = () => {
26+
import { MenuThemeModeSwitch } from '../header-right-items/theme-mode-switch';
27+
import {
28+
StyledHorizontalDivider,
29+
StyledHorizontalDividerContainer,
30+
} from '../styles';
31+
import { LanguageMenu } from './LanguageMenu';
32+
const CommonMenu = () => {
33+
const content = (
34+
<div
35+
onClick={e => {
36+
e.stopPropagation();
37+
}}
38+
>
39+
<MenuThemeModeSwitch />
40+
<LanguageMenu />
41+
</div>
42+
);
43+
return (
44+
<FlexWrapper alignItems="center" justifyContent="center">
45+
<Menu
46+
width={276}
47+
content={content}
48+
// placement="bottom-end"
49+
disablePortal={true}
50+
trigger="click"
51+
>
52+
<IconButton data-testid="editor-option-menu" iconSize={[24, 24]}>
53+
<MoreVerticalIcon />
54+
</IconButton>
55+
</Menu>
56+
</FlexWrapper>
57+
);
58+
};
59+
const PageMenu = () => {
2760
const { t } = useTranslation();
28-
2961
// fixme(himself65): remove these hooks ASAP
3062
const [workspace] = useCurrentWorkspace();
3163
const [pageId] = useCurrentPageId();
@@ -35,55 +67,70 @@ export const EditorOptionMenu = () => {
3567
const pageMeta = useBlockSuitePageMeta(blockSuiteWorkspace).find(
3668
meta => meta.id === pageId
3769
);
70+
assertExists(pageMeta);
3871
const [record, set] = useAtom(workspacePreferredModeAtom);
3972
const mode = record[pageId] ?? 'page';
40-
assertExists(pageMeta);
41-
const { favorite } = pageMeta;
73+
74+
const favorite = pageMeta.favorite ?? false;
4275
const { setPageMeta } = usePageMetaHelper(blockSuiteWorkspace);
4376
const [openConfirm, setOpenConfirm] = useState(false);
4477
const { removeToTrash } = useBlockSuiteMetaHelper(blockSuiteWorkspace);
4578
const EditMenu = (
4679
<>
47-
<MenuItem
48-
data-testid="editor-option-menu-favorite"
49-
onClick={() => {
50-
setPageMeta(pageId, { favorite: !favorite });
51-
toast(
52-
favorite ? t('Removed from Favorites') : t('Added to Favorites')
53-
);
54-
}}
55-
icon={
56-
favorite ? (
57-
<FavoritedIcon style={{ color: 'var(--affine-primary-color)' }} />
58-
) : (
59-
<FavoriteIcon />
60-
)
61-
}
62-
>
63-
{favorite ? t('Remove from favorites') : t('Add to Favorites')}
64-
</MenuItem>
65-
<MenuItem
66-
icon={mode === 'page' ? <EdgelessIcon /> : <PageIcon />}
67-
data-testid="editor-option-menu-edgeless"
68-
onClick={() => {
69-
set(record => ({
70-
...record,
71-
[pageId]: mode === 'page' ? 'edgeless' : 'page',
72-
}));
80+
<>
81+
<MenuItem
82+
data-testid="editor-option-menu-favorite"
83+
onClick={() => {
84+
setPageMeta(pageId, { favorite: !favorite });
85+
toast(
86+
favorite ? t('Removed from Favorites') : t('Added to Favorites')
87+
);
88+
}}
89+
icon={
90+
favorite ? (
91+
<FavoritedIcon style={{ color: 'var(--affine-primary-color)' }} />
92+
) : (
93+
<FavoriteIcon />
94+
)
95+
}
96+
>
97+
{favorite ? t('Remove from favorites') : t('Add to Favorites')}
98+
</MenuItem>
99+
<MenuItem
100+
icon={mode === 'page' ? <EdgelessIcon /> : <PageIcon />}
101+
data-testid="editor-option-menu-edgeless"
102+
onClick={() => {
103+
set(record => ({
104+
...record,
105+
[pageId]: mode === 'page' ? 'edgeless' : 'page',
106+
}));
107+
}}
108+
>
109+
{t('Convert to ')}
110+
{mode === 'page' ? t('Edgeless') : t('Page')}
111+
</MenuItem>
112+
<Export />
113+
{!pageMeta.isRootPinboard && (
114+
<MoveToTrash
115+
testId="editor-option-menu-delete"
116+
onItemClick={() => {
117+
setOpenConfirm(true);
118+
}}
119+
/>
120+
)}
121+
<StyledHorizontalDividerContainer>
122+
<StyledHorizontalDivider />
123+
</StyledHorizontalDividerContainer>
124+
</>
125+
126+
<div
127+
onClick={e => {
128+
e.stopPropagation();
73129
}}
74130
>
75-
{t('Convert to ')}
76-
{mode === 'page' ? t('Edgeless') : t('Page')}
77-
</MenuItem>
78-
<Export />
79-
{!pageMeta.isRootPinboard && (
80-
<MoveToTrash
81-
testId="editor-option-menu-delete"
82-
onItemClick={() => {
83-
setOpenConfirm(true);
84-
}}
85-
/>
86-
)}
131+
<MenuThemeModeSwitch />
132+
<LanguageMenu />
133+
</div>
87134
</>
88135
);
89136

@@ -116,3 +163,7 @@ export const EditorOptionMenu = () => {
116163
</>
117164
);
118165
};
166+
export const EditorOptionMenu = () => {
167+
const router = useRouter();
168+
return router.query.pageId ? <PageMenu /> : <CommonMenu />;
169+
};
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import { Button, displayFlex, Menu, MenuItem, styled } from '@affine/component';
2+
import { LOCALES } from '@affine/i18n';
3+
import { useTranslation } from '@affine/i18n';
4+
import { ArrowDownSmallIcon, PublishIcon } from '@blocksuite/icons';
5+
import type { FC, ReactElement } from 'react';
6+
import { useCallback } from 'react';
7+
8+
const LanguageMenuContent: FC = () => {
9+
const { i18n } = useTranslation();
10+
const changeLanguage = useCallback(
11+
(event: string) => {
12+
i18n.changeLanguage(event);
13+
},
14+
[i18n]
15+
);
16+
return (
17+
<>
18+
{LOCALES.map(option => {
19+
return (
20+
<StyledListItem
21+
key={option.name}
22+
title={option.name}
23+
onClick={() => {
24+
changeLanguage(option.tag);
25+
}}
26+
>
27+
{option.originalName}
28+
</StyledListItem>
29+
);
30+
})}
31+
</>
32+
);
33+
};
34+
export const LanguageMenu: React.FC = () => {
35+
const { i18n } = useTranslation();
36+
37+
const currentLanguage = LOCALES.find(item => item.tag === i18n.language);
38+
39+
return (
40+
<StyledContainer>
41+
<StyledIconContainer>
42+
<PublishIcon />
43+
</StyledIconContainer>
44+
<StyledButtonContainer>
45+
<Menu
46+
content={(<LanguageMenuContent />) as ReactElement}
47+
placement="bottom"
48+
trigger="click"
49+
disablePortal={true}
50+
>
51+
<StyledButton
52+
icon={
53+
<StyledArrowDownContainer>
54+
<ArrowDownSmallIcon />
55+
</StyledArrowDownContainer>
56+
}
57+
iconPosition="end"
58+
noBorder={true}
59+
data-testid="language-menu-button"
60+
>
61+
<StyledCurrentLanguage>
62+
{currentLanguage?.originalName}
63+
</StyledCurrentLanguage>
64+
</StyledButton>
65+
</Menu>
66+
</StyledButtonContainer>
67+
</StyledContainer>
68+
);
69+
};
70+
71+
const StyledListItem = styled(MenuItem)(() => ({
72+
width: '132px',
73+
height: '38px',
74+
fontSize: 'var(--affine-font-base)',
75+
textTransform: 'capitalize',
76+
}));
77+
78+
const StyledContainer = styled('div')(() => {
79+
return {
80+
width: '100%',
81+
height: '48px',
82+
backgroundColor: 'transparent',
83+
...displayFlex('flex-start', 'center'),
84+
padding: '0 14px',
85+
};
86+
});
87+
const StyledIconContainer = styled('div')(() => {
88+
return {
89+
width: '20px',
90+
height: '20px',
91+
color: 'var(--affine-icon-color)',
92+
fontSize: '20px',
93+
...displayFlex('flex-start', 'center'),
94+
};
95+
});
96+
const StyledButtonContainer = styled('div')(() => {
97+
return {
98+
width: '100%',
99+
height: '32px',
100+
borderRadius: '4px',
101+
border: `1px solid var(--affine-border-color)`,
102+
backgroundColor: 'transparent',
103+
...displayFlex('flex-start', 'center'),
104+
marginLeft: '12px',
105+
};
106+
});
107+
const StyledButton = styled(Button)(() => {
108+
return {
109+
width: '100%',
110+
height: '32px',
111+
borderRadius: '4px',
112+
backgroundColor: 'transparent',
113+
...displayFlex('space-between', 'center'),
114+
textTransform: 'capitalize',
115+
padding: '0',
116+
};
117+
});
118+
const StyledArrowDownContainer = styled('div')(() => {
119+
return {
120+
height: '32px',
121+
borderLeft: `1px solid var(--affine-border-color)`,
122+
backgroundColor: 'transparent',
123+
...displayFlex('flex-start', 'center'),
124+
padding: '4px 6px',
125+
fontSize: '24px',
126+
};
127+
});
128+
const StyledCurrentLanguage = styled('div')(() => {
129+
return {
130+
marginLeft: '12px',
131+
color: 'var(--affine-text-color)',
132+
};
133+
});

0 commit comments

Comments
 (0)