{"id":26700377,"url":"https://github.com/riad-azz/react-tree-selector","last_synced_at":"2026-04-18T17:01:48.233Z","repository":{"id":284467397,"uuid":"955035107","full_name":"riad-azz/react-tree-selector","owner":"riad-azz","description":"A simple React tree component with multi-select checkboxes, featuring hierarchical selection logic (parent selection overrides descendants). Built using shadcn/ui.","archived":false,"fork":false,"pushed_at":"2025-03-26T09:16:23.000Z","size":95,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-26T10:26:06.598Z","etag":null,"topics":["checkbox","hierarchical-data","multi-select","next","next-js","react","react-component","shadcn","tree-selection","tree-view","typescript","ui-component"],"latest_commit_sha":null,"homepage":"https://react-tree-selector.vercel.app","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/riad-azz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2025-03-26T02:19:52.000Z","updated_at":"2025-03-26T09:22:20.000Z","dependencies_parsed_at":"2025-03-26T10:26:08.394Z","dependency_job_id":null,"html_url":"https://github.com/riad-azz/react-tree-selector","commit_stats":null,"previous_names":["riad-azz/react-tree-multi-selector","riad-azz/react-tree-selector"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/riad-azz/react-tree-selector","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/riad-azz%2Freact-tree-selector","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/riad-azz%2Freact-tree-selector/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/riad-azz%2Freact-tree-selector/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/riad-azz%2Freact-tree-selector/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/riad-azz","download_url":"https://codeload.github.com/riad-azz/react-tree-selector/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/riad-azz%2Freact-tree-selector/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31976805,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-18T16:27:12.723Z","status":"ssl_error","status_checked_at":"2026-04-18T16:27:11.140Z","response_time":103,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["checkbox","hierarchical-data","multi-select","next","next-js","react","react-component","shadcn","tree-selection","tree-view","typescript","ui-component"],"created_at":"2025-03-27T00:19:56.140Z","updated_at":"2026-04-18T17:01:48.227Z","avatar_url":"https://github.com/riad-azz.png","language":"TypeScript","readme":"# React Tree Selector Component\n\nA 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.\n\n## Features\n\n- **Hierarchical Data Display:** Renders nested tree structures.\n- **Node Selection:** Allows single or multiple node selection via checkboxes.\n- **Expand/Collapse:** Supports expanding and collapsing parent nodes to show/hide children.\n- **Indeterminate State:** Checkboxes accurately reflect the selection state of descendant nodes (checked, unchecked, indeterminate).\n- **Two Selection Modes:**\n  - **Standard Mode:** Selecting a parent automatically selects all its descendants. Unselecting a parent unselects descendants.\n  - **`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`.\n- **Accessibility:** Implements ARIA attributes (`role=\"tree\"`, `role=\"treeitem\"`, `aria-selected`, `aria-expanded`, `aria-level`, `aria-disabled`) for better screen reader support.\n- **Customizable:** Built with Tailwind CSS via `shadcn/ui`, allowing for easy style customization.\n- **Controlled Component:** Selection state is managed via `value` and `onChange` props.\n- **Duplicate ID Warning:** Includes a development-mode warning if duplicate `TreeNode` IDs are detected.\n\n## Dependencies\n\n- **React** (v16.8+)\n- **`shadcn/ui` Components:**\n  - `Button`\n  - `Checkbox`\n  - _(Implies **Tailwind CSS** is set up in your project)_\n- **`lucide-react`:** For icons (ChevronRight, ChevronDown).\n- **`clsx` \u0026 `tailwind-merge`:** (Assumed, for the `cn` utility function). Make sure you have a `cn` utility configured, typically like this:\n\n  ```typescript\n  // lib/utils.ts\n  import { type ClassValue, clsx } from \"clsx\";\n  import { twMerge } from \"tailwind-merge\";\n\n  export function cn(...inputs: ClassValue[]) {\n    return twMerge(clsx(inputs));\n  }\n  ```\n\n## Installation / Setup\n\nThis component is provided as source code and assumes you have a React project set up, likely with `shadcn/ui` already configured.\n\n1.  **Copy the Code:** Copy the `TreeSelector.tsx` (or `.jsx`) file provided above into your project's components directory (e.g., `src/components/TreeSelector.tsx`).\n2.  **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:\n    ```typescript\n    import { Button } from \"@/components/ui/button\";\n    import { Checkbox } from \"@/components/ui/checkbox\";\n    import { cn } from \"@/lib/utils\";\n    ```\n    If your structure is different, update these paths accordingly.\n3.  **Install Dependencies:** Ensure you have the necessary dependencies installed:\n    ```bash\n    npm install lucide-react clsx tailwind-merge\n    # or\n    yarn add lucide-react clsx tailwind-merge\n    # Ensure shadcn/ui Button \u0026 Checkbox are added:\n    npx shadcn-ui@latest add button checkbox\n    ```\n\n## Usage\n\nImport 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.\n\n```typescript jsx\n\"use client\"; // Required if using in Next.js App Router\n\nimport React, { useState } from \"react\";\nimport { TreeSelector, type TreeNode } from \"@/components/TreeSelector\"; // Adjust import path\n\n// Sample hierarchical data\nconst sampleTreeData: TreeNode[] = [\n  {\n    id: \"node-1\",\n    label: \"Documents\",\n    children: [\n      { id: \"node-1-1\", label: \"Work\" },\n      {\n        id: \"node-1-2\",\n        label: \"Personal\",\n        children: [\n          { id: \"node-1-2-1\", label: \"Vacation Photos\" },\n          { id: \"node-1-2-2\", label: \"Recipes\" },\n        ],\n      },\n    ],\n  },\n  {\n    id: \"node-2\",\n    label: \"Downloads\",\n    children: [\n      { id: \"node-2-1\", label: \"Software\" },\n      { id: \"node-2-2\", label: \"Torrents\" },\n    ],\n  },\n  { id: \"node-3\", label: \"Music\" },\n];\n\nfunction MyFeature() {\n  // State to hold the IDs of selected nodes\n  const [selectedNodeIds, setSelectedNodeIds] = useState\u003cstring[]\u003e([\n    \"node-1-2-1\", // Example: Initially select 'Vacation Photos'\n  ]);\n\n  const [isTopLevelOnly, setIsTopLevelOnly] = useState(false);\n\n  const handleSelectionChange = (newSelectedIds: string[]) =\u003e {\n    console.log(\"Selected IDs:\", newSelectedIds);\n    setSelectedNodeIds(newSelectedIds);\n  };\n\n  return (\n    \u003cdiv className=\"p-4 space-y-4\"\u003e\n      {/* Optional: Toggle for selection mode */}\n      \u003clabel className=\"flex items-center space-x-2\"\u003e\n        \u003cinput\n          type=\"checkbox\"\n          checked={isTopLevelOnly}\n          onChange={(e) =\u003e setIsTopLevelOnly(e.target.checked)}\n        /\u003e\n        \u003cspan\u003eUse topLevelOnly Mode\u003c/span\u003e\n      \u003c/label\u003e\n\n      \u003cTreeSelector\n        treeData={sampleTreeData}\n        value={selectedNodeIds}\n        onChange={handleSelectionChange}\n        topLevelOnly={isTopLevelOnly}\n        className=\"max-w-md\" // Optional: Add custom styling/sizing\n      /\u003e\n\n      \u003cdiv className=\"mt-4\"\u003e\n        \u003ch4 className=\"font-semibold\"\u003eCurrent Selection:\u003c/h4\u003e\n        {selectedNodeIds.length \u003e 0 ? (\n          \u003cul\u003e\n            {selectedNodeIds.map((id) =\u003e (\n              \u003cli key={id}\u003e\n                \u003ccode\u003e{id}\u003c/code\u003e\n              \u003c/li\u003e\n            ))}\n          \u003c/ul\u003e\n        ) : (\n          \u003cp className=\"text-muted-foreground\"\u003eNo nodes selected.\u003c/p\u003e\n        )}\n      \u003c/div\u003e\n    \u003c/div\u003e\n  );\n}\n\nexport default MyFeature;\n```\n\n## API\n\n### `\u003cTreeSelector /\u003e` Props\n\n| Prop           | Type                              | Required | Default     | Description                                                                                                                                |\n| -------------- | --------------------------------- | -------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------ |\n| `treeData`     | `TreeNode[]`                      | Yes      | -           | An array of root `TreeNode` objects representing the tree structure.                                                                       |\n| `value`        | `string[]`                        | Yes      | -           | An array of strings representing the IDs of the currently selected nodes. This makes it a controlled component.                            |\n| `onChange`     | `(selectedIds: string[]) =\u003e void` | Yes      | -           | Callback function invoked when the selection changes. It receives an array of the newly selected node IDs.                                 |\n| `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. |\n| `className`    | `string`                          | No       | `undefined` | Optional CSS class name(s) to apply to the root `div` element of the component for custom styling.                                         |\n\n### `TreeNode` Type\n\nEach node in the `treeData` array must conform to this structure:\n\n```typescript\ntype TreeNode = {\n  id: string; // Must be unique across the entire tree\n  label: string;\n  children?: TreeNode[]; // Optional array of child nodes\n};\n```\n\n**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.\n\n## Selection Modes Explained\n\n### Standard Mode (`topLevelOnly = false`, Default)\n\n- **Selection:** Checking a node selects itself and _all_ its descendants.\n- **Unselection:** Unchecking a node unselects itself and _all_ its descendants.\n- **Indeterminate State:** A parent checkbox appears indeterminate (usually a dash or square) if _some_ but not all of its descendants are selected.\n- **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.\n\n### `topLevelOnly` Mode (`topLevelOnly = true`)\n\nThis mode is useful when you only want the user to select the _most specific_ applicable category, preventing implicit selection of entire subtrees.\n\n- **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.\n- **Unselection:** Unchecking a node unselects _only_ that specific node.\n- **Indeterminate State:** The indeterminate state still reflects if any descendant is selected _independently_.\n- **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.\n- **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.\n\n## Styling\n\nThe component relies on Tailwind CSS classes, primarily through the `shadcn/ui` components (`Button`, `Checkbox`) and internal layout `div`s.\n\n- Use the `className` prop on `\u003cTreeSelector\u003e` to add styles (like `width`, `max-height`, `border`, etc.) to the main container.\n- To customize the appearance of buttons, checkboxes, or text, you can either:\n  - Modify the base styles of your `shadcn/ui` components project-wide.\n  - (Advanced) Modify the `TreeSelector.tsx` component directly to change specific classes if needed, though this makes future updates harder.\n\n## Accessibility\n\n- The main container has `role=\"tree\"`.\n- Each node item has `role=\"treeitem\"`.\n- `aria-level` indicates the depth of each node (starting at 1).\n- `aria-selected` indicates if the node itself is selected.\n- `aria-expanded` is present on nodes with children, indicating their expanded/collapsed state.\n- `aria-disabled` indicates if interaction with a node (specifically its checkbox) is disabled (e.g., because an ancestor is selected).\n- Checkboxes have `aria-label` derived from the node's `label` for clarity.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Friad-azz%2Freact-tree-selector","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Friad-azz%2Freact-tree-selector","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Friad-azz%2Freact-tree-selector/lists"}