hierarchical-folder-tree
Version:
A virtualized hierarchical folders list component for React with comprehensive icon support
302 lines (242 loc) • 7.83 kB
Markdown
A React component for displaying hierarchical folder structures with virtualization, keyboard navigation, and customizable styling.
- 🚀 **DOM Virtualization**: Only renders items visible in the viewport for optimal performance
- ⌨️ **Keyboard Navigation**: Full keyboard support (arrows, enter, escape)
- 🎨 **Customizable Styling**: Easily style and customize with Emotion
- 🔧 **Flexible API**: Render custom item content and icons
- 🌲 **Tree View**: Familiar folder tree navigation like in Windows/Mac
- 📦 **Small Bundle Size**: Lightweight with minimal dependencies
- 🎯 **Comprehensive Icon Support**: Multiple ways to provide icons from external sources
## Installation
```bash
npm install hierarchical-folders-list
# or
yarn add hierarchical-folders-list
```
## TypeScript Requirements
This package requires TypeScript configuration that includes ES2015+ features. Make sure your `tsconfig.json` includes:
```json
{
"compilerOptions": {
"target": "es2015",
"lib": ["dom", "dom.iterable", "es2015", "es2016", "es2017"],
"esModuleInterop": true,
"allowSyntheticDefaultImports": true
}
}
```
**Note**: If you see errors about `Set` or `Iterable`, ensure your `lib` array includes `"es2015"` or later.
```jsx
import React from 'react';
import { FolderList } from 'hierarchical-folders-list';
// Sample data
const folderData = [
{
id: 'folder-1',
name: 'Documents',
isFolder: true,
children: [
{
id: 'folder-1-1',
name: 'Work',
isFolder: true,
children: [
{ id: 'file-1-1-1', name: 'Report.pdf' },
{ id: 'file-1-1-2', name: 'Presentation.pptx' }
]
},
{ id: 'file-1-1', name: 'Resume.docx' }
]
},
{
id: 'folder-2',
name: 'Pictures',
isFolder: true,
children: [
{ id: 'file-2-1', name: 'Vacation.jpg' },
{ id: 'file-2-2', name: 'Family.png' }
]
}
];
const App = () => {
const handleItemClick = (item) => {
console.log('Item clicked:', item);
};
return (
<div style={{ height: '400px', width: '300px' }}>
<FolderList
data={folderData}
onItemClick={handleItemClick}
defaultExpandedIds={['folder-1']}
/>
</div>
);
};
export default App;
```
The component provides multiple flexible ways for consumers to provide icons from external sources:
```jsx
const data = [
{
id: 'file-1',
name: 'special-file.txt',
icon: <CustomIcon /> // Direct React element
}
];
```
```jsx
import { FolderList, createFileExtensionIconResolver } from 'hierarchical-folders-list';
const iconConfig = {
// Icon map with keys
iconMap: {
'js': <JavaScriptIcon />,
'ts': <TypeScriptIcon />,
'jsx': <ReactIcon />,
'css': <CSSIcon />,
'folder': <FolderIcon />,
},
// Automatic icon key resolution based on file extension
getIconKey: createFileExtensionIconResolver({
'html': 'web',
'md': 'markdown',
}),
// Default icons
defaultFolderIcon: <FolderIcon />,
defaultFileIcon: <FileIcon />,
};
<FolderList data={data} iconConfig={iconConfig} />
```
```jsx
const iconConfig = {
iconResolver: (item, isExpanded) => {
const isFolder = Boolean(item.isFolder || item.children?.length);
if (isFolder) {
return isExpanded ? <FolderOpenIcon /> : <FolderIcon />;
}
// Custom logic for files
if (item.name.endsWith('.js')) return <JavaScriptIcon />;
if (item.name.endsWith('.ts')) return <TypeScriptIcon />;
return <DefaultFileIcon />;
}
};
```
```jsx
const data = [
{
id: 'special-folder',
name: 'Special Folder',
isFolder: true,
iconKey: 'special' // Will look up in iconConfig.iconMap['special']
}
];
```
```jsx
<FolderList
data={data}
renderItemIcon={(item, isExpanded) => {
// Custom icon logic
return <CustomIcon />;
}}
/>
```
The component resolves icons in the following order:
1. **Direct icon** on item (`item.icon`)
2. **Legacy renderItemIcon** function
3. **Icon from iconMap** using `item.iconKey`
4. **Icon from iconMap** using `getIconKey()` result
5. **Icon from iconResolver** function
6. **Default icons** from config
7. **Built-in default** icons
## Keyboard Navigation
The component supports the following keyboard shortcuts:
- **Up/Down Arrows**: Navigate between items
- **Right Arrow**: Expand folder or navigate to first child
- **Left Arrow**: Collapse folder or navigate to parent
- **Enter**: Toggle folder expansion
- **Escape**: Collapse folder or navigate to parent
## Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `data` | `FolderItemData[]` | Required | Array of folder/file items to display |
| `onItemClick` | `(item: FolderItemData) => void` | - | Callback when an item is clicked |
| `onItemDoubleClick` | `(item: FolderItemData) => void` | - | Callback when an item is double-clicked |
| `onItemContextMenu` | `(item: FolderItemData, event: React.MouseEvent) => void` | - | Callback for context menu events |
| `renderItemContent` | `(item: FolderItemData, isExpanded: boolean) => React.ReactNode` | - | Custom renderer for item content |
| `renderItemIcon` | `(item: FolderItemData, isExpanded: boolean) => React.ReactNode` | - | Custom renderer for item icons (legacy) |
| `iconConfig` | `IconConfig` | - | Icon configuration object |
| `itemHeight` | `number` | `28` | Height of each item in pixels |
| `className` | `string` | - | Additional CSS class for the container |
| `style` | `React.CSSProperties` | - | Additional inline styles for the container |
| `defaultExpandedIds` | `string[]` | `[]` | IDs of items that should be expanded by default |
| `keyboard` | `Object` | See below | Keyboard navigation options |
| `virtualizationOptions` | `Object` | See below | Virtualization options |
### Keyboard Options
```js
{
enabled: true, // Enable keyboard navigation
expandOnEnter: true, // Expand folders with Enter key
collapseOnEscape: true, // Collapse folders with Escape key
navigateWithArrows: true // Navigate with arrow keys
}
```
```js
{
enabled: true, // Enable virtualization
overscan: 10 // Number of items to render outside viewport
}
```
```ts
interface FolderItemData {
id: string;
name: string;
children?: FolderItemData[];
isFolder?: boolean;
icon?: React.ReactNode;
iconKey?: string;
customData?: any;
}
interface IconConfig {
iconMap?: Record<string, React.ReactNode>;
iconResolver?: (item: FolderItemData, isExpanded: boolean) => React.ReactNode | null;
defaultFolderIcon?: React.ReactNode;
defaultFileIcon?: React.ReactNode;
getIconKey?: (item: FolderItemData) => string | null;
}
```
The package exports several utility functions to help with icon management:
```js
import {
createFileExtensionIconResolver,
getFileExtension,
resolveIcon
} from 'hierarchical-folders-list';
// Create an icon key resolver based on file extensions
const getIconKey = createFileExtensionIconResolver({
'html': 'web',
'md': 'markdown',
});
// Get file extension from filename
const extension = getFileExtension('file.tsx'); // 'tsx'
// Manually resolve icon (used internally)
const icon = resolveIcon(item, isExpanded, iconConfig);
```
To run the example locally:
1. Clone this repository
2. Build the package: `npm run build`
3. Serve the example: `npx http-server`
4. Navigate to `http://localhost:8080/example/index.html`
MIT