Layer Dialog 分层对话框
@lark-ui/core
import { useState } from "react";
import { Button, Input, LayerDialog } from "@lark-ui/core";

export function LayerDialogActionDemo() {
  const [name, setName] = useState("生产环境 API");
  const [hostname, setHostname] = useState("api.example.com");

  return (
    <LayerDialog.Root>
      <LayerDialog.Trigger
        render={(props) => <Button {...props}>打开设置</Button>}
      />
      <LayerDialog.Content>
        <LayerDialog.Title>配置自定义主机名</LayerDialog.Title>
        <LayerDialog.Description>
          将该主机名的请求路由到你的 Worker。
        </LayerDialog.Description>
        <LayerDialog.Body>
          <div className="flex flex-col gap-5">
            <Input
              label="主机名"
              onChange={(event) => setHostname(event.target.value)}
              value={hostname}
            />
            <Input
              label="显示名称"
              onChange={(event) => setName(event.target.value)}
              value={name}
            />
          </div>
        </LayerDialog.Body>
        <LayerDialog.Actions>
          <LayerDialog.Action
            disabled={!hostname || !name}
            onClick={() => undefined}
          >
            保存主机名
          </LayerDialog.Action>
        </LayerDialog.Actions>
      </LayerDialog.Content>
    </LayerDialog.Root>
  );
}

安装

统一导出

import { LayerDialog } from "@lark-ui/core";

按需导出

import { LayerDialog } from "@lark-ui/core/components/layer-dialog";

用法

import { Button, LayerDialog } from "@lark-ui/core";

export default function Example() {
  return (
    <LayerDialog.Root>
      <LayerDialog.Trigger
        render={(props) => <Button {...props}>Open settings</Button>}
      />
      <LayerDialog.Content>
        <LayerDialog.Title>Configure custom hostname</LayerDialog.Title>
        <LayerDialog.Description>
          Route requests for this hostname to your Worker.
        </LayerDialog.Description>
        <LayerDialog.Body>{/* form fields */}</LayerDialog.Body>
        <LayerDialog.Actions>
          <LayerDialog.Action onClick={save}>
            Save hostname
          </LayerDialog.Action>
        </LayerDialog.Actions>
      </LayerDialog.Content>
    </LayerDialog.Root>
  );
}

组合规则

LayerDialog.Content 恰好接受一个 Title、一个 Body,以及可选的 Description 和可选的 Actions。它会自动选择与关闭方式对应的界面元素:

  • 不使用 Actions:在标题栏显示 X 按钮,不显示底部栏。
  • 使用 Actions:去掉 X 按钮,显示 Close 或 Cancel 以及一个主操作。在移动端,它们会显示在滚动内容区下方并以细分隔线隔开;在桌面端,它们保留在固定底部栏中。

使用方不能混用这两种布局,也不能添加无关的底部控件。Actions 接受一个 Action,该操作还可以选择性地展开相关菜单项。只读内容请使用 X 关闭按钮,用户必须提交变更时使用 Actions,破坏性操作或关键确认则使用 LayerDialog.Alert。

Description 渲染在固定标题栏内标题的正下方,并成为对话框的无障碍描述。如果没有描述,则由内容区文案来描述该对话框。

信息型对话框

Title 和 Description 始终位于带边框的内容表面内。标题栏在内容滚动时保持可见,并承担唯一的自动 X 关闭操作。当内容滚动超过顶部后,描述会折叠到标题下方,为内容腾出更多空间;回到顶部时又会重新展开。内容边缘的遮罩用于提示内容溢出。

import { Button, LayerDialog, Text } from "@lark-ui/core";

function LongContent() {
  return (
    <div className="flex flex-col gap-5">
      <div className="rounded-lg border border-lark-line p-4 text-lark-subtle">
        导航与命令快捷键
      </div>
      <Text variant="secondary">
        标题栏始终可见,内容滚动后会出现分隔线,滚动遮罩提示下方还有更多内容。
      </Text>
      <div className="h-96" />
    </div>
  );
}

export function LayerDialogInformationalDemo() {
  return (
    <LayerDialog.Root>
      <LayerDialog.Trigger
        render={(props) => <Button {...props}>打开键盘快捷键</Button>}
      />
      <LayerDialog.Content>
        <LayerDialog.Title>键盘快捷键</LayerDialog.Title>
        <LayerDialog.Description>
          查看可用快捷键,无需修改任何设置。
        </LayerDialog.Description>
        <LayerDialog.Body>
          <LongContent />
        </LayerDialog.Body>
      </LayerDialog.Content>
    </LayerDialog.Root>
  );
}

标准操作

添加 Actions 会选定恰好一个 Lark 主操作,并配备一个自动的 Close 按钮。在移动端,操作按钮会显示在滚动内容区下方并以细分隔线隔开;在桌面端,它们位于固定底部栏中。使用方不能添加无关的底部控件或额外的行动号召按钮。主操作接受 variant="primary"(默认)或 variant="destructive"。

import { useState } from "react";
import { Button, Input, LayerDialog } from "@lark-ui/core";

export function LayerDialogActionDemo() {
  const [name, setName] = useState("生产环境 API");
  const [hostname, setHostname] = useState("api.example.com");

  return (
    <LayerDialog.Root>
      <LayerDialog.Trigger
        render={(props) => <Button {...props}>打开设置</Button>}
      />
      <LayerDialog.Content>
        <LayerDialog.Title>配置自定义主机名</LayerDialog.Title>
        <LayerDialog.Description>
          将该主机名的请求路由到你的 Worker。
        </LayerDialog.Description>
        <LayerDialog.Body>
          <div className="flex flex-col gap-5">
            <Input
              label="主机名"
              onChange={(event) => setHostname(event.target.value)}
              value={hostname}
            />
            <Input
              label="显示名称"
              onChange={(event) => setName(event.target.value)}
              value={name}
            />
          </div>
        </LayerDialog.Body>
        <LayerDialog.Actions>
          <LayerDialog.Action
            disabled={!hostname || !name}
            onClick={() => undefined}
          >
            保存主机名
          </LayerDialog.Action>
        </LayerDialog.Actions>
      </LayerDialog.Content>
    </LayerDialog.Root>
  );
}

原生表单

Body 和 Actions 是两个独立的插槽,因此原生的 <form> 应放在 Body 内部,而提交按钮应放在 Actions 中。请为表单设置一个 ID,再通过 HTML 的 form 属性关联主操作。这样既保留了内置校验能力,也支持不依赖 React state 的非受控表单字段。

import { useId } from "react";
import { Button, Input, LayerDialog } from "@lark-ui/core";

export function LayerDialogFormDemo() {
  const formId = useId();

  return (
    <LayerDialog.Root>
      <LayerDialog.Trigger
        render={(props) => <Button {...props}>创建部署</Button>}
      />
      <LayerDialog.Content>
        <LayerDialog.Title>创建部署</LayerDialog.Title>
        <LayerDialog.Description>
          表单原生的校验与提交状态由浏览器管理。
        </LayerDialog.Description>
        <LayerDialog.Body>
          <form
            id={formId}
            className="flex flex-col gap-5"
            onSubmit={(event) => event.preventDefault()}
          >
            <Input
              label="服务名称"
              name="serviceName"
              placeholder="production-api"
              required
            />
            <Input
              label="兼容日期"
              name="compatibilityDate"
              required
              type="date"
            />
          </form>
        </LayerDialog.Body>
        <LayerDialog.Actions dismissLabel="取消">
          <LayerDialog.Action form={formId} type="submit">
            创建部署
          </LayerDialog.Action>
        </LayerDialog.Actions>
      </LayerDialog.Content>
    </LayerDialog.Root>
  );
}

搜索与选择器

控件可以作为 Body 的第一个元素。这里一个没有标签的搜索输入框紧挨在标题栏下方,因此需要为它提供 aria-label。内容区会在其内容上方留出空间,避免输入框的边框和焦点环被滚动区域裁切。

import { useState } from "react";
import { Button, Input, LayerDialog, Text } from "@lark-ui/core";

/** 正文中的第一个控件是无标签的搜索输入框。 */
export function LayerDialogSearchDemo() {
  const [query, setQuery] = useState("");
  const matches = WORKERS.filter((worker) =>
    worker.toLowerCase().includes(query.trim().toLowerCase()),
  );

  return (
    <LayerDialog.Root>
      <LayerDialog.Trigger
        render={(props) => <Button {...props}>选择 Worker</Button>}
      />
      <LayerDialog.Content>
        <LayerDialog.Title>选择 Worker</LayerDialog.Title>
        <LayerDialog.Description>
          选择处理该路由请求的 Worker。
        </LayerDialog.Description>
        <LayerDialog.Body>
          <div className="flex flex-col gap-3">
            <Input
              aria-label="搜索 Worker"
              onChange={(event) => setQuery(event.target.value)}
              placeholder="搜索 Worker"
              type="search"
              value={query}
            />
            {matches.length > 0 ? (
              <ul className="flex flex-col divide-y divide-lark-hairline rounded-lg border border-lark-hairline">
                {matches.map((worker) => (
                  <li key={worker} className="px-3 py-2 text-lark-default">
                    {worker}
                  </li>
                ))}
              </ul>
            ) : (
              <Text variant="secondary">没有匹配“{query}”的 Worker。</Text>
            )}
          </div>
        </LayerDialog.Body>
      </LayerDialog.Content>
    </LayerDialog.Root>
  );
}

划分内容区域

在 LayerDialog.Body 的各个小节之间使用 LayerDialog.Separator。分隔线会贯穿内容区的横向内边距,横跨整个内容表面。

import { Button, LayerDialog, Text } from "@lark-ui/core";

export function LayerDialogSeparatorDemo() {
  return (
    <LayerDialog.Root>
      <LayerDialog.Trigger
        render={(props) => <Button {...props}>检查部署</Button>}
      />
      <LayerDialog.Content>
        <LayerDialog.Title>部署摘要</LayerDialog.Title>
        <LayerDialog.Description>部署前请检查配置。</LayerDialog.Description>
        <LayerDialog.Body>
          <Text variant="secondary">production-api 将部署到全球网络。</Text>
          <LayerDialog.Separator />
          <Text variant="secondary">本次部署使用兼容日期 2026-10-26。</Text>
        </LayerDialog.Body>
        <LayerDialog.Actions>
          <LayerDialog.Action>部署</LayerDialog.Action>
        </LayerDialog.Actions>
      </LayerDialog.Content>
    </LayerDialog.Root>
  );
}

多个主操作

可以通过 LayerDialog.Action 的 menu 属性传入相关操作。它会在操作旁渲染一个统一的分裂按钮箭头。自动关闭按钮应保持独立。例如,Save and deploy 可以展开 Save as draft,而不会让底部栏变成三个并列的操作。

import { Button, DropdownMenu, LayerDialog, Text } from "@lark-ui/core";

export function LayerDialogSplitActionDemo() {
  return (
    <div className="flex flex-wrap justify-center gap-3">
      <LayerDialog.Root>
        <LayerDialog.Trigger
          render={(props) => <Button {...props}>编辑部署</Button>}
        />
        <LayerDialog.Content>
          <LayerDialog.Title>保存部署</LayerDialog.Title>
          <LayerDialog.Description>
            立即部署这些更改,或把它们保存为草稿。
          </LayerDialog.Description>
          <LayerDialog.Body>
            <Text variant="secondary">
              另一种保存结果与主操作相关,因此它位于分裂按钮的菜单中,而不是作为无关的底部按钮。
            </Text>
          </LayerDialog.Body>
          <LayerDialog.Actions>
            <LayerDialog.Action
              menu={[
                <DropdownMenu.Item key="draft">保存为草稿</DropdownMenu.Item>,
              ]}
              menuLabel="保存选项"
            >
              保存并部署
            </LayerDialog.Action>
          </LayerDialog.Actions>
        </LayerDialog.Content>
      </LayerDialog.Root>

      <LayerDialog.Alert>
        <LayerDialog.Trigger
          render={(props) => (
            <Button {...props} variant="secondary">
              删除部署
            </Button>
          )}
        />
        <LayerDialog.Content>
          <LayerDialog.Title>删除部署</LayerDialog.Title>
          <LayerDialog.Description>
            这将永久删除该部署,且无法撤销。
          </LayerDialog.Description>
          <LayerDialog.Body>
            <Text variant="secondary">
              危险操作的分裂按钮让操作的各个部分(包括菜单触发器)保持视觉一致。
            </Text>
          </LayerDialog.Body>
          <LayerDialog.Actions>
            <LayerDialog.Action
              menu={[
                <DropdownMenu.Item key="delete-with-tokens">
                  删除并吊销令牌
                </DropdownMenu.Item>,
              ]}
              menuLabel="删除选项"
              variant="destructive"
            >
              删除部署
            </LayerDialog.Action>
          </LayerDialog.Actions>
        </LayerDialog.Content>
      </LayerDialog.Alert>
    </div>
  );
}

取消按钮文案

当存在 LayerDialog.Actions 时,LayerDialog 会渲染一个关闭按钮。在移动端它采用次要按钮样式,在桌面端底部栏中采用幽灵按钮样式。其默认标签是 “Close”,LayerDialog.Alert 则为 “Cancel”。

仅当该对话框需要更具体的文案时才使用 dismissLabel,例如 “Keep editing” 或 “Discard changes”。标签不会改变按钮的行为:它始终用于关闭对话框。

import { useState } from "react";
import { Button, Input, LayerDialog } from "@lark-ui/core";

export function LayerDialogCancelDemo() {
  const [email, setEmail] = useState("alex@example.com");
  const [name, setName] = useState("亚历克斯·摩根");

  return (
    <LayerDialog.Root>
      <LayerDialog.Trigger
        render={(props) => <Button {...props}>编辑资料</Button>}
      />
      <LayerDialog.Content>
        <LayerDialog.Title>编辑资料</LayerDialog.Title>
        <LayerDialog.Description>
          更新向团队成员展示的个人资料。确认后更改才会保存。
        </LayerDialog.Description>
        <LayerDialog.Body>
          <div className="flex flex-col gap-5">
            <Input
              label="显示名称"
              onChange={(event) => setName(event.target.value)}
              value={name}
            />
            <Input
              label="邮箱地址"
              onChange={(event) => setEmail(event.target.value)}
              type="email"
              value={email}
            />
          </div>
        </LayerDialog.Body>
        <LayerDialog.Actions dismissLabel="取消">
          <LayerDialog.Action
            disabled={!email || !name}
            onClick={() => undefined}
          >
            保存更改
          </LayerDialog.Action>
        </LayerDialog.Actions>
      </LayerDialog.Content>
    </LayerDialog.Root>
  );
}

确认与破坏性操作

LayerDialog.Alert 遵循 Base UI 的警告对话框模式:它提供 role="alertdialog"、一个自动的 Cancel 按钮、不显示 X 按钮、始终以模态呈现,并阻止点击遮罩和滑动关闭。按 Escape 键仍然可以取消,与 ARIA 警告对话框模式一致。 当确认操作不可撤销时,请为主操作传入 variant="destructive"。对于确认非破坏性但关键步骤的警告,保持默认的主操作样式即可。

import { useState } from "react";
import { Button, Input, LayerDialog, Text } from "@lark-ui/core";

export function LayerDialogAlertDemo() {
  const workerName = "example-worker";
  const [confirmation, setConfirmation] = useState("");

  return (
    <LayerDialog.Alert>
      <LayerDialog.Trigger
        render={(props) => (
          <Button variant="secondary-destructive" {...props}>
            删除 Worker
          </Button>
        )}
      />
      <LayerDialog.Content>
        <LayerDialog.Title>删除 Worker</LayerDialog.Title>
        <LayerDialog.Description>
          删除{" "}
          <strong className="font-medium text-lark-default">
            {workerName}
          </strong>{" "}
          是永久性操作。
        </LayerDialog.Description>
        <LayerDialog.Body>
          <div className="flex flex-col gap-5">
            <Text variant="secondary">
              这将删除该 Worker、部署和配置。如果该 Worker 使用了
              Queues,会先移除这些连接。Queues、D1
              数据库和消息仍保留在你的账户中。
            </Text>
            <Input
              label={
                <>
                  输入{" "}
                  <strong className="font-medium text-lark-default">
                    {workerName}
                  </strong>{" "}
                  以确认
                </>
              }
              onChange={(event) => setConfirmation(event.target.value)}
              placeholder={workerName}
              value={confirmation}
            />
          </div>
        </LayerDialog.Body>
        <LayerDialog.Actions>
          <LayerDialog.Action
            disabled={confirmation !== workerName}
            onClick={() => undefined}
            variant="destructive"
          >
            删除 Worker
          </LayerDialog.Action>
        </LayerDialog.Actions>
      </LayerDialog.Content>
    </LayerDialog.Alert>
  );
}

嵌套对话框

当用户必须确认某个相关操作时,可在父对话框的内容区中嵌套一个 LayerDialog.Root 或 LayerDialog.Alert。每个对话框都独立地渲染到 portal;Base UI 会让嵌套的抽屉保持在父级之上,并在其关闭时把焦点交还给父级触发器。嵌套抽屉会在父级之上渲染自己的遮罩,因此父级保持完整,但明显处于非激活状态。无需覆盖 z-index。

import { Button, LayerDialog, Text } from "@lark-ui/core";

export function LayerDialogNestedDemo() {
  return (
    <LayerDialog.Root>
      <LayerDialog.Trigger
        render={(props) => <Button {...props}>编辑部署</Button>}
      />
      <LayerDialog.Content>
        <LayerDialog.Title>编辑部署</LayerDialog.Title>
        <LayerDialog.Description>
          保存前请检查部署设置。
        </LayerDialog.Description>
        <LayerDialog.Body>
          <div className="flex flex-col gap-4">
            <Text variant="secondary">
              在此正文中打开第二个对话框时,第一个对话框会保留在其下方,并在关闭时恢复焦点。
            </Text>
            <LayerDialog.Alert>
              <LayerDialog.Trigger
                render={(props) => (
                  <Button variant="secondary-destructive" {...props}>
                    放弃更改
                  </Button>
                )}
              />
              <LayerDialog.Content size="sm">
                <LayerDialog.Title>放弃未保存的更改?</LayerDialog.Title>
                <LayerDialog.Description>
                  你对部署的修改将永久丢失。
                </LayerDialog.Description>
                <LayerDialog.Body>
                  <Text variant="secondary">
                    这个嵌套告警会独立渲染到 portal,关闭后应返回编辑对话框。
                  </Text>
                </LayerDialog.Body>
                <LayerDialog.Actions>
                  <LayerDialog.Action variant="destructive">
                    放弃更改
                  </LayerDialog.Action>
                </LayerDialog.Actions>
              </LayerDialog.Content>
            </LayerDialog.Alert>
          </div>
        </LayerDialog.Body>
        <LayerDialog.Actions>
          <LayerDialog.Action>保存更改</LayerDialog.Action>
        </LayerDialog.Actions>
      </LayerDialog.Content>
    </LayerDialog.Root>
  );
}

进行中的任务

在异步任务进行期间,dismissDisabled 会统一阻止所有由用户发起的关闭操作。通过 actionsRef.current.close() 或受控的 open 属性进行的程序化关闭仍然有效,因此操作成功后仍可关闭对话框。唯一的主操作拥有独立的加载中或禁用状态。

import { useState } from "react";
import { Button, LayerDialog, Text } from "@lark-ui/core";

export function LayerDialogPendingDemo() {
  const [pending, setPending] = useState(false);
  return (
    <LayerDialog.Root dismissDisabled={pending}>
      <LayerDialog.Trigger
        render={(props) => <Button {...props}>保存设置</Button>}
      />
      <LayerDialog.Content>
        <LayerDialog.Title>保存设置</LayerDialog.Title>
        <LayerDialog.Description>
          保存过程中,关闭按钮、Esc 键、背景点击和移动端滑动关闭会被一同阻止。
        </LayerDialog.Description>
        <LayerDialog.Body>
          <Text variant="secondary">
            通过代码关闭仍然可用,因此保存成功后可以通过 `actionsRef` 或受控的
            `open` 属性关闭对话框。
          </Text>
        </LayerDialog.Body>
        <LayerDialog.Actions>
          <LayerDialog.Action
            loading={pending}
            onClick={() => {
              setPending(true);
              window.setTimeout(() => setPending(false), 1500);
            }}
          >
            保存更改
          </LayerDialog.Action>
        </LayerDialog.Actions>
      </LayerDialog.Content>
    </LayerDialog.Root>
  );
}

清理

请把清理逻辑放在 onOpenChange 中,而不是放在某个关闭按钮的点击处理函数中,这样 X、Close、Escape、遮罩点击和滑动关闭都能触发它。

import { useState } from "react";
import { Button, LayerDialog, Text } from "@lark-ui/core";

export function LayerDialogCleanupDemo() {
  const [open, setOpen] = useState(false);
  const [cleanupCount, setCleanupCount] = useState(0);
  return (
    <LayerDialog.Root
      open={open}
      onOpenChange={(nextOpen) => {
        if (!nextOpen) setCleanupCount((count) => count + 1);
        setOpen(nextOpen);
      }}
    >
      <LayerDialog.Trigger
        render={(props) => <Button {...props}>打开草稿</Button>}
      />
      <LayerDialog.Content>
        <LayerDialog.Title>草稿设置</LayerDialog.Title>
        <LayerDialog.Body>
          <Text variant="secondary">清理已执行 {cleanupCount} 次。</Text>
        </LayerDialog.Body>
      </LayerDialog.Content>
    </LayerDialog.Root>
  );
}

本地化

该对话框会渲染两条自身的文案。请使用 LarkLocaleProvider 配置它们在应用范围内的默认文案:

  • layerDialog.close 用于自动的 X 按钮和默认的底部关闭按钮。默认值:“Close”。
  • layerDialog.cancel 用于 Alert 内部默认的底部关闭按钮。
<LarkLocaleProvider
  translations={{
    layerDialog: { close: "Schließen", cancel: "Abbrechen" },
  }}
>
  <App />
</LarkLocaleProvider>

当某个对话框需要自定义文案时,请在 Content 上使用 closeLabel,或在 Actions 上使用 dismissLabel;显式传入的属性会覆盖 Provider 的默认值。

桌面端宽度

桌面端对话框默认使用 size="base"(576px)。当内容需要其他宽度时,请使用 sm(448px)、lg(672px)或 xl(768px)。移动端对话框在所有尺寸下都保持全宽。

import { useState } from "react";
import { Button, Input, LayerDialog, LarkLayerDialogSize } from "@lark-ui/core";

export function LayerDialogSizeDemo() {
  const [open, setOpen] = useState(false);
  const [size, setSize] = useState<LarkLayerDialogSize>("base");

  const openAtSize = (nextSize: LarkLayerDialogSize) => {
    setSize(nextSize);
    setOpen(true);
  };

  return (
    <>
      <div className="flex flex-wrap gap-2">
        <Button onClick={() => openAtSize("sm")}>小</Button>
        <Button onClick={() => openAtSize("base")}>默认</Button>
        <Button onClick={() => openAtSize("lg")}>大</Button>
        <Button onClick={() => openAtSize("xl")}>特大</Button>
      </div>
      <LayerDialog.Root open={open} onOpenChange={setOpen}>
        <LayerDialog.Content size={size}>
          <LayerDialog.Title>检查部署配置</LayerDialog.Title>
          <LayerDialog.Description>
            创建本次部署前,请确认服务信息和路由配置。
          </LayerDialog.Description>
          <LayerDialog.Body>
            <div className="grid gap-5 sm:grid-cols-2">
              <Input label="服务名称" defaultValue="production-api" />
              <Input label="环境" defaultValue="生产环境" />
              <Input label="主机名" defaultValue="api.example.com" />
              <Input label="兼容日期" defaultValue="2026-09-09" />
            </div>
          </LayerDialog.Body>
          <LayerDialog.Actions>
            <LayerDialog.Action>创建部署</LayerDialog.Action>
          </LayerDialog.Actions>
        </LayerDialog.Content>
      </LayerDialog.Root>
    </>
  );
}

桌面端位置

桌面端对话框默认居中。仅当内容更适合顶部对齐时,才使用 verticalAlign="top" 覆盖;移动端仍然是底部抽屉。

import { Button, LayerDialog, Text } from "@lark-ui/core";

export function LayerDialogTopAlignDemo() {
  return (
    <LayerDialog.Root>
      <LayerDialog.Trigger
        render={(props) => <Button {...props}>打开顶部对齐的对话框</Button>}
      />
      <LayerDialog.Content verticalAlign="top">
        <LayerDialog.Title>顶部对齐的对话框</LayerDialog.Title>
        <LayerDialog.Body>
          <Text variant="secondary">移动端对话框仍为底部抽屉。</Text>
        </LayerDialog.Body>
      </LayerDialog.Content>
    </LayerDialog.Root>
  );
}

最大高度

高度由内容决定,并受视口限制。遵循 Base UI 的内部滚动模式,视口会为每种位置预留垂直留白,弹层填充该空间,因此顶部对齐的对话框永远不会超出底边。超过上限后,只有内容区滚动,标题栏和操作按钮保持固定。移动端抽屉的上限则为视口的 85%。组件没有高度相关属性:使用方只能通过渲染的内容来控制高度。

import { useState } from "react";
import { Button, LayerDialog, LarkLayerDialogVerticalAlign } from "@lark-ui/core";

export function LayerDialogMaxHeightDemo() {
  const [verticalAlign, setVerticalAlign] =
    useState<LarkLayerDialogVerticalAlign>("center");
  const [open, setOpen] = useState(false);

  const openAt = (align: LarkLayerDialogVerticalAlign) => {
    setVerticalAlign(align);
    setOpen(true);
  };

  return (
    <>
      <div className="flex flex-wrap gap-2">
        <Button onClick={() => openAt("center")}>居中,长内容</Button>
        <Button onClick={() => openAt("top")}>顶部对齐,长内容</Button>
      </div>
      <LayerDialog.Root open={open} onOpenChange={setOpen}>
        <LayerDialog.Content verticalAlign={verticalAlign}>
          <LayerDialog.Title>审计日志</LayerDialog.Title>
          <LayerDialog.Description>
            对话框会随内容增高,直到达到视口上限,此后仅正文部分滚动。
          </LayerDialog.Description>
          <LayerDialog.Body>
            <ol className="flex flex-col gap-2">
              {Array.from({ length: 40 }, (_, index) => (
                <li
                  key={index}
                  className="rounded-lg border border-lark-line px-3 py-2 text-lark-subtle"
                >
                  第 {index + 1} 条
                </li>
              ))}
            </ol>
          </LayerDialog.Body>
          <LayerDialog.Actions>
            <LayerDialog.Action>导出日志</LayerDialog.Action>
          </LayerDialog.Actions>
        </LayerDialog.Content>
      </LayerDialog.Root>
    </>
  );
}

API 参考

LayerDialog.Root

控制打开状态。接受 Base UI Drawer 根组件的全部属性,包括 open、defaultOpen、onOpenChange、modal 和 actionsRef。自身不渲染任何 HTML 元素。

属性类型默认值说明
dismissDisabledbooleanfalse

在任务进行期间阻止通过 X、Close、Escape、遮罩点击和滑动关闭对话框。程序化关闭始终不会被阻止。

LayerDialog.Alert

属性与 LayerDialog.Root 相同。强制使用 modal,阻止指针点击关闭,渲染为 role="alertdialog",并且必须搭配 LayerDialog.Actions。

LayerDialog.Trigger

点击时打开对话框的按钮。

属性类型默认值

该组件没有专有属性,支持标准 HTML 属性。

LayerDialog.Content

将遮罩和弹层渲染到 portal,并校验组合结构是否合法。

属性类型默认值说明
children*ReactNode--
containerPortalContainer-传送层的容器元素。会覆盖 `LarkPortalProvider` 上下文。
sizeLarkLayerDialogSize-仅桌面端生效的宽度。移动端对话框始终为全宽。
verticalAlignLarkLayerDialogVerticalAlign-仅桌面端生效的定位方式。移动端对话框始终从底部弹出。
closeLabelstring-自动关闭 X 按钮的无障碍名称。会覆盖 LarkLocaleProvider 中的 `close` 译文。

LayerDialog.Title

为对话框提供无障碍标签的标题。

属性类型默认值
children*ReactNode-

LayerDialog.Description

标题下方的可选补充文案,会作为对话框的无障碍描述。

属性类型默认值
children*ReactNode-

LayerDialog.Body

可滚动的内容区域。

属性类型默认值
children*ReactNode-

LayerDialog.Separator

内容区各小节之间的整幅分隔线。

属性类型默认值

该组件没有专有属性,支持标准 HTML 属性。

LayerDialog.Actions

底部栏,包含一个自动关闭按钮和恰好一个 LayerDialog.Action。

属性类型默认值说明
children*ReactNode-恰好一个操作。向 LayerDialog.Action 传入 `menu` 可承载“Save as draft”之类的相关操作。
dismissLabelstring-自动关闭按钮的文字。只有当流程确实存在取消结果时才写“Cancel”。非英语产品请翻译该文案。

LayerDialog.Action

主操作。渲染为 Lark Button,接受 onClick、disabled、type 等标准按钮属性。通过 menu 传入菜单项,即可渲染配套的分裂按钮菜单。

属性类型默认值

该组件没有专有属性,支持标准 HTML 属性。