Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(treeview): add experimental controllable API #15397

Merged
merged 8 commits into from Feb 26, 2024
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
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>
);
}
```