feat: modify sidebar style (#1703)

This commit is contained in:
Qi
2023-03-28 02:41:04 +08:00
committed by GitHub
parent c2b1a9b118
commit 628ce08d8d
24 changed files with 503 additions and 383 deletions
+1 -1
View File
@@ -18,7 +18,7 @@
"@affine/workspace": "workspace:*", "@affine/workspace": "workspace:*",
"@blocksuite/blocks": "0.5.0-20230324040005-14417c2", "@blocksuite/blocks": "0.5.0-20230324040005-14417c2",
"@blocksuite/editor": "0.5.0-20230324040005-14417c2", "@blocksuite/editor": "0.5.0-20230324040005-14417c2",
"@blocksuite/icons": "2.0.23", "@blocksuite/icons": "latest",
"@blocksuite/store": "0.5.0-20230324040005-14417c2", "@blocksuite/store": "0.5.0-20230324040005-14417c2",
"@emotion/cache": "^11.10.5", "@emotion/cache": "^11.10.5",
"@emotion/react": "^11.10.6", "@emotion/react": "^11.10.6",
@@ -79,7 +79,7 @@ export const OperationCell: React.FC<OperationCellProps> = ({
disablePortal={true} disablePortal={true}
trigger="click" trigger="click"
> >
<IconButton darker={true}> <IconButton>
<MoreVerticalIcon /> <MoreVerticalIcon />
</IconButton> </IconButton>
</Menu> </Menu>
@@ -127,7 +127,6 @@ export const TrashOperationCell: React.FC<TrashOperationCellProps> = ({
<FlexWrapper> <FlexWrapper>
<Tooltip content={t('Restore it')} placement="top-start"> <Tooltip content={t('Restore it')} placement="top-start">
<IconButton <IconButton
darker={true}
style={{ marginRight: '12px' }} style={{ marginRight: '12px' }}
onClick={() => { onClick={() => {
onRestorePage(id); onRestorePage(id);
@@ -139,7 +138,6 @@ export const TrashOperationCell: React.FC<TrashOperationCellProps> = ({
</Tooltip> </Tooltip>
<Tooltip content={t('Delete permanently')} placement="top-start"> <Tooltip content={t('Delete permanently')} placement="top-start">
<IconButton <IconButton
darker={true}
onClick={() => { onClick={() => {
setOpen(true); setOpen(true);
}} }}
@@ -56,7 +56,6 @@ const FavoriteTag: React.FC<FavoriteTagProps> = ({
placement="top-start" placement="top-start"
> >
<IconButton <IconButton
darker={true}
iconSize={[20, 20]} iconSize={[20, 20]}
onClick={e => { onClick={e => {
e.stopPropagation(); e.stopPropagation();
@@ -88,13 +87,12 @@ type PageListProps = {
}; };
const filter = { const filter = {
all: (pageMeta: PageMeta, allMetas: PageMeta[]) => !pageMeta.trash, all: (pageMeta: PageMeta) => !pageMeta.trash,
trash: (pageMeta: PageMeta, allMetas: PageMeta[]) => { trash: (pageMeta: PageMeta, allMetas: PageMeta[]) => {
const parentMeta = allMetas.find(m => m.subpageIds?.includes(pageMeta.id)); const parentMeta = allMetas.find(m => m.subpageIds?.includes(pageMeta.id));
return !parentMeta?.trash && pageMeta.trash; return !parentMeta?.trash && pageMeta.trash;
}, },
favorite: (pageMeta: PageMeta, allMetas: PageMeta[]) => favorite: (pageMeta: PageMeta) => pageMeta.favorite && !pageMeta.trash,
pageMeta.favorite && !pageMeta.trash,
}; };
export const PageList: React.FC<PageListProps> = ({ export const PageList: React.FC<PageListProps> = ({
@@ -8,16 +8,16 @@ import {
export const StyledHeaderContainer = styled('div')<{ hasWarning: boolean }>( export const StyledHeaderContainer = styled('div')<{ hasWarning: boolean }>(
({ hasWarning }) => { ({ hasWarning }) => {
return { return {
height: hasWarning ? '96px' : '60px', height: hasWarning ? '96px' : '48px',
}; };
} }
); );
export const StyledHeader = styled('div')<{ hasWarning: boolean }>( export const StyledHeader = styled('div')<{ hasWarning: boolean }>(
({ theme }) => { ({ theme }) => {
return { return {
height: '64px', height: '48px',
width: '100%', width: '100%',
padding: '0 28px', padding: '0 20px',
...displayFlex('space-between', 'center'), ...displayFlex('space-between', 'center'),
background: theme.colors.pageBackground, background: theme.colors.pageBackground,
transition: 'background-color 0.5s', transition: 'background-color 0.5s',
@@ -1,9 +1,16 @@
import { CloudWorkspaceIcon, LocalWorkspaceIcon } from '@blocksuite/icons';
import type React from 'react'; import type React from 'react';
import { useCurrentWorkspace } from '../../../../hooks/current/use-current-workspace';
import { useBlockSuiteWorkspaceName } from '../../../../hooks/use-blocksuite-workspace-name'; import { useBlockSuiteWorkspaceName } from '../../../../hooks/use-blocksuite-workspace-name';
import type { RemWorkspace } from '../../../../shared'; import type { RemWorkspace } from '../../../../shared';
import { WorkspaceAvatar } from '../../workspace-avatar'; import { WorkspaceAvatar } from '../../workspace-avatar';
import { SelectorWrapper, WorkspaceName } from './styles'; import {
StyledSelectorContainer,
StyledSelectorWrapper,
StyledWorkspaceName,
StyledWorkspaceStatus,
} from './styles';
export type WorkspaceSelectorProps = { export type WorkspaceSelectorProps = {
currentWorkspace: RemWorkspace | null; currentWorkspace: RemWorkspace | null;
@@ -17,19 +24,32 @@ export const WorkspaceSelector: React.FC<WorkspaceSelectorProps> = ({
const [name] = useBlockSuiteWorkspaceName( const [name] = useBlockSuiteWorkspaceName(
currentWorkspace?.blockSuiteWorkspace ?? null currentWorkspace?.blockSuiteWorkspace ?? null
); );
const [workspace] = useCurrentWorkspace();
return ( return (
<> <StyledSelectorContainer onClick={onClick} data-testid="current-workspace">
<SelectorWrapper onClick={onClick} data-testid="current-workspace"> <WorkspaceAvatar
<WorkspaceAvatar data-testid="workspace-avatar"
data-testid="workspace-avatar" style={{
style={{ flexShrink: 0,
flexShrink: 0, }}
}} size={40}
size={32} workspace={currentWorkspace}
workspace={currentWorkspace} />
/> <StyledSelectorWrapper>
<WorkspaceName data-testid="workspace-name">{name}</WorkspaceName> <StyledWorkspaceName data-testid="workspace-name">
</SelectorWrapper> {name}
</> </StyledWorkspaceName>
{workspace && (
<StyledWorkspaceStatus>
{workspace.flavour === 'local' ? (
<LocalWorkspaceIcon />
) : (
<CloudWorkspaceIcon />
)}
{workspace.flavour === 'local' ? 'Local' : 'AFFiNE Cloud'}
</StyledWorkspaceStatus>
)}
</StyledSelectorWrapper>
</StyledSelectorContainer>
); );
}; };
@@ -1,13 +1,14 @@
import { MuiAvatar, textEllipsis } from '@affine/component'; import { displayFlex, textEllipsis } from '@affine/component';
import { styled } from '@affine/component'; import { styled } from '@affine/component';
export const SelectorWrapper = styled('div')(({ theme }) => { export const StyledSelectorContainer = styled('div')(({ theme }) => {
return { return {
marginTop: '4px', marginTop: '4px',
height: '56px', height: '58px',
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
padding: '0 44px 0 12px', padding: '0 6px',
borderRadius: '5px', marginBottom: '16px',
borderRadius: '8px',
color: theme.colors.textColor, color: theme.colors.textColor,
position: 'relative', position: 'relative',
':hover': { ':hover': {
@@ -17,17 +18,31 @@ export const SelectorWrapper = styled('div')(({ theme }) => {
}; };
}); });
export const Avatar = styled(MuiAvatar)({ export const StyledSelectorWrapper = styled('div')(() => {
height: '28px',
width: '28px',
});
export const WorkspaceName = styled('span')(({ theme }) => {
return { return {
marginLeft: '12px', marginLeft: '8px',
fontSize: theme.font.h6,
fontWeight: 500,
flexGrow: 1, flexGrow: 1,
overflow: 'hidden',
};
});
export const StyledWorkspaceName = styled('div')(() => {
return {
lineHeight: '24px',
fontWeight: 600,
...textEllipsis(1), ...textEllipsis(1),
}; };
}); });
export const StyledWorkspaceStatus = styled('div')(({ theme }) => {
return {
height: '22px',
...displayFlex('flex-start', 'center'),
fontSize: theme.font.sm,
color: theme.colors.secondaryTextColor,
svg: {
color: theme.colors.iconColor,
fontSize: theme.font.base,
marginRight: '4px',
},
};
});
@@ -0,0 +1,70 @@
import { MuiCollapse } from '@affine/component';
import { useTranslation } from '@affine/i18n';
import { EdgelessIcon, PageIcon } from '@blocksuite/icons';
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';
export const FavoriteList = ({
pageMeta,
openPage,
showList,
}: FavoriteListProps) => {
const router = useRouter();
const { t } = useTranslation();
const record = useAtomValue(workspacePreferredModeAtom);
const favoriteList = useMemo(
() => pageMeta.filter(p => p.favorite && !p.trash),
[pageMeta]
);
return (
<MuiCollapse
in={showList}
style={{
maxHeight: 300,
overflowY: 'auto',
marginLeft: '16px',
}}
>
{favoriteList.map((pageMeta, index) => {
const active = router.query.pageId === pageMeta.id;
return (
<div key={`${pageMeta}-${index}`}>
<StyledCollapseItem
data-testid={`favorite-list-item-${pageMeta.id}`}
active={active}
ref={ref => {
if (ref && active) {
ref.scrollIntoView({ behavior: 'smooth' });
}
}}
onClick={() => {
if (active) {
return;
}
openPage(pageMeta.id);
}}
>
{record[pageMeta.id] === 'edgeless' ? (
<EdgelessIcon />
) : (
<PageIcon />
)}
{pageMeta.title || 'Untitled'}
</StyledCollapseItem>
</div>
);
})}
{favoriteList.length === 0 && (
<StyledCollapseItem disable={true}>{t('No item')}</StyledCollapseItem>
)}
</MuiCollapse>
);
};
export default FavoriteList;
@@ -0,0 +1,62 @@
import { useTranslation } from '@affine/i18n';
import { ArrowDownSmallIcon, FavoriteIcon } from '@blocksuite/icons';
import { useCallback, useState } from 'react';
import { usePageMeta } from '../../../../hooks/use-page-meta';
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'
>) => {
const currentWorkspaceId = currentWorkspace?.id || null;
const pageMeta = usePageMeta(currentWorkspace?.blockSuiteWorkspace ?? null);
const [showSubFavorite, setOpenSubFavorite] = useState(true);
const { t } = useTranslation();
return (
<>
<StyledListItem
active={
currentPath ===
(currentWorkspaceId && paths.favorite(currentWorkspaceId))
}
>
<StyledCollapseButton
onClick={useCallback(() => {
setOpenSubFavorite(!showSubFavorite);
}, [showSubFavorite])}
collapse={showSubFavorite}
>
<ArrowDownSmallIcon />
</StyledCollapseButton>
<StyledLink
href={{
pathname: currentWorkspaceId && paths.favorite(currentWorkspaceId),
}}
>
<FavoriteIcon />
{t('Favorites')}
</StyledLink>
</StyledListItem>
<FavoriteList
currentPageId={currentPageId}
showList={showSubFavorite}
openPage={openPage}
pageMeta={pageMeta}
/>
</>
);
};
export default Favorite;
@@ -1,35 +1,29 @@
import { MuiCollapse } from '@affine/component';
import { IconButton } from '@affine/component';
import { config } from '@affine/env'; import { config } from '@affine/env';
import { useTranslation } from '@affine/i18n'; import { useTranslation } from '@affine/i18n';
import { import {
ArrowDownSmallIcon,
DeleteTemporarilyIcon, DeleteTemporarilyIcon,
FavoriteIcon,
FolderIcon, FolderIcon,
PlusIcon, PlusIcon,
SearchIcon, SearchIcon,
SettingsIcon, SettingsIcon,
} from '@blocksuite/icons'; } from '@blocksuite/icons';
import type { Page, PageMeta } from '@blocksuite/store'; import type { Page, PageMeta } from '@blocksuite/store';
import Link from 'next/link';
import { useRouter } from 'next/router';
import type React from 'react'; import type React from 'react';
import { useCallback, useMemo, useState } from 'react'; import { useCallback } from 'react';
import { useSidebarStatus } from '../../../hooks/affine/use-sidebar-status'; import { useSidebarStatus } from '../../../hooks/affine/use-sidebar-status';
import { usePageMeta } from '../../../hooks/use-page-meta'; import { usePageMeta } from '../../../hooks/use-page-meta';
import type { RemWorkspace } from '../../../shared'; import type { RemWorkspace } from '../../../shared';
import { SidebarSwitch } from '../../affine/sidebar-switch'; import { SidebarSwitch } from '../../affine/sidebar-switch';
import Favorite from './favorite';
import { Pivot } from './pivot'; import { Pivot } from './pivot';
import { StyledListItem } from './shared-styles';
import { import {
StyledLink, StyledLink,
StyledListItem,
StyledNewPageButton, StyledNewPageButton,
StyledSidebarWrapper, StyledSidebarSwitchWrapper,
StyledSlidebarWrapper,
StyledSliderBar, StyledSliderBar,
StyledSliderBarWrapper,
StyledSubListItem,
} from './style'; } from './style';
import { WorkspaceSelector } from './WorkspaceSelector'; import { WorkspaceSelector } from './WorkspaceSelector';
@@ -40,57 +34,6 @@ export type FavoriteListProps = {
pageMeta: PageMeta[]; pageMeta: PageMeta[];
}; };
const FavoriteList: React.FC<FavoriteListProps> = ({
pageMeta,
openPage,
showList,
}) => {
const router = useRouter();
const { t } = useTranslation();
const favoriteList = useMemo(
() => pageMeta.filter(p => p.favorite && !p.trash),
[pageMeta]
);
return (
<MuiCollapse
in={showList}
style={{
maxHeight: 300,
overflowY: 'auto',
}}
>
{favoriteList.map((pageMeta, index) => {
const active = router.query.pageId === pageMeta.id;
return (
<div key={`${pageMeta}-${index}`}>
<StyledSubListItem
data-testid={`favorite-list-item-${pageMeta.id}`}
active={active}
ref={ref => {
if (ref && active) {
ref.scrollIntoView({ behavior: 'smooth' });
}
}}
onClick={() => {
if (active) {
return;
}
openPage(pageMeta.id);
}}
>
{pageMeta.title || 'Untitled'}
</StyledSubListItem>
</div>
);
})}
{favoriteList.length === 0 && (
<StyledSubListItem disable={true}>{t('No item')}</StyledSubListItem>
)}
</MuiCollapse>
);
};
export type WorkSpaceSliderBarProps = { export type WorkSpaceSliderBarProps = {
isPublicWorkspace: boolean; isPublicWorkspace: boolean;
onOpenQuickSearchModal: () => void; onOpenQuickSearchModal: () => void;
@@ -120,7 +63,6 @@ export const WorkSpaceSliderBar: React.FC<WorkSpaceSliderBarProps> = ({
onOpenWorkspaceListModal, onOpenWorkspaceListModal,
}) => { }) => {
const currentWorkspaceId = currentWorkspace?.id || null; const currentWorkspaceId = currentWorkspace?.id || null;
const [showSubFavorite, setOpenSubFavorite] = useState(true);
const { t } = useTranslation(); const { t } = useTranslation();
const [sidebarOpen] = useSidebarStatus(); const [sidebarOpen] = useSidebarStatus();
const pageMeta = usePageMeta(currentWorkspace?.blockSuiteWorkspace ?? null); const pageMeta = usePageMeta(currentWorkspace?.blockSuiteWorkspace ?? null);
@@ -131,15 +73,15 @@ export const WorkSpaceSliderBar: React.FC<WorkSpaceSliderBarProps> = ({
return ( return (
<> <>
<StyledSliderBar show={isPublicWorkspace ? false : sidebarOpen}> <StyledSliderBar show={isPublicWorkspace ? false : sidebarOpen}>
<StyledSidebarWrapper> <StyledSidebarSwitchWrapper>
<SidebarSwitch <SidebarSwitch
visible={sidebarOpen} visible={sidebarOpen}
tooltipContent={t('Collapse sidebar')} tooltipContent={t('Collapse sidebar')}
testid="sliderBar-arrowButton-collapse" testid="sliderBar-arrowButton-collapse"
/> />
</StyledSidebarWrapper> </StyledSidebarSwitchWrapper>
<StyledSliderBarWrapper data-testid="sliderBar"> <StyledSlidebarWrapper data-testid="sliderBar">
<WorkspaceSelector <WorkspaceSelector
currentWorkspace={currentWorkspace} currentWorkspace={currentWorkspace}
onClick={onOpenWorkspaceListModal} onClick={onOpenWorkspaceListModal}
@@ -147,7 +89,6 @@ export const WorkSpaceSliderBar: React.FC<WorkSpaceSliderBarProps> = ({
<StyledListItem <StyledListItem
data-testid="slider-bar-quick-search-button" data-testid="slider-bar-quick-search-button"
style={{ cursor: 'pointer' }}
onClick={useCallback(() => { onClick={useCallback(() => {
onOpenQuickSearchModal(); onOpenQuickSearchModal();
}, [onOpenQuickSearchModal])} }, [onOpenQuickSearchModal])}
@@ -155,70 +96,16 @@ export const WorkSpaceSliderBar: React.FC<WorkSpaceSliderBarProps> = ({
<SearchIcon /> <SearchIcon />
{t('Quick search')} {t('Quick search')}
</StyledListItem> </StyledListItem>
<Link
href={{
pathname: currentWorkspaceId && paths.all(currentWorkspaceId),
}}
>
<StyledListItem
active={
currentPath ===
(currentWorkspaceId && paths.all(currentWorkspaceId))
}
>
<FolderIcon />
<span data-testid="all-pages">{t('All pages')}</span>
</StyledListItem>
</Link>
{config.enableSubpage && !!currentWorkspace && (
<Pivot
currentWorkspace={currentWorkspace}
openPage={openPage}
allMetas={pageMeta}
/>
)}
<StyledListItem
active={
currentPath ===
(currentWorkspaceId && paths.favorite(currentWorkspaceId))
}
>
<StyledLink
href={{
pathname:
currentWorkspaceId && paths.favorite(currentWorkspaceId),
}}
>
<FavoriteIcon />
{t('Favorites')}
</StyledLink>
<IconButton
darker={true}
onClick={useCallback(() => {
setOpenSubFavorite(!showSubFavorite);
}, [showSubFavorite])}
>
<ArrowDownSmallIcon
style={{
transform: `rotate(${showSubFavorite ? '180' : '0'}deg)`,
}}
/>
</IconButton>
</StyledListItem>
<FavoriteList
currentPageId={currentPageId}
showList={showSubFavorite}
openPage={openPage}
pageMeta={pageMeta}
/>
<StyledListItem <StyledListItem
active={ active={
currentPath === currentPath ===
(currentWorkspaceId && paths.setting(currentWorkspaceId)) (currentWorkspaceId && paths.setting(currentWorkspaceId))
} }
data-testid="slider-bar-workspace-setting-button" data-testid="slider-bar-workspace-setting-button"
style={{
marginBottom: '16px',
}}
> >
<StyledLink <StyledLink
href={{ href={{
@@ -231,42 +118,62 @@ export const WorkSpaceSliderBar: React.FC<WorkSpaceSliderBarProps> = ({
</StyledLink> </StyledLink>
</StyledListItem> </StyledListItem>
{/* <WorkspaceSetting <StyledListItem
isShow={showWorkspaceSetting} active={
onClose={() => { currentPath ===
setOpenWorkspaceSetting(false); (currentWorkspaceId && paths.all(currentWorkspaceId))
}} }
/> */}
{/* TODO: will finish the feature next version */}
{/* <StyledListItem
onClick={() => {
triggerImportModal();
}}
> >
<ImportIcon /> {t('Import')} <StyledLink
</StyledListItem> */} href={{
pathname: currentWorkspaceId && paths.all(currentWorkspaceId),
}}
>
<FolderIcon />
<span data-testid="all-pages">{t('All pages')}</span>
</StyledLink>
</StyledListItem>
<Link <Favorite
href={{ currentPath={currentPath}
pathname: currentWorkspaceId && paths.trash(currentWorkspaceId), paths={paths}
currentPageId={currentPageId}
openPage={openPage}
currentWorkspace={currentWorkspace}
/>
{config.enableSubpage && !!currentWorkspace && (
<Pivot
currentWorkspace={currentWorkspace}
openPage={openPage}
allMetas={pageMeta}
/>
)}
<StyledListItem
active={
currentPath ===
(currentWorkspaceId && paths.trash(currentWorkspaceId))
}
style={{
marginTop: '16px',
}} }}
> >
<StyledListItem <StyledLink
active={ href={{
currentPath === pathname: currentWorkspaceId && paths.trash(currentWorkspaceId),
(currentWorkspaceId && paths.trash(currentWorkspaceId)) }}
}
> >
<DeleteTemporarilyIcon /> {t('Trash')} <DeleteTemporarilyIcon /> {t('Trash')}
</StyledListItem> </StyledLink>
</Link> </StyledListItem>
<StyledNewPageButton </StyledSlidebarWrapper>
data-testid="new-page-button"
onClick={onClickNewPage} <StyledNewPageButton
> data-testid="new-page-button"
<PlusIcon /> {t('New Page')} onClick={onClickNewPage}
</StyledNewPageButton> >
</StyledSliderBarWrapper> <PlusIcon /> {t('New Page')}
</StyledNewPageButton>
</StyledSliderBar> </StyledSliderBar>
</> </>
); );
@@ -1,5 +1,6 @@
import { IconButton, MuiCollapse, TreeView } from '@affine/component'; import { MuiCollapse, TreeView } from '@affine/component';
import { DebugLogger } from '@affine/debug'; import { DebugLogger } from '@affine/debug';
import { useTranslation } from '@affine/i18n';
import { ArrowDownSmallIcon, FolderIcon } from '@blocksuite/icons'; import { ArrowDownSmallIcon, FolderIcon } from '@blocksuite/icons';
import type { PageMeta } from '@blocksuite/store'; import type { PageMeta } from '@blocksuite/store';
import { nanoid } from '@blocksuite/store'; import { nanoid } from '@blocksuite/store';
@@ -8,7 +9,7 @@ import { useCallback, useMemo, useState } from 'react';
import { useBlockSuiteWorkspaceHelper } from '../../../../hooks/use-blocksuite-workspace-helper'; import { useBlockSuiteWorkspaceHelper } from '../../../../hooks/use-blocksuite-workspace-helper';
import { usePageMetaHelper } from '../../../../hooks/use-page-meta'; import { usePageMetaHelper } from '../../../../hooks/use-page-meta';
import type { RemWorkspace } from '../../../../shared'; import type { RemWorkspace } from '../../../../shared';
import { StyledListItem } from '../style'; import { StyledCollapseButton, StyledListItem } from '../shared-styles';
import type { TreeNode } from './types'; import type { TreeNode } from './types';
import { flattenToTree } from './utils'; import { flattenToTree } from './utils';
const logger = new DebugLogger('pivot'); const logger = new DebugLogger('pivot');
@@ -178,6 +179,7 @@ export const PivotInternal = ({
onAdd={handleAdd} onAdd={handleAdd}
onDelete={handleDelete} onDelete={handleDelete}
onDrop={handleDrop} onDrop={handleDrop}
indent={16}
/> />
); );
}; };
@@ -191,32 +193,30 @@ export const Pivot = ({
openPage: (pageId: string) => void; openPage: (pageId: string) => void;
allMetas: PageMeta[]; allMetas: PageMeta[];
}) => { }) => {
const { t } = useTranslation();
const [showPivot, setShowPivot] = useState(true); const [showPivot, setShowPivot] = useState(true);
return ( return (
<> <>
<StyledListItem> <StyledListItem>
<FolderIcon /> <StyledCollapseButton
Pivot
<IconButton
darker={true}
onClick={useCallback(() => { onClick={useCallback(() => {
setShowPivot(!showPivot); setShowPivot(!showPivot);
}, [showPivot])} }, [showPivot])}
collapse={showPivot}
> >
<ArrowDownSmallIcon <ArrowDownSmallIcon />
style={{ </StyledCollapseButton>
transform: `rotate(${showPivot ? '180' : '0'}deg)`, <FolderIcon />
}} {t('Pivots')}
/>
</IconButton>
</StyledListItem> </StyledListItem>
<MuiCollapse <MuiCollapse
in={showPivot} in={showPivot}
style={{ style={{
maxHeight: 300, maxHeight: 300,
paddingLeft: '12px', paddingLeft: '16px',
overflowY: 'auto', overflowY: 'auto',
}} }}
> >
@@ -1,12 +1,18 @@
import { IconButton } from '@affine/component'; import { IconButton } from '@affine/component';
import { import {
ArrowDownSmallIcon, ArrowDownSmallIcon,
DeleteTemporarilyIcon, EdgelessIcon,
PlusIcon, // DeleteTemporarilyIcon,
// PlusIcon,
MoreVerticalIcon,
PageIcon,
} from '@blocksuite/icons'; } from '@blocksuite/icons';
import type { PageMeta } from '@blocksuite/store';
import { useAtomValue } from 'jotai';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { StyledCollapsedButton, StyledPivotItem } from './styles'; import { workspacePreferredModeAtom } from '../../../../atoms';
import { StyledCollapseButton, StyledCollapseItem } from '../shared-styles';
import type { TreeNode } from './types'; import type { TreeNode } from './types';
export const TreeNodeRender: TreeNode['render'] = ( export const TreeNodeRender: TreeNode['render'] = (
@@ -14,12 +20,17 @@ export const TreeNodeRender: TreeNode['render'] = (
{ onAdd, onDelete, collapsed, setCollapsed }, { onAdd, onDelete, collapsed, setCollapsed },
extendProps extendProps
) => { ) => {
const { openPage } = extendProps as { openPage: (pageId: string) => void }; const { openPage, pageMeta } = extendProps as {
openPage: (pageId: string) => void;
pageMeta: PageMeta;
};
const record = useAtomValue(workspacePreferredModeAtom);
const router = useRouter(); const router = useRouter();
const active = router.query.pageId === node.id; const active = router.query.pageId === node.id;
return ( return (
<StyledPivotItem <StyledCollapseItem
onClick={() => { onClick={() => {
if (active) { if (active) {
return; return;
@@ -28,42 +39,49 @@ export const TreeNodeRender: TreeNode['render'] = (
}} }}
active={active} active={active}
> >
<StyledCollapsedButton <StyledCollapseButton
collapse={collapsed}
show={!!node.children?.length} show={!!node.children?.length}
onClick={e => { onClick={e => {
e.stopPropagation(); e.stopPropagation();
setCollapsed(!collapsed); setCollapsed(!collapsed);
}} }}
size="small"
> >
<ArrowDownSmallIcon <ArrowDownSmallIcon />
style={{ </StyledCollapseButton>
transform: `rotate(${collapsed ? '0' : '180'}deg)`, {record[pageMeta.id] === 'edgeless' ? <EdgelessIcon /> : <PageIcon />}
}}
/>
</StyledCollapsedButton>
<span>{node.title || 'Untitled'}</span> <span>{node.title || 'Untitled'}</span>
<IconButton <IconButton
onClick={e => {
e.stopPropagation();
onAdd();
}}
size="small" size="small"
className="pivot-item-button" className="operation-button"
>
<PlusIcon />
</IconButton>
<IconButton
onClick={e => { onClick={e => {
e.stopPropagation(); e.stopPropagation();
}}
>
<MoreVerticalIcon />
</IconButton>
onDelete(); {/*<IconButton*/}
}} {/* onClick={e => {*/}
size="small" {/* e.stopPropagation();*/}
className="pivot-item-button" {/* onAdd();*/}
> {/* }}*/}
<DeleteTemporarilyIcon /> {/* size="small"*/}
</IconButton> {/* className="operation-button"*/}
</StyledPivotItem> {/*>*/}
{/* <PlusIcon />*/}
{/*</IconButton>*/}
{/*<IconButton*/}
{/* onClick={e => {*/}
{/* e.stopPropagation();*/}
{/* onDelete();*/}
{/* }}*/}
{/* size="small"*/}
{/* className="operation-button"*/}
{/*>*/}
{/* <DeleteTemporarilyIcon />*/}
{/*</IconButton>*/}
</StyledCollapseItem>
); );
}; };
@@ -1,35 +1,17 @@
import { import { IconButton, styled } from '@affine/component';
displayFlex,
IconButton,
styled,
textEllipsis,
} from '@affine/component';
export const StyledPivotItem = styled('div')<{ active: boolean }>( export const StyledOperationButton = styled('button')(({ theme }) => {
({ active, theme }) => { return {
return { height: '20px',
width: '100%', width: '20px',
height: '32px', fontSize: '20px',
...displayFlex('flex-start', 'center'), color: theme.colors.iconColor,
paddingLeft: '24px', display: 'none',
position: 'relative', ':hover': {
color: active ? theme.colors.primaryColor : theme.colors.textColor, background: theme.colors.hoverBackground,
cursor: 'pointer', },
span: { };
flexGrow: '1', });
...textEllipsis(1),
},
'.pivot-item-button': {
display: 'none',
},
':hover': {
'.pivot-item-button': {
display: 'flex',
},
},
};
}
);
export const StyledCollapsedButton = styled(IconButton, { export const StyledCollapsedButton = styled(IconButton, {
shouldForwardProp: prop => { shouldForwardProp: prop => {
@@ -32,7 +32,11 @@ export const flattenToTree = (
const returnedMeta: TreeNode = { const returnedMeta: TreeNode = {
...internalMeta, ...internalMeta,
children: helper(childrenMetas), children: helper(childrenMetas),
render: (node, props) => TreeNodeRender!(node, props, renderProps), render: (node, props) =>
TreeNodeRender!(node, props, {
pageMeta: internalMeta,
...renderProps,
}),
}; };
// @ts-ignore // @ts-ignore
returnedMetas.push(returnedMeta); returnedMetas.push(returnedMeta);
@@ -0,0 +1,98 @@
import { displayFlex, styled, textEllipsis } from '@affine/component';
export const StyledListItem = styled('div')<{
active?: boolean;
disabled?: boolean;
}>(({ theme, active, disabled }) => {
return {
height: '32px',
color: active ? theme.colors.primaryColor : theme.colors.textColor,
padding: '0 16px',
borderRadius: '8px',
cursor: 'pointer',
marginBottom: '4px',
position: 'relative',
...displayFlex('flex-start', 'center'),
...(disabled
? {
cursor: 'not-allowed',
color: theme.colors.borderColor,
}
: {}),
'> svg, a > svg': {
fontSize: '20px',
marginRight: '12px',
color: active ? theme.colors.primaryColor : theme.colors.iconColor,
},
':hover:not([disabled])': {
backgroundColor: theme.colors.hoverBackground,
},
};
});
export const StyledCollapseButton = styled('button')<{
collapse: boolean;
show?: boolean;
}>(({ collapse, show = true, theme }) => {
return {
width: '16px',
height: '16px',
fontSize: '16px',
position: 'absolute',
left: '0',
top: '0',
bottom: '0',
margin: 'auto',
color: theme.colors.iconColor,
opacity: '.6',
display: show ? 'block' : 'none',
svg: {
transform: `rotate(${collapse ? '0' : '-90'}deg)`,
},
};
});
export const StyledCollapseItem = styled('button')<{
disable?: boolean;
active?: boolean;
}>(({ disable = false, active = false, theme }) => {
return {
width: '100%',
height: '32px',
borderRadius: '8px',
...displayFlex('flex-start', 'center'),
padding: '0 2px 0 16px',
position: 'relative',
color: disable
? theme.colors.disableColor
: active
? theme.colors.primaryColor
: theme.colors.textColor,
cursor: disable ? 'not-allowed' : 'pointer',
span: {
flexGrow: '1',
textAlign: 'left',
...textEllipsis(1),
},
'> svg': {
fontSize: '20px',
marginRight: '8px',
flexShrink: '0',
color: active ? theme.colors.primaryColor : theme.colors.iconColor,
},
'.operation-button': {
display: 'none',
},
':hover': disable
? {}
: {
backgroundColor: theme.colors.hoverBackground,
'.operation-button': {
display: 'flex',
},
},
};
});
@@ -1,6 +1,5 @@
import { displayFlex, styled, textEllipsis } from '@affine/component'; import { displayFlex, styled } from '@affine/component';
import Link from 'next/link'; import Link from 'next/link';
export const StyledSliderBar = styled('div')<{ show: boolean }>( export const StyledSliderBar = styled('div')<{ show: boolean }>(
({ theme, show }) => { ({ theme, show }) => {
return { return {
@@ -13,60 +12,30 @@ export const StyledSliderBar = styled('div')<{ show: boolean }>(
transition: 'width .15s, padding .15s', transition: 'width .15s, padding .15s',
position: 'relative', position: 'relative',
zIndex: theme.zIndex.modal, zIndex: theme.zIndex.modal,
padding: show ? '0 12px' : '0', padding: show ? '0 4px' : '0',
flexShrink: 0, flexShrink: 0,
display: 'flex',
flexDirection: 'column',
}; };
} }
); );
export const StyledSidebarWrapper = styled('div')(() => { export const StyledSidebarSwitchWrapper = styled('div')(() => {
return { return {
position: 'absolute', height: '48px',
right: '12px', padding: '0 16px',
top: '16px', ...displayFlex('flex-start', 'center'),
zIndex: 1,
}; };
}); });
export const StyledSliderBarWrapper = styled('div')(() => { export const StyledSlidebarWrapper = styled('div')(() => {
return { return {
height: '100%', flexGrow: 1,
overflowX: 'hidden', overflowX: 'hidden',
overflowY: 'auto', overflowY: 'auto',
position: 'relative', position: 'relative',
}; };
}); });
export const StyledListItem = styled('div')<{ export const StyledLink = styled(Link)(() => {
active?: boolean;
disabled?: boolean;
}>(({ theme, active, disabled }) => {
return {
height: '32px',
marginTop: '12px',
color: active ? theme.colors.primaryColor : theme.colors.textColor,
paddingLeft: '12px',
borderRadius: '5px',
cursor: 'pointer',
...displayFlex('flex-start', 'center'),
...(disabled
? {
cursor: 'not-allowed',
color: theme.colors.borderColor,
}
: {}),
'> svg': {
fontSize: '20px',
marginRight: '12px',
color: active ? theme.colors.primaryColor : theme.colors.iconColor,
},
':hover:not([disabled])': {
color: theme.colors.primaryColor,
backgroundColor: theme.colors.hoverBackground,
},
};
});
export const StyledLink = styled(Link)(({ theme }) => {
return { return {
flexGrow: 1, flexGrow: 1,
textAlign: 'left', textAlign: 'left',
@@ -75,51 +44,25 @@ export const StyledLink = styled(Link)(({ theme }) => {
':visited': { ':visited': {
color: 'inherit', color: 'inherit',
}, },
'>svg': { };
});
export const StyledNewPageButton = styled('button')(({ theme }) => {
return {
height: '48px',
...displayFlex('flex-start', 'center'),
borderTop: '1px solid',
borderColor: theme.colors.borderColor,
padding: '0 8px',
svg: {
fontSize: '20px', fontSize: '20px',
marginRight: '12px',
color: theme.colors.iconColor, color: theme.colors.iconColor,
marginRight: '8px',
}, },
};
});
export const StyledNewPageButton = styled(StyledListItem)(() => {
return {
position: 'absolute',
bottom: '24px',
left: '0',
right: '0',
margin: 'auto',
':hover': { ':hover': {
cursor: 'pointer', color: theme.colors.primaryColor,
svg: {
color: theme.colors.primaryColor,
},
}, },
}; };
}); });
export const StyledSubListItem = styled('button')<{
disable?: boolean;
active?: boolean;
}>(({ theme, disable, active }) => {
return {
width: '100%',
height: '32px',
marginTop: '4px',
color: disable
? theme.colors.disableColor
: active
? theme.colors.primaryColor
: theme.colors.textColor,
backgroundColor: active ? theme.colors.hoverBackground : 'unset',
cursor: disable ? 'not-allowed' : 'pointer',
paddingLeft: '45px',
lineHeight: '32px',
textAlign: 'start',
...textEllipsis(1),
':hover': disable
? {}
: {
color: theme.colors.primaryColor,
backgroundColor: theme.colors.hoverBackground,
},
};
});
+2 -2
View File
@@ -18,7 +18,7 @@ export const getLightTheme = (
colors: { colors: {
primaryColor: '#5438FF', primaryColor: '#5438FF',
pageBackground: '#fff', pageBackground: '#fff',
hoverBackground: '#F1F1F4', hoverBackground: 'rgba(0,0,0,.04)',
innerHoverBackground: '#E9E9EC', innerHoverBackground: '#E9E9EC',
popoverBackground: '#fff', popoverBackground: '#fff',
tooltipBackground: '#261499', tooltipBackground: '#261499',
@@ -97,7 +97,7 @@ export const getDarkTheme = (
colors: { colors: {
primaryColor: '#5438FF', primaryColor: '#5438FF',
pageBackground: '#2c2c2c', pageBackground: '#2c2c2c',
hoverBackground: '#3C3C42', hoverBackground: 'rgba(0,0,0,.04)',
innerHoverBackground: '#5A5A5A', innerHoverBackground: '#5A5A5A',
popoverBackground: '#1F2021', popoverBackground: '#1F2021',
tooltipBackground: '#0C0A15', tooltipBackground: '#0C0A15',
@@ -1,5 +1,5 @@
import type { CSSProperties, HTMLAttributes, ReactElement } from 'react'; import type { CSSProperties, HTMLAttributes, ReactElement } from 'react';
import { Children, cloneElement, forwardRef } from 'react'; import { forwardRef } from 'react';
import { StyledIconButton } from './styles'; import { StyledIconButton } from './styles';
const SIZE_SMALL = 'small' as const; const SIZE_SMALL = 'small' as const;
@@ -9,7 +9,7 @@ const SIZE_NORMAL = 'normal' as const;
const SIZE_CONFIG = { const SIZE_CONFIG = {
[SIZE_SMALL]: { [SIZE_SMALL]: {
iconSize: 16, iconSize: 16,
areaSize: 24, areaSize: 20,
}, },
[SIZE_MIDDLE]: { [SIZE_MIDDLE]: {
iconSize: 20, iconSize: 20,
@@ -42,19 +42,14 @@ export type IconButtonProps = {
export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>( export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
( (
{ { size = 'normal', iconSize, disabled = false, children, ...props },
size = 'normal',
iconSize = 'normal',
disabled = false,
children,
...props
},
ref ref
) => { ) => {
iconSize = size;
const [width, height] = Array.isArray(size) const [width, height] = Array.isArray(size)
? size ? size
: [SIZE_CONFIG[size]['areaSize'], SIZE_CONFIG[size]['areaSize']]; : [SIZE_CONFIG[size]['areaSize'], SIZE_CONFIG[size]['areaSize']];
const [iconWidth, iconHeight] = Array.isArray(iconSize) const [iconWidth] = Array.isArray(iconSize)
? iconSize ? iconSize
: [SIZE_CONFIG[iconSize]['iconSize'], SIZE_CONFIG[iconSize]['iconSize']]; : [SIZE_CONFIG[iconSize]['iconSize'], SIZE_CONFIG[iconSize]['iconSize']];
@@ -65,12 +60,10 @@ export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
width={width} width={width}
height={height} height={height}
borderRadius={iconWidth / 4} borderRadius={iconWidth / 4}
fontSize={iconWidth}
{...props} {...props}
> >
{cloneElement(Children.only(children), { {children}
width: iconWidth,
height: iconHeight,
})}
</StyledIconButton> </StyledIconButton>
); );
} }
+5 -8
View File
@@ -15,7 +15,7 @@ export const StyledIconButton = styled('button', {
'hoverBackground', 'hoverBackground',
'hoverColor', 'hoverColor',
'hoverStyle', 'hoverStyle',
'darker', 'fontSize',
].includes(prop as string); ].includes(prop as string);
}, },
})<{ })<{
@@ -27,7 +27,7 @@ export const StyledIconButton = styled('button', {
hoverColor?: string; hoverColor?: string;
hoverStyle?: CSSProperties; hoverStyle?: CSSProperties;
// In some cases, button is in a normal hover status, it should be darkened // In some cases, button is in a normal hover status, it should be darkened
darker?: boolean; fontSize?: CSSProperties['fontSize'];
}>( }>(
({ ({
theme, theme,
@@ -38,11 +38,12 @@ export const StyledIconButton = styled('button', {
hoverBackground, hoverBackground,
hoverColor, hoverColor,
hoverStyle, hoverStyle,
darker = false, fontSize,
}) => { }) => {
return { return {
width, width,
height, height,
fontSize,
color: theme.colors.iconColor, color: theme.colors.iconColor,
...displayInlineFlex('center', 'center'), ...displayInlineFlex('center', 'center'),
position: 'relative', position: 'relative',
@@ -67,11 +68,7 @@ export const StyledIconButton = styled('button', {
':hover': { ':hover': {
color: hoverColor ?? theme.colors.primaryColor, color: hoverColor ?? theme.colors.primaryColor,
'::after': { '::after': {
background: background: hoverBackground || theme.colors.hoverBackground,
hoverBackground ||
(darker
? theme.colors.innerHoverBackground
: theme.colors.hoverBackground),
}, },
...(hoverStyle ?? {}), ...(hoverStyle ?? {}),
}, },
@@ -46,7 +46,7 @@ export const TreeNode = <N,>({
allDrop = true, allDrop = true,
...otherProps ...otherProps
}: TreeNodeProps<N>) => { }: TreeNodeProps<N>) => {
const { onAdd, onDelete, onDrop } = otherProps; const { onAdd, onDelete, onDrop, indent } = otherProps;
const [collapsed, setCollapsed] = useState(false); const [collapsed, setCollapsed] = useState(false);
const [{ isDragging }, drag] = useDrag(() => ({ const [{ isDragging }, drag] = useDrag(() => ({
@@ -109,7 +109,7 @@ export const TreeNode = <N,>({
)} )}
</StyledTreeNodeItem> </StyledTreeNodeItem>
<StyledCollapse in={!collapsed}> <StyledCollapse in={!collapsed} indent={indent}>
{node.children && {node.children &&
node.children.map((childNode, index) => ( node.children.map((childNode, index) => (
<TreeNode <TreeNode
@@ -1,10 +1,13 @@
import MuiCollapse from '@mui/material/Collapse'; import MuiCollapse from '@mui/material/Collapse';
import type { CSSProperties } from 'react';
import { styled } from '../../styles'; import { styled } from '../../styles';
export const StyledCollapse = styled(MuiCollapse)(() => { export const StyledCollapse = styled(MuiCollapse)<{
indent?: CSSProperties['paddingLeft'];
}>(({ indent = 12 }) => {
return { return {
paddingLeft: '12px', paddingLeft: indent,
}; };
}); });
export const StyledTreeNodeItem = styled('div')<{ export const StyledTreeNodeItem = styled('div')<{
+2 -1
View File
@@ -1,4 +1,4 @@
import type { ReactNode } from 'react'; import type { CSSProperties, ReactNode } from 'react';
export type Node<N> = { export type Node<N> = {
id: string; id: string;
@@ -16,6 +16,7 @@ export type Node<N> = {
} & N; } & N;
type CommonProps<N> = { type CommonProps<N> = {
indent?: CSSProperties['paddingLeft'];
onAdd?: (node: Node<N>) => void; onAdd?: (node: Node<N>) => void;
onDelete?: (node: Node<N>) => void; onDelete?: (node: Node<N>) => void;
onDrop?: ( onDrop?: (
+2 -1
View File
@@ -193,5 +193,6 @@
"Help and Feedback": "Help and Feedback", "Help and Feedback": "Help and Feedback",
"Please make sure you are online": "Please make sure you are online", "Please make sure you are online": "Please make sure you are online",
"Workspace Owner": "Workspace Owner", "Workspace Owner": "Workspace Owner",
"Members": "Members" "Members": "Members",
"Pivots": "Pivots"
} }
+1 -1
View File
@@ -3,7 +3,7 @@ import type { Page } from '@playwright/test';
export async function newPage(page: Page) { export async function newPage(page: Page) {
await page.waitForSelector('v-line'); await page.waitForSelector('v-line');
// fixme(himself65): if too fast, the page will crash // fixme(himself65): if too fast, the page will crash
await page.getByTestId('sliderBar').getByText('New Page').click({ await page.getByTestId('new-page-button').click({
delay: 100, delay: 100,
}); });
await page.waitForSelector('v-line'); await page.waitForSelector('v-line');
+11 -1
View File
@@ -227,7 +227,7 @@ __metadata:
"@affine/workspace": "workspace:*" "@affine/workspace": "workspace:*"
"@blocksuite/blocks": 0.5.0-20230324040005-14417c2 "@blocksuite/blocks": 0.5.0-20230324040005-14417c2
"@blocksuite/editor": 0.5.0-20230324040005-14417c2 "@blocksuite/editor": 0.5.0-20230324040005-14417c2
"@blocksuite/icons": 2.0.23 "@blocksuite/icons": latest
"@blocksuite/store": 0.5.0-20230324040005-14417c2 "@blocksuite/store": 0.5.0-20230324040005-14417c2
"@emotion/cache": ^11.10.5 "@emotion/cache": ^11.10.5
"@emotion/react": ^11.10.6 "@emotion/react": ^11.10.6
@@ -1855,6 +1855,16 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@blocksuite/icons@npm:latest":
version: 2.0.26
resolution: "@blocksuite/icons@npm:2.0.26"
peerDependencies:
"@types/react": ^18.0.25
react: ^18.2.0
checksum: bb0142cd503098ca1bd768401d7e4cc761027d7f60ac974f21bb368d3415e7ae2f13a128c83d4389d00aa18c523cd14bee53101e078c2a941186b98d6be7ba07
languageName: node
linkType: hard
"@blocksuite/phasor@npm:0.5.0-20230324040005-14417c2": "@blocksuite/phasor@npm:0.5.0-20230324040005-14417c2":
version: 0.5.0-20230324040005-14417c2 version: 0.5.0-20230324040005-14417c2
resolution: "@blocksuite/phasor@npm:0.5.0-20230324040005-14417c2" resolution: "@blocksuite/phasor@npm:0.5.0-20230324040005-14417c2"