mui / material-ui

Material UI: Comprehensive React component library that implements Google's Material Design. Free forever.
https://mui.com/material-ui/
MIT License
93.89k stars 32.26k forks source link

[RFC] The way to override components and slots #34334

Open siriwatknp opened 2 years ago

siriwatknp commented 2 years ago

What's the problem? πŸ€”

Before reading this RFC, please go through #34333 first so that we are on the same page about the meaning of components and slots.

From #33416, and #21453 it seems like we are only talking about slot override (replacing the slot). However, for Material UI and Joy UI, we also need a component override (changing the HTML tag of the slot).

What are the requirements? ❓

Proposed solution 🟒

My proposed solution aims for the least breaking changes. All components (MUI Base, Material UI, and Joy UI) should follow this:

Components with a single slot (root)

~For this kind of components, it does not make sense to replace the root slot so having just component prop is cleaner.~ This is not true for MUI Base πŸ€”.

Components with more than one slot

To replace the HTML of the slot, use slotsProps={{ listbox: { component: 'div' } }}.

Components that have nested components

See the problem and another example

Flatten the nested component slots with a new name. For example, TextField has Input as a nested component could look something like this:

<TextField
  slotsProps={{
    root: {...},
    inputRoot: { ... },
    inputInput: { ... },
    helperText: { ... }
  }}
/>
michaldudak commented 2 years ago

So, if I understand this correctly, you're proposing that the components.x is a shorter way of writing slotProps.x.component, yes?

siriwatknp commented 2 years ago

So, if I understand this correctly, you're proposing that the components.x is a shorter way of writing slotProps.x.component, yes?

hmm, I forgot that we can use component inside slotsProps:

<Select
  slotProps={{
    listbox: {
      component: 'div',
    }
  }}
/>

This sounds better than having another components prop.

michaldudak commented 2 years ago

Generally, I see two changes are proposed here:

  1. Change (or settle on) the interpretation of the component prop, so that it is used to customize the leaf element (similarly to the as prop).
  2. Introduce the components prop that would act as a shortcut to slotProps.x.component.

Do I understand the intent correctly?

Assuming so, this proposal would only make sense in styled libraries (Material UI and Joy UI). In MUI Base, as explained in https://github.com/mui/material-ui/issues/34333#issuecomment-1251007834, the component is almost exclusively a DOM node, so it does not have the as prop. That being said, if we decide to go with option 1 I listed above, the component prop would be unnecessary in MUI Base (as was already proposed in https://github.com/mui/material-ui/issues/28189). We could leave it as is (as an alias for slots.root), but it would have a different meaning in styled vs unstyled libraries.

mnajdova commented 2 years ago

Just clarifying that I understand the proposal correctly. We would have slots and slotsProps in Mui Base, Material UI and Joy UI, that would behave the same (replace completely the slot that is being rendered). For e.g.:

const Root = slots.root ?? DefaultRootComponent;

Apart from this, in the components inside Material UI and Joy UI, there would be a component prop which is basically an alias for the as prop in emotion (so that when it is used, all styles that were previously defined will still be applied). As all slots would be compossible components, we should already have the support for the component prop there, so we should need only one component prop per component.

We don't really need this in Mui Base, as the behavior is the same as slots.root as we don't have any styles, but that is a different discussion, already linked in https://github.com/mui/material-ui/issues/34334#issuecomment-1251043750.

Btw, this is how it already works in Material UI, so I don't expect big change anyway, unless I am misunderstanding the proposal.

siriwatknp commented 2 years ago

so we should need only one component prop per component.

Yes, the component prop can be considered as a shortcut for slotsProps.component. If both are provided, slotsProps.component should have higher priority. cc @michaldudak

Btw, this is how it already works in Material UI, so I don't expect big change anyway, unless I am misunderstanding the proposal.

I'd say that I expect big change for Material UI because most components are using another pattern. e.g. Accordion has TransitionComponent and TransitionProps. This will be breaking changes:

// current
<Accordion TransitionComponent={Slide} TransitionProps={{ delay: 100 }} />

// new
<Accordion slots={{ transition: Slide }} slotsProps={{ transition: { delay: 100 } }} />
siriwatknp commented 2 years ago

Marked this as RFC for Material UI and Joy UI only.

mnajdova commented 2 years ago

I'd say that I expect big change for Material UI because most components are using another pattern. e.g. Accordion has TransitionComponent and TransitionProps. This will be breaking changes:

// current
<Accordion TransitionComponent={Slide} TransitionProps={{ delay: 100 }} />

// new
<Accordion slots={{ transition: Slide }} slotsProps={{ transition: { delay: 100 } }} />

What I meant is that in terms of behavior it will behave the same. For the props surface, we could support both for smoother migration and provide codemods if people want to migrate to the new paradigm sooner.

michaldudak commented 2 years ago

What I meant is that in terms of behavior it will behave the same.

Not necessarily. The current pattern in Material UI uses the as prop to modify the leaf component:

const AutocompletePaper = styled(Paper, { /* ... */ });

/* ... */

<AutocompletePaper
  ownerState={ownerState}
  as={PaperComponent}
  {...componentsProps.paper}
  className={clsx(classes.paper, componentsProps.paper?.className)}
>

whereas the slots prop would replace the whole slot (unless it has a different behavior in styled vs unstyled libraries).

siriwatknp commented 2 years ago

For the props surface, we could support both for smoother migration and provide codemods if people want to migrate to the new paradigm sooner

Yep, agree. We can start introducing slots and slotsProps in v5 with a codemod and then deprecate the existing props, e.g. TransitionComponent.

mnajdova commented 2 years ago

whereas the slots prop would replace the whole slot (unless it has a different behavior in styled vs unstyled libraries).

Yep, component props is "as" and the slot props behave the same as the component props we have on some components, for example TransitionComponent, PopperComponent etc.

siriwatknp commented 1 year ago

Update: Joy UI already follows this approach.

siriwatknp commented 9 months ago

This issue can be closed once all Material UI components support slots and slotProps. cc @DiegoAndai

colangeloproductions commented 9 months ago

Hi there, I've been trying to understand the possibilities of the slots and slotProps with help of:

But I'm not sure its capable of what I'm trying to do.. maybe someone could give some advice?

I have a React Component thats based on the MUI Base Input -> which is InputCalculate (Math operations inside of Input field)

Now with the NumberInput that has been released I've been trying to use the slots prop to render my InputCalculate instead of the default.

Can anyone advise me how I can render a complete different HTML structure / Component and also give it props? The docs I've read are giving me the impression I can only change the HTML Tag (ol to ul, button to a etc.) and css styles.

Cheers Romeo

michaldudak commented 9 months ago

You can pass in props using the slotProps prop. Unfortunately, due to performance issues with TypeScript, if you pass in a custom slot component, the corresponding slotProps won't have a related type. So if you do slots={{ root: MyFancyComponent }}, the slotProps.root won't expect the props of MyFancyComponent and some casting may be necessary. We are aware of the limitation of this API, and are currently working on improvements in this area. We plan to post an RFC soon and flesh out all the details of the new API.

colangeloproductions commented 9 months ago

Okay thats some clarity, but what about the InputCalculate that I would like render instead of the input slot from the NumberInput?

michaldudak commented 9 months ago

What exactly do you have a problem with?

This should work:

<NumberInput slots={{ input: InputCalculate }} />
poojaphapale commented 8 months ago

@michaldudak How can we pass parameters to InputCalculate <NumberInput slots={{ input: InputCalculate }} />

michaldudak commented 8 months ago

You can use slotProps:

<NumberInput slots={{ input: InputCalculate }} slotProps={{ input: { ... } }} />