简体   繁体   中英

How to always apply focusVisible styling on a focused Material-UI Button component?

For a Material-UI Button component, I would like to have the "focus" styling look the same as "focusVisible" styling. Meaning I want it to have the same ripple effect visible if the button was focused programatically or with the mouse as if the button was focused with the tab key.

A sort-of workaround I have found is to call dispatchEvent(new window.Event("keydown")) on the element before it is focused, causing keyboard to be the last input type used. This will have the effect of making the button look the way I want UNTIL the onMouseLeave event (from MUI <ButtonBase/>) or another mouse event is fired, causing the visible focus to disappear.

I have figured out how to change the focus styling of the component like this:

import React from "react"
import { withStyles } from "@material-ui/core/styles"
import Button from "@material-ui/core/Button"

const styles = {
  root: {
    '&:focus': {
      border: "3px solid #000000"
    }
  }
}

const CustomButtonRaw = React.forwardRef((props, ref) => {
  const { classes, ...rest } = props
  return <Button classes={{root: classes.root}} {...rest} ref={ref}/>
}

const CustomButton = withStyles(styles, { name: "CustomButton" })(CustomButtonRaw)

export default CustomButton

So, I can apply some style to the button when it is in "focus" state. (For ex. I applied a border). But I am missing how to get the styles to apply. I have tried putting the className 'Mui-visibleFocus' on the Button but that did not seem to have an effect. Is there some way to get the styles that would be applied if the Button was in visibleFocus state?

ButtonBase (which Button delegates to ) has an action prop which provides the ability to set the button's focus-visible state .

ButtonBase leverages the useImperativeHandle hook for this. To leverage it, you pass a ref into the action prop and then you can later call actionRef.current.focusVisible() .

However, this by itself is not sufficient, because there are several mouse and touch events that ButtonBase listens to in order to start/stop the ripple. If you use the disableTouchRipple prop, it prevents ButtonBase from trying to start/stop the ripple based on those events .

Unfortunately disableTouchRipple prevents click and touch animations on the button. These can be restored by adding another TouchRipple element explicitly that you control. My example below shows handling onMouseDown and onMouseUp as a proof-of-concept, but an ideal solution would deal with all the different events that ButtonBase handles.

Here's a working example:

import React from "react";
import Button from "@material-ui/core/Button";
import TouchRipple from "@material-ui/core/ButtonBase/TouchRipple";

const FocusRippleButton = React.forwardRef(function FocusRippleButton(
  { onFocus, onMouseDown, onMouseUp, children, ...other },
  ref
) {
  const actionRef = React.useRef();
  const rippleRef = React.useRef();
  const handleFocus = (event) => {
    actionRef.current.focusVisible();
    if (onFocus) {
      onFocus(event);
    }
  };
  const handleMouseUp = (event) => {
    rippleRef.current.stop(event);
    if (onMouseUp) {
      onMouseUp(event);
    }
  };
  const handleMouseDown = (event) => {
    rippleRef.current.start(event);
    if (onMouseDown) {
      onMouseDown(event);
    }
  };
  return (
    <Button
      ref={ref}
      action={actionRef}
      disableTouchRipple
      onFocus={handleFocus}
      onMouseDown={handleMouseDown}
      onMouseUp={handleMouseUp}
      {...other}
    >
      {children}
      <TouchRipple ref={rippleRef} />
    </Button>
  );
});
export default function App() {
  return (
    <div className="App">
      <FocusRippleButton variant="contained" color="primary">
        Button 1
      </FocusRippleButton>
      <br />
      <br />
      <FocusRippleButton
        variant="contained"
        color="primary"
        onFocus={() => console.log("Some extra onFocus functionality")}
      >
        Button 2
      </FocusRippleButton>
    </div>
  );
}

编辑无条件焦点波纹

We can create a reference to the action of the material-ui Button component and use the action reference within useLayoutEffect to achieve the ripple effect

import React, { createRef, useLayoutEffect } from "react";
import Button from "@material-ui/core/Button";

function FocusButton(props) {
  const { handleClose } = props;

  const actionRef = createRef();

  useLayoutEffect(() => {
    if (actionRef.current) {
      actionRef.current.focusVisible();
    }
  }, []);

  return (
    <Button action={actionRef} onClick={handleClose}>
      Ok
    </Button>
  );
}

The above FocusButton can be used as a replacement to Button or simply you can add a reference and call the focusVisible() in the trigger method

Eg:

const buttonRef = createRef();

const handleButton2Click = () => {
    buttonRef.current.focusVisible();
};

.
.
.
.

<Button action={buttonRef} variant="outlined">
        Button 1
</Button>

<Button variant="outlined" color="primary" onClick={handleButton2Click}>
        Button 2
</Button>

You can find the demo in this link

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM