mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-09-24 04:27:27 +08:00
feat(core): support sidebar collapse (#7630)
This commit is contained in:
@@ -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
-1
@@ -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({
|
||||||
|
|||||||
+26
-13
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -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': {
|
||||||
|
|||||||
+33
-23
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -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'
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user