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 = ( <> - { - setPageMeta(pageId, { favorite: !favorite }); - toast( - favorite ? t('Removed from Favorites') : t('Added to Favorites') - ); - }} - icon={ - favorite ? ( - - ) : ( - - ) - } - > - {favorite ? t('Remove from favorites') : t('Add to Favorites')} - - : } - 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); + <> + { + setPageMeta(pageId, { favorite: !favorite }); + toast( + favorite ? t('Removed from Favorites') : t('Added to Favorites') + ); }} - /> - )} + icon={ + favorite ? ( + + ) : ( + + ) + } + > + {favorite ? t('Remove from favorites') : t('Add to Favorites')} + + : } + 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} - > - - - ); -}; - -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'); -// });