mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 12:28:42 +00:00
feat: modify sidebar style (#1703)
This commit is contained in:
@@ -79,7 +79,7 @@ export const OperationCell: React.FC<OperationCellProps> = ({
|
||||
disablePortal={true}
|
||||
trigger="click"
|
||||
>
|
||||
<IconButton darker={true}>
|
||||
<IconButton>
|
||||
<MoreVerticalIcon />
|
||||
</IconButton>
|
||||
</Menu>
|
||||
@@ -127,7 +127,6 @@ export const TrashOperationCell: React.FC<TrashOperationCellProps> = ({
|
||||
<FlexWrapper>
|
||||
<Tooltip content={t('Restore it')} placement="top-start">
|
||||
<IconButton
|
||||
darker={true}
|
||||
style={{ marginRight: '12px' }}
|
||||
onClick={() => {
|
||||
onRestorePage(id);
|
||||
@@ -139,7 +138,6 @@ export const TrashOperationCell: React.FC<TrashOperationCellProps> = ({
|
||||
</Tooltip>
|
||||
<Tooltip content={t('Delete permanently')} placement="top-start">
|
||||
<IconButton
|
||||
darker={true}
|
||||
onClick={() => {
|
||||
setOpen(true);
|
||||
}}
|
||||
|
||||
@@ -56,7 +56,6 @@ const FavoriteTag: React.FC<FavoriteTagProps> = ({
|
||||
placement="top-start"
|
||||
>
|
||||
<IconButton
|
||||
darker={true}
|
||||
iconSize={[20, 20]}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
@@ -88,13 +87,12 @@ type PageListProps = {
|
||||
};
|
||||
|
||||
const filter = {
|
||||
all: (pageMeta: PageMeta, allMetas: PageMeta[]) => !pageMeta.trash,
|
||||
all: (pageMeta: PageMeta) => !pageMeta.trash,
|
||||
trash: (pageMeta: PageMeta, allMetas: PageMeta[]) => {
|
||||
const parentMeta = allMetas.find(m => m.subpageIds?.includes(pageMeta.id));
|
||||
return !parentMeta?.trash && pageMeta.trash;
|
||||
},
|
||||
favorite: (pageMeta: PageMeta, allMetas: PageMeta[]) =>
|
||||
pageMeta.favorite && !pageMeta.trash,
|
||||
favorite: (pageMeta: PageMeta) => pageMeta.favorite && !pageMeta.trash,
|
||||
};
|
||||
|
||||
export const PageList: React.FC<PageListProps> = ({
|
||||
|
||||
@@ -8,16 +8,16 @@ import {
|
||||
export const StyledHeaderContainer = styled('div')<{ hasWarning: boolean }>(
|
||||
({ hasWarning }) => {
|
||||
return {
|
||||
height: hasWarning ? '96px' : '60px',
|
||||
height: hasWarning ? '96px' : '48px',
|
||||
};
|
||||
}
|
||||
);
|
||||
export const StyledHeader = styled('div')<{ hasWarning: boolean }>(
|
||||
({ theme }) => {
|
||||
return {
|
||||
height: '64px',
|
||||
height: '48px',
|
||||
width: '100%',
|
||||
padding: '0 28px',
|
||||
padding: '0 20px',
|
||||
...displayFlex('space-between', 'center'),
|
||||
background: theme.colors.pageBackground,
|
||||
transition: 'background-color 0.5s',
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
import { CloudWorkspaceIcon, LocalWorkspaceIcon } from '@blocksuite/icons';
|
||||
import type React from 'react';
|
||||
|
||||
import { useCurrentWorkspace } from '../../../../hooks/current/use-current-workspace';
|
||||
import { useBlockSuiteWorkspaceName } from '../../../../hooks/use-blocksuite-workspace-name';
|
||||
import type { RemWorkspace } from '../../../../shared';
|
||||
import { WorkspaceAvatar } from '../../workspace-avatar';
|
||||
import { SelectorWrapper, WorkspaceName } from './styles';
|
||||
import {
|
||||
StyledSelectorContainer,
|
||||
StyledSelectorWrapper,
|
||||
StyledWorkspaceName,
|
||||
StyledWorkspaceStatus,
|
||||
} from './styles';
|
||||
|
||||
export type WorkspaceSelectorProps = {
|
||||
currentWorkspace: RemWorkspace | null;
|
||||
@@ -17,19 +24,32 @@ export const WorkspaceSelector: React.FC<WorkspaceSelectorProps> = ({
|
||||
const [name] = useBlockSuiteWorkspaceName(
|
||||
currentWorkspace?.blockSuiteWorkspace ?? null
|
||||
);
|
||||
const [workspace] = useCurrentWorkspace();
|
||||
return (
|
||||
<>
|
||||
<SelectorWrapper onClick={onClick} data-testid="current-workspace">
|
||||
<WorkspaceAvatar
|
||||
data-testid="workspace-avatar"
|
||||
style={{
|
||||
flexShrink: 0,
|
||||
}}
|
||||
size={32}
|
||||
workspace={currentWorkspace}
|
||||
/>
|
||||
<WorkspaceName data-testid="workspace-name">{name}</WorkspaceName>
|
||||
</SelectorWrapper>
|
||||
</>
|
||||
<StyledSelectorContainer onClick={onClick} data-testid="current-workspace">
|
||||
<WorkspaceAvatar
|
||||
data-testid="workspace-avatar"
|
||||
style={{
|
||||
flexShrink: 0,
|
||||
}}
|
||||
size={40}
|
||||
workspace={currentWorkspace}
|
||||
/>
|
||||
<StyledSelectorWrapper>
|
||||
<StyledWorkspaceName data-testid="workspace-name">
|
||||
{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';
|
||||
export const SelectorWrapper = styled('div')(({ theme }) => {
|
||||
export const StyledSelectorContainer = styled('div')(({ theme }) => {
|
||||
return {
|
||||
marginTop: '4px',
|
||||
height: '56px',
|
||||
height: '58px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: '0 44px 0 12px',
|
||||
borderRadius: '5px',
|
||||
padding: '0 6px',
|
||||
marginBottom: '16px',
|
||||
borderRadius: '8px',
|
||||
color: theme.colors.textColor,
|
||||
position: 'relative',
|
||||
':hover': {
|
||||
@@ -17,17 +18,31 @@ export const SelectorWrapper = styled('div')(({ theme }) => {
|
||||
};
|
||||
});
|
||||
|
||||
export const Avatar = styled(MuiAvatar)({
|
||||
height: '28px',
|
||||
width: '28px',
|
||||
});
|
||||
|
||||
export const WorkspaceName = styled('span')(({ theme }) => {
|
||||
export const StyledSelectorWrapper = styled('div')(() => {
|
||||
return {
|
||||
marginLeft: '12px',
|
||||
fontSize: theme.font.h6,
|
||||
fontWeight: 500,
|
||||
marginLeft: '8px',
|
||||
flexGrow: 1,
|
||||
overflow: 'hidden',
|
||||
};
|
||||
});
|
||||
export const StyledWorkspaceName = styled('div')(() => {
|
||||
return {
|
||||
lineHeight: '24px',
|
||||
fontWeight: 600,
|
||||
...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 { useTranslation } from '@affine/i18n';
|
||||
import {
|
||||
ArrowDownSmallIcon,
|
||||
DeleteTemporarilyIcon,
|
||||
FavoriteIcon,
|
||||
FolderIcon,
|
||||
PlusIcon,
|
||||
SearchIcon,
|
||||
SettingsIcon,
|
||||
} from '@blocksuite/icons';
|
||||
import type { Page, PageMeta } from '@blocksuite/store';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import type React from 'react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { useSidebarStatus } from '../../../hooks/affine/use-sidebar-status';
|
||||
import { usePageMeta } from '../../../hooks/use-page-meta';
|
||||
import type { RemWorkspace } from '../../../shared';
|
||||
import { SidebarSwitch } from '../../affine/sidebar-switch';
|
||||
import Favorite from './favorite';
|
||||
import { Pivot } from './pivot';
|
||||
import { StyledListItem } from './shared-styles';
|
||||
import {
|
||||
StyledLink,
|
||||
StyledListItem,
|
||||
StyledNewPageButton,
|
||||
StyledSidebarWrapper,
|
||||
StyledSidebarSwitchWrapper,
|
||||
StyledSlidebarWrapper,
|
||||
StyledSliderBar,
|
||||
StyledSliderBarWrapper,
|
||||
StyledSubListItem,
|
||||
} from './style';
|
||||
import { WorkspaceSelector } from './WorkspaceSelector';
|
||||
|
||||
@@ -40,57 +34,6 @@ export type FavoriteListProps = {
|
||||
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 = {
|
||||
isPublicWorkspace: boolean;
|
||||
onOpenQuickSearchModal: () => void;
|
||||
@@ -120,7 +63,6 @@ export const WorkSpaceSliderBar: React.FC<WorkSpaceSliderBarProps> = ({
|
||||
onOpenWorkspaceListModal,
|
||||
}) => {
|
||||
const currentWorkspaceId = currentWorkspace?.id || null;
|
||||
const [showSubFavorite, setOpenSubFavorite] = useState(true);
|
||||
const { t } = useTranslation();
|
||||
const [sidebarOpen] = useSidebarStatus();
|
||||
const pageMeta = usePageMeta(currentWorkspace?.blockSuiteWorkspace ?? null);
|
||||
@@ -131,15 +73,15 @@ export const WorkSpaceSliderBar: React.FC<WorkSpaceSliderBarProps> = ({
|
||||
return (
|
||||
<>
|
||||
<StyledSliderBar show={isPublicWorkspace ? false : sidebarOpen}>
|
||||
<StyledSidebarWrapper>
|
||||
<StyledSidebarSwitchWrapper>
|
||||
<SidebarSwitch
|
||||
visible={sidebarOpen}
|
||||
tooltipContent={t('Collapse sidebar')}
|
||||
testid="sliderBar-arrowButton-collapse"
|
||||
/>
|
||||
</StyledSidebarWrapper>
|
||||
</StyledSidebarSwitchWrapper>
|
||||
|
||||
<StyledSliderBarWrapper data-testid="sliderBar">
|
||||
<StyledSlidebarWrapper data-testid="sliderBar">
|
||||
<WorkspaceSelector
|
||||
currentWorkspace={currentWorkspace}
|
||||
onClick={onOpenWorkspaceListModal}
|
||||
@@ -147,7 +89,6 @@ export const WorkSpaceSliderBar: React.FC<WorkSpaceSliderBarProps> = ({
|
||||
|
||||
<StyledListItem
|
||||
data-testid="slider-bar-quick-search-button"
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={useCallback(() => {
|
||||
onOpenQuickSearchModal();
|
||||
}, [onOpenQuickSearchModal])}
|
||||
@@ -155,70 +96,16 @@ export const WorkSpaceSliderBar: React.FC<WorkSpaceSliderBarProps> = ({
|
||||
<SearchIcon />
|
||||
{t('Quick search')}
|
||||
</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
|
||||
active={
|
||||
currentPath ===
|
||||
(currentWorkspaceId && paths.setting(currentWorkspaceId))
|
||||
}
|
||||
data-testid="slider-bar-workspace-setting-button"
|
||||
style={{
|
||||
marginBottom: '16px',
|
||||
}}
|
||||
>
|
||||
<StyledLink
|
||||
href={{
|
||||
@@ -231,42 +118,62 @@ export const WorkSpaceSliderBar: React.FC<WorkSpaceSliderBarProps> = ({
|
||||
</StyledLink>
|
||||
</StyledListItem>
|
||||
|
||||
{/* <WorkspaceSetting
|
||||
isShow={showWorkspaceSetting}
|
||||
onClose={() => {
|
||||
setOpenWorkspaceSetting(false);
|
||||
}}
|
||||
/> */}
|
||||
{/* TODO: will finish the feature next version */}
|
||||
{/* <StyledListItem
|
||||
onClick={() => {
|
||||
triggerImportModal();
|
||||
}}
|
||||
<StyledListItem
|
||||
active={
|
||||
currentPath ===
|
||||
(currentWorkspaceId && paths.all(currentWorkspaceId))
|
||||
}
|
||||
>
|
||||
<ImportIcon /> {t('Import')}
|
||||
</StyledListItem> */}
|
||||
<StyledLink
|
||||
href={{
|
||||
pathname: currentWorkspaceId && paths.all(currentWorkspaceId),
|
||||
}}
|
||||
>
|
||||
<FolderIcon />
|
||||
<span data-testid="all-pages">{t('All pages')}</span>
|
||||
</StyledLink>
|
||||
</StyledListItem>
|
||||
|
||||
<Link
|
||||
href={{
|
||||
pathname: currentWorkspaceId && paths.trash(currentWorkspaceId),
|
||||
<Favorite
|
||||
currentPath={currentPath}
|
||||
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
|
||||
active={
|
||||
currentPath ===
|
||||
(currentWorkspaceId && paths.trash(currentWorkspaceId))
|
||||
}
|
||||
<StyledLink
|
||||
href={{
|
||||
pathname: currentWorkspaceId && paths.trash(currentWorkspaceId),
|
||||
}}
|
||||
>
|
||||
<DeleteTemporarilyIcon /> {t('Trash')}
|
||||
</StyledListItem>
|
||||
</Link>
|
||||
<StyledNewPageButton
|
||||
data-testid="new-page-button"
|
||||
onClick={onClickNewPage}
|
||||
>
|
||||
<PlusIcon /> {t('New Page')}
|
||||
</StyledNewPageButton>
|
||||
</StyledSliderBarWrapper>
|
||||
</StyledLink>
|
||||
</StyledListItem>
|
||||
</StyledSlidebarWrapper>
|
||||
|
||||
<StyledNewPageButton
|
||||
data-testid="new-page-button"
|
||||
onClick={onClickNewPage}
|
||||
>
|
||||
<PlusIcon /> {t('New Page')}
|
||||
</StyledNewPageButton>
|
||||
</StyledSliderBar>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { IconButton, MuiCollapse, TreeView } from '@affine/component';
|
||||
import { MuiCollapse, TreeView } from '@affine/component';
|
||||
import { DebugLogger } from '@affine/debug';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { ArrowDownSmallIcon, FolderIcon } from '@blocksuite/icons';
|
||||
import type { PageMeta } 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 { usePageMetaHelper } from '../../../../hooks/use-page-meta';
|
||||
import type { RemWorkspace } from '../../../../shared';
|
||||
import { StyledListItem } from '../style';
|
||||
import { StyledCollapseButton, StyledListItem } from '../shared-styles';
|
||||
import type { TreeNode } from './types';
|
||||
import { flattenToTree } from './utils';
|
||||
const logger = new DebugLogger('pivot');
|
||||
@@ -178,6 +179,7 @@ export const PivotInternal = ({
|
||||
onAdd={handleAdd}
|
||||
onDelete={handleDelete}
|
||||
onDrop={handleDrop}
|
||||
indent={16}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -191,32 +193,30 @@ export const Pivot = ({
|
||||
openPage: (pageId: string) => void;
|
||||
allMetas: PageMeta[];
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [showPivot, setShowPivot] = useState(true);
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledListItem>
|
||||
<FolderIcon />
|
||||
Pivot
|
||||
<IconButton
|
||||
darker={true}
|
||||
<StyledCollapseButton
|
||||
onClick={useCallback(() => {
|
||||
setShowPivot(!showPivot);
|
||||
}, [showPivot])}
|
||||
collapse={showPivot}
|
||||
>
|
||||
<ArrowDownSmallIcon
|
||||
style={{
|
||||
transform: `rotate(${showPivot ? '180' : '0'}deg)`,
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
<ArrowDownSmallIcon />
|
||||
</StyledCollapseButton>
|
||||
<FolderIcon />
|
||||
{t('Pivots')}
|
||||
</StyledListItem>
|
||||
|
||||
<MuiCollapse
|
||||
in={showPivot}
|
||||
style={{
|
||||
maxHeight: 300,
|
||||
paddingLeft: '12px',
|
||||
paddingLeft: '16px',
|
||||
overflowY: 'auto',
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
import { IconButton } from '@affine/component';
|
||||
import {
|
||||
ArrowDownSmallIcon,
|
||||
DeleteTemporarilyIcon,
|
||||
PlusIcon,
|
||||
EdgelessIcon,
|
||||
// DeleteTemporarilyIcon,
|
||||
// PlusIcon,
|
||||
MoreVerticalIcon,
|
||||
PageIcon,
|
||||
} from '@blocksuite/icons';
|
||||
import type { PageMeta } from '@blocksuite/store';
|
||||
import { useAtomValue } from 'jotai';
|
||||
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';
|
||||
|
||||
export const TreeNodeRender: TreeNode['render'] = (
|
||||
@@ -14,12 +20,17 @@ export const TreeNodeRender: TreeNode['render'] = (
|
||||
{ onAdd, onDelete, collapsed, setCollapsed },
|
||||
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 active = router.query.pageId === node.id;
|
||||
|
||||
return (
|
||||
<StyledPivotItem
|
||||
<StyledCollapseItem
|
||||
onClick={() => {
|
||||
if (active) {
|
||||
return;
|
||||
@@ -28,42 +39,49 @@ export const TreeNodeRender: TreeNode['render'] = (
|
||||
}}
|
||||
active={active}
|
||||
>
|
||||
<StyledCollapsedButton
|
||||
<StyledCollapseButton
|
||||
collapse={collapsed}
|
||||
show={!!node.children?.length}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
setCollapsed(!collapsed);
|
||||
}}
|
||||
size="small"
|
||||
>
|
||||
<ArrowDownSmallIcon
|
||||
style={{
|
||||
transform: `rotate(${collapsed ? '0' : '180'}deg)`,
|
||||
}}
|
||||
/>
|
||||
</StyledCollapsedButton>
|
||||
<ArrowDownSmallIcon />
|
||||
</StyledCollapseButton>
|
||||
{record[pageMeta.id] === 'edgeless' ? <EdgelessIcon /> : <PageIcon />}
|
||||
<span>{node.title || 'Untitled'}</span>
|
||||
<IconButton
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
onAdd();
|
||||
}}
|
||||
size="small"
|
||||
className="pivot-item-button"
|
||||
>
|
||||
<PlusIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
className="operation-button"
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<MoreVerticalIcon />
|
||||
</IconButton>
|
||||
|
||||
onDelete();
|
||||
}}
|
||||
size="small"
|
||||
className="pivot-item-button"
|
||||
>
|
||||
<DeleteTemporarilyIcon />
|
||||
</IconButton>
|
||||
</StyledPivotItem>
|
||||
{/*<IconButton*/}
|
||||
{/* onClick={e => {*/}
|
||||
{/* e.stopPropagation();*/}
|
||||
{/* onAdd();*/}
|
||||
{/* }}*/}
|
||||
{/* size="small"*/}
|
||||
{/* className="operation-button"*/}
|
||||
{/*>*/}
|
||||
{/* <PlusIcon />*/}
|
||||
{/*</IconButton>*/}
|
||||
{/*<IconButton*/}
|
||||
{/* onClick={e => {*/}
|
||||
{/* e.stopPropagation();*/}
|
||||
|
||||
{/* onDelete();*/}
|
||||
{/* }}*/}
|
||||
{/* size="small"*/}
|
||||
{/* className="operation-button"*/}
|
||||
{/*>*/}
|
||||
{/* <DeleteTemporarilyIcon />*/}
|
||||
{/*</IconButton>*/}
|
||||
</StyledCollapseItem>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,35 +1,17 @@
|
||||
import {
|
||||
displayFlex,
|
||||
IconButton,
|
||||
styled,
|
||||
textEllipsis,
|
||||
} from '@affine/component';
|
||||
import { IconButton, styled } from '@affine/component';
|
||||
|
||||
export const StyledPivotItem = styled('div')<{ active: boolean }>(
|
||||
({ active, theme }) => {
|
||||
return {
|
||||
width: '100%',
|
||||
height: '32px',
|
||||
...displayFlex('flex-start', 'center'),
|
||||
paddingLeft: '24px',
|
||||
position: 'relative',
|
||||
color: active ? theme.colors.primaryColor : theme.colors.textColor,
|
||||
cursor: 'pointer',
|
||||
span: {
|
||||
flexGrow: '1',
|
||||
...textEllipsis(1),
|
||||
},
|
||||
'.pivot-item-button': {
|
||||
display: 'none',
|
||||
},
|
||||
':hover': {
|
||||
'.pivot-item-button': {
|
||||
display: 'flex',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
);
|
||||
export const StyledOperationButton = styled('button')(({ theme }) => {
|
||||
return {
|
||||
height: '20px',
|
||||
width: '20px',
|
||||
fontSize: '20px',
|
||||
color: theme.colors.iconColor,
|
||||
display: 'none',
|
||||
':hover': {
|
||||
background: theme.colors.hoverBackground,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledCollapsedButton = styled(IconButton, {
|
||||
shouldForwardProp: prop => {
|
||||
|
||||
@@ -32,7 +32,11 @@ export const flattenToTree = (
|
||||
const returnedMeta: TreeNode = {
|
||||
...internalMeta,
|
||||
children: helper(childrenMetas),
|
||||
render: (node, props) => TreeNodeRender!(node, props, renderProps),
|
||||
render: (node, props) =>
|
||||
TreeNodeRender!(node, props, {
|
||||
pageMeta: internalMeta,
|
||||
...renderProps,
|
||||
}),
|
||||
};
|
||||
// @ts-ignore
|
||||
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';
|
||||
|
||||
export const StyledSliderBar = styled('div')<{ show: boolean }>(
|
||||
({ theme, show }) => {
|
||||
return {
|
||||
@@ -13,60 +12,30 @@ export const StyledSliderBar = styled('div')<{ show: boolean }>(
|
||||
transition: 'width .15s, padding .15s',
|
||||
position: 'relative',
|
||||
zIndex: theme.zIndex.modal,
|
||||
padding: show ? '0 12px' : '0',
|
||||
padding: show ? '0 4px' : '0',
|
||||
flexShrink: 0,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
};
|
||||
}
|
||||
);
|
||||
export const StyledSidebarWrapper = styled('div')(() => {
|
||||
export const StyledSidebarSwitchWrapper = styled('div')(() => {
|
||||
return {
|
||||
position: 'absolute',
|
||||
right: '12px',
|
||||
top: '16px',
|
||||
zIndex: 1,
|
||||
height: '48px',
|
||||
padding: '0 16px',
|
||||
...displayFlex('flex-start', 'center'),
|
||||
};
|
||||
});
|
||||
export const StyledSliderBarWrapper = styled('div')(() => {
|
||||
export const StyledSlidebarWrapper = styled('div')(() => {
|
||||
return {
|
||||
height: '100%',
|
||||
flexGrow: 1,
|
||||
overflowX: 'hidden',
|
||||
overflowY: 'auto',
|
||||
position: 'relative',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledListItem = styled('div')<{
|
||||
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 }) => {
|
||||
export const StyledLink = styled(Link)(() => {
|
||||
return {
|
||||
flexGrow: 1,
|
||||
textAlign: 'left',
|
||||
@@ -75,51 +44,25 @@ export const StyledLink = styled(Link)(({ theme }) => {
|
||||
':visited': {
|
||||
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',
|
||||
marginRight: '12px',
|
||||
color: theme.colors.iconColor,
|
||||
marginRight: '8px',
|
||||
},
|
||||
};
|
||||
});
|
||||
export const StyledNewPageButton = styled(StyledListItem)(() => {
|
||||
return {
|
||||
position: 'absolute',
|
||||
bottom: '24px',
|
||||
left: '0',
|
||||
right: '0',
|
||||
margin: 'auto',
|
||||
':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,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user