如何始终在有焦点的 Material-UI Button 组件上应用 focusVisible 样式?

Posted

技术标签:

【中文标题】如何始终在有焦点的 Material-UI Button 组件上应用 focusVisible 样式?【英文标题】:How to always apply focusVisible styling on a focused Material-UI Button component? 【发布时间】:2021-03-29 09:27:17 【问题描述】:

对于 Material-UI Button 组件,我希望“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 状态,是否有某种方法可以获得将应用的样式?

【问题讨论】:

我也遇到了同样的问题,可惜还是不行 Think Blew 发布了一个 Answer 说“尝试了这个并想出了一个使用 Buttonbase https://codesandbox.io/s/modal-dialog-button-autofocus-y9hfz 的操作引用的解决方案” 【参考方案1】:

我们可以创建对material-ui Button组件的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找到演示

【讨论】:

【参考方案2】:

ButtonBase(其中Buttondelegates to)有一个action prop,它提供set the button's focus-visible state的功能。

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

然而,这本身是不够的,因为有几个鼠标和触摸事件ButtonBase listens to 来启动/停止波纹。如果你使用disableTouchRipple 属性,它是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>
  );

【讨论】:

嗨,Ryan,这非常好,而且符合我的要求。但是使用这种应用“disableTouchRipple”道具的解决方案存在一个问题:现在按钮不再具有单击动画。您是否知道我可以从 ButtonBase 访问“rippleRef”以便我自己触发涟漪?或者是否有一种解决方法可以让点击动画再次工作? 你会得到一个点击动画,因为当你点击时焦点波纹立即开始;虽然我意识到它不一样——点击一个已经有焦点的按钮不会提供任何反馈。在没有disableTouchRipple 的情况下,我找不到让焦点涟漪对点击起作用的方法(我尝试了几种方法)。 Material-UI 没有公开任何访问rippleRef 的方式。如果您尝试通过TouchRippleProps 传递它,那么它会破坏ButtonBase,因为它无法访问rippleRef。如果不更改 Material-UI,我看不到任何方法。 看来你是对的。我一直在尝试找到一种访问rippleRef 的方法,但我认为没有一种方法......这很不幸。现在我想知道,是否可以使用单独的&lt;TouchRipple/&gt; 元素自己创建动画? @user3044553 是的,可以为点击动画添加一个单独的&lt;TouchRipple&gt; 元素。我已经更新了我的答案,包括一个过于简单化(不处理 ButtonBase 处理的所有事件)版本。

以上是关于如何始终在有焦点的 Material-UI Button 组件上应用 focusVisible 样式?的主要内容,如果未能解决你的问题,请参考以下文章

如何在错误和焦点上更改 Material-UI TextField 底部和标签颜色

使用 React Material-UI 自动完成始终显示默认选项

在焦点、反应上更改 Material-UI 文本字段的样式

带有 Material-UI 的 s-s-r 上的 @emotion/cache 始终为空

如何在 Horizo​​ntalGridView 中始终将焦点放在第 0 位?

如何让输入单选元素在反应 [material-ui] 中水平对齐?