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

[Button][base] Drop component prop #36677

Merged
merged 36 commits into from Apr 26, 2023

Conversation

mnajdova
Copy link
Member

@mnajdova mnajdova commented Mar 29, 2023

This PR removes the component prop from the ButtonUnstyled component. The motivation for this is that there is a duplicated API for setting the root slot (through the component prop and through the slots.root prop). The difference between these two APIs is that the component prop makes the component polymorphic, which means that you can send the root component's props on the ButtonUnstyled component directly, without getting typescript issues. However, this has shown to have a big performance implications when doing the type checks (see some existing issues for the Material UI's components). One of #36679

BREAKING CHANGE

It will be covered by codemod, see #36831

For Base UI components, the component prop value should be moved to slots.root:

 <Button
-  component="span"
+  slots={{ root: "span" }}
 />

If using TypeScript, you should add the custom component type as a generic on the Button component.

-<Button
+<Button<typeof CustomComponent>
   slots={{ root: CustomComponent }}
   customProp="foo"
 />

@mnajdova mnajdova added breaking change component: button This is the name of the generic UI component, not the React module! package: base-ui Specific to @mui/base labels Mar 29, 2023
@mui-bot
Copy link

mui-bot commented Mar 29, 2023

@mnajdova mnajdova marked this pull request as ready for review March 29, 2023 10:01
@github-actions github-actions bot added the PR: out-of-date The pull request has merge conflicts and can't be merged label Mar 31, 2023
@mnajdova mnajdova added the on hold There is a blocker, we need to wait label Mar 31, 2023
@github-actions github-actions bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged label Apr 17, 2023
@@ -165,6 +165,10 @@ function testSlotsProp(element: React.ReactElement, getOptions: () => UnstyledCo
});

it('uses the component provided in the `component` prop when both `component` and `slots.root` are provided', () => {
if (skip && skip.indexOf('componentProp') >= 0) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test should be run only if we are not skipping the component prop test.

/**
* @ignore
*/
onBlur: PropTypes.func,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@michaldudak I think we should go the other way around and explicitly define these props in the ButtonUnstyledProps interface, as if a custom component is used, that does not have these props in the props interface, there will be a typescript error, which would be a bug in my opinion.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if I understand you correctly. Why would there be a TS error? We don't actually use these props anywhere. Also in ButtonUnstyled.spec.tsx we have a case with CustomComponent that don't have these defined and it works fine.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't have with custom component. For e.g. this is failing:

<ButtonUnstyled<typeof CustomComponent>
  slots={{ root: CustomComponent }}
  stringProp="test"
  numberProp={0}
  onClick={() => {}}
/>

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I get it now. It's an interesting problem. The ButtonUnstyled doesn't handle (or require) the click (or several other) events, so strictly speaking, it's unnecessary. It's necessary in the slots.root, though, but there is no way of enforcing it.
In other words, even if we had onClick in ButtonUnstyledProps, it would be possible to provide CustomComponent that ignores its onClick prop and effectively create a broken button.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is a failing test: https://app.circleci.com/pipelines/github/mui/material-ui/95773/workflows/1ec2c63d-7103-4340-b894-909bf873b7a0/jobs/509209

I think we should update the types to add these event handlers.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple of thoughts I have about this:

  1. How do we decide which events to define explicitly?
  2. If they are defined explicitly, we will lose polymorphism in their parameters. We'll have to type them as either MouseEventHandler<Element> or MouseEventHandler<HTMLButtonElement>. In either case, another test will fail (the last one from the .spec suite).
  3. If we use this pattern, we should also update all the other components

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see your point. I've added ts-expect-error directive on the new test, so that in the future, if we decide to handle this, we have some record that it was an intended decision. We can discuss it separately tough in the future.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if the ButtonUnstyled definition shouldn't be function ButtonUnstyled(props: ButtonUnstyledProps, forwardedRef: React.ForwardedRef<any>) (without the generics). This way, we get strongly typed props inside the component definition. Nothing would change with the exported definition as we cast it to PloymorphicComponent anyway.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the explicitness that we have now. We can open a separate issue for this and see what other things I guess. It's anyway internal change

@mnajdova
Copy link
Member Author

@samuelsycamore do we want to still keep the "Unstyled" in the title for the base pages, for e.g. https://deploy-preview-36677--material-ui.netlify.app/base/react-button/ vs https://mui.com/base/react-button/. I would vote for keeping only Button, as we are talking about the concept of button that is implemented trough component and hook.

@@ -682,22 +685,25 @@ export default async function generateComponentApi(
// eslint-disable-next-line no-console
console.log('Built API docs for', reactApi.name);

const normalizedApiPathname = reactApi.apiPathname.replace(/\\/g, '/');
const noramlizedFilename = reactApi.filename.replace(/\\/g, '/');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const noramlizedFilename = reactApi.filename.replace(/\\/g, '/');
const normalizedFilename = reactApi.filename.replace(/\\/g, '/');

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed

The same applies for props specific to custom primitive elements:

```tsx
<Button<'svg'> viewBox="0 0 24 24" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be:

Suggested change
<Button<'svg'> viewBox="0 0 24 24" />
<Button<'svg'> slots={{ root: 'svg' }} viewBox="0 0 24 24" />

but technically, this won't render anything as the svg should have something inside.
Perhaps an example with another element would work better, such as:

Suggested change
<Button<'svg'> viewBox="0 0 24 24" />
<Button<'img'> slots={{ root: 'img' }} src="button.png" />

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed

@hbjORbj
Copy link
Member

hbjORbj commented Apr 25, 2023

I think this should no longer appear.

mui-base/src/Button/Button.spec.tsx
Screenshot 2023-04-25 at 12 15 24

@michaldudak
Copy link
Member

michaldudak commented Apr 25, 2023

@hbjORbj, you may have your TS definitions cached from another branch (the Button definition says it's an OverridableComponent, which is no longer the case). Restart the TS server, and the error should be gone.

@hbjORbj
Copy link
Member

hbjORbj commented Apr 25, 2023

@michaldudak Yes it's gone ;) thanks

Copy link
Member

@hbjORbj hbjORbj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good!

@samuelsycamore
Copy link
Member

samuelsycamore commented Apr 25, 2023

@samuelsycamore do we want to still keep the "Unstyled" in the title for the base pages, for e.g. https://deploy-preview-36677--material-ui.netlify.app/base/react-button/ vs https://mui.com/base/react-button/. I would vote for keeping only Button, as we are talking about the concept of button that is implemented trough component and hook.

Agreed, I think it's better to drop "Unstyled" from the name wherever it comes up. From a branding perspective, I'd much rather teach our users to call it a "Base UI Button" than an "Unstyled Button."

@github-actions github-actions bot added the PR: out-of-date The pull request has merge conflicts and can't be merged label Apr 26, 2023
@hbjORbj hbjORbj mentioned this pull request Apr 26, 2023
1 task
@michaldudak michaldudak changed the title [ButtonUnstyled] Drop component prop [Button][base] Drop component prop Apr 26, 2023
@michaldudak michaldudak added this to the MUI Base stable release milestone Apr 26, 2023
@github-actions github-actions bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged label Apr 26, 2023
@hbjORbj
Copy link
Member

hbjORbj commented Apr 26, 2023

I made the final touches. Will merge it once CIs all pass.

@hbjORbj hbjORbj enabled auto-merge (squash) April 26, 2023 08:53
@hbjORbj hbjORbj merged commit 95f93e7 into mui:master Apr 26, 2023
21 checks passed
binh1298 pushed a commit to binh1298/material-ui that referenced this pull request May 17, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
breaking change component: button This is the name of the generic UI component, not the React module! package: base-ui Specific to @mui/base
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants