mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-30 00:29:46 +08:00
@@ -0,0 +1,25 @@
|
|||||||
|
import { cssVar } from '@toeverything/theme';
|
||||||
|
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||||
|
import { fallbackVar, style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
import { levelIndent } from '../tree/node.css';
|
||||||
|
|
||||||
|
export const emptyChildren = style({
|
||||||
|
fontSize: cssVar('fontSm'),
|
||||||
|
color: cssVarV2('text/disable'),
|
||||||
|
textAlign: 'left',
|
||||||
|
userSelect: 'none',
|
||||||
|
lineHeight: '22px',
|
||||||
|
padding: '4px 0px',
|
||||||
|
marginTop: 2,
|
||||||
|
// 48 = node.paddingLeft + node.collapsable.width + node.icon.width + node.icon.marginRight
|
||||||
|
// = 4 + 16 + 20 + 8
|
||||||
|
// to align with node's content
|
||||||
|
paddingLeft: `calc(${fallbackVar(levelIndent, '20px')} + 48px)`,
|
||||||
|
selectors: {
|
||||||
|
'&[data-dragged-over="true"]': {
|
||||||
|
background: cssVarV2('layer/background/hoverOverlay'),
|
||||||
|
borderRadius: '4px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import clsx from 'clsx';
|
||||||
|
import { forwardRef, type HTMLAttributes, type Ref } from 'react';
|
||||||
|
|
||||||
|
import { emptyChildren } from './empty-node-children.css';
|
||||||
|
|
||||||
|
export const EmptyNodeChildren = forwardRef(function EmptyNodeChildren(
|
||||||
|
{ children, className, ...attrs }: HTMLAttributes<HTMLDivElement>,
|
||||||
|
ref: Ref<HTMLDivElement>
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<div className={clsx(emptyChildren, className)} ref={ref} {...attrs}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
+4
-4
@@ -7,9 +7,9 @@ import {
|
|||||||
type SVGProps,
|
type SVGProps,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
|
||||||
import * as styles from './empty-layout.css';
|
import * as styles from './empty-section.css';
|
||||||
|
|
||||||
interface ExplorerGroupEmptyProps extends HTMLAttributes<HTMLDivElement> {
|
interface ExplorerEmptySectionProps extends HTMLAttributes<HTMLDivElement> {
|
||||||
icon: (props: SVGProps<SVGSVGElement>) => JSX.Element;
|
icon: (props: SVGProps<SVGSVGElement>) => JSX.Element;
|
||||||
message: string;
|
message: string;
|
||||||
messageTestId?: string;
|
messageTestId?: string;
|
||||||
@@ -17,7 +17,7 @@ interface ExplorerGroupEmptyProps extends HTMLAttributes<HTMLDivElement> {
|
|||||||
onActionClick?: () => void;
|
onActionClick?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ExplorerGroupEmpty = forwardRef(function ExplorerGroupEmpty(
|
export const ExplorerEmptySection = forwardRef(function ExplorerEmptySection(
|
||||||
{
|
{
|
||||||
icon: Icon,
|
icon: Icon,
|
||||||
message,
|
message,
|
||||||
@@ -27,7 +27,7 @@ export const ExplorerGroupEmpty = forwardRef(function ExplorerGroupEmpty(
|
|||||||
className,
|
className,
|
||||||
onActionClick,
|
onActionClick,
|
||||||
...attrs
|
...attrs
|
||||||
}: ExplorerGroupEmptyProps,
|
}: ExplorerEmptySectionProps,
|
||||||
ref: Ref<HTMLDivElement>
|
ref: Ref<HTMLDivElement>
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import { cssVar } from '@toeverything/theme';
|
|
||||||
import { fallbackVar, style } from '@vanilla-extract/css';
|
|
||||||
|
|
||||||
import { levelIndent } from '../../tree/node.css';
|
|
||||||
|
|
||||||
export const noReferences = style({
|
|
||||||
fontSize: cssVar('fontSm'),
|
|
||||||
textAlign: 'left',
|
|
||||||
padding: '4px 0 4px 32px',
|
|
||||||
color: cssVar('black30'),
|
|
||||||
userSelect: 'none',
|
|
||||||
paddingLeft: `calc(${fallbackVar(levelIndent, '20px')} + 32px)`,
|
|
||||||
selectors: {
|
|
||||||
'&[data-dragged-over="true"]': {
|
|
||||||
background: cssVar('--affine-hover-color'),
|
|
||||||
borderRadius: '4px',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -2,7 +2,7 @@ import { type DropTargetDropEvent, useDropTarget } from '@affine/component';
|
|||||||
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 * as styles from './empty.css';
|
import { EmptyNodeChildren } from '../../layouts/empty-node-children';
|
||||||
|
|
||||||
export const Empty = ({
|
export const Empty = ({
|
||||||
onDrop,
|
onDrop,
|
||||||
@@ -17,8 +17,8 @@ export const Empty = ({
|
|||||||
);
|
);
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
return (
|
return (
|
||||||
<div className={styles.noReferences} ref={dropTargetRef}>
|
<EmptyNodeChildren ref={dropTargetRef}>
|
||||||
{t['com.affine.collection.emptyCollection']()}
|
{t['com.affine.collection.emptyCollection']()}
|
||||||
</div>
|
</EmptyNodeChildren>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
import { cssVar } from '@toeverything/theme';
|
|
||||||
import { fallbackVar, style } from '@vanilla-extract/css';
|
|
||||||
|
|
||||||
import { levelIndent } from '../../tree/node.css';
|
|
||||||
|
|
||||||
export const noReferences = style({
|
|
||||||
fontSize: cssVar('fontSm'),
|
|
||||||
textAlign: 'left',
|
|
||||||
padding: '4px 0 4px 32px',
|
|
||||||
color: cssVar('black30'),
|
|
||||||
userSelect: 'none',
|
|
||||||
paddingLeft: `calc(${fallbackVar(levelIndent, '20px')} + 32px)`,
|
|
||||||
selectors: {
|
|
||||||
'&[data-dragged-over="true"]': {
|
|
||||||
background: cssVar('--affine-hover-color'),
|
|
||||||
borderRadius: '4px',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -2,7 +2,7 @@ import { type DropTargetDropEvent, useDropTarget } from '@affine/component';
|
|||||||
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 * as styles from './empty.css';
|
import { EmptyNodeChildren } from '../../layouts/empty-node-children';
|
||||||
|
|
||||||
export const Empty = ({
|
export const Empty = ({
|
||||||
onDrop,
|
onDrop,
|
||||||
@@ -16,9 +16,10 @@ export const Empty = ({
|
|||||||
[onDrop]
|
[onDrop]
|
||||||
);
|
);
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.noReferences} ref={dropTargetRef}>
|
<EmptyNodeChildren ref={dropTargetRef}>
|
||||||
{t['com.affine.rootAppSidebar.docs.no-subdoc']()}
|
{t['com.affine.rootAppSidebar.docs.no-subdoc']()}
|
||||||
</div>
|
</EmptyNodeChildren>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,22 +5,16 @@ import {
|
|||||||
} from '@affine/component';
|
} from '@affine/component';
|
||||||
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 { FolderIcon } from '@blocksuite/icons/rc';
|
|
||||||
import clsx from 'clsx';
|
|
||||||
|
|
||||||
import { ExplorerGroupEmpty } from '../../layouts/empty-layout';
|
import { EmptyNodeChildren } from '../../layouts/empty-node-children';
|
||||||
import * as styles from './empty.css';
|
import { draggedOverHighlight } from './empty.css';
|
||||||
|
|
||||||
export const FolderEmpty = ({
|
export const FolderEmpty = ({
|
||||||
onClickCreate,
|
|
||||||
className,
|
|
||||||
canDrop,
|
canDrop,
|
||||||
onDrop,
|
onDrop,
|
||||||
}: {
|
}: {
|
||||||
onClickCreate?: () => void;
|
|
||||||
onDrop?: (data: DropTargetDropEvent<AffineDNDData>) => void;
|
onDrop?: (data: DropTargetDropEvent<AffineDNDData>) => void;
|
||||||
canDrop?: DropTargetOptions<AffineDNDData>['canDrop'];
|
canDrop?: DropTargetOptions<AffineDNDData>['canDrop'];
|
||||||
className?: string;
|
|
||||||
}) => {
|
}) => {
|
||||||
const { dropTargetRef } = useDropTarget(
|
const { dropTargetRef } = useDropTarget(
|
||||||
() => ({
|
() => ({
|
||||||
@@ -32,16 +26,8 @@ export const FolderEmpty = ({
|
|||||||
|
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
return (
|
return (
|
||||||
<ExplorerGroupEmpty
|
<EmptyNodeChildren ref={dropTargetRef} className={draggedOverHighlight}>
|
||||||
className={clsx(styles.draggedOverHighlight, className)}
|
{t['com.affine.rootAppSidebar.organize.empty-folder']()}
|
||||||
ref={dropTargetRef}
|
</EmptyNodeChildren>
|
||||||
icon={FolderIcon}
|
|
||||||
message={t['com.affine.rootAppSidebar.organize.empty-folder']()}
|
|
||||||
messageTestId="slider-bar-organize-empty-message"
|
|
||||||
actionText={t[
|
|
||||||
'com.affine.rootAppSidebar.organize.empty-folder.add-pages'
|
|
||||||
]()}
|
|
||||||
onActionClick={onClickCreate}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -814,11 +814,7 @@ export const ExplorerFolderNodeFolder = ({
|
|||||||
operations={finalOperations}
|
operations={finalOperations}
|
||||||
canDrop={handleCanDrop}
|
canDrop={handleCanDrop}
|
||||||
childrenPlaceholder={
|
childrenPlaceholder={
|
||||||
<FolderEmpty
|
<FolderEmpty canDrop={handleCanDrop} onDrop={handleDropOnPlaceholder} />
|
||||||
canDrop={handleCanDrop}
|
|
||||||
onDrop={handleDropOnPlaceholder}
|
|
||||||
onClickCreate={() => handleAddToFolder('doc')}
|
|
||||||
/>
|
|
||||||
}
|
}
|
||||||
dropEffect={handleDropEffect}
|
dropEffect={handleDropEffect}
|
||||||
data-testid={`explorer-folder-${node.id}`}
|
data-testid={`explorer-folder-${node.id}`}
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
import { cssVar } from '@toeverything/theme';
|
|
||||||
import { fallbackVar, style } from '@vanilla-extract/css';
|
|
||||||
|
|
||||||
import { levelIndent } from '../../tree/node.css';
|
|
||||||
|
|
||||||
export const noReferences = style({
|
|
||||||
fontSize: cssVar('fontSm'),
|
|
||||||
textAlign: 'left',
|
|
||||||
padding: '4px 0 4px 32px',
|
|
||||||
color: cssVar('black30'),
|
|
||||||
userSelect: 'none',
|
|
||||||
paddingLeft: `calc(${fallbackVar(levelIndent, '20px')} + 32px)`,
|
|
||||||
selectors: {
|
|
||||||
'&[data-dragged-over="true"]': {
|
|
||||||
background: cssVar('--affine-hover-color'),
|
|
||||||
borderRadius: '4px',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -2,7 +2,7 @@ import { type DropTargetDropEvent, useDropTarget } from '@affine/component';
|
|||||||
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 * as styles from './empty.css';
|
import { EmptyNodeChildren } from '../../layouts/empty-node-children';
|
||||||
|
|
||||||
export const Empty = ({
|
export const Empty = ({
|
||||||
onDrop,
|
onDrop,
|
||||||
@@ -17,8 +17,8 @@ export const Empty = ({
|
|||||||
);
|
);
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
return (
|
return (
|
||||||
<div className={styles.noReferences} ref={dropTargetRef}>
|
<EmptyNodeChildren ref={dropTargetRef}>
|
||||||
{t['com.affine.rootAppSidebar.tags.no-doc']()}
|
{t['com.affine.rootAppSidebar.tags.no-doc']()}
|
||||||
</div>
|
</EmptyNodeChildren>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { ViewLayersIcon } from '@blocksuite/icons/rc';
|
import { ViewLayersIcon } from '@blocksuite/icons/rc';
|
||||||
|
|
||||||
import { ExplorerGroupEmpty } from '../../layouts/empty-layout';
|
import { ExplorerEmptySection } from '../../layouts/empty-section';
|
||||||
|
|
||||||
export const RootEmpty = ({
|
export const RootEmpty = ({
|
||||||
onClickCreate,
|
onClickCreate,
|
||||||
@@ -11,7 +11,7 @@ export const RootEmpty = ({
|
|||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ExplorerGroupEmpty
|
<ExplorerEmptySection
|
||||||
icon={ViewLayersIcon}
|
icon={ViewLayersIcon}
|
||||||
message={t['com.affine.collections.empty.message']()}
|
message={t['com.affine.collections.empty.message']()}
|
||||||
messageTestId="slider-bar-collection-empty-message"
|
messageTestId="slider-bar-collection-empty-message"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import type { AffineDNDData } from '@affine/core/types/dnd';
|
|||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { FavoriteIcon } from '@blocksuite/icons/rc';
|
import { FavoriteIcon } from '@blocksuite/icons/rc';
|
||||||
|
|
||||||
import { ExplorerGroupEmpty } from '../../layouts/empty-layout';
|
import { ExplorerEmptySection } from '../../layouts/empty-section';
|
||||||
import { DropEffect, type ExplorerTreeNodeDropEffect } from '../../tree';
|
import { DropEffect, type ExplorerTreeNodeDropEffect } from '../../tree';
|
||||||
|
|
||||||
export const RootEmpty = ({
|
export const RootEmpty = ({
|
||||||
@@ -34,7 +34,7 @@ export const RootEmpty = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ExplorerGroupEmpty
|
<ExplorerEmptySection
|
||||||
ref={dropTargetRef}
|
ref={dropTargetRef}
|
||||||
icon={FavoriteIcon}
|
icon={FavoriteIcon}
|
||||||
message={t['com.affine.rootAppSidebar.favorites.empty']()}
|
message={t['com.affine.rootAppSidebar.favorites.empty']()}
|
||||||
@@ -52,6 +52,6 @@ export const RootEmpty = ({
|
|||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</ExplorerGroupEmpty>
|
</ExplorerEmptySection>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { FolderIcon } from '@blocksuite/icons/rc';
|
import { FolderIcon } from '@blocksuite/icons/rc';
|
||||||
|
|
||||||
import { ExplorerGroupEmpty } from '../../layouts/empty-layout';
|
import { ExplorerEmptySection } from '../../layouts/empty-section';
|
||||||
|
|
||||||
export const RootEmpty = ({
|
export const RootEmpty = ({
|
||||||
onClickCreate,
|
onClickCreate,
|
||||||
@@ -11,7 +11,7 @@ export const RootEmpty = ({
|
|||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ExplorerGroupEmpty
|
<ExplorerEmptySection
|
||||||
icon={FolderIcon}
|
icon={FolderIcon}
|
||||||
message={t['com.affine.rootAppSidebar.organize.empty']()}
|
message={t['com.affine.rootAppSidebar.organize.empty']()}
|
||||||
messageTestId="slider-bar-organize-empty-message"
|
messageTestId="slider-bar-organize-empty-message"
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { TagIcon } from '@blocksuite/icons/rc';
|
import { TagIcon } from '@blocksuite/icons/rc';
|
||||||
|
|
||||||
import { ExplorerGroupEmpty } from '../../layouts/empty-layout';
|
import { ExplorerEmptySection } from '../../layouts/empty-section';
|
||||||
|
|
||||||
export const RootEmpty = () => {
|
export const RootEmpty = () => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ExplorerGroupEmpty
|
<ExplorerEmptySection
|
||||||
icon={TagIcon}
|
icon={TagIcon}
|
||||||
message={t['com.affine.rootAppSidebar.tags.empty']()}
|
message={t['com.affine.rootAppSidebar.tags.empty']()}
|
||||||
messageTestId="slider-bar-tags-empty-message"
|
messageTestId="slider-bar-tags-empty-message"
|
||||||
|
|||||||
@@ -81,7 +81,8 @@ test('Show collections items in sidebar', async ({ page }) => {
|
|||||||
await deletePage.click();
|
await deletePage.click();
|
||||||
await page.getByTestId('confirm-modal-confirm').click();
|
await page.getByTestId('confirm-modal-confirm').click();
|
||||||
expect(await first.locator('[data-testid^="explorer-doc-"]').count()).toBe(0);
|
expect(await first.locator('[data-testid^="explorer-doc-"]').count()).toBe(0);
|
||||||
await first.hover();
|
// position is a workaround for the hover issue when empty collection status's height > 26px (will cause scroll)
|
||||||
|
await first.hover({ position: { x: 10, y: 10 } });
|
||||||
await first.getByTestId('explorer-tree-node-operation-button').click();
|
await first.getByTestId('explorer-tree-node-operation-button').click();
|
||||||
const deleteCollection = page.getByText('Delete');
|
const deleteCollection = page.getByText('Delete');
|
||||||
await deleteCollection.click();
|
await deleteCollection.click();
|
||||||
|
|||||||
Reference in New Issue
Block a user