Tree view
With TreeView
you can render a nested data structure. You can also use filtering and a lot of other features.
Import
import { TreeView, TreeNode, TreeProps } from '@jutro/components';
Nodes
You can add elements to the tree view as JSX children components or as an array of objects passed to the nodes
prop.
The nodes prop
If you want to use the nodes
prop, pass an array of objects with the following keys:
id
- a unique ID for the node. It is used as the ID in HTMLlabel
- the text to displaynodes
(optional) - an array of child nodes
Expand an example 'nodes' object
const foods = [
{
id: 'fruit',
label: { id: 'foods.Fruit', defaultMessage: 'Fruit' },
nodes: [
{
id: 'berries',
label: { id: 'foods.Berries', defaultMessage: 'Berries' },
nodes: [
{
id: 'cranberry',
label: {
id: 'foods.Cranberry',
defaultMessage: 'Cranberry',
},
},
{
id: 'banana',
label: { id: 'foods.Banana', defaultMessage: 'Banana' },
},
],
},
{
id: 'apple',
label: { id: 'foods.Apple', defaultMessage: 'Apple' },
},
],
},
{
id: 'sandwiches',
label: { id: 'foods.Sandwiches', defaultMessage: 'Sandwiches' },
nodes: [
{
id: 'hotdog',
label: { id: 'foods.hot dog', defaultMessage: 'Hot dog' },
},
{
id: 'ham',
label: {
id: 'foods.ham sandwich',
defaultMessage: 'Ham sandwich',
},
},
],
},
{
id: 'soup',
label: { id: 'foods.soups', defaultMessage: 'soups' },
nodes: [
{
id: 'tomato',
label: {
id: 'foods.tomato soup',
defaultMessage: 'tomato soup',
},
},
{
id: 'potato',
label: {
id: 'foods.potato soup',
defaultMessage: 'potato soup',
},
},
],
},
];
return <TreeView nodes={foods} />;
renderNodes
If you want to override something about your nodes when using the nodes
props, you can do it with renderNodes
.
The props
object of renderNodes
contains the following:
nodeId
- theid
assigned to the tree nodenodes
- the nodes object passed to the tree node
The nodes
object contains nested tree nodes, so you need to iterate recursively to render the entire tree.
For example, imagine you want to apply some nice styles to each node. Create a custom component that takes the list of nodes and renders them as TreeNode
s recursively while applying the style to each one.
const TomatoNodes = ({ nodes }) => {
return (
<>
{nodes.map(({ id, label, nodes: childNodes }) => (
<TreeNode
id={id}
label={label}
style={{
backgroundColor: 'tomato',
color: 'blue',
border: '1px solid yellow',
}}>
{childNodes && <TomatoNodes nodes={childNodes} />}
</TreeNode>
))}
</>
);
};
Then use the component in renderNodes
like so:
<TreeView
nodes={foods}
renderNodes={TomatoNodes}
/>
JSX children
If you prefer to use JSX children, you can do it like this:
<TreeView>
<TreeNode
id="1"
label="Fruit">
<TreeNode
id="2"
label="Banana"
/>
<TreeNode
id="3"
label="Apple"
/>
</TreeNode>
<TreeNode
id="4"
label="Vegetables (coming soon!)"
/>
</TreeView>
A more complex example available in Storybook.
Custom tree nodes
The best way to implement custom tree nodes is to wrap the TreeNode
element.
Let's create a tree node with a link in the header:
import React, { useMemo } from 'react';
import { Link } from '@jutro/components';
import { TreeNode, TreeNodeHeader } from '@jutro/components';
const TreeNodeLink = ({ to, ...otherProps }) => {
const renderHeader = useMemo(
() => (props) =>
(
<Link to={to}>
<TreeNodeHeader {...props} />
</Link>
),
[to]
);
return (
<TreeNode
{...otherProps}
renderHeader={renderHeader}
/>
);
};
This component does a few things, so let's break it down:
- The component returns a
TreeNode
and we pass the props down to it. - The component also takes an additional prop,
to
, which is our link target. - To display the link, our component uses the
renderHeader
callback. The callback returns aTreeNodeHeader
wrapped in a JutroLink
. - The custom header is additionally wrapped in a
useMemo
, so that it is not re-mounted on every render.
You can now use the component like so:
<TreeView>
<TreeNodeLink
id="entrees"
label="Entrees"
to="/menu/entrees">
<TreeNodeLink
id="pasta"
label="Pasta"
to="/menu/entrees/pasta"
/>
<TreeNodeLink
id="steaks"
label="Steaks"
to="/menu/entrees/steaks"
/>
</TreeNodeLink>
</TreeView>
defaultRenderHeader
Each item has a header component which you can override using the defaultRenderHeader
callback function. The props
object the following:
id
- theid
of the header element. By default, is the node ID with a-header
suffixnodeId
- theid
assigned to the tree nodelabel
- thelabel
assigned to the tree node
<TreeView
nodes={nodes}
defaultRenderHeader={({ is, nodeId, label }) => (
<MyComponent id={id}>{label}</MyComponent>
)}
/>
defaultRenderChildren
The defaultRenderChildren
callback allows you to wrap the collection of child nodes in a custom wrapper. The props
object contains the following:
nodeId
- theid
assigned to the tree nodeexpanded
- true if the node is currently expandedchildren
- child nodes of the node component
Using defaultRenderChildren
on the TreeView
works on all nested groups of children.
<TreeView
nodes={foods}
defaultRenderChildren={({ nodeId, children }) => {
// using Card imported from @jutro/components
return <Card id={`${nodeId}-card`}>{children}</Card>;
}}></TreeView>
onClick
You can assign a custom onClick
function which is triggered when a user clicks any of the nodes in the tree. The function takes a single object which contains the following:
hasNodes
-true
if the node has child nodesid
- theid
assigned to the tree nodeexpanded
(optional) -true
if the node is currently expanded
Filter
If you want to include a filter field in your tree view, add the showFilter
prop. The field uses a default filtering function, and if you want to override it, use the filterCallback
prop.
The filterCallback
function takes two arguments:
- the translated label of the tree element
- the filtering query that the user entered in the filter field
The function needs to return an array of indices that show ranges where the label matches the query, for example [{ start: 2, end: 4 }, { start: 7, end: 9 }]
.
If the query does not match, the function needs to return null
.
The example below matches items only if the label starts with the query (case sensitive).
<TreeView
showFilter
filterCallback={(value, query) => {
if (value.startsWith(query)) {
return [{ start: 0, end: query.length }];
}
return null;
}}
/>
The filter field is a standard Jutro InputField
. You have two options to customize it:
-
Pass additional props using the
filterProps
prop<TreeView
showFilter
filterProps={{
icon: 'gw-bathtub',
placeholder: 'Item starts with...',
}}
/> -
Render a custom filter field using
renderFilter
. The callback allows you to pass theonChange
event from the tree view.<TreeView
showFilter
renderFilter={(onChange) => (
<input onChange={(event) => onChange(event.target.value)} />
)}
/>
Icons
You can control icons displayed in the tree view using the following props:
-
defaultParentIcon
- the default icon for a component which has children -
defaultEndIcon
- the default icon for a component without any children<TreeView
defaultParentIcon="gw-business-center"
defaultEndIcon="gw-account-box"
/> -
defaultRenderIcon
- the functional component to render icons, the props contain the following:nodeId
- theid
assigned to the tree nodeicon
- the string denoting the name of the icon, for example the string passed todefaultParentIcon
.
<TreeView
defaultRenderIcon={({ icon, nodeId }) => (
<Link>
{nodeId}: {icon}
</span>
)}
/> -
defaultRenderExpandIcon
- the function to render the "expand" chevron. The first argument is an object which contains the following parameters:nodeId
- theid
assigned to the tree nodeexpanded
-true
if the node is currently expandedisEndNode
-true
if the node does not have any child nodes
You can override the default chevron like this:
<TreeView
defaultRenderExpandIcon={({ expanded, isEndNode }) => {
if (isEndNode) {
return <span>🤔</span>;
}
return <span>{expanded ? '👇' : '👉'}</span>;
}}
/>
Accessibility
Tree view is fully accessible and supports keyboard navigation.
Usage in metadata
If you use this component only in metadata and you don't use it in JSX, you need to import this module explicitly.
import '@jutro/components';
Learn more
To see examples of what you can do, visit Storybook.
Was this page helpful?