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

[docs] New demo of a Tree View and a 3D canvas #13214

Draft
wants to merge 10 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions docs/data/tree-view/rich-tree-view/customization/ThreeDCanvas.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import * as React from 'react';
import Stack from '@mui/material/Stack';
import { RichTreeView } from '@mui/x-tree-view/RichTreeView';
import { CustomTreeItem } from './three-d/CustomTreeItem';
import { Scene } from './three-d/Scene';
import { ALL_SCENE_OBJECTS } from './three-d/SceneObjects';

const DEFAULT_EXPANDED_ITEMS = ['lights', 'chassi', 'wheels', 'car'];

export default function ThreeDCanvas() {
const [sceneObjects, setSceneObjects] = React.useState(ALL_SCENE_OBJECTS);

const toggleVisibility = (itemId) => {
const toggleItemVisibility = (item) => {
if (item.id === itemId) {
return { ...item, visibility: !item.visibility };
}

if (item.type === 'collection') {
return {
...item,
children: item.children.map(toggleItemVisibility),
};
}

return item;
};

setSceneObjects((prevState) => prevState.map(toggleItemVisibility));
};
return (
<Stack spacing={2} sx={{ flexGrow: 1, position: 'relative' }}>
<RichTreeView
items={sceneObjects}
defaultExpandedItems={DEFAULT_EXPANDED_ITEMS}
slots={{ item: CustomTreeItem }}
slotProps={{
item: { toggleVisibility },
}}
/>
<Scene items={sceneObjects} />
</Stack>
);
}
44 changes: 44 additions & 0 deletions docs/data/tree-view/rich-tree-view/customization/ThreeDCanvas.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import * as React from 'react';
import Stack from '@mui/material/Stack';
import { RichTreeView } from '@mui/x-tree-view/RichTreeView';
import { CustomTreeItem } from './three-d/CustomTreeItem';
import { Scene } from './three-d/Scene';
import { ALL_SCENE_OBJECTS, ThreeDItem } from './three-d/SceneObjects';

const DEFAULT_EXPANDED_ITEMS = ['lights', 'chassi', 'wheels', 'car'];

export default function ThreeDCanvas() {
const [sceneObjects, setSceneObjects] = React.useState(ALL_SCENE_OBJECTS);

const toggleVisibility = (itemId: string) => {
const toggleItemVisibility = (item: ThreeDItem): ThreeDItem => {
if (item.id === itemId) {
return { ...item, visibility: !item.visibility };
}

if (item.type === 'collection') {
return {
...item,
children: item.children.map(toggleItemVisibility),
};
}

return item;
};

setSceneObjects((prevState) => prevState.map(toggleItemVisibility));
};
return (
<Stack spacing={2} sx={{ flexGrow: 1, position: 'relative' }}>
<RichTreeView
items={sceneObjects}
defaultExpandedItems={DEFAULT_EXPANDED_ITEMS}
slots={{ item: CustomTreeItem as any }}
slotProps={{
item: { toggleVisibility } as any,
}}
/>
<Scene items={sceneObjects} />
</Stack>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<RichTreeView
items={sceneObjects}
defaultExpandedItems={DEFAULT_EXPANDED_ITEMS}
slots={{ item: CustomTreeItem as any }}
slotProps={{
item: { toggleVisibility } as any,
}}
/>
<Scene items={sceneObjects} />
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,9 @@ You can learn more about this new component in the [Overview page](/x/react-tree
The demo below shows many of the previous customization examples brought together to make the Tree View component look completely different than its default design.

{{"demo": "FileExplorer.js", "defaultCodeOpen": false}}

### 3D Canvas

This example is built using the new `useTreeItem`.

{{"demo": "ThreeDCanvas.js", "defaultCodeOpen": false}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import * as React from 'react';
import VisibilityIcon from '@mui/icons-material/Visibility';
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
import ViewInArOutlinedIcon from '@mui/icons-material/ViewInArOutlined';
import LightbulbOutlinedIcon from '@mui/icons-material/LightbulbOutlined';
import FolderOutlinedIcon from '@mui/icons-material/FolderOutlined';
import { unstable_useTreeItem2 as useTreeItem2 } from '@mui/x-tree-view/useTreeItem2';
import {
TreeItem2Content,
TreeItem2IconContainer,
TreeItem2Label,
TreeItem2Root,
TreeItem2GroupTransition,
} from '@mui/x-tree-view/TreeItem2';
import { TreeItem2Icon } from '@mui/x-tree-view/TreeItem2Icon';
import { TreeItem2Provider } from '@mui/x-tree-view/TreeItem2Provider';
import { useTreeItem2Utils } from '@mui/x-tree-view/hooks';

export const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) {
const { id, itemId, label, children, sceneObjects, toggleVisibility, ...other } =
props;

const {
getRootProps,
getContentProps,
getIconContainerProps,
getLabelProps,
getGroupTransitionProps,
status,
publicAPI,
} = useTreeItem2({ id, itemId, children, label, rootRef: ref });

const { interactions } = useTreeItem2Utils({
itemId: props.itemId,
children: props.children,
});

const item = publicAPI.getItem(itemId);

const handleVisibilityIconClick = (event) => {
event.defaultMuiPrevented = true;
toggleVisibility(itemId);
};

const handleContentClick = (event) => {
if (event.defaultMuiPrevented) {
return;
}

event.defaultMuiPrevented = true;
interactions.handleSelection(event);
};

const handleIconContainerClick = (event) => {
interactions.handleExpansion(event);
};

let itemIcon;
switch (item.type) {
case 'mesh':
itemIcon = <ViewInArOutlinedIcon style={{ color: 'darkolivegreen' }} />;
break;
case 'light':
itemIcon = <LightbulbOutlinedIcon style={{ color: 'yellow' }} />;
break;
case 'collection':
itemIcon = <FolderOutlinedIcon style={{ color: 'gray' }} />;
break;
default:
itemIcon = null;
}

return (
<TreeItem2Provider itemId={itemId}>
<TreeItem2Root {...getRootProps(other)}>
<TreeItem2Content
{...getContentProps({
onClick: handleContentClick,
sx: { gap: '12px' },
})}
>
<TreeItem2IconContainer
{...getIconContainerProps({ onClick: handleIconContainerClick })}
>
<TreeItem2Icon status={status} />
</TreeItem2IconContainer>
<TreeItem2IconContainer onClick={handleVisibilityIconClick}>
{item.visibility ? (
<VisibilityIcon color="primary" />
) : (
<VisibilityOffIcon sx={{ color: '#555' }} />
)}
</TreeItem2IconContainer>
{itemIcon}
<TreeItem2Label
{...getLabelProps({
sx: {
opacity: item.visibility ? 1 : 0.5,
},
})}
/>
</TreeItem2Content>
{children && <TreeItem2GroupTransition {...getGroupTransitionProps()} />}
</TreeItem2Root>
</TreeItem2Provider>
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import * as React from 'react';
import VisibilityIcon from '@mui/icons-material/Visibility';
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
import ViewInArOutlinedIcon from '@mui/icons-material/ViewInArOutlined';
import LightbulbOutlinedIcon from '@mui/icons-material/LightbulbOutlined';
import FolderOutlinedIcon from '@mui/icons-material/FolderOutlined';
import {
unstable_useTreeItem2 as useTreeItem2,
UseTreeItem2Parameters,
UseTreeItem2ContentSlotOwnProps,
} from '@mui/x-tree-view/useTreeItem2';
import {
TreeItem2Content,
TreeItem2IconContainer,
TreeItem2Label,
TreeItem2Root,
TreeItem2GroupTransition,
} from '@mui/x-tree-view/TreeItem2';
import { TreeItem2Icon } from '@mui/x-tree-view/TreeItem2Icon';
import { TreeItem2Provider } from '@mui/x-tree-view/TreeItem2Provider';
import { useTreeItem2Utils } from '@mui/x-tree-view/hooks';
import { ThreeDItem } from './SceneObjects';

interface CustomTreeItemProps extends Omit<UseTreeItem2Parameters, 'rootRef'> {
toggleVisibility: (itemId: string) => void;
sceneObjects: ThreeDItem[];
}

export const CustomTreeItem = React.forwardRef(function CustomTreeItem(
props: CustomTreeItemProps,
ref: React.Ref<HTMLLIElement>,
) {
const { id, itemId, label, children, sceneObjects, toggleVisibility, ...other } =
props;

const {
getRootProps,
getContentProps,
getIconContainerProps,
getLabelProps,
getGroupTransitionProps,
status,
publicAPI,
} = useTreeItem2({ id, itemId, children, label, rootRef: ref });

const { interactions } = useTreeItem2Utils({
itemId: props.itemId,
children: props.children,
});

const item = publicAPI.getItem(itemId);

const handleVisibilityIconClick = (
event: React.MouseEvent & { defaultMuiPrevented?: boolean },
) => {
event.defaultMuiPrevented = true;
toggleVisibility(itemId);
};

const handleContentClick: UseTreeItem2ContentSlotOwnProps['onClick'] = (event) => {
if (event.defaultMuiPrevented) {
return;
}

event.defaultMuiPrevented = true;
interactions.handleSelection(event);
};

const handleIconContainerClick = (event: React.MouseEvent) => {
interactions.handleExpansion(event);
};

let itemIcon: React.ReactNode;
switch (item.type) {
case 'mesh':
itemIcon = <ViewInArOutlinedIcon style={{ color: 'darkolivegreen' }} />;
break;
case 'light':
itemIcon = <LightbulbOutlinedIcon style={{ color: 'yellow' }} />;
break;
case 'collection':
itemIcon = <FolderOutlinedIcon style={{ color: 'gray' }} />;
break;
default:
itemIcon = null;
}

return (
<TreeItem2Provider itemId={itemId}>
<TreeItem2Root {...getRootProps(other)}>
<TreeItem2Content
{...getContentProps({
onClick: handleContentClick,
sx: { gap: '12px' },
})}
>
<TreeItem2IconContainer
{...getIconContainerProps({ onClick: handleIconContainerClick })}
>
<TreeItem2Icon status={status} />
</TreeItem2IconContainer>
<TreeItem2IconContainer onClick={handleVisibilityIconClick}>
{item.visibility ? (
<VisibilityIcon color="primary" />
) : (
<VisibilityOffIcon sx={{ color: '#555' }} />
)}
</TreeItem2IconContainer>
{itemIcon}
<TreeItem2Label
{...getLabelProps({
sx: {
opacity: item.visibility ? 1 : 0.5,
},
})}
/>
</TreeItem2Content>
{children && <TreeItem2GroupTransition {...getGroupTransitionProps()} />}
</TreeItem2Root>
</TreeItem2Provider>
);
});
Loading