diff --git a/packages/react-core/src/components/TreeView/examples/TreeView.md b/packages/react-core/src/components/TreeView/examples/TreeView.md
index 710fb7e8366..9039d6c8857 100644
--- a/packages/react-core/src/components/TreeView/examples/TreeView.md
+++ b/packages/react-core/src/components/TreeView/examples/TreeView.md
@@ -11,1289 +11,76 @@ import { FolderIcon, FolderOpenIcon, EllipsisVIcon, ClipboardIcon, HamburgerIcon
### Default
-```js
-import React from 'react';
-import { TreeView, Button } from '@patternfly/react-core';
+```ts file='./TreeViewDefault.tsx'
-class DefaultTreeView extends React.Component {
- constructor(props) {
- super(props);
-
- this.state = { activeItems: [], allExpanded: null };
-
- this.onSelect = (evt, treeViewItem) => {
- // Ignore folders for selection
- if (treeViewItem && !treeViewItem.children) {
- this.setState({
- activeItems: [treeViewItem]
- });
- }
- };
-
- this.onToggle = (evt) => {
- const { allExpanded } = this.state;
- this.setState({
- allExpanded: allExpanded !== undefined ? !allExpanded : true
- });
- };
- }
-
- render() {
- const { activeItems, allExpanded } = this.state;
-
- const options = [
- {
- name: 'Application launcher',
- id: 'example1-AppLaunch',
- children: [
- {
- name: 'Application 1',
- id: 'example1-App1',
- children: [
- { name: 'Settings', id: 'example1-App1Settings' },
- { name: 'Current', id: 'example1-App1Current' }
- ]
- },
- {
- name: 'Application 2',
- id: 'example1-App2',
- children: [
- { name: 'Settings', id: 'example1-App2Settings' },
- {
- name: 'Loader',
- id: 'example1-App2Loader',
- children: [
- { name: 'Loading App 1', id: 'example1-LoadApp1' },
- { name: 'Loading App 2', id: 'example1-LoadApp2' },
- { name: 'Loading App 3', id: 'example1-LoadApp3' }
- ]
- }
- ]
- }
- ],
- defaultExpanded: true
- },
- {
- name: 'Cost management',
- id: 'example1-Cost',
- children: [
- {
- name: 'Application 3',
- id: 'example1-App3',
- children: [
- { name: 'Settings', id: 'example1-App3Settings' },
- { name: 'Current', id: 'example1-App3Current' }
- ]
- }
- ]
- },
- {
- name: 'Sources',
- id: 'example1-Sources',
- children: [
- { name: 'Application 4', id: 'example1-App4', children: [{ name: 'Settings', id: 'example1-App4Settings' }] }
- ]
- },
- {
- name: 'Really really really long folder name that overflows the container it is in',
- id: 'example1-Long',
- children: [{ name: 'Application 5', id: 'example1-App5' }]
- }
- ];
- return (
-
-
-
-
- );
- }
-}
```
### With separate selection and expansion
-The `hasSelectableNodes` modifier will separate the expansion and selection behaviors, allowing a parent node to be selected or deselected with toggling its expansion.
-
-```js
-import React from 'react';
-import { TreeView, Button } from '@patternfly/react-core';
-
-class SelectableNodesTreeView extends React.Component {
- constructor(props) {
- super(props);
+The `hasSelectableNodes` modifier will separate the expansion and selection behaviors, allowing a parent node to be selected or deselected without toggling its expansion.
- this.state = { activeItems: {} };
+```ts file='./TreeViewSelectionExpansion.tsx'
- this.onSelect = (evt, treeViewItem) => {
- this.setState({
- activeItems: [treeViewItem]
- });
- };
- }
-
- render() {
- const { activeItems, allExpanded } = this.state;
-
- const options = [
- {
- name: 'Application launcher',
- id: 'SelNodesTreeView-AppLaunch',
- children: [
- {
- name: 'Application 1',
- id: 'SelNodesTreeView-App1',
- children: [
- { name: 'Settings', id: 'SelNodesTreeView-App1Settings' },
- { name: 'Current', id: 'SelNodesTreeView-App1Current' }
- ]
- },
- {
- name: 'Application 2',
- id: 'SelNodesTreeView-App2',
- children: [
- { name: 'Settings', id: 'SelNodesTreeView-App2Settings' },
- {
- name: 'Loader',
- id: 'SelNodesTreeView-App2Loader',
- children: [
- { name: 'Loading App 1', id: 'SelNodesTreeView-LoadApp1' },
- { name: 'Loading App 2', id: 'SelNodesTreeView-LoadApp2' },
- { name: 'Loading App 3', id: 'SelNodesTreeView-LoadApp3' }
- ]
- }
- ]
- }
- ],
- defaultExpanded: true
- },
- {
- name: 'Cost management',
- id: 'SelNodesTreeView-Cost',
- children: [
- {
- name: 'Application 3',
- id: 'SelNodesTreeView-App3',
- children: [
- { name: 'Settings', id: 'SelNodesTreeView-App3Settings' },
- { name: 'Current', id: 'SelNodesTreeView-App3Current' }
- ]
- }
- ]
- },
- {
- name: 'Sources',
- id: 'SelNodesTreeView-Sources',
- children: [
- {
- name: 'Application 4',
- id: 'SelNodesTreeView-App4',
- children: [{ name: 'Settings', id: 'SelNodesTreeView-App4Settings' }]
- }
- ]
- },
- {
- name: 'Really really really long folder name that overflows the container it is in',
- id: 'SelNodesTreeView-Long',
- children: [{ name: 'Application 5', id: 'SelNodesTreeView-App5' }]
- }
- ];
- return (
-
- );
- }
-}
```
### With search
-```js
-import React from 'react';
-import { Toolbar, ToolbarContent, ToolbarItem, TreeView, TreeViewSearch } from '@patternfly/react-core';
-
-class SearchTreeView extends React.Component {
- constructor(props) {
- super(props);
-
- this.options = [
- {
- name: 'Application launcher',
- id: 'example2-AppLaunch',
- children: [
- {
- name: 'Application 1',
- id: 'example2-App1',
- children: [
- { name: 'Settings', id: 'example2-App1Settings' },
- { name: 'Current', id: 'example2-App1Current' }
- ]
- },
- {
- name: 'Application 2',
- id: 'example2-App2',
- children: [
- { name: 'Settings', id: 'example2-App2Settings' },
- {
- name: 'Loader',
- id: 'example2-App2Loader',
- children: [
- { name: 'Loading App 1', id: 'example2-LoadApp1' },
- { name: 'Loading App 2', id: 'example2-LoadApp2' },
- { name: 'Loading App 3', id: 'example2-LoadApp3' }
- ]
- }
- ]
- }
- ],
- defaultExpanded: true
- },
- {
- name: 'Cost management',
- id: 'example2-Cost',
- children: [
- {
- name: 'Application 3',
- id: 'example2-App3',
- children: [
- { name: 'Settings', id: 'example2-App3Settings' },
- { name: 'Current', id: 'example2-App3Current' }
- ]
- }
- ]
- },
- {
- name: 'Sources',
- id: 'example2-Sources',
- children: [
- {
- name: 'Application 4',
- id: 'example2-App4',
- children: [{ name: 'Settingexample2-s', id: 'example2-App4Settings' }]
- }
- ]
- },
- {
- name: 'Really really really long folder name that overflows the container it is in',
- id: 'example2-Long',
- children: [{ name: 'Application 5', id: 'example2-App5' }]
- }
- ];
-
- this.state = { activeItems: {}, filteredItems: this.options, isFiltered: null };
-
- this.onSelect = (evt, treeViewItem) => {
- // Ignore folders for selection
- if (treeViewItem && !treeViewItem.children) {
- this.setState({
- activeItems: [treeViewItem]
- });
- }
- };
-
- this.onSearch = (evt) => {
- const input = evt.target.value;
- if (input === '') {
- this.setState({ filteredItems: this.options, isFiltered: false });
- } else {
- const filtered = this.options
- .map((opt) => Object.assign({}, opt))
- .filter((item) => this.filterItems(item, input));
- this.setState({ filteredItems: filtered, isFiltered: true });
- }
- };
+```ts file='./TreeViewWithSearch.tsx'
- this.filterItems = (item, input) => {
- if (item.name.toLowerCase().includes(input.toLowerCase())) {
- return true;
- }
-
- if (item.children) {
- return (
- (item.children = item.children
- .map((opt) => Object.assign({}, opt))
- .filter((child) => this.filterItems(child, input))).length > 0
- );
- }
- };
- }
-
- render() {
- const { activeItems, filteredItems, isFiltered } = this.state;
-
- const toolbar = (
-
-
-
-
-
-
-
- );
-
- return (
-
- );
- }
-}
```
### With checkboxes
-```js
-import React from 'react';
-import { TreeView } from '@patternfly/react-core';
-
-class CheckboxTreeView extends React.Component {
- constructor(props) {
- super(props);
- this.options = [
- {
- name: 'Application launcher',
- id: 'example3-AppLaunch',
- checkProps: { 'aria-label': 'app-launcher-check', checked: false },
- children: [
- {
- name: 'Application 1',
- id: 'example3-App1',
- checkProps: { checked: false },
- children: [
- {
- name: 'Settings',
- id: 'example3-App1Settings',
- checkProps: { checked: false }
- },
- {
- name: 'Current',
- id: 'example3-App1Current',
- checkProps: { checked: false }
- }
- ]
- },
- {
- name: 'Application 2',
- id: 'example3-App2',
- checkProps: { checked: false },
- children: [
- {
- name: 'Settings',
- id: 'example3-App2Settings',
- checkProps: { checked: false }
- },
- {
- name: 'Loader',
- id: 'example3-App2Loader',
- checkProps: { checked: false },
- children: [
- {
- name: 'Loading App 1',
- id: 'example3-LoadApp1',
- checkProps: { checked: false }
- },
- {
- name: 'Loading App 2',
- id: 'example3-LoadApp2',
- checkProps: { checked: false }
- },
- {
- name: 'Loading App 3',
- id: 'example3-LoadApp3',
- checkProps: { checked: false }
- }
- ]
- }
- ]
- }
- ],
- defaultExpanded: true
- },
- {
- name: 'Cost management',
- id: 'example3-Cost',
- checkProps: { 'aria-label': 'cost-check', checked: false },
- children: [
- {
- name: 'Application 3',
- id: 'example3-App3',
- checkProps: { 'aria-label': 'app-3-check', checked: false },
- children: [
- {
- name: 'Settings',
- id: 'example3-App3Settings',
- checkProps: { 'aria-label': 'app-3-settings-check', checked: false }
- },
- {
- name: 'Current',
- id: 'example3-App3Current',
- checkProps: { 'aria-label': 'app-3-current-check', checked: false }
- }
- ]
- }
- ]
- },
- {
- name: 'Sources',
- id: 'example3-Sources',
- checkProps: { 'aria-label': 'sources-check', checked: false },
- children: [
- {
- name: 'Application 4',
- id: 'example3-App4',
- checkProps: { 'aria-label': 'app-4-check', checked: false },
- children: [
- {
- name: 'Settings',
- id: 'example3-App4Settings',
- checkProps: { 'aria-label': 'app-4-settings-check', checked: false }
- }
- ]
- }
- ]
- },
- {
- name: 'Really really really long folder name that overflows the container it is in',
- id: 'example3-Long',
- checkProps: { 'aria-label': 'long-check', checked: false },
- children: [
- { name: 'Application 5', id: 'example3-App5', checkProps: { 'aria-label': 'app-5-check', checked: false } }
- ]
- }
- ];
-
- this.state = { checkedItems: [] };
-
- this.onCheck = (evt, treeViewItem) => {
- const checked = evt.target.checked;
- console.log(checked);
-
- const checkedItemTree = this.options
- .map((opt) => Object.assign({}, opt))
- .filter((item) => this.filterItems(item, treeViewItem));
- const flatCheckedItems = this.flattenTree(checkedItemTree);
- console.log('flat', flatCheckedItems);
-
- this.setState(
- (prevState) => ({
- checkedItems: checked
- ? prevState.checkedItems.concat(
- flatCheckedItems.filter((item) => !prevState.checkedItems.some((i) => i.id === item.id))
- )
- : prevState.checkedItems.filter((item) => !flatCheckedItems.some((i) => i.id === item.id))
- }),
- () => {
- console.log('Checked items: ', this.state.checkedItems);
- }
- );
- };
-
- // Helper functions
- const isChecked = (dataItem) => this.state.checkedItems.some((item) => item.id === dataItem.id);
- const areAllDescendantsChecked = (dataItem) =>
- dataItem.children ? dataItem.children.every((child) => areAllDescendantsChecked(child)) : isChecked(dataItem);
- const areSomeDescendantsChecked = (dataItem) =>
- dataItem.children ? dataItem.children.some((child) => areSomeDescendantsChecked(child)) : isChecked(dataItem);
-
- this.flattenTree = (tree) => {
- var result = [];
- tree.forEach((item) => {
- result.push(item);
- if (item.children) {
- result = result.concat(this.flattenTree(item.children));
- }
- });
- return result;
- };
-
- this.mapTree = (item) => {
- const hasCheck = areAllDescendantsChecked(item);
- // Reset checked properties to be updated
- item.checkProps.checked = false;
-
- if (hasCheck) {
- item.checkProps.checked = true;
- } else {
- const hasPartialCheck = areSomeDescendantsChecked(item);
- if (hasPartialCheck) {
- item.checkProps.checked = null;
- }
- }
+```ts file='./TreeViewWithCheckboxes.tsx'
- if (item.children) {
- return {
- ...item,
- children: item.children.map((child) => this.mapTree(child))
- };
- }
- return item;
- };
-
- this.filterItems = (item, checkedItem) => {
- if (item.id === checkedItem.id) {
- return true;
- }
-
- if (item.children) {
- return (
- (item.children = item.children
- .map((opt) => Object.assign({}, opt))
- .filter((child) => this.filterItems(child, checkedItem))).length > 0
- );
- }
- };
- }
-
- render() {
- const mapped = this.options.map((item) => this.mapTree(item));
- return ;
- }
-}
```
### With icons
-```js
-import React from 'react';
-import { TreeView } from '@patternfly/react-core';
-import FolderIcon from '@patternfly/react-icons/dist/esm/icons/folder-icon';
-import FolderOpenIcon from '@patternfly/react-icons/dist/esm/icons/folder-open-icon';
-
-class IconTreeView extends React.Component {
- constructor(props) {
- super(props);
+```ts file='./TreeViewWithIcons.tsx'
- this.state = { activeItems: {} };
-
- this.onSelect = (evt, treeViewItem) => {
- // Ignore folders for selection
- if (treeViewItem && !treeViewItem.children) {
- this.setState({
- activeItems: [treeViewItem]
- });
- }
- };
- }
-
- render() {
- const { activeItems } = this.state;
- const options = [
- {
- name: 'Application launcher',
- id: 'example4-AppLaunch',
- children: [
- {
- name: 'Application 1',
- id: 'example4-App1',
- children: [
- { name: 'Settings', id: 'example4-App1Settings' },
- { name: 'Current', id: 'example4-App1Current' }
- ]
- },
- {
- name: 'Application 2',
- id: 'example4-App2',
- children: [
- { name: 'Settings', id: 'example4-App2Settings' },
- {
- name: 'Loader',
- id: 'example4-App2Loader',
- children: [
- { name: 'Loading App 1', id: 'example4-LoadApp1' },
- { name: 'Loading App 2', id: 'example4-LoadApp2' },
- { name: 'Loading App 3', id: 'example4-LoadApp3' }
- ]
- }
- ]
- }
- ],
- defaultExpanded: true
- },
- {
- name: 'Cost management',
- id: 'example4-Cost',
- children: [
- {
- name: 'Application 3',
- id: 'example4-App3',
- children: [
- { name: 'Settings', id: 'example4-App3Settings' },
- { name: 'Current', id: 'example4-App3Current' }
- ]
- }
- ]
- },
- {
- name: 'Sources',
- id: 'example4-Sources',
- children: [
- { name: 'Application 4', id: 'example4-App4', children: [{ name: 'Settings', id: 'example4-App4Settings' }] }
- ]
- },
- {
- name: 'Really really really long folder name that overflows the container it is in',
- id: 'example4-Long',
- children: [{ name: 'Application 5', id: 'example4-App5' }]
- }
- ];
- return (
- }
- expandedIcon={}
- />
- );
- }
-}
```
### With badges
-```js
-import React from 'react';
-import { TreeView } from '@patternfly/react-core';
-
-class BadgesTreeView extends React.Component {
- constructor(props) {
- super(props);
+```ts file='./TreeViewWithBadges.tsx'
- this.state = { activeItems: {} };
-
- this.onSelect = (evt, treeViewItem) => {
- // Ignore folders for selection
- if (treeViewItem && !treeViewItem.children) {
- this.setState({
- activeItems: [treeViewItem]
- });
- }
- };
- }
-
- render() {
- const { activeItems } = this.state;
- const options = [
- {
- name: 'Application launcher',
- id: 'example5-AppLaunch',
- children: [
- {
- name: 'Application 1',
- id: 'example5-App1',
- children: [
- { name: 'Settings', id: 'example5-App1Settings' },
- { name: 'Current', id: 'example5-App1Current' }
- ]
- },
- {
- name: 'Application 2',
- id: 'example5-App2',
- children: [
- { name: 'Settings', id: 'example5-App2Settings' },
- {
- name: 'Loader',
- id: 'example5-App2Loader',
- children: [
- { name: 'Loading App 1', id: 'example5-LoadApp1' },
- { name: 'Loading App 2', id: 'example5-LoadApp2' },
- { name: 'Loading App 3', id: 'example5-LoadApp3' }
- ]
- }
- ]
- }
- ],
- defaultExpanded: true
- },
- {
- name: 'Cost management',
- id: 'example5-Cost',
- children: [
- {
- name: 'Application 3',
- id: 'example5-App3',
- children: [
- { name: 'Settings', id: 'example5-App3Settings' },
- { name: 'Current', id: 'example5-App3Current' }
- ]
- }
- ]
- },
- {
- name: 'Sources',
- id: 'example5-Sources',
- children: [
- { name: 'Application 4', id: 'example5-App4', children: [{ name: 'Settings', id: 'example5-App4Settings' }] }
- ]
- },
- {
- name: 'Really really really long folder name that overflows the container it is in',
- id: 'example5-Long',
- children: [{ name: 'Application 5', id: 'example5-App5' }]
- }
- ];
- return ;
- }
-}
```
### With custom badges
-```js
-import React from 'react';
-import { TreeView } from '@patternfly/react-core';
-
-class CustomBadgesTreeView extends React.Component {
- constructor(props) {
- super(props);
+```ts file='./TreeViewWithCustomBadges.tsx'
- this.state = { activeItems: {} };
-
- this.onSelect = (evt, treeViewItem) => {
- // Ignore folders for selection
- if (treeViewItem && !treeViewItem.children) {
- this.setState({
- activeItems: [treeViewItem]
- });
- }
- };
- }
-
- render() {
- const { activeItems } = this.state;
- const options = [
- {
- name: 'Application launcher',
- id: 'example6-AppLaunch',
- customBadgeContent: '2 applications',
- children: [
- {
- name: 'Application 1',
- id: 'example6-App1',
- customBadgeContent: '2 children',
- children: [
- { name: 'Settings', id: 'example6-App1Settings' },
- { name: 'Current', id: 'example6-App1Current' }
- ]
- },
- {
- name: 'Application 2',
- id: 'example6-App2',
- customBadgeContent: '2 children',
- children: [
- { name: 'Settings', id: 'example6-App2Settings' },
- {
- name: 'Loader',
- id: 'example6-App2Loader',
- customBadgeContent: '3 loading apps',
- children: [
- { name: 'Loading app 1', id: 'example6-LoadApp1' },
- { name: 'Loading app 2', id: 'example6-LoadApp2' },
- { name: 'Loading app 3', id: 'example6-LoadApp3' }
- ]
- }
- ]
- }
- ],
- defaultExpanded: true
- },
- {
- name: 'Cost management',
- id: 'example6-Cost',
- customBadgeContent: '1 applications',
- children: [
- {
- name: 'Application 3',
- id: 'example6-App3',
- customBadgeContent: '2 children',
- children: [
- { name: 'Settings', id: 'example6-App3Settings' },
- { name: 'Current', id: 'example6-App3Current' }
- ]
- }
- ]
- },
- {
- name: 'Sources',
- id: 'example6-Sources',
- customBadgeContent: '1 source',
- children: [
- {
- name: 'Application 4',
- id: 'example6-App4',
- customBadgeContent: '1 child',
- children: [{ name: 'Settings', id: 'example6-App4Settings' }]
- }
- ]
- },
- {
- name: 'Really really really long folder name that overflows the container it is in',
- id: 'example6-Long',
- customBadgeContent: '1 application',
- children: [{ name: 'Application 5', id: 'example6-App5' }]
- }
- ];
- return ;
- }
-}
```
### With action items
-```js
-import React from 'react';
-import {
- TreeView,
- Button,
- Dropdown,
- DropdownList,
- DropdownItem,
- MenuToggle,
- MenuToggleElement
-} from '@patternfly/react-core';
-import ClipboardIcon from '@patternfly/react-icons/dist/esm/icons/clipboard-icon';
-import HamburgerIcon from '@patternfly/react-icons/dist/esm/icons/hamburger-icon';
-import EllipsisVIcon from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon';
-
-class IconTreeView extends React.Component {
- constructor(props) {
- super(props);
+```ts file='./TreeViewWithActionItems.tsx'
- this.state = { activeItems: {}, isOpen: false };
-
- this.onSelect = (evt, treeViewItem) => {
- // Ignore folders for selection
- if (treeViewItem && !treeViewItem.children) {
- this.setState({
- activeItems: [treeViewItem]
- });
- }
- };
-
- this.onToggle = () => {
- this.setState({
- isOpen: !this.state.isOpen
- });
- };
-
- this.onAppLaunchSelect = () => {
- this.setState({
- isOpen: !this.state.isOpen
- });
- };
- }
-
- render() {
- const { activeItems, isOpen } = this.state;
-
- const options = [
- {
- name: 'Application launcher',
- id: 'example7-AppLaunch',
- action: (
- this.setState({ isOpen })}
- toggle={(toggleRef) => (
-
-
-
- )}
- >
-
- Action
- ev.preventDefault()}
- >
- Link
-
- Disabled Action
-
- Disabled Link
-
-
-
- ),
- children: [
- {
- name: 'Application 1',
- id: 'example7-App1',
- action: (
-
- ),
- actionProps: {
- 'aria-label': 'Launch app 1'
- },
- children: [
- { name: 'Settings', id: 'example7-App1Settings' },
- { name: 'Current', id: 'example7-App1Current' }
- ]
- },
- {
- name: 'Application 2',
- id: 'example7-App2',
- action: (
-
- ),
- children: [
- { name: 'Settings', id: 'example7-App2Settings' },
- {
- name: 'Loader',
- id: 'example7-App2Loader',
- children: [
- { name: 'Loading App 1', id: 'example7-LoadApp1' },
- { name: 'Loading App 2', id: 'example7-LoadApp2' },
- { name: 'Loading App 3', id: 'example7-LoadApp3' }
- ]
- }
- ]
- }
- ],
- defaultExpanded: true
- },
- {
- name: 'Cost management',
- id: 'example7-Cost',
- children: [
- {
- name: 'Application 3',
- id: 'example7-App3',
- children: [
- { name: 'Settings', id: 'example7-App3Settings' },
- { name: 'Current', id: 'example7-App3Current' }
- ]
- }
- ]
- },
- {
- name: 'Sources',
- id: 'example7-Sources',
- children: [
- { name: 'Application 4', id: 'example7-App4', children: [{ name: 'Settings', id: 'example7-App4Settings' }] }
- ]
- },
- {
- name: 'Really really really long folder name that overflows the container it is in',
- id: 'example7-Long',
- children: [{ name: 'Application 5', id: 'example7-App5' }]
- }
- ];
- return ;
- }
-}
```
### Guides
-```ts
-import React from 'react';
-import { TreeView, TreeViewDataItem } from '@patternfly/react-core';
+```ts file='./TreeViewGuides.tsx'
-const GuidesTreeView: React.FunctionComponent = () => {
- const options: TreeViewDataItem[] = [
- {
- name: 'Application launcher',
- id: 'example8-AppLaunch',
- children: [
- {
- name: 'Application 1',
- id: 'example8-App1',
- children: [
- { name: 'Settings', id: 'example8-App1Settings' },
- { name: 'Current', id: 'example8-App1Current' }
- ]
- },
- {
- name: 'Application 2',
- id: 'example8-App2',
- children: [
- { name: 'Settings', id: 'example8-App2Settings' },
- {
- name: 'Loader',
- id: 'example8-App2Loader',
- children: [
- { name: 'Loading App 1', id: 'example8-LoadApp1' },
- { name: 'Loading App 2', id: 'example8-LoadApp2' },
- { name: 'Loading App 3', id: 'example8-LoadApp3' }
- ]
- }
- ]
- }
- ],
- defaultExpanded: true
- },
- {
- name: 'Cost management',
- id: 'example8-Cost',
- children: [
- {
- name: 'Application 3',
- id: 'example8-App3',
- children: [
- { name: 'Settings', id: 'example8-App3Settings' },
- { name: 'Current', id: 'example8-App3Current' }
- ]
- }
- ]
- },
- {
- name: 'Sources',
- id: 'example8-Sources',
- children: [
- { name: 'Application 4', id: 'example8-App4', children: [{ name: 'Settings', id: 'example8-App4Settings' }] }
- ]
- },
- {
- name: 'Really really really long folder name that overflows the container it is in',
- id: 'example8-Long',
- children: [{ name: 'Application 5', id: 'example8-App5' }]
- }
- ];
- return ;
-};
```
### Compact
-```ts
-import React from 'react';
-import { TreeView, TreeViewDataItem } from '@patternfly/react-core';
+```ts file='./TreeViewCompact.tsx'
-const CompactTreeView: React.FunctionComponent = () => {
- const options: TreeViewDataItem[] = [
- {
- name: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value and may reject unrecognized values.',
- title: 'apiVersion',
- id: 'example9-apiVersion'
- },
- {
- name: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated is CamelCase. More info:',
- title: 'kind',
- id: 'example9-kind'
- },
- {
- name: 'Standard metadata object',
- title: 'metadata',
- id: 'example9-metadata'
- },
- {
- name: 'Standard metadata object',
- title: 'spec',
- id: 'example9-spec',
- children: [
- {
- name: 'Minimum number of seconds for which a newly created pod should be ready without any of its container crashing, for it to be considered available. Default to 0 (pod will be considered available as soon as it is ready).',
- title: 'minReadySeconds',
- id: 'example9-minReadySeconds'
- },
- {
- name: 'Indicates that the deployment is paused',
- title: 'paused',
- id: 'example9-paused'
- },
- {
- name: 'The maximum time in seconds for a deployment to make progress before it is considered to be failed. The deployment controller will continue to process failed deployments and a condition with a ProgressDeadlineExceeded reason will be surfaced in the deployment status. Note that the progress will not de estimated during the time a deployment is paused. Defaults to 600s.',
- title: 'progressDeadlineSeconds',
- id: 'example9-progressDeadlineSeconds',
- children: [
- {
- name: 'The number of old ReplicaSets to retain to allow rollback. This is a pointer to distinguish between explicit zero and not specified. Defaults to 10.',
- title: 'revisionHistoryLimit',
- id: 'example9-revisionHistoryLimit',
- children: [
- {
- name: 'Map of {key.value} pairs. A single {key.value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In" and the values array contains only "value". The requirements are ANDed.',
- title: 'matchLabels',
- id: 'example9-matchLabels'
- }
- ]
- }
- ]
- }
- ]
- }
- ];
- return ;
-};
```
### Compact, no background
-```ts
-import React from 'react';
-import { TreeView, TreeViewDataItem } from '@patternfly/react-core';
+```ts file='./TreeViewCompactNoBackground.tsx'
-const CompactNoBackgroundTreeView: React.FunctionComponent = () => {
- const options: TreeViewDataItem[] = [
- {
- name: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value and may reject unrecognized values.',
- title: 'apiVersion',
- id: 'example10-apiVersion'
- },
- {
- name: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated is CamelCase. More info:',
- title: 'kind',
- id: 'example10-kind'
- },
- {
- name: 'Standard metadata object',
- title: 'metadata',
- id: 'example10-metadata'
- },
- {
- name: 'Standard metadata object',
- title: 'spec',
- id: 'example10-spec',
- children: [
- {
- name: 'Minimum number of seconds for which a newly created pod should be ready without any of its container crashing, for it to be considered available. Default to 0 (pod will be considered available as soon as it is ready).',
- title: 'minReadySeconds',
- id: 'example10-minReadySeconds'
- },
- {
- name: 'Indicates that the deployment is paused',
- title: 'paused',
- id: 'example10-paused'
- },
- {
- name: 'The maximum time in seconds for a deployment to make progress before it is considered to be failed. The deployment controller will continue to process failed deployments and a condition with a ProgressDeadlineExceeded reason will be surfaced in the deployment status. Note that the progress will not de estimated during the time a deployment is paused. Defaults to 600s.',
- title: 'progressDeadlineSeconds',
- id: 'example10-progressDeadlineSeconds',
- children: [
- {
- name: 'The number of old ReplicaSets to retain to allow rollback. This is a pointer to distinguish between explicit zero and not specified. Defaults to 10.',
- title: 'revisionHistoryLimit',
- id: 'example10-revisionHistoryLimit',
- children: [
- {
- name: 'Map of {key.value} pairs. A single {key.value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In" and the values array contains only "value". The requirements are ANDed.',
- title: 'matchLabels',
- id: 'example10-matchLabels'
- }
- ]
- }
- ]
- }
- ]
- }
- ];
- return ;
-};
```
### With memoization
Turning on memoization with the `useMemo` property helps prevent unnecessary re-renders for large data sets. With this flag active, `activeItems` must pass in an array of nodes along the selected item's path to update properly.
-```js
-import React from 'react';
-import { TreeView, Button } from '@patternfly/react-core';
-
-class MemoTreeView extends React.Component {
- constructor(props) {
- super(props);
-
- this.state = { activeItems: {}, allExpanded: false };
-
- this.onSelect = (evt, treeViewItem) => {
- let filtered = [];
- this.options.forEach((item) => this.filterItems(item, treeViewItem.id, filtered));
- this.setState({
- activeItems: filtered
- });
- };
-
- this.onToggle = (evt) => {
- const { allExpanded } = this.state;
- this.setState({
- allExpanded: allExpanded !== undefined ? !allExpanded : true
- });
- };
-
- this.filterItems = (item, input, list) => {
- if (item.children) {
- let childContained = false;
- item.children.forEach((child) => {
- if (childContained) {
- this.filterItems(child, input, list);
- } else {
- childContained = this.filterItems(child, input, list);
- }
- });
- if (childContained) {
- list.push(item);
- }
- }
-
- if (item.id === input) {
- list.push(item);
- return true;
- } else {
- return false;
- }
- };
-
- this.options = [];
- for (let i = 1; i <= 20; i++) {
- const childNum = 5;
- let childOptions = [];
- for (let j = 1; j <= childNum; j++) {
- childOptions.push({ name: 'Child ' + j, id: `Option${i} - Child${j}` });
- }
- this.options.push({ name: 'Option ' + i, id: i, children: childOptions });
- }
- }
-
- render() {
- const { activeItems, allExpanded } = this.state;
- const tree = (
-
- );
+```ts file='./TreeViewWithMemoization.tsx'
- return (
-
-
- {tree}
-
- );
- }
-}
```
diff --git a/packages/react-core/src/components/TreeView/examples/TreeViewCompact.tsx b/packages/react-core/src/components/TreeView/examples/TreeViewCompact.tsx
new file mode 100644
index 00000000000..68fd651d60f
--- /dev/null
+++ b/packages/react-core/src/components/TreeView/examples/TreeViewCompact.tsx
@@ -0,0 +1,59 @@
+import React from 'react';
+import { TreeView, TreeViewDataItem } from '@patternfly/react-core';
+
+export const TreeViewCompact: React.FunctionComponent = () => {
+ const options: TreeViewDataItem[] = [
+ {
+ name: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value and may reject unrecognized values.',
+ title: 'apiVersion',
+ id: 'example9-apiVersion'
+ },
+ {
+ name: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated is CamelCase. More info:',
+ title: 'kind',
+ id: 'example9-kind'
+ },
+ {
+ name: 'Standard metadata object',
+ title: 'metadata',
+ id: 'example9-metadata'
+ },
+ {
+ name: 'Standard metadata object',
+ title: 'spec',
+ id: 'example9-spec',
+ children: [
+ {
+ name: 'Minimum number of seconds for which a newly created pod should be ready without any of its container crashing, for it to be considered available. Default to 0 (pod will be considered available as soon as it is ready).',
+ title: 'minReadySeconds',
+ id: 'example9-minReadySeconds'
+ },
+ {
+ name: 'Indicates that the deployment is paused',
+ title: 'paused',
+ id: 'example9-paused'
+ },
+ {
+ name: 'The maximum time in seconds for a deployment to make progress before it is considered to be failed. The deployment controller will continue to process failed deployments and a condition with a ProgressDeadlineExceeded reason will be surfaced in the deployment status. Note that the progress will not de estimated during the time a deployment is paused. Defaults to 600s.',
+ title: 'progressDeadlineSeconds',
+ id: 'example9-progressDeadlineSeconds',
+ children: [
+ {
+ name: 'The number of old ReplicaSets to retain to allow rollback. This is a pointer to distinguish between explicit zero and not specified. Defaults to 10.',
+ title: 'revisionHistoryLimit',
+ id: 'example9-revisionHistoryLimit',
+ children: [
+ {
+ name: 'Map of {key.value} pairs. A single {key.value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In" and the values array contains only "value". The requirements are ANDed.',
+ title: 'matchLabels',
+ id: 'example9-matchLabels'
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ];
+ return ;
+};
diff --git a/packages/react-core/src/components/TreeView/examples/TreeViewCompactNoBackground.tsx b/packages/react-core/src/components/TreeView/examples/TreeViewCompactNoBackground.tsx
new file mode 100644
index 00000000000..bc4030518c0
--- /dev/null
+++ b/packages/react-core/src/components/TreeView/examples/TreeViewCompactNoBackground.tsx
@@ -0,0 +1,59 @@
+import React from 'react';
+import { TreeView, TreeViewDataItem } from '@patternfly/react-core';
+
+export const TreeViewCompactNoBackground: React.FunctionComponent = () => {
+ const options: TreeViewDataItem[] = [
+ {
+ name: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value and may reject unrecognized values.',
+ title: 'apiVersion',
+ id: 'example10-apiVersion'
+ },
+ {
+ name: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated is CamelCase. More info:',
+ title: 'kind',
+ id: 'example10-kind'
+ },
+ {
+ name: 'Standard metadata object',
+ title: 'metadata',
+ id: 'example10-metadata'
+ },
+ {
+ name: 'Standard metadata object',
+ title: 'spec',
+ id: 'example10-spec',
+ children: [
+ {
+ name: 'Minimum number of seconds for which a newly created pod should be ready without any of its container crashing, for it to be considered available. Default to 0 (pod will be considered available as soon as it is ready).',
+ title: 'minReadySeconds',
+ id: 'example10-minReadySeconds'
+ },
+ {
+ name: 'Indicates that the deployment is paused',
+ title: 'paused',
+ id: 'example10-paused'
+ },
+ {
+ name: 'The maximum time in seconds for a deployment to make progress before it is considered to be failed. The deployment controller will continue to process failed deployments and a condition with a ProgressDeadlineExceeded reason will be surfaced in the deployment status. Note that the progress will not de estimated during the time a deployment is paused. Defaults to 600s.',
+ title: 'progressDeadlineSeconds',
+ id: 'example10-progressDeadlineSeconds',
+ children: [
+ {
+ name: 'The number of old ReplicaSets to retain to allow rollback. This is a pointer to distinguish between explicit zero and not specified. Defaults to 10.',
+ title: 'revisionHistoryLimit',
+ id: 'example10-revisionHistoryLimit',
+ children: [
+ {
+ name: 'Map of {key.value} pairs. A single {key.value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In" and the values array contains only "value". The requirements are ANDed.',
+ title: 'matchLabels',
+ id: 'example10-matchLabels'
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ];
+ return ;
+};
diff --git a/packages/react-core/src/components/TreeView/examples/TreeViewDefault.tsx b/packages/react-core/src/components/TreeView/examples/TreeViewDefault.tsx
new file mode 100644
index 00000000000..db14d756cf2
--- /dev/null
+++ b/packages/react-core/src/components/TreeView/examples/TreeViewDefault.tsx
@@ -0,0 +1,87 @@
+import React from 'react';
+import { TreeView, Button, TreeViewDataItem } from '@patternfly/react-core';
+
+export const TreeViewDefault: React.FunctionComponent = () => {
+ const [activeItems, setActiveItems] = React.useState();
+ const [allExpanded, setAllExpanded] = React.useState();
+
+ const onSelect = (_event: React.MouseEvent, treeViewItem: TreeViewDataItem) => {
+ // Ignore folders for selection
+ if (treeViewItem && !treeViewItem.children) {
+ setActiveItems([treeViewItem]);
+ }
+ };
+
+ const onToggle = (_event: React.MouseEvent) => {
+ setAllExpanded((prevAllExpanded) => !prevAllExpanded);
+ };
+
+ const options = [
+ {
+ name: 'Application launcher',
+ id: 'example1-AppLaunch',
+ children: [
+ {
+ name: 'Application 1',
+ id: 'example1-App1',
+ children: [
+ { name: 'Settings', id: 'example1-App1Settings' },
+ { name: 'Current', id: 'example1-App1Current' }
+ ]
+ },
+ {
+ name: 'Application 2',
+ id: 'example1-App2',
+ children: [
+ { name: 'Settings', id: 'example1-App2Settings' },
+ {
+ name: 'Loader',
+ id: 'example1-App2Loader',
+ children: [
+ { name: 'Loading App 1', id: 'example1-LoadApp1' },
+ { name: 'Loading App 2', id: 'example1-LoadApp2' },
+ { name: 'Loading App 3', id: 'example1-LoadApp3' }
+ ]
+ }
+ ]
+ }
+ ],
+ defaultExpanded: true
+ },
+ {
+ name: 'Cost management',
+ id: 'example1-Cost',
+ children: [
+ {
+ name: 'Application 3',
+ id: 'example1-App3',
+ children: [
+ { name: 'Settings', id: 'example1-App3Settings' },
+ { name: 'Current', id: 'example1-App3Current' }
+ ]
+ }
+ ]
+ },
+ {
+ name: 'Sources',
+ id: 'example1-Sources',
+ children: [
+ { name: 'Application 4', id: 'example1-App4', children: [{ name: 'Settings', id: 'example1-App4Settings' }] }
+ ]
+ },
+ {
+ name: 'Really really really long folder name that overflows the container it is in',
+ id: 'example1-Long',
+ children: [{ name: 'Application 5', id: 'example1-App5' }]
+ }
+ ];
+ return (
+
+
+
+
+ );
+};
diff --git a/packages/react-core/src/components/TreeView/examples/TreeViewGuides.tsx b/packages/react-core/src/components/TreeView/examples/TreeViewGuides.tsx
new file mode 100644
index 00000000000..4fa9cd2a353
--- /dev/null
+++ b/packages/react-core/src/components/TreeView/examples/TreeViewGuides.tsx
@@ -0,0 +1,65 @@
+import React from 'react';
+import { TreeView, TreeViewDataItem } from '@patternfly/react-core';
+
+export const GuidesTreeView: React.FunctionComponent = () => {
+ const options: TreeViewDataItem[] = [
+ {
+ name: 'Application launcher',
+ id: 'example8-AppLaunch',
+ children: [
+ {
+ name: 'Application 1',
+ id: 'example8-App1',
+ children: [
+ { name: 'Settings', id: 'example8-App1Settings' },
+ { name: 'Current', id: 'example8-App1Current' }
+ ]
+ },
+ {
+ name: 'Application 2',
+ id: 'example8-App2',
+ children: [
+ { name: 'Settings', id: 'example8-App2Settings' },
+ {
+ name: 'Loader',
+ id: 'example8-App2Loader',
+ children: [
+ { name: 'Loading App 1', id: 'example8-LoadApp1' },
+ { name: 'Loading App 2', id: 'example8-LoadApp2' },
+ { name: 'Loading App 3', id: 'example8-LoadApp3' }
+ ]
+ }
+ ]
+ }
+ ],
+ defaultExpanded: true
+ },
+ {
+ name: 'Cost management',
+ id: 'example8-Cost',
+ children: [
+ {
+ name: 'Application 3',
+ id: 'example8-App3',
+ children: [
+ { name: 'Settings', id: 'example8-App3Settings' },
+ { name: 'Current', id: 'example8-App3Current' }
+ ]
+ }
+ ]
+ },
+ {
+ name: 'Sources',
+ id: 'example8-Sources',
+ children: [
+ { name: 'Application 4', id: 'example8-App4', children: [{ name: 'Settings', id: 'example8-App4Settings' }] }
+ ]
+ },
+ {
+ name: 'Really really really long folder name that overflows the container it is in',
+ id: 'example8-Long',
+ children: [{ name: 'Application 5', id: 'example8-App5' }]
+ }
+ ];
+ return ;
+};
diff --git a/packages/react-core/src/components/TreeView/examples/TreeViewSelectionExpansion.tsx b/packages/react-core/src/components/TreeView/examples/TreeViewSelectionExpansion.tsx
new file mode 100644
index 00000000000..d1a2396d040
--- /dev/null
+++ b/packages/react-core/src/components/TreeView/examples/TreeViewSelectionExpansion.tsx
@@ -0,0 +1,75 @@
+import React from 'react';
+import { TreeView, TreeViewDataItem } from '@patternfly/react-core';
+
+export const TreeViewSelectableNodes: React.FunctionComponent = () => {
+ const [activeItems, setActiveItems] = React.useState();
+
+ const onSelect = (_event: React.MouseEvent, treeViewItem: TreeViewDataItem) => {
+ setActiveItems([treeViewItem]);
+ };
+
+ const options = [
+ {
+ name: 'Application launcher',
+ id: 'SelNodesTreeView-AppLaunch',
+ children: [
+ {
+ name: 'Application 1',
+ id: 'SelNodesTreeView-App1',
+ children: [
+ { name: 'Settings', id: 'SelNodesTreeView-App1Settings' },
+ { name: 'Current', id: 'SelNodesTreeView-App1Current' }
+ ]
+ },
+ {
+ name: 'Application 2',
+ id: 'SelNodesTreeView-App2',
+ children: [
+ { name: 'Settings', id: 'SelNodesTreeView-App2Settings' },
+ {
+ name: 'Loader',
+ id: 'SelNodesTreeView-App2Loader',
+ children: [
+ { name: 'Loading App 1', id: 'SelNodesTreeView-LoadApp1' },
+ { name: 'Loading App 2', id: 'SelNodesTreeView-LoadApp2' },
+ { name: 'Loading App 3', id: 'SelNodesTreeView-LoadApp3' }
+ ]
+ }
+ ]
+ }
+ ],
+ defaultExpanded: true
+ },
+ {
+ name: 'Cost management',
+ id: 'SelNodesTreeView-Cost',
+ children: [
+ {
+ name: 'Application 3',
+ id: 'SelNodesTreeView-App3',
+ children: [
+ { name: 'Settings', id: 'SelNodesTreeView-App3Settings' },
+ { name: 'Current', id: 'SelNodesTreeView-App3Current' }
+ ]
+ }
+ ]
+ },
+ {
+ name: 'Sources',
+ id: 'SelNodesTreeView-Sources',
+ children: [
+ {
+ name: 'Application 4',
+ id: 'SelNodesTreeView-App4',
+ children: [{ name: 'Settings', id: 'SelNodesTreeView-App4Settings' }]
+ }
+ ]
+ },
+ {
+ name: 'Really really really long folder name that overflows the container it is in',
+ id: 'SelNodesTreeView-Long',
+ children: [{ name: 'Application 5', id: 'SelNodesTreeView-App5' }]
+ }
+ ];
+ return ;
+};
diff --git a/packages/react-core/src/components/TreeView/examples/TreeViewWithActionItems.tsx b/packages/react-core/src/components/TreeView/examples/TreeViewWithActionItems.tsx
new file mode 100644
index 00000000000..0fe77a8ad97
--- /dev/null
+++ b/packages/react-core/src/components/TreeView/examples/TreeViewWithActionItems.tsx
@@ -0,0 +1,140 @@
+import React from 'react';
+import {
+ TreeView,
+ Button,
+ Dropdown,
+ DropdownList,
+ DropdownItem,
+ MenuToggle,
+ TreeViewDataItem
+} from '@patternfly/react-core';
+import ClipboardIcon from '@patternfly/react-icons/dist/esm/icons/clipboard-icon';
+import HamburgerIcon from '@patternfly/react-icons/dist/esm/icons/hamburger-icon';
+import EllipsisVIcon from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon';
+
+export const TreeViewWithActionItems: React.FunctionComponent = () => {
+ const [activeItems, setActiveItems] = React.useState();
+ const [isOpen, setIsOpen] = React.useState();
+
+ const onSelect = (_event: React.MouseEvent, treeViewItem: TreeViewDataItem) => {
+ // Ignore folders for selection
+ if (treeViewItem && !treeViewItem.children) {
+ setActiveItems([treeViewItem]);
+ }
+ };
+
+ const onToggle = () => {
+ setIsOpen((prevIsOpen) => !prevIsOpen);
+ };
+
+ const onAppLaunchSelect = () => {
+ setIsOpen((prevIsOpen) => !prevIsOpen);
+ };
+
+ const options = [
+ {
+ name: 'Application launcher',
+ id: 'example7-AppLaunch',
+ action: (
+ setIsOpen(isOpen)}
+ toggle={(toggleRef) => (
+
+
+
+ )}
+ >
+
+ Action
+ ev.preventDefault()}
+ >
+ Link
+
+ Disabled Action
+
+ Disabled Link
+
+
+
+ ),
+ children: [
+ {
+ name: 'Application 1',
+ id: 'example7-App1',
+ action: (
+
+ ),
+ actionProps: {
+ 'aria-label': 'Launch app 1'
+ },
+ children: [
+ { name: 'Settings', id: 'example7-App1Settings' },
+ { name: 'Current', id: 'example7-App1Current' }
+ ]
+ },
+ {
+ name: 'Application 2',
+ id: 'example7-App2',
+ action: (
+
+ ),
+ children: [
+ { name: 'Settings', id: 'example7-App2Settings' },
+ {
+ name: 'Loader',
+ id: 'example7-App2Loader',
+ children: [
+ { name: 'Loading App 1', id: 'example7-LoadApp1' },
+ { name: 'Loading App 2', id: 'example7-LoadApp2' },
+ { name: 'Loading App 3', id: 'example7-LoadApp3' }
+ ]
+ }
+ ]
+ }
+ ],
+ defaultExpanded: true
+ },
+ {
+ name: 'Cost management',
+ id: 'example7-Cost',
+ children: [
+ {
+ name: 'Application 3',
+ id: 'example7-App3',
+ children: [
+ { name: 'Settings', id: 'example7-App3Settings' },
+ { name: 'Current', id: 'example7-App3Current' }
+ ]
+ }
+ ]
+ },
+ {
+ name: 'Sources',
+ id: 'example7-Sources',
+ children: [
+ { name: 'Application 4', id: 'example7-App4', children: [{ name: 'Settings', id: 'example7-App4Settings' }] }
+ ]
+ },
+ {
+ name: 'Really really really long folder name that overflows the container it is in',
+ id: 'example7-Long',
+ children: [{ name: 'Application 5', id: 'example7-App5' }]
+ }
+ ];
+ return ;
+};
diff --git a/packages/react-core/src/components/TreeView/examples/TreeViewWithBadges.tsx b/packages/react-core/src/components/TreeView/examples/TreeViewWithBadges.tsx
new file mode 100644
index 00000000000..9bc603bb9bd
--- /dev/null
+++ b/packages/react-core/src/components/TreeView/examples/TreeViewWithBadges.tsx
@@ -0,0 +1,79 @@
+import React from 'react';
+import { TreeView, TreeViewDataItem } from '@patternfly/react-core';
+
+export const TreeViewBadges: React.FunctionComponent = () => {
+ const [activeItems, setActiveItems] = React.useState();
+
+ const onSelect = (_event: React.MouseEvent, treeViewItem: TreeViewDataItem) => {
+ // Ignore folders for selection
+ if (treeViewItem && !treeViewItem.children) {
+ setActiveItems([treeViewItem]);
+ }
+ };
+
+ const options = [
+ {
+ name: 'Application launcher',
+ id: 'example5-AppLaunch',
+ children: [
+ {
+ name: 'Application 1',
+ id: 'example5-App1',
+ children: [
+ { name: 'Settings', id: 'example5-App1Settings' },
+ { name: 'Current', id: 'example5-App1Current' }
+ ]
+ },
+ {
+ name: 'Application 2',
+ id: 'example5-App2',
+ children: [
+ { name: 'Settings', id: 'example5-App2Settings' },
+ {
+ name: 'Loader',
+ id: 'example5-App2Loader',
+ children: [
+ { name: 'Loading App 1', id: 'example5-LoadApp1' },
+ { name: 'Loading App 2', id: 'example5-LoadApp2' },
+ { name: 'Loading App 3', id: 'example5-LoadApp3' }
+ ]
+ }
+ ]
+ }
+ ],
+ defaultExpanded: true
+ },
+ {
+ name: 'Cost management',
+ id: 'example5-Cost',
+ children: [
+ {
+ name: 'Application 3',
+ id: 'example5-App3',
+ children: [
+ { name: 'Settings', id: 'example5-App3Settings' },
+ { name: 'Current', id: 'example5-App3Current' }
+ ]
+ }
+ ]
+ },
+ {
+ name: 'Sources',
+ id: 'example5-Sources',
+ children: [
+ {
+ name: 'Application 4',
+ id: 'example5-App4',
+ children: [{ name: 'Settings', id: 'example5-App4Settings' }]
+ }
+ ]
+ },
+ {
+ name: 'Really really really long folder name that overflows the container it is in',
+ id: 'example5-Long',
+ children: [{ name: 'Application 5', id: 'example5-App5' }]
+ }
+ ];
+
+ return ;
+};
diff --git a/packages/react-core/src/components/TreeView/examples/TreeViewWithCheckboxes.tsx b/packages/react-core/src/components/TreeView/examples/TreeViewWithCheckboxes.tsx
new file mode 100644
index 00000000000..3e660959a7e
--- /dev/null
+++ b/packages/react-core/src/components/TreeView/examples/TreeViewWithCheckboxes.tsx
@@ -0,0 +1,198 @@
+import React from 'react';
+import { TreeView, TreeViewDataItem } from '@patternfly/react-core';
+
+export const TreeViewWithCheckboxes: React.FunctionComponent = () => {
+ const [checkedItems, setCheckedItems] = React.useState([]);
+
+ React.useEffect(() => {
+ // eslint-disable-next-line no-console
+ console.log('Checked items: ', checkedItems);
+ }, [checkedItems]);
+
+ const options = [
+ {
+ name: 'Application launcher',
+ id: 'example3-AppLaunch',
+ checkProps: { 'aria-label': 'app-launcher-check', checked: false },
+ children: [
+ {
+ name: 'Application 1',
+ id: 'example3-App1',
+ checkProps: { checked: false },
+ children: [
+ {
+ name: 'Settings',
+ id: 'example3-App1Settings',
+ checkProps: { checked: false }
+ },
+ {
+ name: 'Current',
+ id: 'example3-App1Current',
+ checkProps: { checked: false }
+ }
+ ]
+ },
+ {
+ name: 'Application 2',
+ id: 'example3-App2',
+ checkProps: { checked: false },
+ children: [
+ {
+ name: 'Settings',
+ id: 'example3-App2Settings',
+ checkProps: { checked: false }
+ },
+ {
+ name: 'Loader',
+ id: 'example3-App2Loader',
+ checkProps: { checked: false },
+ children: [
+ {
+ name: 'Loading App 1',
+ id: 'example3-LoadApp1',
+ checkProps: { checked: false }
+ },
+ {
+ name: 'Loading App 2',
+ id: 'example3-LoadApp2',
+ checkProps: { checked: false }
+ },
+ {
+ name: 'Loading App 3',
+ id: 'example3-LoadApp3',
+ checkProps: { checked: false }
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ defaultExpanded: true
+ },
+ {
+ name: 'Cost management',
+ id: 'example3-Cost',
+ checkProps: { 'aria-label': 'cost-check', checked: false },
+ children: [
+ {
+ name: 'Application 3',
+ id: 'example3-App3',
+ checkProps: { 'aria-label': 'app-3-check', checked: false },
+ children: [
+ {
+ name: 'Settings',
+ id: 'example3-App3Settings',
+ checkProps: { 'aria-label': 'app-3-settings-check', checked: false }
+ },
+ {
+ name: 'Current',
+ id: 'example3-App3Current',
+ checkProps: { 'aria-label': 'app-3-current-check', checked: false }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ name: 'Sources',
+ id: 'example3-Sources',
+ checkProps: { 'aria-label': 'sources-check', checked: false },
+ children: [
+ {
+ name: 'Application 4',
+ id: 'example3-App4',
+ checkProps: { 'aria-label': 'app-4-check', checked: false },
+ children: [
+ {
+ name: 'Settings',
+ id: 'example3-App4Settings',
+ checkProps: { 'aria-label': 'app-4-settings-check', checked: false }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ name: 'Really really really long folder name that overflows the container it is in',
+ id: 'example3-Long',
+ checkProps: { 'aria-label': 'long-check', checked: false },
+ children: [
+ { name: 'Application 5', id: 'example3-App5', checkProps: { 'aria-label': 'app-5-check', checked: false } }
+ ]
+ }
+ ];
+
+ const onCheck = (event: React.ChangeEvent, treeViewItem: TreeViewDataItem) => {
+ const checked = (event.target as HTMLInputElement).checked;
+
+ const checkedItemTree = options
+ .map((opt) => Object.assign({}, opt))
+ .filter((item) => filterItems(item, treeViewItem));
+ const flatCheckedItems = flattenTree(checkedItemTree);
+
+ setCheckedItems((prevCheckedItems) =>
+ checked
+ ? prevCheckedItems.concat(flatCheckedItems.filter((item) => !checkedItems.some((i) => i.id === item.id)))
+ : prevCheckedItems.filter((item) => !flatCheckedItems.some((i) => i.id === item.id))
+ );
+ };
+
+ // Helper functions
+ const isChecked = (dataItem: TreeViewDataItem) => checkedItems.some((item) => item.id === dataItem.id);
+ const areAllDescendantsChecked = (dataItem: TreeViewDataItem) =>
+ dataItem.children ? dataItem.children.every((child) => areAllDescendantsChecked(child)) : isChecked(dataItem);
+ const areSomeDescendantsChecked = (dataItem: TreeViewDataItem) =>
+ dataItem.children ? dataItem.children.some((child) => areSomeDescendantsChecked(child)) : isChecked(dataItem);
+
+ const flattenTree = (tree: TreeViewDataItem[]) => {
+ let result: TreeViewDataItem[] = [];
+ tree.forEach((item) => {
+ result.push(item);
+ if (item.children) {
+ result = result.concat(flattenTree(item.children));
+ }
+ });
+ return result;
+ };
+
+ const mapTree = (item: TreeViewDataItem) => {
+ const hasCheck = areAllDescendantsChecked(item);
+ // Reset checked properties to be updated
+ if (item.checkProps) {
+ item.checkProps.checked = false;
+
+ if (hasCheck) {
+ item.checkProps.checked = true;
+ } else {
+ const hasPartialCheck = areSomeDescendantsChecked(item);
+ if (hasPartialCheck) {
+ item.checkProps.checked = null;
+ }
+ }
+
+ if (item.children) {
+ return {
+ ...item,
+ children: item.children.map((child) => mapTree(child))
+ };
+ }
+ }
+ return item;
+ };
+
+ const filterItems = (item: TreeViewDataItem, checkedItem: TreeViewDataItem) => {
+ if (item.id === checkedItem.id) {
+ return true;
+ }
+
+ if (item.children) {
+ return (
+ (item.children = item.children
+ .map((opt) => Object.assign({}, opt))
+ .filter((child) => filterItems(child, checkedItem))).length > 0
+ );
+ }
+ };
+ const mapped = options.map((item) => mapTree(item));
+ return ;
+};
diff --git a/packages/react-core/src/components/TreeView/examples/TreeViewWithCustomBadges.tsx b/packages/react-core/src/components/TreeView/examples/TreeViewWithCustomBadges.tsx
new file mode 100644
index 00000000000..c520dabaec4
--- /dev/null
+++ b/packages/react-core/src/components/TreeView/examples/TreeViewWithCustomBadges.tsx
@@ -0,0 +1,86 @@
+import React from 'react';
+import { TreeView, TreeViewDataItem } from '@patternfly/react-core';
+
+export const TreeViewCustomBadges: React.FunctionComponent = () => {
+ const [activeItems, setActiveItems] = React.useState();
+
+ const onSelect = (_event: React.MouseEvent, treeViewItem: TreeViewDataItem) => {
+ // Ignore folders for selection
+ if (treeViewItem && !treeViewItem.children) {
+ setActiveItems([treeViewItem]);
+ }
+ };
+ const options = [
+ {
+ name: 'Application launcher',
+ id: 'example6-AppLaunch',
+ customBadgeContent: '2 applications',
+ children: [
+ {
+ name: 'Application 1',
+ id: 'example6-App1',
+ customBadgeContent: '2 children',
+ children: [
+ { name: 'Settings', id: 'example6-App1Settings' },
+ { name: 'Current', id: 'example6-App1Current' }
+ ]
+ },
+ {
+ name: 'Application 2',
+ id: 'example6-App2',
+ customBadgeContent: '2 children',
+ children: [
+ { name: 'Settings', id: 'example6-App2Settings' },
+ {
+ name: 'Loader',
+ id: 'example6-App2Loader',
+ customBadgeContent: '3 loading apps',
+ children: [
+ { name: 'Loading app 1', id: 'example6-LoadApp1' },
+ { name: 'Loading app 2', id: 'example6-LoadApp2' },
+ { name: 'Loading app 3', id: 'example6-LoadApp3' }
+ ]
+ }
+ ]
+ }
+ ],
+ defaultExpanded: true
+ },
+ {
+ name: 'Cost management',
+ id: 'example6-Cost',
+ customBadgeContent: '1 applications',
+ children: [
+ {
+ name: 'Application 3',
+ id: 'example6-App3',
+ customBadgeContent: '2 children',
+ children: [
+ { name: 'Settings', id: 'example6-App3Settings' },
+ { name: 'Current', id: 'example6-App3Current' }
+ ]
+ }
+ ]
+ },
+ {
+ name: 'Sources',
+ id: 'example6-Sources',
+ customBadgeContent: '1 source',
+ children: [
+ {
+ name: 'Application 4',
+ id: 'example6-App4',
+ customBadgeContent: '1 child',
+ children: [{ name: 'Settings', id: 'example6-App4Settings' }]
+ }
+ ]
+ },
+ {
+ name: 'Really really really long folder name that overflows the container it is in',
+ id: 'example6-Long',
+ customBadgeContent: '1 application',
+ children: [{ name: 'Application 5', id: 'example6-App5' }]
+ }
+ ];
+ return ;
+};
diff --git a/packages/react-core/src/components/TreeView/examples/TreeViewWithIcons.tsx b/packages/react-core/src/components/TreeView/examples/TreeViewWithIcons.tsx
new file mode 100644
index 00000000000..4e3027fa388
--- /dev/null
+++ b/packages/react-core/src/components/TreeView/examples/TreeViewWithIcons.tsx
@@ -0,0 +1,83 @@
+import React from 'react';
+import { TreeView, TreeViewDataItem } from '@patternfly/react-core';
+import FolderIcon from '@patternfly/react-icons/dist/esm/icons/folder-icon';
+import FolderOpenIcon from '@patternfly/react-icons/dist/esm/icons/folder-open-icon';
+
+export const TreeViewWithIcons: React.FunctionComponent = () => {
+ const [activeItems, setActiveItems] = React.useState();
+
+ const onSelect = (_event: React.MouseEvent, treeViewItem: TreeViewDataItem) => {
+ // Ignore folders for selection
+ if (treeViewItem && !treeViewItem.children) {
+ setActiveItems([treeViewItem]);
+ }
+ };
+ const options = [
+ {
+ name: 'Application launcher',
+ id: 'example4-AppLaunch',
+ children: [
+ {
+ name: 'Application 1',
+ id: 'example4-App1',
+ children: [
+ { name: 'Settings', id: 'example4-App1Settings' },
+ { name: 'Current', id: 'example4-App1Current' }
+ ]
+ },
+ {
+ name: 'Application 2',
+ id: 'example4-App2',
+ children: [
+ { name: 'Settings', id: 'example4-App2Settings' },
+ {
+ name: 'Loader',
+ id: 'example4-App2Loader',
+ children: [
+ { name: 'Loading App 1', id: 'example4-LoadApp1' },
+ { name: 'Loading App 2', id: 'example4-LoadApp2' },
+ { name: 'Loading App 3', id: 'example4-LoadApp3' }
+ ]
+ }
+ ]
+ }
+ ],
+ defaultExpanded: true
+ },
+ {
+ name: 'Cost management',
+ id: 'example4-Cost',
+ children: [
+ {
+ name: 'Application 3',
+ id: 'example4-App3',
+ children: [
+ { name: 'Settings', id: 'example4-App3Settings' },
+ { name: 'Current', id: 'example4-App3Current' }
+ ]
+ }
+ ]
+ },
+ {
+ name: 'Sources',
+ id: 'example4-Sources',
+ children: [
+ { name: 'Application 4', id: 'example4-App4', children: [{ name: 'Settings', id: 'example4-App4Settings' }] }
+ ]
+ },
+ {
+ name: 'Really really really long folder name that overflows the container it is in',
+ id: 'example4-Long',
+ children: [{ name: 'Application 5', id: 'example4-App5' }]
+ }
+ ];
+ return (
+ }
+ expandedIcon={}
+ />
+ );
+};
diff --git a/packages/react-core/src/components/TreeView/examples/TreeViewWithMemoization.tsx b/packages/react-core/src/components/TreeView/examples/TreeViewWithMemoization.tsx
new file mode 100644
index 00000000000..1006f4b3ad2
--- /dev/null
+++ b/packages/react-core/src/components/TreeView/examples/TreeViewWithMemoization.tsx
@@ -0,0 +1,65 @@
+import React from 'react';
+import { TreeView, Button, TreeViewDataItem } from '@patternfly/react-core';
+
+export const TreeViewWithMemoization: React.FunctionComponent = () => {
+ const [activeItems, setActiveItems] = React.useState();
+ const [allExpanded, setAllExpanded] = React.useState(false);
+
+ const onSelect = (_event: React.MouseEvent, treeViewItem: TreeViewDataItem) => {
+ const filtered: TreeViewDataItem[] = [];
+ options.forEach((item) => filterItems(item, treeViewItem.id, filtered));
+ if (treeViewItem && !treeViewItem.children) {
+ setActiveItems(filtered);
+ }
+ };
+
+ const onToggle = (_event: React.MouseEvent) => {
+ setAllExpanded((prevAllExpanded) => !prevAllExpanded);
+ };
+
+ const filterItems = (item: TreeViewDataItem, input: string | undefined, list: TreeViewDataItem[]) => {
+ if (item.children) {
+ let childContained = false;
+ item.children.forEach((child) => {
+ if (childContained) {
+ filterItems(child, input, list);
+ } else {
+ childContained = filterItems(child, input, list);
+ }
+ });
+ if (childContained) {
+ list.push(item);
+ }
+ }
+
+ if (item.id === input) {
+ list.push(item);
+ return true;
+ } else {
+ return false;
+ }
+ };
+
+ const options: TreeViewDataItem[] = [];
+ for (let i = 1; i <= 20; i++) {
+ const childNum = 5;
+ const childOptions: TreeViewDataItem[] = [];
+ for (let j = 1; j <= childNum; j++) {
+ childOptions.push({ name: 'Child ' + j, id: `Option${i} - Child${j}` });
+ }
+ options.push({ name: 'Option ' + i, id: i.toString(), children: childOptions });
+ }
+ const tree = (
+
+ );
+
+ return (
+
+
+ {tree}
+
+ );
+};
diff --git a/packages/react-core/src/components/TreeView/examples/TreeViewWithSearch.tsx b/packages/react-core/src/components/TreeView/examples/TreeViewWithSearch.tsx
new file mode 100644
index 00000000000..ea1c3dd58cf
--- /dev/null
+++ b/packages/react-core/src/components/TreeView/examples/TreeViewWithSearch.tsx
@@ -0,0 +1,128 @@
+import React from 'react';
+import {
+ Toolbar,
+ ToolbarContent,
+ ToolbarItem,
+ TreeView,
+ TreeViewDataItem,
+ TreeViewSearch
+} from '@patternfly/react-core';
+export const TreeViewWithSearch: React.FunctionComponent = () => {
+ const options = [
+ {
+ name: 'Application launcher',
+ id: 'example2-AppLaunch',
+ children: [
+ {
+ name: 'Application 1',
+ id: 'example2-App1',
+ children: [
+ { name: 'Settings', id: 'example2-App1Settings' },
+ { name: 'Current', id: 'example2-App1Current' }
+ ]
+ },
+ {
+ name: 'Application 2',
+ id: 'example2-App2',
+ children: [
+ { name: 'Settings', id: 'example2-App2Settings' },
+ {
+ name: 'Loader',
+ id: 'example2-App2Loader',
+ children: [
+ { name: 'Loading App 1', id: 'example2-LoadApp1' },
+ { name: 'Loading App 2', id: 'example2-LoadApp2' },
+ { name: 'Loading App 3', id: 'example2-LoadApp3' }
+ ]
+ }
+ ]
+ }
+ ],
+ defaultExpanded: true
+ },
+ {
+ name: 'Cost management',
+ id: 'example2-Cost',
+ children: [
+ {
+ name: 'Application 3',
+ id: 'example2-App3',
+ children: [
+ { name: 'Settings', id: 'example2-App3Settings' },
+ { name: 'Current', id: 'example2-App3Current' }
+ ]
+ }
+ ]
+ },
+ {
+ name: 'Sources',
+ id: 'example2-Sources',
+ children: [
+ {
+ name: 'Application 4',
+ id: 'example2-App4',
+ children: [{ name: 'Settingexample2-s', id: 'example2-App4Settings' }]
+ }
+ ]
+ },
+ {
+ name: 'Really really really long folder name that overflows the container it is in',
+ id: 'example2-Long',
+ children: [{ name: 'Application 5', id: 'example2-App5' }]
+ }
+ ];
+
+ const [activeItems, setActiveItems] = React.useState();
+ const [filteredItems, setFilteredItems] = React.useState(options);
+ const [isFiltered, setIsFiltered] = React.useState(false);
+
+ const onSelect = (_event: React.MouseEvent, treeViewItem: TreeViewDataItem) => {
+ // Ignore folders for selection
+ if (treeViewItem && !treeViewItem.children) {
+ setActiveItems([treeViewItem]);
+ }
+ };
+
+ const onSearch = (event: React.ChangeEvent) => {
+ const input = event.target.value;
+ if (input === '') {
+ setFilteredItems(options);
+ setIsFiltered(false);
+ } else {
+ const filtered = options.map((opt) => Object.assign({}, opt)).filter((item) => filterItems(item, input));
+ setFilteredItems(filtered);
+ setIsFiltered(true);
+ }
+ };
+ const filterItems = (item, input) => {
+ if (item.name.toLowerCase().includes(input.toLowerCase())) {
+ return true;
+ }
+ if (item.children) {
+ return (
+ (item.children = item.children
+ .map((opt) => Object.assign({}, opt))
+ .filter((child) => filterItems(child, input))).length > 0
+ );
+ }
+ };
+ const toolbar = (
+
+
+
+
+
+
+
+ );
+
+ return (
+
+ );
+};