Skip to content

Commit 675d043

Browse files
committed
chore: added types to TreeNode & TreeView
1 parent b12ac2e commit 675d043

File tree

2 files changed

+133
-52
lines changed

2 files changed

+133
-52
lines changed

packages/react/src/components/TreeView/TreeNode.js renamed to packages/react/src/components/TreeView/TreeNode.tsx

Lines changed: 62 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,58 @@
55
* LICENSE file in the root directory of this source tree.
66
*/
77

8-
import React, { useState, useEffect, useRef } from 'react';
9-
import PropTypes from 'prop-types';
10-
import { CaretDown } from '@carbon/icons-react';
8+
import { CarbonIconType, CaretDown } from '@carbon/icons-react';
119
import classNames from 'classnames';
10+
import PropTypes from 'prop-types';
11+
import React, { useEffect, useRef, useState } from 'react';
1212
import { keys, match, matches } from '../../internal/keyboard';
13-
import uniqueId from '../../tools/uniqueId';
14-
import { usePrefix } from '../../internal/usePrefix';
1513
import { useControllableState } from '../../internal/useControllableState';
14+
import { usePrefix } from '../../internal/usePrefix';
15+
import uniqueId from '../../tools/uniqueId';
1616
import { useFeatureFlag } from '../FeatureFlags';
1717

18-
const TreeNode = React.forwardRef(
18+
export type TreeNodeProps = {
19+
/**
20+
* **Note:** this is controlled by the parent TreeView component, do not set manually.
21+
* The ID of the active node in the tree
22+
*/
23+
active?: string | number;
24+
children?: React.ReactNode;
25+
className?: string;
26+
/**
27+
* **[Experimental]** The default expansion state of the node.
28+
* *This is only supported with the `enable-treeview-controllable` feature flag!*
29+
*/
30+
defaultIsExpanded?: boolean;
31+
/**
32+
* **Note:** this is controlled by the parent TreeView component, do not set manually.
33+
* TreeNode depth to determine spacing
34+
*/
35+
depth?: number;
36+
disabled?: boolean;
37+
id?: string;
38+
isExpanded?: boolean;
39+
label: React.ReactNode;
40+
onNodeFocusEvent?: (event: React.FocusEvent<HTMLLIElement>) => void;
41+
onSelect?: (event: React.MouseEvent, node?: TreeNodeProps) => void;
42+
onToggle?: (event: React.MouseEvent, node?: TreeNodeProps) => void;
43+
onTreeSelect?: (event: React.MouseEvent, node?: TreeNodeProps) => void;
44+
renderIcon?: CarbonIconType;
45+
/**
46+
* **Note:** this is controlled by the parent TreeView component, do not set manually.
47+
* Array containing all selected node IDs in the tree
48+
*/
49+
selected?: Array<string | number>;
50+
value?: string;
51+
} & Omit<React.LiHTMLAttributes<HTMLLIElement>, 'onSelect'>;
52+
53+
const TreeNode = React.forwardRef<HTMLLIElement, TreeNodeProps>(
1954
(
2055
{
2156
active,
2257
children,
2358
className,
24-
depth,
59+
depth: propDepth,
2560
disabled,
2661
id: nodeId,
2762
isExpanded,
@@ -32,12 +67,14 @@ const TreeNode = React.forwardRef(
3267
onToggle,
3368
onTreeSelect,
3469
renderIcon: Icon,
35-
selected,
70+
selected: propSelected,
3671
value,
3772
...rest
3873
},
3974
ref
4075
) => {
76+
const depth = propDepth as number;
77+
const selected = propSelected as (string | number)[];
4178
const enableTreeviewControllable = useFeatureFlag(
4279
'enable-treeview-controllable'
4380
);
@@ -54,19 +91,20 @@ const TreeNode = React.forwardRef(
5491
? controllableExpandedState
5592
: uncontrollableExpandedState;
5693

57-
const currentNode = useRef(null);
58-
const currentNodeLabel = useRef(null);
94+
const currentNode = useRef<HTMLLIElement>(null);
95+
const currentNodeLabel = useRef<HTMLDivElement>(null);
5996
const prefix = usePrefix();
6097
const nodesWithProps = React.Children.map(children, (node) => {
6198
if (React.isValidElement(node)) {
6299
return React.cloneElement(node, {
63100
active,
101+
// @ts-ignore
64102
depth: depth + 1,
65103
disabled: disabled || node.props.disabled,
66104
onTreeSelect,
67105
selected,
68106
tabIndex: (!node.props.disabled && -1) || null,
69-
});
107+
} as TreeNodeProps);
70108
}
71109
});
72110
const isActive = active === id;
@@ -85,7 +123,7 @@ const TreeNode = React.forwardRef(
85123
[`${prefix}--tree-parent-node__toggle-icon--expanded`]: expanded,
86124
}
87125
);
88-
function handleToggleClick(event) {
126+
function handleToggleClick(event: React.MouseEvent<HTMLSpanElement>) {
89127
if (disabled) {
90128
return;
91129
}
@@ -98,12 +136,12 @@ const TreeNode = React.forwardRef(
98136
}
99137
setExpanded(!expanded);
100138
}
101-
function handleClick(event) {
139+
function handleClick(event: React.MouseEvent) {
102140
event.stopPropagation();
103141
if (!disabled) {
104142
onTreeSelect?.(event, { id, label, value });
105143
onNodeSelect?.(event, { id, label, value });
106-
rest?.onClick?.(event);
144+
rest?.onClick?.(event as React.MouseEvent<HTMLLIElement>);
107145
}
108146
}
109147
function handleKeyDown(event) {
@@ -133,7 +171,7 @@ const TreeNode = React.forwardRef(
133171
* When focus is on a leaf node or a closed parent node, move focus to
134172
* its parent node (unless its depth is level 1)
135173
*/
136-
findParentTreeNode(currentNode.current.parentNode)?.focus();
174+
findParentTreeNode(currentNode.current?.parentNode)?.focus();
137175
}
138176
}
139177
if (children && match(event, keys.ArrowRight)) {
@@ -142,7 +180,7 @@ const TreeNode = React.forwardRef(
142180
* When focus is on an expanded parent node, move focus to the first
143181
* child node
144182
*/
145-
currentNode.current.lastChild.firstChild.focus();
183+
(currentNode.current?.lastChild?.firstChild as HTMLElement).focus();
146184
} else {
147185
if (!enableTreeviewControllable) {
148186
onToggle?.(event, { id, isExpanded: true, label, value });
@@ -213,19 +251,20 @@ const TreeNode = React.forwardRef(
213251
setExpanded,
214252
]);
215253

216-
const treeNodeProps = {
254+
const treeNodeProps: React.LiHTMLAttributes<HTMLLIElement> = {
217255
...rest,
218-
['aria-current']: isActive || null,
219-
['aria-selected']: disabled ? null : isSelected,
256+
['aria-current']: isActive || undefined,
257+
['aria-selected']: disabled ? undefined : isSelected,
220258
['aria-disabled']: disabled,
221259
className: treeNodeClasses,
222260
id,
223261
onBlur: handleFocusEvent,
224262
onClick: handleClick,
225263
onFocus: handleFocusEvent,
226264
onKeyDown: handleKeyDown,
227-
ref: currentNode,
228265
role: 'treeitem',
266+
// @ts-ignore
267+
ref: currentNode,
229268
};
230269

231270
if (!children) {
@@ -246,6 +285,7 @@ const TreeNode = React.forwardRef(
246285
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
247286
<span
248287
className={`${prefix}--tree-parent-node__toggle`}
288+
// @ts-ignore
249289
disabled={disabled}
250290
onClick={handleToggleClick}>
251291
<CaretDown className={toggleClasses} />
@@ -338,12 +378,14 @@ TreeNode.propTypes = {
338378
* Optional prop to allow each node to have an associated icon.
339379
* Can be a React component class
340380
*/
381+
// @ts-ignore
341382
renderIcon: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
342383

343384
/**
344385
* **Note:** this is controlled by the parent TreeView component, do not set manually.
345386
* Array containing all selected node IDs in the tree
346387
*/
388+
// @ts-ignore
347389
selected: PropTypes.arrayOf(
348390
PropTypes.oneOfType([PropTypes.string, PropTypes.number])
349391
),

0 commit comments

Comments
 (0)