Skip to content
Dialog

dialog 是一个浮动元素,它显示需要立即关注的信息,出现在页面内容上方并阻止与页面的交互,直到它被关闭。

¥A dialog is a floating element that displays information that requires immediate attention, appearing over the page content and blocking interactions with the page until it is dismissed.

它与弹出窗口具有类似的交互作用,但有两个关键区别:

¥It has similar interactions to a popover but with two key differences:

  • 它是模态的,在对话框后面渲染一个背景,使其后面的内容变暗,从而使页面的其余部分无法访问。

    ¥It is modal and renders a backdrop behind the dialog that dims the content behind it, making the rest of the page inaccessible.

  • 它位于视口的中心,不锚定到任何特定的参考元素。

    ¥It is centered in the viewport, not anchored to any particular reference element.

基础

¥Essentials

可访问的对话框组件具有以下品质:

¥An accessible dialog component has the following qualities:

  • 退出:当用户在打开的对话框外按 esc 键或时,它将关闭。

    ¥Dismissal: When the user presses the esc key or outside the dialog while it is open, it closes.

  • 角色:这些元素被赋予了相关的角色和 ARIA 属性,以便屏幕阅读器可以访问。

    ¥Role: The elements are given relevant role and ARIA attributes to be accessible to screen readers.

  • 焦点管理:焦点完全被困在对话框内,必须由用户关闭。

    ¥Focus management: Focus is fully trapped inside the dialog and must be dismissed by the user.

示例

¥Examples

下面有部分对这两个示例进行了深入解释。

¥Both of these examples have sections explaining them in-depth below.

基本对话框

¥Basic dialog

代码沙盒演示

¥CodeSandbox demo

此示例演示如何创建在单个实例中使用的对话框以熟悉基础知识。

¥This example demonstrates how to create a dialog for use in a single instance to familiarize yourself with the fundamentals.

让我们看一下这个例子:

¥Let’s walk through the example:

打开状态

¥Open state

import {useState} from 'react';
 
function Dialog() {
  const [isOpen, setIsOpen] = useState(false);
}

isOpen 确定对话框当前是否在屏幕上打开。它用于条件渲染。

¥isOpen determines whether or not the dialog is currently open on the screen. It is used for conditional rendering.

useFloating 钩子

¥useFloating Hook

useFloating() Hook 为我们的对话框提供了上下文。我们需要向它传递一些信息:

¥The useFloating() Hook provides context for our dialog. We need to pass it some information:

  • open:上面 useState() Hook 的打开状态。

    ¥open: The open state from our useState() Hook above.

  • onOpenChange:打开或关闭对话框时将调用的回调函数。我们将使用它来更新我们的 isOpen 状态。

    ¥onOpenChange: A callback function that will be called when the dialog is opened or closed. We’ll use this to update our isOpen state.

import {useFloating} from '@floating-ui/react';
 
function Dialog() {
  const [isOpen, setIsOpen] = useState(false);
 
  const {refs, context} = useFloating({
    open: isOpen,
    onOpenChange: setIsOpen,
  });
}

与钩子交互

¥Interaction Hooks

交互钩子返回包含属性键的对象,这些属性使对话框能够打开、关闭或可供屏幕阅读器访问。

¥Interaction Hooks return objects containing keys of props that enable the dialog to be opened, closed, or accessible to screen readers.

使用从 Hook 返回的 context,调用交互 Hooks:

¥Using the context that was returned from the Hook, call the interaction Hooks:

import {
  // ...
  useClick,
  useDismiss,
  useRole,
  useInteractions,
  useId,
} from '@floating-ui/react';
 
function Dialog() {
  const [isOpen, setIsOpen] = useState(false);
 
  const {refs, context} = useFloating({
    open: isOpen,
    onOpenChange: setIsOpen,
  });
 
  const click = useClick(context);
  const dismiss = useDismiss(context, {
    outsidePressEvent: 'mousedown',
  });
  const role = useRole(context);
 
  // Merge all the interactions into prop getters
  const {getReferenceProps, getFloatingProps} = useInteractions([
    click,
    dismiss,
    role,
  ]);
 
  // Set up label and description ids
  const labelId = useId();
  const descriptionId = useId();
}
  • useClick() 添加了单击参考元素时打开或关闭对话框的功能。不过,对话框可能不会附加到参考元素,因此这是可选的。

    ¥useClick() adds the ability to toggle the dialog open or closed when the reference element is clicked. A dialog may not be attached to a reference element though, so this is optional.

  • useDismiss() 添加了当用户按 esc 键或按对话框外部时关闭对话框的功能。outsidePressEvent 选项设置为 'mousedown',以便触摸事件变得惰性并且不会穿过背景,因为默认行为是预先的。

    ¥useDismiss() adds the ability to dismiss the dialog when the user presses the esc key or presses outside of the dialog. The outsidePressEvent option is set to 'mousedown' so that touch events become lazy and do not fall through the backdrop, as the default behavior is eager.

  • useRole()dialog 的正确 ARIA 属性添加到对话框和参考元素中。

    ¥useRole() adds the correct ARIA attributes for a dialog to the dialog and reference elements.

最后,useInteractions() 将他们所有的 props 合并为可用于渲染的 prop getter。

¥Finally, useInteractions() merges all of their props into prop getters which can be used for rendering.

在这之后:

¥After this:

  • useId() 为对话框的标题和描述元素生成唯一的 id,以便屏幕阅读器以广泛的兼容性宣布对话框的内容。

    ¥useId() generates a unique id for the heading and description elements of the dialog, so that the content of the dialog is announced by screen readers with wide compatibility.

渲染

¥Rendering

现在我们已经设置了所有变量和 Hooks,我们可以渲染我们的元素。

¥Now we have all the variables and Hooks set up, we can render out our elements.

function Dialog() {
  // ...
  return (
    <>
      <button ref={refs.setReference} {...getReferenceProps()}>
        Reference element
      </button>
      {isOpen && (
        <FloatingOverlay
          lockScroll
          style={{background: 'rgba(0, 0, 0, 0.8)'}}
        >
          <FloatingFocusManager context={context}>
            <div
              ref={refs.setFloating}
              aria-labelledby={labelId}
              aria-describedby={descriptionId}
              {...getFloatingProps()}
            >
              <h2 id={labelId}>Heading element</h2>
              <p id={descriptionId}>Description element</p>
              <button onClick={() => setIsOpen(false)}>
                Close
              </button>
            </div>
          </FloatingFocusManager>
        </FloatingOverlay>
      )}
    </>
  );
}
  • {...getReferenceProps()} / {...getFloatingProps()} 将交互 Hook 中的 props 传播到相关元素上。它们包含 onClickaria-expanded 等属性。

    ¥{...getReferenceProps()} / {...getFloatingProps()} spreads the props from the interaction Hooks onto the relevant elements. They contain props like onClick, aria-expanded, etc.

  • <FloatingOverlay /> 是一个在浮动元素后面渲染背景覆盖元素的组件,具有锁定正文滚动的能力。FloatingOverlay 文档

    ¥<FloatingOverlay /> is a component that renders a backdrop overlay element behind the floating element, with the ability to lock the body scroll. FloatingOverlay docs.

  • <FloatingFocusManager /> 是一个管理模式行为对话框焦点的组件,将焦点捕获在内部。它应该直接封装浮动元素,并且仅在渲染对话框时才渲染。FloatingFocusManager 文档

    ¥<FloatingFocusManager /> is a component that manages focus of the dialog for modal behavior, trapping focus inside. It should directly wrap the floating element and only be rendered when the dialog is also rendered. FloatingFocusManager docs.

可重用的对话框组件

¥Reusable dialog component

代码沙盒演示

¥CodeSandbox demo

最好创建一个可重用的组件 API,可以更轻松地在各种不同的场景中使用。我们可以将所有 Hook 放入单个自定义 Hook 中,以获得更好的可重用性,然后由封装状态的控制器组件使用。

¥It is better to create a reusable component API that can be used in a variety of different scenarios more easily. We can place all of our Hooks into a single custom Hook for better reusability, which is then used by a controller component which encapsulates the state.

可重复使用的组件可以:

¥The reusable component can:

  • 不受控制或受控制

    ¥Be uncontrolled or controlled

  • 接受任何元素作为 <DialogTrigger />

    ¥Accept any element as the <DialogTrigger />

  • 读取打开状态改变样式

    ¥Read the open state to change styles

function App() {
  return (
    <Dialog>
      <DialogTrigger>My trigger</DialogTrigger>
      <DialogContent>
        <DialogHeading>My dialog heading</DialogHeading>
        <DialogDescription>
          My dialog description
        </DialogDescription>
        <DialogClose>Close</DialogClose>
      </DialogContent>
    </Dialog>
  );
}

控制器组件

¥Controller component

  • <Dialog />

这是管理对话框状态并向其余组件提供 API 的控制器组件。

¥This is the controller component that manages the dialog’s state and provides the API to the rest of the components.

渲染组件

¥Render components

这些组件读取根对话框组件提供的上下文并渲染适当的元素。

¥These components read the context provided by the root Dialog component and render the appropriate elements.

组件必须封装在 forwardRef() 中以允许引用,并且应该合并引用以确保所有引用都被保留并转发到元素。属性也被合并以防止覆盖。

¥The components must be wrapped in forwardRef() to allow refs, and should merge the refs to ensure all refs are preserved and forwarded to the element. Props are also merged to prevent overwriting.

  • <DialogTrigger /> 是对话框所附加的触发按钮。如果你想将 asChild 属性附加到自定义元素,则它接受 asChild 属性。它还具有基于打开/关闭状态附加到样式的 data-state

    ¥<DialogTrigger /> is the trigger button the dialog is attached to. This accepts an asChild prop if you want to attach it to a custom element. It also has a data-state attached to style based on the open/closed state.

  • <DialogContent /> 是对话框元素,它可以包含任何子元素(React 节点)。

    ¥<DialogContent /> is the dialog element, which can contain any children (React nodes).

  • <DialogHeading /> 是对话框的标题元素。

    ¥<DialogHeading /> is the heading element for the dialog.

  • <DialogDescription /> 是对话框的描述元素。

    ¥<DialogDescription /> is the description element for the dialog.

  • <DialogClose /> 是对话框的关闭按钮。

    ¥<DialogClose /> is the close button for the dialog.