import { createContext, useContext, JSXElement, Show, For, createEffect, mergeProps } from 'solid-js'; import { createStore } from 'solid-js/store'; import { Dynamic } from 'solid-js/web'; import './TreeView.css'; // TreeView Context Type type TreeViewContextType = { expandedItems: string[]; selectedItem: string | null; toggleItem: (itemId: string) => void; selectItem: (itemId: string) => void; isExpanded: (itemId: string) => boolean; isSelected: (itemId: string) => boolean; isHighlighted: (itemId: string) => boolean; highlightedItems: string[]; }; // Create TreeView Context const TreeViewContext = createContext({ expandedItems: [], selectedItem: null, highlightedItems: [], // eslint-disable-next-line toggleItem: () => {}, // eslint-disable-next-line selectItem: () => {}, isExpanded: () => false, isSelected: () => false, isHighlighted: () => false, }); // Icons for expanded and collapsed states const ChevronRightIcon = () => ( ); const ChevronDownIcon = () => ( ); // Default folder icons const FolderIcon = () => ( ); const FolderOpenIcon = () => ( ); const FileIcon = () => ( ); // Props for RichTreeView component type RichTreeViewProps = { children: JSXElement; defaultExpanded?: string[]; onNodeSelect?: (itemId: string) => void; highlightItems?: string[]; multiSelect?: boolean; class?: string; indentationLevel?: number; }; // Define the state type to fix type errors type TreeViewState = { expandedItems: string[]; selectedItem: string | null; highlightedItems: string[]; }; // RichTreeView component export const RichTreeView = (props: RichTreeViewProps) => { const mergedProps = mergeProps( { defaultExpanded: [] as string[], indentationLevel: 16, // Default indentation of 16px }, props, ); const [state, setState] = createStore({ expandedItems: mergedProps.defaultExpanded, selectedItem: null, highlightedItems: props.highlightItems || [], }); createEffect(() => { if (props.highlightItems) { setState('highlightedItems', [...props.highlightItems]); } }); const toggleItem = (itemId: string) => { setState('expandedItems', (prev) => { if (prev.includes(itemId)) { return prev.filter((id) => id !== itemId); } else { return [...prev, itemId]; } }); }; const selectItem = (itemId: string) => { setState('selectedItem', itemId); props.onNodeSelect && props.onNodeSelect(itemId); }; const isExpanded = (itemId: string) => { return state.expandedItems.includes(itemId); }; const isSelected = (itemId: string) => { return state.selectedItem === itemId; }; const isHighlighted = (itemId: string) => { return state.highlightedItems.includes(itemId); }; const contextValue = { expandedItems: state.expandedItems, selectedItem: state.selectedItem, highlightedItems: state.highlightedItems, toggleItem, selectItem, isExpanded, isSelected, isHighlighted, }; // Add a style block for dynamic indentation const treeViewStyle = ` .tree-item-children { padding-left: ${mergedProps.indentationLevel}px !important; } `; return (
{props.children}
); }; // Props for TreeItem component type TreeItemProps = { itemId: string; label: string | JSXElement; children?: JSXElement; icon?: JSXElement; expandedIcon?: JSXElement; endIcon?: JSXElement; isLeaf?: boolean; }; // TreeItem component export const TreeItem = (props: TreeItemProps) => { const context = useContext(TreeViewContext); const hasChildren = !!props.children; const isLeaf = props.isLeaf !== undefined ? props.isLeaf : !hasChildren; const handleClick = (e: MouseEvent) => { e.stopPropagation(); context.selectItem(props.itemId); if (hasChildren) { context.toggleItem(props.itemId); } }; const getDefaultIcon = () => { if (isLeaf) { return ; } return <>{context.isExpanded(props.itemId) ? : }; }; return (
{!isLeaf && }
{props.icon ? props.icon : props.expandedIcon && context.isExpanded(props.itemId) ? props.expandedIcon : getDefaultIcon()}
{props.label}
{props.endIcon}
{props.children}
); };