Skip to content

Commit

Permalink
[tree view] Add support for checkbox selection
Browse files Browse the repository at this point in the history
  • Loading branch information
flaviendelangle committed Dec 19, 2023
1 parent ab6fb0e commit 4e7ee8d
Show file tree
Hide file tree
Showing 9 changed files with 54 additions and 9 deletions.
1 change: 1 addition & 0 deletions docs/pages/x/api/tree-view/tree-view.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"props": {
"checkboxSelection": { "type": { "name": "bool" } },
"children": { "type": { "name": "node" } },
"classes": { "type": { "name": "object" }, "additionalInfo": { "cssApi": true } },
"className": { "type": { "name": "string" } },
Expand Down
7 changes: 6 additions & 1 deletion docs/translations/api-docs/tree-view/tree-view.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
{
"componentDescription": "",
"propDescriptions": {
"checkboxSelection": {
"description": "If <code>true</code>, the tree view renders a checkbox at the left of its label that allows selecting it.",
"deprecated": "",
"typeDescriptions": {}
},
"children": {
"description": "The content of the component.",
"deprecated": "",
Expand Down Expand Up @@ -67,7 +72,7 @@
"typeDescriptions": {}
},
"multiSelect": {
"description": "If true <code>ctrl</code> and <code>shift</code> will trigger multiselect.",
"description": "If <code>true</code>, <code>ctrl</code> and <code>shift</code> will trigger multiselect.",
"deprecated": "",
"typeDescriptions": {}
},
Expand Down
20 changes: 19 additions & 1 deletion packages/x-tree-view/src/TreeItem/TreeItemContent.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import clsx from 'clsx';
import Checkbox from '@mui/material/Checkbox';
import { useTreeItem } from './useTreeItem';

export interface TreeItemContentProps extends React.HTMLAttributes<HTMLElement> {
Expand Down Expand Up @@ -76,12 +77,14 @@ const TreeItemContent = React.forwardRef(function TreeItemContent(
expanded,
selected,
focused,
checkboxSelection,
handleExpansion,
handleSelection,
preventSelection,
} = useTreeItem(nodeId);

const icon = iconProp || expansionIcon || displayIcon;
const checkboxRef = React.useRef<HTMLButtonElement>(null);

const handleMouseDown = (event: React.MouseEvent<HTMLDivElement>) => {
preventSelection(event);
Expand All @@ -92,14 +95,25 @@ const TreeItemContent = React.forwardRef(function TreeItemContent(
};

const handleClick = (event: React.MouseEvent<HTMLDivElement>) => {
if (checkboxRef.current?.contains(event.target as HTMLElement)) {
return;
}

handleExpansion(event);
handleSelection(event);

if (!checkboxSelection) {
handleSelection(event);
}

if (onClick) {
onClick(event);
}
};

const handleCheckboxSelectionChange = (event: React.ChangeEvent<HTMLInputElement>) => {
handleSelection(event);
};

return (
/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions -- Key event is handled by the TreeView */
<div
Expand All @@ -115,6 +129,10 @@ const TreeItemContent = React.forwardRef(function TreeItemContent(
ref={ref}
>
<div className={classes.iconContainer}>{icon}</div>
{checkboxSelection && (
<Checkbox checked={selected} onChange={handleCheckboxSelectionChange} ref={checkboxRef} />
)}

<div className={classes.label}>{label}</div>
</div>
);
Expand Down
14 changes: 10 additions & 4 deletions packages/x-tree-view/src/TreeItem/useTreeItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useTreeViewContext } from '../internals/TreeViewProvider/useTreeViewCon
import { DefaultTreeViewPlugins } from '../internals/plugins';

export function useTreeItem(nodeId: string) {
const { instance, multiSelect } = useTreeViewContext<DefaultTreeViewPlugins>();
const { instance, multiSelect, checkboxSelection } = useTreeViewContext<DefaultTreeViewPlugins>();

const expandable = instance ? instance.isNodeExpandable(nodeId) : false;
const expanded = instance ? instance.isNodeExpanded(nodeId) : false;
Expand All @@ -26,16 +26,21 @@ export function useTreeItem(nodeId: string) {
}
};

const handleSelection = (event: React.MouseEvent<HTMLDivElement>) => {
const handleSelection = (event: React.ChangeEvent | React.MouseEvent) => {
if (instance && !disabled) {
if (!focused) {
instance.focusNode(event, nodeId);
}

const multiple = multiSelect && (event.shiftKey || event.ctrlKey || event.metaKey);
const nativeEvent = (event.type === 'change'
? event.nativeEvent
: event) as unknown as React.MouseEvent;

const multiple =
multiSelect && (nativeEvent.shiftKey || nativeEvent.ctrlKey || nativeEvent.metaKey);

if (multiple) {
if (event.shiftKey) {
if (nativeEvent.shiftKey) {
instance.selectRange(event, { end: nodeId });
} else {
instance.selectNode(event, nodeId, true);
Expand All @@ -58,6 +63,7 @@ export function useTreeItem(nodeId: string) {
expanded,
selected,
focused,
checkboxSelection,
handleExpansion,
handleSelection,
preventSelection,
Expand Down
9 changes: 8 additions & 1 deletion packages/x-tree-view/src/TreeView/TreeView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ const TreeView = React.forwardRef(function TreeView<
defaultSelected,
selected,
multiSelect,
checkboxSelection,
onNodeSelect,
id,
defaultCollapseIcon,
Expand All @@ -84,6 +85,7 @@ const TreeView = React.forwardRef(function TreeView<
defaultSelected,
selected,
multiSelect,
checkboxSelection,
onNodeSelect,
id,
defaultCollapseIcon,
Expand Down Expand Up @@ -117,6 +119,11 @@ TreeView.propTypes = {
// | These PropTypes are generated from the TypeScript type definitions |
// | To update them edit the TypeScript types and run "yarn proptypes" |
// ----------------------------------------------------------------------
/**
* If `true`, the tree view renders a checkbox at the left of its label that allows selecting it.
* @default false
*/
checkboxSelection: PropTypes.bool,
/**
* The content of the component.
*/
Expand Down Expand Up @@ -183,7 +190,7 @@ TreeView.propTypes = {
*/
id: PropTypes.string,
/**
* If true `ctrl` and `shift` will trigger multiselect.
* If `true`, `ctrl` and `shift` will trigger multiselect.
* @default false
*/
multiSelect: PropTypes.bool,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export interface TreeViewContextValue<TPlugins extends readonly TreeViewAnyPlugi
treeId: string | undefined;
instance: TreeViewInstance<TPlugins> | null;
multiSelect: boolean;
checkboxSelection: boolean;
disabledItemsFocusable: boolean;
icons: {
defaultCollapseIcon: React.ReactNode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export const useTreeViewContextValueBuilder: TreeViewPlugin<
treeId,
instance: instance as TreeViewInstance<any>,
multiSelect: params.multiSelect,
checkboxSelection: params.checkboxSelection,
disabledItemsFocusable: params.disabledItemsFocusable,
icons: {
defaultCollapseIcon: params.defaultCollapseIcon,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,5 +206,6 @@ useTreeViewSelection.getDefaultizedParams = (params) => ({
...params,
disableSelection: params.disableSelection ?? false,
multiSelect: params.multiSelect ?? false,
checkboxSelection: params.checkboxSelection ?? false,
defaultSelected: params.defaultSelected ?? (params.multiSelect ? DEFAULT_SELECTED : null),
});
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,15 @@ export interface UseTreeViewSelectionParameters<Multiple extends boolean | undef
*/
selected?: TreeViewSelectionValue<Multiple>;
/**
* If true `ctrl` and `shift` will trigger multiselect.
* If `true`, `ctrl` and `shift` will trigger multiselect.
* @default false
*/
multiSelect?: Multiple;
/**
* If `true`, the tree view renders a checkbox at the left of its label that allows selecting it.
* @default false
*/
checkboxSelection?: boolean;
/**
* Callback fired when tree items are selected/unselected.
* @param {React.SyntheticEvent} event The event source of the callback
Expand All @@ -51,7 +56,7 @@ export interface UseTreeViewSelectionParameters<Multiple extends boolean | undef

export type UseTreeViewSelectionDefaultizedParameters<Multiple extends boolean> = DefaultizedProps<
UseTreeViewSelectionParameters<Multiple>,
'disableSelection' | 'defaultSelected' | 'multiSelect'
'disableSelection' | 'defaultSelected' | 'multiSelect' | 'checkboxSelection'
>;

export type UseTreeViewSelectionSignature<Multiple extends boolean | undefined> =
Expand Down

0 comments on commit 4e7ee8d

Please sign in to comment.