learn and share notes on atproto (wip) 馃 malfestio.stormlightlabs.org/
readability solid axum atproto srs
at main 79 lines 3.0 kB view raw
1import { createSignal, For, Show, splitProps } from "solid-js"; 2import type { Component, JSX } from "solid-js"; 3 4export type TreeNode = { id: string; label: string; icon?: JSX.Element; children?: TreeNode[] }; 5 6type TreeViewProps = { nodes: TreeNode[]; onSelect?: (node: TreeNode) => void; class?: string }; 7 8type TreeNodeItemProps = { node: TreeNode; level: number; onSelect?: (node: TreeNode) => void }; 9 10const ChevronIcon: Component<{ expanded: boolean }> = (props) => ( 11 <svg 12 class={`w-4 h-4 transition-transform duration-200 ${props.expanded ? "rotate-90" : ""}`} 13 fill="none" 14 viewBox="0 0 24 24" 15 stroke="currentColor"> 16 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" /> 17 </svg> 18); 19 20const TreeNodeItem: Component<TreeNodeItemProps> = (props) => { 21 const [expanded, setExpanded] = createSignal(false); 22 const hasChildren = () => props.node.children && props.node.children.length > 0; 23 24 const handleKeyDown = (e: KeyboardEvent) => { 25 if (e.key === "Enter" || e.key === " ") { 26 e.preventDefault(); 27 if (hasChildren()) { 28 setExpanded(!expanded()); 29 } 30 props.onSelect?.(props.node); 31 } else if (e.key === "ArrowRight" && hasChildren() && !expanded()) { 32 setExpanded(true); 33 } else if (e.key === "ArrowLeft" && expanded()) { 34 setExpanded(false); 35 } 36 }; 37 38 return ( 39 <li role="treeitem" aria-expanded={hasChildren() ? expanded() : undefined}> 40 <div 41 class="flex items-center gap-1 px-2 py-1.5 hover:bg-gray-800 cursor-pointer text-gray-300 hover:text-white transition-colors rounded" 42 style={{ "padding-left": `${props.level * 16 + 8}px` }} 43 onClick={() => { 44 if (hasChildren()) setExpanded(!expanded()); 45 props.onSelect?.(props.node); 46 }} 47 onKeyDown={handleKeyDown} 48 tabIndex={0} 49 role="button"> 50 <span class="w-4 h-4 flex items-center justify-center text-gray-500"> 51 <Show when={hasChildren()} fallback={<span class="w-4" />}> 52 <ChevronIcon expanded={expanded()} /> 53 </Show> 54 </span> 55 <Show when={props.node.icon}> 56 <span class="w-4 h-4 flex items-center justify-center">{props.node.icon}</span> 57 </Show> 58 <span class="text-sm truncate">{props.node.label}</span> 59 </div> 60 <Show when={expanded() && hasChildren()}> 61 <ul role="group" class="border-l border-gray-800 ml-4"> 62 <For each={props.node.children}> 63 {(child) => <TreeNodeItem node={child} level={props.level + 1} onSelect={props.onSelect} />} 64 </For> 65 </ul> 66 </Show> 67 </li> 68 ); 69}; 70 71export const TreeView: Component<TreeViewProps> = (props) => { 72 const [local, others] = splitProps(props, ["nodes", "onSelect", "class"]); 73 74 return ( 75 <ul role="tree" class={`text-sm ${local.class || ""}`} {...others}> 76 <For each={local.nodes}>{(node) => <TreeNodeItem node={node} level={0} onSelect={local.onSelect} />}</For> 77 </ul> 78 ); 79};