diff --git a/apps/electron/tests/basic.spec.ts b/apps/electron/tests/basic.spec.ts
index 9c769b4d5b..5336cea91d 100644
--- a/apps/electron/tests/basic.spec.ts
+++ b/apps/electron/tests/basic.spec.ts
@@ -51,6 +51,7 @@ test('app theme', async () => {
await page.screenshot({
path: resolve(testResultDir, 'affine-light-theme-electron.png'),
});
+ await page.getByTestId('editor-option-menu').click();
await page.getByTestId('change-theme-dark').click();
await page.waitForTimeout(50);
{
diff --git a/apps/web/src/components/blocksuite/workspace-header/header-right-items/EditorOptionMenu.tsx b/apps/web/src/components/blocksuite/workspace-header/header-right-items/EditorOptionMenu.tsx
index 766df2f3e1..0115f1989a 100644
--- a/apps/web/src/components/blocksuite/workspace-header/header-right-items/EditorOptionMenu.tsx
+++ b/apps/web/src/components/blocksuite/workspace-header/header-right-items/EditorOptionMenu.tsx
@@ -14,6 +14,7 @@ import {
usePageMetaHelper,
} from '@toeverything/hooks/use-block-suite-page-meta';
import { useAtom } from 'jotai';
+import { useRouter } from 'next/router';
import { useState } from 'react';
import { workspacePreferredModeAtom } from '../../../../atoms';
@@ -22,10 +23,41 @@ import { useCurrentPageId } from '../../../../hooks/current/use-current-page-id'
import { useCurrentWorkspace } from '../../../../hooks/current/use-current-workspace';
import { toast } from '../../../../utils';
import { Export, MoveToTrash } from '../../../affine/operation-menu-items';
-
-export const EditorOptionMenu = () => {
+import { MenuThemeModeSwitch } from '../header-right-items/theme-mode-switch';
+import {
+ StyledHorizontalDivider,
+ StyledHorizontalDividerContainer,
+} from '../styles';
+import { LanguageMenu } from './LanguageMenu';
+const CommonMenu = () => {
+ const content = (
+
{
+ e.stopPropagation();
+ }}
+ >
+
+
+
+ );
+ return (
+
+
+
+ );
+};
+const PageMenu = () => {
const { t } = useTranslation();
-
// fixme(himself65): remove these hooks ASAP
const [workspace] = useCurrentWorkspace();
const [pageId] = useCurrentPageId();
@@ -35,55 +67,70 @@ export const EditorOptionMenu = () => {
const pageMeta = useBlockSuitePageMeta(blockSuiteWorkspace).find(
meta => meta.id === pageId
);
+ assertExists(pageMeta);
const [record, set] = useAtom(workspacePreferredModeAtom);
const mode = record[pageId] ?? 'page';
- assertExists(pageMeta);
- const { favorite } = pageMeta;
+
+ const favorite = pageMeta.favorite ?? false;
const { setPageMeta } = usePageMetaHelper(blockSuiteWorkspace);
const [openConfirm, setOpenConfirm] = useState(false);
const { removeToTrash } = useBlockSuiteMetaHelper(blockSuiteWorkspace);
const EditMenu = (
<>
-
- : }
- data-testid="editor-option-menu-edgeless"
- onClick={() => {
- set(record => ({
- ...record,
- [pageId]: mode === 'page' ? 'edgeless' : 'page',
- }));
- }}
- >
- {t('Convert to ')}
- {mode === 'page' ? t('Edgeless') : t('Page')}
-
-
- {!pageMeta.isRootPinboard && (
- {
- setOpenConfirm(true);
+ <>
+
+ : }
+ data-testid="editor-option-menu-edgeless"
+ onClick={() => {
+ set(record => ({
+ ...record,
+ [pageId]: mode === 'page' ? 'edgeless' : 'page',
+ }));
+ }}
+ >
+ {t('Convert to ')}
+ {mode === 'page' ? t('Edgeless') : t('Page')}
+
+
+ {!pageMeta.isRootPinboard && (
+ {
+ setOpenConfirm(true);
+ }}
+ />
+ )}
+
+
+
+ >
+
+ {
+ e.stopPropagation();
+ }}
+ >
+
+
+
>
);
@@ -116,3 +163,7 @@ export const EditorOptionMenu = () => {
>
);
};
+export const EditorOptionMenu = () => {
+ const router = useRouter();
+ return router.query.pageId ? : ;
+};
diff --git a/apps/web/src/components/blocksuite/workspace-header/header-right-items/LanguageMenu.tsx b/apps/web/src/components/blocksuite/workspace-header/header-right-items/LanguageMenu.tsx
new file mode 100644
index 0000000000..bdf474def1
--- /dev/null
+++ b/apps/web/src/components/blocksuite/workspace-header/header-right-items/LanguageMenu.tsx
@@ -0,0 +1,133 @@
+import { Button, displayFlex, Menu, MenuItem, styled } from '@affine/component';
+import { LOCALES } from '@affine/i18n';
+import { useTranslation } from '@affine/i18n';
+import { ArrowDownSmallIcon, PublishIcon } from '@blocksuite/icons';
+import type { FC, ReactElement } from 'react';
+import { useCallback } from 'react';
+
+const LanguageMenuContent: FC = () => {
+ const { i18n } = useTranslation();
+ const changeLanguage = useCallback(
+ (event: string) => {
+ i18n.changeLanguage(event);
+ },
+ [i18n]
+ );
+ return (
+ <>
+ {LOCALES.map(option => {
+ return (
+ {
+ changeLanguage(option.tag);
+ }}
+ >
+ {option.originalName}
+
+ );
+ })}
+ >
+ );
+};
+export const LanguageMenu: React.FC = () => {
+ const { i18n } = useTranslation();
+
+ const currentLanguage = LOCALES.find(item => item.tag === i18n.language);
+
+ return (
+
+
+
+
+
+ ) as ReactElement}
+ placement="bottom"
+ trigger="click"
+ disablePortal={true}
+ >
+
+
+
+ }
+ iconPosition="end"
+ noBorder={true}
+ data-testid="language-menu-button"
+ >
+
+ {currentLanguage?.originalName}
+
+
+
+
+
+ );
+};
+
+const StyledListItem = styled(MenuItem)(() => ({
+ width: '132px',
+ height: '38px',
+ fontSize: 'var(--affine-font-base)',
+ textTransform: 'capitalize',
+}));
+
+const StyledContainer = styled('div')(() => {
+ return {
+ width: '100%',
+ height: '48px',
+ backgroundColor: 'transparent',
+ ...displayFlex('flex-start', 'center'),
+ padding: '0 14px',
+ };
+});
+const StyledIconContainer = styled('div')(() => {
+ return {
+ width: '20px',
+ height: '20px',
+ color: 'var(--affine-icon-color)',
+ fontSize: '20px',
+ ...displayFlex('flex-start', 'center'),
+ };
+});
+const StyledButtonContainer = styled('div')(() => {
+ return {
+ width: '100%',
+ height: '32px',
+ borderRadius: '4px',
+ border: `1px solid var(--affine-border-color)`,
+ backgroundColor: 'transparent',
+ ...displayFlex('flex-start', 'center'),
+ marginLeft: '12px',
+ };
+});
+const StyledButton = styled(Button)(() => {
+ return {
+ width: '100%',
+ height: '32px',
+ borderRadius: '4px',
+ backgroundColor: 'transparent',
+ ...displayFlex('space-between', 'center'),
+ textTransform: 'capitalize',
+ padding: '0',
+ };
+});
+const StyledArrowDownContainer = styled('div')(() => {
+ return {
+ height: '32px',
+ borderLeft: `1px solid var(--affine-border-color)`,
+ backgroundColor: 'transparent',
+ ...displayFlex('flex-start', 'center'),
+ padding: '4px 6px',
+ fontSize: '24px',
+ };
+});
+const StyledCurrentLanguage = styled('div')(() => {
+ return {
+ marginLeft: '12px',
+ color: 'var(--affine-text-color)',
+ };
+});
diff --git a/apps/web/src/components/blocksuite/workspace-header/header-right-items/theme-mode-switch/index.tsx b/apps/web/src/components/blocksuite/workspace-header/header-right-items/theme-mode-switch/index.tsx
index b97414a259..ccccc35ec2 100644
--- a/apps/web/src/components/blocksuite/workspace-header/header-right-items/theme-mode-switch/index.tsx
+++ b/apps/web/src/components/blocksuite/workspace-header/header-right-items/theme-mode-switch/index.tsx
@@ -1,50 +1,65 @@
import { DarkModeIcon, LightModeIcon } from '@blocksuite/icons';
import { useTheme } from 'next-themes';
-import { useEffect, useState } from 'react';
+import { useEffect } from 'react';
-import { StyledSwitchItem, StyledThemeModeSwitch } from './style';
-export const ThemeModeSwitch = () => {
- const { setTheme, resolvedTheme } = useTheme();
+import {
+ StyledSwitchItem,
+ StyledThemeButton,
+ StyledThemeButtonContainer,
+ StyledThemeModeContainer,
+ StyledThemeModeSwitch,
+ StyledVerticalDivider,
+} from './style';
+export const MenuThemeModeSwitch = () => {
+ const { setTheme, resolvedTheme, theme } = useTheme();
useEffect(() => {
if (environment.isDesktop) {
window.apis?.onThemeChange(resolvedTheme === 'dark' ? 'dark' : 'light');
}
}, [resolvedTheme]);
-
- const [isHover, setIsHover] = useState(false);
return (
- {
- setIsHover(true);
- }}
- onMouseLeave={() => {
- setIsHover(false);
- }}
- >
- {
- setTheme('light');
- }}
- >
-
-
- {
- setTheme('dark');
- }}
- >
-
-
-
+
+
+
+
+
+
+
+
+
+
+ {
+ setTheme('light');
+ }}
+ >
+ light
+
+
+ {
+ setTheme('dark');
+ }}
+ >
+ dark
+
+
+ {
+ setTheme('system');
+ }}
+ >
+ system
+
+
+
);
};
-export default ThemeModeSwitch;
+export default MenuThemeModeSwitch;
diff --git a/apps/web/src/components/blocksuite/workspace-header/header-right-items/theme-mode-switch/style.ts b/apps/web/src/components/blocksuite/workspace-header/header-right-items/theme-mode-switch/style.ts
index 7b01906483..29973c966f 100644
--- a/apps/web/src/components/blocksuite/workspace-header/header-right-items/theme-mode-switch/style.ts
+++ b/apps/web/src/components/blocksuite/workspace-header/header-right-items/theme-mode-switch/style.ts
@@ -3,24 +3,63 @@ import { css, displayFlex, keyframes, styled } from '@affine/component';
import spring, { toString } from 'css-spring';
const ANIMATE_DURATION = 400;
-
-export const StyledThemeModeSwitch = styled('button')(() => {
+export const StyledThemeModeContainer = styled('div')(() => {
return {
- width: '32px',
+ width: '100%',
+ height: '48px',
+ borderRadius: '6px',
+ backgroundColor: 'transparent',
+ color: 'var(--affine-icon-color)',
+ fontSize: '16px',
+ ...displayFlex('flex-start', 'center'),
+ padding: '0 14px',
+ };
+});
+export const StyledThemeButtonContainer = styled('div')(() => {
+ return {
+ border: `1px solid var(--affine-border-color)`,
+ borderRadius: '4px',
+ cursor: 'pointer',
+ ...displayFlex('space-evenly', 'center'),
+ flexGrow: 1,
+ marginLeft: '12px',
+ };
+});
+export const StyledThemeButton = styled('button')<{
+ active: boolean;
+}>(({ active }) => {
+ return {
+ cursor: 'pointer',
+ color: active ? 'var(--affine-primary-color)' : 'var(--affine-icon-color)',
+ };
+});
+export const StyledVerticalDivider = styled('div')(() => {
+ return {
+ width: '1px',
height: '32px',
+ borderLeft: `1px solid var(--affine-border-color)`,
+ };
+});
+export const StyledThemeModeSwitch = styled('button')<{
+ inMenu?: boolean;
+}>(({ inMenu }) => {
+ return {
+ width: inMenu ? '20px' : '32px',
+ height: inMenu ? '20px' : '32px',
borderRadius: '6px',
overflow: 'hidden',
WebkitAppRegion: 'no-drag',
backgroundColor: 'transparent',
position: 'relative',
color: 'var(--affine-icon-color)',
- fontSize: '24px',
+ fontSize: inMenu ? '20px' : '24px',
};
});
export const StyledSwitchItem = styled('div')<{
active: boolean;
- isHover: boolean;
-}>(({ active, isHover }) => {
+ isHover?: boolean;
+ inMenu?: boolean;
+}>(({ active, isHover, inMenu }) => {
const activeRaiseAnimate = toString(
spring({ top: '0' }, { top: '-100%' }, { preset: 'gentle' })
);
@@ -58,8 +97,8 @@ export const StyledSwitchItem = styled('div')<{
};
return css`
${css(displayFlex('center', 'center'))}
- width: 32px;
- height: 32px;
+ width:${inMenu ? '20px' : '32px'} ;
+ height: ${inMenu ? '20px' : '32px'} ;
position: absolute;
left: 0;
cursor: pointer;
diff --git a/apps/web/src/components/blocksuite/workspace-header/header.tsx b/apps/web/src/components/blocksuite/workspace-header/header.tsx
index 7add5fba23..16ca624507 100644
--- a/apps/web/src/components/blocksuite/workspace-header/header.tsx
+++ b/apps/web/src/components/blocksuite/workspace-header/header.tsx
@@ -19,7 +19,6 @@ import { EditorOptionMenu } from './header-right-items/EditorOptionMenu';
import EditPage from './header-right-items/EditPage';
import { HeaderShareMenu } from './header-right-items/ShareMenu';
import SyncUser from './header-right-items/SyncUser';
-import ThemeModeSwitch from './header-right-items/theme-mode-switch';
import TrashButtonGroup from './header-right-items/TrashButtonGroup';
import UserAvatar from './header-right-items/UserAvatar';
import {
@@ -66,7 +65,6 @@ export type BaseHeaderProps<
export const enum HeaderRightItemName {
EditorOptionMenu = 'editorOptionMenu',
TrashButtonGroup = 'trashButtonGroup',
- ThemeModeSwitch = 'themeModeSwitch',
SyncUser = 'syncUser',
ShareMenu = 'shareMenu',
EditPage = 'editPage',
@@ -98,12 +96,6 @@ const HeaderRightItems: Record = {
return !isPublic && !isPreview;
},
},
- [HeaderRightItemName.ThemeModeSwitch]: {
- Component: ThemeModeSwitch,
- availableWhen: (_, currentPage) => {
- return currentPage?.meta.trash !== true;
- },
- },
[HeaderRightItemName.ShareMenu]: {
Component: HeaderShareMenu,
availableWhen: (workspace, currentPage) => {
@@ -125,7 +117,7 @@ const HeaderRightItems: Record = {
[HeaderRightItemName.EditorOptionMenu]: {
Component: EditorOptionMenu,
availableWhen: (_, currentPage, { isPublic, isPreview }) => {
- return !!currentPage && !isPublic && !isPreview;
+ return !isPublic && !isPreview;
},
},
};
diff --git a/apps/web/src/components/blocksuite/workspace-header/styles.ts b/apps/web/src/components/blocksuite/workspace-header/styles.ts
index eb1426a647..d2f5c12561 100644
--- a/apps/web/src/components/blocksuite/workspace-header/styles.ts
+++ b/apps/web/src/components/blocksuite/workspace-header/styles.ts
@@ -167,3 +167,16 @@ export const StyledQuickSearchTipContent = styled('div')(() => {
flexDirection: 'column',
};
});
+
+export const StyledHorizontalDivider = styled('div')(() => {
+ return {
+ width: '100%',
+ borderTop: `1px solid var(--affine-border-color)`,
+ };
+});
+export const StyledHorizontalDividerContainer = styled('div')(() => {
+ return {
+ width: '100%',
+ padding: '14px',
+ };
+});
diff --git a/apps/web/src/components/pure/workspace-list-modal/index.tsx b/apps/web/src/components/pure/workspace-list-modal/index.tsx
index 5d95e1ab0c..223a12044d 100644
--- a/apps/web/src/components/pure/workspace-list-modal/index.tsx
+++ b/apps/web/src/components/pure/workspace-list-modal/index.tsx
@@ -15,7 +15,6 @@ import { useCallback } from 'react';
import type { AllWorkspace } from '../../../shared';
import { Footer } from '../footer';
-import { LanguageMenu } from './language-menu';
import {
StyledCreateWorkspaceCard,
StyledHelperContainer,
@@ -24,7 +23,6 @@ import {
StyledModalHeaderLeft,
StyledModalTitle,
StyledOperationWrapper,
- StyledSplitLine,
StyleWorkspaceAdd,
StyleWorkspaceInfo,
StyleWorkspaceTitle,
@@ -86,8 +84,6 @@ export const WorkspaceListModal = ({
-
-
{
diff --git a/apps/web/src/components/pure/workspace-list-modal/language-menu.tsx b/apps/web/src/components/pure/workspace-list-modal/language-menu.tsx
deleted file mode 100644
index 243f2f90a5..0000000000
--- a/apps/web/src/components/pure/workspace-list-modal/language-menu.tsx
+++ /dev/null
@@ -1,64 +0,0 @@
-import { Button, Menu, MenuItem, styled } from '@affine/component';
-import { LOCALES } from '@affine/i18n';
-import { useTranslation } from '@affine/i18n';
-import { ArrowDownSmallIcon } from '@blocksuite/icons';
-import type { FC, ReactElement } from 'react';
-import { useCallback } from 'react';
-
-const LanguageMenuContent: FC = () => {
- const { i18n } = useTranslation();
- const changeLanguage = useCallback(
- (event: string) => {
- i18n.changeLanguage(event);
- },
- [i18n]
- );
- return (
- <>
- {LOCALES.map(option => {
- return (
- {
- changeLanguage(option.tag);
- }}
- >
- {option.originalName}
-
- );
- })}
- >
- );
-};
-export const LanguageMenu: React.FC = () => {
- const { i18n } = useTranslation();
-
- const currentLanguage = LOCALES.find(item => item.tag === i18n.language);
-
- return (
- ) as ReactElement}
- placement="bottom"
- trigger="click"
- disablePortal={true}
- >
- }
- iconPosition="end"
- noBorder={true}
- style={{ textTransform: 'capitalize', padding: '0 12px' }}
- data-testid="language-menu-button"
- >
- {currentLanguage?.originalName}
-
-
- );
-};
-
-const ListItem = styled(MenuItem)(() => ({
- height: '38px',
- fontSize: 'var(--affine-font-base)',
- textTransform: 'capitalize',
- padding: '0 24px',
-}));
diff --git a/tests/parallels/change-page-mode.spec.ts b/tests/parallels/change-page-mode.spec.ts
index 78bc448243..0cabe6dcea 100644
--- a/tests/parallels/change-page-mode.spec.ts
+++ b/tests/parallels/change-page-mode.spec.ts
@@ -9,7 +9,7 @@ test('Switch to edgeless by switch edgeless item', async ({ page }) => {
await waitMarkdownImported(page);
const btn = await page.getByTestId('switch-edgeless-mode-button');
await btn.click();
-
+ await page.waitForTimeout(100);
const edgeless = page.locator('affine-edgeless-page');
expect(await edgeless.isVisible()).toBe(true);
@@ -27,7 +27,6 @@ test('Convert to edgeless by editor header items', async ({ page }) => {
await clickPageMoreActions(page);
const menusEdgelessItem = page.getByTestId('editor-option-menu-edgeless');
await menusEdgelessItem.click({ delay: 100 });
-
const edgeless = page.locator('affine-edgeless-page');
expect(await edgeless.isVisible()).toBe(true);
});
diff --git a/tests/parallels/local-first-workspace.spec.ts b/tests/parallels/local-first-workspace.spec.ts
index 3b87014a4c..c9a36bf67e 100644
--- a/tests/parallels/local-first-workspace.spec.ts
+++ b/tests/parallels/local-first-workspace.spec.ts
@@ -3,7 +3,6 @@ import { expect } from '@playwright/test';
import { openHomePage } from '../libs/load-page';
import { waitMarkdownImported } from '../libs/page-logic';
-import { clickSideBarCurrentWorkspaceBanner } from '../libs/sidebar';
import { assertCurrentWorkspaceFlavour } from '../libs/workspace';
test('preset workspace name', async ({ page }) => {
@@ -24,7 +23,9 @@ test('preset workspace name', async ({ page }) => {
test('Open language switch menu', async ({ page }) => {
await openHomePage(page);
await waitMarkdownImported(page);
- await clickSideBarCurrentWorkspaceBanner(page);
+ const editorOptionMenuButton = page.getByTestId('editor-option-menu');
+ await expect(editorOptionMenuButton).toBeVisible();
+ await editorOptionMenuButton.click();
const languageMenuButton = page.getByTestId('language-menu-button');
await expect(languageMenuButton).toBeVisible();
const actual = await languageMenuButton.innerText();
diff --git a/tests/parallels/theme.spec.ts b/tests/parallels/theme.spec.ts
index c3a5e8eda8..c93edd89d5 100644
--- a/tests/parallels/theme.spec.ts
+++ b/tests/parallels/theme.spec.ts
@@ -22,32 +22,10 @@ test('default white', async ({ browser }) => {
await page.screenshot({
path: resolve(testResultDir, 'affine-light-theme.png'),
});
+ await page.getByTestId('editor-option-menu').click();
await page.getByTestId('change-theme-dark').click();
await page.waitForTimeout(50);
await page.screenshot({
path: resolve(testResultDir, 'affine-dark-theme.png'),
});
});
-
-// test('change theme to dark', async ({ page }) => {
-// const changeThemeContainer = page.locator(
-// '[data-testid=change-theme-container]'
-// );
-// const box = await changeThemeContainer.boundingBox();
-// expect(box?.x).not.toBeUndefined();
-//
-// await page.mouse.move((box?.x ?? 0) + 5, (box?.y ?? 0) + 5);
-// await page.waitForTimeout(1000);
-// const darkButton = page.locator('[data-testid=change-theme-dark]');
-// const darkButtonPositionTop = await darkButton.evaluate(
-// element => element.getBoundingClientRect().y
-// );
-// expect(darkButtonPositionTop).toBe(box?.y);
-//
-// await page.mouse.click((box?.x ?? 0) + 5, (box?.y ?? 0) + 5);
-// const root = page.locator('html');
-// const themeMode = await root.evaluate(element =>
-// element.getAttribute('data-theme')
-// );
-// expect(themeMode).toBe('dark');
-// });