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

[TreeView] Create RichTreeViewPro component #12610

Merged
merged 16 commits into from
Apr 4, 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
1 change: 1 addition & 0 deletions docs/pages/_app.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ ponyfillGlobal.muiDocConfig = {
'@mui/x-date-pickers-pro': getMuiPackageVersion('x-date-pickers-pro', muiCommitRef),
'@mui/x-charts': getMuiPackageVersion('x-charts', muiCommitRef),
'@mui/x-tree-view': getMuiPackageVersion('x-tree-view', muiCommitRef),
'@mui/x-tree-view-pro': getMuiPackageVersion('x-tree-view-pro', muiCommitRef),
exceljs: 'latest',
};
return output;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ import MuiLicenseInfoContext from '../Unstable_LicenseInfoProvider/MuiLicenseInf
export type MuiCommercialPackageName =
| 'x-data-grid-pro'
| 'x-data-grid-premium'
| 'x-date-pickers-pro';
| 'x-date-pickers-pro'
| 'x-tree-view-pro';

export const sharedLicenseStatuses: {
[packageName in MuiCommercialPackageName]?: {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import * as React from 'react';
import { createRenderer } from '@mui-internal/test-utils';
import {
RichTreeViewPro,
richTreeViewProClasses as classes,
} from '@mui/x-tree-view-pro/RichTreeViewPro';
import { describeConformance } from 'test/utils/describeConformance';

describe('<RichTreeViewPro />', () => {
const { render } = createRenderer();

describeConformance(<RichTreeViewPro items={[]} />, () => ({
classes,
inheritComponent: 'ul',
render,
refInstanceof: window.HTMLUListElement,
muiName: 'MuiRichTreeViewPro',
skip: ['componentProp', 'componentsProp', 'themeVariants'],
}));
});
159 changes: 159 additions & 0 deletions packages/x-tree-view-pro/src/RichTreeViewPro/RichTreeViewPro.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import * as React from 'react';
import { styled, useThemeProps } from '@mui/material/styles';
import composeClasses from '@mui/utils/composeClasses';
import { useLicenseVerifier, Watermark } from '@mui/x-license';
import { useSlotProps } from '@mui/base/utils';
import { TreeItem, TreeItemProps } from '@mui/x-tree-view/TreeItem';
import {
useTreeView,
TreeViewProvider,
buildWarning,
extractPluginParamsFromProps,
} from '@mui/x-tree-view/internals';
import { getRichTreeViewProUtilityClass } from './richTreeViewProClasses';
import {
RichTreeViewProProps,
RichTreeViewProSlotProps,
RichTreeViewProSlots,
} from './RichTreeViewPro.types';
import { DEFAULT_TREE_VIEW_PRO_PLUGINS } from '../internals/plugins';
import { getReleaseInfo } from '../internals/utils/releaseInfo';

const useUtilityClasses = <R extends {}, Multiple extends boolean | undefined>(
ownerState: RichTreeViewProProps<R, Multiple>,
) => {
const { classes } = ownerState;

const slots = {
root: ['root'],
};

return composeClasses(slots, getRichTreeViewProUtilityClass, classes);
};

export const RichTreeViewProRoot = styled('ul', {
name: 'MuiRichTreeViewPro',
slot: 'Root',
overridesResolver: (props, styles) => styles.root,
})<{ ownerState: RichTreeViewProProps<any, any> }>({
padding: 0,
margin: 0,
listStyle: 'none',
outline: 0,
position: 'relative',
});

type RichTreeViewProComponent = (<R extends {}, Multiple extends boolean | undefined = undefined>(
props: RichTreeViewProProps<R, Multiple> & React.RefAttributes<HTMLUListElement>,
) => React.JSX.Element) & { propTypes?: any };

function WrappedTreeItem<R extends {}>({
slots,
slotProps,
label,
id,
itemId,
children,
}: Pick<RichTreeViewProProps<R, any>, 'slots' | 'slotProps'> &
Pick<TreeItemProps, 'id' | 'itemId' | 'children'> & { label: string }) {
const Item = slots?.item ?? TreeItem;
const itemProps = useSlotProps({
elementType: Item,
externalSlotProps: slotProps?.item,
additionalProps: { itemId, id, label },
ownerState: { itemId, label },
});

return <Item {...itemProps}>{children}</Item>;
}

const releaseInfo = getReleaseInfo();

const childrenWarning = buildWarning([
'MUI X: The `RichTreeViewPro` component does not support JSX children.',
'If you want to add items, you need to use the `items` prop',
'Check the documentation for more details: https://mui.com/x/react-tree-view/rich-tree-view/items/',
]);

/**
*
* Demos:
*
* - [Tree View](https://mui.com/x/react-tree-view/)
*
* API:
*
* - [RichTreeView API](https://mui.com/x/api/tree-view/rich-tree-view/)
*/
const RichTreeViewPro = React.forwardRef(function RichTreeViewPro<
R extends {},
Multiple extends boolean | undefined = undefined,
>(inProps: RichTreeViewProProps<R, Multiple>, ref: React.Ref<HTMLUListElement>) {
const props = useThemeProps({ props: inProps, name: 'MuiRichTreeViewPro' });

useLicenseVerifier('x-tree-view-pro', releaseInfo);

if (process.env.NODE_ENV !== 'production') {
if ((props as any).children != null) {
childrenWarning();
}
}

const { pluginParams, slots, slotProps, otherProps } = extractPluginParamsFromProps<
typeof DEFAULT_TREE_VIEW_PRO_PLUGINS,
RichTreeViewProSlots,
RichTreeViewProSlotProps<R, Multiple>,
RichTreeViewProProps<R, Multiple>
>({
props,
plugins: DEFAULT_TREE_VIEW_PRO_PLUGINS,
rootRef: ref,
});

const { getRootProps, contextValue, instance } = useTreeView(pluginParams);

const classes = useUtilityClasses(props);

const Root = slots?.root ?? RichTreeViewProRoot;
const rootProps = useSlotProps({
elementType: Root,
externalSlotProps: slotProps?.root,
externalForwardedProps: otherProps,
className: classes.root,
getSlotProps: getRootProps,
ownerState: props as RichTreeViewProProps<any, any>,
});

const itemsToRender = instance.getItemsToRender();

const renderItem = ({
label,
itemId,
id,
children,
}: ReturnType<typeof instance.getItemsToRender>[number]) => {
return (
<WrappedTreeItem
slots={slots}
slotProps={slotProps}
key={itemId}
label={label}
id={id}
itemId={itemId}
>
{children?.map(renderItem)}
</WrappedTreeItem>
);
};

return (
<TreeViewProvider value={contextValue}>
<Root {...rootProps}>
{itemsToRender.map(renderItem)}
<Watermark packageName="x-tree-view-pro" releaseInfo={releaseInfo} />
</Root>
</TreeViewProvider>
);
}) as RichTreeViewProComponent;

export { RichTreeViewPro };
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import * as React from 'react';
import { Theme } from '@mui/material/styles';
import { SxProps } from '@mui/system';
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 { RichTreeViewProClasses } from './richTreeViewProClasses';
import {
DefaultTreeViewProPluginParameters,
DefaultTreeViewProPluginSlotProps,
DefaultTreeViewProPluginSlots,
DefaultTreeViewProPlugins,
} from '../internals/plugins/defaultPlugins';

interface RichTreeViewItemProSlotOwnerState {
itemId: TreeViewItemId;
label: string;
}

export interface RichTreeViewProSlots extends DefaultTreeViewProPluginSlots {
/**
* Element rendered at the root.
* @default RichTreeViewProRoot
*/
root?: React.ElementType;
/**
* Custom component for the item.
* @default TreeItem.
*/
item?: React.JSXElementConstructor<TreeItemProps> | React.JSXElementConstructor<TreeItem2Props>;
}

export interface RichTreeViewProSlotProps<R extends {}, Multiple extends boolean | undefined>
extends DefaultTreeViewProPluginSlotProps {
root?: SlotComponentProps<'ul', {}, RichTreeViewProProps<R, Multiple>>;
item?: SlotComponentProps<typeof TreeItem, {}, RichTreeViewItemProSlotOwnerState>;
}

export type RichTreeViewProApiRef = React.MutableRefObject<
TreeViewPublicAPI<DefaultTreeViewProPlugins> | undefined
>;

export interface RichTreeViewProPropsBase extends React.HTMLAttributes<HTMLUListElement> {
className?: string;
/**
* Override or extend the styles applied to the component.
*/
classes?: Partial<RichTreeViewProClasses>;
/**
* The system prop that allows defining system overrides as well as additional CSS styles.
*/
sx?: SxProps<Theme>;
}

export interface RichTreeViewProProps<R extends {}, Multiple extends boolean | undefined>
extends DefaultTreeViewProPluginParameters<R, Multiple>,
RichTreeViewProPropsBase {
/**
* Overridable component slots.
* @default {}
*/
slots?: RichTreeViewProSlots;
/**
* The props used for each component slot.
* @default {}
*/
slotProps?: RichTreeViewProSlotProps<R, Multiple>;
/**
* The ref object that allows Tree View manipulation. Can be instantiated with `useTreeViewApiRef()`.
*/
apiRef?: RichTreeViewProApiRef;
}
8 changes: 8 additions & 0 deletions packages/x-tree-view-pro/src/RichTreeViewPro/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export * from './RichTreeViewPro';
export * from './richTreeViewProClasses';
export type {
RichTreeViewProProps,
RichTreeViewProPropsBase,
RichTreeViewProSlots,
RichTreeViewProSlotProps,
} from './RichTreeViewPro.types';
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import generateUtilityClass from '@mui/utils/generateUtilityClass';
import generateUtilityClasses from '@mui/utils/generateUtilityClasses';

export interface RichTreeViewProClasses {
/** Styles applied to the root element. */
root: string;
}

export type RichTreeViewProClassKey = keyof RichTreeViewProClasses;

export function getRichTreeViewProUtilityClass(slot: string): string {
return generateUtilityClass('MuiRichTreeViewPro', slot);
}

export const richTreeViewProClasses: RichTreeViewProClasses = generateUtilityClasses(
'MuiRichTreeViewPro',
['root'],
);
18 changes: 17 additions & 1 deletion packages/x-tree-view-pro/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,17 @@
export * from '@mui/x-tree-view';
// Tree View
export * from '@mui/x-tree-view/TreeView';
export * from '@mui/x-tree-view/SimpleTreeView';
export * from './RichTreeViewPro';

// Tree Item
export * from '@mui/x-tree-view/TreeItem';
export * from '@mui/x-tree-view/TreeItem2';
export * from '@mui/x-tree-view/useTreeItem2';
export * from '@mui/x-tree-view/TreeItem2Icon';
export * from '@mui/x-tree-view/TreeItem2Provider';

export { unstable_resetCleanupTracking } from '@mui/x-tree-view/internals';

export * from '@mui/x-tree-view/models';
export * from '@mui/x-tree-view/icons';
export * from '@mui/x-tree-view/hooks';
52 changes: 52 additions & 0 deletions packages/x-tree-view-pro/src/internals/plugins/defaultPlugins.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import {
useTreeViewId,
UseTreeViewIdParameters,
useTreeViewItems,
UseTreeViewItemsParameters,
useTreeViewExpansion,
UseTreeViewExpansionParameters,
useTreeViewSelection,
UseTreeViewSelectionParameters,
useTreeViewFocus,
UseTreeViewFocusParameters,
useTreeViewKeyboardNavigation,
useTreeViewIcons,
UseTreeViewIconsParameters,
ConvertPluginsIntoSignatures,
MergePluginsProperty,
} from '@mui/x-tree-view/internals';

export const DEFAULT_TREE_VIEW_PRO_PLUGINS = [
useTreeViewId,
useTreeViewItems,
useTreeViewExpansion,
useTreeViewSelection,
useTreeViewFocus,
useTreeViewKeyboardNavigation,
useTreeViewIcons,
] as const;

export type DefaultTreeViewProPlugins = ConvertPluginsIntoSignatures<
typeof DEFAULT_TREE_VIEW_PRO_PLUGINS
>;

export type DefaultTreeViewProPluginSlots = MergePluginsProperty<
DefaultTreeViewProPlugins,
'slots'
>;

export type DefaultTreeViewProPluginSlotProps = MergePluginsProperty<
DefaultTreeViewProPlugins,
'slotProps'
>;

// We can't infer this type from the plugin, otherwise we would lose the generics.
export interface DefaultTreeViewProPluginParameters<
R extends {},
Multiple extends boolean | undefined,
> extends UseTreeViewIdParameters,
UseTreeViewItemsParameters<R>,
UseTreeViewExpansionParameters,
UseTreeViewFocusParameters,
UseTreeViewSelectionParameters<Multiple>,
UseTreeViewIconsParameters {}
2 changes: 2 additions & 0 deletions packages/x-tree-view-pro/src/internals/plugins/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { DEFAULT_TREE_VIEW_PRO_PLUGINS } from './defaultPlugins';
export type { DefaultTreeViewProPlugins } from './defaultPlugins';
15 changes: 15 additions & 0 deletions packages/x-tree-view-pro/src/internals/utils/releaseInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { ponyfillGlobal } from '@mui/utils';

export const getReleaseInfo = () => {
const releaseInfo = '__RELEASE_INFO__';
if (process.env.NODE_ENV !== 'production') {
// A simple hack to set the value in the test environment (has no build step).
// eslint-disable-next-line no-useless-concat
if (releaseInfo === '__RELEASE' + '_INFO__') {
// eslint-disable-next-line no-underscore-dangle
return ponyfillGlobal.__MUI_RELEASE_INFO__;
}
}

return releaseInfo;
};