Skip to content

Commit

Permalink
feat(treeview): add experimental controllable API (#15397)
Browse files Browse the repository at this point in the history
Co-authored-by: Andrea N. Cardona <cardona.n.andrea@gmail.com>
  • Loading branch information
janhassel and andreancardona committed Feb 26, 2024
1 parent ee60555 commit b6710e7
Show file tree
Hide file tree
Showing 8 changed files with 410 additions and 21 deletions.
4 changes: 4 additions & 0 deletions packages/feature-flags/feature-flags.yml
Expand Up @@ -34,3 +34,7 @@ feature-flags:
description: >
Enable the use of the v12 OverflowMenu leveraging the Menu subcomponents
enabled: false
- name: enable-treeview-controllable
description: >
Enable the new TreeView controllable API
enabled: false
9 changes: 9 additions & 0 deletions packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap
Expand Up @@ -9175,6 +9175,9 @@ Map {
"className": Object {
"type": "string",
},
"defaultIsExpanded": Object {
"type": "bool",
},
"depth": Object {
"type": "number",
},
Expand Down Expand Up @@ -9262,6 +9265,9 @@ Map {
"className": Object {
"type": "string",
},
"defaultIsExpanded": Object {
"type": "bool",
},
"depth": Object {
"type": "number",
},
Expand Down Expand Up @@ -9356,6 +9362,9 @@ Map {
"multiselect": Object {
"type": "bool",
},
"onActivate": Object {
"type": "func",
},
"onSelect": Object {
"type": "func",
},
Expand Down
Expand Up @@ -34,8 +34,9 @@ components with all feature flags turned on.
| ----------------------------------- | ------------------------------------------------------------------------ | ------- | --------------- | --------- |
| `enable-v11-release` | Flag enabling the v11 features | `true` |||
| `enable-experimental-tile-contrast` | Enable the improved styling for tiles that provides better contrast | `false` | ||
| `enable-v12-tile-default-icons` | Enable default icons for Tile components | `false` ||
| `enable-v12-overflowmenu` | Enable the use of the v12 OverflowMenu leveraging the Menu subcomponents | `false` ||
| `enable-v12-tile-default-icons` | Enable default icons for Tile components | `false` || |
| `enable-v12-overflowmenu` | Enable the use of the v12 OverflowMenu leveraging the Menu subcomponents | `false` || |
| `enable-treeview-controllable` | Enable the new TreeView controllable API | `false` || |

## Turning on feature flags in Javascript/react

Expand Down
54 changes: 46 additions & 8 deletions packages/react/src/components/TreeView/TreeNode.js
Expand Up @@ -12,6 +12,8 @@ import classNames from 'classnames';
import { keys, match, matches } from '../../internal/keyboard';
import uniqueId from '../../tools/uniqueId';
import { usePrefix } from '../../internal/usePrefix';
import { useControllableState } from '../../internal/useControllableState';
import { useFeatureFlag } from '../FeatureFlags';

const TreeNode = React.forwardRef(
(
Expand All @@ -23,6 +25,7 @@ const TreeNode = React.forwardRef(
disabled,
id: nodeId,
isExpanded,
defaultIsExpanded,
label,
onNodeFocusEvent,
onSelect: onNodeSelect,
Expand All @@ -35,8 +38,22 @@ const TreeNode = React.forwardRef(
},
ref
) => {
const enableTreeviewControllable = useFeatureFlag(
'enable-treeview-controllable'
);

const { current: id } = useRef(nodeId || uniqueId());
const [expanded, setExpanded] = useState(isExpanded);

const controllableExpandedState = useControllableState({
value: isExpanded,
onChange: onToggle,
defaultValue: defaultIsExpanded,
});
const uncontrollableExpandedState = useState(isExpanded);
const [expanded, setExpanded] = enableTreeviewControllable
? controllableExpandedState
: uncontrollableExpandedState;

const currentNode = useRef(null);
const currentNodeLabel = useRef(null);
const prefix = usePrefix();
Expand Down Expand Up @@ -76,7 +93,9 @@ const TreeNode = React.forwardRef(
// Prevent the node from being selected
event.stopPropagation();

onToggle?.(event, { id, isExpanded: !expanded, label, value });
if (!enableTreeviewControllable) {
onToggle?.(event, { id, isExpanded: !expanded, label, value });
}
setExpanded(!expanded);
}
function handleClick(event) {
Expand Down Expand Up @@ -105,7 +124,9 @@ const TreeNode = React.forwardRef(
return findParentTreeNode(node.parentNode);
};
if (children && expanded) {
onToggle?.(event, { id, isExpanded: false, label, value });
if (!enableTreeviewControllable) {
onToggle?.(event, { id, isExpanded: false, label, value });
}
setExpanded(false);
} else {
/**
Expand All @@ -123,7 +144,9 @@ const TreeNode = React.forwardRef(
*/
currentNode.current.lastChild.firstChild.focus();
} else {
onToggle?.(event, { id, isExpanded: true, label, value });
if (!enableTreeviewControllable) {
onToggle?.(event, { id, isExpanded: true, label, value });
}
setExpanded(true);
}
}
Expand Down Expand Up @@ -177,9 +200,18 @@ const TreeNode = React.forwardRef(
currentNodeLabel.current.style.paddingInlineStart = `${calcOffset()}rem`;
}

// sync props and state
setExpanded(isExpanded);
}, [children, depth, Icon, isExpanded]);
if (!enableTreeviewControllable) {
// sync props and state
setExpanded(isExpanded);
}
}, [
children,
depth,
Icon,
isExpanded,
enableTreeviewControllable,
setExpanded,
]);

const treeNodeProps = {
...rest,
Expand Down Expand Up @@ -251,7 +283,13 @@ TreeNode.propTypes = {
className: PropTypes.string,

/**
* * **Note:** this is controlled by the parent TreeView component, do not set manually.
* **[Experimental]** The default expansion state of the node.
* *This is only supported with the `enable-treeview-controllable` feature flag!*
*/
defaultIsExpanded: PropTypes.bool,

/**
* **Note:** this is controlled by the parent TreeView component, do not set manually.
* TreeNode depth to determine spacing
*/
depth: PropTypes.number,
Expand Down
33 changes: 33 additions & 0 deletions packages/react/src/components/TreeView/TreeView.featureflag.mdx
@@ -0,0 +1,33 @@
# TreeView controllable API

The new controllable API of TreeView allows you to synchronize the state of `selected` and `active` with your application.

You can opt-in to this by enabling the `enable-treeview-controllable` feature flag. This changes the following behaviour:

- `TreeView`
- `props.onActivate` will be called with a node's ID whenever it is activated.
- The signature of `props.onSelect` changes from `(event, selectedIDs)` to `(selectedIDs)`.
- Whenever you update `props.selected` or `props.active`, the internal state will be updated accordingly and the component re-renders.
- `TreeNode`
- The signature of `props.onToggle` changes from `(event, { id, isExpanded, label, value })` to `(isExpanded)`.
- `props.defaultIsExpanded` is added to allow for a default state in uncontrolled mode.

## Example

```jsx
function SynchronizedTreeView() {
const [selected, setSelected] = useState([]);
const [active, setActive] = useState(null);

return (
<TreeView
selected={selected}
onSelect={setSelected}
active={active}
onActivate={setActive}
>
...
</TreeView>
);
}
```

0 comments on commit b6710e7

Please sign in to comment.