add empty state

This commit is contained in:
2026-11-01 16:12:06 +07:00
parent 61ff8cfd0a
commit b2ce60f0e6
5 changed files with 153 additions and 1 deletions
+2
View File
@@ -36,6 +36,7 @@ full = [
"menu",
"select",
"dialog",
"empty_state",
"alert",
"toast",
]
@@ -58,5 +59,6 @@ tooltip = []
menu = ["scroll_area"]
select = ["menu"]
dialog = []
empty_state = []
alert = []
toast = []
+109
View File
@@ -0,0 +1,109 @@
use gpui::prelude::FluentBuilder as _;
use gpui::{
App, IntoElement, ParentElement as _, RenderOnce, SharedString, StyleRefinement, Styled as _,
Window, div, relative,
};
use gpui_native_icons::{IconRole, icon};
use gpui_native_theme::{ActiveTheme as _, Size};
use crate::common::{StyledExt, v_flex};
/// The centered "nothing here yet" panel for lists, tables, and searches.
#[derive(IntoElement)]
pub struct EmptyState {
icon: Option<IconRole>,
headline: SharedString,
hint: Option<SharedString>,
action: Option<gpui::AnyElement>,
style: StyleRefinement,
}
impl EmptyState {
/// Creates an empty state whose headline is the primary statement.
pub fn new<L>(headline: L) -> Self
where
L: Into<SharedString>,
{
Self {
icon: None,
headline: headline.into(),
hint: None,
action: None,
style: StyleRefinement::default(),
}
}
/// Sets a large muted icon above the headline.
pub fn icon(mut self, role: IconRole) -> Self {
self.icon = Some(role);
self
}
/// Sets a secondary line under the headline.
pub fn hint<L>(mut self, hint: L) -> Self
where
L: Into<SharedString>,
{
self.hint = Some(hint.into());
self
}
/// Sets an action element — usually a button — under the hint.
pub fn action<E>(mut self, action: E) -> Self
where
E: IntoElement,
{
self.action = Some(action.into_any_element());
self
}
}
impl gpui::Styled for EmptyState {
fn style(&mut self) -> &mut StyleRefinement {
&mut self.style
}
}
impl RenderOnce for EmptyState {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let theme = cx.theme();
let metrics = theme.sizes.resolve(Size::Medium);
let muted_foreground = theme.colors.muted_foreground;
let foreground = theme.colors.foreground;
let illustration = metrics.icon_size * 2.0;
v_flex()
.w_full()
.items_center()
.justify_center()
.text_center()
.p_4()
.when_some(self.icon, |state, role| {
state.child(
div()
.mb_2()
.child(icon(role).size(illustration).color(muted_foreground)),
)
})
.child(
div()
.text_size(metrics.text_size)
.font_weight(gpui::FontWeight::MEDIUM)
.text_color(foreground)
.line_height(relative(1.3))
.child(self.headline),
)
.when_some(self.hint, |state, hint| {
state.child(
div()
.text_size(metrics.caption_size)
.text_color(muted_foreground)
.child(hint),
)
})
.when_some(self.action, |state, action| {
state.child(div().mt_3().child(action))
})
.refine_style(&self.style)
}
}
+3
View File
@@ -48,6 +48,9 @@ pub mod input;
#[cfg(feature = "dialog")]
pub mod dialog;
#[cfg(feature = "empty_state")]
pub mod empty_state;
#[cfg(feature = "menu")]
pub mod menu;