feat: support subpage (#1663)

This commit is contained in:
Qi
2023-03-23 13:47:07 +08:00
committed by GitHub
parent 2551785451
commit 6a7b5601aa
26 changed files with 824 additions and 82 deletions

View File

@@ -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 {

View 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;

View 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;

View File

@@ -0,0 +1,3 @@
export * from './TreeNode';
export * from './TreeView';
export * from './types';

View 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,
};
}
);

View 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'>;