mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 12:28:42 +00:00
feat: support subpage (#1663)
This commit is contained in:
@@ -15,6 +15,7 @@ export * from './ui/shared/Container';
|
||||
export * from './ui/table';
|
||||
export * from './ui/toast';
|
||||
export * from './ui/tooltip';
|
||||
export * from './ui/tree-view';
|
||||
|
||||
declare module '@mui/material/styles' {
|
||||
interface Theme {
|
||||
|
||||
128
packages/component/src/ui/tree-view/TreeNode.tsx
Normal file
128
packages/component/src/ui/tree-view/TreeNode.tsx
Normal file
@@ -0,0 +1,128 @@
|
||||
import { useState } from 'react';
|
||||
import { useDrag, useDrop } from 'react-dnd';
|
||||
|
||||
import {
|
||||
StyledCollapse,
|
||||
StyledNodeLine,
|
||||
StyledTreeNodeContainer,
|
||||
StyledTreeNodeItem,
|
||||
} from './styles';
|
||||
import type { Node, NodeLIneProps, TreeNodeProps } from './types';
|
||||
|
||||
const NodeLine = <N,>({
|
||||
node,
|
||||
onDrop,
|
||||
allowDrop = true,
|
||||
isTop = false,
|
||||
}: NodeLIneProps<N>) => {
|
||||
const [{ isOver }, drop] = useDrop(
|
||||
() => ({
|
||||
accept: 'node',
|
||||
drop: (item: Node<N>, monitor) => {
|
||||
const didDrop = monitor.didDrop();
|
||||
if (didDrop) {
|
||||
return;
|
||||
}
|
||||
onDrop?.(item, node, {
|
||||
internal: false,
|
||||
topLine: isTop,
|
||||
bottomLine: !isTop,
|
||||
});
|
||||
},
|
||||
collect: monitor => ({
|
||||
isOver: monitor.isOver(),
|
||||
canDrop: monitor.canDrop(),
|
||||
}),
|
||||
}),
|
||||
[onDrop]
|
||||
);
|
||||
|
||||
return <StyledNodeLine ref={drop} show={isOver && allowDrop} isTop={isTop} />;
|
||||
};
|
||||
|
||||
export const TreeNode = <N,>({
|
||||
node,
|
||||
index,
|
||||
allDrop = true,
|
||||
...otherProps
|
||||
}: TreeNodeProps<N>) => {
|
||||
const { onAdd, onDelete, onDrop } = otherProps;
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
|
||||
const [{ isDragging }, drag] = useDrag(() => ({
|
||||
type: 'node',
|
||||
item: node,
|
||||
collect: monitor => ({
|
||||
isDragging: monitor.isDragging(),
|
||||
}),
|
||||
}));
|
||||
|
||||
const [{ canDrop, isOver }, drop] = useDrop(
|
||||
() => ({
|
||||
accept: 'node',
|
||||
drop: (item: Node<N>, monitor) => {
|
||||
const didDrop = monitor.didDrop();
|
||||
if (didDrop || item.id === node.id || !allDrop) {
|
||||
return;
|
||||
}
|
||||
onDrop?.(item, node, {
|
||||
internal: true,
|
||||
topLine: false,
|
||||
bottomLine: false,
|
||||
});
|
||||
},
|
||||
collect: monitor => ({
|
||||
isOver: monitor.isOver(),
|
||||
canDrop: monitor.canDrop(),
|
||||
}),
|
||||
}),
|
||||
[onDrop, allDrop]
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledTreeNodeContainer ref={drag} isDragging={isDragging}>
|
||||
<StyledTreeNodeItem
|
||||
ref={drop}
|
||||
isOver={isOver && !isDragging}
|
||||
canDrop={canDrop}
|
||||
>
|
||||
{index === 0 && (
|
||||
<NodeLine
|
||||
node={node}
|
||||
{...otherProps}
|
||||
allowDrop={!isDragging && allDrop}
|
||||
isTop={true}
|
||||
/>
|
||||
)}
|
||||
{node.render?.(node, {
|
||||
onAdd: () => onAdd?.(node),
|
||||
onDelete: () => onDelete?.(node),
|
||||
collapsed,
|
||||
setCollapsed,
|
||||
})}
|
||||
{(!node.children?.length || collapsed) && (
|
||||
<NodeLine
|
||||
node={node}
|
||||
{...otherProps}
|
||||
allowDrop={!isDragging && allDrop}
|
||||
/>
|
||||
)}
|
||||
</StyledTreeNodeItem>
|
||||
|
||||
<StyledCollapse in={!collapsed}>
|
||||
{node.children &&
|
||||
node.children.map((childNode, index) => (
|
||||
<TreeNode
|
||||
key={childNode.id}
|
||||
node={childNode}
|
||||
index={index}
|
||||
allDrop={isDragging ? false : allDrop}
|
||||
{...otherProps}
|
||||
/>
|
||||
))}
|
||||
</StyledCollapse>
|
||||
</StyledTreeNodeContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default TreeNode;
|
||||
16
packages/component/src/ui/tree-view/TreeView.tsx
Normal file
16
packages/component/src/ui/tree-view/TreeView.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { DndProvider } from 'react-dnd';
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||
|
||||
import { TreeNode } from './TreeNode';
|
||||
import type { TreeViewProps } from './types';
|
||||
export const TreeView = <N,>({ data, ...otherProps }: TreeViewProps<N>) => {
|
||||
return (
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
{data.map((node, index) => (
|
||||
<TreeNode key={node.id} index={index} node={node} {...otherProps} />
|
||||
))}
|
||||
</DndProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default TreeView;
|
||||
3
packages/component/src/ui/tree-view/index.ts
Normal file
3
packages/component/src/ui/tree-view/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './TreeNode';
|
||||
export * from './TreeView';
|
||||
export * from './types';
|
||||
40
packages/component/src/ui/tree-view/styles.ts
Normal file
40
packages/component/src/ui/tree-view/styles.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import MuiCollapse from '@mui/material/Collapse';
|
||||
|
||||
import { styled } from '../../styles';
|
||||
|
||||
export const StyledCollapse = styled(MuiCollapse)(() => {
|
||||
return {
|
||||
paddingLeft: '12px',
|
||||
};
|
||||
});
|
||||
export const StyledTreeNodeItem = styled('div')<{
|
||||
isOver?: boolean;
|
||||
canDrop?: boolean;
|
||||
}>(({ isOver, canDrop, theme }) => {
|
||||
return {
|
||||
background: isOver && canDrop ? theme.colors.hoverBackground : '',
|
||||
position: 'relative',
|
||||
};
|
||||
});
|
||||
export const StyledTreeNodeContainer = styled('div')<{ isDragging: boolean }>(
|
||||
({ isDragging, theme }) => {
|
||||
return {
|
||||
background: isDragging ? theme.colors.hoverBackground : '',
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
export const StyledNodeLine = styled('div')<{ show: boolean; isTop?: boolean }>(
|
||||
({ show, isTop = false, theme }) => {
|
||||
return {
|
||||
position: 'absolute',
|
||||
left: '0',
|
||||
...(isTop ? { top: '0' } : { bottom: '0' }),
|
||||
width: '100%',
|
||||
paddingTop: '3px',
|
||||
borderBottom: '3px solid',
|
||||
borderColor: show ? theme.colors.primaryColor : 'transparent',
|
||||
zIndex: 1,
|
||||
};
|
||||
}
|
||||
);
|
||||
45
packages/component/src/ui/tree-view/types.ts
Normal file
45
packages/component/src/ui/tree-view/types.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export type Node<N> = {
|
||||
id: string;
|
||||
children?: Node<N>[];
|
||||
render?: (
|
||||
node: Node<N>,
|
||||
eventsAndStatus: {
|
||||
onAdd: () => void;
|
||||
onDelete: () => void;
|
||||
collapsed: boolean;
|
||||
setCollapsed: (collapsed: boolean) => void;
|
||||
},
|
||||
extendProps?: unknown
|
||||
) => ReactNode;
|
||||
} & N;
|
||||
|
||||
type CommonProps<N> = {
|
||||
onAdd?: (node: Node<N>) => void;
|
||||
onDelete?: (node: Node<N>) => void;
|
||||
onDrop?: (
|
||||
dragNode: Node<N>,
|
||||
dropNode: Node<N>,
|
||||
position: {
|
||||
topLine: boolean;
|
||||
bottomLine: boolean;
|
||||
internal: boolean;
|
||||
}
|
||||
) => void;
|
||||
};
|
||||
|
||||
export type TreeNodeProps<N> = {
|
||||
node: Node<N>;
|
||||
index: number;
|
||||
allDrop?: boolean;
|
||||
} & CommonProps<N>;
|
||||
|
||||
export type TreeViewProps<N> = {
|
||||
data: Node<N>[];
|
||||
} & CommonProps<N>;
|
||||
|
||||
export type NodeLIneProps<N> = {
|
||||
allowDrop: boolean;
|
||||
isTop?: boolean;
|
||||
} & Pick<TreeNodeProps<N>, 'node' | 'onDrop'>;
|
||||
Reference in New Issue
Block a user