Skip to content

Commit

Permalink
[TreeView] Allow to define indentation at the item level (mui#13126)
Browse files Browse the repository at this point in the history
  • Loading branch information
flaviendelangle authored and thomasmoon committed Sep 6, 2024
1 parent 8cebe9e commit 1f92246
Show file tree
Hide file tree
Showing 32 changed files with 253 additions and 41 deletions.
3 changes: 3 additions & 0 deletions docs/pages/x/api/tree-view/rich-tree-view.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
"disabledItemsFocusable": { "type": { "name": "bool" }, "default": "false" },
"disableSelection": { "type": { "name": "bool" }, "default": "false" },
"expandedItems": { "type": { "name": "arrayOf", "description": "Array<string>" } },
"experimentalFeatures": {
"type": { "name": "shape", "description": "{ indentationAtItemLevel?: bool }" }
},
"getItemId": {
"type": { "name": "func" },
"default": "(item) => item.id",
Expand Down
3 changes: 3 additions & 0 deletions docs/pages/x/api/tree-view/simple-tree-view.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
"disabledItemsFocusable": { "type": { "name": "bool" }, "default": "false" },
"disableSelection": { "type": { "name": "bool" }, "default": "false" },
"expandedItems": { "type": { "name": "arrayOf", "description": "Array<string>" } },
"experimentalFeatures": {
"type": { "name": "shape", "description": "{ indentationAtItemLevel?: bool }" }
},
"id": { "type": { "name": "string" } },
"multiSelect": { "type": { "name": "bool" }, "default": "false" },
"onExpandedItemsChange": {
Expand Down
3 changes: 3 additions & 0 deletions docs/pages/x/api/tree-view/tree-view.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
"disabledItemsFocusable": { "type": { "name": "bool" }, "default": "false" },
"disableSelection": { "type": { "name": "bool" }, "default": "false" },
"expandedItems": { "type": { "name": "arrayOf", "description": "Array<string>" } },
"experimentalFeatures": {
"type": { "name": "shape", "description": "{ indentationAtItemLevel?: bool }" }
},
"id": { "type": { "name": "string" } },
"multiSelect": { "type": { "name": "bool" }, "default": "false" },
"onExpandedItemsChange": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
"expandedItems": {
"description": "Expanded item ids. Used when the item's expansion is controlled."
},
"experimentalFeatures": {
"description": "Unstable features, breaking changes might be introduced. For each feature, if the flag is not explicitly set to <code>true</code>, the feature will be fully disabled and any property / method call will not have any effect."
},
"getItemId": {
"description": "Used to determine the id of a given item.",
"typeDescriptions": { "item": "The item to check.", "string": "The id of the item." }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
"expandedItems": {
"description": "Expanded item ids. Used when the item&#39;s expansion is controlled."
},
"experimentalFeatures": {
"description": "Unstable features, breaking changes might be introduced. For each feature, if the flag is not explicitly set to <code>true</code>, the feature will be fully disabled and any property / method call will not have any effect."
},
"id": {
"description": "This prop is used to help implement the accessibility logic. If you don&#39;t provide this prop. It falls back to a randomly generated id."
},
Expand Down
3 changes: 3 additions & 0 deletions docs/translations/api-docs/tree-view/tree-view/tree-view.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
"expandedItems": {
"description": "Expanded item ids. Used when the item&#39;s expansion is controlled."
},
"experimentalFeatures": {
"description": "Unstable features, breaking changes might be introduced. For each feature, if the flag is not explicitly set to <code>true</code>, the feature will be fully disabled and any property / method call will not have any effect."
},
"id": {
"description": "This prop is used to help implement the accessibility logic. If you don&#39;t provide this prop. It falls back to a randomly generated id."
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { SlotComponentProps } from '@mui/base/utils';
import { TreeItem, TreeItemProps } from '@mui/x-tree-view/TreeItem';
import { TreeItem2Props } from '@mui/x-tree-view/TreeItem2';
import { TreeViewItemId } from '@mui/x-tree-view/models';
import { TreeViewPublicAPI } from '@mui/x-tree-view/internals';
import { TreeViewPublicAPI, TreeViewExperimentalFeatures } from '@mui/x-tree-view/internals';
import { RichTreeViewProClasses } from './richTreeViewProClasses';
import {
DefaultTreeViewProPluginParameters,
Expand Down Expand Up @@ -71,4 +71,10 @@ export interface RichTreeViewProProps<R extends {}, Multiple extends boolean | u
* The ref object that allows Tree View manipulation. Can be instantiated with `useTreeViewApiRef()`.
*/
apiRef?: RichTreeViewProApiRef;
/**
* Unstable features, breaking changes might be introduced.
* For each feature, if the flag is not explicitly set to `true`,
* the feature will be fully disabled and any property / method call will not have any effect.
*/
experimentalFeatures?: TreeViewExperimentalFeatures<DefaultTreeViewProPlugins>;
}
8 changes: 8 additions & 0 deletions packages/x-tree-view/src/RichTreeView/RichTreeView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,14 @@ RichTreeView.propTypes = {
* Used when the item's expansion is controlled.
*/
expandedItems: PropTypes.arrayOf(PropTypes.string),
/**
* Unstable features, breaking changes might be introduced.
* For each feature, if the flag is not explicitly set to `true`,
* the feature will be fully disabled and any property / method call will not have any effect.
*/
experimentalFeatures: PropTypes.shape({
indentationAtItemLevel: PropTypes.bool,
}),
/**
* Used to determine the id of a given item.
*
Expand Down
12 changes: 11 additions & 1 deletion packages/x-tree-view/src/RichTreeView/RichTreeView.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ import {
import { TreeItemProps } from '../TreeItem';
import { TreeItem2Props } from '../TreeItem2';
import { TreeViewItemId } from '../models';
import { SlotComponentPropsFromProps, TreeViewPublicAPI } from '../internals/models';
import {
SlotComponentPropsFromProps,
TreeViewExperimentalFeatures,
TreeViewPublicAPI,
} from '../internals/models';

interface RichTreeViewItemSlotOwnerState {
itemId: TreeViewItemId;
Expand Down Expand Up @@ -75,4 +79,10 @@ export interface RichTreeViewProps<R extends {}, Multiple extends boolean | unde
* The ref object that allows Tree View manipulation. Can be instantiated with `useTreeViewApiRef()`.
*/
apiRef?: RichTreeViewApiRef;
/**
* Unstable features, breaking changes might be introduced.
* For each feature, if the flag is not explicitly set to `true`,
* the feature will be fully disabled and any property / method call will not have any effect.
*/
experimentalFeatures?: TreeViewExperimentalFeatures<DefaultTreeViewPlugins>;
}
8 changes: 8 additions & 0 deletions packages/x-tree-view/src/SimpleTreeView/SimpleTreeView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,14 @@ SimpleTreeView.propTypes = {
* Used when the item's expansion is controlled.
*/
expandedItems: PropTypes.arrayOf(PropTypes.string),
/**
* Unstable features, breaking changes might be introduced.
* For each feature, if the flag is not explicitly set to `true`,
* the feature will be fully disabled and any property / method call will not have any effect.
*/
experimentalFeatures: PropTypes.shape({
indentationAtItemLevel: PropTypes.bool,
}),
/**
* This prop is used to help implement the accessibility logic.
* If you don't provide this prop. It falls back to a randomly generated id.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
SimpleTreeViewPluginSlots,
SimpleTreeViewPlugins,
} from './SimpleTreeView.plugins';
import { TreeViewPublicAPI } from '../internals/models';
import { TreeViewExperimentalFeatures, TreeViewPublicAPI } from '../internals/models';

export interface SimpleTreeViewSlots extends SimpleTreeViewPluginSlots {
/**
Expand Down Expand Up @@ -55,4 +55,10 @@ export interface SimpleTreeViewProps<Multiple extends boolean | undefined>
* The ref object that allows Tree View manipulation. Can be instantiated with `useTreeViewApiRef()`.
*/
apiRef?: SimpleTreeViewApiRef;
/**
* Unstable features, breaking changes might be introduced.
* For each feature, if the flag is not explicitly set to `true`,
* the feature will be fully disabled and any property / method call will not have any effect.
*/
experimentalFeatures?: TreeViewExperimentalFeatures<SimpleTreeViewPlugins>;
}
1 change: 1 addition & 0 deletions packages/x-tree-view/src/TreeItem/TreeItem.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const TEST_TREE_VIEW_CONTEXT_VALUE: TreeViewContextValue<SimpleTreeViewPlugins>
wrapItem: ({ children }) => children,
wrapRoot: ({ children }) => children,
disabledItemsFocusable: false,
indentationAtItemLevel: false,
icons: {
slots: {},
slotProps: {},
Expand Down
31 changes: 31 additions & 0 deletions packages/x-tree-view/src/TreeItem/TreeItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import clsx from 'clsx';
import Collapse from '@mui/material/Collapse';
import { resolveComponentProps, useSlotProps } from '@mui/base/utils';
import useForkRef from '@mui/utils/useForkRef';
import { shouldForwardProp } from '@mui/system/createStyled';
import { alpha, styled, useThemeProps } from '@mui/material/styles';
import { TransitionProps } from '@mui/material/transitions';
import unsupportedProp from '@mui/utils/unsupportedProp';
Expand All @@ -16,6 +17,7 @@ import { useTreeViewContext } from '../internals/TreeViewProvider/useTreeViewCon
import { DefaultTreeViewPlugins } from '../internals/plugins';
import { TreeViewCollapseIcon, TreeViewExpandIcon } from '../icons';
import { TreeItem2Provider } from '../TreeItem2Provider';
import { TreeViewItemDepthContext } from '../internals/TreeViewItemDepthContext';

const useUtilityClasses = (ownerState: TreeItemOwnerState) => {
const { classes } = ownerState;
Expand Down Expand Up @@ -61,6 +63,7 @@ const StyledTreeItemContent = styled(TreeItemContent, {
},
];
},
shouldForwardProp: (prop) => shouldForwardProp(prop) && prop !== 'indentationAtItemLevel',
})<{ ownerState: TreeItemOwnerState }>(({ theme }) => ({
padding: theme.spacing(0.5, 1),
borderRadius: theme.shape.borderRadius,
Expand Down Expand Up @@ -132,16 +135,31 @@ const StyledTreeItemContent = styled(TreeItemContent, {
[`& .${treeItemClasses.checkbox}`]: {
padding: 0,
},
variants: [
{
props: { indentationAtItemLevel: true },
style: {
paddingLeft: `calc(${theme.spacing(1)} + 12px * var(--TreeView-itemDepth))`,
},
},
],
}));

const TreeItemGroup = styled(Collapse, {
name: 'MuiTreeItem',
slot: 'GroupTransition',
overridesResolver: (props, styles) => styles.groupTransition,
shouldForwardProp: (prop) => shouldForwardProp(prop) && prop !== 'indentationAtItemLevel',
})({
margin: 0,
padding: 0,
paddingLeft: 12,
variants: [
{
props: { indentationAtItemLevel: true },
style: { paddingLeft: 0 },
},
],
});

/**
Expand All @@ -163,8 +181,10 @@ export const TreeItem = React.forwardRef(function TreeItem(
runItemPlugins,
selection: { multiSelect },
disabledItemsFocusable,
indentationAtItemLevel,
instance,
} = useTreeViewContext<DefaultTreeViewPlugins>();
const depthContext = React.useContext(TreeViewItemDepthContext);

const props = useThemeProps({ props: inProps, name: 'MuiTreeItem' });

Expand Down Expand Up @@ -216,6 +236,7 @@ export const TreeItem = React.forwardRef(function TreeItem(
focused,
selected,
disabled,
indentationAtItemLevel,
};

const classes = useUtilityClasses(ownerState);
Expand All @@ -230,6 +251,7 @@ export const TreeItem = React.forwardRef(function TreeItem(
in: expanded,
component: 'ul',
role: 'group',
...(indentationAtItemLevel ? { indentationAtItemLevel: true } : {}),
},
className: classes.groupTransition,
});
Expand Down Expand Up @@ -329,6 +351,15 @@ export const TreeItem = React.forwardRef(function TreeItem(
onBlur={handleBlur}
onKeyDown={handleKeyDown}
ref={handleRootRef}
style={
indentationAtItemLevel
? ({
...other.style,
'--TreeView-itemDepth':
typeof depthContext === 'function' ? depthContext(itemId) : depthContext,
} as React.CSSProperties)
: other.style
}
>
<StyledTreeItemContent
as={ContentComponent}
Expand Down
1 change: 1 addition & 0 deletions packages/x-tree-view/src/TreeItem/TreeItem.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,5 @@ export interface TreeItemOwnerState extends TreeItemProps {
focused: boolean;
selected: boolean;
disabled: boolean;
indentationAtItemLevel: boolean;
}
31 changes: 20 additions & 11 deletions packages/x-tree-view/src/TreeItem2/TreeItem2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ import { alpha, styled, useThemeProps } from '@mui/material/styles';
import Collapse from '@mui/material/Collapse';
import MuiCheckbox, { CheckboxProps } from '@mui/material/Checkbox';
import { useSlotProps } from '@mui/base/utils';
import { shouldForwardProp } from '@mui/system';
import { shouldForwardProp } from '@mui/system/createStyled';
import composeClasses from '@mui/utils/composeClasses';
import { TreeItem2Props, TreeItem2OwnerState } from './TreeItem2.types';
import {
unstable_useTreeItem2 as useTreeItem2,
UseTreeItem2ContentSlotOwnProps,
UseTreeItem2Status,
} from '../useTreeItem2';
import { getTreeItemUtilityClass, treeItemClasses } from '../TreeItem';
import { getTreeItemUtilityClass } from '../TreeItem';
import { TreeItem2Icon } from '../TreeItem2Icon';
import { TreeItem2Provider } from '../TreeItem2Provider';

Expand All @@ -32,8 +33,9 @@ export const TreeItem2Content = styled('div', {
name: 'MuiTreeItem2',
slot: 'Content',
overridesResolver: (props, styles) => styles.content,
shouldForwardProp: (prop) => shouldForwardProp(prop) && prop !== 'status',
})(({ theme }) => ({
shouldForwardProp: (prop) =>
shouldForwardProp(prop) && prop !== 'status' && prop !== 'indentationAtItemLevel',
})<{ status: UseTreeItem2Status; indentationAtItemLevel?: true }>(({ theme }) => ({
padding: theme.spacing(0.5, 1),
borderRadius: theme.shape.borderRadius,
width: '100%',
Expand All @@ -50,12 +52,13 @@ export const TreeItem2Content = styled('div', {
backgroundColor: 'transparent',
},
},
[`& .${treeItemClasses.groupTransition}`]: {
margin: 0,
padding: 0,
paddingLeft: 12,
},
variants: [
{
props: { indentationAtItemLevel: true },
style: {
paddingLeft: `calc(${theme.spacing(1)} + 12px * var(--TreeView-itemDepth))`,
},
},
{
props: ({ status }: UseTreeItem2ContentSlotOwnProps) => status.disabled,
style: {
Expand Down Expand Up @@ -134,10 +137,17 @@ export const TreeItem2GroupTransition = styled(Collapse, {
name: 'MuiTreeItem2',
slot: 'GroupTransition',
overridesResolver: (props, styles) => styles.groupTransition,
})({
shouldForwardProp: (prop) => shouldForwardProp(prop) && prop !== 'indentationAtItemLevel',
})<{ indentationAtItemLevel?: true }>({
margin: 0,
padding: 0,
paddingLeft: 12,
variants: [
{
props: { indentationAtItemLevel: true },
style: { paddingLeft: 0 },
},
],
});

export const TreeItem2Checkbox = styled(
Expand Down Expand Up @@ -250,7 +260,6 @@ export const TreeItem2 = React.forwardRef(function TreeItem2(
[classes.disabled]: status.disabled,
}),
});

const IconContainer: React.ElementType = slots.iconContainer ?? TreeItem2IconContainer;
const iconContainerProps = useSlotProps({
elementType: IconContainer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { useTreeViewContext } from '../internals/TreeViewProvider/useTreeViewCon

function TreeItem2Provider(props: TreeItem2ProviderProps) {
const { children, itemId } = props;
const { wrapItem } = useTreeViewContext<[]>();
const { wrapItem, instance } = useTreeViewContext<[]>();

return wrapItem({ children, itemId });
return wrapItem({ children, itemId, instance });
}

TreeItem2Provider.propTypes = {
Expand Down
8 changes: 8 additions & 0 deletions packages/x-tree-view/src/TreeView/TreeView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,14 @@ TreeView.propTypes = {
* Used when the item's expansion is controlled.
*/
expandedItems: PropTypes.arrayOf(PropTypes.string),
/**
* Unstable features, breaking changes might be introduced.
* For each feature, if the flag is not explicitly set to `true`,
* the feature will be fully disabled and any property / method call will not have any effect.
*/
experimentalFeatures: PropTypes.shape({
indentationAtItemLevel: PropTypes.bool,
}),
/**
* This prop is used to help implement the accessibility logic.
* If you don't provide this prop. It falls back to a randomly generated id.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import * as React from 'react';
import { TreeViewItemId } from '../../models';

export const TreeViewItemDepthContext = React.createContext<
number | ((itemId: TreeViewItemId) => number)
>(() => -1);

if (process.env.NODE_ENV !== 'production') {
TreeViewItemDepthContext.displayName = 'TreeViewItemDepthContext';
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { TreeViewItemDepthContext } from './TreeViewItemDepthContext';
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ export type TreeViewContextValue<TPlugins extends readonly TreeViewAnyPluginSign
instance: TreeViewInstance<TPlugins>;
publicAPI: TreeViewPublicAPI<TPlugins>;
rootRef: React.RefObject<HTMLUListElement>;
wrapItem: TreeItemWrapper;
wrapRoot: TreeRootWrapper;
wrapItem: TreeItemWrapper<TPlugins>;
wrapRoot: TreeRootWrapper<TPlugins>;
runItemPlugins: <TProps extends {}>(props: TProps) => Required<TreeViewItemPluginResponse>;
};

Expand Down
Loading

0 comments on commit 1f92246

Please sign in to comment.