feat(core): support sidebar collapse (#7630)

This commit is contained in:
EYHN
2024-07-29 09:57:33 +00:00
parent c5cf8480fc
commit 0472ffe569
17 changed files with 257 additions and 129 deletions
@@ -20,5 +20,18 @@ export const root = style({
}); });
export const label = style({ export const label = style({
color: cssVar('black30'), color: cssVar('black30'),
flex: '1', flexGrow: '0',
display: 'flex',
alignItems: 'center',
justifyContent: 'start',
cursor: 'pointer',
});
export const collapseButton = style({
selectors: {
[`${label} > &`]: {
color: cssVar('black30'),
transform: 'translateY(1px)',
},
},
}); });
@@ -1,3 +1,5 @@
import { IconButton } from '@affine/component';
import { ToggleCollapseIcon, ToggleExpandIcon } from '@blocksuite/icons/rc';
import clsx from 'clsx'; import clsx from 'clsx';
import { type ForwardedRef, forwardRef, type PropsWithChildren } from 'react'; import { type ForwardedRef, forwardRef, type PropsWithChildren } from 'react';
@@ -7,6 +9,8 @@ export type CategoryDividerProps = PropsWithChildren<
{ {
label: string; label: string;
className?: string; className?: string;
collapsed?: boolean;
setCollapsed?: (collapsed: boolean) => void;
} & { } & {
[key: `data-${string}`]: unknown; [key: `data-${string}`]: unknown;
} }
@@ -14,12 +18,35 @@ export type CategoryDividerProps = PropsWithChildren<
export const CategoryDivider = forwardRef( export const CategoryDivider = forwardRef(
( (
{ label, children, className, ...otherProps }: CategoryDividerProps, {
label,
children,
className,
collapsed,
setCollapsed,
...otherProps
}: CategoryDividerProps,
ref: ForwardedRef<HTMLDivElement> ref: ForwardedRef<HTMLDivElement>
) => { ) => {
return ( return (
<div className={clsx([styles.root, className])} ref={ref} {...otherProps}> <div className={clsx([styles.root, className])} ref={ref} {...otherProps}>
<div className={styles.label}>{label}</div> <div
className={styles.label}
onClick={() => setCollapsed?.(!collapsed)}
>
{label}
{collapsed !== undefined && (
<IconButton
withoutHoverStyle
className={styles.collapseButton}
size="small"
data-testid="category-divider-collapse-button"
>
{collapsed ? <ToggleCollapseIcon /> : <ToggleExpandIcon />}
</IconButton>
)}
</div>
<div style={{ flex: 1 }}></div>
{children} {children}
</div> </div>
); );
@@ -166,9 +166,11 @@ export const RootAppSidebar = memo(
{runtimeConfig.enableNewFavorite && <ExplorerFavorites />} {runtimeConfig.enableNewFavorite && <ExplorerFavorites />}
{runtimeConfig.enableOrganize && <ExplorerOrganize />} {runtimeConfig.enableOrganize && <ExplorerOrganize />}
{runtimeConfig.enableNewFavorite && <ExplorerMigrationFavorites />} {runtimeConfig.enableNewFavorite && <ExplorerMigrationFavorites />}
{runtimeConfig.enableOldFavorite && <ExplorerOldFavorites />} {runtimeConfig.enableOldFavorite && (
<ExplorerCollections /> <ExplorerOldFavorites defaultCollapsed />
<ExplorerTags /> )}
<ExplorerCollections defaultCollapsed />
<ExplorerTags defaultCollapsed />
<CategoryDivider label={t['com.affine.rootAppSidebar.others']()} /> <CategoryDivider label={t['com.affine.rootAppSidebar.others']()} />
{/* fixme: remove the following spacer */} {/* fixme: remove the following spacer */}
<div style={{ height: '4px' }} /> <div style={{ height: '4px' }} />
@@ -7,20 +7,26 @@ import { ExplorerTreeRoot } from '@affine/core/modules/explorer/views/tree';
import { WorkbenchService } from '@affine/core/modules/workbench'; import { WorkbenchService } from '@affine/core/modules/workbench';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import { PlusIcon } from '@blocksuite/icons/rc'; import { PlusIcon } from '@blocksuite/icons/rc';
import * as Collapsible from '@radix-ui/react-collapsible';
import { useLiveData, useServices } from '@toeverything/infra'; import { useLiveData, useServices } from '@toeverything/infra';
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
import { useCallback } from 'react'; import { useCallback, useState } from 'react';
import { ExplorerCollectionNode } from '../../nodes/collection'; import { ExplorerCollectionNode } from '../../nodes/collection';
import { RootEmpty } from './empty'; import { RootEmpty } from './empty';
import * as styles from './styles.css'; import * as styles from './styles.css';
export const ExplorerCollections = () => { export const ExplorerCollections = ({
defaultCollapsed = false,
}: {
defaultCollapsed?: boolean;
}) => {
const t = useI18n(); const t = useI18n();
const { collectionService, workbenchService } = useServices({ const { collectionService, workbenchService } = useServices({
CollectionService, CollectionService,
WorkbenchService, WorkbenchService,
}); });
const [collapsed, setCollapsed] = useState(defaultCollapsed);
const collections = useLiveData(collectionService.collections$); const collections = useLiveData(collectionService.collections$);
const { node, open: openCreateCollectionModel } = useEditCollectionName({ const { node, open: openCreateCollectionModel } = useEditCollectionName({
title: t['com.affine.editCollection.createCollection'](), title: t['com.affine.editCollection.createCollection'](),
@@ -33,6 +39,7 @@ export const ExplorerCollections = () => {
const id = nanoid(); const id = nanoid();
collectionService.addCollection(createEmptyCollection(id, { name })); collectionService.addCollection(createEmptyCollection(id, { name }));
workbenchService.workbench.openCollection(id); workbenchService.workbench.openCollection(id);
setCollapsed(false);
}) })
.catch(err => { .catch(err => {
console.error(err); console.error(err);
@@ -41,8 +48,16 @@ export const ExplorerCollections = () => {
return ( return (
<> <>
<div className={styles.container} data-testid="explorer-collections"> <Collapsible.Root
<CategoryDivider label={t['com.affine.rootAppSidebar.collections']()}> className={styles.container}
data-testid="explorer-collections"
open={!collapsed}
>
<CategoryDivider
label={t['com.affine.rootAppSidebar.collections']()}
setCollapsed={setCollapsed}
collapsed={collapsed}
>
<IconButton <IconButton
data-testid="explorer-bar-add-collection-button" data-testid="explorer-bar-add-collection-button"
onClick={handleCreateCollection} onClick={handleCreateCollection}
@@ -51,21 +66,23 @@ export const ExplorerCollections = () => {
<PlusIcon /> <PlusIcon />
</IconButton> </IconButton>
</CategoryDivider> </CategoryDivider>
<ExplorerTreeRoot <Collapsible.Content>
placeholder={<RootEmpty onClickCreate={handleCreateCollection} />} <ExplorerTreeRoot
> placeholder={<RootEmpty onClickCreate={handleCreateCollection} />}
{collections.map(collection => ( >
<ExplorerCollectionNode {collections.map(collection => (
key={collection.id} <ExplorerCollectionNode
collectionId={collection.id} key={collection.id}
reorderable={false} collectionId={collection.id}
location={{ reorderable={false}
at: 'explorer:collection:list', location={{
}} at: 'explorer:collection:list',
/> }}
))} />
</ExplorerTreeRoot> ))}
</div> </ExplorerTreeRoot>
</Collapsible.Content>
</Collapsible.Root>
{node} {node}
</> </>
); );
@@ -1,5 +1,5 @@
import { style } from '@vanilla-extract/css'; import { style } from '@vanilla-extract/css';
export const container = style({ export const container = style({
marginTop: '16px', marginTop: '8px',
}); });
@@ -19,8 +19,9 @@ import { WorkbenchService } from '@affine/core/modules/workbench';
import type { AffineDNDData } from '@affine/core/types/dnd'; import type { AffineDNDData } from '@affine/core/types/dnd';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import { PlusIcon } from '@blocksuite/icons/rc'; import { PlusIcon } from '@blocksuite/icons/rc';
import * as Collapsible from '@radix-ui/react-collapsible';
import { DocsService, useLiveData, useServices } from '@toeverything/infra'; import { DocsService, useLiveData, useServices } from '@toeverything/infra';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo, useState } from 'react';
import { ExplorerCollectionNode } from '../../nodes/collection'; import { ExplorerCollectionNode } from '../../nodes/collection';
import { ExplorerDocNode } from '../../nodes/doc'; import { ExplorerDocNode } from '../../nodes/doc';
@@ -29,12 +30,17 @@ import { ExplorerTagNode } from '../../nodes/tag';
import { RootEmpty } from './empty'; import { RootEmpty } from './empty';
import * as styles from './styles.css'; import * as styles from './styles.css';
export const ExplorerFavorites = () => { export const ExplorerFavorites = ({
defaultCollapsed = false,
}: {
defaultCollapsed?: boolean;
}) => {
const { favoriteService, docsService, workbenchService } = useServices({ const { favoriteService, docsService, workbenchService } = useServices({
FavoriteService, FavoriteService,
DocsService, DocsService,
WorkbenchService, WorkbenchService,
}); });
const [collapsed, setCollapsed] = useState(defaultCollapsed);
const favorites = useLiveData(favoriteService.favoriteList.sortedList$); const favorites = useLiveData(favoriteService.favoriteList.sortedList$);
@@ -51,6 +57,7 @@ export const ExplorerFavorites = () => {
data.source.data.entity.id, data.source.data.entity.id,
favoriteService.favoriteList.indexAt('before') favoriteService.favoriteList.indexAt('before')
); );
setCollapsed(false);
} }
}, },
[favoriteService] [favoriteService]
@@ -83,6 +90,7 @@ export const ExplorerFavorites = () => {
favoriteService.favoriteList.indexAt('before') favoriteService.favoriteList.indexAt('before')
); );
workbenchService.workbench.openDoc(newDoc.id); workbenchService.workbench.openDoc(newDoc.id);
setCollapsed(false);
}, [docsService, favoriteService, workbenchService]); }, [docsService, favoriteService, workbenchService]);
const handleOnChildrenDrop = useCallback( const handleOnChildrenDrop = useCallback(
@@ -179,12 +187,18 @@ export const ExplorerFavorites = () => {
); );
return ( return (
<div className={styles.container} data-testid="explorer-favorites"> <Collapsible.Root
className={styles.container}
data-testid="explorer-favorites"
open={!collapsed}
>
<CategoryDivider <CategoryDivider
className={styles.draggedOverHighlight} className={styles.draggedOverHighlight}
label={t['com.affine.rootAppSidebar.favorites']()} label={t['com.affine.rootAppSidebar.favorites']()}
ref={dropTargetRef} ref={dropTargetRef}
data-testid="explorer-favorite-category-divider" data-testid="explorer-favorite-category-divider"
setCollapsed={setCollapsed}
collapsed={collapsed}
> >
<IconButton <IconButton
data-testid="explorer-bar-add-favorite-button" data-testid="explorer-bar-add-favorite-button"
@@ -206,26 +220,28 @@ export const ExplorerFavorites = () => {
/> />
)} )}
</CategoryDivider> </CategoryDivider>
<ExplorerTreeRoot <Collapsible.Content>
placeholder={ <ExplorerTreeRoot
<RootEmpty placeholder={
onDrop={handleDrop} <RootEmpty
canDrop={handleCanDrop} onDrop={handleDrop}
dropEffect={handleDropEffect} canDrop={handleCanDrop}
/> dropEffect={handleDropEffect}
} />
> }
{favorites.map(favorite => ( >
<ExplorerFavoriteNode {favorites.map(favorite => (
key={favorite.id} <ExplorerFavoriteNode
favorite={favorite} key={favorite.id}
onDrop={handleOnChildrenDrop} favorite={favorite}
dropEffect={handleChildrenDropEffect} onDrop={handleOnChildrenDrop}
canDrop={handleChildrenCanDrop} dropEffect={handleChildrenDropEffect}
/> canDrop={handleChildrenCanDrop}
))} />
</ExplorerTreeRoot> ))}
</div> </ExplorerTreeRoot>
</Collapsible.Content>
</Collapsible.Root>
); );
}; };
@@ -2,7 +2,7 @@ import { cssVar } from '@toeverything/theme';
import { style } from '@vanilla-extract/css'; import { style } from '@vanilla-extract/css';
export const container = style({ export const container = style({
marginTop: '16px', marginTop: '8px',
}); });
export const draggedOverHighlight = style({ export const draggedOverHighlight = style({
@@ -4,14 +4,21 @@ import { ExplorerTreeRoot } from '@affine/core/modules/explorer/views/tree';
import { FavoriteItemsAdapter } from '@affine/core/modules/properties'; import { FavoriteItemsAdapter } from '@affine/core/modules/properties';
import { Trans, useI18n } from '@affine/i18n'; import { Trans, useI18n } from '@affine/i18n';
import { BroomIcon, HelpIcon } from '@blocksuite/icons/rc'; import { BroomIcon, HelpIcon } from '@blocksuite/icons/rc';
import * as Collapsible from '@radix-ui/react-collapsible';
import { DocsService, useLiveData, useServices } from '@toeverything/infra'; import { DocsService, useLiveData, useServices } from '@toeverything/infra';
import { useCallback } from 'react'; import { useCallback, useState } from 'react';
import { ExplorerCollectionNode } from '../../nodes/collection'; import { ExplorerCollectionNode } from '../../nodes/collection';
import { ExplorerDocNode } from '../../nodes/doc'; import { ExplorerDocNode } from '../../nodes/doc';
import * as styles from './styles.css'; import * as styles from './styles.css';
export const ExplorerMigrationFavorites = () => { export const ExplorerMigrationFavorites = ({
defaultCollapsed = false,
}: {
defaultCollapsed?: boolean;
}) => {
const [collapsed, setCollapsed] = useState(defaultCollapsed);
const t = useI18n(); const t = useI18n();
const { favoriteItemsAdapter, docsService } = useServices({ const { favoriteItemsAdapter, docsService } = useServices({
@@ -89,8 +96,12 @@ export const ExplorerMigrationFavorites = () => {
} }
return ( return (
<div className={styles.container}> <Collapsible.Root className={styles.container} open={!collapsed}>
<CategoryDivider label={t['com.affine.rootAppSidebar.migration-data']()}> <CategoryDivider
label={t['com.affine.rootAppSidebar.migration-data']()}
setCollapsed={setCollapsed}
collapsed={collapsed}
>
<IconButton <IconButton
data-testid="explorer-bar-favorite-migration-clear-button" data-testid="explorer-bar-favorite-migration-clear-button"
onClick={handleClickClear} onClick={handleClickClear}
@@ -106,15 +117,17 @@ export const ExplorerMigrationFavorites = () => {
<HelpIcon /> <HelpIcon />
</IconButton> </IconButton>
</CategoryDivider> </CategoryDivider>
<ExplorerTreeRoot> <Collapsible.Content>
{favorites.map((favorite, i) => ( <ExplorerTreeRoot>
<ExplorerMigrationFavoriteNode {favorites.map((favorite, i) => (
key={favorite.id + ':' + i} <ExplorerMigrationFavoriteNode
favorite={favorite} key={favorite.id + ':' + i}
/> favorite={favorite}
))} />
</ExplorerTreeRoot> ))}
</div> </ExplorerTreeRoot>
</Collapsible.Content>
</Collapsible.Root>
); );
}; };
@@ -2,7 +2,7 @@ import { cssVar } from '@toeverything/theme';
import { style } from '@vanilla-extract/css'; import { style } from '@vanilla-extract/css';
export const container = style({ export const container = style({
marginTop: '16px', marginTop: '8px',
position: 'relative', position: 'relative',
selectors: { selectors: {
'&:after': { '&:after': {
@@ -13,8 +13,9 @@ import { WorkbenchService } from '@affine/core/modules/workbench';
import type { AffineDNDData } from '@affine/core/types/dnd'; import type { AffineDNDData } from '@affine/core/types/dnd';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import { PlusIcon } from '@blocksuite/icons/rc'; import { PlusIcon } from '@blocksuite/icons/rc';
import * as Collapsible from '@radix-ui/react-collapsible';
import { DocsService, useLiveData, useServices } from '@toeverything/infra'; import { DocsService, useLiveData, useServices } from '@toeverything/infra';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo, useState } from 'react';
import { ExplorerCollectionNode } from '../../nodes/collection'; import { ExplorerCollectionNode } from '../../nodes/collection';
import { ExplorerDocNode } from '../../nodes/doc'; import { ExplorerDocNode } from '../../nodes/doc';
@@ -24,12 +25,17 @@ import * as styles from './styles.css';
/** /**
* @deprecated remove this after 0.17 released * @deprecated remove this after 0.17 released
*/ */
export const ExplorerOldFavorites = () => { export const ExplorerOldFavorites = ({
defaultCollapsed = false,
}: {
defaultCollapsed?: boolean;
}) => {
const { favoriteItemsAdapter, docsService, workbenchService } = useServices({ const { favoriteItemsAdapter, docsService, workbenchService } = useServices({
FavoriteItemsAdapter, FavoriteItemsAdapter,
DocsService, DocsService,
WorkbenchService, WorkbenchService,
}); });
const [collapsed, setCollapsed] = useState(defaultCollapsed);
const docs = useLiveData(docsService.list.docs$); const docs = useLiveData(docsService.list.docs$);
const trashDocs = useLiveData(docsService.list.trashDocs$); const trashDocs = useLiveData(docsService.list.trashDocs$);
@@ -174,7 +180,7 @@ export const ExplorerOldFavorites = () => {
); );
return ( return (
<div className={styles.container}> <Collapsible.Root className={styles.container} open={!collapsed}>
<CategoryDivider <CategoryDivider
className={styles.draggedOverHighlight} className={styles.draggedOverHighlight}
label={ label={
@@ -182,6 +188,8 @@ export const ExplorerOldFavorites = () => {
? `${t['com.affine.rootAppSidebar.favorites']()} (OLD)` ? `${t['com.affine.rootAppSidebar.favorites']()} (OLD)`
: t['com.affine.rootAppSidebar.favorites']() : t['com.affine.rootAppSidebar.favorites']()
} }
setCollapsed={setCollapsed}
collapsed={collapsed}
> >
<IconButton <IconButton
data-testid="explorer-bar-add-old-favorite-button" data-testid="explorer-bar-add-old-favorite-button"
@@ -191,26 +199,28 @@ export const ExplorerOldFavorites = () => {
<PlusIcon /> <PlusIcon />
</IconButton> </IconButton>
</CategoryDivider> </CategoryDivider>
<ExplorerTreeRoot <Collapsible.Content>
placeholder={ <ExplorerTreeRoot
<RootEmpty placeholder={
onDrop={handleDrop} <RootEmpty
canDrop={handleCanDrop} onDrop={handleDrop}
dropEffect={handleDropEffect} canDrop={handleCanDrop}
/> dropEffect={handleDropEffect}
} />
> }
{favorites.map((favorite, i) => ( >
<ExplorerFavoriteNode {favorites.map((favorite, i) => (
key={favorite.id + ':' + i} <ExplorerFavoriteNode
favorite={favorite} key={favorite.id + ':' + i}
onDrop={handleOnChildrenDrop} favorite={favorite}
dropEffect={handleChildrenDropEffect} onDrop={handleOnChildrenDrop}
canDrop={handleChildrenCanDrop} dropEffect={handleChildrenDropEffect}
/> canDrop={handleChildrenCanDrop}
))} />
</ExplorerTreeRoot> ))}
</div> </ExplorerTreeRoot>
</Collapsible.Content>
</Collapsible.Root>
); );
}; };
@@ -2,7 +2,7 @@ import { cssVar } from '@toeverything/theme';
import { style } from '@vanilla-extract/css'; import { style } from '@vanilla-extract/css';
export const container = style({ export const container = style({
marginTop: '16px', marginTop: '8px',
}); });
export const draggedOverHighlight = style({ export const draggedOverHighlight = style({
@@ -16,6 +16,7 @@ import {
import type { AffineDNDData } from '@affine/core/types/dnd'; import type { AffineDNDData } from '@affine/core/types/dnd';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import { PlusIcon } from '@blocksuite/icons/rc'; import { PlusIcon } from '@blocksuite/icons/rc';
import * as Collapsible from '@radix-ui/react-collapsible';
import { useLiveData, useServices } from '@toeverything/infra'; import { useLiveData, useServices } from '@toeverything/infra';
import { useCallback, useMemo, useState } from 'react'; import { useCallback, useMemo, useState } from 'react';
@@ -23,9 +24,14 @@ import { ExplorerFolderNode } from '../../nodes/folder';
import { RootEmpty } from './empty'; import { RootEmpty } from './empty';
import * as styles from './styles.css'; import * as styles from './styles.css';
export const ExplorerOrganize = () => { export const ExplorerOrganize = ({
defaultCollapsed = false,
}: {
defaultCollapsed?: boolean;
}) => {
const { organizeService } = useServices({ OrganizeService }); const { organizeService } = useServices({ OrganizeService });
const [newFolderId, setNewFolderId] = useState<string | null>(null); const [newFolderId, setNewFolderId] = useState<string | null>(null);
const [collapsed, setCollapsed] = useState(defaultCollapsed);
const t = useI18n(); const t = useI18n();
@@ -39,6 +45,7 @@ export const ExplorerOrganize = () => {
rootFolder.indexAt('before') rootFolder.indexAt('before')
); );
setNewFolderId(newFolderId); setNewFolderId(newFolderId);
setCollapsed(false);
}, [rootFolder]); }, [rootFolder]);
const handleOnChildrenDrop = useCallback( const handleOnChildrenDrop = useCallback(
@@ -89,10 +96,12 @@ export const ExplorerOrganize = () => {
>(() => args => args.source.data.entity?.type === 'folder', []); >(() => args => args.source.data.entity?.type === 'folder', []);
return ( return (
<div className={styles.container}> <Collapsible.Root className={styles.container} open={!collapsed}>
<CategoryDivider <CategoryDivider
className={styles.draggedOverHighlight} className={styles.draggedOverHighlight}
label={t['com.affine.rootAppSidebar.organize']()} label={t['com.affine.rootAppSidebar.organize']()}
setCollapsed={setCollapsed}
collapsed={collapsed}
> >
<IconButton <IconButton
data-testid="explorer-bar-add-organize-button" data-testid="explorer-bar-add-organize-button"
@@ -102,24 +111,26 @@ export const ExplorerOrganize = () => {
<PlusIcon /> <PlusIcon />
</IconButton> </IconButton>
</CategoryDivider> </CategoryDivider>
<ExplorerTreeRoot <Collapsible.Content>
placeholder={<RootEmpty onClickCreate={handleCreateFolder} />} <ExplorerTreeRoot
> placeholder={<RootEmpty onClickCreate={handleCreateFolder} />}
{folders.map(child => ( >
<ExplorerFolderNode {folders.map(child => (
key={child.id} <ExplorerFolderNode
nodeId={child.id as string} key={child.id}
defaultRenaming={child.id === newFolderId} nodeId={child.id as string}
onDrop={handleOnChildrenDrop} defaultRenaming={child.id === newFolderId}
dropEffect={handleChildrenDropEffect} onDrop={handleOnChildrenDrop}
canDrop={handleChildrenCanDrop} dropEffect={handleChildrenDropEffect}
location={{ canDrop={handleChildrenCanDrop}
at: 'explorer:organize:folder-node', location={{
nodeId: child.id as string, at: 'explorer:organize:folder-node',
}} nodeId: child.id as string,
/> }}
))} />
</ExplorerTreeRoot> ))}
</div> </ExplorerTreeRoot>
</Collapsible.Content>
</Collapsible.Root>
); );
}; };
@@ -2,7 +2,7 @@ import { cssVar } from '@toeverything/theme';
import { style } from '@vanilla-extract/css'; import { style } from '@vanilla-extract/css';
export const container = style({ export const container = style({
marginTop: '16px', marginTop: '8px',
}); });
export const draggedOverHighlight = style({ export const draggedOverHighlight = style({
@@ -5,6 +5,7 @@ import type { Tag } from '@affine/core/modules/tag';
import { TagService } from '@affine/core/modules/tag'; import { TagService } from '@affine/core/modules/tag';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import { PlusIcon } from '@blocksuite/icons/rc'; import { PlusIcon } from '@blocksuite/icons/rc';
import * as Collapsible from '@radix-ui/react-collapsible';
import { useLiveData, useServices } from '@toeverything/infra'; import { useLiveData, useServices } from '@toeverything/infra';
import { useCallback, useState } from 'react'; import { useCallback, useState } from 'react';
@@ -12,12 +13,17 @@ import { ExplorerTagNode } from '../../nodes/tag';
import { RootEmpty } from './empty'; import { RootEmpty } from './empty';
import * as styles from './styles.css'; import * as styles from './styles.css';
export const ExplorerTags = () => { export const ExplorerTags = ({
defaultCollapsed = false,
}: {
defaultCollapsed?: boolean;
}) => {
const { tagService } = useServices({ const { tagService } = useServices({
TagService, TagService,
}); });
const [createdTag, setCreatedTag] = useState<Tag | null>(null); const [createdTag, setCreatedTag] = useState<Tag | null>(null);
const [collapsed, setCollapsed] = useState(defaultCollapsed);
const tags = useLiveData(tagService.tagList.tags$); const tags = useLiveData(tagService.tagList.tags$);
const t = useI18n(); const t = useI18n();
@@ -28,13 +34,16 @@ export const ExplorerTags = () => {
tagService.randomTagColor() tagService.randomTagColor()
); );
setCreatedTag(newTags); setCreatedTag(newTags);
setCollapsed(false);
}, [t, tagService]); }, [t, tagService]);
return ( return (
<div className={styles.container}> <Collapsible.Root className={styles.container} open={!collapsed}>
<CategoryDivider <CategoryDivider
className={styles.draggedOverHighlight} className={styles.draggedOverHighlight}
label={t['com.affine.rootAppSidebar.tags']()} label={t['com.affine.rootAppSidebar.tags']()}
setCollapsed={setCollapsed}
collapsed={collapsed}
> >
<IconButton <IconButton
data-testid="explorer-bar-add-favorite-button" data-testid="explorer-bar-add-favorite-button"
@@ -44,21 +53,23 @@ export const ExplorerTags = () => {
<PlusIcon /> <PlusIcon />
</IconButton> </IconButton>
</CategoryDivider> </CategoryDivider>
<ExplorerTreeRoot <Collapsible.Content>
placeholder={<RootEmpty onClickCreate={handleCreateNewFavoriteDoc} />} <ExplorerTreeRoot
> placeholder={<RootEmpty onClickCreate={handleCreateNewFavoriteDoc} />}
{tags.map(tag => ( >
<ExplorerTagNode {tags.map(tag => (
key={tag.id} <ExplorerTagNode
tagId={tag.id} key={tag.id}
reorderable={false} tagId={tag.id}
location={{ reorderable={false}
at: 'explorer:tags:list', location={{
}} at: 'explorer:tags:list',
defaultRenaming={createdTag?.id === tag.id} }}
/> defaultRenaming={createdTag?.id === tag.id}
))} />
</ExplorerTreeRoot> ))}
</div> </ExplorerTreeRoot>
</Collapsible.Content>
</Collapsible.Root>
); );
}; };
@@ -2,7 +2,7 @@ import { cssVar } from '@toeverything/theme';
import { style } from '@vanilla-extract/css'; import { style } from '@vanilla-extract/css';
export const container = style({ export const container = style({
marginTop: '16px', marginTop: '8px',
}); });
export const draggedOverHighlight = style({ export const draggedOverHighlight = style({
@@ -131,6 +131,7 @@ test('can sync collections between different browser', async ({
await loginUser(page2, user.email); await loginUser(page2, user.email);
await page2.goto(page.url()); await page2.goto(page.url());
const collections = page2.getByTestId('explorer-collections'); const collections = page2.getByTestId('explorer-collections');
await collections.getByTestId('category-divider-collapse-button').click();
await expect(collections.getByText('test collection')).toBeVisible(); await expect(collections.getByText('test collection')).toBeVisible();
} }
}); });
@@ -65,6 +65,7 @@ test('Show collections items in sidebar', async ({ page }) => {
await removeOnboardingPages(page); await removeOnboardingPages(page);
await createAndPinCollection(page); await createAndPinCollection(page);
const collections = page.getByTestId('explorer-collections'); const collections = page.getByTestId('explorer-collections');
await collections.getByTestId('category-divider-collapse-button').click();
const items = collections.locator('[data-testid^="explorer-collection-"]'); const items = collections.locator('[data-testid^="explorer-collection-"]');
expect(await items.count()).toBe(1); expect(await items.count()).toBe(1);
const first = items.first(); const first = items.first();
@@ -105,6 +106,7 @@ test('edit collection', async ({ page }) => {
await removeOnboardingPages(page); await removeOnboardingPages(page);
await createAndPinCollection(page); await createAndPinCollection(page);
const collections = page.getByTestId('explorer-collections'); const collections = page.getByTestId('explorer-collections');
await collections.getByTestId('category-divider-collapse-button').click();
const items = collections.locator('[data-testid^="explorer-collection-"]'); const items = collections.locator('[data-testid^="explorer-collection-"]');
expect(await items.count()).toBe(1); expect(await items.count()).toBe(1);
const first = items.first(); const first = items.first();
@@ -122,6 +124,7 @@ test('edit collection and change filter date', async ({ page }) => {
await removeOnboardingPages(page); await removeOnboardingPages(page);
await createAndPinCollection(page); await createAndPinCollection(page);
const collections = page.getByTestId('explorer-collections'); const collections = page.getByTestId('explorer-collections');
await collections.getByTestId('category-divider-collapse-button').click();
const items = collections.locator('[data-testid^="explorer-collection-"]'); const items = collections.locator('[data-testid^="explorer-collection-"]');
expect(await items.count()).toBe(1); expect(await items.count()).toBe(1);
const first = items.first(); const first = items.first();
@@ -143,6 +146,10 @@ test('add collection from sidebar', async ({ page }) => {
await page.getByTestId('all-pages').click(); await page.getByTestId('all-pages').click();
const cell = page.getByTestId('page-list-item-title').getByText('test page'); const cell = page.getByTestId('page-list-item-title').getByText('test page');
await expect(cell).toBeVisible(); await expect(cell).toBeVisible();
await page
.getByTestId('explorer-collections')
.getByTestId('category-divider-collapse-button')
.click();
const nullCollection = page.getByTestId( const nullCollection = page.getByTestId(
'slider-bar-collection-empty-message' 'slider-bar-collection-empty-message'
); );