如何始终在聚焦的 Material-UI 按钮组件上应用 focusVisible 样式?

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

对于 Material-UI 按钮组件,我希望“focus”样式看起来与“focusVisible”样式相同。这意味着如果按钮以编程方式聚焦或使用鼠标聚焦,我希望它具有与使用 tab 键聚焦按钮相同的波纹效果。

我发现的一种解决方法是在元素获得焦点之前调用 dispatchEvent(new window.Event("keydown")),从而使键盘成为最后使用的输入类型。这将使按钮看起来像我想要的那样,直到 onMouseLeave 事件(来自 MUI )或另一个鼠标事件被触发,导致可见焦点消失。

我已经想出如何像这样更改组件的焦点样式:

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

因此,当按钮处于“焦点”状态时,我可以对其应用一些样式。 (例如,我应用了边框)。但我缺少如何应用样式。我试过将 className 'Mui-visibleFocus' 放在 Button 上,但这似乎没有效果。如果 Button 处于 visibleFocus 状态,是否有一些方法可以应用样式?

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

ButtonBase 为此利用了 useImperativeHandle hook。为了利用它,您将 ref 传递到 action 属性中,然后您可以稍后调用 actionRef.current.focusVisible().

然而,这本身是不够的,因为有几个鼠标和触摸事件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

不幸的是,disableTouchRipple 阻止了按钮上的点击和触摸动画。这些可以通过添加另一个您明确控制的 TouchRipple 元素来恢复。我下面的示例显示了处理 onMouseDownonMouseUp 作为概念验证,但理想的解决方案将处理 ButtonBase 处理的所有不同事件。

这是一个工作示例:

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>
  );
}

我们可以创建对material-uiButton组件的action的引用,并使用useLayoutEffect中的动作引用来实现涟漪效应

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>
  );
}

上面的FocusButton可以用来替代Button,或者你可以简单地添加一个引用并在触发方法中调用focusVisible()

例如:

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>

您可以在此找到演示 link