diff --git a/README.md b/README.md index 1701aae..8a84083 100644 --- a/README.md +++ b/README.md @@ -1,50 +1,197 @@ -# react-deep-tree +# React Deep Tree -An attempt to make traversing trees with deep-deep structure user-friendly. Started as a part of our [UI prototype](https://github.com/linksplatform/InfiniteDepthTreeWebUIPrototype). +A React component for rendering infinite depth tree structures with navigation, search, and keyboard controls. This package is adapted from the [InfiniteDepthTreeWebUIPrototype](https://github.com/linksplatform/InfiniteDepthTreeWebUIPrototype) repository and provides a React-based implementation for client-side tree navigation. -## Install +## Features -```Shell -npm i react-deep-tree +- ✨ **Infinite Depth**: Support for unlimited levels of nested tree structures +- ⌨️ **Keyboard Navigation**: Arrow keys, mouse wheel navigation +- 🔍 **Search Functionality**: Real-time search with filtering +- 🎨 **Customizable Components**: Replace default HTML elements with custom components +- 🖱️ **Mouse Interactions**: Click to select, smooth scrolling +- 📱 **Responsive Design**: Mobile-friendly layout +- 🎯 **TypeScript Support**: Full TypeScript definitions included + +## Installation + +```bash +npm install react-deep-tree ``` -## [Use](https://codesandbox.io/s/intelligent-bird-j5vit) +## Quick Start -```JavaScript -import React from "react"; -import DeepTree from "react-deep-tree"; -import type { DataNode } from "react-deep-tree"; +```tsx +import DeepTree, { DataNode } from 'react-deep-tree'; -const data : DataNode[] = [ - { - content: 'Text of a first level of the first element', - children: [ - { - content: 'Text of a second level of the first element', - children: [], - }, - { - content: 'Text of a second level of the first element', - children: [], - }, - ], - }, +const data: DataNode[] = [ { - content: 'Text of a first level of the second element', + content: 'Root Item', children: [ - { - content: 'Text of a second level of the second element', - children: [], - }, - { - content: 'Text of a second level of the second element', - children: [], - }, - ], - }, + { content: 'Child 1' }, + { + content: 'Child 2', + children: [ + { content: 'Grandchild 1' }, + { content: 'Grandchild 2' } + ] + } + ] + } ]; export default function App() { return ; } ``` + +## Advanced Usage + +### With Navigation and Search + +```tsx +import DeepTree from 'react-deep-tree'; + +function NavigableTree() { + return ( + { + console.log('Selected:', node.content); + }} + /> + ); +} +``` + +### Custom Components + +```tsx +const CustomListItem = ({ children, ...props }) => ( +
+ {children} +
+); + +function CustomTree() { + return ( + + ); +} +``` + +## API Reference + +### DeepTree Props + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `data` | `DataNode[]` | - | **Required.** The tree data structure | +| `enableNavigation` | `boolean` | `false` | Enable keyboard/mouse navigation | +| `enableSearch` | `boolean` | `false` | Show search input by default | +| `onNodeSelect` | `(node: DataNode, index: number) => void` | - | Callback when a node is selected | +| `selectedIndex` | `number` | - | Controlled selected index | +| `ListItem` | `React.ElementType` | `'li'` | Component for list items | +| `List` | `React.ElementType` | `'ul'` | Component for lists | +| `ContentFrame` | `React.ElementType` | `'div'` | Component for content wrapper | +| `TreeFrame` | `React.ElementType` | `'div'` | Component for tree wrapper | +| `className` | `string` | `''` | CSS class for the tree container | +| `style` | `React.CSSProperties` | `{}` | Inline styles for the tree container | + +### DataNode Interface + +```tsx +interface DataNode { + readonly content: any; + readonly children?: DataNode[]; +} +``` + +### Navigation Hook + +You can also use the navigation functionality separately: + +```tsx +import { useTreeNavigation } from 'react-deep-tree'; + +function MyComponent() { + const navigation = useTreeNavigation({ + data: myData, + enableNavigation: true, + onNodeSelect: handleSelect + }); + + // Access navigation state + console.log(navigation.selectedIndex); + console.log(navigation.query); + console.log(navigation.showQuery); +} +``` + +## Keyboard Shortcuts + +When `enableNavigation` is true: + +- `↑/↓ Arrow Keys`: Navigate up/down through items +- `Mouse Wheel`: Navigate through items +- `/`: Toggle search input +- `Escape`: Close search input + +## Styling + +The component includes default styling inspired by the original prototype. You can: + +1. **Use the included CSS file:** + ```tsx + import 'react-deep-tree/styles.css'; + ``` + +2. **Override with custom styles:** + ```css + .deep-tree { + background-color: your-color; + } + ``` + +3. **Use custom components** for complete control over styling + +## Examples + +Check the `/example` directory for comprehensive examples including: + +- Basic tree rendering +- Navigation-enabled trees +- Search functionality +- Custom styled components +- Interactive demos + +## Development + +```bash +# Install dependencies +npm install + +# Build the library +npm run build + +# Run linting +npm run lint +``` + +## License + +LGPL-3.0 - See LICENSE file for details. + +## Contributing + +This project is part of the Links Platform ecosystem. Contributions are welcome! Please read the contributing guidelines before submitting PRs. + +## Credits + +Adapted from the [InfiniteDepthTreeWebUIPrototype](https://github.com/linksplatform/InfiniteDepthTreeWebUIPrototype) by Links Platform. diff --git a/example/components/deep-tree.tsx b/example/components/deep-tree.tsx index 88a973f..2c022f8 100644 --- a/example/components/deep-tree.tsx +++ b/example/components/deep-tree.tsx @@ -1,58 +1,274 @@ -import React from 'react'; +import React, { useState } from 'react'; +import DeepTree, { DataNode, useTreeNavigation } from '../../index'; -interface DataNode { - readonly content: any; - readonly children: DataNode[]; -} -export type { DataNode }; +// Example data demonstrating infinite depth tree structure +const exampleData: DataNode[] = [ + { + content: 'Root Item 1: Documents', + children: [ + { + content: 'Work Documents', + children: [ + { + content: 'Project Alpha', + children: [ + { content: 'Requirements.pdf' }, + { content: 'Design.sketch' }, + { content: 'Implementation', + children: [ + { content: 'Frontend Code' }, + { content: 'Backend Code' }, + { content: 'Database Schema' } + ] + } + ] + }, + { content: 'Project Beta.docx' }, + { content: 'Meeting Notes.md' } + ] + }, + { + content: 'Personal Files', + children: [ + { content: 'Photos', + children: [ + { content: 'Vacation 2023' }, + { content: 'Family Events' } + ] + }, + { content: 'Books', + children: [ + { content: 'Technical', + children: [ + { content: 'React Patterns.epub' }, + { content: 'TypeScript Deep Dive.pdf' } + ] + }, + { content: 'Fiction', + children: [ + { content: 'Sci-Fi Collection' }, + { content: 'Mystery Novels' } + ] + } + ] + } + ] + } + ] + }, + { + content: 'Root Item 2: Development', + children: [ + { + content: 'Web Projects', + children: [ + { content: 'E-commerce Site' }, + { content: 'Portfolio Website' }, + { content: 'Blog Platform', + children: [ + { content: 'Frontend (React)' }, + { content: 'Backend (Node.js)' }, + { content: 'Database (PostgreSQL)' } + ] + } + ] + }, + { + content: 'Mobile Apps', + children: [ + { content: 'iOS App' }, + { content: 'Android App' }, + { content: 'React Native Project' } + ] + } + ] + }, + { + content: 'Root Item 3: Learning Resources', + children: [ + { + content: 'Online Courses', + children: [ + { content: 'JavaScript Fundamentals' }, + { content: 'Advanced React Patterns' }, + { content: 'System Design' } + ] + }, + { content: 'Video Tutorials' }, + { content: 'Documentation Links' } + ] + } +]; -interface TreeNodeProps { - data: DataNode; - ListItem?: React.ElementType; - List?: React.ElementType; - ContentFrame?: React.ElementType; +export function BasicDeepTreeExample() { + return ( +
+

Basic Deep Tree (Static)

+ +
+ ); } -export function TreeNode({ data, ListItem = 'li', List = 'ul', ContentFrame = 'div' }: TreeNodeProps) { - const { content, children } = data; - let item; - let treeNodesList; +export function NavigationEnabledExample() { + const [selectedNode, setSelectedNode] = useState(null); - if (content !== undefined) { - item = {content}; - } - if (children && children.length > 0) { - const treeNodes = children.map((element, index) => ( - - )); - treeNodesList = {treeNodes}; - } return ( - - {item} - {treeNodesList} - +
+
+

Navigation Controls

+
    +
  • ↑/↓ Arrow Keys: Navigate items
  • +
  • Mouse Wheel: Navigate items
  • +
  • / : Toggle search
  • +
  • ESC: Close search
  • +
+ +

Selected Item:

+
+ {selectedNode ? selectedNode.content : 'None selected'} +
+
+ +
+ setSelectedNode(node)} + /> +
+
); } -interface DeepTreeProps { - data: DataNode[]; - ListItem?: React.ElementType; - List?: React.ElementType; - ContentFrame?: React.ElementType; - TreeFrame?: React.ElementType; +export function SearchEnabledExample() { + return ( +
+

+ Search-Enabled Deep Tree +

+

+ Press "/" to search, or use the search box above +

+ + console.log('Selected:', node.content)} + /> +
+ ); } -export function DeepTree({ data, ListItem = 'li', List = 'ul', ContentFrame = 'div', TreeFrame='div' }: DeepTreeProps) { +// Demo of customizable components +export function CustomStyledExample() { + const CustomListItem = ({ children, ...props }: any) => ( +
+ {children} +
+ ); + + const CustomContentFrame = ({ children, ...props }: any) => ( + + {children} + + ); + return ( - - - {data.map((object, index) => ( - - ))} - - +
+

Custom Styled Components

+ +
); } -export default DeepTree; \ No newline at end of file +export default function DeepTreeDemo() { + const [currentExample, setCurrentExample] = useState('navigation'); + + const examples = { + basic: , + navigation: , + search: , + custom: + }; + + return ( +
+
+ {Object.keys(examples).map((key) => ( + + ))} +
+ +
+ {examples[currentExample as keyof typeof examples]} +
+
+ ); +} \ No newline at end of file diff --git a/index.js b/index.js index 3520392..4a19126 100644 --- a/index.js +++ b/index.js @@ -1,31 +1,205 @@ "use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.DeepTree = exports.TreeNode = void 0; -const react_1 = __importDefault(require("react")); -function TreeNode({ data }) { +exports.DeepTree = exports.TreeNode = exports.useTreeNavigation = void 0; +const react_1 = __importStar(require("react")); +function flattenTree(data, level = 0) { + const result = []; + data.forEach((node, index) => { + result.push({ node, level, index, parent: undefined }); + if (node.children && node.children.length > 0) { + const childNodes = flattenTree(node.children, level + 1); + childNodes.forEach((child) => { + child.parent = node; + result.push(child); + }); + } + }); + return result; +} +function useTreeNavigation({ data, enableNavigation, onNodeSelect }) { + const [selectedIndex, setSelectedIndex] = react_1.useState(0); + const [query, setQuery] = react_1.useState(''); + const [showQuery, setShowQuery] = react_1.useState(false); + const flatNodes = react_1.useMemo(() => flattenTree(data), [data]); + const filteredNodes = react_1.useMemo(() => { + if (!query) { + return flatNodes; + } + return flatNodes.filter(({ node }) => node.content && node.content.toString().toLowerCase().includes(query.toLowerCase())); + }, [flatNodes, query]); + const currentNode = filteredNodes[selectedIndex]; + const navigateUp = react_1.useCallback(() => { + setSelectedIndex((prev) => Math.max(0, prev - 1)); + }, []); + const navigateDown = react_1.useCallback(() => { + setSelectedIndex((prev) => Math.min(filteredNodes.length - 1, prev + 1)); + }, [filteredNodes.length]); + const toggleQuery = react_1.useCallback(() => { + setShowQuery((prev) => !prev); + if (showQuery) { + setQuery(''); + } + }, [showQuery]); + react_1.useEffect(() => { + if (!enableNavigation) + return; + const handleKeyDown = (event) => { + switch (event.key) { + case 'ArrowUp': + event.preventDefault(); + navigateUp(); + break; + case 'ArrowDown': + event.preventDefault(); + navigateDown(); + break; + case '/': + if (!event.ctrlKey && !event.metaKey) { + event.preventDefault(); + toggleQuery(); + } + break; + case 'Escape': + if (showQuery) { + event.preventDefault(); + setShowQuery(false); + setQuery(''); + } + break; + } + }; + const handleWheel = (event) => { + if (event.deltaY > 0) { + navigateDown(); + } + else { + navigateUp(); + } + }; + document.addEventListener('keydown', handleKeyDown); + document.addEventListener('wheel', handleWheel, { passive: true }); + return () => { + document.removeEventListener('keydown', handleKeyDown); + document.removeEventListener('wheel', handleWheel); + }; + }, [enableNavigation, navigateUp, navigateDown, toggleQuery, showQuery]); + react_1.useEffect(() => { + if (currentNode && onNodeSelect) { + onNodeSelect(currentNode.node, currentNode.index); + } + }, [currentNode, onNodeSelect]); + return { + selectedIndex, + setSelectedIndex, + query, + setQuery, + showQuery, + setShowQuery, + toggleQuery, + filteredNodes, + currentNode + }; +} +exports.useTreeNavigation = useTreeNavigation; +function TreeNode({ data, ListItem = 'li', List = 'ul', ContentFrame = 'div', level = 0, isSelected = false, onSelect, nodeIndex = 0, className = '' }) { const { content, children } = data; + const nodeRef = react_1.useRef(null); + react_1.useEffect(() => { + if (isSelected && nodeRef.current) { + nodeRef.current.scrollIntoView({ + behavior: 'smooth', + block: 'center' + }); + } + }, [isSelected]); + const handleClick = react_1.useCallback(() => { + if (onSelect) { + onSelect(data, nodeIndex); + } + }, [data, nodeIndex, onSelect]); let item; let treeNodesList; if (content !== undefined) { - item = react_1.default.createElement("div", null, content); + item = (react_1.default.createElement(ContentFrame, { className: isSelected ? 'selected' : '', onClick: handleClick, style: { + cursor: 'pointer', + padding: '4px 8px', + backgroundColor: isSelected ? '#EA7500' : 'transparent', + color: isSelected ? '#fff' : 'inherit', + transition: 'all 0.3s ease-in-out' + } }, content)); } if (children && children.length > 0) { - const treeNodes = children.map((element, index) => react_1.default.createElement(TreeNode, { data: element, key: index })); - treeNodesList = react_1.default.createElement("ul", null, treeNodes); + const treeNodes = children.map((element, index) => (react_1.default.createElement(TreeNode, { data: element, key: index, ListItem: ListItem, List: List, ContentFrame: ContentFrame, level: level + 1, isSelected: false, onSelect: onSelect, nodeIndex: index }))); + treeNodesList = react_1.default.createElement(List, { style: { marginLeft: level > 0 ? '20px' : '0' } }, treeNodes); } - return (react_1.default.createElement("li", null, + return (react_1.default.createElement(ListItem, { ref: nodeRef, className: className, style: { + listStyle: 'none', + margin: '2px 0' + } }, item, treeNodesList)); } exports.TreeNode = TreeNode; -function DeepTree({ data }) { - return (react_1.default.createElement("ul", null, - " ", - data.map((object, index) => react_1.default.createElement(TreeNode, { key: index, data: object })), - " ")); +function DeepTree({ data, ListItem = 'li', List = 'ul', ContentFrame = 'div', TreeFrame = 'div', enableNavigation = false, enableSearch = false, onNodeSelect, selectedIndex, className = '', style = {} }) { + const navigation = useTreeNavigation({ + data, + enableNavigation, + onNodeSelect + }); + const currentSelectedIndex = selectedIndex !== undefined ? selectedIndex : navigation.selectedIndex; + const defaultStyle = Object.assign({ fontFamily: 'Verdana, sans-serif, Calibri', fontSize: '15px', lineHeight: '1.5em', backgroundColor: '#1F1F1F', color: '#959595', padding: '20px', minHeight: '100vh' }, style); + return (react_1.default.createElement(TreeFrame, { className: className, style: defaultStyle }, + (enableSearch || navigation.showQuery) && (react_1.default.createElement("div", { style: { + position: 'fixed', + top: '20px', + left: '20px', + right: '20px', + zIndex: 1000 + } }, + react_1.default.createElement("input", { type: "text", value: navigation.query, onChange: (e) => navigation.setQuery(e.target.value), placeholder: "Search...", autoComplete: "off", spellCheck: false, style: { + width: '100%', + padding: '10px 15px', + backgroundColor: '#2A2A2A', + border: 'none', + borderRadius: '4px', + color: '#fff', + fontSize: '16px', + boxShadow: '0 2px 10px rgba(0,0,0,0.3)', + outline: 'none' + }, autoFocus: true }))), + react_1.default.createElement("div", { style: { marginTop: navigation.showQuery ? '80px' : '0' } }, + react_1.default.createElement(List, { style: { padding: 0, margin: 0 } }, navigation.query ? + navigation.filteredNodes.map((item, index) => { + const isSelected = enableNavigation && index === currentSelectedIndex; + return (react_1.default.createElement(TreeNode, { key: index, data: item.node, ListItem: ListItem, List: List, ContentFrame: ContentFrame, isSelected: isSelected, onSelect: navigation.currentNode ? + (node, nodeIndex) => onNodeSelect === null || onNodeSelect === void 0 ? void 0 : onNodeSelect(node, nodeIndex) : + undefined, nodeIndex: index })); + }) : + data.map((item, index) => { + const isSelected = enableNavigation && index === currentSelectedIndex; + return (react_1.default.createElement(TreeNode, { key: index, data: item, ListItem: ListItem, List: List, ContentFrame: ContentFrame, isSelected: isSelected, onSelect: navigation.currentNode ? + (node, nodeIndex) => onNodeSelect === null || onNodeSelect === void 0 ? void 0 : onNodeSelect(node, nodeIndex) : + undefined, nodeIndex: index })); + }))))); } exports.DeepTree = DeepTree; exports.default = DeepTree; diff --git a/index.js.map b/index.js.map index 2148afb..6d9e3f9 100644 --- a/index.js.map +++ b/index.js.map @@ -1 +1 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["index.tsx"],"names":[],"mappings":";;;;;;AAAA,kDAA0B;AAQ1B,SAAgB,QAAQ,CAAC,EAAE,IAAI,EAAE;IAC/B,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;IACnC,IAAI,IAAI,CAAC;IACT,IAAI,aAAa,CAAC;IAElB,IAAI,OAAO,KAAK,SAAS,EAAE;QACzB,IAAI,GAAG,2CAAM,OAAO,CAAO,CAAC;KAC7B;IACD,IAAI,QAAQ,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE;QAEnC,MAAM,SAAS,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,CAAC,8BAAC,QAAQ,IAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,GAAI,CAAC,CAAC;QAC5F,aAAa,GAAG,0CAAK,SAAS,CAAM,CAAC;KACtC;IACD,OAAO,CACL;QACG,IAAI;QACJ,aAAa,CACX,CACN,CAAC;AACJ,CAAC;AAnBD,4BAmBC;AAED,SAAgB,QAAQ,CAAC,EAAE,IAAI,EAAE;IAC/B,OAAO,CACL;;QAAM,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,8BAAC,QAAQ,IAAC,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,GAAI,CAAC;YAAO,CACjF,CAAC;AACJ,CAAC;AAJD,4BAIC;AAED,kBAAe,QAAQ,CAAC"} \ No newline at end of file +{"version":3,"file":"index.js","sourceRoot":"","sources":["index.tsx"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/index.tsx b/index.tsx index 0609686..0a2d36f 100644 --- a/index.tsx +++ b/index.tsx @@ -1,35 +1,349 @@ -import React from 'react'; +import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react'; -interface DataNode { +export interface DataNode { readonly content: any; - readonly children: DataNode[]; + readonly children?: DataNode[]; } -export type { DataNode }; -export function TreeNode({ data }) { +export interface TreeNodeProps { + data: DataNode; + ListItem?: React.ElementType; + List?: React.ElementType; + ContentFrame?: React.ElementType; + level?: number; + isSelected?: boolean; + onSelect?: (node: DataNode, index: number) => void; + nodeIndex?: number; + className?: string; +} + +export interface DeepTreeProps { + data: DataNode[]; + ListItem?: React.ElementType; + List?: React.ElementType; + ContentFrame?: React.ElementType; + TreeFrame?: React.ElementType; + enableNavigation?: boolean; + enableSearch?: boolean; + onNodeSelect?: (node: DataNode, index: number) => void; + selectedIndex?: number; + className?: string; + style?: React.CSSProperties; +} + +export interface NavigationHookProps { + data: DataNode[]; + enableNavigation: boolean; + onNodeSelect?: (node: DataNode, index: number) => void; +} + +export interface FlatNode { + node: DataNode; + level: number; + index: number; + parent?: DataNode; +} + +function flattenTree(data: DataNode[], level: number = 0): FlatNode[] { + const result: FlatNode[] = []; + data.forEach((node, index) => { + result.push({ node, level, index, parent: undefined }); + if (node.children && node.children.length > 0) { + const childNodes = flattenTree(node.children, level + 1); + childNodes.forEach((child) => { + child.parent = node; + result.push(child); + }); + } + }); + return result; +} + +export function useTreeNavigation({ data, enableNavigation, onNodeSelect }: NavigationHookProps) { + const [selectedIndex, setSelectedIndex] = useState(0); + const [query, setQuery] = useState(''); + const [showQuery, setShowQuery] = useState(false); + + const flatNodes = useMemo(() => flattenTree(data), [data]); + const filteredNodes = useMemo(() => { + if (!query) { return flatNodes; } + return flatNodes.filter(({ node }) => + node.content && node.content.toString().toLowerCase().includes(query.toLowerCase()), + ); + }, [flatNodes, query]); + + const currentNode = filteredNodes[selectedIndex]; + + const navigateUp = useCallback(() => { + setSelectedIndex((prev) => Math.max(0, prev - 1)); + }, []); + + const navigateDown = useCallback(() => { + setSelectedIndex((prev) => Math.min(filteredNodes.length - 1, prev + 1)); + }, [filteredNodes.length]); + + const toggleQuery = useCallback(() => { + setShowQuery((prev) => !prev); + if (showQuery) { + setQuery(''); + } + }, [showQuery]); + + useEffect(() => { + if (!enableNavigation) return; + + const handleKeyDown = (event: KeyboardEvent) => { + switch (event.key) { + case 'ArrowUp': + event.preventDefault(); + navigateUp(); + break; + case 'ArrowDown': + event.preventDefault(); + navigateDown(); + break; + case '/': + if (!event.ctrlKey && !event.metaKey) { + event.preventDefault(); + toggleQuery(); + } + break; + case 'Escape': + if (showQuery) { + event.preventDefault(); + setShowQuery(false); + setQuery(''); + } + break; + } + }; + + const handleWheel = (event: WheelEvent) => { + if (event.deltaY > 0) { + navigateDown(); + } else { + navigateUp(); + } + }; + + document.addEventListener('keydown', handleKeyDown); + document.addEventListener('wheel', handleWheel, { passive: true }); + + return () => { + document.removeEventListener('keydown', handleKeyDown); + document.removeEventListener('wheel', handleWheel); + }; + }, [enableNavigation, navigateUp, navigateDown, toggleQuery, showQuery]); + + useEffect(() => { + if (currentNode && onNodeSelect) { + onNodeSelect(currentNode.node, currentNode.index); + } + }, [currentNode, onNodeSelect]); + + return { + selectedIndex, + setSelectedIndex, + query, + setQuery, + showQuery, + setShowQuery, + toggleQuery, + filteredNodes, + currentNode + }; +} + +export function TreeNode({ + data, + ListItem = 'li', + List = 'ul', + ContentFrame = 'div', + level = 0, + isSelected = false, + onSelect, + nodeIndex = 0, + className = '' +}: TreeNodeProps) { const { content, children } = data; + const nodeRef = useRef(null); + + useEffect(() => { + if (isSelected && nodeRef.current) { + nodeRef.current.scrollIntoView({ + behavior: 'smooth', + block: 'center' + }); + } + }, [isSelected]); + + const handleClick = useCallback(() => { + if (onSelect) { + onSelect(data, nodeIndex); + } + }, [data, nodeIndex, onSelect]); + let item; let treeNodesList; if (content !== undefined) { - item =
{content}
; + item = ( + + {content} + + ); } - if (children && children.length > 0) { - const treeNodes = children.map((element, index) => ); - treeNodesList =
    {treeNodes}
; + if (children && children.length > 0) { + const treeNodes = children.map((element, index) => ( + + )); + treeNodesList = 0 ? '20px' : '0' }}>{treeNodes}; } + return ( -
  • + {item} {treeNodesList} -
  • + ); } -export function DeepTree({ data }) { +export function DeepTree({ + data, + ListItem = 'li', + List = 'ul', + ContentFrame = 'div', + TreeFrame = 'div', + enableNavigation = false, + enableSearch = false, + onNodeSelect, + selectedIndex, + className = '', + style = {} +}: DeepTreeProps) { + const navigation = useTreeNavigation({ + data, + enableNavigation, + onNodeSelect + }); + + const currentSelectedIndex = selectedIndex !== undefined ? selectedIndex : navigation.selectedIndex; + + const defaultStyle: React.CSSProperties = { + fontFamily: 'Verdana, sans-serif, Calibri', + fontSize: '15px', + lineHeight: '1.5em', + backgroundColor: '#1F1F1F', + color: '#959595', + padding: '20px', + minHeight: '100vh', + ...style + }; + return ( -
      {data.map((object, index) => )}
    + + {(enableSearch || navigation.showQuery) && ( +
    + navigation.setQuery(e.target.value)} + placeholder="Search..." + autoComplete="off" + spellCheck={false} + style={{ + width: '100%', + padding: '10px 15px', + backgroundColor: '#2A2A2A', + border: 'none', + borderRadius: '4px', + color: '#fff', + fontSize: '16px', + boxShadow: '0 2px 10px rgba(0,0,0,0.3)', + outline: 'none' + }} + autoFocus + /> +
    + )} + +
    + + {navigation.query ? + navigation.filteredNodes.map((item, index) => { + const isSelected = enableNavigation && index === currentSelectedIndex; + return ( + onNodeSelect?.(node, nodeIndex) : + undefined + } + nodeIndex={index} + /> + ); + }) : + data.map((item, index) => { + const isSelected = enableNavigation && index === currentSelectedIndex; + return ( + onNodeSelect?.(node, nodeIndex) : + undefined + } + nodeIndex={index} + /> + ); + }) + } + +
    +
    ); } diff --git a/package-lock.json b/package-lock.json index 5e27122..6affdcf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,358 +1,538 @@ { "name": "react-deep-tree", "version": "0.0.6", - "lockfileVersion": 1, + "lockfileVersion": 3, "requires": true, - "dependencies": { - "@babel/code-frame": { + "packages": { + "": { + "name": "react-deep-tree", + "version": "0.0.6", + "license": "LGPL-3.0", + "dependencies": { + "debug": "^4.1.1", + "tslint": "^6.1.2", + "tslint-config-airbnb": "^5.11.2", + "tslint-eslint-rules": "^5.4.0" + }, + "devDependencies": { + "@types/node": "^14.0.23", + "@types/react": "^16.9.43", + "@types/react-dom": "^16.9.8", + "typescript": "^3.9.7" + }, + "peerDependencies": { + "react": "^16.13.1", + "react-dom": "^16.13.1" + } + }, + "node_modules/@babel/code-frame": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", - "requires": { + "dependencies": { "@babel/highlight": "^7.10.4" } }, - "@babel/helper-validator-identifier": { + "node_modules/@babel/helper-validator-identifier": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==" }, - "@babel/highlight": { + "node_modules/@babel/highlight": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", - "requires": { + "dependencies": { "@babel/helper-validator-identifier": "^7.10.4", "chalk": "^2.0.0", "js-tokens": "^4.0.0" } }, - "@fimbul/bifrost": { + "node_modules/@fimbul/bifrost": { "version": "0.21.0", "resolved": "https://registry.npmjs.org/@fimbul/bifrost/-/bifrost-0.21.0.tgz", "integrity": "sha512-ou8VU+nTmOW1jeg+FT+sn+an/M0Xb9G16RucrfhjXGWv1Q97kCoM5CG9Qj7GYOSdu7km72k7nY83Eyr53Bkakg==", - "requires": { + "dependencies": { "@fimbul/ymir": "^0.21.0", "get-caller-file": "^2.0.0", "tslib": "^1.8.1", "tsutils": "^3.5.0" }, + "peerDependencies": { + "tslint": "^5.0.0", + "typescript": ">= 3.3.0 || >= 3.6.0-dev || >= 3.7.0-dev" + } + }, + "node_modules/@fimbul/bifrost/node_modules/tsutils": { + "version": "3.17.1", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz", + "integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==", "dependencies": { - "tsutils": { - "version": "3.17.1", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz", - "integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==", - "requires": { - "tslib": "^1.8.1" - } - } + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" } }, - "@fimbul/ymir": { + "node_modules/@fimbul/ymir": { "version": "0.21.0", "resolved": "https://registry.npmjs.org/@fimbul/ymir/-/ymir-0.21.0.tgz", "integrity": "sha512-T/y7WqPsm4n3zhT08EpB5sfdm2Kvw3gurAxr2Lr5dQeLi8ZsMlNT/Jby+ZmuuAAd1PnXYzKp+2SXgIkQIIMCUg==", - "requires": { + "dependencies": { "inversify": "^5.0.0", "reflect-metadata": "^0.1.12", "tslib": "^1.8.1" + }, + "peerDependencies": { + "tsutils": ">=2.29.0", + "typescript": ">= 3.3.0 || >= 3.6.0-dev || >= 3.7.0-dev" } }, - "@types/node": { + "node_modules/@types/node": { "version": "14.6.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.6.1.tgz", "integrity": "sha512-HnYlg/BRF8uC1FyKRFZwRaCPTPYKa+6I8QiUZFLredaGOou481cgFS4wKRFyKvQtX8xudqkSdBczJHIYSQYKrQ==", "dev": true }, - "@types/prop-types": { + "node_modules/@types/prop-types": { "version": "15.7.3", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==", "dev": true }, - "@types/react": { + "node_modules/@types/react": { "version": "16.9.48", "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.48.tgz", "integrity": "sha512-4ykBVswgYitPGMXFRxJCHkxJDU2rjfU3/zw67f8+dB7sNdVJXsrwqoYxz/stkAucymnEEbRPFmX7Ce5Mc/kJCw==", "dev": true, - "requires": { + "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" } }, - "@types/react-dom": { + "node_modules/@types/react-dom": { "version": "16.9.8", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.8.tgz", "integrity": "sha512-ykkPQ+5nFknnlU6lDd947WbQ6TE3NNzbQAkInC2EKY1qeYdTKp7onFusmYZb+ityzx2YviqT6BXSu+LyWWJwcA==", "dev": true, - "requires": { + "dependencies": { "@types/react": "*" } }, - "ansi-styles": { + "node_modules/ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { + "dependencies": { "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" } }, - "argparse": { + "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "requires": { + "dependencies": { "sprintf-js": "~1.0.2" } }, - "balanced-match": { + "node_modules/balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, - "brace-expansion": { + "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { + "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, - "builtin-modules": { + "node_modules/builtin-modules": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=" + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "engines": { + "node": ">=0.10.0" + } }, - "chalk": { + "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { + "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" } }, - "color-convert": { + "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { + "dependencies": { "color-name": "1.1.3" } }, - "color-name": { + "node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, - "commander": { + "node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" }, - "concat-map": { + "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, - "csstype": { + "node_modules/csstype": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.3.tgz", "integrity": "sha512-jPl+wbWPOWJ7SXsWyqGRk3lGecbar0Cb0OvZF/r/ZU011R4YqiRehgkQ9p4eQfo9DSDLqLL3wHwfxeJiuIsNag==", "dev": true }, - "debug": { + "node_modules/debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "requires": { + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dependencies": { "ms": "^2.1.1" } }, - "diff": { + "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==" + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "engines": { + "node": ">=0.3.1" + } }, - "doctrine": { + "node_modules/doctrine": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-0.7.2.tgz", "integrity": "sha1-fLhgNZujvpDgQLJrcpzkv6ZUxSM=", - "requires": { + "dependencies": { "esutils": "^1.1.6", "isarray": "0.0.1" }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - } + "engines": { + "node": ">=0.10.0" } }, - "escape-string-regexp": { + "node_modules/doctrine/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "engines": { + "node": ">=0.8.0" + } }, - "esprima": { + "node_modules/esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } }, - "esutils": { + "node_modules/esutils": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/esutils/-/esutils-1.1.6.tgz", - "integrity": "sha1-wBzKqa5LiXxtDD4hCuUvPHqEQ3U=" + "integrity": "sha1-wBzKqa5LiXxtDD4hCuUvPHqEQ3U=", + "engines": { + "node": ">=0.10.0" + } }, - "fs.realpath": { + "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, - "get-caller-file": { + "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } }, - "glob": { + "node_modules/glob": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "requires": { + "deprecated": "Glob versions prior to v9 are no longer supported", + "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.0.4", "once": "^1.3.0", "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "has-flag": { + "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "engines": { + "node": ">=4" + } }, - "inflight": { + "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "requires": { + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, - "inherits": { + "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, - "inversify": { + "node_modules/inversify": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/inversify/-/inversify-5.0.1.tgz", "integrity": "sha512-Ieh06s48WnEYGcqHepdsJUIJUXpwH5o5vodAX+DK2JA/gjy4EbEcQZxw+uFfzysmKjiLXGYwNG3qDZsKVMcINQ==" }, - "js-tokens": { + "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, - "js-yaml": { + "node_modules/js-yaml": { "version": "3.14.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", - "requires": { + "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "minimatch": { + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "requires": { + "dependencies": { "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" } }, - "minimist": { + "node_modules/minimist": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" }, - "mkdirp": { + "node_modules/mkdirp": { "version": "0.5.5", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "requires": { + "dependencies": { "minimist": "^1.2.5" + }, + "bin": { + "mkdirp": "bin/cmd.js" } }, - "ms": { + "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, - "once": { + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { + "dependencies": { "wrappy": "1" } }, - "path-is-absolute": { + "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "engines": { + "node": ">=0.10.0" + } }, - "path-parse": { + "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, - "reflect-metadata": { + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/react": { + "version": "16.14.0", + "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", + "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "16.14.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz", + "integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==", + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "scheduler": "^0.19.1" + }, + "peerDependencies": { + "react": "^16.14.0" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT", + "peer": true + }, + "node_modules/reflect-metadata": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" }, - "resolve": { + "node_modules/resolve": { "version": "1.17.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", - "requires": { + "dependencies": { "path-parse": "^1.0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "semver": { + "node_modules/scheduler": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz", + "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==", + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "node_modules/semver": { "version": "5.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==" + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "bin": { + "semver": "bin/semver" + } }, - "sprintf-js": { + "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, - "supports-color": { + "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { + "dependencies": { "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" } }, - "tslib": { + "node_modules/tslib": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==" }, - "tslint": { + "node_modules/tslint": { "version": "6.1.3", "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz", "integrity": "sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==", - "requires": { + "deprecated": "TSLint has been deprecated in favor of ESLint. Please see https://github.com/palantir/tslint/issues/4534 for more information.", + "dependencies": { "@babel/code-frame": "^7.0.0", "builtin-modules": "^1.1.1", "chalk": "^2.3.0", @@ -366,143 +546,128 @@ "semver": "^5.3.0", "tslib": "^1.13.0", "tsutils": "^2.29.0" + }, + "bin": { + "tslint": "bin/tslint" + }, + "engines": { + "node": ">=4.8.0" + }, + "peerDependencies": { + "typescript": ">=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >=3.0.0-dev || >= 3.1.0-dev || >= 3.2.0-dev || >= 4.0.0-dev" } }, - "tslint-config-airbnb": { + "node_modules/tslint-config-airbnb": { "version": "5.11.2", "resolved": "https://registry.npmjs.org/tslint-config-airbnb/-/tslint-config-airbnb-5.11.2.tgz", "integrity": "sha512-mUpHPTeeCFx8XARGG/kzYP4dPSOgoCqNiYbGHh09qTH8q+Y1ghsOgaeZKYYQT7IyxMos523z/QBaiv2zKNBcow==", - "requires": { + "dependencies": { "tslint-consistent-codestyle": "^1.14.1", "tslint-eslint-rules": "^5.4.0", "tslint-microsoft-contrib": "~5.2.1" + }, + "peerDependencies": { + "tslint": "^5.11.0" } }, - "tslint-consistent-codestyle": { + "node_modules/tslint-consistent-codestyle": { "version": "1.16.0", "resolved": "https://registry.npmjs.org/tslint-consistent-codestyle/-/tslint-consistent-codestyle-1.16.0.tgz", "integrity": "sha512-ebR/xHyMEuU36hGNOgCfjGBNYxBPixf0yU1Yoo6s3BrpBRFccjPOmIVaVvQsWAUAMdmfzHOCihVkcaMfimqvHw==", - "requires": { + "dependencies": { "@fimbul/bifrost": "^0.21.0", "tslib": "^1.7.1", "tsutils": "^2.29.0" + }, + "peerDependencies": { + "tslint": "^5.0.0", + "typescript": ">=2.1.4 || >=2.1.0-dev || >=2.2.0-dev || >=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >=3.0.0-dev || >=3.1.0-dev || >=3.2.0-dev || >=3.3.0-dev || >=3.4.0-dev" } }, - "tslint-eslint-rules": { + "node_modules/tslint-eslint-rules": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/tslint-eslint-rules/-/tslint-eslint-rules-5.4.0.tgz", "integrity": "sha512-WlSXE+J2vY/VPgIcqQuijMQiel+UtmXS+4nvK4ZzlDiqBfXse8FAvkNnTcYhnQyOTW5KFM+uRRGXxYhFpuBc6w==", - "requires": { + "dependencies": { "doctrine": "0.7.2", "tslib": "1.9.0", "tsutils": "^3.0.0" }, + "peerDependencies": { + "tslint": "^5.0.0", + "typescript": "^2.2.0 || ^3.0.0" + } + }, + "node_modules/tslint-eslint-rules/node_modules/tslib": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.0.tgz", + "integrity": "sha512-f/qGG2tUkrISBlQZEjEqoZ3B2+npJjIf04H1wuAv9iA8i04Icp+61KRXxFdha22670NJopsZCIjhC3SnjPRKrQ==" + }, + "node_modules/tslint-eslint-rules/node_modules/tsutils": { + "version": "3.17.1", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz", + "integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==", "dependencies": { - "tslib": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.0.tgz", - "integrity": "sha512-f/qGG2tUkrISBlQZEjEqoZ3B2+npJjIf04H1wuAv9iA8i04Icp+61KRXxFdha22670NJopsZCIjhC3SnjPRKrQ==" - }, - "tsutils": { - "version": "3.17.1", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz", - "integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==", - "requires": { - "tslib": "^1.8.1" - } - } + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" } }, - "tslint-microsoft-contrib": { + "node_modules/tslint-microsoft-contrib": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/tslint-microsoft-contrib/-/tslint-microsoft-contrib-5.2.1.tgz", "integrity": "sha512-PDYjvpo0gN9IfMULwKk0KpVOPMhU6cNoT9VwCOLeDl/QS8v8W2yspRpFFuUS7/c5EIH/n8ApMi8TxJAz1tfFUA==", - "requires": { + "deprecated": "TSLint has been deprecated in favor of ESLint. Please see https://github.com/palantir/tslint/issues/4534 for more information.", + "dependencies": { "tsutils": "^2.27.2 <2.29.0" }, + "peerDependencies": { + "tslint": "^5.1.0", + "typescript": "^2.1.0 || ^3.0.0" + } + }, + "node_modules/tslint-microsoft-contrib/node_modules/tsutils": { + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.28.0.tgz", + "integrity": "sha512-bh5nAtW0tuhvOJnx1GLRn5ScraRLICGyJV5wJhtRWOLsxW70Kk5tZtpK3O/hW6LDnqKS9mlUMPZj9fEMJ0gxqA==", "dependencies": { - "tsutils": { - "version": "2.28.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.28.0.tgz", - "integrity": "sha512-bh5nAtW0tuhvOJnx1GLRn5ScraRLICGyJV5wJhtRWOLsxW70Kk5tZtpK3O/hW6LDnqKS9mlUMPZj9fEMJ0gxqA==", - "requires": { - "tslib": "^1.8.1" - } - } + "tslib": "^1.8.1" + }, + "peerDependencies": { + "typescript": ">=2.1.0 || >=2.1.0-dev || >=2.2.0-dev || >=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >= 3.0.0-dev || >= 3.1.0-dev" } }, - "tsutils": { + "node_modules/tsutils": { "version": "2.29.0", "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", - "requires": { + "dependencies": { "tslib": "^1.8.1" + }, + "peerDependencies": { + "typescript": ">=2.1.0 || >=2.1.0-dev || >=2.2.0-dev || >=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >= 3.0.0-dev || >= 3.1.0-dev" } }, - "typescript": { + "node_modules/typescript": { "version": "3.9.7", "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz", "integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==", - "dev": true + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } }, - "wrappy": { + "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "@next/swc-darwin-arm64": { - "version": "14.2.15", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.15.tgz", - "integrity": "sha512-Rvh7KU9hOUBnZ9TJ28n2Oa7dD9cvDBKua9IKx7cfQQ0GoYUwg9ig31O2oMwH3wm+pE3IkAQ67ZobPfEgurPZIA==", - "optional": true - }, - "@next/swc-darwin-x64": { - "version": "14.2.15", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.15.tgz", - "integrity": "sha512-5TGyjFcf8ampZP3e+FyCax5zFVHi+Oe7sZyaKOngsqyaNEpOgkKB3sqmymkZfowy3ufGA/tUgDPPxpQx931lHg==", - "optional": true - }, - "@next/swc-linux-arm64-gnu": { - "version": "14.2.15", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.15.tgz", - "integrity": "sha512-3Bwv4oc08ONiQ3FiOLKT72Q+ndEMyLNsc/D3qnLMbtUYTQAmkx9E/JRu0DBpHxNddBmNT5hxz1mYBphJ3mfrrw==", - "optional": true - }, - "@next/swc-linux-arm64-musl": { - "version": "14.2.15", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.15.tgz", - "integrity": "sha512-k5xf/tg1FBv/M4CMd8S+JL3uV9BnnRmoe7F+GWC3DxkTCD9aewFRH1s5rJ1zkzDa+Do4zyN8qD0N8c84Hu96FQ==", - "optional": true - }, - "@next/swc-linux-x64-gnu": { - "version": "14.2.15", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.15.tgz", - "integrity": "sha512-kE6q38hbrRbKEkkVn62reLXhThLRh6/TvgSP56GkFNhU22TbIrQDEMrO7j0IcQHcew2wfykq8lZyHFabz0oBrA==", - "optional": true - }, - "@next/swc-linux-x64-musl": { - "version": "14.2.15", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.15.tgz", - "integrity": "sha512-PZ5YE9ouy/IdO7QVJeIcyLn/Rc4ml9M2G4y3kCM9MNf1YKvFY4heg3pVa/jQbMro+tP6yc4G2o9LjAz1zxD7tQ==", - "optional": true - }, - "@next/swc-win32-arm64-msvc": { - "version": "14.2.15", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.15.tgz", - "integrity": "sha512-2raR16703kBvYEQD9HNLyb0/394yfqzmIeyp2nDzcPV4yPjqNUG3ohX6jX00WryXz6s1FXpVhsCo3i+g4RUX+g==", - "optional": true - }, - "@next/swc-win32-ia32-msvc": { - "version": "14.2.15", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.15.tgz", - "integrity": "sha512-fyTE8cklgkyR1p03kJa5zXEaZ9El+kDNM5A+66+8evQS5e/6v0Gk28LqA0Jet8gKSOyP+OTm/tJHzMlGdQerdQ==", - "optional": true - }, - "@next/swc-win32-x64-msvc": { - "version": "14.2.15", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.15.tgz", - "integrity": "sha512-SzqGbsLsP9OwKNUG9nekShTwhj6JSB9ZLMWQ8g1gG6hdE5gQLncbnbymrwy2yVmH9nikSLYRYxYMFu78Ggp7/g==", - "optional": true } } } diff --git a/package.json b/package.json index c3401c5..a3465c4 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,13 @@ { "name": "react-deep-tree", - "version": "0.0.6", + "version": "0.1.0", "license": "LGPL-3.0", "author": "LinksPlatform (https://github.com/linksplatform)", "repository": { "type": "git", "url": "git+https://github.com/linksplatform/react-deep-tree.git" }, - "description": "An UI component for tree rendering.", + "description": "A React component for rendering infinite depth tree structures with navigation, search, and keyboard controls.", "dependencies": { "debug": "^4.1.1", "tslint": "^6.1.2", diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..a096300 --- /dev/null +++ b/styles.css @@ -0,0 +1,88 @@ +/* Deep Tree React Component Styles */ + +.deep-tree { + font-family: Verdana, sans-serif, Calibri; + font-size: 15px; + line-height: 1.5em; + background-color: #1F1F1F; + color: #959595; + padding: 20px; + min-height: 100vh; +} + +.deep-tree-search { + position: fixed; + top: 20px; + left: 20px; + right: 20px; + z-index: 1000; +} + +.deep-tree-search input { + width: 100%; + padding: 10px 15px; + background-color: #2A2A2A; + border: none; + border-radius: 4px; + color: #fff; + font-size: 16px; + box-shadow: 0 2px 10px rgba(0,0,0,0.3); + outline: none; +} + +.deep-tree-search input:focus { + box-shadow: 0 2px 10px rgba(234, 117, 0, 0.3); +} + +.deep-tree-content { + transition: margin-top 0.3s ease-in-out; +} + +.deep-tree-list { + padding: 0; + margin: 0; +} + +.deep-tree-item { + list-style: none; + margin: 2px 0; +} + +.deep-tree-node { + cursor: pointer; + padding: 4px 8px; + background-color: transparent; + color: inherit; + transition: all 0.3s ease-in-out; + border-radius: 3px; +} + +.deep-tree-node:hover { + background-color: rgba(234, 117, 0, 0.1); +} + +.deep-tree-node.selected { + background-color: #EA7500; + color: #fff; +} + +.deep-tree-children { + margin-left: 20px; +} + +@media (max-width: 600px) { + .deep-tree { + font-size: 13px; + padding: 10px; + } + + .deep-tree-search { + left: 10px; + right: 10px; + top: 10px; + } + + .deep-tree-children { + margin-left: 15px; + } +} \ No newline at end of file