-
Notifications
You must be signed in to change notification settings - Fork 29
feat: add TreeView Component #2191
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
JoshMK
wants to merge
26
commits into
develop
Choose a base branch
from
feat-tree
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 24 commits
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
fe892f3
feat: initial treeview code
JoshMK 67baefe
chore: separate components
JoshMK e3b8e65
feat: add props
JoshMK 10c4469
feat: add onaction prop
JoshMK 5c4bb0b
feat: begin styling
JoshMK 97596f1
feat: begin using cauldron checkbox
JoshMK 6f49217
fix: retain state for checkboxes
JoshMK 06d1f2a
fix: re-add textvalue for no checkboxes
JoshMK 1c5a415
fix: refactor checkbox code
JoshMK d8fe27e
fix: checkbox bug
JoshMK 085f35c
feat: add single selection logic
JoshMK af8f246
fix: clean up props
JoshMK 29cfa1a
fix: style adjustments
JoshMK d3458b9
test: add tests
JoshMK b60c7f7
fix: code cleanup
JoshMK 5ac2699
feat: reduce complexity
JoshMK 439e717
fix: simplify code
JoshMK a3d8e67
test: add more tests
JoshMK 2edd59a
fix: update docs, bake onaction fix into component
JoshMK 1f1fb1a
fix: handling logic bug
JoshMK 64785b6
feat: add mutation observer to sync checkbox
JoshMK a316c7b
fix: use isSelected, modify checkbox
JoshMK 5c15c61
fix: onAction handling
JoshMK c1a4800
chore: update docs
JoshMK b9003b7
fix: doc updates
JoshMK 7bec2fa
fix: don't need this
JoshMK File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,155 @@ | ||
| --- | ||
| title: TreeView | ||
| description: A component that displays a hierarchical tree structure. | ||
| source: https://github.com/dequelabs/cauldron/tree/develop/packages/react/src/components/TreeView/TreeView.tsx | ||
| --- | ||
|
|
||
| import { TreeView } from '@deque/cauldron-react'; | ||
|
|
||
| ```jsx | ||
| import { TreeView, type TreeViewFileType } from '@deque/cauldron-react'; | ||
| ``` | ||
|
|
||
| ## Examples | ||
|
|
||
| ### Single Selection | ||
|
|
||
| Allows only one tree item to be selected at a time. | ||
|
|
||
| ```jsx example | ||
| function SingleSelectionTreeView() { | ||
| const items = [ | ||
| { | ||
| id: '1', | ||
| textValue: 'Documents', | ||
| children: [{ id: '2', textValue: 'Project' }] | ||
| }, | ||
| { | ||
| id: '3', | ||
| textValue: 'Photos', | ||
| children: [{ id: '4', textValue: 'Image 1' }] | ||
| } | ||
| ]; | ||
| return <TreeView selectionMode="single" items={items} />; | ||
| } | ||
| ``` | ||
|
|
||
| ### Multiple Selection | ||
|
|
||
| Allows multiple tree items to be selected at once. | ||
|
|
||
| ```jsx example | ||
| function MultipleSelectionTreeView() { | ||
| const items = [ | ||
| { | ||
| id: '1', | ||
| textValue: 'Documents', | ||
| children: [{ id: '2', textValue: 'Project' }] | ||
| }, | ||
| { | ||
| id: '3', | ||
| textValue: 'Photos', | ||
| children: [{ id: '4', textValue: 'Image 1' }] | ||
| } | ||
| ]; | ||
| return <TreeView selectionMode="multiple" items={items} />; | ||
JoshMK marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| ``` | ||
|
|
||
| ### Default Expanded | ||
|
|
||
| Pass keys into the `defaultExpandedKeys` prop array to expand them by default. | ||
|
|
||
| ```jsx example | ||
| function DefaultExpandedTreeView() { | ||
| const items = [ | ||
| { | ||
| id: '1', | ||
| textValue: 'Documents', | ||
| children: [{ id: '2', textValue: 'Project' }] | ||
| }, | ||
| { | ||
| id: '3', | ||
| textValue: 'Photos', | ||
| children: [{ id: '4', textValue: 'Image 1' }] | ||
| }, | ||
| { | ||
| id: '5', | ||
| textValue: 'More Photos', | ||
| children: [ | ||
| { id: '6', textValue: 'Another Image 1' }, | ||
| { id: '7', textValue: 'Another Image 2' }, | ||
| { | ||
| id: '8', | ||
| textValue: 'Another Image 3', | ||
| children: [{ id: '9', textValue: 'Another Image 4' }] | ||
| } | ||
| ] | ||
| } | ||
| ]; | ||
| return <TreeView defaultExpandedKeys={['1', '5', '8']} items={items} />; | ||
JoshMK marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| ``` | ||
|
|
||
| ### Custom onAction | ||
|
|
||
| TreeView with a custom action handler for item selection. | ||
|
|
||
| ```jsx example | ||
| function ActionTreeView() { | ||
| const onAction = (key) => { | ||
| alert(`Selected item: ${key}`); | ||
| }; | ||
| const items = [ | ||
| { | ||
| id: '1', | ||
| textValue: 'Documents', | ||
| children: [{ id: '2', textValue: 'Project' }] | ||
| }, | ||
| { | ||
| id: '3', | ||
| textValue: 'Photos', | ||
| children: [{ id: '4', textValue: 'Image 1' }] | ||
| } | ||
| ]; | ||
| return <TreeView selectionMode="single" onAction={onAction} items={items} />; | ||
| } | ||
| ``` | ||
|
|
||
| ## Props | ||
|
|
||
| <ComponentProps | ||
| props={[ | ||
| { | ||
| name: 'ariaLabel', | ||
| type: 'string', | ||
| required: true, | ||
| description: 'Accessible label for the tree view component.' | ||
| }, | ||
| { | ||
| name: 'items', | ||
| type: 'TreeViewFileType[]', | ||
| required: true, | ||
| description: 'Array of tree items to display.' | ||
| }, | ||
| { | ||
| name: 'onAction', | ||
| type: '() => void', | ||
| defaultValue: 'undefined', | ||
| description: 'Callback fired when a tree item is activated.' | ||
| }, | ||
| { | ||
| name: 'selectionMode', | ||
| type: "'none' | 'single' | 'multiple'", | ||
| defaultValue: 'none', | ||
| description: | ||
| 'Selection mode: none, single, or multiple selection allowed. Checkboxes will not show if selectioMode is none' | ||
JoshMK marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| }, | ||
| { | ||
| name: 'defaultExpandedKeys', | ||
| type: 'string[]', | ||
| defaultValue: 'undefined', | ||
| description: 'Keys of tree items that should be expanded by default.' | ||
| } | ||
| ]} | ||
| /> | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -114,5 +114,6 @@ | |
| "webpack": "^5.102.1", | ||
| "webpack-cli": "^6.0.1", | ||
| "webpack-dev-server": "^5.2.2" | ||
| } | ||
| }, | ||
| "packageManager": "[email protected]+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
123 changes: 123 additions & 0 deletions
123
packages/react/src/components/TreeView/TreeView.test.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| import React from 'react'; | ||
| import { render, fireEvent } from '@testing-library/react'; | ||
| import TreeView, { TreeViewFileType } from '../../../src/components/TreeView'; | ||
|
|
||
| const items: TreeViewFileType[] = [ | ||
| { | ||
| id: '1', | ||
| textValue: 'TreeView', | ||
| children: [ | ||
| { id: '2', textValue: 'pizza' }, | ||
| { id: '3', textValue: 'pie' } | ||
| ] | ||
| }, | ||
| { | ||
| id: '4', | ||
| textValue: 'Another One', | ||
| children: [ | ||
| { id: '5', textValue: 'foo' }, | ||
| { id: '6', textValue: 'bar' } | ||
| ] | ||
| } | ||
| ]; | ||
|
|
||
| test('renders tree items', () => { | ||
| const { getByText } = render( | ||
| <TreeView ariaLabel="Test TreeView" items={items} /> | ||
| ); | ||
| expect(getByText('TreeView')).toBeInTheDocument(); | ||
| expect(getByText('Another One')).toBeInTheDocument(); | ||
| }); | ||
|
|
||
| test('selects a tree item on click', () => { | ||
| const { getByText } = render( | ||
| <TreeView ariaLabel="Test TreeView" items={items} selectionMode="single" /> | ||
| ); | ||
| const child1 = getByText('TreeView'); | ||
| fireEvent.click(child1); | ||
| expect(child1.closest('[aria-selected="true"]')).toBeTruthy(); | ||
| }); | ||
|
|
||
| test('selects a checkbox when clicked', () => { | ||
| const { getAllByLabelText } = render( | ||
| <TreeView | ||
| ariaLabel="Test TreeView" | ||
| items={items} | ||
| selectionMode="multiple" | ||
| /> | ||
| ); | ||
| const checkbox = getAllByLabelText('TreeView')[1]; | ||
| fireEvent.click(checkbox); | ||
| expect(checkbox).toBeChecked(); | ||
| }); | ||
|
|
||
| test('calls onAction when a tree item is activated', () => { | ||
| const onAction = jest.fn(); | ||
| const { getByText } = render( | ||
| <TreeView | ||
| ariaLabel="Test TreeView" | ||
| items={items} | ||
| onAction={onAction} | ||
| selectionMode="single" | ||
| /> | ||
| ); | ||
| fireEvent.click(getByText('TreeView')); | ||
| expect(onAction).toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| test('only one item can be selected in single selection mode', () => { | ||
| const { getByText } = render( | ||
| <TreeView ariaLabel="Test TreeView" items={items} selectionMode="single" /> | ||
| ); | ||
| const item1 = getByText('TreeView'); | ||
| const item2 = getByText('Another One'); | ||
| fireEvent.click(item1); | ||
| expect(item1.closest('[aria-selected="true"]')).toBeTruthy(); | ||
| fireEvent.click(item2); | ||
| expect(item2.closest('[aria-selected="true"]')).toBeTruthy(); | ||
| expect(item1.closest('[aria-selected="true"]')).toBeFalsy(); | ||
| }); | ||
|
|
||
| test('multiple items can be selected in multiple selection mode', () => { | ||
| const { getAllByLabelText } = render( | ||
| <TreeView | ||
| ariaLabel="Test TreeView" | ||
| items={items} | ||
| selectionMode="multiple" | ||
| /> | ||
| ); | ||
| const checkbox1 = getAllByLabelText('TreeView')[1]; | ||
| const checkbox2 = getAllByLabelText('Another One')[1]; | ||
| fireEvent.click(checkbox1); | ||
| fireEvent.click(checkbox2); | ||
| expect(checkbox1).toBeChecked(); | ||
| expect(checkbox2).toBeChecked(); | ||
| }); | ||
|
|
||
| test('children are rendered when treeview is open', () => { | ||
| const { getByText, queryByText } = render( | ||
| <TreeView | ||
| ariaLabel="Test TreeView" | ||
| items={items} | ||
| defaultExpandedKeys={['1']} | ||
| /> | ||
| ); | ||
| expect(getByText('pizza')).toBeInTheDocument(); | ||
| expect(getByText('pie')).toBeInTheDocument(); | ||
| expect(queryByText('foo')).toBeNull(); | ||
| expect(queryByText('bar')).toBeNull(); | ||
| }); | ||
|
|
||
| test('multiple treeviews can be open at once', () => { | ||
| const { getByText } = render( | ||
| <TreeView | ||
| ariaLabel="Test TreeView" | ||
| items={items} | ||
| defaultExpandedKeys={['1', '4']} | ||
| /> | ||
| ); | ||
| expect(getByText('pizza')).toBeInTheDocument(); | ||
| expect(getByText('pie')).toBeInTheDocument(); | ||
| expect(getByText('foo')).toBeInTheDocument(); | ||
| expect(getByText('bar')).toBeInTheDocument(); | ||
| }); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.