Empty

Empty 用于缺少内容、搜索无结果和首次使用等空状态。命名插槽负责布局与视觉层级, 应用决定何时显示空状态,并管理子组件的状态和操作。

该组件无状态,完整位于 GPUI Component 层,使用该层的主题和原生控件。

#导入

use gpui_kit::{ParentElement as _, Styled as _, rems};
use gpui_kit::assets::IconName;
use gpui_kit::component::{
    ActiveTheme as _, Icon, Sizable as _,
    avatar::{Avatar, AvatarGroup},
    button::{Button, ButtonVariants as _},
    empty::{
        Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia,
        EmptyMediaVariant, EmptyTitle,
    },
    input::{Input, InputState},
    link::Link,
};

通过 gpui_kit::component::empty 导入该组件。GPUI 自带的 gpui_kit::Empty 是另一个不渲染内容的元素。

#基本用法

Empty::new()
    .header(
        EmptyHeader::new()
            .media(
                EmptyMedia::new()
                    .with_variant(EmptyMediaVariant::Icon)
                    .child(Icon::new(IconName::Folder)),
            )
            .title(EmptyTitle::new().child("No projects yet"))
            .description(
                EmptyDescription::new()
                    .child("Create your first project to get started."),
            ),
    )
    .content(
        EmptyContent::new()
            .flex_row()
            .flex_wrap()
            .justify_center()
            .gap_2()
            .child(Button::new("create-project").label("Create project…"))
            .child(
                Button::new("import-project")
                    .outline()
                    .label("Import project…"),
            ),
    )
    .child(
        Link::new("empty-help")
            .href("https://gpui-kit.com/docs/getting-started")
            .text_sm()
            .child("Learn more"),
    )

使用 Button 的常规回调连接应用操作。根部额外添加的子元素显示在 EmptyContent 之后,因此帮助链接可以独立于主要内容组。

#组成

部分组合方式职责
Empty.header(EmptyHeader).content(EmptyContent).child(...)整体对齐与间距
EmptyHeader.media(EmptyMedia).title(EmptyTitle).description(EmptyDescription)媒体与说明内容
EmptyMedia.with_variant(...).child(...)图标、图片、头像或任意媒体
EmptyTitle.child(...)标题文字或自定义内容
EmptyDescription.child(...)可换行的文字或富内容说明
EmptyContent.child(...)操作、输入框或其他控件

所有部分均提供 new()Default,并实现 Styled。 除 EmptyHeader 外,其他部分都实现了 ParentElement

命名插槽均为可选项,重复设置时替换原值:调用两次 .header(...) 会保留第二个 Header。 渲染顺序固定为 Header、Content,以及 Header 内的 Media、Title、Description, 与这些 setter 的调用顺序无关。根部直接添加的子元素按照自身插入顺序显示在两个 命名插槽之后,不会自动放进 Content。替换某个插槽不会影响其他插槽或根部额外内容。

#边框

Empty 默认背景透明,没有可见边框。通过 Styled 开启边框后,默认使用虚线样式。

Empty::new()
    .border_1()
    .header(
        EmptyHeader::new()
            .title(EmptyTitle::new().child("Cloud storage is empty"))
            .description(
                EmptyDescription::new()
                    .child("Upload files to access them anywhere."),
            ),
    )

通过 .border_color(...) 调整边框的语义颜色。

#背景

直接应用语义背景,无需新增组件变体:

Empty::new()
    .bg(cx.theme().muted.opacity(0.3))
    .header(
        EmptyHeader::new()
            .title(EmptyTitle::new().child("No notifications"))
            .description(
                EmptyDescription::new()
                    .child("New notifications will appear here."),
            ),
    )

#头像

默认媒体变体不添加外框、背景或固定尺寸。现有 Avatar 保留自己的图片、回退内容、 尺寸和外观。

EmptyHeader::new()
    .media(
        EmptyMedia::new().child(
            Avatar::new()
                .name("Alex Morgan")
                .src("https://avatars.githubusercontent.com/u/5518?v=4"),
        ),
    )
    .title(EmptyTitle::new().child("Alex is offline"))
    .description(
        EmptyDescription::new()
            .child("Leave a message for Alex to read when they're back."),
    )

#头像组

多个头像使用同一个媒体插槽。头像的重叠和尺寸由 AvatarGroup 管理,Empty 不检查 或修改其子元素。

EmptyHeader::new()
    .media(
        EmptyMedia::new().child(
            AvatarGroup::new()
                .child(Avatar::new().name("Alex Morgan"))
                .child(Avatar::new().name("Taylor Lee"))
                .child(Avatar::new().name("Sam Chen")),
        ),
    )
    .title(EmptyTitle::new().child("No team members"))
    .description(
        EmptyDescription::new()
            .child("Invite your team to collaborate on this project."),
    )

#输入框与自定义内容

在所属视图中持有 Entity<InputState>,然后将现有 Input 放入 EmptyContent

EmptyContent::new()
    .child(
        Input::new(&self.search)
            .prefix(Icon::new(IconName::Search).size_4())
            .cleanable(true),
    )
    .child(
        EmptyDescription::new()
            .child("Search by name or try a different keyword."),
    )

应用处理输入事件,并在结果列表与 Empty 之间切换。每个同时渲染的输入框各自持有 状态实体和焦点。Empty 不管理输入状态、校验、提交或加载策略。

#受限布局

同时调整根元素与各个插槽,即可构建紧凑、起始侧对齐的空状态:

Empty::new()
    .max_w(rems(20.))
    .p_4()
    .items_start()
    .text_left()
    .header(
        EmptyHeader::new()
            .items_start()
            .title(EmptyTitle::new().child("No shared files"))
            .description(
                EmptyDescription::new()
                    .child("Add files so your team can review and edit them together."),
            ),
    )
    .content(
        EmptyContent::new()
            .items_start()
            .child(Button::new("add-files").outline().label("Add files…")),
    )

根元素填满可用宽度,并可在 flex 布局中增长。Header 和 Content 使用可用宽度, 上限为 24 rem。文字自然换行;Empty 不裁切子元素,也不创建滚动区域。视口和所需的 滚动由父容器提供。自定义媒体应适应容器宽度,操作行可使用 .flex_wrap() 处理窄空间。

#默认样式

部分默认值
根元素居中列布局,p_6()gap_4()、主题 radius_tokens().xl
Headergap_2(),子项居中,最大宽度 24 rem
Media按内容确定尺寸的居中列布局,mb_2(),不收缩
Icon 媒体size_8()、muted 背景、前景色、主题 radius_tokens().lg
Titletext_sm(),中等字重
Descriptiontext_sm(),1.625 行高,muted 前景色
Content居中列布局,gap_2p5()text_sm(),最大宽度 24 rem

实例样式覆盖默认值和媒体变体样式。Icon 媒体提供一 rem 字号,未显式设置尺寸的 GPUI Component Icon 会继承该尺寸;显式设置的图标尺寸仍然有效。任意 SVG 或图片 子元素保留自己的尺寸。排版跟随应用字体和 rem 比例,使用 GPUI 的原生换行与字距; 组件不另行实现 CSS 的 text-balancetracking-tight

Empty 不创建焦点目标,也不会自动作为警告或实时状态播报。内部 Button 和 Input 保留正常的焦点与键盘行为。应用命令使用 Button,外部资源使用 Link。