mirror of
https://github.com/sasjs/server.git
synced 2025-12-10 11:24:35 +00:00
chore: add custom tree view component
This commit is contained in:
138
web/src/components/tree.tsx
Normal file
138
web/src/components/tree.tsx
Normal file
@@ -0,0 +1,138 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { Menu, MenuItem } from '@mui/material'
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
|
||||
import ChevronRightIcon from '@mui/icons-material/ChevronRight'
|
||||
|
||||
import { TreeNode } from '../utils/types'
|
||||
|
||||
type TreeViewProps = {
|
||||
node: TreeNode
|
||||
selectedFilePath: string
|
||||
handleSelect: (filePath: string) => void
|
||||
defaultExpanded?: string[]
|
||||
}
|
||||
|
||||
const TreeView = ({
|
||||
node,
|
||||
selectedFilePath,
|
||||
handleSelect,
|
||||
defaultExpanded
|
||||
}: TreeViewProps) => {
|
||||
return (
|
||||
<ul
|
||||
style={{
|
||||
listStyle: 'none',
|
||||
padding: '0.75rem 1.25rem',
|
||||
width: 'max-content'
|
||||
}}
|
||||
>
|
||||
<TreeViewNode
|
||||
node={node}
|
||||
selectedFilePath={selectedFilePath}
|
||||
handleSelect={handleSelect}
|
||||
defaultExpanded={defaultExpanded}
|
||||
/>
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
|
||||
export default TreeView
|
||||
|
||||
type TreeViewNodeProps = {
|
||||
node: TreeNode
|
||||
selectedFilePath: string
|
||||
handleSelect: (filePath: string) => void
|
||||
defaultExpanded?: string[]
|
||||
}
|
||||
|
||||
const TreeViewNode = ({
|
||||
node,
|
||||
selectedFilePath,
|
||||
handleSelect,
|
||||
defaultExpanded
|
||||
}: TreeViewNodeProps) => {
|
||||
const [childVisible, setChildVisibility] = useState(false)
|
||||
const [contextMenu, setContextMenu] = useState<{
|
||||
mouseX: number
|
||||
mouseY: number
|
||||
} | null>(null)
|
||||
|
||||
const handleContextMenu = (event: React.MouseEvent) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
setContextMenu(
|
||||
contextMenu === null
|
||||
? {
|
||||
mouseX: event.clientX + 2,
|
||||
mouseY: event.clientY - 6
|
||||
}
|
||||
: null
|
||||
)
|
||||
}
|
||||
|
||||
const hasChild = node.children.length ? true : false
|
||||
|
||||
const handleItemClick = () => {
|
||||
if (node.children.length) {
|
||||
setChildVisibility((v) => !v)
|
||||
return
|
||||
}
|
||||
|
||||
if (!node.name.includes('.')) return
|
||||
|
||||
handleSelect(node.relativePath)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (defaultExpanded && defaultExpanded[0] === node.relativePath) {
|
||||
setChildVisibility(true)
|
||||
defaultExpanded.shift()
|
||||
}
|
||||
}, [defaultExpanded, node.relativePath])
|
||||
|
||||
return (
|
||||
<div onContextMenu={handleContextMenu} style={{ cursor: 'context-menu' }}>
|
||||
<li style={{ display: 'list-item' }}>
|
||||
<div
|
||||
className={`tree-item-label ${
|
||||
selectedFilePath === node.relativePath ? 'selected' : ''
|
||||
}`}
|
||||
onClick={() => handleItemClick()}
|
||||
>
|
||||
{hasChild &&
|
||||
(childVisible ? <ExpandMoreIcon /> : <ChevronRightIcon />)}
|
||||
<div>{node.name}</div>
|
||||
</div>
|
||||
|
||||
{hasChild &&
|
||||
childVisible &&
|
||||
node.children.map((child, index) => (
|
||||
<TreeView
|
||||
key={node.relativePath + '-' + index}
|
||||
node={child}
|
||||
selectedFilePath={selectedFilePath}
|
||||
handleSelect={handleSelect}
|
||||
defaultExpanded={defaultExpanded}
|
||||
/>
|
||||
))}
|
||||
</li>
|
||||
<Menu
|
||||
open={contextMenu !== null}
|
||||
onClose={() => setContextMenu(null)}
|
||||
anchorReference="anchorPosition"
|
||||
anchorPosition={
|
||||
contextMenu !== null
|
||||
? { top: contextMenu.mouseY, left: contextMenu.mouseX }
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{hasChild &&
|
||||
['Add Folder', 'Add File'].map((item) => (
|
||||
<MenuItem key={item}>{item}</MenuItem>
|
||||
))}
|
||||
<MenuItem>Rename</MenuItem>
|
||||
<MenuItem>Delete</MenuItem>
|
||||
</Menu>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -25,3 +25,15 @@ code {
|
||||
padding: '5px 10px';
|
||||
margin-top: '10px';
|
||||
}
|
||||
|
||||
.tree-item-label {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.tree-item-label.selected {
|
||||
background: lightgoldenrodyellow;
|
||||
}
|
||||
|
||||
.tree-item-label:hover {
|
||||
background: lightgray;
|
||||
}
|
||||
|
||||
@@ -30,3 +30,9 @@ export interface RegisterPermissionPayload {
|
||||
principalType: string
|
||||
principalId: number
|
||||
}
|
||||
|
||||
export interface TreeNode {
|
||||
name: string
|
||||
relativePath: string
|
||||
children: Array<TreeNode>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user