Files
metabuilder/fakemui/react/components/lab/TreeView.tsx
johndoe6345789 54a819ed71 chore(fakemui): reorganize folder structure by implementation type
ORGANIZED INTO 4 MAIN CATEGORIES:
- react/              React TypeScript components (145 components + Python bindings)
- qml/               QML desktop components (104+ QML components)
- python/            Python package implementations
- legacy/            Utilities, contexts, and migration-in-progress code

SUPPORTING FOLDERS (kept as-is):
- icons/             421 SVG icons
- theming/           Material Design 3 theme system
- styles/            SCSS modules and utilities
- scss/              SCSS preprocessor files
- docs/              Documentation files

STRUCTURE IMPROVEMENTS:
 All code preserved (nothing deleted)
 Clear separation by implementation type
 Better navigation and discoverability
 Easy to find what you need
 Professional organization

DOCUMENTATION:
- Added STRUCTURE.md explaining the new layout
- Updated folder organization with clear purpose
- Maintained all original functionality

All files reorganized while keeping full functionality intact.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-01-23 17:09:48 +00:00

190 lines
5.9 KiB
TypeScript

'use client'
import React, { useState, createContext, useContext, forwardRef } from 'react'
import { classNames } from '../utils/classNames'
interface TreeViewContextType {
expanded: string[]
selected: string[]
multiSelect: boolean
onNodeToggle: (event: React.SyntheticEvent, nodeId: string) => void
onNodeSelect: (event: React.SyntheticEvent, nodeId: string) => void
defaultCollapseIcon?: React.ReactNode
defaultEndIcon?: React.ReactNode
defaultExpandIcon?: React.ReactNode
defaultParentIcon?: React.ReactNode
}
const TreeViewContext = createContext<TreeViewContextType>({
expanded: [],
selected: [],
multiSelect: false,
onNodeToggle: () => {},
onNodeSelect: () => {},
})
export interface TreeViewProps extends React.HTMLAttributes<HTMLUListElement> {
children?: React.ReactNode
defaultCollapseIcon?: React.ReactNode
defaultEndIcon?: React.ReactNode
defaultExpandIcon?: React.ReactNode
defaultParentIcon?: React.ReactNode
expanded?: string[]
defaultExpanded?: string[]
selected?: string | string[]
defaultSelected?: string | string[]
multiSelect?: boolean
onNodeToggle?: (event: React.SyntheticEvent, nodeIds: string[]) => void
onNodeSelect?: (event: React.SyntheticEvent, nodeIds: string | string[]) => void
disableSelection?: boolean
}
export function TreeView({
children,
defaultCollapseIcon,
defaultEndIcon,
defaultExpandIcon,
defaultParentIcon,
expanded: controlledExpanded,
defaultExpanded = [],
selected: controlledSelected,
defaultSelected = [],
multiSelect = false,
onNodeToggle,
onNodeSelect,
disableSelection = false,
className,
...props
}: TreeViewProps) {
const [internalExpanded, setInternalExpanded] = useState<string[]>(defaultExpanded)
const [internalSelected, setInternalSelected] = useState<string[]>(
Array.isArray(defaultSelected) ? defaultSelected : [defaultSelected]
)
const expanded = controlledExpanded ?? internalExpanded
const selected = controlledSelected ?? internalSelected
const handleNodeToggle = (event: React.SyntheticEvent, nodeId: string) => {
const newExpanded = expanded.includes(nodeId) ? expanded.filter((id) => id !== nodeId) : [...expanded, nodeId]
if (controlledExpanded === undefined) {
setInternalExpanded(newExpanded)
}
onNodeToggle?.(event, newExpanded)
}
const handleNodeSelect = (event: React.SyntheticEvent, nodeId: string) => {
if (disableSelection) return
let newSelected: string[]
if (multiSelect) {
newSelected = (Array.isArray(selected) ? selected : [selected]).includes(nodeId)
? (Array.isArray(selected) ? selected : [selected]).filter((id) => id !== nodeId)
: [...(Array.isArray(selected) ? selected : [selected]), nodeId]
} else {
newSelected = [nodeId]
}
if (controlledSelected === undefined) {
setInternalSelected(newSelected)
}
onNodeSelect?.(event, multiSelect ? newSelected : nodeId)
}
return (
<TreeViewContext.Provider
value={{
expanded,
selected: Array.isArray(selected) ? selected : [selected],
multiSelect,
onNodeToggle: handleNodeToggle,
onNodeSelect: handleNodeSelect,
defaultCollapseIcon,
defaultEndIcon,
defaultExpandIcon,
defaultParentIcon,
}}
>
<ul className={classNames('fakemui-tree-view', className)} role="tree" aria-multiselectable={multiSelect} {...props}>
{children}
</ul>
</TreeViewContext.Provider>
)
}
export interface TreeItemProps extends React.LiHTMLAttributes<HTMLLIElement> {
nodeId: string
label?: React.ReactNode
children?: React.ReactNode
icon?: React.ReactNode
expandIcon?: React.ReactNode
collapseIcon?: React.ReactNode
endIcon?: React.ReactNode
disabled?: boolean
ContentComponent?: React.ElementType
ContentProps?: any
}
export const TreeItem = forwardRef<HTMLLIElement, TreeItemProps>(function TreeItem(
{ nodeId, label, children, icon, expandIcon, collapseIcon, endIcon, disabled = false, className, ContentComponent, ContentProps = {}, ...props },
ref
) {
const { expanded, selected, onNodeToggle, onNodeSelect, defaultCollapseIcon, defaultEndIcon, defaultExpandIcon } =
useContext(TreeViewContext)
const isExpanded = expanded.includes(nodeId)
const isSelected = selected.includes(nodeId)
const hasChildren = React.Children.count(children) > 0
const handleClick = (event: React.MouseEvent) => {
if (disabled) return
onNodeSelect(event, nodeId)
}
const handleExpandClick = (event: React.MouseEvent) => {
event.stopPropagation()
if (disabled) return
onNodeToggle(event, nodeId)
}
const displayIcon = hasChildren
? isExpanded
? collapseIcon || defaultCollapseIcon || '▼'
: expandIcon || defaultExpandIcon || '▶'
: endIcon || defaultEndIcon || null
return (
<li
ref={ref}
className={classNames('fakemui-tree-item', className, {
'fakemui-tree-item-expanded': isExpanded,
'fakemui-tree-item-selected': isSelected,
'fakemui-tree-item-disabled': disabled,
})}
role="treeitem"
aria-expanded={hasChildren ? isExpanded : undefined}
aria-selected={isSelected}
aria-disabled={disabled}
{...props}
>
<div className="fakemui-tree-item-content" onClick={handleClick} {...ContentProps}>
{hasChildren && (
<span className="fakemui-tree-item-icon-container" onClick={handleExpandClick}>
{displayIcon}
</span>
)}
{!hasChildren && displayIcon && <span className="fakemui-tree-item-icon-container">{displayIcon}</span>}
{icon && <span className="fakemui-tree-item-icon">{icon}</span>}
<span className="fakemui-tree-item-label">{label}</span>
</div>
{hasChildren && isExpanded && (
<ul className="fakemui-tree-item-group" role="group">
{children}
</ul>
)}
</li>
)
})
export default TreeView