feat(component): init app sidebar (#2135)

This commit is contained in:
Himself65
2023-04-27 16:46:08 -05:00
committed by GitHub
parent f3cbe54625
commit c1a65b6b76
23 changed files with 668 additions and 451 deletions

View File

@@ -1,19 +1,14 @@
import { atomWithSyncStorage } from '@affine/jotai'; import { atomWithStorage } from 'jotai/utils';
export type Visibility = Record<string, boolean>; export type Visibility = Record<string, boolean>;
const DEFAULT_VALUE = '0.0.0'; const DEFAULT_VALUE = '0.0.0';
export const lastVersionAtom = atomWithSyncStorage( export const lastVersionAtom = atomWithStorage('lastVersion', DEFAULT_VALUE);
'lastVersion',
DEFAULT_VALUE
);
export const guideHiddenAtom = atomWithSyncStorage<Visibility>(
'guideHidden',
{}
);
export const guideHiddenUntilNextUpdateAtom = atomWithSyncStorage<Visibility>( export const guideHiddenAtom = atomWithStorage<Visibility>('guideHidden', {});
export const guideHiddenUntilNextUpdateAtom = atomWithStorage<Visibility>(
'guideHiddenUntilNextUpdate', 'guideHiddenUntilNextUpdate',
{} {}
); );

View File

@@ -1,6 +1,8 @@
import { Tooltip } from '@affine/component'; import { Tooltip } from '@affine/component';
import { appSidebarOpenAtom } from '@affine/component/app-sidebar';
import { getEnvironment } from '@affine/env'; import { getEnvironment } from '@affine/env';
import { useTranslation } from '@affine/i18n'; import { useTranslation } from '@affine/i18n';
import { useAtom } from 'jotai';
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import { import {
@@ -8,7 +10,6 @@ import {
useGuideHiddenUntilNextUpdate, useGuideHiddenUntilNextUpdate,
useUpdateTipsOnVersionChange, useUpdateTipsOnVersionChange,
} from '../../../hooks/use-is-first-load'; } from '../../../hooks/use-is-first-load';
import { useSidebarStatus } from '../../../hooks/use-sidebar-status';
import { SidebarSwitchIcon } from './icons'; import { SidebarSwitchIcon } from './icons';
import { StyledSidebarSwitch } from './style'; import { StyledSidebarSwitch } from './style';
type SidebarSwitchProps = { type SidebarSwitchProps = {
@@ -24,7 +25,7 @@ export const SidebarSwitch = ({
...props ...props
}: SidebarSwitchProps) => { }: SidebarSwitchProps) => {
useUpdateTipsOnVersionChange(); useUpdateTipsOnVersionChange();
const [open, setOpen] = useSidebarStatus(); const [open, setOpen] = useAtom(appSidebarOpenAtom);
const [tooltipVisible, setTooltipVisible] = useState(false); const [tooltipVisible, setTooltipVisible] = useState(false);
const [guideHidden, setGuideHidden] = useGuideHidden(); const [guideHidden, setGuideHidden] = useGuideHidden();
const [guideHiddenUntilNextUpdate, setGuideHiddenUntilNextUpdate] = const [guideHiddenUntilNextUpdate, setGuideHiddenUntilNextUpdate] =
@@ -56,9 +57,9 @@ export const SidebarSwitch = ({
visible={visible} visible={visible}
disabled={!visible} disabled={!visible}
onClick={useCallback(() => { onClick={useCallback(() => {
setOpen(!open); setOpen(open => !open);
setTooltipVisible(false); setTooltipVisible(false);
if (guideHiddenUntilNextUpdate['quickSearchTips'] === false) { if (!guideHiddenUntilNextUpdate['quickSearchTips']) {
setGuideHiddenUntilNextUpdate({ setGuideHiddenUntilNextUpdate({
...guideHiddenUntilNextUpdate, ...guideHiddenUntilNextUpdate,
quickSearchTips: true, quickSearchTips: true,
@@ -70,7 +71,6 @@ export const SidebarSwitch = ({
}, [ }, [
guideHidden, guideHidden,
guideHiddenUntilNextUpdate, guideHiddenUntilNextUpdate,
open,
setGuideHidden, setGuideHidden,
setGuideHiddenUntilNextUpdate, setGuideHiddenUntilNextUpdate,
setOpen, setOpen,

View File

@@ -1,7 +1,9 @@
import { appSidebarOpenAtom } from '@affine/component/app-sidebar';
import { useTranslation } from '@affine/i18n'; import { useTranslation } from '@affine/i18n';
import { WorkspaceFlavour } from '@affine/workspace/type'; import { WorkspaceFlavour } from '@affine/workspace/type';
import { CloseIcon } from '@blocksuite/icons'; import { CloseIcon } from '@blocksuite/icons';
import type { Page } from '@blocksuite/store'; import type { Page } from '@blocksuite/store';
import { useAtom } from 'jotai';
import type { FC, HTMLAttributes, PropsWithChildren } from 'react'; import type { FC, HTMLAttributes, PropsWithChildren } from 'react';
import { import {
forwardRef, forwardRef,
@@ -12,10 +14,6 @@ import {
useState, useState,
} from 'react'; } from 'react';
import {
useSidebarFloating,
useSidebarStatus,
} from '../../../hooks/use-sidebar-status';
import type { AffineOfficialWorkspace } from '../../../shared'; import type { AffineOfficialWorkspace } from '../../../shared';
import { EditorOptionMenu } from './header-right-items/EditorOptionMenu'; import { EditorOptionMenu } from './header-right-items/EditorOptionMenu';
import EditPage from './header-right-items/EditPage'; import EditPage from './header-right-items/EditPage';
@@ -142,17 +140,11 @@ export const Header = forwardRef<
useEffect(() => { useEffect(() => {
setShowWarning(shouldShowWarning()); setShowWarning(shouldShowWarning());
}, []); }, []);
const [open] = useSidebarStatus(); const [open] = useAtom(appSidebarOpenAtom);
const sidebarFloating = useSidebarFloating();
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<StyledHeaderContainer <StyledHeaderContainer ref={ref} hasWarning={showWarning} {...props}>
sidebarFloating={sidebarFloating && open}
ref={ref}
hasWarning={showWarning}
{...props}
>
<BrowserWarning <BrowserWarning
show={showWarning} show={showWarning}
onClose={() => { onClose={() => {

View File

@@ -7,8 +7,7 @@ import {
export const StyledHeaderContainer = styled('div')<{ export const StyledHeaderContainer = styled('div')<{
hasWarning: boolean; hasWarning: boolean;
sidebarFloating: boolean; }>(({ hasWarning }) => {
}>(({ theme, hasWarning, sidebarFloating }) => {
return { return {
height: hasWarning ? '96px' : '52px', height: hasWarning ? '96px' : '52px',
flexShrink: 0, flexShrink: 0,
@@ -16,10 +15,6 @@ export const StyledHeaderContainer = styled('div')<{
top: 0, top: 0,
background: 'var(--affine-background-primary-color)', background: 'var(--affine-background-primary-color)',
zIndex: 1, zIndex: 1,
WebkitAppRegion: sidebarFloating ? '' : 'drag',
button: {
WebkitAppRegion: 'no-drag',
},
}; };
}); });
export const StyledHeader = styled('div')<{ hasWarning: boolean }>( export const StyledHeader = styled('div')<{ hasWarning: boolean }>(

View File

@@ -1,47 +1,6 @@
import { config } from '@affine/env';
import { useTranslation } from '@affine/i18n';
import { WorkspaceFlavour } from '@affine/workspace/type';
import {
DeleteTemporarilyIcon,
FolderIcon,
PlusIcon,
SearchIcon,
SettingsIcon,
ShareIcon,
} from '@blocksuite/icons';
import type { Page, PageMeta } from '@blocksuite/store'; import type { Page, PageMeta } from '@blocksuite/store';
import type React from 'react';
import type { UIEvent } from 'react';
import { lazy, Suspense, useCallback, useEffect, useState } from 'react';
import {
useSidebarFloating,
useSidebarResizing,
useSidebarStatus,
useSidebarWidth,
} from '../../../hooks/use-sidebar-status';
import type { AllWorkspace } from '../../../shared'; import type { AllWorkspace } from '../../../shared';
import { ChangeLog } from './changeLog';
import Favorite from './favorite';
import { RouteNavigation } from './RouteNavigation';
import { StyledListItem } from './shared-styles';
import {
StyledLink,
StyledNewPageButton,
StyledScrollWrapper,
StyledSidebarHeader,
StyledSliderBar,
StyledSliderBarInnerWrapper,
StyledSliderBarWrapper,
StyledSliderModalBackground,
} from './style';
import { WorkspaceSelector } from './WorkspaceSelector';
const SidebarSwitch = lazy(() =>
import('../../affine/sidebar-switch').then(module => ({
default: module.SidebarSwitch,
}))
);
export type FavoriteListProps = { export type FavoriteListProps = {
currentPageId: string | null; currentPageId: string | null;
@@ -67,204 +26,3 @@ export type WorkSpaceSliderBarProps = {
shared: (workspaceId: string) => string; shared: (workspaceId: string) => string;
}; };
}; };
export const WorkSpaceSliderBar: React.FC<WorkSpaceSliderBarProps> = ({
isPublicWorkspace,
currentWorkspace,
currentPageId,
openPage,
createPage,
currentPath,
paths,
onOpenQuickSearchModal,
onOpenWorkspaceListModal,
}) => {
const currentWorkspaceId = currentWorkspace?.id || null;
const blockSuiteWorkspace = currentWorkspace?.blockSuiteWorkspace;
const { t } = useTranslation();
const [sidebarOpen, setSidebarOpen] = useSidebarStatus();
const onClickNewPage = useCallback(async () => {
const page = await createPage();
openPage(page.id);
}, [createPage, openPage]);
const floatingSlider = useSidebarFloating();
const [sliderWidth] = useSidebarWidth();
const [isResizing] = useSidebarResizing();
const [isScrollAtTop, setIsScrollAtTop] = useState(true);
const show = isPublicWorkspace ? false : sidebarOpen;
const actualWidth = floatingSlider ? 'calc(10vw + 400px)' : sliderWidth;
useEffect(() => {
if (environment.isDesktop) {
window.apis?.onSidebarVisibilityChange(sidebarOpen);
}
}, [sidebarOpen]);
useEffect(() => {
const keydown = (e: KeyboardEvent) => {
if ((e.key === '/' && e.metaKey) || (e.key === '/' && e.ctrlKey)) {
setSidebarOpen(!sidebarOpen);
}
};
document.addEventListener('keydown', keydown, { capture: true });
return () =>
document.removeEventListener('keydown', keydown, { capture: true });
}, [sidebarOpen, setSidebarOpen]);
return (
<>
<StyledSliderBarWrapper
resizing={isResizing}
floating={floatingSlider}
show={show}
style={{ width: actualWidth }}
data-testid="sliderBar-root"
>
<StyledSliderBar>
<StyledSidebarHeader>
<RouteNavigation />
<Suspense>
<SidebarSwitch
visible={sidebarOpen}
tooltipContent={t('Collapse sidebar')}
data-testid="sliderBar-arrowButton-collapse"
/>
</Suspense>
</StyledSidebarHeader>
<StyledSliderBarInnerWrapper data-testid="sliderBar-inner">
<WorkspaceSelector
currentWorkspace={currentWorkspace}
onClick={onOpenWorkspaceListModal}
/>
<ChangeLog />
<StyledListItem
data-testid="slider-bar-quick-search-button"
onClick={useCallback(() => {
onOpenQuickSearchModal();
}, [onOpenQuickSearchModal])}
>
<SearchIcon />
{t('Quick search')}
</StyledListItem>
<StyledListItem
active={
currentPath ===
(currentWorkspaceId && paths.setting(currentWorkspaceId))
}
data-testid="slider-bar-workspace-setting-button"
style={{
marginBottom: '16px',
}}
>
<StyledLink
href={{
pathname:
currentWorkspaceId && paths.setting(currentWorkspaceId),
}}
>
<SettingsIcon />
<div>{t('Workspace Settings')}</div>
</StyledLink>
</StyledListItem>
<StyledListItem
active={
currentPath ===
(currentWorkspaceId && paths.all(currentWorkspaceId))
}
>
<StyledLink
href={{
pathname: currentWorkspaceId && paths.all(currentWorkspaceId),
}}
>
<FolderIcon />
<span data-testid="all-pages">{t('All pages')}</span>
</StyledLink>
</StyledListItem>
<StyledScrollWrapper
showTopBorder={!isScrollAtTop}
onScroll={(e: UIEvent<HTMLDivElement>) => {
(e.target as HTMLDivElement).scrollTop === 0
? setIsScrollAtTop(true)
: setIsScrollAtTop(false);
}}
>
{blockSuiteWorkspace && (
<Favorite
currentPath={currentPath}
paths={paths}
currentPageId={currentPageId}
openPage={openPage}
currentWorkspace={currentWorkspace}
/>
)}
</StyledScrollWrapper>
<div style={{ height: 16 }}></div>
{config.enableLegacyCloud &&
(currentWorkspace?.flavour === WorkspaceFlavour.AFFINE &&
currentWorkspace.public ? (
<StyledListItem>
<StyledLink
href={{
pathname:
currentWorkspaceId && paths.setting(currentWorkspaceId),
}}
>
<ShareIcon />
<span data-testid="Published-to-web">Published to web</span>
</StyledLink>
</StyledListItem>
) : (
<StyledListItem
active={
currentPath ===
(currentWorkspaceId && paths.shared(currentWorkspaceId))
}
>
<StyledLink
href={{
pathname:
currentWorkspaceId && paths.shared(currentWorkspaceId),
}}
>
<ShareIcon />
<span data-testid="shared-pages">{t('Shared Pages')}</span>
</StyledLink>
</StyledListItem>
))}
<StyledListItem
active={
currentPath ===
(currentWorkspaceId && paths.trash(currentWorkspaceId))
}
>
<StyledLink
href={{
pathname:
currentWorkspaceId && paths.trash(currentWorkspaceId),
}}
>
<DeleteTemporarilyIcon /> {t('Trash')}
</StyledLink>
</StyledListItem>
</StyledSliderBarInnerWrapper>
<StyledNewPageButton
data-testid="new-page-button"
onClick={onClickNewPage}
>
<PlusIcon /> {t('New Page')}
</StyledNewPageButton>
</StyledSliderBar>
</StyledSliderBarWrapper>
<StyledSliderModalBackground
data-testid="sliderBar-modalBackground"
active={floatingSlider && sidebarOpen}
onClick={() => setSidebarOpen(false)}
/>
</>
);
};
export default WorkSpaceSliderBar;

View File

@@ -2,60 +2,10 @@ import { displayFlex, styled, textEllipsis } from '@affine/component';
import { baseTheme } from '@toeverything/theme'; import { baseTheme } from '@toeverything/theme';
import Link from 'next/link'; import Link from 'next/link';
const macosElectron = environment.isDesktop && environment.isMacOs;
export const StyledSliderBarWrapper = styled('div')<{
show: boolean;
floating: boolean;
resizing: boolean;
}>(({ theme, show, floating, resizing }) => {
return {
height: '100%',
position: 'absolute',
'button, a': {
userSelect: 'none',
},
zIndex: 'var(--affine-z-index-modal)',
transition: resizing ? '' : 'transform .3s, width .3s, max-width .3s',
transform: show ? 'translateX(0)' : 'translateX(-100%)',
maxWidth: floating ? 'calc(10vw + 400px)' : 'calc(100vw - 698px)',
background:
!floating && macosElectron
? 'transparent'
: 'var(--affine-background-secondary-color)',
borderRight: macosElectron ? '' : '1px solid',
borderColor: 'var(--affine-border-color)',
};
});
export const StyledSliderBar = styled('div')(() => {
return {
whiteSpace: 'nowrap',
width: '100%',
height: '100%',
padding: '0 4px',
flexShrink: 0,
display: 'flex',
flexDirection: 'column',
};
});
export const StyledSidebarHeader = styled('div')(() => {
return {
height: '52px',
flexShrink: 0,
padding: '0 16px 0 10px',
WebkitAppRegion: 'drag',
button: {
WebkitAppRegion: 'no-drag',
},
...displayFlex(macosElectron ? 'flex-end' : 'space-between', 'center'),
};
});
export const StyledSliderBarInnerWrapper = styled('div')(() => { export const StyledSliderBarInnerWrapper = styled('div')(() => {
return { return {
flexGrow: 1, flexGrow: 1,
// overflowX: 'hidden', margin: '0 2px',
// overflowY: 'auto',
position: 'relative', position: 'relative',
height: 'calc(100% - 52px * 2)', height: 'calc(100% - 52px * 2)',
display: 'flex', display: 'flex',
@@ -91,8 +41,6 @@ export const StyledNewPageButton = styled('button')(({ theme }) => {
return { return {
height: '52px', height: '52px',
...displayFlex('flex-start', 'center'), ...displayFlex('flex-start', 'center'),
borderTop: '1px solid',
borderColor: 'var(--affine-border-color)',
padding: '0 8px 0 16px', padding: '0 8px 0 16px',
svg: { svg: {
fontSize: '20px', fontSize: '20px',

View File

@@ -0,0 +1,216 @@
import {
AppSidebar,
appSidebarOpenAtom,
ResizeIndicator,
} from '@affine/component/app-sidebar';
import { config } from '@affine/env';
import { useTranslation } from '@affine/i18n';
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 React, { useCallback, useEffect, useRef, useState } 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 { WorkspaceSelector } from '../pure/workspace-slider-bar/WorkspaceSelector';
export type RootAppSidebarProps = {
isPublicWorkspace: boolean;
onOpenQuickSearchModal: () => void;
onOpenWorkspaceListModal: () => void;
currentWorkspace: AllWorkspace | null;
currentPageId: string | null;
openPage: (pageId: string) => void;
createPage: () => Page;
currentPath: string;
paths: {
all: (workspaceId: string) => string;
favorite: (workspaceId: string) => string;
trash: (workspaceId: string) => string;
setting: (workspaceId: string) => string;
shared: (workspaceId: string) => string;
};
};
/**
* This is for the whole affine app sidebar.
* This component wraps the app sidebar in `@affine/component` with logic and data.
*
* @todo(himself65): rewrite all styled component into @vanilla-extract/css
*/
export const RootAppSidebar = ({
currentWorkspace,
currentPageId,
openPage,
createPage,
currentPath,
paths,
onOpenQuickSearchModal,
onOpenWorkspaceListModal,
}: RootAppSidebarProps): ReactElement => {
const currentWorkspaceId = currentWorkspace?.id || null;
const blockSuiteWorkspace = currentWorkspace?.blockSuiteWorkspace;
const { t } = useTranslation();
const [isScrollAtTop, setIsScrollAtTop] = useState(true);
const onClickNewPage = useCallback(async () => {
const page = await createPage();
openPage(page.id);
}, [createPage, openPage]);
const sidebarOpen = useAtomValue(appSidebarOpenAtom);
useEffect(() => {
if (environment.isDesktop) {
window.apis?.onSidebarVisibilityChange(sidebarOpen);
}
}, [sidebarOpen]);
const ref = useRef<HTMLElement>(null);
return (
<>
<AppSidebar
ref={ref}
footer={
<StyledNewPageButton
data-testid="new-page-button"
onClick={onClickNewPage}
>
<PlusIcon /> {t('New Page')}
</StyledNewPageButton>
}
>
<StyledSliderBarInnerWrapper data-testid="sliderBar-inner">
<WorkspaceSelector
currentWorkspace={currentWorkspace}
onClick={onOpenWorkspaceListModal}
/>
<ChangeLog />
<StyledListItem
data-testid="slider-bar-quick-search-button"
onClick={useCallback(() => {
onOpenQuickSearchModal();
}, [onOpenQuickSearchModal])}
>
<SearchIcon />
{t('Quick search')}
</StyledListItem>
<StyledListItem
active={
currentPath ===
(currentWorkspaceId && paths.setting(currentWorkspaceId))
}
data-testid="slider-bar-workspace-setting-button"
style={{
marginBottom: '16px',
}}
>
<StyledLink
href={{
pathname:
currentWorkspaceId && paths.setting(currentWorkspaceId),
}}
>
<SettingsIcon />
<div>{t('Workspace Settings')}</div>
</StyledLink>
</StyledListItem>
<StyledListItem
active={
currentPath ===
(currentWorkspaceId && paths.all(currentWorkspaceId))
}
>
<StyledLink
href={{
pathname: currentWorkspaceId && paths.all(currentWorkspaceId),
}}
>
<FolderIcon />
<span data-testid="all-pages">{t('All pages')}</span>
</StyledLink>
</StyledListItem>
<StyledScrollWrapper
showTopBorder={!isScrollAtTop}
onScroll={(e: UIEvent<HTMLDivElement>) => {
(e.target as HTMLDivElement).scrollTop === 0
? setIsScrollAtTop(true)
: setIsScrollAtTop(false);
}}
>
{blockSuiteWorkspace && (
<Favorite
currentPath={currentPath}
paths={paths}
currentPageId={currentPageId}
openPage={openPage}
currentWorkspace={currentWorkspace}
/>
)}
</StyledScrollWrapper>
<div style={{ height: 16 }}></div>
{config.enableLegacyCloud &&
(currentWorkspace?.flavour === WorkspaceFlavour.AFFINE &&
currentWorkspace.public ? (
<StyledListItem>
<StyledLink
href={{
pathname:
currentWorkspaceId && paths.setting(currentWorkspaceId),
}}
>
<ShareIcon />
<span data-testid="Published-to-web">Published to web</span>
</StyledLink>
</StyledListItem>
) : (
<StyledListItem
active={
currentPath ===
(currentWorkspaceId && paths.shared(currentWorkspaceId))
}
>
<StyledLink
href={{
pathname:
currentWorkspaceId && paths.shared(currentWorkspaceId),
}}
>
<ShareIcon />
<span data-testid="shared-pages">{t('Shared Pages')}</span>
</StyledLink>
</StyledListItem>
))}
<StyledListItem
active={
currentPath ===
(currentWorkspaceId && paths.trash(currentWorkspaceId))
}
>
<StyledLink
href={{
pathname: currentWorkspaceId && paths.trash(currentWorkspaceId),
}}
>
<DeleteTemporarilyIcon /> {t('Trash')}
</StyledLink>
</StyledListItem>
</StyledSliderBarInnerWrapper>
</AppSidebar>
<ResizeIndicator targetElement={ref} />
</>
);
};

View File

@@ -1,28 +0,0 @@
import { useTheme } from '@mui/material';
import { useMediaQuery } from '@react-hookz/web';
import { atom, useAtom } from 'jotai';
import { atomWithStorage } from 'jotai/utils';
const sideBarOpenAtom = atomWithStorage('sidebarOpen', true);
const sideBarWidthAtom = atomWithStorage('sidebarWidth', 256);
const sidebarResizingAtom = atom(false);
export function useSidebarStatus() {
return useAtom(sideBarOpenAtom);
}
export function useSidebarWidth() {
return useAtom(sideBarWidthAtom);
}
export function useSidebarFloating() {
const theme = useTheme();
return (
useMediaQuery(theme.breakpoints.down('md').replace(/^@media( ?)/m, '')) ??
false
);
}
export function useSidebarResizing() {
return useAtom(sidebarResizingAtom);
}

View File

@@ -10,6 +10,7 @@ export const StyledPage = styled('div')<{ resizing?: boolean }>(
transition: 'background-color .5s', transition: 'background-color .5s',
display: 'flex', display: 'flex',
flexGrow: '1', flexGrow: '1',
flexDirection: 'row',
'--affine-editor-width': '686px', '--affine-editor-width': '686px',
[theme.breakpoints.down('sm')]: { [theme.breakpoints.down('sm')]: {
'--affine-editor-width': '550px', '--affine-editor-width': '550px',
@@ -40,17 +41,15 @@ export const StyledWrapper = styled('div')(() => {
}; };
}); });
export const MainContainerWrapper = styled('div')<{ resizing: boolean }>( export const MainContainerWrapper = styled('div')(() => {
({ resizing }) => { return {
return { display: 'flex',
display: 'flex', flexGrow: 1,
flexGrow: 1, position: 'relative',
position: 'relative', maxWidth: '100vw',
maxWidth: '100vw', overflow: 'auto',
overflow: 'auto', };
}; });
}
);
export const MainContainer = styled('div')(({ theme }) => { export const MainContainer = styled('div')(({ theme }) => {
return { return {

View File

@@ -33,17 +33,11 @@ import {
} from '../atoms/public-workspace'; } from '../atoms/public-workspace';
import { HelpIsland } from '../components/pure/help-island'; import { HelpIsland } from '../components/pure/help-island';
import { PageLoading } from '../components/pure/loading'; import { PageLoading } from '../components/pure/loading';
import WorkSpaceSliderBar from '../components/pure/workspace-slider-bar'; import { RootAppSidebar } from '../components/root-app-sidebar';
import { useCurrentWorkspace } from '../hooks/current/use-current-workspace'; import { useCurrentWorkspace } from '../hooks/current/use-current-workspace';
import { useRouterHelper } from '../hooks/use-router-helper'; import { useRouterHelper } from '../hooks/use-router-helper';
import { useRouterTitle } from '../hooks/use-router-title'; import { useRouterTitle } from '../hooks/use-router-title';
import { useRouterWithWorkspaceIdDefense } from '../hooks/use-router-with-workspace-id-defense'; import { useRouterWithWorkspaceIdDefense } from '../hooks/use-router-with-workspace-id-defense';
import {
useSidebarFloating,
useSidebarResizing,
useSidebarStatus,
useSidebarWidth,
} from '../hooks/use-sidebar-status';
import { useSyncRouterWithCurrentPageId } from '../hooks/use-sync-router-with-current-page-id'; import { useSyncRouterWithCurrentPageId } from '../hooks/use-sync-router-with-current-page-id';
import { useSyncRouterWithCurrentWorkspaceId } from '../hooks/use-sync-router-with-current-workspace-id'; import { useSyncRouterWithCurrentWorkspaceId } from '../hooks/use-sync-router-with-current-workspace-id';
import { useWorkspaces } from '../hooks/use-workspaces'; import { useWorkspaces } from '../hooks/use-workspaces';
@@ -55,9 +49,6 @@ import {
MainContainer, MainContainer,
MainContainerWrapper, MainContainerWrapper,
StyledPage, StyledPage,
StyledSliderResizer,
StyledSliderResizerInner,
StyledSpacer,
StyledToolWrapper, StyledToolWrapper,
} from './styles'; } from './styles';
@@ -377,49 +368,14 @@ export const WorkspaceLayoutInner: FC<PropsWithChildren> = ({ children }) => {
const handleOpenQuickSearchModal = useCallback(() => { const handleOpenQuickSearchModal = useCallback(() => {
setOpenQuickSearchModalAtom(true); setOpenQuickSearchModalAtom(true);
}, [setOpenQuickSearchModalAtom]); }, [setOpenQuickSearchModalAtom]);
const [resizingSidebar, setIsResizing] = useSidebarResizing();
const [sidebarOpen, setSidebarOpen] = useSidebarStatus();
const sidebarFloating = useSidebarFloating();
const [sidebarWidth, setSliderWidth] = useSidebarWidth();
const actualSidebarWidth = !sidebarOpen
? 0
: sidebarFloating
? 'calc(10vw + 400px)'
: sidebarWidth;
const mainWidth =
sidebarOpen && !sidebarFloating ? `calc(100% - ${sidebarWidth}px)` : '100%';
const [resizing] = useSidebarResizing();
const onResizeStart = useCallback(() => {
let resized = false;
function onMouseMove(e: MouseEvent) {
const newWidth = Math.min(480, Math.max(e.clientX, 256));
setSliderWidth(newWidth);
setIsResizing(true);
resized = true;
}
document.addEventListener('mousemove', onMouseMove);
document.addEventListener(
'mouseup',
() => {
// if not resized, toggle sidebar
if (!resized) {
setSidebarOpen(o => !o);
}
setIsResizing(false);
document.removeEventListener('mousemove', onMouseMove);
},
{ once: true }
);
}, [setIsResizing, setSidebarOpen, setSliderWidth]);
return ( return (
<> <>
<Head> <Head>
<title>{title}</title> <title>{title}</title>
</Head> </Head>
<StyledPage resizing={resizingSidebar}> <StyledPage>
<WorkSpaceSliderBar <RootAppSidebar
isPublicWorkspace={isPublicWorkspace} isPublicWorkspace={isPublicWorkspace}
onOpenQuickSearchModal={handleOpenQuickSearchModal} onOpenQuickSearchModal={handleOpenQuickSearchModal}
currentWorkspace={currentWorkspace} currentWorkspace={currentWorkspace}
@@ -436,23 +392,7 @@ export const WorkspaceLayoutInner: FC<PropsWithChildren> = ({ children }) => {
currentPath={router.asPath.split('?')[0]} currentPath={router.asPath.split('?')[0]}
paths={isPublicWorkspace ? publicPathGenerator : pathGenerator} paths={isPublicWorkspace ? publicPathGenerator : pathGenerator}
/> />
<StyledSpacer <MainContainerWrapper>
floating={sidebarFloating}
resizing={resizing}
sidebarOpen={sidebarOpen}
style={{ width: actualSidebarWidth }}
>
{!sidebarFloating && sidebarOpen && (
<StyledSliderResizer
data-testid="sliderBar-resizer"
isResizing={resizing}
onMouseDown={onResizeStart}
>
<StyledSliderResizerInner isResizing={resizing} />
</StyledSliderResizer>
)}
</StyledSpacer>
<MainContainerWrapper resizing={resizing} style={{ width: mainWidth }}>
<MainContainer className="main-container"> <MainContainer className="main-container">
<Suspense fallback={<PageLoading text={t('Page is Loading')} />}> <Suspense fallback={<PageLoading text={t('Page is Loading')} />}>
{isLoading ? ( {isLoading ? (

View File

@@ -33,9 +33,11 @@
"@mui/base": "5.0.0-alpha.127", "@mui/base": "5.0.0-alpha.127",
"@mui/icons-material": "^5.11.16", "@mui/icons-material": "^5.11.16",
"@mui/material": "^5.12.2", "@mui/material": "^5.12.2",
"@popperjs/core": "^2.11.7",
"@radix-ui/react-avatar": "^1.0.2", "@radix-ui/react-avatar": "^1.0.2",
"@toeverything/hooks": "workspace:*", "@toeverything/hooks": "workspace:*",
"@toeverything/theme": "workspace:*", "@toeverything/theme": "workspace:*",
"@vanilla-extract/dynamic": "^2.0.3",
"clsx": "^1.2.1", "clsx": "^1.2.1",
"jotai": "^2.0.4", "jotai": "^2.0.4",
"lit": "^2.7.2", "lit": "^2.7.2",

View File

@@ -0,0 +1,96 @@
import { baseTheme } from '@toeverything/theme';
import { createVar, style } from '@vanilla-extract/css';
export const floatingMaxWidth = 768;
export const navWidthVar = createVar('nav-width');
export const navStyle = style({
position: 'relative',
backgroundColor: 'var(--affine-background-secondary-color)',
width: navWidthVar,
minWidth: navWidthVar,
height: '100%',
display: 'flex',
flexDirection: 'column',
transition: 'margin-left .3s',
zIndex: parseInt(baseTheme.zIndexModal),
borderRight: '1px solid var(--affine-border-color)',
'@media': {
[`(max-width: ${floatingMaxWidth}px)`]: {
position: 'absolute',
width: `calc(10vw + ${navWidthVar})`,
selectors: {
'&[data-open="false"]': {
marginLeft: `calc((10vw + ${navWidthVar}) * -1)`,
},
'&[data-is-macos-electron="true"]': {
backgroundColor: 'var(--affine-background-secondary-color)',
},
},
},
},
selectors: {
'&[data-open="false"]': {
marginLeft: `calc(${navWidthVar} * -1)`,
},
'&[data-is-macos-electron="true"]': {
backgroundColor: 'transparent',
},
},
vars: {
[navWidthVar]: '256px',
},
});
export const navHeaderStyle = style({
flex: '0 0 auto',
height: '52px',
padding: '0px 16px 0px 10px',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
selectors: {
'&[data-is-macos-electron="true"]': {
justifyContent: 'flex-end',
},
},
});
export const navBodyStyle = style({
flex: '1 1 auto',
});
export const navFooterStyle = style({
flex: '0 0 auto',
borderTop: '1px solid var(--affine-border-color)',
});
export const sidebarButtonStyle = style({
width: '32px',
height: '32px',
color: 'var(--affine-icon-color)',
});
export const sidebarFloatMaskStyle = style({
transition: 'opacity .15s',
opacity: 0,
pointerEvents: 'none',
position: 'fixed',
top: 0,
left: 0,
right: '100%',
bottom: 0,
zIndex: parseInt(baseTheme.zIndexModal) - 1,
background: 'var(--affine-background-modal-color)',
'@media': {
[`(max-width: ${floatingMaxWidth}px)`]: {
selectors: {
'&[data-open="true"]': {
opacity: 1,
pointerEvents: 'auto',
right: '0',
},
},
},
},
});

View File

@@ -0,0 +1,7 @@
import { atomWithStorage } from 'jotai/utils';
export const appSidebarOpenAtom = atomWithStorage('app-sidebar-open', true);
export const appSidebarWidthAtom = atomWithStorage(
'app-sidebar-width',
256 /* px */
);

View File

@@ -0,0 +1,53 @@
import { IconButton } from '@affine/component';
import { SidebarIcon } from '@blocksuite/icons';
import type { Meta, StoryFn } from '@storybook/react';
import { useAtom } from 'jotai';
import { useRef } from 'react';
import { AppSidebar, appSidebarOpenAtom, ResizeIndicator } from '.';
import { navHeaderStyle, sidebarButtonStyle } from './index.css';
export default {
title: 'Components/AppSidebar',
component: AppSidebar,
} satisfies Meta;
const Footer = () => <div>Add Page</div>;
export const Default: StoryFn = props => {
const [open, setOpen] = useAtom(appSidebarOpenAtom);
const ref = useRef<HTMLElement>(null);
return (
<>
<main
style={{
position: 'relative',
width: '100vw',
height: '600px',
overflow: 'hidden',
display: 'flex',
flexDirection: 'row',
}}
>
<AppSidebar footer={<Footer />} ref={ref}>
Test
</AppSidebar>
<ResizeIndicator targetElement={ref} />
<div>
<div className={navHeaderStyle}>
{!open && (
<IconButton
className={sidebarButtonStyle}
onClick={() => {
setOpen(true);
}}
>
<SidebarIcon width={24} height={24} />
</IconButton>
)}
</div>
</div>
</main>
</>
);
};

View File

@@ -0,0 +1,108 @@
import { getEnvironment } from '@affine/env';
import {
ArrowLeftSmallIcon,
ArrowRightSmallIcon,
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, useImperativeHandle, useRef } from 'react';
import { IconButton } from '../../ui/button/IconButton';
import {
navBodyStyle,
navFooterStyle,
navHeaderStyle,
navStyle,
navWidthVar,
sidebarButtonStyle,
sidebarFloatMaskStyle,
} from './index.css';
import { appSidebarOpenAtom, appSidebarWidthAtom } from './index.jotai';
export { appSidebarOpenAtom };
export type AppSidebarProps = PropsWithChildren<{
footer?: ReactNode | undefined;
}>;
export const AppSidebar = forwardRef<HTMLElement, AppSidebarProps>(
function AppSidebar(props, forwardedRef): ReactElement {
const ref = useRef<HTMLElement>(null);
const [open, setOpen] = useAtom(appSidebarOpenAtom);
const appSidebarWidth = useAtomValue(appSidebarWidthAtom);
const handleSidebarOpen = useCallback(() => {
setOpen(open => !open);
}, [setOpen]);
useImperativeHandle(forwardedRef, () => ref.current as HTMLElement);
const environment = getEnvironment();
const isMacosDesktop = environment.isDesktop && environment.isMacOs;
return (
<>
<nav
className={navStyle}
ref={ref}
style={assignInlineVars({
[navWidthVar]: `${appSidebarWidth}px`,
})}
data-testid="app-sidebar"
data-open={open}
data-is-macos-electron={isMacosDesktop}
>
<div
className={navHeaderStyle}
data-is-macos-electron={isMacosDesktop}
>
{isMacosDesktop && (
<>
<IconButton
size="middle"
onClick={() => {
window.history.back();
}}
>
<ArrowLeftSmallIcon />
</IconButton>
<IconButton
size="middle"
onClick={() => {
window.history.forward();
}}
style={{ marginLeft: '32px' }}
>
<ArrowRightSmallIcon />
</IconButton>
</>
)}
<IconButton
data-testid="app-sidebar-arrow-button-collapse"
className={sidebarButtonStyle}
onClick={handleSidebarOpen}
>
<SidebarIcon width={24} height={24} />
</IconButton>
</div>
<div className={navBodyStyle}>{props.children}</div>
<div className={navFooterStyle}>{props.footer}</div>
</nav>
<div
data-testid="app-sidebar-float-mask"
data-open={open}
className={sidebarFloatMaskStyle}
onClick={useCallback(() => {
setOpen(false);
}, [setOpen])}
/>
</>
);
}
);
export type { ResizeIndicatorProps } from './resize-indicator';
export { ResizeIndicator } from './resize-indicator';

View File

@@ -0,0 +1,37 @@
import { style } from '@vanilla-extract/css';
import { navWidthVar } from '../index.css';
export const spacerStyle = style({
position: 'absolute',
width: '1px',
left: navWidthVar,
top: 0,
bottom: 0,
height: '100%',
zIndex: 'calc(var(--affine-z-index-modal) - 1)',
backgroundColor: 'var(--affine-border-color)',
opacity: 0,
cursor: 'col-resize',
'@media': {
'(max-width: 600px)': {
// do not allow resizing on mobile
display: 'none',
},
},
transition: 'opacity 0.15s ease 0.1s',
selectors: {
'&:hover': {
opacity: 1,
},
'&[data-resizing="true"]': {
transition: 'width .3s, min-width .3s, max-width .3s',
},
'&[data-open="false"]': {
display: 'none',
},
'&[data-open="open"]': {
display: 'block',
},
},
});

View File

@@ -0,0 +1,86 @@
import type { Instance } from '@popperjs/core';
import { createPopper } from '@popperjs/core';
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
import type { ReactElement, RefObject } from 'react';
import {
useCallback,
useDeferredValue,
useEffect,
useRef,
useState,
} from 'react';
import { appSidebarOpenAtom, appSidebarWidthAtom } from '../index.jotai';
import { spacerStyle } from './index.css';
export type ResizeIndicatorProps = {
targetElement: RefObject<HTMLElement>;
};
export const ResizeIndicator = (props: ResizeIndicatorProps): ReactElement => {
const ref = useRef<HTMLDivElement>(null);
const popperRef = useRef<Instance | null>(null);
const setWidth = useSetAtom(appSidebarWidthAtom);
const [sidebarOpen, setSidebarOpen] = useAtom(appSidebarOpenAtom);
const [isResizing, setIsResizing] = useState(false);
useEffect(() => {
if (ref.current) {
if (props.targetElement.current) {
popperRef.current = createPopper(
props.targetElement.current,
ref.current,
{
placement: 'right',
}
);
}
}
}, [props.targetElement]);
const sidebarWidth = useDeferredValue(useAtomValue(appSidebarWidthAtom));
useEffect(() => {
if (popperRef.current) {
popperRef.current.update();
}
}, [sidebarWidth]);
const onResizeStart = useCallback(() => {
let resized = false;
function onMouseMove(e: MouseEvent) {
e.preventDefault();
const newWidth = Math.min(480, Math.max(e.clientX, 256));
setWidth(newWidth);
setIsResizing(true);
resized = true;
}
document.addEventListener('mousemove', onMouseMove);
document.addEventListener(
'mouseup',
() => {
// if not resized, toggle sidebar
if (!resized) {
setSidebarOpen(o => !o);
}
if (popperRef.current) {
popperRef.current.update();
}
setIsResizing(false);
document.removeEventListener('mousemove', onMouseMove);
},
{ once: true }
);
}, [setSidebarOpen, setWidth]);
return (
<div
ref={ref}
className={spacerStyle}
data-testid="app-sidebar-resizer"
data-resizing={isResizing}
data-open={sidebarOpen}
onMouseDown={onResizeStart}
/>
);
};

View File

@@ -66,16 +66,18 @@ export const changeLogWrapperSlideOutStyle = style({
animation: `${slideOut} .3s ease-in-out forwards`, animation: `${slideOut} .3s ease-in-out forwards`,
}); });
export const changeLogSlideInStyle = style({ export const changeLogSlideInStyle = style({
width: '110%', // fixme: if width is 100% and marginLeft is 0,
// the UI will overflow on app sidebar
width: '99%',
marginLeft: '2px',
height: '32px', height: '32px',
display: 'flex', display: 'flex',
justifyContent: 'flex-start', justifyContent: 'space-between',
alignItems: 'center', alignItems: 'center',
color: 'var(--affine-primary-color)', color: 'var(--affine-primary-color)',
backgroundColor: 'var(--affine-tertiary-color)', backgroundColor: 'var(--affine-tertiary-color)',
border: '1px solid var(--affine-primary-color)', border: '1px solid var(--affine-primary-color)',
borderRight: 'none', borderRight: 'none',
marginLeft: '8px',
paddingLeft: '8px', paddingLeft: '8px',
borderRadius: '16px 0 0 16px', borderRadius: '16px 0 0 16px',
cursor: 'pointer', cursor: 'pointer',
@@ -89,7 +91,6 @@ export const changeLogSlideOutStyle = style({
animation: `${slideOut2} .3s ease-in-out forwards`, animation: `${slideOut2} .3s ease-in-out forwards`,
}); });
export const linkStyle = style({ export const linkStyle = style({
flexGrow: 1,
textAlign: 'left', textAlign: 'left',
color: 'var(--affine-text-emphasis-color)', color: 'var(--affine-text-emphasis-color)',
display: 'flex', display: 'flex',
@@ -103,6 +104,6 @@ export const iconStyle = style({
}); });
export const iconButtonStyle = style({ export const iconButtonStyle = style({
fontSize: '20px', fontSize: '20px',
marginRight: '12%', marginRight: '0',
color: 'var(--affine-icon-color)', color: 'var(--affine-icon-color)',
}); });

View File

@@ -30,7 +30,6 @@ export const StyledIconButton = styled('button', {
fontSize?: CSSProperties['fontSize']; fontSize?: CSSProperties['fontSize'];
}>( }>(
({ ({
theme,
width, width,
height, height,
borderRadius, borderRadius,

View File

@@ -7,15 +7,15 @@ import { waitMarkdownImported } from '../libs/page-logic';
test('Collapse Sidebar', async ({ page }) => { test('Collapse Sidebar', async ({ page }) => {
await openHomePage(page); await openHomePage(page);
await waitMarkdownImported(page); await waitMarkdownImported(page);
await page.getByTestId('sliderBar-arrowButton-collapse').click(); await page.getByTestId('app-sidebar-arrow-button-collapse').click();
const sliderBarArea = page.getByTestId('sliderBar-root'); const sliderBarArea = page.getByTestId('app-sidebar');
await expect(sliderBarArea).not.toBeInViewport(); await expect(sliderBarArea).not.toBeInViewport();
}); });
test('Expand Sidebar', async ({ page }) => { test('Expand Sidebar', async ({ page }) => {
await openHomePage(page); await openHomePage(page);
await waitMarkdownImported(page); await waitMarkdownImported(page);
await page.getByTestId('sliderBar-arrowButton-collapse').click(); await page.getByTestId('app-sidebar-arrow-button-collapse').click();
const sliderBarArea = page.getByTestId('sliderBar-inner'); const sliderBarArea = page.getByTestId('sliderBar-inner');
await expect(sliderBarArea).not.toBeInViewport(); await expect(sliderBarArea).not.toBeInViewport();
@@ -29,7 +29,7 @@ test('Click resizer can close sidebar', async ({ page }) => {
const sliderBarArea = page.getByTestId('sliderBar-inner'); const sliderBarArea = page.getByTestId('sliderBar-inner');
await expect(sliderBarArea).toBeVisible(); await expect(sliderBarArea).toBeVisible();
await page.getByTestId('sliderBar-resizer').click(); await page.getByTestId('app-sidebar-resizer').click();
await expect(sliderBarArea).not.toBeInViewport(); await expect(sliderBarArea).not.toBeInViewport();
}); });
@@ -39,14 +39,14 @@ test('Drag resizer can resize sidebar', async ({ page }) => {
const sliderBarArea = page.getByTestId('sliderBar-inner'); const sliderBarArea = page.getByTestId('sliderBar-inner');
await expect(sliderBarArea).toBeVisible(); await expect(sliderBarArea).toBeVisible();
const sliderResizer = page.getByTestId('sliderBar-resizer'); const sliderResizer = page.getByTestId('app-sidebar-resizer');
await sliderResizer.hover(); await sliderResizer.hover();
await page.mouse.down(); await page.mouse.down();
await page.mouse.move(400, 300, { await page.mouse.move(400, 300, {
steps: 10, steps: 10,
}); });
await page.mouse.up(); await page.mouse.up();
const boundingBox = await page.getByTestId('sliderBar-root').boundingBox(); const boundingBox = await page.getByTestId('app-sidebar').boundingBox();
expect(boundingBox?.width).toBe(400); expect(boundingBox?.width).toBe(400);
}); });
@@ -54,9 +54,7 @@ test('Sidebar in between sm & md breakpoint', async ({ page }) => {
await openHomePage(page); await openHomePage(page);
await waitMarkdownImported(page); await waitMarkdownImported(page);
const sliderBarArea = page.getByTestId('sliderBar-inner'); const sliderBarArea = page.getByTestId('sliderBar-inner');
const sliderBarModalBackground = page.getByTestId( const sliderBarModalBackground = page.getByTestId('app-sidebar-float-mask');
'sliderBar-modalBackground'
);
await expect(sliderBarArea).toBeInViewport(); await expect(sliderBarArea).toBeInViewport();
await expect(sliderBarModalBackground).not.toBeVisible(); await expect(sliderBarModalBackground).not.toBeVisible();

View File

@@ -178,7 +178,9 @@ test('When opening the website for the first time, the first folding sidebar wil
await waitMarkdownImported(page); await waitMarkdownImported(page);
const quickSearchTips = page.locator('[data-testid=quick-search-tips]'); const quickSearchTips = page.locator('[data-testid=quick-search-tips]');
await expect(quickSearchTips).not.toBeVisible(); await expect(quickSearchTips).not.toBeVisible();
await page.getByTestId('sliderBar-arrowButton-collapse').click(); await page.getByTestId('app-sidebar-arrow-button-collapse').click();
await page.waitForTimeout(200);
await page.getByTestId('sliderBar-arrowButton-expand').click();
const sliderBarArea = page.getByTestId('sliderBar-inner'); const sliderBarArea = page.getByTestId('sliderBar-inner');
await expect(sliderBarArea).not.toBeInViewport(); await expect(sliderBarArea).not.toBeInViewport();
await expect(quickSearchTips).toBeVisible(); await expect(quickSearchTips).toBeVisible();
@@ -192,15 +194,17 @@ test('After appearing once, it will not appear a second time', async ({
await waitMarkdownImported(page); await waitMarkdownImported(page);
const quickSearchTips = page.locator('[data-testid=quick-search-tips]'); const quickSearchTips = page.locator('[data-testid=quick-search-tips]');
await expect(quickSearchTips).not.toBeVisible(); await expect(quickSearchTips).not.toBeVisible();
await page.getByTestId('sliderBar-arrowButton-collapse').click(); await page.getByTestId('app-sidebar-arrow-button-collapse').click();
await page.waitForTimeout(200);
await page.getByTestId('sliderBar-arrowButton-expand').click();
const sliderBarArea = page.getByTestId('sliderBar'); const sliderBarArea = page.getByTestId('sliderBar');
await expect(sliderBarArea).not.toBeVisible(); await expect(sliderBarArea).not.toBeVisible();
await expect(quickSearchTips).toBeVisible(); await expect(quickSearchTips).toBeVisible();
await page.locator('[data-testid=quick-search-got-it]').click(); await page.locator('[data-testid=quick-search-got-it]').click();
await expect(quickSearchTips).not.toBeVisible(); await expect(quickSearchTips).not.toBeVisible();
await page.reload(); await page.reload();
await page.locator('[data-testid=sliderBar-arrowButton-expand]').click(); await page.waitForSelector('v-line');
await page.getByTestId('sliderBar-arrowButton-collapse').click(); await page.getByTestId('app-sidebar-arrow-button-collapse').click();
await expect(quickSearchTips).not.toBeVisible(); await expect(quickSearchTips).not.toBeVisible();
}); });

View File

@@ -7,7 +7,7 @@ import { waitMarkdownImported } from '../libs/page-logic';
test('Create subpage', async ({ page }) => { test('Create subpage', async ({ page }) => {
await openHomePage(page); await openHomePage(page);
await waitMarkdownImported(page); await waitMarkdownImported(page);
await page.getByTestId('sliderBar-arrowButton-collapse').click(); await page.getByTestId('app-sidebar-arrow-button-collapse').click();
const sliderBarArea = page.getByTestId('sliderBar-inner'); const sliderBarArea = page.getByTestId('sliderBar-inner');
await expect(sliderBarArea).not.toBeInViewport(); await expect(sliderBarArea).not.toBeInViewport();
}); });

View File

@@ -64,6 +64,7 @@ __metadata:
"@mui/base": 5.0.0-alpha.127 "@mui/base": 5.0.0-alpha.127
"@mui/icons-material": ^5.11.16 "@mui/icons-material": ^5.11.16
"@mui/material": ^5.12.2 "@mui/material": ^5.12.2
"@popperjs/core": ^2.11.7
"@radix-ui/react-avatar": ^1.0.2 "@radix-ui/react-avatar": ^1.0.2
"@storybook/addon-actions": ^7.0.7 "@storybook/addon-actions": ^7.0.7
"@storybook/addon-coverage": ^0.0.8 "@storybook/addon-coverage": ^0.0.8
@@ -84,6 +85,7 @@ __metadata:
"@types/react-dnd": ^3.0.2 "@types/react-dnd": ^3.0.2
"@types/react-dom": 18.0.11 "@types/react-dom": 18.0.11
"@vanilla-extract/css": ^1.11.0 "@vanilla-extract/css": ^1.11.0
"@vanilla-extract/dynamic": ^2.0.3
"@vitejs/plugin-react": ^4.0.0 "@vitejs/plugin-react": ^4.0.0
clsx: ^1.2.1 clsx: ^1.2.1
concurrently: ^8.0.1 concurrently: ^8.0.1
@@ -9259,6 +9261,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@vanilla-extract/dynamic@npm:^2.0.3":
version: 2.0.3
resolution: "@vanilla-extract/dynamic@npm:2.0.3"
dependencies:
"@vanilla-extract/private": ^1.0.3
checksum: 9ad4068d7e28361a7aca46b5f14094e74613428fb600e54227d8ba7a35926c0d7339de1876eb2b3563f6d97c0f08fa09d5ff597f76776f8edca37510165682b0
languageName: node
linkType: hard
"@vanilla-extract/integration@npm:^6.0.0, @vanilla-extract/integration@npm:^6.0.2": "@vanilla-extract/integration@npm:^6.0.0, @vanilla-extract/integration@npm:^6.0.2":
version: 6.2.1 version: 6.2.1
resolution: "@vanilla-extract/integration@npm:6.2.1" resolution: "@vanilla-extract/integration@npm:6.2.1"