https://github.com/riad-azz/react-tree-selector
A simple React tree component with multi-select checkboxes, featuring hierarchical selection logic (parent selection overrides descendants). Built using shadcn/ui.
https://github.com/riad-azz/react-tree-selector
checkbox hierarchical-data multi-select next next-js react react-component shadcn tree-selection tree-view typescript ui-component
Last synced: 9 days ago
JSON representation
A simple React tree component with multi-select checkboxes, featuring hierarchical selection logic (parent selection overrides descendants). Built using shadcn/ui.
- Host: GitHub
- URL: https://github.com/riad-azz/react-tree-selector
- Owner: riad-azz
- Created: 2025-03-26T02:19:52.000Z (about 1 year ago)
- Default Branch: main
- Last Pushed: 2025-03-26T09:16:23.000Z (about 1 year ago)
- Last Synced: 2025-03-26T10:26:06.598Z (about 1 year ago)
- Topics: checkbox, hierarchical-data, multi-select, next, next-js, react, react-component, shadcn, tree-selection, tree-view, typescript, ui-component
- Language: TypeScript
- Homepage: https://react-tree-selector.vercel.app
- Size: 92.8 KB
- Stars: 1
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# React Tree Selector Component
A customizable and accessible tree view component for React, built with `shadcn/ui` and `lucide-react`. It allows users to display and select items from hierarchical data structures using checkboxes.
## Features
- **Hierarchical Data Display:** Renders nested tree structures.
- **Node Selection:** Allows single or multiple node selection via checkboxes.
- **Expand/Collapse:** Supports expanding and collapsing parent nodes to show/hide children.
- **Indeterminate State:** Checkboxes accurately reflect the selection state of descendant nodes (checked, unchecked, indeterminate).
- **Two Selection Modes:**
- **Standard Mode:** Selecting a parent automatically selects all its descendants. Unselecting a parent unselects descendants.
- **`topLevelOnly` Mode:** Selecting a node only selects _that_ node. Selecting a child node _does not_ affect the parent's selection status. Ensures only the highest selected node in any given branch is part of the final `value`.
- **Accessibility:** Implements ARIA attributes (`role="tree"`, `role="treeitem"`, `aria-selected`, `aria-expanded`, `aria-level`, `aria-disabled`) for better screen reader support.
- **Customizable:** Built with Tailwind CSS via `shadcn/ui`, allowing for easy style customization.
- **Controlled Component:** Selection state is managed via `value` and `onChange` props.
- **Duplicate ID Warning:** Includes a development-mode warning if duplicate `TreeNode` IDs are detected.
## Dependencies
- **React** (v16.8+)
- **`shadcn/ui` Components:**
- `Button`
- `Checkbox`
- _(Implies **Tailwind CSS** is set up in your project)_
- **`lucide-react`:** For icons (ChevronRight, ChevronDown).
- **`clsx` & `tailwind-merge`:** (Assumed, for the `cn` utility function). Make sure you have a `cn` utility configured, typically like this:
```typescript
// lib/utils.ts
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
```
## Installation / Setup
This component is provided as source code and assumes you have a React project set up, likely with `shadcn/ui` already configured.
1. **Copy the Code:** Copy the `TreeSelector.tsx` (or `.jsx`) file provided above into your project's components directory (e.g., `src/components/TreeSelector.tsx`).
2. **Adapt Imports:** Adjust the import paths for `Button`, `Checkbox`, and the `cn` utility to match your project structure. For example, if your `shadcn/ui` components are in `src/components/ui` and utils in `src/lib`, the default imports might work:
```typescript
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import { cn } from "@/lib/utils";
```
If your structure is different, update these paths accordingly.
3. **Install Dependencies:** Ensure you have the necessary dependencies installed:
```bash
npm install lucide-react clsx tailwind-merge
# or
yarn add lucide-react clsx tailwind-merge
# Ensure shadcn/ui Button & Checkbox are added:
npx shadcn-ui@latest add button checkbox
```
## Usage
Import the `TreeSelector` component and provide it with the required props. You'll need to manage the selection state yourself using the `value` and `onChange` props.
```typescript jsx
"use client"; // Required if using in Next.js App Router
import React, { useState } from "react";
import { TreeSelector, type TreeNode } from "@/components/TreeSelector"; // Adjust import path
// Sample hierarchical data
const sampleTreeData: TreeNode[] = [
{
id: "node-1",
label: "Documents",
children: [
{ id: "node-1-1", label: "Work" },
{
id: "node-1-2",
label: "Personal",
children: [
{ id: "node-1-2-1", label: "Vacation Photos" },
{ id: "node-1-2-2", label: "Recipes" },
],
},
],
},
{
id: "node-2",
label: "Downloads",
children: [
{ id: "node-2-1", label: "Software" },
{ id: "node-2-2", label: "Torrents" },
],
},
{ id: "node-3", label: "Music" },
];
function MyFeature() {
// State to hold the IDs of selected nodes
const [selectedNodeIds, setSelectedNodeIds] = useState([
"node-1-2-1", // Example: Initially select 'Vacation Photos'
]);
const [isTopLevelOnly, setIsTopLevelOnly] = useState(false);
const handleSelectionChange = (newSelectedIds: string[]) => {
console.log("Selected IDs:", newSelectedIds);
setSelectedNodeIds(newSelectedIds);
};
return (
{/* Optional: Toggle for selection mode */}
setIsTopLevelOnly(e.target.checked)}
/>
Use topLevelOnly Mode
Current Selection:
{selectedNodeIds.length > 0 ? (
{selectedNodeIds.map((id) => (
-
{id}
))}
) : (
No nodes selected.
)}
);
}
export default MyFeature;
```
## API
### `` Props
| Prop | Type | Required | Default | Description |
| -------------- | --------------------------------- | -------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `treeData` | `TreeNode[]` | Yes | - | An array of root `TreeNode` objects representing the tree structure. |
| `value` | `string[]` | Yes | - | An array of strings representing the IDs of the currently selected nodes. This makes it a controlled component. |
| `onChange` | `(selectedIds: string[]) => void` | Yes | - | Callback function invoked when the selection changes. It receives an array of the newly selected node IDs. |
| `topLevelOnly` | `boolean` | No | `false` | If `true`, only the highest selected node in a branch is retained in the `value`. Selecting a child does not affect the parent. See below. |
| `className` | `string` | No | `undefined` | Optional CSS class name(s) to apply to the root `div` element of the component for custom styling. |
### `TreeNode` Type
Each node in the `treeData` array must conform to this structure:
```typescript
type TreeNode = {
id: string; // Must be unique across the entire tree
label: string;
children?: TreeNode[]; // Optional array of child nodes
};
```
**Important:** Ensure that every `id` within the `treeData` is unique across the entire tree structure. Duplicate IDs will lead to unpredictable behavior and trigger a warning in development mode.
## Selection Modes Explained
### Standard Mode (`topLevelOnly = false`, Default)
- **Selection:** Checking a node selects itself and _all_ its descendants.
- **Unselection:** Unchecking a node unselects itself and _all_ its descendants.
- **Indeterminate State:** A parent checkbox appears indeterminate (usually a dash or square) if _some_ but not all of its descendants are selected.
- **Disabled State:** If a node's ancestor is selected, that node's checkbox becomes checked and disabled, as it's implicitly selected via its parent.
### `topLevelOnly` Mode (`topLevelOnly = true`)
This mode is useful when you only want the user to select the _most specific_ applicable category, preventing implicit selection of entire subtrees.
- **Selection:** Checking a node selects _only_ that specific node. If its parent was previously selected, the parent might become unselected depending on how `onChange` updates the `value` based on the component's internal logic designed to keep only top-level selections. When a node is checked, the component ensures none of its descendants remain in the selection set.
- **Unselection:** Unchecking a node unselects _only_ that specific node.
- **Indeterminate State:** The indeterminate state still reflects if any descendant is selected _independently_.
- **Disabled State:** A node's checkbox becomes disabled if an ancestor is selected, but it _won't_ be automatically checked. Its state depends on whether it was independently selected before the ancestor was.
- **Value Processing:** The component actively processes the `value` array. If both a parent and its child are somehow included in the `value` prop initially, the component will automatically remove the child ID from the effective selection set when `topLevelOnly` is true, ensuring only the parent (the "top level" selection in that branch) remains.
## Styling
The component relies on Tailwind CSS classes, primarily through the `shadcn/ui` components (`Button`, `Checkbox`) and internal layout `div`s.
- Use the `className` prop on `` to add styles (like `width`, `max-height`, `border`, etc.) to the main container.
- To customize the appearance of buttons, checkboxes, or text, you can either:
- Modify the base styles of your `shadcn/ui` components project-wide.
- (Advanced) Modify the `TreeSelector.tsx` component directly to change specific classes if needed, though this makes future updates harder.
## Accessibility
- The main container has `role="tree"`.
- Each node item has `role="treeitem"`.
- `aria-level` indicates the depth of each node (starting at 1).
- `aria-selected` indicates if the node itself is selected.
- `aria-expanded` is present on nodes with children, indicating their expanded/collapsed state.
- `aria-disabled` indicates if interaction with a node (specifically its checkbox) is disabled (e.g., because an ancestor is selected).
- Checkboxes have `aria-label` derived from the node's `label` for clarity.