From 10b4558947f3a708c476248474b4127e7caaea94 Mon Sep 17 00:00:00 2001 From: Peng Xiao Date: Fri, 12 May 2023 11:13:51 +0800 Subject: [PATCH] feat: new sidebar (app shell) styles (#2303) --- apps/electron/layers/main/src/main-window.ts | 2 +- .../components/affine/sidebar-switch/style.ts | 2 +- .../blocksuite/workspace-header/header.tsx | 5 +- .../blocksuite/workspace-header/styles.css.ts | 11 +- .../WorkspaceSelector/styles.ts | 2 +- .../favorite/empty-item.tsx | 7 +- .../favorite/favorite-list.tsx | 58 ++--- .../workspace-slider-bar/favorite/index.tsx | 67 +---- .../pure/workspace-slider-bar/icons.tsx | 13 - .../pure/workspace-slider-bar/index.tsx | 7 +- .../workspace-slider-bar/route-navigation.tsx | 31 --- .../src/components/root-app-sidebar/index.tsx | 235 +++++++----------- apps/web/src/layouts/workspace-layout.tsx | 12 +- .../app-sidebar/add-page-button/index.css.ts | 30 +++ .../add-page-button/index.stories.tsx | 16 ++ .../app-sidebar/add-page-button/index.tsx | 31 +++ .../app-updater-button/dot-animation.svg | 34 +++ .../app-updater-button/index.css.ts | 132 ++++++++++ .../app-updater-button/index.stories.tsx | 16 ++ .../app-sidebar/app-updater-button/index.tsx | 36 +++ .../app-sidebar/category-divider/index.css.ts | 16 ++ .../category-divider/index.stories.tsx | 16 ++ .../app-sidebar/category-divider/index.tsx | 17 ++ .../components/app-sidebar/fallback.css.ts | 1 - .../src/components/app-sidebar/index.css.ts | 111 +++------ .../src/components/app-sidebar/index.jotai.ts | 2 + .../components/app-sidebar/index.stories.tsx | 96 +++++-- .../src/components/app-sidebar/index.tsx | 206 ++++++--------- .../app-sidebar/menu-item/index.css.ts | 48 ++++ .../app-sidebar/menu-item/index.stories.tsx | 34 +++ .../app-sidebar/menu-item/index.tsx | 47 ++++ .../quick-search-input/index.css.ts | 35 +++ .../quick-search-input/index.stories.tsx | 16 ++ .../app-sidebar/quick-search-input/index.tsx | 32 +++ .../app-sidebar/resize-indicator/index.css.ts | 23 +- .../app-sidebar/resize-indicator/index.tsx | 74 +++--- .../sidebar-containers/index.css.ts | 75 ++++++ .../app-sidebar/sidebar-containers/index.tsx | 61 +++++ .../app-sidebar/sidebar-header/index.tsx | 52 ++++ .../src/components/changeLog/index.css.ts | 2 +- .../src/components/workspace/index.css.ts | 34 ++- .../src/components/workspace/index.tsx | 10 +- packages/env/package.json | 1 + packages/i18n/src/resources/en.json | 2 + .../parallels/local-first-delete-page.spec.ts | 4 +- .../parallels/local-first-export-page.spec.ts | 4 +- .../local-first-favorite-page.spec.ts | 8 +- .../local-first-favorites-items.spec.ts | 14 +- tests/parallels/local-first-new-page.spec.ts | 2 +- .../local-first-openpage-newtab.spec.ts | 2 +- .../local-first-restore-page.spec.ts | 6 +- .../local-first-show-delete-modal.spec.ts | 4 +- .../parallels/local-first-trash-page.spec.ts | 4 +- tests/parallels/open-affine.spec.ts | 2 +- 54 files changed, 1166 insertions(+), 642 deletions(-) delete mode 100644 apps/web/src/components/pure/workspace-slider-bar/icons.tsx delete mode 100644 apps/web/src/components/pure/workspace-slider-bar/route-navigation.tsx create mode 100644 packages/component/src/components/app-sidebar/add-page-button/index.css.ts create mode 100644 packages/component/src/components/app-sidebar/add-page-button/index.stories.tsx create mode 100644 packages/component/src/components/app-sidebar/add-page-button/index.tsx create mode 100644 packages/component/src/components/app-sidebar/app-updater-button/dot-animation.svg create mode 100644 packages/component/src/components/app-sidebar/app-updater-button/index.css.ts create mode 100644 packages/component/src/components/app-sidebar/app-updater-button/index.stories.tsx create mode 100644 packages/component/src/components/app-sidebar/app-updater-button/index.tsx create mode 100644 packages/component/src/components/app-sidebar/category-divider/index.css.ts create mode 100644 packages/component/src/components/app-sidebar/category-divider/index.stories.tsx create mode 100644 packages/component/src/components/app-sidebar/category-divider/index.tsx create mode 100644 packages/component/src/components/app-sidebar/menu-item/index.css.ts create mode 100644 packages/component/src/components/app-sidebar/menu-item/index.stories.tsx create mode 100644 packages/component/src/components/app-sidebar/menu-item/index.tsx create mode 100644 packages/component/src/components/app-sidebar/quick-search-input/index.css.ts create mode 100644 packages/component/src/components/app-sidebar/quick-search-input/index.stories.tsx create mode 100644 packages/component/src/components/app-sidebar/quick-search-input/index.tsx create mode 100644 packages/component/src/components/app-sidebar/sidebar-containers/index.css.ts create mode 100644 packages/component/src/components/app-sidebar/sidebar-containers/index.tsx create mode 100644 packages/component/src/components/app-sidebar/sidebar-header/index.tsx diff --git a/apps/electron/layers/main/src/main-window.ts b/apps/electron/layers/main/src/main-window.ts index e58dcf3ac7..a527aa34c4 100644 --- a/apps/electron/layers/main/src/main-window.ts +++ b/apps/electron/layers/main/src/main-window.ts @@ -28,7 +28,7 @@ async function createWindow() { y: mainWindowState.y, width: mainWindowState.width, minWidth: 640, - transparent: isMacOS(), + minHeight: 480, visualEffectState: 'active', vibrancy: 'under-window', height: mainWindowState.height, diff --git a/apps/web/src/components/affine/sidebar-switch/style.ts b/apps/web/src/components/affine/sidebar-switch/style.ts index b8e3a70ddd..c9a9749dc4 100644 --- a/apps/web/src/components/affine/sidebar-switch/style.ts +++ b/apps/web/src/components/affine/sidebar-switch/style.ts @@ -7,7 +7,7 @@ export const StyledSidebarSwitch = styled(IconButton, { })<{ visible: boolean }>(({ visible }) => { return { opacity: visible ? 1 : 0, - WebkitAppRegion: 'no-drag', + WebkitAppRegion: visible ? 'no-drag' : 'drag', transition: 'all 0.2s ease-in-out', }; }); diff --git a/apps/web/src/components/blocksuite/workspace-header/header.tsx b/apps/web/src/components/blocksuite/workspace-header/header.tsx index 69653c652f..8f78a3972b 100644 --- a/apps/web/src/components/blocksuite/workspace-header/header.tsx +++ b/apps/web/src/components/blocksuite/workspace-header/header.tsx @@ -4,7 +4,7 @@ import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { WorkspaceFlavour } from '@affine/workspace/type'; import { CloseIcon, MinusIcon, RoundedRectangleIcon } from '@blocksuite/icons'; import type { Page } from '@blocksuite/store'; -import { useAtom } from 'jotai'; +import { useAtom, useAtomValue } from 'jotai'; import type { FC, HTMLAttributes, PropsWithChildren } from 'react'; import { forwardRef, @@ -161,7 +161,7 @@ export const Header = forwardRef< setShowWarning(shouldShowWarning()); setShowGuideDownloadClientTip(shouldShowGuideDownloadClientTip); }, [shouldShowGuideDownloadClientTip]); - const [open] = useAtom(appSidebarOpenAtom); + const open = useAtomValue(appSidebarOpenAtom); const t = useAFFiNEI18N(); const mode = useCurrentMode(); @@ -189,7 +189,6 @@ export const Header = forwardRef< className={styles.header} data-has-warning={showWarning} data-testid="editor-header-items" - data-tauri-drag-region data-is-edgeless={mode === 'edgeless'} > diff --git a/apps/web/src/components/blocksuite/workspace-header/styles.css.ts b/apps/web/src/components/blocksuite/workspace-header/styles.css.ts index 9c062765e4..3dd7ab9d18 100644 --- a/apps/web/src/components/blocksuite/workspace-header/styles.css.ts +++ b/apps/web/src/components/blocksuite/workspace-header/styles.css.ts @@ -6,18 +6,9 @@ export const headerContainer = style({ position: 'sticky', top: 0, background: 'var(--affine-background-primary-color)', + // @ts-ignore WebkitAppRegion: 'drag', zIndex: 'var(--affine-z-index-popover)', - '@media': { - '(max-width: 768px)': { - selectors: { - '&[data-open="true"]': { - // @ts-ignore - WebkitAppRegion: 'no-drag', - }, - }, - }, - }, selectors: { '&[data-has-warning="true"]': { height: '96px', diff --git a/apps/web/src/components/pure/workspace-slider-bar/WorkspaceSelector/styles.ts b/apps/web/src/components/pure/workspace-slider-bar/WorkspaceSelector/styles.ts index 1f01c09214..a64c8da32e 100644 --- a/apps/web/src/components/pure/workspace-slider-bar/WorkspaceSelector/styles.ts +++ b/apps/web/src/components/pure/workspace-slider-bar/WorkspaceSelector/styles.ts @@ -6,7 +6,7 @@ export const StyledSelectorContainer = styled('div')(() => { display: 'flex', alignItems: 'center', padding: '0 6px', - marginBottom: '16px', + margin: '0 -6px', borderRadius: '8px', color: 'var(--affine-text-primary-color)', ':hover': { diff --git a/apps/web/src/components/pure/workspace-slider-bar/favorite/empty-item.tsx b/apps/web/src/components/pure/workspace-slider-bar/favorite/empty-item.tsx index 96ee3f66f4..0937b0cdaa 100644 --- a/apps/web/src/components/pure/workspace-slider-bar/favorite/empty-item.tsx +++ b/apps/web/src/components/pure/workspace-slider-bar/favorite/empty-item.tsx @@ -1,13 +1,10 @@ +import { MenuItem } from '@affine/component/app-sidebar'; import { useAFFiNEI18N } from '@affine/i18n/hooks'; -import { StyledCollapseItem } from '../shared-styles'; - export const EmptyItem = () => { const t = useAFFiNEI18N(); return ( - - {t['Favorite pages for easy access']()} - + {t['Favorite pages for easy access']()} ); }; diff --git a/apps/web/src/components/pure/workspace-slider-bar/favorite/favorite-list.tsx b/apps/web/src/components/pure/workspace-slider-bar/favorite/favorite-list.tsx index 3a6f2d607d..d58efde4ec 100644 --- a/apps/web/src/components/pure/workspace-slider-bar/favorite/favorite-list.tsx +++ b/apps/web/src/components/pure/workspace-slider-bar/favorite/favorite-list.tsx @@ -1,20 +1,18 @@ -import { MuiCollapse } from '@affine/component'; +import { MenuLinkItem } from '@affine/component/app-sidebar'; import { EdgelessIcon, PageIcon } from '@blocksuite/icons'; +import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta'; import { useAtomValue } from 'jotai'; import { useRouter } from 'next/router'; import { useMemo } from 'react'; import { workspacePreferredModeAtom } from '../../../../atoms'; import type { FavoriteListProps } from '../index'; -import { StyledCollapseItem } from '../shared-styles'; import EmptyItem from './empty-item'; -export const FavoriteList = ({ - pageMeta, - openPage, - showList, -}: FavoriteListProps) => { +export const FavoriteList = ({ currentWorkspace }: FavoriteListProps) => { const router = useRouter(); const record = useAtomValue(workspacePreferredModeAtom); + const pageMeta = useBlockSuitePageMeta(currentWorkspace.blockSuiteWorkspace); + const workspaceId = currentWorkspace.id; const favoriteList = useMemo( () => pageMeta.filter(p => p.favorite && !p.trash), @@ -22,45 +20,25 @@ export const FavoriteList = ({ ); return ( - + <> {favoriteList.map((pageMeta, index) => { const active = router.query.pageId === pageMeta.id; + const icon = + record[pageMeta.id] === 'edgeless' ? : ; return ( -
- { - if (ref && active) { - ref.scrollIntoView({ behavior: 'smooth' }); - } - }} - onClick={() => { - if (active) { - return; - } - openPage(pageMeta.id); - }} - > - {record[pageMeta.id] === 'edgeless' ? ( - - ) : ( - - )} - {pageMeta.title || 'Untitled'} - -
+ + {pageMeta.title || 'Untitled'} + ); })} {favoriteList.length === 0 && } -
+ ); }; diff --git a/apps/web/src/components/pure/workspace-slider-bar/favorite/index.tsx b/apps/web/src/components/pure/workspace-slider-bar/favorite/index.tsx index 12f8dbbdc8..c2c2dc0aa1 100644 --- a/apps/web/src/components/pure/workspace-slider-bar/favorite/index.tsx +++ b/apps/web/src/components/pure/workspace-slider-bar/favorite/index.tsx @@ -1,66 +1 @@ -import { useAFFiNEI18N } from '@affine/i18n/hooks'; -import { ArrowDownSmallIcon, FavoriteIcon } from '@blocksuite/icons'; -import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta'; -import { useCallback, useState } from 'react'; - -import type { AllWorkspace } from '../../../../shared'; -import type { WorkSpaceSliderBarProps } from '../index'; -import { StyledCollapseButton, StyledListItem } from '../shared-styles'; -import { StyledLink } from '../style'; -import FavoriteList from './favorite-list'; - -export const Favorite = ({ - currentPath, - paths, - currentPageId, - openPage, - currentWorkspace, -}: Pick< - WorkSpaceSliderBarProps, - 'currentPath' | 'paths' | 'currentPageId' | 'openPage' -> & { - currentWorkspace: AllWorkspace; -}) => { - const currentWorkspaceId = currentWorkspace.id; - const pageMeta = useBlockSuitePageMeta(currentWorkspace.blockSuiteWorkspace); - - const [showSubFavorite, setOpenSubFavorite] = useState(true); - - const t = useAFFiNEI18N(); - - return ( - <> - - - - {t['Favorites']()} - - { - setOpenSubFavorite(!showSubFavorite); - }, [showSubFavorite])} - collapse={showSubFavorite} - > - - - - - - ); -}; - -export default Favorite; +export * from './favorite-list'; diff --git a/apps/web/src/components/pure/workspace-slider-bar/icons.tsx b/apps/web/src/components/pure/workspace-slider-bar/icons.tsx deleted file mode 100644 index 160dac720d..0000000000 --- a/apps/web/src/components/pure/workspace-slider-bar/icons.tsx +++ /dev/null @@ -1,13 +0,0 @@ -export const Arrow = () => { - return ( - - - - ); -}; diff --git a/apps/web/src/components/pure/workspace-slider-bar/index.tsx b/apps/web/src/components/pure/workspace-slider-bar/index.tsx index 2fd3bd8632..c1b3a17b7a 100644 --- a/apps/web/src/components/pure/workspace-slider-bar/index.tsx +++ b/apps/web/src/components/pure/workspace-slider-bar/index.tsx @@ -1,12 +1,9 @@ -import type { Page, PageMeta } from '@blocksuite/store'; +import type { Page } from '@blocksuite/store'; import type { AllWorkspace } from '../../../shared'; export type FavoriteListProps = { - currentPageId: string | null; - openPage: (pageId: string) => void; - showList: boolean; - pageMeta: PageMeta[]; + currentWorkspace: AllWorkspace; }; export type WorkSpaceSliderBarProps = { diff --git a/apps/web/src/components/pure/workspace-slider-bar/route-navigation.tsx b/apps/web/src/components/pure/workspace-slider-bar/route-navigation.tsx deleted file mode 100644 index 0224f37305..0000000000 --- a/apps/web/src/components/pure/workspace-slider-bar/route-navigation.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { IconButton } from '@affine/component'; -import { ArrowLeftSmallIcon, ArrowRightSmallIcon } from '@blocksuite/icons'; - -import { StyledRouteNavigationWrapper } from './shared-styles'; - -export const RouteNavigation = () => { - if (!environment.isDesktop) { - return <>; - } - return ( - - { - window.history.back(); - }} - > - - - { - window.history.forward(); - }} - style={{ marginLeft: '32px' }} - > - - - - ); -}; diff --git a/apps/web/src/components/root-app-sidebar/index.tsx b/apps/web/src/components/root-app-sidebar/index.tsx index a0d89ea467..b609e37efa 100644 --- a/apps/web/src/components/root-app-sidebar/index.tsx +++ b/apps/web/src/components/root-app-sidebar/index.tsx @@ -1,7 +1,14 @@ import { + AddPageButton, AppSidebar, appSidebarOpenAtom, - ResizeIndicator, + AppUpdaterButton, + CategoryDivider, + MenuLinkItem, + QuickSearchInput, + SidebarContainer, + SidebarScrollableContainer, + updateAvailableAtom, } from '@affine/component/app-sidebar'; import { config } from '@affine/env'; import { useAFFiNEI18N } from '@affine/i18n/hooks'; @@ -9,27 +16,18 @@ import { WorkspaceFlavour } from '@affine/workspace/type'; import { DeleteTemporarilyIcon, FolderIcon, - PlusIcon, - SearchIcon, SettingsIcon, ShareIcon, } from '@blocksuite/icons'; import type { Page } from '@blocksuite/store'; import { useAtomValue } from 'jotai'; -import type { ReactElement, UIEvent } from 'react'; +import type { ReactElement } from 'react'; import type React from 'react'; -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect } from 'react'; import type { AllWorkspace } from '../../shared'; import ChangeLog from '../pure/workspace-slider-bar/changeLog'; -import Favorite from '../pure/workspace-slider-bar/favorite'; -import { StyledListItem } from '../pure/workspace-slider-bar/shared-styles'; -import { - StyledLink, - StyledNewPageButton, - StyledScrollWrapper, - StyledSliderBarInnerWrapper, -} from '../pure/workspace-slider-bar/style'; +import FavoriteList from '../pure/workspace-slider-bar/favorite/favorite-list'; import { WorkspaceSelector } from '../pure/workspace-slider-bar/WorkspaceSelector'; export type RootAppSidebarProps = { @@ -37,7 +35,6 @@ export type RootAppSidebarProps = { onOpenQuickSearchModal: () => void; onOpenWorkspaceListModal: () => void; currentWorkspace: AllWorkspace | null; - currentPageId: string | null; openPage: (pageId: string) => void; createPage: () => Page; currentPath: string; @@ -50,6 +47,26 @@ export type RootAppSidebarProps = { }; }; +const RouteMenuLinkItem = ({ + currentPath, + path, + icon, + children, + ...props +}: { + currentPath: string; // todo: pass through useRouter? + path?: string | null; + icon: ReactElement; + children?: ReactElement; +} & React.HTMLAttributes) => { + const active = currentPath === path; + return ( + + {children} + + ); +}; + /** * This is for the whole affine app sidebar. * This component wraps the app sidebar in `@affine/component` with logic and data. @@ -58,7 +75,6 @@ export type RootAppSidebarProps = { */ export const RootAppSidebar = ({ currentWorkspace, - currentPageId, openPage, createPage, currentPath, @@ -69,7 +85,6 @@ export const RootAppSidebar = ({ const currentWorkspaceId = currentWorkspace?.id || null; const blockSuiteWorkspace = currentWorkspace?.blockSuiteWorkspace; const t = useAFFiNEI18N(); - const [isScrollAtTop, setIsScrollAtTop] = useState(true); const onClickNewPage = useCallback(async () => { const page = await createPage(); openPage(page.id); @@ -80,160 +95,78 @@ export const RootAppSidebar = ({ window.apis?.ui.handleSidebarVisibilityChange(sidebarOpen); } }, [sidebarOpen]); - const [ref, setRef] = useState(null); - const handleQuickSearchButtonKeyDown = useCallback( - (e: React.KeyboardEvent) => { - if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault(); - onOpenQuickSearchModal(); - } - }, - [onOpenQuickSearchModal] - ); + const clientUpdateAvailable = useAtomValue(updateAvailableAtom); + return ( <> - - {t['New Page']()} - - } - > - + + - { - onOpenQuickSearchModal(); - }, [onOpenQuickSearchModal])} - onKeyDown={handleQuickSearchButtonKeyDown} + onClick={onOpenQuickSearchModal} + /> + } + currentPath={currentPath} + path={currentWorkspaceId && paths.all(currentWorkspaceId)} > -
- - {t['Quick search']()} -
-
- {t['All pages']()} + + } + currentPath={currentPath} + path={currentWorkspaceId && paths.setting(currentWorkspaceId)} > - - -
{t['Workspace Settings']()}
-
-
- - - - {t['All pages']()} - - - ) => { - (e.target as HTMLDivElement).scrollTop === 0 - ? setIsScrollAtTop(true) - : setIsScrollAtTop(false); - }} - > - {blockSuiteWorkspace && ( - - )} - -
+ {t['Settings']()} + +
+ + + + {blockSuiteWorkspace && ( + + )} {config.enableLegacyCloud && (currentWorkspace?.flavour === WorkspaceFlavour.AFFINE && currentWorkspace.public ? ( - - - - Published to web - - - ) : ( - } + currentPath={currentPath} + path={currentWorkspaceId && paths.setting(currentWorkspaceId)} > - - - {t['Shared Pages']()} - - + Published to web + + ) : ( + } + currentPath={currentPath} + path={currentWorkspaceId && paths.shared(currentWorkspaceId)} + > + {t['Shared Pages']()} + ))} - + } + currentPath={currentPath} + path={currentWorkspaceId && paths.trash(currentWorkspaceId)} > - - {t['Trash']()} - - -
+ {t['Trash']()} + + + + {clientUpdateAvailable && } + +
- ); }; diff --git a/apps/web/src/layouts/workspace-layout.tsx b/apps/web/src/layouts/workspace-layout.tsx index eaa1ca383a..3710f5e7d8 100644 --- a/apps/web/src/layouts/workspace-layout.tsx +++ b/apps/web/src/layouts/workspace-layout.tsx @@ -1,3 +1,7 @@ +import { + appSidebarOpenAtom, + appSidebarResizingAtom, +} from '@affine/component/app-sidebar'; import { AppContainer, MainContainer, @@ -330,17 +334,19 @@ export const WorkspaceLayoutInner: FC = ({ children }) => { setOpenQuickSearchModalAtom(true); }, [setOpenQuickSearchModalAtom]); + const resizing = useAtomValue(appSidebarResizingAtom); + const sidebarOpen = useAtomValue(appSidebarOpenAtom); + return ( <> {title} - + { @@ -353,7 +359,7 @@ export const WorkspaceLayoutInner: FC = ({ children }) => { currentPath={router.asPath.split('?')[0]} paths={isPublicWorkspace ? publicPathGenerator : pathGenerator} /> - + }>{children} {/* fixme(himself65): remove this */} diff --git a/packages/component/src/components/app-sidebar/add-page-button/index.css.ts b/packages/component/src/components/app-sidebar/add-page-button/index.css.ts new file mode 100644 index 0000000000..f3f1c08a97 --- /dev/null +++ b/packages/component/src/components/app-sidebar/add-page-button/index.css.ts @@ -0,0 +1,30 @@ +import { style } from '@vanilla-extract/css'; + +export const root = style({ + display: 'inline-flex', + background: 'var(--affine-white-30)', + alignItems: 'center', + borderRadius: '8px', + border: '1px solid var(--affine-black-10)', + fontSize: 'var(--affine-font-sm)', + width: '100%', + height: '52px', + userSelect: 'none', + cursor: 'pointer', + padding: '0 24px', + selectors: { + '&:hover': { + background: 'var(--affine-hover-color)', + }, + }, +}); + +export const icon = style({ + marginRight: '18px', + color: 'var(--affine-icon-color)', + fontSize: '24px', +}); + +export const spacer = style({ + flex: 1, +}); diff --git a/packages/component/src/components/app-sidebar/add-page-button/index.stories.tsx b/packages/component/src/components/app-sidebar/add-page-button/index.stories.tsx new file mode 100644 index 0000000000..5619d04f58 --- /dev/null +++ b/packages/component/src/components/app-sidebar/add-page-button/index.stories.tsx @@ -0,0 +1,16 @@ +import type { Meta, StoryFn } from '@storybook/react'; + +import { AddPageButton } from '.'; + +export default { + title: 'Components/AppSidebar/AddPageButton', + component: AddPageButton, +} satisfies Meta; + +export const Default: StoryFn = () => { + return ( +
+ alert('opened')} /> +
+ ); +}; diff --git a/packages/component/src/components/app-sidebar/add-page-button/index.tsx b/packages/component/src/components/app-sidebar/add-page-button/index.tsx new file mode 100644 index 0000000000..49bda1f2be --- /dev/null +++ b/packages/component/src/components/app-sidebar/add-page-button/index.tsx @@ -0,0 +1,31 @@ +import { useAFFiNEI18N } from '@affine/i18n/hooks'; +import { PlusIcon } from '@blocksuite/icons'; +import clsx from 'clsx'; + +import * as styles from './index.css'; + +interface AddPageButtonProps { + onClick?: () => void; + className?: string; + style?: React.CSSProperties; +} + +// Although it is called an input, it is actually a button. +export function AddPageButton({ + onClick, + className, + style, +}: AddPageButtonProps) { + const t = useAFFiNEI18N(); + + return ( + + ); +} diff --git a/packages/component/src/components/app-sidebar/app-updater-button/dot-animation.svg b/packages/component/src/components/app-sidebar/app-updater-button/dot-animation.svg new file mode 100644 index 0000000000..b062c0a0e0 --- /dev/null +++ b/packages/component/src/components/app-sidebar/app-updater-button/dot-animation.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/component/src/components/app-sidebar/app-updater-button/index.css.ts b/packages/component/src/components/app-sidebar/app-updater-button/index.css.ts new file mode 100644 index 0000000000..a206b8b2ff --- /dev/null +++ b/packages/component/src/components/app-sidebar/app-updater-button/index.css.ts @@ -0,0 +1,132 @@ +import { style } from '@vanilla-extract/css'; + +export const root = style({ + display: 'inline-flex', + background: 'var(--affine-white-30)', + alignItems: 'center', + borderRadius: '8px', + border: '1px solid var(--affine-black-10)', + fontSize: 'var(--affine-font-sm)', + width: '100%', + height: '52px', + userSelect: 'none', + cursor: 'pointer', + padding: '0 12px', + position: 'relative', + selectors: { + '&:hover': { + background: 'var(--affine-hover-color)', + }, + '&:before': { + content: "''", + position: 'absolute', + top: '-3px', + right: '-3px', + width: '8px', + height: '8px', + backgroundColor: 'var(--affine-primary-color)', + borderRadius: '50%', + zIndex: 1, + opacity: 1, + transition: '0.3s ease', + }, + }, + vars: { + '--svg-dot-animation': `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 122 116'%3E%3Cpath id='b' stroke='%23fff' stroke-linecap='round' stroke-width='0' d='M17.9256 115C17.434 111.774 13.1701 104.086 13.4282 95.6465C13.6862 87.207 18.6628 76.0721 17.9256 64.3628C17.1883 52.6535 8.7772 35.9512 9.00452 25.3907C9.23185 14.8302 16.2114 5.06512 17.9256 1'/%3E%3Cpath id='d' stroke='%23fff' stroke-linecap='round' stroke-width='0' d='M84.1628 115C85.2376 112.055 94.5618 98.8394 93.9975 91.1338C93.4332 83.4281 82.5505 73.2615 84.1628 62.5704C85.775 51.8793 96.4803 35.4248 95.9832 25.7826C95.4861 16.1404 87.9113 4.71163 84.1628 1'/%3E%3Cpath id='f' stroke='%23fff' stroke-linecap='round' stroke-width='0' d='M37.0913 115C37.9604 111.921 44.4347 99.4545 45.3816 92.9773C48.9305 68.7011 35.7877 73.9552 37.0913 62.7781C38.3949 51.6011 47.3889 36.9895 46.9869 26.9091C46.585 16.8286 40.1222 4.88034 37.0913 1'/%3E%3Cpath id='h' stroke='%23fff' stroke-linecap='round' stroke-width='0' d='M112.443 115C111.698 112.235 108.25 106.542 107.715 93.7582C107.241 82.4286 107.229 83.9543 112.443 66.1429C116.085 44.0408 100.661 42.5908 101.006 33.539C101.35 24.4871 109.843 4.48439 112.443 1'/%3E%3Cg%3E%3Ccircle r='1.5' fill='rgba(96, 70, 254, 0.3)'%3E%3CanimateMotion dur='10s' repeatCount='indefinite'%3E%3Cmpath href='%23b' /%3E%3C/animateMotion%3E%3C/circle%3E%3C/g%3E%3Cg%3E%3Ccircle r='1' fill='rgba(96, 70, 254, 0.3)' fill-opacity='1' shape-rendering='crispEdges'%3E%3CanimateMotion dur='8s' repeatCount='indefinite'%3E%3Cmpath href='%23d' /%3E%3C/animateMotion%3E%3C/circle%3E%3C/g%3E%3Cg%3E%3Ccircle r='.5' fill='rgba(96, 70, 254, 0.3)' fill-opacity='1' shape-rendering='crispEdges'%3E%3CanimateMotion dur='4s' repeatCount='indefinite'%3E%3Cmpath href='%23f' /%3E%3C/animateMotion%3E%3C/circle%3E%3C/g%3E%3Cg%3E%3Ccircle r='.8' fill='rgba(96, 70, 254, 0.3)' fill-opacity='1' shape-rendering='crispEdges'%3E%3CanimateMotion dur='6s' repeatCount='indefinite'%3E%3Cmpath href='%23h' /%3E%3C/animateMotion%3E%3C/circle%3E%3C/g%3E%3C/svg%3E")`, + }, +}); + +export const icon = style({ + marginRight: '18px', + color: 'var(--affine-primary-color)', + fontSize: '24px', +}); + +export const particles = style({ + background: `var(--svg-dot-animation), var(--svg-dot-animation)`, + backgroundRepeat: 'no-repeat, repeat', + backgroundPosition: 'center, center top 100%', + backgroundSize: '100%, 130%', + WebkitMaskImage: + 'linear-gradient(to top, transparent, black, black, transparent)', + width: '100%', + height: '100%', + position: 'absolute', + left: 0, +}); + +export const particlesBefore = style({ + content: '""', + display: 'block', + position: 'absolute', + width: '100%', + height: '100%', + background: `var(--svg-dot-animation), var(--svg-dot-animation), var(--svg-dot-animation)`, + backgroundRepeat: 'no-repeat, repeat, repeat', + backgroundPosition: 'center, center top 100%, center center', + backgroundSize: '100% 120%, 150%, 120%', + filter: 'blur(1px)', + willChange: 'filter', +}); + +export const installLabel = style({ + display: 'flex', + justifyContent: 'flex-start', + alignItems: 'center', + width: '100%', + height: '100%', + fontSize: 'var(--affine-font-sm)', + whiteSpace: 'nowrap', +}); + +export const installLabelNormal = style([ + installLabel, + { + selectors: { + [`${root}:hover &`]: { + display: 'none', + }, + }, + }, +]); + +export const installLabelHover = style([ + installLabel, + { + display: 'none', + selectors: { + [`${root}:hover &`]: { + display: 'flex', + }, + }, + }, +]); + +export const halo = style({ + overflow: 'hidden', + width: '100%', + height: '100%', + position: 'absolute', + top: 0, + left: 0, + ':before': { + content: '""', + display: 'block', + width: '60%', + height: '40%', + position: 'absolute', + top: '80%', + left: '50%', + background: + 'linear-gradient(180deg, rgba(50, 26, 206, 0.1) 10%, rgba(50, 26, 206, 0.35) 30%, rgba(84, 56, 255, 1) 50%)', + filter: 'blur(10px) saturate(1.2)', + transform: 'translateX(-50%) translateY(calc(0 * 1%)) scale(0)', + transition: '0.3s ease', + willChange: 'filter', + }, + selectors: { + '&:hover:before': { + transform: 'translateX(-50%) translateY(calc(-70 * 1%)) scale(1)', + }, + }, +}); diff --git a/packages/component/src/components/app-sidebar/app-updater-button/index.stories.tsx b/packages/component/src/components/app-sidebar/app-updater-button/index.stories.tsx new file mode 100644 index 0000000000..110293e588 --- /dev/null +++ b/packages/component/src/components/app-sidebar/app-updater-button/index.stories.tsx @@ -0,0 +1,16 @@ +import type { Meta, StoryFn } from '@storybook/react'; + +import { AppUpdaterButton } from '.'; + +export default { + title: 'Components/AppSidebar/AppUpdaterButton', + component: AppUpdaterButton, +} satisfies Meta; + +export const Default: StoryFn = () => { + return ( +
+ +
+ ); +}; diff --git a/packages/component/src/components/app-sidebar/app-updater-button/index.tsx b/packages/component/src/components/app-sidebar/app-updater-button/index.tsx new file mode 100644 index 0000000000..df05bf422a --- /dev/null +++ b/packages/component/src/components/app-sidebar/app-updater-button/index.tsx @@ -0,0 +1,36 @@ +import { useAFFiNEI18N } from '@affine/i18n/hooks'; +import { ResetIcon } from '@blocksuite/icons'; +import clsx from 'clsx'; + +import * as styles from './index.css'; + +interface AddPageButtonProps { + className?: string; + style?: React.CSSProperties; +} + +// Although it is called an input, it is actually a button. +export function AppUpdaterButton({ className, style }: AddPageButtonProps) { + const t = useAFFiNEI18N(); + + return ( + + ); +} diff --git a/packages/component/src/components/app-sidebar/category-divider/index.css.ts b/packages/component/src/components/app-sidebar/category-divider/index.css.ts new file mode 100644 index 0000000000..2f67a9a0b0 --- /dev/null +++ b/packages/component/src/components/app-sidebar/category-divider/index.css.ts @@ -0,0 +1,16 @@ +import { style } from '@vanilla-extract/css'; + +export const root = style({ + fontSize: 'var(--affine-font-xs)', + height: '16px', + userSelect: 'none', + selectors: { + '&:not(:first-of-type)': { + marginTop: '10px', + }, + }, +}); + +export const label = style({ + color: 'var(--affine-black-30)', +}); diff --git a/packages/component/src/components/app-sidebar/category-divider/index.stories.tsx b/packages/component/src/components/app-sidebar/category-divider/index.stories.tsx new file mode 100644 index 0000000000..5239c42c58 --- /dev/null +++ b/packages/component/src/components/app-sidebar/category-divider/index.stories.tsx @@ -0,0 +1,16 @@ +import type { Meta, StoryFn } from '@storybook/react'; + +import { CategoryDivider } from '.'; + +export default { + title: 'Components/AppSidebar/CategoryDivider', + component: CategoryDivider, +} satisfies Meta; + +export const Default: StoryFn = () => { + return ( +
+ +
+ ); +}; diff --git a/packages/component/src/components/app-sidebar/category-divider/index.tsx b/packages/component/src/components/app-sidebar/category-divider/index.tsx new file mode 100644 index 0000000000..b4a703675c --- /dev/null +++ b/packages/component/src/components/app-sidebar/category-divider/index.tsx @@ -0,0 +1,17 @@ +import clsx from 'clsx'; +import type { PropsWithChildren } from 'react'; + +import * as styles from './index.css'; + +interface CategoryDividerProps extends PropsWithChildren { + label: string; +} + +export function CategoryDivider({ label, children }: CategoryDividerProps) { + return ( +
+
{label}
+ {children} +
+ ); +} diff --git a/packages/component/src/components/app-sidebar/fallback.css.ts b/packages/component/src/components/app-sidebar/fallback.css.ts index 8a38d29eb1..15925f7d77 100644 --- a/packages/component/src/components/app-sidebar/fallback.css.ts +++ b/packages/component/src/components/app-sidebar/fallback.css.ts @@ -6,7 +6,6 @@ export const fallbackStyle = style({ }); export const fallbackHeaderStyle = style({ - padding: '0 6px', height: '58px', width: '100%', display: 'flex', diff --git a/packages/component/src/components/app-sidebar/index.css.ts b/packages/component/src/components/app-sidebar/index.css.ts index 5c69b8ea26..1e30c196b5 100644 --- a/packages/component/src/components/app-sidebar/index.css.ts +++ b/packages/component/src/components/app-sidebar/index.css.ts @@ -5,27 +5,26 @@ import { createVar, style } from '@vanilla-extract/css'; export const floatingMaxWidth = 768; export const navWidthVar = createVar('nav-width'); -export const navStyle = style({ +export const navWrapperStyle = style({ + vars: { + [navWidthVar]: '256px', + }, position: 'relative', - backgroundColor: 'var(--affine-background-secondary-color)', width: navWidthVar, minWidth: navWidthVar, height: '100%', - display: 'flex', - flexDirection: 'column', - transition: 'margin-left .3s, width .3s', - zIndex: parseInt(baseTheme.zIndexModal), - borderRight: '1px solid var(--affine-border-color)', + backgroundColor: 'var(--affine-background-secondary-color)', '@media': { [`(max-width: ${floatingMaxWidth}px)`]: { position: 'absolute', - width: `calc(10vw + ${navWidthVar})`, + width: `calc(${navWidthVar})`, + zIndex: 2, selectors: { '&[data-open="false"]': { marginLeft: `calc((10vw + ${navWidthVar}) * -1)`, }, '&[data-is-macos-electron="true"]': { - backgroundColor: 'var(--affine-background-secondary-color)', + backgroundColor: 'var(--affine-background-primary-color)', }, }, }, @@ -37,19 +36,30 @@ export const navStyle = style({ '&[data-is-macos-electron="true"]': { backgroundColor: 'transparent', }, + '&[data-enable-animation="true"]': { + transition: 'margin-left .3s, width .3s', + }, }, - vars: { - [navWidthVar]: '256px', - }, +}); + +export const navStyle = style({ + position: 'relative', + width: '100%', + height: '100%', + display: 'flex', + flexDirection: 'column', + zIndex: parseInt(baseTheme.zIndexModal), + borderRight: '1px solid transparent', }); export const navHeaderStyle = style({ flex: '0 0 auto', height: '52px', - padding: '0px 16px 0px 10px', + padding: '0px 16px', display: 'flex', justifyContent: 'space-between', alignItems: 'center', + gap: '32px', '@media': { [`(max-width: ${floatingMaxWidth}px)`]: { selectors: { @@ -69,15 +79,13 @@ export const navHeaderStyle = style({ export const navBodyStyle = style({ flex: '1 1 auto', -}); - -export const navFooterStyle = style({ - flex: '0 0 auto', - borderTop: '1px solid var(--affine-border-color)', + height: 'calc(100% - 52px)', + display: 'flex', + flexDirection: 'column', }); export const sidebarButtonStyle = style({ - width: '32px', + width: 'auto', height: '32px', color: 'var(--affine-icon-color)', }); @@ -91,7 +99,7 @@ export const sidebarFloatMaskStyle = style({ left: 0, right: '100%', bottom: 0, - zIndex: parseInt(baseTheme.zIndexModal) - 1, + zIndex: 1, background: 'var(--affine-background-modal-color)', '@media': { [`(max-width: ${floatingMaxWidth}px)`]: { @@ -105,66 +113,3 @@ export const sidebarFloatMaskStyle = style({ }, }, }); - -export const haloStyle = style({ - overflow: 'hidden', - width: '100%', - height: '100%', - position: 'absolute', - top: 0, - left: 0, - ':before': { - content: '""', - display: 'block', - width: '60%', - height: '40%', - position: 'absolute', - top: '80%', - left: '50%', - background: - 'linear-gradient(180deg, rgba(50, 26, 206, 0.1) 10%, rgba(50, 26, 206, 0.35) 30%, rgba(84, 56, 255, 1) 50%)', - filter: 'blur(10px) saturate(1.2)', - transform: 'translateX(-50%) translateY(calc(0 * 1%)) scale(0)', - transition: '0.3s ease', - willChange: 'filter', - }, - selectors: { - '&:hover:before': { - transform: 'translateX(-50%) translateY(calc(-70 * 1%)) scale(1)', - }, - }, -}); - -export const updaterButtonStyle = style({}); -export const particlesStyle = style({ - background: `var(--svg-animation), var(--svg-animation)`, - backgroundRepeat: 'no-repeat, repeat', - backgroundPosition: 'center, center top 100%', - backgroundSize: '100%, 130%', - WebkitMaskImage: - 'linear-gradient(to top, transparent, black, black, transparent)', - width: '100%', - height: '100%', - position: 'absolute', -}); - -export const particlesBefore = style({ - content: '""', - display: 'block', - position: 'absolute', - width: '100%', - height: '100%', - background: `var(--svg-animation), var(--svg-animation), var(--svg-animation)`, - backgroundRepeat: 'no-repeat, repeat, repeat', - backgroundPosition: 'center, center top 100%, center center', - backgroundSize: '100% 120%, 150%, 120%', - filter: 'blur(1px)', - willChange: 'filter', -}); - -export const installLabelStyle = style({ - display: 'flex', - justifyContent: 'flex-start', - alignItems: 'center', - paddingLeft: '8px', -}); diff --git a/packages/component/src/components/app-sidebar/index.jotai.ts b/packages/component/src/components/app-sidebar/index.jotai.ts index 429f9af6b5..ab99ed7de4 100644 --- a/packages/component/src/components/app-sidebar/index.jotai.ts +++ b/packages/component/src/components/app-sidebar/index.jotai.ts @@ -1,3 +1,4 @@ +import { atom } from 'jotai'; import { atomWithObservable } from 'jotai/utils'; import { atomWithStorage } from 'jotai/utils'; import { Observable } from 'rxjs'; @@ -7,6 +8,7 @@ export const appSidebarOpenAtom = atomWithStorage( APP_SIDEBAR_OPEN, undefined as boolean | undefined ); +export const appSidebarResizingAtom = atom(false); export const appSidebarWidthAtom = atomWithStorage( 'app-sidebar-width', 256 /* px */ diff --git a/packages/component/src/components/app-sidebar/index.stories.tsx b/packages/component/src/components/app-sidebar/index.stories.tsx index 7b2c0fb5b9..2ba818965e 100644 --- a/packages/component/src/components/app-sidebar/index.stories.tsx +++ b/packages/component/src/components/app-sidebar/index.stories.tsx @@ -1,17 +1,23 @@ import { IconButton } from '@affine/component'; -import { SidebarIcon } from '@blocksuite/icons'; +import { + DeleteTemporarilyIcon, + SettingsIcon, + SidebarIcon, +} from '@blocksuite/icons'; import type { Meta, StoryFn } from '@storybook/react'; import { useAtom } from 'jotai'; import type { PropsWithChildren } from 'react'; -import { useState } from 'react'; -import { - AppSidebar, - AppSidebarFallback, - appSidebarOpenAtom, - ResizeIndicator, -} from '.'; +import { AppSidebar, AppSidebarFallback, appSidebarOpenAtom } from '.'; +import { AddPageButton } from './add-page-button'; +import { CategoryDivider } from './category-divider'; import { navHeaderStyle, sidebarButtonStyle } from './index.css'; +import { MenuLinkItem } from './menu-item'; +import { QuickSearchInput } from './quick-search-input'; +import { + SidebarContainer, + SidebarScrollableContainer, +} from './sidebar-containers'; export default { title: 'Components/AppSidebar', @@ -23,7 +29,7 @@ const Container = ({ children }: PropsWithChildren) => ( style={{ position: 'relative', width: '100vw', - height: '600px', + height: 'calc(100vh - 40px)', overflow: 'hidden', display: 'flex', flexDirection: 'row', @@ -51,17 +57,12 @@ const Main = () => { ); }; -const Footer = () =>
Add Page
; export const Default: StoryFn = () => { - const [ref, setRef] = useState(null); return ( <> - } ref={setRef}> - Test - - +
@@ -76,3 +77,68 @@ export const Fallback = () => { ); }; + +export const WithItems: StoryFn = () => { + return ( + + + + +
+ } + href="/test" + onClick={() => alert('opened')} + > + Settings + + } + href="/test" + onClick={() => alert('opened')} + > + Settings + + } + href="/test" + onClick={() => alert('opened')} + > + Settings + + + + + + } + href="/test" + onClick={() => alert('opened')} + > + Settings + + } + href="/test" + onClick={() => alert('opened')} + > + Settings + + + + } + href="/test" + onClick={() => alert('opened')} + > + Trash + + + + + + +
+ + ); +}; diff --git a/packages/component/src/components/app-sidebar/index.tsx b/packages/component/src/components/app-sidebar/index.tsx index db3d807a54..3f529d7942 100644 --- a/packages/component/src/components/app-sidebar/index.tsx +++ b/packages/component/src/components/app-sidebar/index.tsx @@ -1,156 +1,102 @@ -import { Button } from '@affine/component'; import { getEnvironment } from '@affine/env'; -import { useAFFiNEI18N } from '@affine/i18n/hooks'; -import { - ArrowLeftSmallIcon, - ArrowRightSmallIcon, - ResetIcon, - SidebarIcon, -} from '@blocksuite/icons'; import { assignInlineVars } from '@vanilla-extract/dynamic'; import { useAtom, useAtomValue } from 'jotai'; import type { PropsWithChildren, ReactElement } from 'react'; -import type { ReactNode } from 'react'; -import { forwardRef, useCallback, useEffect } from 'react'; +import { useEffect, useRef, useState } from 'react'; -import { IconButton } from '../../ui/button/icon-button'; import { floatingMaxWidth, - haloStyle, - installLabelStyle, navBodyStyle, - navFooterStyle, - navHeaderStyle, navStyle, navWidthVar, - particlesStyle, - sidebarButtonStyle, + navWrapperStyle, sidebarFloatMaskStyle, - updaterButtonStyle, } from './index.css'; import { APP_SIDEBAR_OPEN, appSidebarOpenAtom, + appSidebarResizingAtom, appSidebarWidthAtom, updateAvailableAtom, } from './index.jotai'; +import { ResizeIndicator } from './resize-indicator'; +import { SidebarHeader } from './sidebar-header'; -export { appSidebarOpenAtom }; +export type AppSidebarProps = PropsWithChildren; -export type AppSidebarProps = PropsWithChildren<{ - footer?: ReactNode | undefined; -}>; +function useEnableAnimation() { + const [enable, setEnable] = useState(false); + useEffect(() => { + setTimeout(() => { + setEnable(true); + }, 500); + }, []); + return enable; +} -export const AppSidebar = forwardRef( - function AppSidebar(props, forwardedRef): ReactElement { - const [open, setOpen] = useAtom(appSidebarOpenAtom); - const clientUpdateAvailable = useAtomValue(updateAvailableAtom); - const t = useAFFiNEI18N(); - const appSidebarWidth = useAtomValue(appSidebarWidthAtom); - const initialRender = open === undefined; +export function AppSidebar(props: AppSidebarProps): ReactElement { + const [open, setOpen] = useAtom(appSidebarOpenAtom); + const appSidebarWidth = useAtomValue(appSidebarWidthAtom); + const initialRender = open === undefined; - const handleSidebarOpen = useCallback(() => { - setOpen(open => !open); - }, [setOpen]); + const isResizing = useAtomValue(appSidebarResizingAtom); + const navRef = useRef(null); - useEffect(() => { - if ( - open === undefined && - localStorage.getItem(APP_SIDEBAR_OPEN) === null - ) { - // give the initial value, - // so that the sidebar can be closed on mobile by default - const { matches } = window.matchMedia( - `(min-width: ${floatingMaxWidth}px)` - ); - - setOpen(matches); - } - }, [open, setOpen]); - - const environment = getEnvironment(); - const isMacosDesktop = environment.isDesktop && environment.isMacOs; - if (initialRender) { - // avoid the UI flash - return
; + useEffect(() => { + if (open === undefined && localStorage.getItem(APP_SIDEBAR_OPEN) === null) { + // give the initial value, + // so that the sidebar can be closed on mobile by default + const { matches } = window.matchMedia( + `(min-width: ${floatingMaxWidth}px)` + ); + setOpen(matches); } - return ( - <> - -
setOpen(false)} - /> - - ); - } -); + }, [open, setOpen]); + // disable animation to avoid UI flash + const enableAnimation = useEnableAnimation(); + + const environment = getEnvironment(); + const isMacosDesktop = environment.isDesktop && environment.isMacOs; + if (initialRender) { + // avoid the UI flash + return
; + } + + return ( + <> +
+ + +
+
setOpen(false)} + /> + + ); +} + +export * from './add-page-button'; +export * from './app-updater-button'; +export * from './category-divider'; export { AppSidebarFallback } from './fallback'; -export type { ResizeIndicatorProps } from './resize-indicator'; -export { ResizeIndicator } from './resize-indicator'; +export * from './menu-item'; +export * from './quick-search-input'; +export * from './sidebar-containers'; +export { appSidebarOpenAtom, appSidebarResizingAtom, updateAvailableAtom }; diff --git a/packages/component/src/components/app-sidebar/menu-item/index.css.ts b/packages/component/src/components/app-sidebar/menu-item/index.css.ts new file mode 100644 index 0000000000..b1360a9393 --- /dev/null +++ b/packages/component/src/components/app-sidebar/menu-item/index.css.ts @@ -0,0 +1,48 @@ +import { style } from '@vanilla-extract/css'; + +export const root = style({ + display: 'inline-flex', + alignItems: 'center', + borderRadius: '4px', + width: '100%', + minHeight: '30px', + userSelect: 'none', + cursor: 'pointer', + padding: '0 12px', + fontSize: 'var(--affine-font-sm)', + selectors: { + '&:hover': { + background: 'var(--affine-hover-color)', + }, + '&[data-active="true"]': { + color: 'var(--affine-primary-color)', + background: 'var(--affine-hover-color)', + }, + '&[data-disabled="true"]': { + cursor: 'default', + color: 'var(--affine-text-secondary-color)', + pointerEvents: 'none', + }, + }, +}); + +export const content = style({ + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', +}); + +export const icon = style({ + marginRight: '14px', + color: 'var(--affine-icon-color)', + fontSize: '18px', +}); + +export const spacer = style({ + flex: 1, +}); + +export const linkItemRoot = style({ + color: 'inherit', + display: 'contents', +}); diff --git a/packages/component/src/components/app-sidebar/menu-item/index.stories.tsx b/packages/component/src/components/app-sidebar/menu-item/index.stories.tsx new file mode 100644 index 0000000000..d6ea793966 --- /dev/null +++ b/packages/component/src/components/app-sidebar/menu-item/index.stories.tsx @@ -0,0 +1,34 @@ +import { SettingsIcon } from '@blocksuite/icons'; +import type { Meta, StoryFn } from '@storybook/react'; + +import { MenuItem, MenuLinkItem } from '.'; + +export default { + title: 'Components/AppSidebar/MenuItem', + component: MenuItem, +} satisfies Meta; + +export const Default: StoryFn = () => { + return ( +
+ } onClick={() => alert('opened')}> + Normal Item + + } + href="/test" + onClick={() => alert('opened')} + > + Normal Link Item + + } + href="/test" + onClick={() => alert('opened')} + > + Primary Item + +
+ ); +}; diff --git a/packages/component/src/components/app-sidebar/menu-item/index.tsx b/packages/component/src/components/app-sidebar/menu-item/index.tsx new file mode 100644 index 0000000000..600ae138d8 --- /dev/null +++ b/packages/component/src/components/app-sidebar/menu-item/index.tsx @@ -0,0 +1,47 @@ +import clsx from 'clsx'; +import type { LinkProps } from 'next/link'; +import Link from 'next/link'; +import React from 'react'; + +import * as styles from './index.css'; + +interface MenuItemProps extends React.HTMLAttributes { + icon?: React.ReactElement; + active?: boolean; + disabled?: boolean; +} + +interface MenuLinkItemProps extends MenuItemProps, Pick {} + +export function MenuItem({ + onClick, + icon, + active, + children, + disabled, + ...props +}: MenuItemProps) { + return ( +
+ {icon && + React.cloneElement(icon, { + className: clsx([styles.icon, icon.props.className]), + })} +
{children}
+
+ ); +} + +export function MenuLinkItem({ href, ...props }: MenuLinkItemProps) { + return ( + + + + ); +} diff --git a/packages/component/src/components/app-sidebar/quick-search-input/index.css.ts b/packages/component/src/components/app-sidebar/quick-search-input/index.css.ts new file mode 100644 index 0000000000..a816b5d67b --- /dev/null +++ b/packages/component/src/components/app-sidebar/quick-search-input/index.css.ts @@ -0,0 +1,35 @@ +import { style } from '@vanilla-extract/css'; + +export const root = style({ + display: 'inline-flex', + background: 'var(--affine-white-10)', + alignItems: 'center', + borderRadius: '8px', + border: '1px solid var(--affine-black-10)', + fontSize: 'var(--affine-font-sm)', + width: '100%', + height: '36px', + userSelect: 'none', + cursor: 'pointer', + padding: '0 12px', + margin: '12px 0', + selectors: { + '&:hover': { + background: 'var(--affine-hover-color)', + }, + }, +}); + +export const icon = style({ + marginRight: '14px', + color: 'var(--affine-icon-color)', +}); + +export const spacer = style({ + flex: 1, +}); + +export const shortcutHint = style({ + color: 'var(--affine-black-30)', + fontSize: 'var(--affine-font-base)', +}); diff --git a/packages/component/src/components/app-sidebar/quick-search-input/index.stories.tsx b/packages/component/src/components/app-sidebar/quick-search-input/index.stories.tsx new file mode 100644 index 0000000000..153716b46b --- /dev/null +++ b/packages/component/src/components/app-sidebar/quick-search-input/index.stories.tsx @@ -0,0 +1,16 @@ +import type { Meta, StoryFn } from '@storybook/react'; + +import { QuickSearchInput } from '.'; + +export default { + title: 'Components/AppSidebar/QuickSearchInput', + component: QuickSearchInput, +} satisfies Meta; + +export const Default: StoryFn = () => { + return ( +
+ alert('opened')} /> +
+ ); +}; diff --git a/packages/component/src/components/app-sidebar/quick-search-input/index.tsx b/packages/component/src/components/app-sidebar/quick-search-input/index.tsx new file mode 100644 index 0000000000..e1a67507dd --- /dev/null +++ b/packages/component/src/components/app-sidebar/quick-search-input/index.tsx @@ -0,0 +1,32 @@ +import { getEnvironment } from '@affine/env/config'; +import { useAFFiNEI18N } from '@affine/i18n/hooks'; +import { SearchIcon } from '@blocksuite/icons'; +import clsx from 'clsx'; + +import * as styles from './index.css'; + +interface QuickSearchInputProps extends React.HTMLAttributes { + onClick?: () => void; +} + +// Although it is called an input, it is actually a button. +export function QuickSearchInput({ onClick, ...props }: QuickSearchInputProps) { + const t = useAFFiNEI18N(); + const environment = getEnvironment(); + const isMac = environment.isBrowser && environment.isMacOs; + + return ( +
+ + {t['Quick search']()} +
+
+ {isMac ? ' ⌘ + K' : ' Ctrl + K'} +
+
+ ); +} diff --git a/packages/component/src/components/app-sidebar/resize-indicator/index.css.ts b/packages/component/src/components/app-sidebar/resize-indicator/index.css.ts index e7e8c7c8ca..eb64d2216d 100644 --- a/packages/component/src/components/app-sidebar/resize-indicator/index.css.ts +++ b/packages/component/src/components/app-sidebar/resize-indicator/index.css.ts @@ -1,16 +1,14 @@ import { style } from '@vanilla-extract/css'; -import { navWidthVar } from '../index.css'; - -export const spacerStyle = style({ +export const resizerContainer = style({ position: 'absolute', - left: navWidthVar, + right: 0, top: 0, bottom: 0, - width: '7px', + width: '10px', height: '100%', - borderLeft: '1px solid var(--affine-border-color)', - zIndex: 'calc(var(--affine-z-index-modal) - 1)', + zIndex: 'calc(var(--affine-z-index-modal) + 1)', + transform: 'translateX(50%)', backgroundColor: 'transparent', opacity: 0, cursor: 'col-resize', @@ -36,3 +34,14 @@ export const spacerStyle = style({ }, }, }); + +export const resizerInner = style({ + position: 'absolute', + height: '100%', + width: '2px', + left: '3px', + backgroundColor: 'var(--affine-border-color)', + selectors: { + [`${resizerContainer}:hover &`]: {}, + }, +}); diff --git a/packages/component/src/components/app-sidebar/resize-indicator/index.tsx b/packages/component/src/components/app-sidebar/resize-indicator/index.tsx index 61c72aca3b..2e6f0d9548 100644 --- a/packages/component/src/components/app-sidebar/resize-indicator/index.tsx +++ b/packages/component/src/components/app-sidebar/resize-indicator/index.tsx @@ -1,56 +1,38 @@ -import type { Instance } from '@popperjs/core'; -import { createPopper } from '@popperjs/core'; -import { useAtom, useAtomValue, useSetAtom } from 'jotai'; +import { useAtom, useSetAtom } from 'jotai'; import type { ReactElement } from 'react'; +import { useCallback, useLayoutEffect, useState } from 'react'; + import { - useCallback, - useDeferredValue, - useEffect, - useRef, - useState, -} from 'react'; + appSidebarOpenAtom, + appSidebarResizingAtom, + appSidebarWidthAtom, +} from '../index.jotai'; +import * as styles from './index.css'; -import { appSidebarOpenAtom, appSidebarWidthAtom } from '../index.jotai'; -import { spacerStyle } from './index.css'; - -export type ResizeIndicatorProps = { +type ResizeIndicatorProps = { targetElement: HTMLElement | null; }; export const ResizeIndicator = (props: ResizeIndicatorProps): ReactElement => { - const ref = useRef(null); - const popperRef = useRef(null); const setWidth = useSetAtom(appSidebarWidthAtom); const [sidebarOpen, setSidebarOpen] = useAtom(appSidebarOpenAtom); - const [isResizing, setIsResizing] = useState(false); - useEffect(() => { - if (ref.current) { - if (props.targetElement) { - const popper = createPopper(props.targetElement, ref.current, { - placement: 'right', - }); - popperRef.current = popper; - return () => { - popper.destroy(); - popperRef.current = null; - }; - } - } - }, [props.targetElement]); + const [isResizing, setIsResizing] = useAtom(appSidebarResizingAtom); - const sidebarWidth = useDeferredValue(useAtomValue(appSidebarWidthAtom)); - useEffect(() => { - if (popperRef.current) { - popperRef.current.update(); - } - }, [sidebarWidth]); + const [anchorLeft, setAnchorLeft] = useState(0); + + useLayoutEffect(() => { + if (!props.targetElement) return; + const { left } = props.targetElement.getBoundingClientRect(); + setAnchorLeft(left); + }, [props.targetElement]); const onResizeStart = useCallback(() => { let resized = false; function onMouseMove(e: MouseEvent) { e.preventDefault(); - const newWidth = Math.min(480, Math.max(e.clientX, 256)); + if (!props.targetElement) return; + const newWidth = Math.min(480, Math.max(e.clientX - anchorLeft, 256)); setWidth(newWidth); setIsResizing(true); resized = true; @@ -64,24 +46,28 @@ export const ResizeIndicator = (props: ResizeIndicatorProps): ReactElement => { if (!resized) { setSidebarOpen(o => !o); } - if (popperRef.current) { - popperRef.current.update(); - } setIsResizing(false); document.removeEventListener('mousemove', onMouseMove); }, { once: true } ); - }, [setSidebarOpen, setWidth]); + }, [ + anchorLeft, + props.targetElement, + setIsResizing, + setSidebarOpen, + setWidth, + ]); return (
+ > +
+
); }; diff --git a/packages/component/src/components/app-sidebar/sidebar-containers/index.css.ts b/packages/component/src/components/app-sidebar/sidebar-containers/index.css.ts new file mode 100644 index 0000000000..7071794798 --- /dev/null +++ b/packages/component/src/components/app-sidebar/sidebar-containers/index.css.ts @@ -0,0 +1,75 @@ +import { globalStyle, style } from '@vanilla-extract/css'; + +export const baseContainer = style({ + padding: '12px 16px', + display: 'flex', + flexFlow: 'column nowrap', + rowGap: '8px', +}); + +export const scrollableContainerRoot = style({ + flex: '1 1 auto', + overflowY: 'hidden', + vars: { + '--scrollbar-width': '10px', + }, + transition: 'all .3s .2s', + borderTop: '1px solid transparent', + selectors: { + '&[data-has-scroll-top="true"]': { + boxShadow: 'inset 0 8px 8px -8px var(--affine-black-10)', + }, + }, +}); + +export const scrollableViewport = style({ + height: '100%', +}); + +globalStyle(`${scrollableViewport} > div`, { + maxWidth: '100%', + display: 'block !important', +}); + +export const scrollableContainer = style([ + baseContainer, + { + height: '100%', + }, +]); + +export const scrollbar = style({ + display: 'flex', + flexDirection: 'column', + userSelect: 'none', + touchAction: 'none', + padding: '0 2px', + width: 'var(--scrollbar-width)', + height: '100%', + opacity: 1, + transition: 'opacity .15s', + selectors: { + '&[data-state="hidden"]': { + opacity: 0, + }, + }, +}); + +export const scrollbarThumb = style({ + position: 'relative', + background: 'var(--affine-black-30)', + borderRadius: '4px', + selectors: { + '&::before': { + content: '""', + position: 'absolute', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + width: '100%', + height: '100%', + minWidth: '44px', + minHeight: '44px', + }, + }, +}); diff --git a/packages/component/src/components/app-sidebar/sidebar-containers/index.tsx b/packages/component/src/components/app-sidebar/sidebar-containers/index.tsx new file mode 100644 index 0000000000..3aab50ba43 --- /dev/null +++ b/packages/component/src/components/app-sidebar/sidebar-containers/index.tsx @@ -0,0 +1,61 @@ +import * as ScrollArea from '@radix-ui/react-scroll-area'; +import clsx from 'clsx'; +import { type PropsWithChildren, useEffect, useRef, useState } from 'react'; + +import * as styles from './index.css'; + +export function SidebarContainer({ children }: PropsWithChildren) { + return
{children}
; +} + +function useHasScrollTop() { + const ref = useRef(null); + const [hasScrollTop, setHasScrollTop] = useState(false); + + useEffect(() => { + if (!ref.current) { + return; + } + + const container = ref.current; + + function updateScrollTop() { + if (container) { + const hasScrollTop = container.scrollTop > 0; + setHasScrollTop(hasScrollTop); + } + } + + container.addEventListener('scroll', updateScrollTop); + updateScrollTop(); + return () => { + container.removeEventListener('scroll', updateScrollTop); + }; + }, []); + + return [hasScrollTop, ref] as const; +} + +export function SidebarScrollableContainer({ children }: PropsWithChildren) { + const [hasScrollTop, ref] = useHasScrollTop(); + return ( + + +
{children}
+
+ + + +
+ ); +} diff --git a/packages/component/src/components/app-sidebar/sidebar-header/index.tsx b/packages/component/src/components/app-sidebar/sidebar-header/index.tsx new file mode 100644 index 0000000000..0f0f851f22 --- /dev/null +++ b/packages/component/src/components/app-sidebar/sidebar-header/index.tsx @@ -0,0 +1,52 @@ +import { IconButton } from '@affine/component'; +import { getEnvironment } from '@affine/env/config'; +import { + ArrowLeftSmallIcon, + ArrowRightSmallIcon, + SidebarIcon, +} from '@blocksuite/icons'; +import { useAtom } from 'jotai'; + +import { navHeaderStyle, sidebarButtonStyle } from '../index.css'; +import { appSidebarOpenAtom } from '../index.jotai'; + +export const SidebarHeader = () => { + const [open, setOpen] = useAtom(appSidebarOpenAtom); + const environment = getEnvironment(); + const isMacosDesktop = environment.isDesktop && environment.isMacOs; + return ( +
+ {isMacosDesktop && ( + <> + { + window.history.back(); + }} + > + + + { + window.history.forward(); + }} + > + + + + )} + setOpen(open => !open)} + > + + +
+ ); +}; diff --git a/packages/component/src/components/changeLog/index.css.ts b/packages/component/src/components/changeLog/index.css.ts index ab9144bcd4..d3954bd5f2 100644 --- a/packages/component/src/components/changeLog/index.css.ts +++ b/packages/component/src/components/changeLog/index.css.ts @@ -51,7 +51,7 @@ const slideOut2 = keyframes({ export const changeLogWrapperSlideInStyle = style({ width: 'calc(100% + 4px)', - height: '0px', + flexShrink: 0, animation: `${slideIn} 1s ease-in-out forwards`, display: 'flex', justifyContent: 'flex-start', diff --git a/packages/component/src/components/workspace/index.css.ts b/packages/component/src/components/workspace/index.css.ts index 43dd4e4c24..d26edb34c7 100644 --- a/packages/component/src/components/workspace/index.css.ts +++ b/packages/component/src/components/workspace/index.css.ts @@ -1,4 +1,4 @@ -import { style } from '@vanilla-extract/css'; +import { globalStyle, style } from '@vanilla-extract/css'; import { breakpoints } from '../../styles/mui-theme'; export const appStyle = style({ @@ -13,6 +13,14 @@ export const appStyle = style({ '&[data-is-resizing="true"]': { cursor: 'col-resize', }, + '&[data-noise-background="true"]:before': { + content: '""', + position: 'absolute', + inset: 0, + opacity: 'var(--affine-noise-opacity)', + backgroundSize: '25%', + backgroundImage: `url("data:image/svg+xml,%3Csvg viewBox='0 0 1024 1024' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.25' numOctaves='10' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)'/%3E%3C/svg%3E")`, + }, }, vars: { '--affine-editor-width': '686px', @@ -26,11 +34,35 @@ export const appStyle = style({ }, }); +globalStyle(`html[data-theme="light"] ${appStyle}`, { + vars: { + '--affine-noise-opacity': '0.25', + }, +}); + +globalStyle(`html[data-theme="dark"] ${appStyle}`, { + vars: { + '--affine-noise-opacity': '0.1', + }, +}); + export const mainContainerStyle = style({ position: 'relative', flexGrow: 1, maxWidth: '100%', + zIndex: 0, backgroundColor: 'var(--affine-background-primary-color)', + selectors: { + '&[data-is-desktop="true"]': { + margin: '8px 8px 8px 8px', + borderRadius: '8px', + overflow: 'hidden', + boxShadow: 'var(--affine-shadow-1)', + }, + '&[data-is-desktop="true"][data-is-sidebar-open="true"]': { + marginLeft: '2px', + }, + }, }); export const toolStyle = style({ diff --git a/packages/component/src/components/workspace/index.tsx b/packages/component/src/components/workspace/index.tsx index 83ffd3326c..dd7472839d 100644 --- a/packages/component/src/components/workspace/index.tsx +++ b/packages/component/src/components/workspace/index.tsx @@ -9,8 +9,13 @@ export type WorkspaceRootProps = PropsWithChildren<{ }>; export const AppContainer = (props: WorkspaceRootProps): ReactElement => { + const noisyBackground = environment.isDesktop && environment.isMacOs; return ( -
+
{props.children}
); @@ -18,12 +23,15 @@ export const AppContainer = (props: WorkspaceRootProps): ReactElement => { export type MainContainerProps = PropsWithChildren<{ className?: string; + sidebarOpen?: boolean; }>; export const MainContainer = (props: MainContainerProps): ReactElement => { return (
{props.children}
diff --git a/packages/env/package.json b/packages/env/package.json index f4a260e62d..a6e6476eec 100644 --- a/packages/env/package.json +++ b/packages/env/package.json @@ -12,6 +12,7 @@ }, "exports": { ".": "./src/index.ts", + "./config": "./src/config.ts", "./constant": "./src/constant.ts", "./blocksuite": "./src/blocksuite.ts" }, diff --git a/packages/i18n/src/resources/en.json b/packages/i18n/src/resources/en.json index 50be5ae516..6e4305705c 100644 --- a/packages/i18n/src/resources/en.json +++ b/packages/i18n/src/resources/en.json @@ -18,6 +18,7 @@ "No item": "No item", "Import": "Import", "Trash": "Trash", + "others": "Others", "New Page": "New Page", "New Keyword Page": "New '{{query}}' page", "Find 0 result": "Find 0 result", @@ -231,6 +232,7 @@ "Synced with AFFiNE Cloud": "Synced with AFFiNE Cloud", "Recent": "Recent", "Successfully deleted": "Successfully deleted", + "Update Available": "Update available", "Restart Install Client Update": "Restart to install update", "Add Workspace": "Add Workspace", "Add Workspace Hint": "Select where you already have", diff --git a/tests/parallels/local-first-delete-page.spec.ts b/tests/parallels/local-first-delete-page.spec.ts index b3d7217821..4d1c0aec91 100644 --- a/tests/parallels/local-first-delete-page.spec.ts +++ b/tests/parallels/local-first-delete-page.spec.ts @@ -18,7 +18,7 @@ test('New a page , then delete it in all pages, permanently delete it', async ({ await getBlockSuiteEditorTitle(page).click(); await getBlockSuiteEditorTitle(page).fill('this is a new page to restore'); const newPageId = page.url().split('/').reverse()[0]; - await page.getByRole('link', { name: 'All pages' }).click(); + await page.getByTestId('all-pages').click(); const cell = page.getByRole('cell', { name: 'this is a new page to restore', }); @@ -36,7 +36,7 @@ test('New a page , then delete it in all pages, permanently delete it', async ({ await page.getByRole('button', { name: 'Delete' }).click(); - await page.getByRole('link', { name: 'Trash' }).click(); + await page.getByTestId('trash-page').click(); // permanently delete it await page .getByTestId('more-actions-' + newPageId) diff --git a/tests/parallels/local-first-export-page.spec.ts b/tests/parallels/local-first-export-page.spec.ts index 9c48b4d46e..e4d74a5555 100644 --- a/tests/parallels/local-first-export-page.spec.ts +++ b/tests/parallels/local-first-export-page.spec.ts @@ -18,7 +18,7 @@ test.skip('New a page ,then open it and export html', async ({ page }) => { await page .getByPlaceholder('Title') .fill('this is a new page to export html content'); - await page.getByRole('link', { name: 'All pages' }).click(); + await page.getByTestId('all-pages').click(); const cell = page.getByRole('cell', { name: 'this is a new page to export html content', @@ -46,7 +46,7 @@ test.skip('New a page ,then open it and export markdown', async ({ page }) => { await page .getByPlaceholder('Title') .fill('this is a new page to export markdown content'); - await page.getByRole('link', { name: 'All pages' }).click(); + await page.getByTestId('all-pages').click(); const cell = page.getByRole('cell', { name: 'this is a new page to export markdown content', }); diff --git a/tests/parallels/local-first-favorite-page.spec.ts b/tests/parallels/local-first-favorite-page.spec.ts index a19a085d6b..ef426da005 100644 --- a/tests/parallels/local-first-favorite-page.spec.ts +++ b/tests/parallels/local-first-favorite-page.spec.ts @@ -16,7 +16,7 @@ test('New a page and open it ,then favorite it', async ({ page }) => { await newPage(page); await getBlockSuiteEditorTitle(page).click(); await getBlockSuiteEditorTitle(page).fill('this is a new page to favorite'); - await page.getByRole('link', { name: 'All pages' }).click(); + await page.getByTestId('all-pages').click(); const cell = page.getByRole('cell', { name: 'this is a new page to favorite', }); @@ -55,7 +55,7 @@ test('Cancel favorite', async ({ page }) => { await newPage(page); await getBlockSuiteEditorTitle(page).click(); await getBlockSuiteEditorTitle(page).fill('this is a new page to favorite'); - await page.getByRole('link', { name: 'All pages' }).click(); + await page.getByTestId('all-pages').click(); const cell = page.getByRole('cell', { name: 'this is a new page to favorite', }); @@ -68,14 +68,13 @@ test('Cancel favorite', async ({ page }) => { await favoriteBtn.click(); // expect it in favorite list - await page.getByRole('link', { name: 'Favorites' }).click(); expect( page.getByRole('cell', { name: 'this is a new page to favorite' }) ).not.toBeUndefined(); // cancel favorite - await page.getByRole('link', { name: 'All pages' }).click(); + await page.getByTestId('all-pages').click(); const box = await page .getByRole('cell', { name: 'this is a new page to favorite' }) @@ -86,7 +85,6 @@ test('Cancel favorite', async ({ page }) => { await page.getByTestId('favorited-icon').click(); // expect it not in favorite list - await page.getByRole('link', { name: 'Favorites' }).click(); expect( page.getByText( 'Tips: Click Add to Favorites/Trash and the page will appear here.' diff --git a/tests/parallels/local-first-favorites-items.spec.ts b/tests/parallels/local-first-favorites-items.spec.ts index e6c88c6941..4fa62677d9 100644 --- a/tests/parallels/local-first-favorites-items.spec.ts +++ b/tests/parallels/local-first-favorites-items.spec.ts @@ -8,7 +8,6 @@ import { newPage, waitMarkdownImported, } from '../libs/page-logic'; -import { assertCurrentWorkspaceFlavour } from '../libs/workspace'; test('Show favorite items in sidebar', async ({ page }) => { await openHomePage(page); @@ -17,7 +16,7 @@ test('Show favorite items in sidebar', async ({ page }) => { await getBlockSuiteEditorTitle(page).click(); await getBlockSuiteEditorTitle(page).fill('this is a new page to favorite'); const newPageId = page.url().split('/').reverse()[0]; - await page.getByRole('link', { name: 'All pages' }).click(); + await page.getByTestId('all-pages').click(); const cell = page.getByRole('cell', { name: 'this is a new page to favorite', }); @@ -41,7 +40,7 @@ test('Show favorite items in favorite list', async ({ page }) => { await newPage(page); await getBlockSuiteEditorTitle(page).click(); await getBlockSuiteEditorTitle(page).fill('this is a new page to favorite'); - await page.getByRole('link', { name: 'All pages' }).click(); + await page.getByTestId('all-pages').click(); const cell = page.getByRole('cell', { name: 'this is a new page to favorite', }); @@ -52,16 +51,11 @@ test('Show favorite items in favorite list', async ({ page }) => { const favoriteBtn = page.getByTestId('editor-option-menu-favorite'); await favoriteBtn.click(); - await page.getByRole('link', { name: 'Favorites' }).click(); + await page.getByTestId('all-pages').click(); + expect( page.getByRole('cell', { name: 'this is a new page to favorite' }) ).not.toBeUndefined(); await page.getByRole('cell').getByRole('button').nth(0).click(); - expect( - await page - .getByText('Click Add to Favorites and the page will appear here.') - .isVisible() - ).toBe(true); - await assertCurrentWorkspaceFlavour('local', page); }); diff --git a/tests/parallels/local-first-new-page.spec.ts b/tests/parallels/local-first-new-page.spec.ts index d7be89fa17..0a46ebdb0a 100644 --- a/tests/parallels/local-first-new-page.spec.ts +++ b/tests/parallels/local-first-new-page.spec.ts @@ -25,7 +25,7 @@ test('click btn bew page and find it in all pages', async ({ page }) => { await newPage(page); await getBlockSuiteEditorTitle(page).click(); await getBlockSuiteEditorTitle(page).fill('this is a new page'); - await page.getByRole('link', { name: 'All pages' }).click(); + await page.getByTestId('all-pages').click(); const cell = page.getByRole('cell', { name: 'this is a new page' }); expect(cell).not.toBeUndefined(); await assertCurrentWorkspaceFlavour('local', page); diff --git a/tests/parallels/local-first-openpage-newtab.spec.ts b/tests/parallels/local-first-openpage-newtab.spec.ts index 65b78e253b..d335b74acf 100644 --- a/tests/parallels/local-first-openpage-newtab.spec.ts +++ b/tests/parallels/local-first-openpage-newtab.spec.ts @@ -18,7 +18,7 @@ test('click btn bew page and open in tab', async ({ page }) => { const newPageUrl = page.url(); const newPageId = page.url().split('/').reverse()[0]; - await page.getByRole('link', { name: 'All pages' }).click(); + await page.getByTestId('all-pages').click(); await page .getByTestId('more-actions-' + newPageId) diff --git a/tests/parallels/local-first-restore-page.spec.ts b/tests/parallels/local-first-restore-page.spec.ts index 1536854936..49995ffba5 100644 --- a/tests/parallels/local-first-restore-page.spec.ts +++ b/tests/parallels/local-first-restore-page.spec.ts @@ -18,7 +18,7 @@ test('New a page , then delete it in all pages, restore it', async ({ await getBlockSuiteEditorTitle(page).click(); await getBlockSuiteEditorTitle(page).fill('this is a new page to restore'); const newPageId = page.url().split('/').reverse()[0]; - await page.getByRole('link', { name: 'All pages' }).click(); + await page.getByTestId('all-pages').click(); const cell = page.getByRole('cell', { name: 'this is a new page to restore', }); @@ -36,7 +36,7 @@ test('New a page , then delete it in all pages, restore it', async ({ await page.getByRole('button', { name: 'Delete' }).click(); - await page.getByRole('link', { name: 'Trash' }).click(); + await page.getByTestId('trash-page').click(); await page.waitForTimeout(50); const trashPage = page.url(); // restore it @@ -48,7 +48,7 @@ test('New a page , then delete it in all pages, restore it', async ({ // stay in trash page expect(page.url()).toBe(trashPage); - await page.getByRole('link', { name: 'All pages' }).click(); + await page.getByTestId('all-pages').click(); const restoreCell = page.getByRole('cell', { name: 'this is a new page to restore', }); diff --git a/tests/parallels/local-first-show-delete-modal.spec.ts b/tests/parallels/local-first-show-delete-modal.spec.ts index 542e080483..3933806b7b 100644 --- a/tests/parallels/local-first-show-delete-modal.spec.ts +++ b/tests/parallels/local-first-show-delete-modal.spec.ts @@ -16,7 +16,7 @@ test('New a page ,then open it and show delete modal', async ({ page }) => { await newPage(page); await getBlockSuiteEditorTitle(page).click(); await getBlockSuiteEditorTitle(page).fill('this is a new page to delete'); - await page.getByRole('link', { name: 'All pages' }).click(); + await page.getByTestId('all-pages').click(); const cell = page.getByRole('cell', { name: 'this is a new page to delete', }); @@ -40,7 +40,7 @@ test('New a page ,then go to all pages and show delete modal', async ({ await getBlockSuiteEditorTitle(page).click(); await getBlockSuiteEditorTitle(page).fill('this is a new page to delete'); const newPageId = page.url().split('/').reverse()[0]; - await page.getByRole('link', { name: 'All pages' }).click(); + await page.getByTestId('all-pages').click(); const cell = page.getByRole('cell', { name: 'this is a new page to delete', }); diff --git a/tests/parallels/local-first-trash-page.spec.ts b/tests/parallels/local-first-trash-page.spec.ts index 93fe66ccd0..451829f7c9 100644 --- a/tests/parallels/local-first-trash-page.spec.ts +++ b/tests/parallels/local-first-trash-page.spec.ts @@ -18,7 +18,7 @@ test('New a page , then delete it in all pages, finally find it in trash', async await getBlockSuiteEditorTitle(page).click(); await getBlockSuiteEditorTitle(page).fill('this is a new page to delete'); const newPageId = page.url().split('/').reverse()[0]; - await page.getByRole('link', { name: 'All pages' }).click(); + await page.getByTestId('all-pages').click(); const cell = page.getByRole('cell', { name: 'this is a new page to delete', }); @@ -36,7 +36,7 @@ test('New a page , then delete it in all pages, finally find it in trash', async await page.getByRole('button', { name: 'Delete' }).click(); - await page.getByRole('link', { name: 'Trash' }).click(); + await page.getByTestId('trash-page').click(); expect( page.getByRole('cell', { name: 'this is a new page to delete' }) ).not.toBeUndefined(); diff --git a/tests/parallels/open-affine.spec.ts b/tests/parallels/open-affine.spec.ts index d286fb98c5..9aa111e5e0 100644 --- a/tests/parallels/open-affine.spec.ts +++ b/tests/parallels/open-affine.spec.ts @@ -48,7 +48,7 @@ test('Click right-bottom corner change log icon', async ({ page }) => { ); await page.waitForTimeout(50); expect(await editorRightBottomChangeLog.isVisible()).toEqual(true); - await page.getByRole('link', { name: 'All pages' }).click(); + await page.getByTestId('all-pages').click(); const normalRightBottomChangeLog = page.locator( '[data-testid=right-bottom-change-log-icon]' );