Repository: mui/material-ui (TypeScript)
Date: 2021-09-06 11:19:27+00:00
Task type: Bug Fix

Issue:
[ButtonBase] Unfocus does not clear ripple
<!-- Provide a general summary of the issue in the Title above -->

keyboard tab between ButtonBase component does not clear the focus ripple. I can confirm that revert the code on this commit does not have this issue (in `ButtonBase.js`). https://github.com/mui-org/material-ui/commit/5f30983bfa16195237fde55a78d5e43b151a29fa

cc @michaldudak can you help take a look?

<!--
  Thank you very much for contributing to Material-UI by creating an issue! ❤️
  To avoid duplicate issues we ask you to check off the following list.
-->

<!-- Checked checkbox should look like this: [x] -->

- [x] The issue is present in the latest release.
- [x] I have searched the [issues](https://github.com/mui-org/material-ui/issues) of this repository and believe that this is not a duplicate.

## Current Behavior 😯

<!-- Describe what happens instead of the expected behavior. -->

<img width="1440" alt="Screen Shot 2564-09-06 at 14 58 24" src="https://user-images.githubusercontent.com/18292247/132181388-0bdc536c-db56-4330-97a8-8e6b2542e371.png">

## Expected Behavior 🤔

it should clear the ripple element
<!-- Describe what should happen. -->

## Steps to Reproduce 🕹

<!--
  Provide a link to a live example (you can use codesandbox.io) and an unambiguous set of steps to reproduce this bug.
  Include code to reproduce, if relevant (which it most likely is).

  You should use the official codesandbox template as a starting point: https://material-ui.com/r/issue-template-next

  If you have an issue concerning TypeScript please start from this TypeScript playground: https://material-ui.com/r/ts-issue-template

  Issues without some form of live example have a longer response time.
-->

Steps:

1. open https://next.material-ui.com/components/buttons/#main-content
2. tab through the docs
3.
4.

## Context 🔦

<!--
  What are you trying to accomplish? How has this issue affected you?
  Providing context helps us come up with a solution that is most useful in the real world.
-->

## Your Environment 🌎

<!--
  Run `npx @mui/envinfo` and post the results.
  If you encounter issues with TypeScript please include the used tsconfig.
-->
<details>
  <summary>`npx @mui/envinfo`</summary>
  
```
  Don't forget to mention which browser you used.
  Output from `npx @mui/envinfo` goes here.
```
</details>



Issue discussion:
Yes, I've noticed that and I'm already working on a fix.
I think I've talked repeatedly about not doing the abstraction. The ButtonBase is integral to the codebase and shouldn't be jeopardized by the unstyled effort. The unstyled effort can be a separate experiment that doesn't need to affect the core product.
> The ButtonBase is integral to the codebase and shouldn't be jeopardized by the unstyled effort

@eps1lon If you could expand on the why, I think that it would be great.

Looking at this regression, it seems that one of the values of using unstyled in the material design components is that it allows surfacing problems earlier. For instance, maybe we missed a test case, or maybe we didn't use the best abstraction for useButton or maybe it's something completely unrelated.
> @eps1lon If you could expand on the why, I think that it would be great.

Why do I need to explain that a fundamental component should not be affected by experiments? Like what would you expect to hear?
> I think I've talked repeatedly about not doing the abstraction.

You mean not to implement `Button` with `useButton`? If so, I suppose I may have missed that. In the useButton PR, both @mnajdova and @oliviertassinari suggested using the new hook in the ButtonBase and I haven't seen any objections from your side.

As for the issue itself, I already pinpointed it and I'm adding few tests to ButtonBase to help to avoid such problems in the future.

Solution patch:
diff --git a/packages/mui-material/src/useTouchRipple/useTouchRipple.ts b/packages/mui-material/src/useTouchRipple/useTouchRipple.ts
--- a/packages/mui-material/src/useTouchRipple/useTouchRipple.ts
+++ b/packages/mui-material/src/useTouchRipple/useTouchRipple.ts
@@ -43,12 +43,9 @@ const useTouchRipple = (props: UseTouchRippleProps) => {
 
   function useRippleHandler(
     rippleAction: keyof TouchRippleActions,
-    eventCallback?: (event: React.SyntheticEvent) => void,
     skipRippleAction = disableTouchRipple,
   ) {
     return useEventCallback((event: React.SyntheticEvent) => {
-      eventCallback?.(event);
-
       if (!skipRippleAction && rippleRef.current) {
         rippleRef.current[rippleAction](event);
       }
@@ -90,7 +87,7 @@ const useTouchRipple = (props: UseTouchRippleProps) => {
     }
   });
 
-  const handleBlur = useRippleHandler('stop');
+  const handleBlur = useRippleHandler('stop', false);
   const handleMouseDown = useRippleHandler('start');
   const handleContextMenu = useRippleHandler('stop');
   const handleDragLeave = useRippleHandler('stop');


Test patch:
diff --git a/packages/mui-material/src/ButtonBase/ButtonBase.test.js b/packages/mui-material/src/ButtonBase/ButtonBase.test.js
--- a/packages/mui-material/src/ButtonBase/ButtonBase.test.js
+++ b/packages/mui-material/src/ButtonBase/ButtonBase.test.js
@@ -521,6 +521,36 @@ describe('<ButtonBase />', () => {
         fireEvent.click(getByTestId('trigger'));
         expect(container.querySelectorAll('.ripple-pulsate')).to.have.lengthOf(1);
       });
+
+      it('should stop the ripple on blur if disableTouchRipple is set', () => {
+        const buttonActions = React.createRef();
+
+        const { getByRole } = render(
+          <ButtonBase
+            action={buttonActions}
+            focusRipple
+            disableTouchRipple
+            TouchRippleProps={{
+              classes: {
+                rippleVisible: 'ripple-visible',
+                child: 'child',
+                childLeaving: 'child-leaving',
+              },
+            }}
+          />,
+        );
+
+        const button = getByRole('button');
+
+        simulatePointerDevice();
+        focusVisible(button);
+
+        act(() => {
+          button.blur();
+        });
+
+        expect(button.querySelectorAll('.ripple-visible .child-leaving')).to.have.lengthOf(1);
+      });
     });
   });