AlertDialog 确认对话框

需要用户明确回答的确认对话框。

'use client'

import { useState } from 'react'
import { AlertDialog, Button, Stack, Text } from '@hina-ui/react'

export default function Demo() {
  const [removed, setRemoved] = useState(false)

  async function remove() {
    await new Promise(resolve => setTimeout(resolve, 800))
    setRemoved(true)
  }

  return (
    <Stack gap="sm" align="start">
      <AlertDialog
        title="删除这篇文章?"
        description="删除后无法恢复,文章下的评论也会一并删除。"
        tone="danger"
        confirmText="删除"
        onConfirm={remove}
      >
        <Button variant="outline" tone="danger">
          删除文章
        </Button>
      </AlertDialog>
      {removed && (
        <Text tone="muted" size="sm">
          文章已删除。
        </Text>
      )}
    </Stack>
  )
}
tsx

用法

import { AlertDialog } from '@hina-ui/react'
ts

确认对话框只问一个问题:title 是问题本身,description 补充后果,底部固定一对按钮,文字可以用 cancelText 与 confirmText 替换。children 是触发器。点击遮罩不会关闭它,按 Esc 或者点「取消」才会;它没有右上角的关闭按钮,回答本身就是关闭的方式。

'use client'

import { useState } from 'react'
import { AlertDialog, Button, Stack, Text } from '@hina-ui/react'

export default function Demo() {
  const [published, setPublished] = useState(false)
  return (
    <Stack gap="sm" align="start">
      <AlertDialog
        title="发布这篇文章?"
        description="发布之后所有人都能看到它。"
        confirmText="发布"
        cancelText="再想想"
        onConfirm={() => setPublished(true)}
      >
        <Button variant="outline" tone="neutral">
          发布
        </Button>
      </AlertDialog>
      {published && (
        <Text tone="muted" size="sm">
          文章已发布。
        </Text>
      )}
    </Stack>
  )
}
tsx

示例

危险操作

tone="danger" 把「确定」换成危险色,用于删除这类不可撤销的操作。

import { AlertDialog, Button } from '@hina-ui/react'

export default function Demo() {
  return (
    <AlertDialog
      title="注销账号?"
      description="账号与所有数据会在 30 天后永久删除,期间登录即可撤销。"
      tone="danger"
      confirmText="注销"
    >
      <Button variant="outline" tone="danger">
        注销账号
      </Button>
    </AlertDialog>
  )
}
tsx

确认倒计时

confirmDelay 指定确认前的等待秒数,默认为 0。每次打开时重新计时,期间确认按钮禁用并显示剩余秒数;结束后恢复原文案与可点击状态,取消和 Esc 在等待期间仍可用。

打开期间修改 confirmDelay 会按新值重新计时,设为 0 立即解除等待。正小数向上取整,非正数或非有限值按 0 处理。确认失败后可以直接重试。

import { AlertDialog, Button } from '@hina-ui/react'

export default function Demo() {
  return (
    <AlertDialog
      title="确认操作?"
      description="倒计时结束后,确认按钮可以点击。"
      tone="danger"
      confirmText="确认"
      confirmDelay={3}
    >
      <Button variant="outline" tone="neutral">
        确认倒计时
      </Button>
    </AlertDialog>
  )
}
tsx

异步确认

onConfirm 接收确认处理函数。返回 Promise 或 PromiseLike 时,对话框等待其完成再关闭:期间「确定」显示加载指示,「取消」与 Esc 都不可用。

同步抛错或异步拒绝由组件内部捕获,并通过 onError 回调传出原始错误。对话框保持打开并恢复按钮,可以直接重试;使用 onError 更新错误提示。

'use client'

import { useState } from 'react'
import { AlertDialog, Button, Stack, Switch, Text } from '@hina-ui/react'

export default function Demo() {
  const [fail, setFail] = useState(false)
  const [status, setStatus] = useState('')

  async function archive() {
    setStatus('')
    await new Promise(resolve => setTimeout(resolve, 1000))
    if (fail) {
      throw new Error('归档失败,请稍后再试。')
    }
    setStatus('已归档。')
  }
  function handleError(error: unknown) {
    setStatus(error instanceof Error ? error.message : String(error))
  }

  return (
    <Stack gap="md" align="start">
      <Switch checked={fail} onCheckedChange={setFail}>
        模拟失败
      </Switch>
      <AlertDialog
        title="归档这个项目?"
        description="归档后项目变为只读,可以随时恢复。"
        confirmText="归档"
        onConfirm={archive}
        onError={handleError}
        content={
          status && (
            <Text tone="muted" size="sm">
              {status}
            </Text>
          )
        }
      >
        <Button variant="outline" tone="neutral">
          归档项目
        </Button>
      </AlertDialog>
    </Stack>
  )
}
tsx

受控

open 可受控。省略 children 时不渲染触发器,对话框只能从外部打开。

'use client'

import { useState } from 'react'
import { AlertDialog, Button, Inline } from '@hina-ui/react'

export default function Demo() {
  const [open, setOpen] = useState(false)
  return (
    <Inline gap="sm">
      <Button variant="outline" tone="neutral" onClick={() => setOpen(true)}>
        从外部打开
      </Button>
      <AlertDialog
        open={open}
        onOpenChange={setOpen}
        title="离开当前页面?"
        description="未保存的修改会丢失。"
      />
    </Inline>
  )
}
tsx

尺寸与位置

size 默认 sm,宽 384 像素,md 为 448 像素;placement 与 Dialog 相同,不设置时宽屏居中、窄屏贴底。

import { AlertDialog, Button, Inline } from '@hina-ui/react'

export default function Demo() {
  return (
    <Inline gap="sm">
      <AlertDialog title="清空回收站?" description="回收站里的 12 个文件会被永久删除。" size="md">
        <Button variant="outline" tone="neutral">
          md 尺寸
        </Button>
      </AlertDialog>
      <AlertDialog
        title="清空回收站?"
        description="回收站里的 12 个文件会被永久删除。"
        placement="center"
      >
        <Button variant="outline" tone="neutral">
          始终居中
        </Button>
      </AlertDialog>
      <AlertDialog
        title="清空回收站?"
        description="回收站里的 12 个文件会被永久删除。"
        placement="bottom"
      >
        <Button variant="outline" tone="neutral">
          始终贴底
        </Button>
      </AlertDialog>
    </Inline>
  )
}
tsx

行为

  • 打开时焦点落在「取消」按钮上,误按回车不会执行操作;关闭后焦点回到触发器。
  • 点击遮罩不关闭;Esc 关闭,等同于取消。
  • 打开期间页面停止滚动,焦点被限制在面板内。
  • 「确定」的处理函数返回 Promise 时,对话框在其结束前处于忙碌状态,成功后关闭;失败时调用 onError 并保持打开。

无障碍

  • 面板是 role="alertdialog",标题与说明分别关联到 aria-labelledby 与 aria-describedby。
  • 忙碌期间面板带有 aria-busy。
  • 按钮文字取自当前语言,可以替换。

API

Props

属性
类型
默认值
说明
title
string
—
必填。问题本身
description
string
—
补充说明
confirmText
string
当前语言
「确定」按钮的文字
confirmDelay
number
0
每次打开后的确认等待秒数
cancelText
string
当前语言
「取消」按钮的文字
tone
'accent' | 'danger'
'accent'
「确定」按钮的色调
size
'sm' | 'md'
'sm'
面板的最大宽度
placement
'center' | 'bottom'
—
不设置时随屏幕宽度变化
onConfirm
() => unknown
—
确认处理函数,可返回 Promise 或 PromiseLike
open
boolean
—
是否打开,可受控
className
string
—
追加至面板的类名

内容属性

属性
说明
children
触发器
content
说明与按钮之间的内容

回调

回调
参数
说明
onConfirm
—
点击「确定」;处理函数返回 Promise 时等待其完成
onCancel
—
点击「取消」
onError
unknown
确认失败的原始错误,弹窗保持打开