diff --git a/docs/data/common-concepts/custom-components/CustomSlot.js b/docs/data/common-concepts/custom-components/CustomSlot.js new file mode 100644 index 000000000000..9740551294d5 --- /dev/null +++ b/docs/data/common-concepts/custom-components/CustomSlot.js @@ -0,0 +1,13 @@ +import * as React from 'react'; +import FlightTakeoffIcon from '@mui/icons-material/FlightTakeoff'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; +import { DatePicker } from '@mui/x-date-pickers/DatePicker'; + +export default function CustomSlot() { + return ( + + + + ); +} diff --git a/docs/data/common-concepts/custom-components/CustomSlot.tsx b/docs/data/common-concepts/custom-components/CustomSlot.tsx new file mode 100644 index 000000000000..9740551294d5 --- /dev/null +++ b/docs/data/common-concepts/custom-components/CustomSlot.tsx @@ -0,0 +1,13 @@ +import * as React from 'react'; +import FlightTakeoffIcon from '@mui/icons-material/FlightTakeoff'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; +import { DatePicker } from '@mui/x-date-pickers/DatePicker'; + +export default function CustomSlot() { + return ( + + + + ); +} diff --git a/docs/data/common-concepts/custom-components/CustomSlot.tsx.preview b/docs/data/common-concepts/custom-components/CustomSlot.tsx.preview new file mode 100644 index 000000000000..d5fe738985f4 --- /dev/null +++ b/docs/data/common-concepts/custom-components/CustomSlot.tsx.preview @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/data/common-concepts/custom-components/CustomSlotAndSlotProps.js b/docs/data/common-concepts/custom-components/CustomSlotAndSlotProps.js new file mode 100644 index 000000000000..a6c3a5e036b7 --- /dev/null +++ b/docs/data/common-concepts/custom-components/CustomSlotAndSlotProps.js @@ -0,0 +1,20 @@ +import * as React from 'react'; +import FlightTakeoffIcon from '@mui/icons-material/FlightTakeoff'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; +import { DatePicker } from '@mui/x-date-pickers/DatePicker'; + +export default function CustomSlotAndSlotProps() { + return ( + + + + ); +} diff --git a/docs/data/common-concepts/custom-components/CustomSlotAndSlotProps.tsx b/docs/data/common-concepts/custom-components/CustomSlotAndSlotProps.tsx new file mode 100644 index 000000000000..a6c3a5e036b7 --- /dev/null +++ b/docs/data/common-concepts/custom-components/CustomSlotAndSlotProps.tsx @@ -0,0 +1,20 @@ +import * as React from 'react'; +import FlightTakeoffIcon from '@mui/icons-material/FlightTakeoff'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; +import { DatePicker } from '@mui/x-date-pickers/DatePicker'; + +export default function CustomSlotAndSlotProps() { + return ( + + + + ); +} diff --git a/docs/data/common-concepts/custom-components/CustomSlotAndSlotProps.tsx.preview b/docs/data/common-concepts/custom-components/CustomSlotAndSlotProps.tsx.preview new file mode 100644 index 000000000000..663cf8c57329 --- /dev/null +++ b/docs/data/common-concepts/custom-components/CustomSlotAndSlotProps.tsx.preview @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/docs/data/common-concepts/custom-components/CustomSlotProps.js b/docs/data/common-concepts/custom-components/CustomSlotProps.js new file mode 100644 index 000000000000..686f4e6898f8 --- /dev/null +++ b/docs/data/common-concepts/custom-components/CustomSlotProps.js @@ -0,0 +1,18 @@ +import * as React from 'react'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; +import { DatePicker } from '@mui/x-date-pickers/DatePicker'; + +export default function CustomSlotProps() { + return ( + + + + ); +} diff --git a/docs/data/common-concepts/custom-components/CustomSlotProps.tsx b/docs/data/common-concepts/custom-components/CustomSlotProps.tsx new file mode 100644 index 000000000000..686f4e6898f8 --- /dev/null +++ b/docs/data/common-concepts/custom-components/CustomSlotProps.tsx @@ -0,0 +1,18 @@ +import * as React from 'react'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; +import { DatePicker } from '@mui/x-date-pickers/DatePicker'; + +export default function CustomSlotProps() { + return ( + + + + ); +} diff --git a/docs/data/common-concepts/custom-components/CustomSlotProps.tsx.preview b/docs/data/common-concepts/custom-components/CustomSlotProps.tsx.preview new file mode 100644 index 000000000000..023ce6ddb493 --- /dev/null +++ b/docs/data/common-concepts/custom-components/CustomSlotProps.tsx.preview @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/docs/data/common-concepts/custom-components/CustomSlotPropsCallback.js b/docs/data/common-concepts/custom-components/CustomSlotPropsCallback.js new file mode 100644 index 000000000000..e1272f2776ba --- /dev/null +++ b/docs/data/common-concepts/custom-components/CustomSlotPropsCallback.js @@ -0,0 +1,18 @@ +import * as React from 'react'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; +import { DatePicker } from '@mui/x-date-pickers/DatePicker'; + +export default function CustomSlotPropsCallback() { + return ( + + ({ + color: ownerState.open ? 'secondary' : 'primary', + }), + }} + /> + + ); +} diff --git a/docs/data/common-concepts/custom-components/CustomSlotPropsCallback.tsx b/docs/data/common-concepts/custom-components/CustomSlotPropsCallback.tsx new file mode 100644 index 000000000000..e1272f2776ba --- /dev/null +++ b/docs/data/common-concepts/custom-components/CustomSlotPropsCallback.tsx @@ -0,0 +1,18 @@ +import * as React from 'react'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; +import { DatePicker } from '@mui/x-date-pickers/DatePicker'; + +export default function CustomSlotPropsCallback() { + return ( + + ({ + color: ownerState.open ? 'secondary' : 'primary', + }), + }} + /> + + ); +} diff --git a/docs/data/common-concepts/custom-components/CustomSlotPropsCallback.tsx.preview b/docs/data/common-concepts/custom-components/CustomSlotPropsCallback.tsx.preview new file mode 100644 index 000000000000..100188718fae --- /dev/null +++ b/docs/data/common-concepts/custom-components/CustomSlotPropsCallback.tsx.preview @@ -0,0 +1,7 @@ + ({ + color: ownerState.open ? 'secondary' : 'primary', + }), + }} +/> \ No newline at end of file diff --git a/docs/data/common-concepts/custom-components/TypescriptCasting.js b/docs/data/common-concepts/custom-components/TypescriptCasting.js new file mode 100644 index 000000000000..4d0346ef92f1 --- /dev/null +++ b/docs/data/common-concepts/custom-components/TypescriptCasting.js @@ -0,0 +1,63 @@ +import * as React from 'react'; + +import Stack from '@mui/material/Stack'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import Switch from '@mui/material/Switch'; +import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import { DateCalendar } from '@mui/x-date-pickers/DateCalendar'; + +import { PickersCalendarHeader } from '@mui/x-date-pickers/PickersCalendarHeader'; + +function DisplayWeekNumberToggle({ value, onChange }) { + return ( + onChange(event.target.checked)} + /> + } + label="Display week number" + /> + ); +} + +function CustomCalendarHeader({ + displayWeekNumber, + setDisplayWeekNumber, + ...other +}) { + return ( + + + + + ); +} + +export default function TypescriptCasting() { + const [displayWeekNumber, setDisplayWeekNumber] = React.useState(false); + + return ( + + + + ); +} diff --git a/docs/data/common-concepts/custom-components/TypescriptCasting.tsx b/docs/data/common-concepts/custom-components/TypescriptCasting.tsx new file mode 100644 index 000000000000..2d68d2c1e6be --- /dev/null +++ b/docs/data/common-concepts/custom-components/TypescriptCasting.tsx @@ -0,0 +1,80 @@ +import * as React from 'react'; +import { Dayjs } from 'dayjs'; +import Stack from '@mui/material/Stack'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import Switch from '@mui/material/Switch'; +import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import { + DateCalendar, + DateCalendarSlots, + DateCalendarSlotProps, +} from '@mui/x-date-pickers/DateCalendar'; +import { PropsFromSlot } from '@mui/x-date-pickers/models'; +import { PickersCalendarHeader } from '@mui/x-date-pickers/PickersCalendarHeader'; + +function DisplayWeekNumberToggle({ + value, + onChange, +}: { + value: boolean; + onChange: (value: boolean) => void; +}) { + return ( + onChange(event.target.checked)} + /> + } + label="Display week number" + /> + ); +} + +interface CustomCalendarHeaderProps + extends PropsFromSlot['calendarHeader']> { + displayWeekNumber: boolean; + setDisplayWeekNumber: (displayWeekNumber: boolean) => void; +} + +function CustomCalendarHeader({ + displayWeekNumber, + setDisplayWeekNumber, + ...other +}: CustomCalendarHeaderProps) { + return ( + + + + + ); +} + +export default function TypescriptCasting() { + const [displayWeekNumber, setDisplayWeekNumber] = React.useState(false); + + return ( + + ['calendarHeader'], + }} + slotProps={{ + calendarHeader: { + displayWeekNumber, + setDisplayWeekNumber, + } as DateCalendarSlotProps['calendarHeader'], + }} + /> + + ); +} diff --git a/docs/data/common-concepts/custom-components/TypescriptCasting.tsx.preview b/docs/data/common-concepts/custom-components/TypescriptCasting.tsx.preview new file mode 100644 index 000000000000..461108b0d9c5 --- /dev/null +++ b/docs/data/common-concepts/custom-components/TypescriptCasting.tsx.preview @@ -0,0 +1,14 @@ +['calendarHeader'], + }} + slotProps={{ + calendarHeader: { + displayWeekNumber, + setDisplayWeekNumber, + } as DateCalendarSlotProps['calendarHeader'], + }} +/> \ No newline at end of file diff --git a/docs/data/common-concepts/custom-components/custom-components.md b/docs/data/common-concepts/custom-components/custom-components.md new file mode 100644 index 000000000000..28e37c605637 --- /dev/null +++ b/docs/data/common-concepts/custom-components/custom-components.md @@ -0,0 +1,252 @@ +# Custom slots and subcomponents + +

Learn how to override parts of the MUI X components.

+ +## What is a slot? + +A slot is a part of a component that can be [overridden](/x/common-concepts/custom-components/#how-to-override-a-slot) and/or [customized](/x/common-concepts/custom-components/#how-to-customize-a-slot). + +Some of those slots allow you to provide your own UI primitives to the MUI X components. +This is the role of all the `baseXXX` component on the `DataGrid` component (`baseButton`, `baseSelect`, ...). +These slots receive props that should be as generic as possible so that it is easy to interface any other design system. + +Other slots allow you to override parts of the MUI X UI components with a custom UI built specifically for this component. +This is the role of slots like `calendarHeader` on the `DateCalendar` component or `item` on the `RichTreeView` component. +These slots receive props specific to this part of the UI and will most likely not be re-use throughout your application. + +## Basic usage + +### How to override a slot? + +You can override a slot by providing a custom component to the `slots` prop: + +{{"demo": "CustomSlot.js"}} + +### How to customize a slot? + +You can pass props to any slot using the `slotProps` prop: + +{{"demo": "CustomSlotProps.js"}} + +You can also use both `slots` and `slotProps` on the same component: + +{{"demo": "CustomSlotAndSlotProps.js"}} + +Most slots also support a callback version of `slotProps`. +This callback receives an object that contains information about the current state of the component, +that information can vary depending on the slot being used: + +{{"demo": "CustomSlotPropsCallback.js"}} + +## Correct usage + +A slot is a React component; therefore, it should keep the same JavaScript reference between two renders. +If the JavaScript reference of component changes between two renders, React will remount it. +You can avoid it by not inlining the component definition in the `slots` prop. + +The first two examples below are buggy because the calendar header will remount after each keystroke, leading to a loss of focus. + +```jsx +// ❌ The `calendarHeader` slot is re-defined each time the parent component renders, +// causing the component to remount. +function MyApp() { + const [name, setName] = React.useState(''); + return ( + ( + setName(event.target.value)} /> + ), + }} + /> + ); +} +``` + +```jsx +// ❌ The `calendarHeader` slot is re-defined each time `name` is updated, +// causing the component to remount. +function MyApp() { + const [name, setName] = React.useState(''); + + const CustomCalendarHeader = React.useCallback( + () => setName(event.target.value)} />, + [name], + ); + + return ; +} +``` + +```jsx +// ✅ The `calendarHeader` slot is defined only once, it will never remount. +const CustomCalendarHeader = ({ name, setName }) => ( + setName(event.target.value)} /> +); + +function MyApp() { + const [name, setName] = React.useState(''); + return ( + + ); +} +``` + +## Usage with TypeScript + +### Type custom slots + +If you want to ensure type safety on your custom slot component, +you can declare your component using the `PropsFromSlot` interface: + +```tsx +function CustomCalendarHeader({ + currentMonth, +}: PropsFromSlot['calendarHeader']>) { + return
{currentMonth?.format('MM-DD-YYYY')}
; +} +``` + +:::success +The `PropsFromSlot` is exported from every package that supports slots: + +```ts +import { PropsFromSlot } from '@mui/x-data-grid/models'; +import { PropsFromSlot } from '@mui/x-date-pickers/models'; +// ... +``` + +It takes the slot type (as defined in the `slots` prop of your component) and returns the props that the slot receives. + +```ts +import { Dayjs } from 'dayjs'; +import { PropsFromSlot, GridSlots } from '@mui/x-data-grid'; +import { DateCalendarSlots } from '@mui/x-date-pickers'; + +type ToolbarProps = PropsFromSlot; + +// Most of the picker slots interfaces need to receive the date type as a generic. +type CalendarHeaderProps = PropsFromSlot['calendarHeader']>; +``` + +::: + +### Using additional props + +If you are passing additional props to your slot, you can add them to the props your custom component receives: + +```ts +interface CustomCalendarHeaderProps + extends PropsFromSlot['calendarHeader']> { + displayWeekNumber: boolean; + setDisplayWeekNumber: (displayWeekNumber: boolean) => void; +} +``` + +You can then use these props in your custom component and access both the props provided by the host component +and the props you added: + +```tsx +function CustomCalendarHeader({ + displayWeekNumber, + setDisplayWeekNumber, + ...other +}: CustomCalendarHeaderProps) { + return ( + + + + + ); +} +``` + +If your custom component has a different type than the default one, you will need to cast it to the correct type. +This can happen if you pass additional props to your custom component using `slotProps`. +If we take the example of the `calendarHeader` slot, you can cast your custom component as below: + +```tsx +function MyApp() { + const [displayWeekNumber, setDisplayWeekNumber] = React.useState(false); + return ( + ['calendarHeader'], + }} + slotProps={{ + calendarHeader: { + displayWeekNumber, + setDisplayWeekNumber, + } as DateCalendarSlotProps['calendarHeader'], + }} + /> + ); +} +``` + +{{"demo": "TypescriptCasting.js", "defaultCodeOpen": false}} + +### Using module augmentation + +If you are using one of the data grid packages, +you can also use [module augmentation](/x/react-data-grid/components/#custom-slot-props-with-typescript) to let TypeScript know about your custom props: + +```ts +declare module '@mui/x-data-grid' { + interface ToolbarPropsOverrides { + name: string; + setName: (name: string) => void; + } +} +``` + +You can then use your custom slot without any type casting: + +```tsx +function CustomToolbar({ name, setName }: PropsFromSlot) { + return setName(event.target.value)} />; +} + +function MyApp() { + const [name, setName] = React.useState(''); + return ( + + ); +} +``` + +See [Data Grid - Custom slots and subcomponents—Custom slot props with TypeScript](/x/react-data-grid/components/#custom-slot-props-with-typescript) for more details. + +:::warning +The module augmentation feature isn't implemented yet for the other sets of components. It's coming. + +- 👍 Upvote [issue 9775](https://github.com/mui/mui-x/issues/9775) if you want to see it land faster on the Date and Time Pickers. +- 👍 Upvote [issue 14063](https://github.com/mui/mui-x/issues/14063) if you want to see it land faster on the Charts. +- 👍 Upvote [issue 14062](https://github.com/mui/mui-x/issues/14062) if you want to see it land faster on the Tree View. + + ::: + +## Slots of the X components + +You can find the list of slots in the API documentation of each component (e.g. [DataGrid](/x/api/data-grid/data-grid/#slots), [DatePicker](/x/api/date-pickers/date-picker/#slots), [BarChart](/x/api/charts/bar-chart/#slots) or [RichTreeView](/x/api/tree-view/rich-tree-view/#slots)). + +Most of the slots have a standalone doc example to show how to use them: + +- [Data Grid—Custom slots and subcomponents](/x/react-data-grid/components/) +- [Date Picker—Custom slots and subcomponents](/x/react-date-pickers/custom-components/) +- [RichTreeView—Customization](/x/react-tree-view/rich-tree-view/customization/) / [SimpleTreeView—Customization](/x/react-tree-view/simple-tree-view/customization/) diff --git a/docs/data/data-grid/column-menu/column-menu.md b/docs/data/data-grid/column-menu/column-menu.md index fcbe4a8b40dd..cb20213f0d60 100644 --- a/docs/data/data-grid/column-menu/column-menu.md +++ b/docs/data/data-grid/column-menu/column-menu.md @@ -126,7 +126,7 @@ As a reference, here are the column menu `slots` along with the default item com ## Custom menu component -You can also customize and replace the column menu by [passing a fully custom component](/x/react-data-grid/components/#overriding-components) to the `columnMenu` slot of the Data Grid. If you want to add some of the default menu items to your custom component, you can import and re-use them. +You can also customize and replace the column menu by [passing a fully custom component](/x/react-data-grid/components/#component-slots) to the `columnMenu` slot of the Data Grid. If you want to add some of the default menu items to your custom component, you can import and re-use them. {{"demo": "CustomColumnMenuGrid.js", "bg": "inline"}} diff --git a/docs/data/data-grid/components/components.md b/docs/data/data-grid/components/components.md index 6d78d7487beb..af1eb59c460f 100644 --- a/docs/data/data-grid/components/components.md +++ b/docs/data/data-grid/components/components.md @@ -1,43 +1,8 @@ -# Data Grid - Custom subcomponents +# Data Grid - Custom slots and subcomponents -

The grid is highly customizable. Override components using the slots prop.

+

Learn how to override parts of the grid.

-## Overriding components - -As part of the customization API, the Data Grid allows you to override internal components with the `slots` prop. -The prop accepts an object of type [`GridSlotsComponent`](/x/api/data-grid/data-grid/#slots). - -If you wish to pass additional props in a component slot, you can do it using the `slotProps` prop. -This prop is of type `GridSlotsComponentsProps`. Note that if you do and you use TypeScript, you'll need to cast your custom component so it can fit in the slot type. - -As an example, you could override the column menu and pass additional props as below. - -```tsx - -``` - -If you want to ensure type safety, you can declare your component using the slot props typings: - -```tsx -import { GridSlotProps } from '@mui/x-data-grid'; - -function MyCustomColumnMenu( - props: GridSlotProps['columnMenu'] & { background: string; counter: number }, -) { - // ... -} -``` - -### Interacting with the data grid +## Interacting with the data grid The grid exposes two hooks to help you access the data grid data while overriding component slots. @@ -64,7 +29,12 @@ function CustomPagination() { ## Component slots -The full list of overridable component slots can be found on the [`GridSlotsComponent`](/x/api/data-grid/data-grid/#slots) API page. +:::success + +- See [Common concepts—Custom slots and subcomponents](/x/common-concepts/custom-components/) to learn how to use slots. +- See [`GridSlotsComponent`](/x/api/data-grid/data-grid/#slots) to learn about the available slots. + + ::: ### Column menu @@ -153,22 +123,13 @@ As any component slot, every icon can be customized. However, it is not yet poss See the [Overlays](/x/react-data-grid/overlays/) documentation on how to customize the `loadingOverlay`, `noRowsOverlay`, and `noResultsOverlay`. -## Slot props - -To override default props or pass custom props to slot components, use the `slotProps` prop. +## Custom slot props with TypeScript -```tsx - -``` +:::success +This section focuses on module augmentation. -### Custom slot props with TypeScript +See [Custom slots and subcomponents—Usage with TypeScript](/x/common-concepts/custom-components/#usage-with-typescript) if you don't want to use this approach. +::: If the custom component requires additional props to work properly, TypeScript may throw type errors. To solve these type errors, use [module augmentation](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation) to enhance the props interface. @@ -201,7 +162,7 @@ declare module '@mui/x-data-grid' { }} slotProps={{ toolbar: { - // props required by CustomGridToolbar + // props used by CustomGridToolbar someCustomString: 'Hello', someCustomNumber: 42, }, @@ -225,7 +186,7 @@ declare module '@mui/x-data-grid-pro' { }} slotProps={{ toolbar: { - // props required by CustomGridToolbar + // props used by CustomGridToolbar someCustomString: 'Hello', someCustomNumber: 42, }, @@ -249,7 +210,7 @@ declare module '@mui/x-data-grid-premium' { }} slotProps={{ toolbar: { - // props required by CustomGridToolbar + // props used by CustomGridToolbar someCustomString: 'Hello', someCustomNumber: 42, }, diff --git a/docs/data/data-grid/filtering/customization.md b/docs/data/data-grid/filtering/customization.md index 83566d25d21e..5d55919b988e 100644 --- a/docs/data/data-grid/filtering/customization.md +++ b/docs/data/data-grid/filtering/customization.md @@ -147,7 +147,7 @@ const ratingColumnType: GridColTypeDef = { ## Custom filter panel -You can customize the rendering of the filter panel as shown in [the component section](/x/react-data-grid/components/#overriding-components) of the documentation. +You can customize the rendering of the filter panel as shown in [the component section](/x/react-data-grid/components/#component-slots) of the documentation. ### Customize the filter panel content diff --git a/docs/data/date-pickers/custom-components/custom-components.md b/docs/data/date-pickers/custom-components/custom-components.md index 65034ecb8882..023e89cb0a05 100644 --- a/docs/data/date-pickers/custom-components/custom-components.md +++ b/docs/data/date-pickers/custom-components/custom-components.md @@ -6,94 +6,17 @@ components: DateTimePickerTabs, PickersActionBar, DatePickerToolbar, TimePickerT # Custom slots and subcomponents -

Learn how to override the default DOM structure of the Date and Time Pickers.

+

Learn how to override parts of the Date and Time Pickers.

:::info The components that can be customized are listed under `slots` section in Date and Time Pickers [API Reference](/x/api/date-pickers/). For example, available Date Picker slots can be found [here](/x/api/date-pickers/date-picker/#slots). ::: -## Overriding slot components - -You can override the internal elements of the component (known as "slots") using the `slots` prop. - -Use the `slotProps` prop if you need to pass additional props to a component slot. - -As an example, you could override the `ActionBar` and pass additional props to the custom component as shown below: - -```jsx - with a custom one - actionBar: CustomActionBar, - }} - slotProps={{ - // pass props `actions={['clear']}` to the actionBar slot - actionBar: { actions: ['clear'] }, - }} -/> -``` - -To modify components position, have a look at the [custom layout](/x/react-date-pickers/custom-layout/) docs page. - -### Recommended usage - :::success -Remember to pass a reference to the component instead of an inline render function and define it outside the main component. -This ensures that the component is not remounted on every update. +See [Common concepts—Custom slots and subcomponents](/x/common-concepts/custom-components/) to learn how to use slots. ::: -The first two examples below are buggy because the toolbar will remount after each keystroke, leading to a loss of focus. - -```jsx -// ❌ The `toolbar` slot is re-defined each time the parent component renders, -// causing the component to remount. -function MyApp() { - return ( - ( - setName(event.target.value)} /> - ), - }} - /> - ); -} -``` - -```jsx -// ❌ The `toolbar` slot is re-defined each time `name` is updated, -// causing the component to remount. -function MyApp() { - const [name, setName] = React.useState(''); - - const CustomToolbar = React.useCallback( - () => setName(event.target.value)} />, - [name], - ); - - return ; -} -``` - -```jsx -// ✅ The `toolbar` slot is defined only once, it will never remount. -const CustomToolbar = ({ name, setName }) => ( - setName(event.target.value)} /> -); - -function MyApp() { - const [name, setName] = React.useState(''); - return ( - - ); -} -``` - ## Action bar ### Component props diff --git a/docs/data/date-pickers/custom-field/custom-field.md b/docs/data/date-pickers/custom-field/custom-field.md index bb3301d7bf66..b58ea51d833f 100644 --- a/docs/data/date-pickers/custom-field/custom-field.md +++ b/docs/data/date-pickers/custom-field/custom-field.md @@ -10,6 +10,10 @@ components: PickersSectionList, PickersTextField

The Date and Time Pickers let you customize the field by passing props or custom components

+:::success +See [Common concepts—Custom slots and subcomponents](/x/common-concepts/custom-components/) to learn how to use slots. +::: + ## Customize the default field ### Customize the `TextField` diff --git a/docs/data/date-pickers/custom-layout/custom-layout.md b/docs/data/date-pickers/custom-layout/custom-layout.md index 7a26bee7a059..421e80851236 100644 --- a/docs/data/date-pickers/custom-layout/custom-layout.md +++ b/docs/data/date-pickers/custom-layout/custom-layout.md @@ -10,6 +10,10 @@ packageName: '@mui/x-date-pickers'

The Date and Time Pickers let you reorganize the layout

+:::success +See [Common concepts—Custom slots and subcomponents](/x/common-concepts/custom-components/) to learn how to use slots. +::: + ## Default layout structure By default, pickers are made of 5 subcomponents present in the following order: diff --git a/docs/data/date-pickers/custom-opening-button/custom-opening-button.md b/docs/data/date-pickers/custom-opening-button/custom-opening-button.md index 4dadc61d97e0..2e71fe686d5b 100644 --- a/docs/data/date-pickers/custom-opening-button/custom-opening-button.md +++ b/docs/data/date-pickers/custom-opening-button/custom-opening-button.md @@ -7,6 +7,10 @@ title: Date and Time Pickers - Custom opening button

The date picker lets you customize the button to open the views.

+:::success +See [Common concepts—Custom slots and subcomponents](/x/common-concepts/custom-components/) to learn how to use slots. +::: + ## Set a custom opening icon If you want to change the icon opening the picker without changing its behavior, you can use the `openPickerIcon` slot: diff --git a/docs/data/pages.ts b/docs/data/pages.ts index dfa4c550422e..68a022f3d6aa 100644 --- a/docs/data/pages.ts +++ b/docs/data/pages.ts @@ -12,7 +12,6 @@ const pages: MuiPage[] = [ { pathname: '/x/introduction-group', title: 'Introduction', - children: [ { pathname: `/x/introduction`, title: 'Overview' }, { pathname: `/x/introduction/installation` }, @@ -21,6 +20,13 @@ const pages: MuiPage[] = [ { pathname: `/x/introduction/roadmap` }, ], }, + { + pathname: '/x/common-features-group', + title: 'Common concepts', + children: [ + { pathname: `/x/common-concepts/custom-components`, title: 'Custom slots and subcomponents' }, + ], + }, { pathname: '/x/react-data-grid-group', title: 'Data Grid', @@ -88,7 +94,7 @@ const pages: MuiPage[] = [ { pathname: '/x/react-data-grid/export' }, { pathname: '/x/react-data-grid/clipboard', title: 'Copy and paste', newFeature: true }, { pathname: '/x/react-data-grid/overlays', title: 'Overlays' }, - { pathname: '/x/react-data-grid/components', title: 'Custom subcomponents' }, + { pathname: '/x/react-data-grid/components', title: 'Custom slots and subcomponents' }, { pathname: '/x/react-data-grid/style-group', title: 'Style', diff --git a/docs/data/tree-view/rich-tree-view/customization/customization.md b/docs/data/tree-view/rich-tree-view/customization/customization.md index 4bedda31d764..d8172900c158 100644 --- a/docs/data/tree-view/rich-tree-view/customization/customization.md +++ b/docs/data/tree-view/rich-tree-view/customization/customization.md @@ -11,6 +11,10 @@ waiAria: https://www.w3.org/WAI/ARIA/apg/patterns/treeview/

Learn how to customize the Rich Tree View component.

+:::success +See [Common concepts—Custom slots and subcomponents](/x/common-concepts/custom-components/) to learn how to use slots. +::: + ## Basics ### Custom icons diff --git a/docs/data/tree-view/simple-tree-view/customization/customization.md b/docs/data/tree-view/simple-tree-view/customization/customization.md index 7cf009836436..3774ec10f6ea 100644 --- a/docs/data/tree-view/simple-tree-view/customization/customization.md +++ b/docs/data/tree-view/simple-tree-view/customization/customization.md @@ -11,6 +11,10 @@ waiAria: https://www.w3.org/WAI/ARIA/apg/patterns/treeview/

Learn how to customize the Simple Tree View component.

+:::success +See [Common concepts—Custom slots and subcomponents](/x/common-concepts/custom-components/) to learn how to use slots. +::: + ## Basics ### Custom icons diff --git a/docs/pages/x/common-concepts/custom-components.js b/docs/pages/x/common-concepts/custom-components.js new file mode 100644 index 000000000000..51a99e446b11 --- /dev/null +++ b/docs/pages/x/common-concepts/custom-components.js @@ -0,0 +1,7 @@ +import * as React from 'react'; +import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs'; +import * as pageProps from 'docsx/data/common-concepts/custom-components/custom-components.md?muiMarkdown'; + +export default function Page() { + return ; +} diff --git a/packages/x-charts-pro/package.json b/packages/x-charts-pro/package.json index cceb2b2915e6..765359ab82d3 100644 --- a/packages/x-charts-pro/package.json +++ b/packages/x-charts-pro/package.json @@ -43,6 +43,7 @@ "@mui/utils": "^5.16.6", "@mui/x-charts": "workspace:*", "@mui/x-charts-vendor": "workspace:*", + "@mui/x-internals": "workspace:*", "@mui/x-license": "workspace:*", "@react-spring/rafz": "^9.7.4", "@react-spring/web": "^9.7.4", diff --git a/packages/x-charts-pro/tsconfig.build.json b/packages/x-charts-pro/tsconfig.build.json index bf4eaef647d2..d85b5e559d65 100644 --- a/packages/x-charts-pro/tsconfig.build.json +++ b/packages/x-charts-pro/tsconfig.build.json @@ -13,6 +13,7 @@ }, "references": [ { "path": "../x-charts/tsconfig.build.json" }, + { "path": "../x-internals/tsconfig.build.json" }, { "path": "../x-license/tsconfig.build.json" } ], "include": ["src/**/*.ts*"], diff --git a/packages/x-charts/package.json b/packages/x-charts/package.json index f11292365784..bf10d935f48c 100644 --- a/packages/x-charts/package.json +++ b/packages/x-charts/package.json @@ -42,6 +42,7 @@ "@babel/runtime": "^7.25.6", "@mui/utils": "^5.16.6", "@mui/x-charts-vendor": "workspace:*", + "@mui/x-internals": "workspace:*", "@react-spring/rafz": "^9.7.4", "@react-spring/web": "^9.7.4", "clsx": "^2.1.1", diff --git a/packages/x-charts/src/models/index.ts b/packages/x-charts/src/models/index.ts index abb5d36002f6..1b370c0ec5fe 100644 --- a/packages/x-charts/src/models/index.ts +++ b/packages/x-charts/src/models/index.ts @@ -8,3 +8,6 @@ export type { ScaleName, ContinuousScaleName, } from './axis'; + +// Utils shared across the X packages +export type { PropsFromSlot } from '@mui/x-internals/slots'; diff --git a/packages/x-charts/tsconfig.build.json b/packages/x-charts/tsconfig.build.json index d67bc4572b32..60d43624f21d 100644 --- a/packages/x-charts/tsconfig.build.json +++ b/packages/x-charts/tsconfig.build.json @@ -18,6 +18,7 @@ "csstype" ] }, + "references": [{ "path": "../x-internals/tsconfig.build.json" }], "include": ["src/**/*.ts*"], "exclude": ["src/**/*.spec.ts*", "src/**/*.test.ts*", "src/tests/**/*"] } diff --git a/packages/x-data-grid/src/models/index.ts b/packages/x-data-grid/src/models/index.ts index e7761edb34ad..cbef8f09c1e4 100644 --- a/packages/x-data-grid/src/models/index.ts +++ b/packages/x-data-grid/src/models/index.ts @@ -35,3 +35,6 @@ export type { GridPrintExportOptions, } from './gridExport'; export * from './gridFilterOperator'; + +// Utils shared across the X packages +export type { PropsFromSlot } from '@mui/x-internals/slots'; diff --git a/packages/x-date-pickers-pro/package.json b/packages/x-date-pickers-pro/package.json index c93e75124ef1..6035608cf2ec 100644 --- a/packages/x-date-pickers-pro/package.json +++ b/packages/x-date-pickers-pro/package.json @@ -45,6 +45,7 @@ "@babel/runtime": "^7.25.6", "@mui/utils": "^5.16.6", "@mui/x-date-pickers": "workspace:*", + "@mui/x-internals": "workspace:*", "@mui/x-license": "workspace:*", "clsx": "^2.1.1", "prop-types": "^15.8.1", diff --git a/packages/x-date-pickers-pro/tsconfig.build.json b/packages/x-date-pickers-pro/tsconfig.build.json index 08e5c49be00c..e61dc5932044 100644 --- a/packages/x-date-pickers-pro/tsconfig.build.json +++ b/packages/x-date-pickers-pro/tsconfig.build.json @@ -13,7 +13,8 @@ }, "references": [ { "path": "../x-date-pickers/tsconfig.build.json" }, - { "path": "../x-license/tsconfig.build.json" } + { "path": "../x-license/tsconfig.build.json" }, + { "path": "../x-internals/tsconfig.build.json" } ], "include": ["./src/**/*.ts*"], "exclude": ["src/**/*.spec.ts*", "src/**/*.test.ts*", "src/tests/**/*"] diff --git a/packages/x-date-pickers/package.json b/packages/x-date-pickers/package.json index 05688ddbfe25..7d125dfb1840 100644 --- a/packages/x-date-pickers/package.json +++ b/packages/x-date-pickers/package.json @@ -47,6 +47,7 @@ "dependencies": { "@babel/runtime": "^7.25.6", "@mui/utils": "^5.16.6", + "@mui/x-internals": "workspace:*", "@types/react-transition-group": "^4.4.11", "clsx": "^2.1.1", "prop-types": "^15.8.1", diff --git a/packages/x-date-pickers/src/internals/hooks/useDesktopPicker/useDesktopPicker.tsx b/packages/x-date-pickers/src/internals/hooks/useDesktopPicker/useDesktopPicker.tsx index 56e943302f79..9a2bb45a81cc 100644 --- a/packages/x-date-pickers/src/internals/hooks/useDesktopPicker/useDesktopPicker.tsx +++ b/packages/x-date-pickers/src/internals/hooks/useDesktopPicker/useDesktopPicker.tsx @@ -6,6 +6,7 @@ import useForkRef from '@mui/utils/useForkRef'; import useId from '@mui/utils/useId'; import { PickersPopper } from '../../components/PickersPopper'; import { + UseDesktopPickerOwnerState, UseDesktopPickerParams, UseDesktopPickerProps, UseDesktopPickerSlotProps, @@ -90,6 +91,11 @@ export const useDesktopPicker = < wrapperVariant: 'desktop', }); + // TODO v8: Apply this ownerState to all the slots in this hook. + const ownerStateV8: UseDesktopPickerOwnerState = { + open, + }; + const InputAdornment = slots.inputAdornment ?? MuiInputAdornment; const { ownerState: inputAdornmentOwnerState, ...inputAdornmentProps } = useSlotProps({ elementType: InputAdornment, @@ -114,6 +120,11 @@ export const useDesktopPicker = < }); const OpenPickerIcon = slots.openPickerIcon; + const openPickerIconProps = useSlotProps({ + elementType: OpenPickerIcon, + externalSlotProps: innerSlotProps?.openPickerIcon, + ownerState: ownerStateV8, + }); const Field = slots.field; const fieldProps = useSlotProps< @@ -163,7 +174,7 @@ export const useDesktopPicker = < [`${inputAdornmentProps.position}Adornment`]: ( - + ), diff --git a/packages/x-date-pickers/src/internals/hooks/useDesktopPicker/useDesktopPicker.types.ts b/packages/x-date-pickers/src/internals/hooks/useDesktopPicker/useDesktopPicker.types.ts index 59ff56096d93..c9cc83019e06 100644 --- a/packages/x-date-pickers/src/internals/hooks/useDesktopPicker/useDesktopPicker.types.ts +++ b/packages/x-date-pickers/src/internals/hooks/useDesktopPicker/useDesktopPicker.types.ts @@ -71,6 +71,10 @@ export interface UseDesktopPickerSlotProps< > extends ExportedUseDesktopPickerSlotProps, Pick, 'toolbar'> {} +export interface UseDesktopPickerOwnerState { + open: boolean; +} + export interface ExportedUseDesktopPickerSlotProps< TDate extends PickerValidDate, TView extends DateOrTimeViewWithMeridiem, @@ -96,7 +100,7 @@ export interface ExportedUseDesktopPickerSlotProps< {}, UseDesktopPickerProps >; - openPickerIcon?: Record; + openPickerIcon?: SlotComponentPropsFromProps, {}, UseDesktopPickerOwnerState>; } export interface DesktopOnlyPickerProps diff --git a/packages/x-date-pickers/src/models/index.ts b/packages/x-date-pickers/src/models/index.ts index 1bfcf091432b..06374a006750 100644 --- a/packages/x-date-pickers/src/models/index.ts +++ b/packages/x-date-pickers/src/models/index.ts @@ -5,3 +5,6 @@ export * from './views'; export * from './adapters'; export * from './common'; export * from './pickers'; + +// Utils shared across the X packages +export type { PropsFromSlot } from '@mui/x-internals/slots'; diff --git a/packages/x-date-pickers/tsconfig.build.json b/packages/x-date-pickers/tsconfig.build.json index e46d96ed4652..308d2110b536 100644 --- a/packages/x-date-pickers/tsconfig.build.json +++ b/packages/x-date-pickers/tsconfig.build.json @@ -18,6 +18,7 @@ "@emotion/styled" ] }, + "references": [{ "path": "../x-internals/tsconfig.build.json" }], "include": ["src/**/*.ts*"], "exclude": ["src/**/*.spec.ts*", "src/**/*.test.ts*", "src/tests/**/*"] } diff --git a/packages/x-internals/src/slots/index.ts b/packages/x-internals/src/slots/index.ts new file mode 100644 index 000000000000..4cd63cda1ead --- /dev/null +++ b/packages/x-internals/src/slots/index.ts @@ -0,0 +1,8 @@ +import * as React from 'react'; + +export type PropsFromSlot< + Slot extends React.JSXElementConstructor | React.ElementType | null | undefined, +> = + NonNullable extends React.ElementType + ? P + : React.ComponentProps>; diff --git a/packages/x-tree-view/src/models/index.ts b/packages/x-tree-view/src/models/index.ts index d5c9e3473a30..d3853dec6dd7 100644 --- a/packages/x-tree-view/src/models/index.ts +++ b/packages/x-tree-view/src/models/index.ts @@ -1 +1,4 @@ export * from './items'; + +// Utils shared across the X packages +export type { PropsFromSlot } from '@mui/x-internals/slots'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3a7d599a9669..a792af58f1fb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -726,6 +726,9 @@ importers: '@mui/x-charts-vendor': specifier: workspace:* version: link:../x-charts-vendor + '@mui/x-internals': + specifier: workspace:* + version: link:../x-internals/build '@react-spring/rafz': specifier: ^9.7.4 version: 9.7.4 @@ -791,6 +794,9 @@ importers: '@mui/x-charts-vendor': specifier: workspace:* version: link:../x-charts-vendor + '@mui/x-internals': + specifier: workspace:* + version: link:../x-internals/build '@mui/x-license': specifier: workspace:* version: link:../x-license/build @@ -1191,6 +1197,9 @@ importers: '@mui/utils': specifier: ^5.16.6 version: 5.16.6(@types/react@18.3.4)(react@18.3.1) + '@mui/x-internals': + specifier: workspace:* + version: link:../x-internals/build '@types/react-transition-group': specifier: ^4.4.11 version: 4.4.11 @@ -1277,6 +1286,9 @@ importers: '@mui/x-date-pickers': specifier: workspace:* version: link:../x-date-pickers/build + '@mui/x-internals': + specifier: workspace:* + version: link:../x-internals/build '@mui/x-license': specifier: workspace:* version: link:../x-license/build diff --git a/scripts/x-charts-pro.exports.json b/scripts/x-charts-pro.exports.json index 5a6002d4f448..ba8a95232123 100644 --- a/scripts/x-charts-pro.exports.json +++ b/scripts/x-charts-pro.exports.json @@ -264,6 +264,7 @@ { "name": "PieSeriesType", "kind": "Interface" }, { "name": "PieValueType", "kind": "TypeAlias" }, { "name": "PopperProps", "kind": "TypeAlias" }, + { "name": "PropsFromSlot", "kind": "TypeAlias" }, { "name": "referenceLineClasses", "kind": "Variable" }, { "name": "ResponsiveChartContainerPro", "kind": "Variable" }, { "name": "ResponsiveChartContainerProProps", "kind": "Interface" }, diff --git a/scripts/x-charts.exports.json b/scripts/x-charts.exports.json index 77e4e3e29edc..060d4920f609 100644 --- a/scripts/x-charts.exports.json +++ b/scripts/x-charts.exports.json @@ -253,6 +253,7 @@ { "name": "PieSeriesType", "kind": "Interface" }, { "name": "PieValueType", "kind": "TypeAlias" }, { "name": "PopperProps", "kind": "TypeAlias" }, + { "name": "PropsFromSlot", "kind": "TypeAlias" }, { "name": "referenceLineClasses", "kind": "Variable" }, { "name": "ResponsiveChartContainer", "kind": "Variable" }, { "name": "ResponsiveChartContainerProps", "kind": "Interface" }, diff --git a/scripts/x-data-grid-premium.exports.json b/scripts/x-data-grid-premium.exports.json index 9cdbd7893a21..6f24de3deed6 100644 --- a/scripts/x-data-grid-premium.exports.json +++ b/scripts/x-data-grid-premium.exports.json @@ -645,6 +645,7 @@ { "name": "PaginationPropsOverrides", "kind": "Interface" }, { "name": "PanelPropsOverrides", "kind": "Interface" }, { "name": "PinnedRowsPropsOverrides", "kind": "Interface" }, + { "name": "PropsFromSlot", "kind": "TypeAlias" }, { "name": "renderActionsCell", "kind": "Variable" }, { "name": "renderBooleanCell", "kind": "Variable" }, { "name": "renderEditBooleanCell", "kind": "Variable" }, diff --git a/scripts/x-data-grid-pro.exports.json b/scripts/x-data-grid-pro.exports.json index 243ae7852d41..b276834add7a 100644 --- a/scripts/x-data-grid-pro.exports.json +++ b/scripts/x-data-grid-pro.exports.json @@ -596,6 +596,7 @@ { "name": "PaginationPropsOverrides", "kind": "Interface" }, { "name": "PanelPropsOverrides", "kind": "Interface" }, { "name": "PinnedRowsPropsOverrides", "kind": "Interface" }, + { "name": "PropsFromSlot", "kind": "TypeAlias" }, { "name": "renderActionsCell", "kind": "Variable" }, { "name": "renderBooleanCell", "kind": "Variable" }, { "name": "renderEditBooleanCell", "kind": "Variable" }, diff --git a/scripts/x-data-grid.exports.json b/scripts/x-data-grid.exports.json index 56013ba8ab98..387626660b89 100644 --- a/scripts/x-data-grid.exports.json +++ b/scripts/x-data-grid.exports.json @@ -544,6 +544,7 @@ { "name": "PaginationPropsOverrides", "kind": "Interface" }, { "name": "PanelPropsOverrides", "kind": "Interface" }, { "name": "PinnedRowsPropsOverrides", "kind": "Interface" }, + { "name": "PropsFromSlot", "kind": "TypeAlias" }, { "name": "renderActionsCell", "kind": "Variable" }, { "name": "renderBooleanCell", "kind": "Variable" }, { "name": "renderEditBooleanCell", "kind": "Variable" }, diff --git a/scripts/x-date-pickers-pro.exports.json b/scripts/x-date-pickers-pro.exports.json index 809ccdd38ed4..2c71d64e476b 100644 --- a/scripts/x-date-pickers-pro.exports.json +++ b/scripts/x-date-pickers-pro.exports.json @@ -328,6 +328,7 @@ { "name": "PickersYearClassKey", "kind": "TypeAlias" }, { "name": "PickerValidDate", "kind": "TypeAlias" }, { "name": "PickerValidDateLookup", "kind": "Interface" }, + { "name": "PropsFromSlot", "kind": "TypeAlias" }, { "name": "RangeFieldSection", "kind": "Interface" }, { "name": "RangeFieldSeparatorProps", "kind": "Interface" }, { "name": "RangePosition", "kind": "TypeAlias" }, diff --git a/scripts/x-date-pickers.exports.json b/scripts/x-date-pickers.exports.json index f816e70b63e6..db45cedd262a 100644 --- a/scripts/x-date-pickers.exports.json +++ b/scripts/x-date-pickers.exports.json @@ -241,6 +241,7 @@ { "name": "PickersYearClassKey", "kind": "TypeAlias" }, { "name": "PickerValidDate", "kind": "TypeAlias" }, { "name": "PickerValidDateLookup", "kind": "Interface" }, + { "name": "PropsFromSlot", "kind": "TypeAlias" }, { "name": "renderDateViewCalendar", "kind": "Variable" }, { "name": "renderDigitalClockTimeView", "kind": "Variable" }, { "name": "renderMultiSectionDigitalClockTimeView", "kind": "Variable" }, diff --git a/scripts/x-tree-view-pro.exports.json b/scripts/x-tree-view-pro.exports.json index 16f61d3c4727..1bc3c9b84ef8 100644 --- a/scripts/x-tree-view-pro.exports.json +++ b/scripts/x-tree-view-pro.exports.json @@ -4,6 +4,7 @@ { "name": "getTreeItemUtilityClass", "kind": "Function" }, { "name": "getTreeViewUtilityClass", "kind": "Function" }, { "name": "MultiSelectTreeViewProps", "kind": "TypeAlias" }, + { "name": "PropsFromSlot", "kind": "TypeAlias" }, { "name": "RichTreeViewPro", "kind": "Variable" }, { "name": "richTreeViewProClasses", "kind": "Variable" }, { "name": "RichTreeViewProClasses", "kind": "Interface" }, diff --git a/scripts/x-tree-view.exports.json b/scripts/x-tree-view.exports.json index 49a0d1ae5a7b..b6b039d45db3 100644 --- a/scripts/x-tree-view.exports.json +++ b/scripts/x-tree-view.exports.json @@ -4,6 +4,7 @@ { "name": "getTreeItemUtilityClass", "kind": "Function" }, { "name": "getTreeViewUtilityClass", "kind": "Function" }, { "name": "MultiSelectTreeViewProps", "kind": "TypeAlias" }, + { "name": "PropsFromSlot", "kind": "TypeAlias" }, { "name": "RICH_TREE_VIEW_PLUGINS", "kind": "Variable" }, { "name": "RichTreeView", "kind": "Variable" }, { "name": "richTreeViewClasses", "kind": "Variable" },