Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion packages/react-core/src/components/TreeView/TreeView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ export interface TreeViewProps {
onCheck?: (event: React.ChangeEvent<HTMLInputElement>, item: TreeViewDataItem, parentItem: TreeViewDataItem) => void;
/** Callback for item selection. */
onSelect?: (event: React.MouseEvent, item: TreeViewDataItem, parentItem: TreeViewDataItem) => void;
/** Callback for expanding a node with children. */
onExpand?: (event: React.MouseEvent, item: TreeViewDataItem, parentItem: TreeViewDataItem) => void;
/** Callback for collapsing a node with children. */
onCollapse?: (event: React.MouseEvent, item: TreeViewDataItem, parentItem: TreeViewDataItem) => void;
/** Internal. Parent item of a tree view list item. */
parentItem?: TreeViewDataItem;
/** Toolbar to display above the tree view. */
Expand Down Expand Up @@ -104,6 +108,8 @@ export const TreeView: React.FunctionComponent<TreeViewProps> = ({
parentItem,
onSelect,
onCheck,
onExpand,
onCollapse,
toolbar,
activeItems,
compareItems = (item, itemToCheck) => item.id === itemToCheck.id,
Expand All @@ -113,7 +119,7 @@ export const TreeView: React.FunctionComponent<TreeViewProps> = ({
}: TreeViewProps) => {
const treeViewList = (
<TreeViewList isNested={isNested} toolbar={toolbar}>
{data.map(item => (
{data.map((item) => (
<TreeViewListItem
key={item.id?.toString() || item.name?.toString()}
name={item.name}
Expand All @@ -124,6 +130,8 @@ export const TreeView: React.FunctionComponent<TreeViewProps> = ({
defaultExpanded={item.defaultExpanded !== undefined ? item.defaultExpanded : defaultAllExpanded}
onSelect={onSelect}
onCheck={onCheck}
onExpand={onExpand}
onCollapse={onCollapse}
hasCheckbox={item.hasCheckbox !== undefined ? item.hasCheckbox : hasCheckboxes}
checkProps={item.checkProps}
hasBadge={item.hasBadge !== undefined ? item.hasBadge : hasBadges}
Expand Down Expand Up @@ -153,6 +161,8 @@ export const TreeView: React.FunctionComponent<TreeViewProps> = ({
defaultAllExpanded={defaultAllExpanded}
onSelect={onSelect}
onCheck={onCheck}
onExpand={onExpand}
onCollapse={onCollapse}
activeItems={activeItems}
icon={icon}
expandedIcon={expandedIcon}
Expand Down
32 changes: 25 additions & 7 deletions packages/react-core/src/components/TreeView/TreeViewListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ export interface TreeViewListItemProps {
* from toggling.
*/
onSelect?: (event: React.MouseEvent, item: TreeViewDataItem, parent: TreeViewDataItem) => void;
/** Callback for expanding a node with children. */
onExpand?: (event: React.MouseEvent, item: TreeViewDataItem, parentItem: TreeViewDataItem) => void;
/** Callback for collapsing a node with children. */
onCollapse?: (event: React.MouseEvent, item: TreeViewDataItem, parentItem: TreeViewDataItem) => void;
/** Parent item of tree view item. */
parentItem?: TreeViewDataItem;
/** Title of a tree view item. */
Expand All @@ -74,6 +78,8 @@ const TreeViewListItemBase: React.FunctionComponent<TreeViewListItemProps> = ({
defaultExpanded = false,
children = null,
onSelect,
onExpand,
onCollapse,
onCheck,
hasCheckbox = false,
checkProps = {
Expand Down Expand Up @@ -117,6 +123,11 @@ const TreeViewListItemBase: React.FunctionComponent<TreeViewListItemProps> = ({
className={css(styles.treeViewNodeToggle)}
onClick={(evt: React.MouseEvent) => {
if (isSelectable || hasCheckbox) {
if (internalIsExpanded) {
onCollapse && onCollapse(evt, itemData, parentItem);
} else {
onExpand && onExpand(evt, itemData, parentItem);
}
setIsExpanded(!internalIsExpanded);
}
if (isSelectable) {
Expand All @@ -135,9 +146,9 @@ const TreeViewListItemBase: React.FunctionComponent<TreeViewListItemProps> = ({
<span className={css(styles.treeViewNodeCheck)}>
<input
type="checkbox"
onChange={evt => onCheck && onCheck(evt, itemData, parentItem)}
onClick={evt => evt.stopPropagation()}
ref={elem => elem && (elem.indeterminate = checkProps.checked === null)}
onChange={(evt) => onCheck && onCheck(evt, itemData, parentItem)}
onClick={(evt) => evt.stopPropagation()}
ref={(elem) => elem && (elem.indeterminate = checkProps.checked === null)}
{...checkProps}
checked={checkProps.checked === null ? false : checkProps.checked}
id={randomId}
Expand Down Expand Up @@ -192,22 +203,27 @@ const TreeViewListItemBase: React.FunctionComponent<TreeViewListItemProps> = ({
>
<div className={css(styles.treeViewContent)}>
<GenerateId prefix={isSelectable ? 'selectable-id' : 'checkbox-id'}>
{randomId => (
{(randomId) => (
<Component
className={css(
styles.treeViewNode,
children && (isSelectable || hasCheckbox) && styles.modifiers.selectable,
(!children || isSelectable) &&
activeItems &&
activeItems.length > 0 &&
activeItems.some(item => compareItems && item && compareItems(item, itemData))
activeItems.some((item) => compareItems && item && compareItems(item, itemData))
? styles.modifiers.current
: ''
)}
onClick={(evt: React.MouseEvent) => {
if (!hasCheckbox) {
onSelect && onSelect(evt, itemData, parentItem);
if (!isSelectable && children && evt.isDefaultPrevented() !== true) {
if (internalIsExpanded) {
onCollapse && onCollapse(evt, itemData, parentItem);
} else {
onExpand && onExpand(evt, itemData, parentItem);
}
setIsExpanded(!internalIsExpanded);
}
}
Expand Down Expand Up @@ -241,13 +257,13 @@ export const TreeViewListItem = React.memo(TreeViewListItemBase, (prevProps, nex
prevProps.activeItems &&
prevProps.activeItems.length > 0 &&
prevProps.activeItems.some(
item => prevProps.compareItems && item && prevProps.compareItems(item, prevProps.itemData)
(item) => prevProps.compareItems && item && prevProps.compareItems(item, prevProps.itemData)
);
const nextIncludes =
nextProps.activeItems &&
nextProps.activeItems.length > 0 &&
nextProps.activeItems.some(
item => nextProps.compareItems && item && nextProps.compareItems(item, nextProps.itemData)
(item) => nextProps.compareItems && item && nextProps.compareItems(item, nextProps.itemData)
);

if (prevIncludes || nextIncludes) {
Expand All @@ -262,6 +278,8 @@ export const TreeViewListItem = React.memo(TreeViewListItemBase, (prevProps, nex
prevProps.defaultExpanded !== nextProps.defaultExpanded ||
prevProps.onSelect !== nextProps.onSelect ||
prevProps.onCheck !== nextProps.onCheck ||
prevProps.onExpand !== nextProps.onExpand ||
prevProps.onCollapse !== nextProps.onCollapse ||
prevProps.hasCheckbox !== nextProps.hasCheckbox ||
prevProps.checkProps !== nextProps.checkProps ||
prevProps.hasBadge !== nextProps.hasBadge ||
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { render } from '@testing-library/react';
import { render, screen, fireEvent } from '@testing-library/react';
import { TreeView } from '../TreeView';
import { Button } from '@patternfly/react-core';
import { FolderIcon, FolderOpenIcon } from '@patternfly/react-icons';
Expand Down Expand Up @@ -148,6 +148,37 @@ describe('tree view', () => {
expect(asFragment()).toMatchSnapshot();
});

test('calls onExpand and onCollapse appropriately', () => {
const onExpand = jest.fn();
const onCollapse = jest.fn();
const { asFragment } = render(
<TreeView data={options} onSelect={jest.fn()} onExpand={onExpand} onCollapse={onCollapse} />
);
expect(onExpand).not.toHaveBeenCalled();
expect(onCollapse).not.toHaveBeenCalled();
expect(screen.queryByText('Application 3')).toBeNull();
expect(screen.getByText('Cost Management')).toBeInTheDocument();
fireEvent(
screen.getByText('Cost Management'),
new MouseEvent('click', {
bubbles: true,
cancelable: true
})
);
expect(onExpand).toHaveBeenCalled();
expect(onCollapse).not.toHaveBeenCalled();
expect(screen.getByText('Application 3')).toBeInTheDocument();
fireEvent(
screen.getByText('Cost Management'),
new MouseEvent('click', {
bubbles: true,
cancelable: true
})
);
expect(onCollapse).toHaveBeenCalled();
expect(screen.queryByText('Application 3')).toBeNull();
});

test('renders active successfully', () => {
const { asFragment } = render(<TreeView data={options} activeItems={active} onSelect={jest.fn()} />);
expect(asFragment()).toMatchSnapshot();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1905,14 +1905,14 @@ exports[`tree view renders checkboxes successfully 1`] = `
>
<label
class="pf-v5-c-tree-view__node pf-m-selectable"
for="checkbox-id18"
id="label-checkbox-id18"
for="checkbox-id25"
id="label-checkbox-id25"
>
<span
class="pf-v5-c-tree-view__node-container"
>
<button
aria-labelledby="label-checkbox-id18"
aria-labelledby="label-checkbox-id25"
class="pf-v5-c-tree-view__node-toggle"
tabindex="0"
>
Expand All @@ -1938,7 +1938,7 @@ exports[`tree view renders checkboxes successfully 1`] = `
class="pf-v5-c-tree-view__node-check"
>
<input
id="checkbox-id18"
id="checkbox-id25"
tabindex="0"
type="checkbox"
/>
Expand Down Expand Up @@ -1966,14 +1966,14 @@ exports[`tree view renders checkboxes successfully 1`] = `
>
<label
class="pf-v5-c-tree-view__node pf-m-selectable"
for="checkbox-id19"
id="label-checkbox-id19"
for="checkbox-id26"
id="label-checkbox-id26"
>
<span
class="pf-v5-c-tree-view__node-container"
>
<button
aria-labelledby="label-checkbox-id19"
aria-labelledby="label-checkbox-id26"
class="pf-v5-c-tree-view__node-toggle"
tabindex="-1"
>
Expand All @@ -1999,7 +1999,7 @@ exports[`tree view renders checkboxes successfully 1`] = `
class="pf-v5-c-tree-view__node-check"
>
<input
id="checkbox-id19"
id="checkbox-id26"
tabindex="-1"
type="checkbox"
/>
Expand All @@ -2024,14 +2024,14 @@ exports[`tree view renders checkboxes successfully 1`] = `
>
<label
class="pf-v5-c-tree-view__node pf-m-selectable"
for="checkbox-id20"
id="label-checkbox-id20"
for="checkbox-id27"
id="label-checkbox-id27"
>
<span
class="pf-v5-c-tree-view__node-container"
>
<button
aria-labelledby="label-checkbox-id20"
aria-labelledby="label-checkbox-id27"
class="pf-v5-c-tree-view__node-toggle"
tabindex="-1"
>
Expand All @@ -2057,7 +2057,7 @@ exports[`tree view renders checkboxes successfully 1`] = `
class="pf-v5-c-tree-view__node-check"
>
<input
id="checkbox-id20"
id="checkbox-id27"
tabindex="-1"
type="checkbox"
/>
Expand All @@ -2084,14 +2084,14 @@ exports[`tree view renders checkboxes successfully 1`] = `
>
<label
class="pf-v5-c-tree-view__node pf-m-selectable"
for="checkbox-id21"
id="label-checkbox-id21"
for="checkbox-id28"
id="label-checkbox-id28"
>
<span
class="pf-v5-c-tree-view__node-container"
>
<button
aria-labelledby="label-checkbox-id21"
aria-labelledby="label-checkbox-id28"
class="pf-v5-c-tree-view__node-toggle"
tabindex="-1"
>
Expand All @@ -2117,7 +2117,7 @@ exports[`tree view renders checkboxes successfully 1`] = `
class="pf-v5-c-tree-view__node-check"
>
<input
id="checkbox-id21"
id="checkbox-id28"
tabindex="-1"
type="checkbox"
/>
Expand All @@ -2142,14 +2142,14 @@ exports[`tree view renders checkboxes successfully 1`] = `
>
<label
class="pf-v5-c-tree-view__node pf-m-selectable"
for="checkbox-id22"
id="label-checkbox-id22"
for="checkbox-id29"
id="label-checkbox-id29"
>
<span
class="pf-v5-c-tree-view__node-container"
>
<button
aria-labelledby="label-checkbox-id22"
aria-labelledby="label-checkbox-id29"
class="pf-v5-c-tree-view__node-toggle"
tabindex="-1"
>
Expand All @@ -2175,7 +2175,7 @@ exports[`tree view renders checkboxes successfully 1`] = `
class="pf-v5-c-tree-view__node-check"
>
<input
id="checkbox-id22"
id="checkbox-id29"
tabindex="-1"
type="checkbox"
/>
Expand All @@ -2200,14 +2200,14 @@ exports[`tree view renders checkboxes successfully 1`] = `
>
<label
class="pf-v5-c-tree-view__node pf-m-selectable"
for="checkbox-id23"
id="label-checkbox-id23"
for="checkbox-id30"
id="label-checkbox-id30"
>
<span
class="pf-v5-c-tree-view__node-container"
>
<button
aria-labelledby="label-checkbox-id23"
aria-labelledby="label-checkbox-id30"
class="pf-v5-c-tree-view__node-toggle"
tabindex="-1"
>
Expand All @@ -2233,7 +2233,7 @@ exports[`tree view renders checkboxes successfully 1`] = `
class="pf-v5-c-tree-view__node-check"
>
<input
id="checkbox-id23"
id="checkbox-id30"
tabindex="-1"
type="checkbox"
/>
Expand Down Expand Up @@ -2672,15 +2672,15 @@ exports[`tree view renders individual flag options successfully 1`] = `
>
<label
class="pf-v5-c-tree-view__node pf-m-selectable"
for="checkbox-id36"
id="label-checkbox-id36"
for="checkbox-id43"
id="label-checkbox-id43"
tabindex="0"
>
<span
class="pf-v5-c-tree-view__node-container"
>
<button
aria-labelledby="label-checkbox-id36"
aria-labelledby="label-checkbox-id43"
class="pf-v5-c-tree-view__node-toggle"
tabindex="-1"
>
Expand All @@ -2706,7 +2706,7 @@ exports[`tree view renders individual flag options successfully 1`] = `
class="pf-v5-c-tree-view__node-check"
>
<input
id="checkbox-id36"
id="checkbox-id43"
tabindex="-1"
type="checkbox"
/>
Expand Down