dialog: Rename Modal to Dialog. (#1538)

## Break Change

- Renamed `Modal` to `Dialog`.

```diff
- window.open_modal(...)
+ window.open_dialog(...)

- window.close_modal(...);
+ window.close_dialog(...);
```

- Renamed `show_close` method to `close_button` in Dialog.
```diff
- .show_close(false)
+ .close_button(true)
```
This commit is contained in:
Jason Lee 2025-11-07 17:25:55 +08:00 committed by GitHub
parent c78811c4b9
commit 945db0be56
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 540 additions and 580 deletions

View file

@ -36,7 +36,7 @@
"default": null
},
"radius.lg": {
"description": "The border radius for large elements like Modals and Notifications, default is 8.",
"description": "The border radius for large elements like Dialogs and Notifications, default is 8.",
"type": ["number", "null"],
"default": null
},

18
Cargo.lock generated
View file

@ -1603,6 +1603,15 @@ version = "1.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04"
[[package]]
name = "dialog_overlay"
version = "0.4.0-preview1"
dependencies = [
"anyhow",
"gpui",
"gpui-component",
]
[[package]]
name = "diff"
version = "0.1.13"
@ -4367,15 +4376,6 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "modal_overlay"
version = "0.4.0-preview1"
dependencies = [
"anyhow",
"gpui",
"gpui-component",
]
[[package]]
name = "naga"
version = "25.0.1"

View file

@ -8,7 +8,7 @@ members = [
"examples/hello_world",
"examples/input",
"examples/window_title",
"examples/modal_overlay",
"examples/dialog_overlay",
]
resolver = "2"

View file

@ -10,8 +10,8 @@ use gpui_component::{
use serde::Deserialize;
use std::{sync::Arc, time::Duration};
use story::{
AccordionStory, AppState, AppTitleBar, Assets, ButtonStory, CalendarStory, FormStory,
IconStory, ImageStory, InputStory, LabelStory, ListStory, ModalStory, NotificationStory, Open,
AccordionStory, AppState, AppTitleBar, Assets, ButtonStory, CalendarStory, DialogStory,
FormStory, IconStory, ImageStory, InputStory, LabelStory, ListStory, NotificationStory, Open,
PopoverStory, ProgressStory, ResizableStory, ScrollableStory, SelectStory, SidebarStory,
StoryContainer, SwitchStory, TableStory, TooltipStory, WebViewStory,
};
@ -138,11 +138,11 @@ impl StoryWorkspace {
Box::new(TogglePanelVisible(SharedString::from("Sidebar"))),
)
.menu_with_check(
"Modal",
"Dialog",
!invisible_panels
.read(cx)
.contains(&SharedString::from("Modal")),
Box::new(TogglePanelVisible(SharedString::from("Modal"))),
.contains(&SharedString::from("Dialog")),
Box::new(TogglePanelVisible(SharedString::from("Dialog"))),
)
.menu_with_check(
"Accordion",
@ -347,7 +347,7 @@ impl StoryWorkspace {
Arc::new(StoryContainer::panel::<InputStory>(window, cx)),
Arc::new(StoryContainer::panel::<SelectStory>(window, cx)),
Arc::new(StoryContainer::panel::<LabelStory>(window, cx)),
Arc::new(StoryContainer::panel::<ModalStory>(window, cx)),
Arc::new(StoryContainer::panel::<DialogStory>(window, cx)),
Arc::new(StoryContainer::panel::<PopoverStory>(window, cx)),
Arc::new(StoryContainer::panel::<SwitchStory>(window, cx)),
Arc::new(StoryContainer::panel::<ProgressStory>(window, cx)),
@ -435,7 +435,7 @@ impl StoryWorkspace {
1 => Arc::new(StoryContainer::panel::<InputStory>(window, cx)),
2 => Arc::new(StoryContainer::panel::<SelectStory>(window, cx)),
3 => Arc::new(StoryContainer::panel::<LabelStory>(window, cx)),
4 => Arc::new(StoryContainer::panel::<ModalStory>(window, cx)),
4 => Arc::new(StoryContainer::panel::<DialogStory>(window, cx)),
5 => Arc::new(StoryContainer::panel::<PopoverStory>(window, cx)),
6 => Arc::new(StoryContainer::panel::<SwitchStory>(window, cx)),
7 => Arc::new(StoryContainer::panel::<ProgressStory>(window, cx)),
@ -507,7 +507,7 @@ pub fn open_new(
impl Render for StoryWorkspace {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let sheet_layer = Root::render_sheet_layer(window, cx);
let modal_layer = Root::render_modal_layer(window, cx);
let dialog_layer = Root::render_dialog_layer(window, cx);
let notification_layer = Root::render_notification_layer(window, cx);
div()
@ -522,7 +522,7 @@ impl Render for StoryWorkspace {
.child(self.title_bar.clone())
.child(self.dock_area.clone())
.children(sheet_layer)
.children(modal_layer)
.children(dialog_layer)
.children(notification_layer)
}
}

View file

@ -692,7 +692,7 @@ impl Example {
let editor = self.editor.clone();
let input_state = self.go_to_line_state.clone();
window.open_modal(cx, move |modal, window, cx| {
window.open_dialog(cx, move |dialog, window, cx| {
input_state.update(cx, |state, cx| {
let cursor_pos = editor.read(cx).cursor_position();
state.set_placeholder(
@ -703,7 +703,7 @@ impl Example {
state.focus(window, cx);
});
modal
dialog
.title("Go to line")
.child(Input::new(&input_state))
.confirm()

View file

@ -53,7 +53,7 @@ impl Example {
let editor = self.editor.clone();
let input_state = self.go_to_line_state.clone();
window.open_modal(cx, move |modal, window, cx| {
window.open_dialog(cx, move |dialog, window, cx| {
input_state.update(cx, |state, cx| {
let position = editor.read(cx).cursor_position();
state.set_placeholder(
@ -64,7 +64,7 @@ impl Example {
state.focus(window, cx);
});
modal
dialog
.title("Go to line")
.child(Input::new(&input_state))
.confirm()

View file

@ -406,7 +406,7 @@ pub fn open_new(
impl Render for StoryTiles {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let sheet_layer = Root::render_sheet_layer(window, cx);
let modal_layer = Root::render_modal_layer(window, cx);
let dialog_layer = Root::render_dialog_layer(window, cx);
let notification_layer = Root::render_notification_layer(window, cx);
div()
@ -420,7 +420,7 @@ impl Render for StoryTiles {
.child(TitleBar::new().child(div().flex().items_center().child("Story Tiles")))
.child(self.dock_area.clone())
.children(sheet_layer)
.children(modal_layer)
.children(dialog_layer)
.children(notification_layer)
}
}

View file

@ -8,9 +8,9 @@ use gpui_component::{
button::{Button, ButtonVariant, ButtonVariants as _},
checkbox::Checkbox,
date_picker::{DatePicker, DatePickerState},
dialog::DialogButtonProps,
h_flex,
input::{Input, InputState},
modal::ModalButtonProps,
select::{Select, SelectState},
text::TextView,
v_flex,
@ -18,26 +18,26 @@ use gpui_component::{
use crate::{TestAction, section};
pub struct ModalStory {
pub struct DialogStory {
focus_handle: FocusHandle,
selected_value: Option<SharedString>,
input1: Entity<InputState>,
input2: Entity<InputState>,
date: Entity<DatePickerState>,
select: Entity<SelectState<Vec<String>>>,
modal_overlay: bool,
model_show_close: bool,
model_keyboard: bool,
dialog_overlay: bool,
close_button: bool,
keyboard: bool,
overlay_closable: bool,
}
impl super::Story for ModalStory {
impl super::Story for DialogStory {
fn title() -> &'static str {
"Modal"
"Dialog"
}
fn description() -> &'static str {
"A modal dialog"
"A dialog dialog"
}
fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render> {
@ -45,7 +45,7 @@ impl super::Story for ModalStory {
}
}
impl ModalStory {
impl DialogStory {
pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {
cx.new(|cx| Self::new(window, cx))
}
@ -53,7 +53,7 @@ impl ModalStory {
fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
let input1 = cx.new(|cx| InputState::new(window, cx).placeholder("Your Name"));
let input2 = cx.new(|cx| {
InputState::new(window, cx).placeholder("For test focus back on modal close.")
InputState::new(window, cx).placeholder("For test focus back on dialog close.")
});
let date = cx.new(|cx| DatePickerState::new(window, cx));
let select = cx.new(|cx| {
@ -76,34 +76,34 @@ impl ModalStory {
input2,
date,
select,
modal_overlay: true,
model_show_close: true,
model_keyboard: true,
dialog_overlay: true,
close_button: true,
keyboard: true,
overlay_closable: true,
}
}
fn show_modal(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let overlay = self.modal_overlay;
let modal_show_close = self.model_show_close;
fn show_dialog(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let overlay = self.dialog_overlay;
let dialog_close_button = self.close_button;
let overlay_closable = self.overlay_closable;
let input1 = self.input1.clone();
let date = self.date.clone();
let select = self.select.clone();
let view = cx.entity().clone();
let keyboard = self.model_keyboard;
let keyboard = self.keyboard;
window.open_modal(cx, move |modal, _, _| {
modal
.title("Form Modal")
window.open_dialog(cx, move |dialog, _, _| {
dialog
.title("Form Dialog")
.overlay(overlay)
.keyboard(keyboard)
.show_close(modal_show_close)
.close_button(dialog_close_button)
.overlay_closable(overlay_closable)
.child(
v_flex()
.gap_3()
.child("This is a modal dialog.")
.child("This is a dialog dialog.")
.child("You can put anything here.")
.child(Input::new(&input1))
.child(Select::new(&select))
@ -120,7 +120,7 @@ impl ModalStory {
let input1 = input1.clone();
let date = date.clone();
move |_, window, cx| {
window.close_modal(cx);
window.close_dialog(cx);
view.update(cx, |view, cx| {
view.selected_value = Some(
@ -134,24 +134,24 @@ impl ModalStory {
});
}
}),
Button::new("new-modal").label("Open Other Modal").on_click(
move |_, window, cx| {
window.open_modal(cx, move |modal, _, _| {
modal
.title("Other Modal")
.child("This is another modal.")
Button::new("new-dialog")
.label("Open Other Dialog")
.on_click(move |_, window, cx| {
window.open_dialog(cx, move |dialog, _, _| {
dialog
.title("Other Dialog")
.child("This is another dialog.")
.min_h(px(100.))
.overlay(overlay)
.keyboard(keyboard)
.show_close(modal_show_close)
.close_button(dialog_close_button)
.overlay_closable(overlay_closable)
});
},
),
}),
Button::new("cancel")
.label("Cancel")
.on_click(move |_, window, cx| {
window.close_modal(cx);
window.close_dialog(cx);
}),
]
}
@ -171,19 +171,19 @@ impl ModalStory {
}
}
impl Focusable for ModalStory {
impl Focusable for DialogStory {
fn focus_handle(&self, _cx: &gpui::App) -> FocusHandle {
self.focus_handle.clone()
}
}
impl Render for ModalStory {
impl Render for DialogStory {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let modal_overlay = self.modal_overlay;
let dialog_overlay = self.dialog_overlay;
let overlay_closable = self.overlay_closable;
div()
.id("modal-story")
.id("dialog-story")
.track_focus(&self.focus_handle)
.on_action(cx.listener(Self::on_action_test_action))
.size_full()
@ -195,11 +195,11 @@ impl Render for ModalStory {
.items_center()
.gap_3()
.child(
Checkbox::new("modal-overlay")
.label("Modal Overlay")
.checked(self.modal_overlay)
Checkbox::new("dialog-overlay")
.label("Dialog Overlay")
.checked(self.dialog_overlay)
.on_click(cx.listener(|view, _, _, cx| {
view.modal_overlay = !view.modal_overlay;
view.dialog_overlay = !view.dialog_overlay;
cx.notify();
})),
)
@ -213,31 +213,31 @@ impl Render for ModalStory {
})),
)
.child(
Checkbox::new("modal-show-close")
Checkbox::new("dialog-show-close")
.label("Model Close Button")
.checked(self.model_show_close)
.checked(self.close_button)
.on_click(cx.listener(|view, _, _, cx| {
view.model_show_close = !view.model_show_close;
view.close_button = !view.close_button;
cx.notify();
})),
)
.child(
Checkbox::new("modal-keyboard")
Checkbox::new("dialog-keyboard")
.label("Keyboard")
.checked(self.model_keyboard)
.checked(self.keyboard)
.on_click(cx.listener(|view, _, _, cx| {
view.model_keyboard = !view.model_keyboard;
view.keyboard = !view.keyboard;
cx.notify();
})),
),
)
.child(
section("Normal Modal").child(
Button::new("show-modal")
section("Normal Dialog").child(
Button::new("show-dialog")
.outline()
.label("Open Modal")
.label("Open Dialog")
.on_click(
cx.listener(|this, _, window, cx| this.show_modal(window, cx)),
cx.listener(|this, _, window, cx| this.show_dialog(window, cx)),
),
),
)
@ -255,21 +255,21 @@ impl Render for ModalStory {
})
.tooltip(
"This button for test dispatch action, \
to make sure when Modal close,\
to make sure when Dialog close,\
\nthis still can handle the action.",
),
),
)
.child(
section("Confirm Modal").child(
Button::new("confirm-modal0")
section("Confirm Dialog").child(
Button::new("confirm-dialog0")
.outline()
.label("Open Confirm Modal")
.label("Open Confirm Dialog")
.on_click(cx.listener(move |_, _, window, cx| {
window.open_modal(cx, move |modal, _, _| {
modal
window.open_dialog(cx, move |dialog, _, _| {
dialog
.confirm()
.overlay(modal_overlay)
.overlay(dialog_overlay)
.overlay_closable(overlay_closable)
.child("Are you sure to submit?")
.on_ok(|_, window, cx| {
@ -289,16 +289,16 @@ impl Render for ModalStory {
),
)
.child(
section("Confirm Modal with custom buttons").child(
Button::new("confirm-modal1")
section("Confirm Dialog with custom buttons").child(
Button::new("confirm-dialog1")
.outline()
.label("Custom Buttons")
.on_click(cx.listener(move |_, _, window, cx| {
window.open_modal(cx, move |modal, _, cx| {
modal
window.open_dialog(cx, move |dialog, _, cx| {
dialog
.rounded_lg()
.confirm()
.overlay(modal_overlay)
.overlay(dialog_overlay)
.overlay_closable(overlay_closable)
.child(
h_flex().gap_3()
@ -306,7 +306,7 @@ impl Render for ModalStory {
.child("Update successful, we need to restart the application.")
)
.button_props(
ModalButtonProps::default()
DialogButtonProps::default()
.cancel_text("Later")
.cancel_variant(ButtonVariant::Secondary)
.ok_text("Restart Now")
@ -331,15 +331,15 @@ impl Render for ModalStory {
),
)
.child(
section("Alert Modal").child(
Button::new("alert-modal")
section("Alert Dialog").child(
Button::new("alert-dialog")
.outline()
.label("Alert")
.on_click(cx.listener(move |_, _, window, cx| {
window.open_modal(cx, move |modal, _, _| {
modal
window.open_dialog(cx, move |dialog, _, _| {
dialog
.confirm()
.overlay(modal_overlay)
.overlay(dialog_overlay)
.overlay_closable(overlay_closable)
.child("You are successfully logged in.")
.alert()
@ -352,17 +352,17 @@ impl Render for ModalStory {
),
)
.child(
section("Scrollable Modal").child(
Button::new("scrollable-modal")
section("Scrollable Dialog").child(
Button::new("scrollable-dialog")
.outline()
.label("Scrollable Modal")
.label("Scrollable Dialog")
.on_click(cx.listener(move |_, _, window, cx| {
window.open_modal(cx, move |modal, window, cx| {
modal
window.open_dialog(cx, move |dialog, window, cx| {
dialog
.h(px(450.))
.overlay(modal_overlay)
.overlay(dialog_overlay)
.overlay_closable(overlay_closable)
.title("Modal with scrollbar")
.title("Dialog with scrollbar")
.child(TextView::markdown(
"markdown1",
include_str!("../../../README.md"),
@ -375,32 +375,32 @@ impl Render for ModalStory {
)
.child(
section("Custom Paddings").child(
Button::new("custom-modal-paddings")
Button::new("custom-dialog-paddings")
.outline()
.label("Custom Paddings")
.on_click(cx.listener(move |_, _, window, cx| {
window.open_modal(cx, move |modal, _, _| {
modal
window.open_dialog(cx, move |dialog, _, _| {
dialog
.p_3()
.title("Custom Modal Title")
.child("This is a custom modal content, we can use paddings to control the layout and spacing within the modal.")
.title("Custom Dialog Title")
.child("This is a custom dialog content, we can use paddings to control the layout and spacing within the dialog.")
});
})),
),
)
.child(
section("Custom Style").child(
Button::new("custom-modal-style")
Button::new("custom-dialog-style")
.outline()
.label("Custom Modal Style")
.label("Custom Dialog Style")
.on_click(cx.listener(move |_, _, window, cx| {
window.open_modal(cx, move |modal, _, cx| {
modal
window.open_dialog(cx, move |dialog, _, cx| {
dialog
.rounded_lg()
.bg(cx.theme().cyan)
.text_color(cx.theme().info_foreground)
.title("Custom Modal Title")
.child("This is a custom modal content.")
.title("Custom Dialog Title")
.child("This is a custom dialog content.")
});
})),
),

View file

@ -13,6 +13,7 @@ mod collapsible_story;
mod color_picker_story;
mod date_picker_story;
mod description_list_story;
mod dialog_story;
mod form_story;
mod group_box_story;
mod icon_story;
@ -22,7 +23,6 @@ mod kbd_story;
mod label_story;
mod list_story;
mod menu_story;
mod modal_story;
mod notification_story;
mod number_input_story;
mod otp_input_story;
@ -73,6 +73,7 @@ pub use collapsible_story::CollapsibleStory;
pub use color_picker_story::ColorPickerStory;
pub use date_picker_story::DatePickerStory;
pub use description_list_story::DescriptionListStory;
pub use dialog_story::DialogStory;
pub use form_story::FormStory;
pub use group_box_story::GroupBoxStory;
pub use icon_story::IconStory;
@ -82,7 +83,6 @@ pub use kbd_story::KbdStory;
pub use label_story::LabelStory;
pub use list_story::ListStory;
pub use menu_story::MenuStory;
pub use modal_story::ModalStory;
pub use notification_story::NotificationStory;
pub use number_input_story::NumberInputStory;
pub use otp_input_story::OtpInputStory;
@ -263,7 +263,7 @@ impl StoryRoot {
impl Render for StoryRoot {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let sheet_layer = Root::render_sheet_layer(window, cx);
let modal_layer = Root::render_modal_layer(window, cx);
let dialog_layer = Root::render_dialog_layer(window, cx);
let notification_layer = Root::render_notification_layer(window, cx);
div()
@ -275,7 +275,7 @@ impl Render for StoryRoot {
.child(div().flex_1().overflow_hidden().child(self.view.clone())),
)
.children(sheet_layer)
.children(modal_layer)
.children(dialog_layer)
.children(notification_layer)
}
}
@ -666,7 +666,7 @@ impl StoryState {
"ImageStory" => story!(ImageStory),
"InputStory" => story!(InputStory),
"ListStory" => story!(ListStory),
"ModalStory" => story!(ModalStory),
"DialogStory" => story!(DialogStory),
"PopoverStory" => story!(PopoverStory),
"ProgressStory" => story!(ProgressStory),
"ResizableStory" => story!(ResizableStory),

View file

@ -49,6 +49,7 @@ impl Gallery {
StoryContainer::panel::<ColorPickerStory>(window, cx),
StoryContainer::panel::<DatePickerStory>(window, cx),
StoryContainer::panel::<DescriptionListStory>(window, cx),
StoryContainer::panel::<DialogStory>(window, cx),
StoryContainer::panel::<FormStory>(window, cx),
StoryContainer::panel::<GroupBoxStory>(window, cx),
StoryContainer::panel::<IconStory>(window, cx),
@ -58,7 +59,6 @@ impl Gallery {
StoryContainer::panel::<LabelStory>(window, cx),
StoryContainer::panel::<ListStory>(window, cx),
StoryContainer::panel::<MenuStory>(window, cx),
StoryContainer::panel::<ModalStory>(window, cx),
StoryContainer::panel::<NotificationStory>(window, cx),
StoryContainer::panel::<NumberInputStory>(window, cx),
StoryContainer::panel::<OtpInputStory>(window, cx),

View file

@ -247,7 +247,7 @@ impl SheetStory {
let input1 = cx.new(|cx| InputState::new(window, cx).placeholder("Your Name"));
let input2 = cx.new(|cx| {
InputState::new(window, cx).placeholder("For test focus back on modal close.")
InputState::new(window, cx).placeholder("For test focus back on dialog close.")
});
let date = cx.new(|cx| DatePickerState::new(window, cx));
@ -424,7 +424,7 @@ impl Render for SheetStory {
})
.tooltip(
"This button for test dispatch action, \
to make sure when Modal close,\
to make sure when Dialog close,\
\nthis still can handle the action.",
),
),

View file

@ -138,7 +138,7 @@ Dock:
zh-CN: 展开
zh-HK: 展開
it: Espandi
Modal:
Dialog:
ok:
en: OK
zh-CN: 确定

View file

@ -15,7 +15,7 @@ use crate::{
h_flex, v_flex, ActiveTheme as _, IconName, Root, Sizable as _, StyledExt, WindowExt as _,
};
const CONTEXT: &str = "Modal";
const CONTEXT: &str = "Dialog";
pub(crate) fn init(cx: &mut App) {
cx.bind_keys([
KeyBinding::new("escape", Cancel, Some(CONTEXT)),
@ -27,15 +27,15 @@ type RenderButtonFn = Box<dyn FnOnce(&mut Window, &mut App) -> AnyElement>;
type FooterFn =
Box<dyn Fn(RenderButtonFn, RenderButtonFn, &mut Window, &mut App) -> Vec<AnyElement>>;
/// Modal button props.
pub struct ModalButtonProps {
/// Dialog button props.
pub struct DialogButtonProps {
ok_text: Option<SharedString>,
ok_variant: ButtonVariant,
cancel_text: Option<SharedString>,
cancel_variant: ButtonVariant,
}
impl Default for ModalButtonProps {
impl Default for DialogButtonProps {
fn default() -> Self {
Self {
ok_text: None,
@ -46,7 +46,7 @@ impl Default for ModalButtonProps {
}
}
impl ModalButtonProps {
impl DialogButtonProps {
/// Sets the text of the OK button. Default is `OK`.
pub fn ok_text(mut self, ok_text: impl Into<SharedString>) -> Self {
self.ok_text = Some(ok_text.into());
@ -74,7 +74,7 @@ impl ModalButtonProps {
/// A modal to display content in a dialog box.
#[derive(IntoElement)]
pub struct Modal {
pub struct Dialog {
style: StyleRefinement,
title: Option<AnyElement>,
footer: Option<FooterFn>,
@ -86,13 +86,13 @@ pub struct Modal {
on_close: Rc<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
on_ok: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App) -> bool + 'static>>,
on_cancel: Rc<dyn Fn(&ClickEvent, &mut Window, &mut App) -> bool + 'static>,
button_props: ModalButtonProps,
show_close: bool,
button_props: DialogButtonProps,
close_button: bool,
overlay: bool,
overlay_closable: bool,
keyboard: bool,
/// This will be change when open the modal, the focus handle is create when open the modal.
/// This will be change when open the dialog, the focus handle is create when open the dialog.
pub(crate) focus_handle: FocusHandle,
pub(crate) layer_ix: usize,
pub(crate) overlay_visible: bool,
@ -106,8 +106,8 @@ pub(crate) fn overlay_color(overlay: bool, cx: &App) -> Hsla {
cx.theme().overlay
}
impl Modal {
/// Create a new modal.
impl Dialog {
/// Create a new dialog.
pub fn new(_: &mut Window, cx: &mut App) -> Self {
Self {
focus_handle: cx.focus_handle(),
@ -125,19 +125,19 @@ impl Modal {
on_close: Rc::new(|_, _, _| {}),
on_ok: None,
on_cancel: Rc::new(|_, _, _| true),
button_props: ModalButtonProps::default(),
show_close: true,
button_props: DialogButtonProps::default(),
close_button: true,
overlay_closable: true,
}
}
/// Sets the title of the modal.
/// Sets the title of the dialog.
pub fn title(mut self, title: impl IntoElement) -> Self {
self.title = Some(title.into_any_element());
self
}
/// Set the footer of the modal.
/// Set the footer of the dialog.
///
/// The `footer` is a function that takes two `RenderButtonFn` and a `WindowContext` and returns a list of `AnyElement`.
///
@ -159,31 +159,31 @@ impl Modal {
self
}
/// Set to use confirm modal, with OK and Cancel buttons.
/// Set to use confirm dialog, with OK and Cancel buttons.
///
/// See also [`Self::alert`]
pub fn confirm(self) -> Self {
self.footer(|ok, cancel, window, cx| vec![cancel(window, cx), ok(window, cx)])
.overlay_closable(false)
.show_close(false)
.close_button(false)
}
/// Set to as a alter modal, with OK button.
/// Set to as a alter dialog, with OK button.
///
/// See also [`Self::confirm`]
pub fn alert(self) -> Self {
self.footer(|ok, _, window, cx| vec![ok(window, cx)])
.overlay_closable(false)
.show_close(false)
.close_button(false)
}
/// Set the button props of the modal.
pub fn button_props(mut self, button_props: ModalButtonProps) -> Self {
/// Set the button props of the dialog.
pub fn button_props(mut self, button_props: DialogButtonProps) -> Self {
self.button_props = button_props;
self
}
/// Sets the callback for when the modal is closed.
/// Sets the callback for when the dialog is closed.
///
/// Called after [`Self::on_ok`] or [`Self::on_cancel`] callback.
pub fn on_close(
@ -194,9 +194,9 @@ impl Modal {
self
}
/// Sets the callback for when the modal is has been confirmed.
/// Sets the callback for when the dialog is has been confirmed.
///
/// The callback should return `true` to close the modal, if return `false` the modal will not be closed.
/// The callback should return `true` to close the dialog, if return `false` the dialog will not be closed.
pub fn on_ok(
mut self,
on_ok: impl Fn(&ClickEvent, &mut Window, &mut App) -> bool + 'static,
@ -205,9 +205,9 @@ impl Modal {
self
}
/// Sets the callback for when the modal is has been canceled.
/// Sets the callback for when the dialog is has been canceled.
///
/// The callback should return `true` to close the modal, if return `false` the modal will not be closed.
/// The callback should return `true` to close the dialog, if return `false` the dialog will not be closed.
pub fn on_cancel(
mut self,
on_cancel: impl Fn(&ClickEvent, &mut Window, &mut App) -> bool + 'static,
@ -217,44 +217,44 @@ impl Modal {
}
/// Sets the false to hide close icon, default: true
pub fn show_close(mut self, show_close: bool) -> Self {
self.show_close = show_close;
pub fn close_button(mut self, close_button: bool) -> Self {
self.close_button = close_button;
self
}
/// Set the top offset of the modal, defaults to None, will use the 1/10 of the viewport height.
/// Set the top offset of the dialog, defaults to None, will use the 1/10 of the viewport height.
pub fn margin_top(mut self, margin_top: Pixels) -> Self {
self.margin_top = Some(margin_top);
self
}
/// Sets the width of the modal, defaults to 480px.
/// Sets the width of the dialog, defaults to 480px.
pub fn width(mut self, width: Pixels) -> Self {
self.width = width;
self
}
/// Set the maximum width of the modal, defaults to `None`.
/// Set the maximum width of the dialog, defaults to `None`.
pub fn max_w(mut self, max_width: Pixels) -> Self {
self.max_width = Some(max_width);
self
}
/// Set the overlay of the modal, defaults to `true`.
/// Set the overlay of the dialog, defaults to `true`.
pub fn overlay(mut self, overlay: bool) -> Self {
self.overlay = overlay;
self
}
/// Set the overlay closable of the modal, defaults to `true`.
/// Set the overlay closable of the dialog, defaults to `true`.
///
/// When the overlay is clicked, the modal will be closed.
/// When the overlay is clicked, the dialog will be closed.
pub fn overlay_closable(mut self, overlay_closable: bool) -> Self {
self.overlay_closable = overlay_closable;
self
}
/// Set whether to support keyboard esc to close the modal, defaults to `true`.
/// Set whether to support keyboard esc to close the dialog, defaults to `true`.
pub fn keyboard(mut self, keyboard: bool) -> Self {
self.keyboard = keyboard;
self
@ -265,19 +265,19 @@ impl Modal {
}
}
impl ParentElement for Modal {
impl ParentElement for Dialog {
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
self.content.extend(elements);
}
}
impl Styled for Modal {
impl Styled for Dialog {
fn style(&mut self) -> &mut gpui::StyleRefinement {
&mut self.style
}
}
impl RenderOnce for Modal {
impl RenderOnce for Dialog {
fn render(self, window: &mut Window, cx: &mut App) -> impl gpui::IntoElement {
let layer_ix = self.layer_ix;
let on_close = self.on_close.clone();
@ -290,7 +290,7 @@ impl RenderOnce for Modal {
let ok_text = self
.button_props
.ok_text
.unwrap_or_else(|| t!("Modal.ok").into());
.unwrap_or_else(|| t!("Dialog.ok").into());
let ok_variant = self.button_props.ok_variant;
move |_, _| {
Button::new("ok")
@ -308,7 +308,7 @@ impl RenderOnce for Modal {
}
on_close(&ClickEvent::default(), window, cx);
window.close_modal(cx);
window.close_dialog(cx);
}
})
.into_any_element()
@ -320,7 +320,7 @@ impl RenderOnce for Modal {
let cancel_text = self
.button_props
.cancel_text
.unwrap_or_else(|| t!("Modal.cancel").into());
.unwrap_or_else(|| t!("Dialog.cancel").into());
let cancel_variant = self.button_props.cancel_variant;
move |_, _| {
Button::new("cancel")
@ -335,7 +335,7 @@ impl RenderOnce for Modal {
}
on_close(&ClickEvent::default(), window, cx);
window.close_modal(cx);
window.close_dialog(cx);
}
})
.into_any_element()
@ -381,7 +381,7 @@ impl RenderOnce for Modal {
.snap_to_window()
.child(
div()
.id("modal")
.id("dialog")
.occlude()
.w(view_size.width)
.h(view_size.height)
@ -389,8 +389,8 @@ impl RenderOnce for Modal {
this.bg(overlay_color(self.overlay, cx))
})
.when(self.overlay, |this| {
// Only the last modal owns the `mouse down - close modal` event.
if (self.layer_ix + 1) != Root::read(window, cx).active_modals.len() {
// Only the last dialog owns the `mouse down - close dialog` event.
if (self.layer_ix + 1) != Root::read(window, cx).active_dialogs.len() {
return this;
}
@ -403,7 +403,7 @@ impl RenderOnce for Modal {
if self.overlay_closable && event.button == MouseButton::Left {
on_cancel(&ClickEvent::default(), window, cx);
on_close(&ClickEvent::default(), window, cx);
window.close_modal(cx);
window.close_dialog(cx);
}
}
})
@ -431,11 +431,11 @@ impl RenderOnce for Modal {
move |_: &Cancel, window, cx| {
// FIXME:
//
// Here some Modal have no focus_handle, so it will not work will Escape key.
// But by now, we `cx.close_modal()` going to close the last active model, so the Escape is unexpected to work.
// Here some Dialog have no focus_handle, so it will not work will Escape key.
// But by now, we `cx.close_dialog()` going to close the last active model, so the Escape is unexpected to work.
on_cancel(&ClickEvent::default(), window, cx);
on_close(&ClickEvent::default(), window, cx);
window.close_modal(cx);
window.close_dialog(cx);
}
})
.on_action({
@ -446,10 +446,10 @@ impl RenderOnce for Modal {
if let Some(on_ok) = &on_ok {
if on_ok(&ClickEvent::default(), window, cx) {
on_close(&ClickEvent::default(), window, cx);
window.close_modal(cx);
window.close_dialog(cx);
}
} else if has_footer {
window.close_modal(cx);
window.close_dialog(cx);
}
}
})
@ -472,7 +472,7 @@ impl RenderOnce for Modal {
.child(title),
)
})
.children(self.show_close.then(|| {
.children(self.close_button.then(|| {
let top = (paddings.top - px(10.)).max(px(8.));
let right = (paddings.right - px(10.)).max(px(8.));
@ -489,7 +489,7 @@ impl RenderOnce for Modal {
move |_, window, cx| {
on_cancel(&ClickEvent::default(), window, cx);
on_close(&ClickEvent::default(), window, cx);
window.close_modal(cx);
window.close_dialog(cx);
}
})
}))

View file

@ -1166,7 +1166,7 @@ impl InputState {
self.replace_text_in_range_silent(None, &new_line_text, window, cx);
self.pause_blink_cursor(cx);
} else {
// Single line input, just emit the event (e.g.: In a modal dialog to confirm).
// Single line input, just emit the event (e.g.: In a dialog to confirm).
cx.propagate();
}

View file

@ -29,6 +29,7 @@ pub mod clipboard;
pub mod collapsible;
pub mod color_picker;
pub mod description_list;
pub mod dialog;
pub mod divider;
pub mod dock;
pub mod form;
@ -41,7 +42,6 @@ pub mod label;
pub mod link;
pub mod list;
pub mod menu;
pub mod modal;
pub mod notification;
pub mod plot;
pub mod popover;
@ -104,7 +104,7 @@ pub fn init(cx: &mut App) {
select::init(cx);
input::init(cx);
list::init(cx);
modal::init(cx);
dialog::init(cx);
popover::init(cx);
menu::init(cx);
table::init(cx);

View file

@ -418,8 +418,8 @@ impl<M: ManagedView> Element for Popover<M> {
.subscribe(
&new_content_view,
cx,
move |modal, _: &DismissEvent, window, cx| {
if modal.focus_handle(cx).contains_focused(window, cx) {
move |dialog, _: &DismissEvent, window, cx| {
if dialog.focus_handle(cx).contains_focused(window, cx) {
if let Some(previous_focus_handle) =
previous_focus_handle.as_ref()
{

View file

@ -1,6 +1,6 @@
use crate::{
dialog::Dialog,
input::InputState,
modal::Modal,
notification::{Notification, NotificationList},
sheet::Sheet,
window_border, ActiveTheme, Placement,
@ -22,7 +22,7 @@ pub(crate) fn init(cx: &mut App) {
]);
}
/// Extension trait for [`Window`] to add modal, sheet .. functionality.
/// Extension trait for [`Window`] to add dialog, sheet .. functionality.
pub trait WindowExt: Sized {
/// Opens a Sheet at right placement.
fn open_sheet<F>(&mut self, cx: &mut App, build: F)
@ -40,19 +40,19 @@ pub trait WindowExt: Sized {
/// Closes the active Sheet.
fn close_sheet(&mut self, cx: &mut App);
/// Opens a Modal.
fn open_modal<F>(&mut self, cx: &mut App, build: F)
/// Opens a Dialog.
fn open_dialog<F>(&mut self, cx: &mut App, build: F)
where
F: Fn(Modal, &mut Window, &mut App) -> Modal + 'static;
F: Fn(Dialog, &mut Window, &mut App) -> Dialog + 'static;
/// Return true, if there is an active Modal.
fn has_active_modal(&mut self, cx: &mut App) -> bool;
/// Return true, if there is an active Dialog.
fn has_active_dialog(&mut self, cx: &mut App) -> bool;
/// Closes the last active Modal.
fn close_modal(&mut self, cx: &mut App);
/// Closes the last active Dialog.
fn close_dialog(&mut self, cx: &mut App);
/// Closes all active Modals.
fn close_all_modals(&mut self, cx: &mut App);
/// Closes all active Dialogs.
fn close_all_dialogs(&mut self, cx: &mut App);
/// Pushes a notification to the notification list.
fn push_notification(&mut self, note: impl Into<Notification>, cx: &mut App);
@ -114,21 +114,21 @@ impl WindowExt for Window {
})
}
fn open_modal<F>(&mut self, cx: &mut App, build: F)
fn open_dialog<F>(&mut self, cx: &mut App, build: F)
where
F: Fn(Modal, &mut Window, &mut App) -> Modal + 'static,
F: Fn(Dialog, &mut Window, &mut App) -> Dialog + 'static,
{
Root::update(self, cx, move |root, window, cx| {
// Only save focus handle if there are no active modals.
// This is used to restore focus when all modals are closed.
if root.active_modals.len() == 0 {
// Only save focus handle if there are no active dialogs.
// This is used to restore focus when all dialogs are closed.
if root.active_dialogs.len() == 0 {
root.previous_focus_handle = window.focused(cx);
}
let focus_handle = cx.focus_handle();
focus_handle.focus(window);
root.active_modals.push(ActiveModal {
root.active_dialogs.push(ActiveDialog {
focus_handle,
builder: Rc::new(build),
});
@ -136,30 +136,30 @@ impl WindowExt for Window {
})
}
fn has_active_modal(&mut self, cx: &mut App) -> bool {
Root::read(self, cx).active_modals.len() > 0
fn has_active_dialog(&mut self, cx: &mut App) -> bool {
Root::read(self, cx).active_dialogs.len() > 0
}
fn close_modal(&mut self, cx: &mut App) {
fn close_dialog(&mut self, cx: &mut App) {
Root::update(self, cx, move |root, window, cx| {
root.focused_input = None;
root.active_modals.pop();
root.active_dialogs.pop();
if let Some(top_modal) = root.active_modals.last() {
// Focus the next modal.
top_modal.focus_handle.focus(window);
if let Some(top_dialog) = root.active_dialogs.last() {
// Focus the next dialog.
top_dialog.focus_handle.focus(window);
} else {
// Restore focus if there are no more modals.
// Restore focus if there are no more dialogs.
root.focus_back(window, cx);
}
cx.notify();
})
}
fn close_all_modals(&mut self, cx: &mut App) {
fn close_all_dialogs(&mut self, cx: &mut App) {
Root::update(self, cx, |root, window, cx| {
root.focused_input = None;
root.active_modals.clear();
root.active_dialogs.clear();
root.focus_back(window, cx);
cx.notify();
})
@ -208,13 +208,13 @@ impl WindowExt for Window {
/// Root is a view for the App window for as the top level view (Must be the first view in the window).
///
/// It is used to manage the Sheet, Modal, and Notification.
/// It is used to manage the Sheet, Dialog, and Notification.
pub struct Root {
/// Used to store the focus handle of the previous view.
/// When the Modal, Sheet closes, we will focus back to the previous view.
/// When the Dialog, Sheet closes, we will focus back to the previous view.
previous_focus_handle: Option<FocusHandle>,
active_sheet: Option<ActiveSheet>,
pub(crate) active_modals: Vec<ActiveModal>,
pub(crate) active_dialogs: Vec<ActiveDialog>,
pub(super) focused_input: Option<Entity<InputState>>,
pub notification: Entity<NotificationList>,
sheet_size: Option<DefiniteLength>,
@ -229,9 +229,9 @@ struct ActiveSheet {
}
#[derive(Clone)]
pub(crate) struct ActiveModal {
pub(crate) struct ActiveDialog {
focus_handle: FocusHandle,
builder: Rc<dyn Fn(Modal, &mut Window, &mut App) -> Modal + 'static>,
builder: Rc<dyn Fn(Dialog, &mut Window, &mut App) -> Dialog + 'static>,
}
impl Root {
@ -239,7 +239,7 @@ impl Root {
Self {
previous_focus_handle: None,
active_sheet: None,
active_modals: Vec::new(),
active_dialogs: Vec::new(),
focused_input: None,
notification: cx.new(|cx| NotificationList::new(window, cx)),
sheet_size: None,
@ -326,49 +326,49 @@ impl Root {
None
}
/// Render the Modal layer.
pub fn render_modal_layer(window: &mut Window, cx: &mut App) -> Option<impl IntoElement> {
/// Render the Dialog layer.
pub fn render_dialog_layer(window: &mut Window, cx: &mut App) -> Option<impl IntoElement> {
let root = window.root::<Root>()??;
let active_modals = root.read(cx).active_modals.clone();
let active_dialogs = root.read(cx).active_dialogs.clone();
if active_modals.is_empty() {
if active_dialogs.is_empty() {
return None;
}
let mut show_overlay_ix = None;
let mut modals = active_modals
let mut dialogs = active_dialogs
.iter()
.enumerate()
.map(|(i, active_modal)| {
let mut modal = Modal::new(window, cx);
.map(|(i, active_dialog)| {
let mut dialog = Dialog::new(window, cx);
modal = (active_modal.builder)(modal, window, cx);
dialog = (active_dialog.builder)(dialog, window, cx);
// Give the modal the focus handle, because `modal` is a temporary value, is not possible to
// keep the focus handle in the modal.
// Give the dialog the focus handle, because `dialog` is a temporary value, is not possible to
// keep the focus handle in the dialog.
//
// So we keep the focus handle in the `active_modal`, this is owned by the `Root`.
modal.focus_handle = active_modal.focus_handle.clone();
// So we keep the focus handle in the `active_dialog`, this is owned by the `Root`.
dialog.focus_handle = active_dialog.focus_handle.clone();
modal.layer_ix = i;
// Find the modal which one needs to show overlay.
if modal.has_overlay() {
dialog.layer_ix = i;
// Find the dialog which one needs to show overlay.
if dialog.has_overlay() {
show_overlay_ix = Some(i);
}
modal
dialog
})
.collect::<Vec<_>>();
if let Some(ix) = show_overlay_ix {
if let Some(modal) = modals.get_mut(ix) {
modal.overlay_visible = true;
if let Some(dialog) = dialogs.get_mut(ix) {
dialog.overlay_visible = true;
}
}
Some(div().children(modals))
Some(div().children(dialogs))
}
/// Return the root view of the Root.

View file

@ -685,7 +685,7 @@ where
}
fn enter(&mut self, _: &Confirm, window: &mut Window, cx: &mut Context<Self>) {
// Propagate the event to the parent view, for example to the Modal to support ENTER to confirm.
// Propagate the event to the parent view, for example to the Dialog to support ENTER to confirm.
cx.propagate();
if !self.open {

View file

@ -10,8 +10,8 @@ use gpui::{
use crate::{
actions::Cancel,
button::{Button, ButtonVariants as _},
dialog::overlay_color,
h_flex,
modal::overlay_color,
title_bar::TITLE_BAR_HEIGHT,
v_flex, ActiveTheme, IconName, Placement, Sizable, StyledExt as _, WindowExt as _,
};

View file

@ -49,7 +49,7 @@ pub struct Theme {
pub font_size: Pixels,
/// Radius for the general elements.
pub radius: Pixels,
/// Radius for the large elements, e.g.: Modal, Notification border radius.
/// Radius for the large elements, e.g.: Dialog, Notification border radius.
pub radius_lg: Pixels,
pub shadow: bool,
pub transparent: Hsla,

View file

@ -45,7 +45,7 @@ pub struct ThemeConfig {
/// The border radius for general elements, default is 6.
#[serde(rename = "radius")]
pub radius: Option<usize>,
/// The border radius for large elements like Modals and Notifications, default is 8.
/// The border radius for large elements like Dialogs and Notifications, default is 8.
#[serde(rename = "radius.lg")]
pub radius_lg: Option<usize>,
/// Set shadows in the theme, for example the Input and Button, default is true.

View file

@ -46,7 +46,7 @@
"children": [],
"info": {
"panel": {
"story_klass": "ModalStory"
"story_klass": "DialogStory"
}
}
},

View file

@ -0,0 +1,287 @@
---
title: Dialog
description: A dialog dialog for displaying content in a layer above the app.
---
# Dialog
Dialog component for creating dialogs, confirmations, and alerts. Supports overlay, keyboard shortcuts, and various customizations.
## Import
```rust
use gpui_component::dialog::DialogButtonProps;
use gpui_component::WindowExt;
```
## Usage
### Setup application root view for display of dialogs
You need to set up your application's root view to render the dialog layer. This is typically done in your main application struct's render method.
The [Root::render_dialog_layer](https://docs.rs/gpui-component/latest/gpui_component/struct.Root.html#method.render_dialog_layer) function handles rendering any active dialogs on top of your app content.
```rust
use gpui_component::TitleBar;
struct MyApp {
view: AnyView,
}
impl Render for MyApp {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let dialog_layer = Root::render_dialog_layer(window, cx);
div()
.size_full()
.child(
v_flex()
.size_full()
.child(TitleBar::new())
.child(div().flex_1().overflow_hidden().child(self.view.clone())),
)
// Render the dialog layer on top of the app content
.children(dialog_layer)
}
}
```
### Basic Dialog
```rust
window.open_dialog(cx, |dialog, _, _| {
dialog
.title("Welcome")
.child("This is a dialog dialog.")
})
```
### Form Dialog
```rust
let input = cx.new(|cx| InputState::new(window, cx));
window.open_dialog(cx, |dialog, _, _| {
dialog
.title("User Information")
.child(
v_flex()
.gap_3()
.child("Please enter your details:")
.child(Input::new(&input))
)
.footer(|_, _, _, _| {
vec![
Button::new("ok")
.primary()
.label("Submit")
.on_click(|_, window, cx| {
window.close_dialog(cx);
}),
Button::new("cancel")
.label("Cancel")
.on_click(|_, window, cx| {
window.close_dialog(cx);
}),
]
})
})
```
### Confirm Dialog
```rust
window.open_dialog(cx, |dialog, _, _| {
dialog
.confirm()
.child("Are you sure you want to delete this item?")
.on_ok(|_, window, cx| {
window.push_notification("Item deleted", cx);
true // Return true to close dialog
})
.on_cancel(|_, window, cx| {
window.push_notification("Cancelled", cx);
true
})
})
```
### Alert Dialog
```rust
window.open_dialog(cx, |dialog, _, _| {
dialog
.alert()
.child("Operation completed successfully!")
.on_close(|_, window, cx| {
window.push_notification("Alert closed", cx);
})
})
```
### Custom Button Labels
```rust
use gpui_component::button::ButtonVariant;
window.open_dialog(cx, |dialog, _, _| {
dialog
.confirm()
.child("Update available. Restart now?")
.button_props(
DialogButtonProps::default()
.cancel_text("Later")
.cancel_variant(ButtonVariant::Secondary)
.ok_text("Restart Now")
.ok_variant(ButtonVariant::Danger)
)
.on_ok(|_, window, cx| {
window.push_notification("Restarting...", cx);
true
})
})
```
### Dialog with Icon
```rust
window.open_dialog(cx, |dialog, _, cx| {
dialog
.confirm()
.child(
h_flex()
.gap_3()
.child(Icon::new(IconName::TriangleAlert)
.size_6()
.text_color(cx.theme().warning))
.child("This action cannot be undone.")
)
})
```
### Scrollable Dialog
```rust
window.open_dialog(cx, |dialog, window, cx| {
dialog
.h(px(450.))
.title("Long Content")
.child(TextView::markdown("content", long_markdown_text, window, cx))
})
```
### Dialog Options
```rust
window.open_dialog(cx, |dialog, _, _| {
dialog
.title("Custom Dialog")
.overlay(true) // Show overlay (default: true)
.overlay_closable(true) // Click overlay to close (default: true)
.keyboard(true) // ESC to close (default: true)
.close_button(false) // Show close button (default: true)
.child("Dialog content")
})
```
### Nested Dialogs
```rust
window.open_dialog(cx, |dialog, _, _| {
dialog
.title("First Dialog")
.child("This is the first dialog")
.footer(|_, _, _, _| {
vec![
Button::new("open-another")
.label("Open Another Dialog")
.on_click(|_, window, cx| {
window.open_dialog(cx, |dialog, _, _| {
dialog
.title("Second Dialog")
.child("This is nested")
});
}),
]
})
})
```
### Custom Styling
```rust
window.open_dialog(cx, |dialog, _, cx| {
dialog
.rounded_lg()
.bg(cx.theme().cyan)
.text_color(cx.theme().info_foreground)
.title("Custom Style")
.child("Styled dialog content")
})
```
### Custom Padding
```rust
window.open_dialog(cx, |dialog, _, _| {
dialog
.p_3() // Custom padding
.title("Custom Padding")
.child("Dialog with custom spacing")
})
```
### Close Dialog Programmatically
The `close_dialog` method can be used to close the active dialog from anywhere within the window context.
```rust
// Close top level active dialog.
window.close_dialog(cx);
// Close and perform action
Button::new("submit")
.primary()
.label("Submit")
.on_click(|_, window, cx| {
// Do something
window.close_dialog(cx);
})
```
## Examples
### Delete Confirmation
```rust
Button::new("delete")
.danger()
.label("Delete")
.on_click(|_, window, cx| {
window.open_dialog(cx, |dialog, _, _| {
dialog
.confirm()
.child("Are you sure you want to delete this item?")
.on_ok(|_, window, cx| {
// Perform delete
window.push_notification("Deleted", cx);
true
})
});
})
```
### Success Alert
```rust
window.open_dialog(cx, |dialog, _, _| {
dialog
.confirm()
.alert()
.child("Your changes have been saved successfully!")
.on_close(|_, _, _| {
// Optional close handler
})
})
```

View file

@ -320,6 +320,6 @@ GroupBox::new()
## Related Components
- **Form**: Use GroupBox within forms to organize sections
- **Modal**: GroupBox works well within modal dialogs for organizing content
- **Dialog**: GroupBox works well within dialogs for organizing content
- **Accordion**: For collapsible grouped content, consider using Accordion instead
- **Card**: For elevated content containers with more visual weight

View file

@ -44,7 +44,7 @@ collapsed: false
- [DescriptionList](description-list) - Key-value pair display
- [GroupBox](group-box) - Grouped content with borders
- [Modal](modal) - Dialog and modal windows
- [Dialog](dialog) - Dialog and modal windows
- [Notification](notification) - Toast notifications
- [Popover](popover) - Floating content display
- [Resizable](resizable) - Resizable panels and containers

View file

@ -1,327 +0,0 @@
---
title: Modal
description: A modal dialog for displaying content in a layer above the app.
---
# Modal
Modal component for creating dialogs, confirmations, and alerts. Supports overlay, keyboard shortcuts, and various customizations.
## Import
```rust
use gpui_component::modal::ModalButtonProps;
use gpui_component::WindowExt;
```
## Usage
### Setup application root view for display of modals
You need to set up your application's root view to render the modal layer. This is typically done in your main application struct's render method.
The [Root::render_modal_layer](https://docs.rs/gpui-component/latest/gpui_component/struct.Root.html#method.render_modal_layer) function handles rendering any active modals on top of your app content.
```rust
use gpui_component::TitleBar;
struct MyApp {
view: AnyView,
}
impl Render for MyApp {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let modal_layer = Root::render_modal_layer(window, cx);
div()
.size_full()
.child(
v_flex()
.size_full()
.child(TitleBar::new())
.child(div().flex_1().overflow_hidden().child(self.view.clone())),
)
// Render the modal layer on top of the app content
.children(modal_layer)
}
}
```
### Basic Modal
```rust
window.open_modal(cx, |modal, _, _| {
modal
.title("Welcome")
.child("This is a modal dialog.")
})
```
### Form Modal
```rust
let input = cx.new(|cx| InputState::new(window, cx));
window.open_modal(cx, |modal, _, _| {
modal
.title("User Information")
.child(
v_flex()
.gap_3()
.child("Please enter your details:")
.child(Input::new(&input))
)
.footer(|_, _, _, _| {
vec![
Button::new("ok")
.primary()
.label("Submit")
.on_click(|_, window, cx| {
window.close_modal(cx);
}),
Button::new("cancel")
.label("Cancel")
.on_click(|_, window, cx| {
window.close_modal(cx);
}),
]
})
})
```
### Confirm Modal
```rust
window.open_modal(cx, |modal, _, _| {
modal
.confirm()
.child("Are you sure you want to delete this item?")
.on_ok(|_, window, cx| {
window.push_notification("Item deleted", cx);
true // Return true to close modal
})
.on_cancel(|_, window, cx| {
window.push_notification("Cancelled", cx);
true
})
})
```
### Alert Modal
```rust
window.open_modal(cx, |modal, _, _| {
modal
.confirm()
.alert()
.child("Operation completed successfully!")
.on_close(|_, window, cx| {
window.push_notification("Alert closed", cx);
})
})
```
### Custom Button Labels
```rust
use gpui_component::button::ButtonVariant;
window.open_modal(cx, |modal, _, _| {
modal
.confirm()
.child("Update available. Restart now?")
.button_props(
ModalButtonProps::default()
.cancel_text("Later")
.cancel_variant(ButtonVariant::Secondary)
.ok_text("Restart Now")
.ok_variant(ButtonVariant::Danger)
)
.on_ok(|_, window, cx| {
window.push_notification("Restarting...", cx);
true
})
})
```
### Modal with Icon
```rust
window.open_modal(cx, |modal, _, cx| {
modal
.confirm()
.child(
h_flex()
.gap_3()
.child(Icon::new(IconName::TriangleAlert)
.size_6()
.text_color(cx.theme().warning))
.child("This action cannot be undone.")
)
})
```
### Scrollable Modal
```rust
window.open_modal(cx, |modal, window, cx| {
modal
.h(px(450.))
.title("Long Content")
.child(TextView::markdown("content", long_markdown_text, window, cx))
})
```
### Modal Options
```rust
window.open_modal(cx, |modal, _, _| {
modal
.title("Custom Modal")
.overlay(true) // Show overlay (default: true)
.overlay_closable(true) // Click overlay to close (default: true)
.keyboard(true) // ESC to close (default: true)
.show_close(true) // Show close button (default: true)
.child("Modal content")
})
```
### Nested Modals
```rust
window.open_modal(cx, |modal, _, _| {
modal
.title("First Modal")
.child("This is the first modal")
.footer(|_, _, _, _| {
vec![
Button::new("open-another")
.label("Open Another Modal")
.on_click(|_, window, cx| {
window.open_modal(cx, |modal, _, _| {
modal
.title("Second Modal")
.child("This is nested")
});
}),
]
})
})
```
### Custom Styling
```rust
window.open_modal(cx, |modal, _, cx| {
modal
.rounded_lg()
.bg(cx.theme().cyan)
.text_color(cx.theme().info_foreground)
.title("Custom Style")
.child("Styled modal content")
})
```
### Custom Padding
```rust
window.open_modal(cx, |modal, _, _| {
modal
.p_3() // Custom padding
.title("Custom Padding")
.child("Modal with custom spacing")
})
```
### Close Modal Programmatically
```rust
// From inside modal
window.close_modal(cx);
// Close and perform action
Button::new("submit")
.primary()
.label("Submit")
.on_click(|_, window, cx| {
// Do something
window.close_modal(cx);
})
```
## API Reference
### Modal Builder
| Method | Description |
| ------------------------ | ---------------------------------------- |
| `title(str)` | Set modal title |
| `child(el)` | Add content to modal body |
| `footer(fn)` | Set footer with custom buttons |
| `overlay(bool)` | Show/hide overlay (default: true) |
| `overlay_closable(bool)` | Allow closing by clicking overlay |
| `keyboard(bool)` | Allow closing with ESC key |
| `show_close(bool)` | Show close button in header |
| `confirm()` | Use confirm modal style |
| `alert()` | Use alert modal style (single OK button) |
| `button_props(props)` | Customize confirm/alert buttons |
| `on_ok(fn)` | OK button callback (confirm/alert) |
| `on_cancel(fn)` | Cancel button callback (confirm) |
| `on_close(fn)` | Close callback (alert) |
| `min_h(px)` | Set minimum height |
| `h(px)` | Set fixed height |
| `rounded_lg()` | Apply large border radius |
| `p_*()` | Custom padding |
| `bg()` | Custom background |
### ModalButtonProps
| Method | Description |
| ------------------------- | ----------------------- |
| `ok_text(str)` | Text for OK button |
| `ok_variant(variant)` | Style for OK button |
| `cancel_text(str)` | Text for Cancel button |
| `cancel_variant(variant)` | Style for Cancel button |
### Window Extensions
| Method | Description |
| -------------------- | ------------------- |
| `open_modal(cx, fn)` | Open a modal dialog |
| `close_modal(cx)` | Close current modal |
## Examples
### Delete Confirmation
```rust
Button::new("delete")
.danger()
.label("Delete")
.on_click(|_, window, cx| {
window.open_modal(cx, |modal, _, _| {
modal
.confirm()
.child("Are you sure you want to delete this item?")
.on_ok(|_, window, cx| {
// Perform delete
window.push_notification("Deleted", cx);
true
})
});
})
```
### Success Alert
```rust
window.open_modal(cx, |modal, _, _| {
modal
.confirm()
.alert()
.child("Your changes have been saved successfully!")
.on_close(|_, _, _| {
// Optional close handler
})
})
```

View file

@ -180,7 +180,7 @@ Explore the component documentation to learn more about each component:
- [Button](./components/button) - Interactive button component
- [Input](./components/input) - Text input with validation
- [Modal](./components/modal) - Dialog and modal windows
- [Dialog](./components/dialog) - Dialog and modal windows
- [Table](./components/table) - High-performance data tables
- [More components...](./components/index)

View file

@ -32,9 +32,9 @@ fn main() {
## Overlays
We have modals, drawers, notifications, we need placement for them to show, so [Root] provides methods to render these overlays:
We have dialogs, sheets, notifications, we need placement for them to show, so [Root] provides methods to render these overlays:
- [Root::render_modal_layer](https://docs.rs/gpui-component/latest/gpui_component/struct.Root.html#method.render_modal_layer) - Render the current opened modals.
- [Root::render_dialog_layer](https://docs.rs/gpui-component/latest/gpui_component/struct.Root.html#method.render_dialog_layer) - Render the current opened modals.
- [Root::render_sheet_layer](https://docs.rs/gpui-component/latest/gpui_component/struct.Root.html#method.render_sheet_layer) - Render the current opened drawers.
- [Root::render_notification_layer](https://docs.rs/gpui-component/latest/gpui_component/struct.Root.html#method.render_notification_layer) - Render the notification list.
@ -48,7 +48,7 @@ impl Render for MyApp {
div()
.size_full()
.child("My App Content")
.children(Root::render_modal_layer(cx))
.children(Root::render_dialog_layer(cx))
.children(Root::render_sheet_layer(cx))
.children(Root::render_notification_layer(cx))
}
@ -56,7 +56,7 @@ impl Render for MyApp {
```
:::tip
Here the example we used `children` method, it because if there is no opened modals/drawers/notifications, these methods will return `None`, so GPUI will not render anything.
Here the example we used `children` method, it because if there is no opened dialogs, sheets, notifications, these methods will return `None`, so GPUI will not render anything.
:::
[Root]: https://docs.rs/gpui-component/latest/gpui_component/root/struct.Root.html

View file

@ -1,7 +1,7 @@
[package]
edition = "2021"
name = "modal_overlay"
description = "An example of using gpui-component to create a modal with overlay."
name = "dialog_overlay"
description = "An example of using gpui-component to create a Dialog with overlay."
publish = false
version = "0.4.0-preview1"

View file

@ -6,9 +6,9 @@ actions!(class_menu, [Open, Delete, Export, Info]);
pub struct HelloWorld;
impl HelloWorld {
fn show_modal(&mut self, _: &ClickEvent, window: &mut Window, cx: &mut Context<Self>) {
window.open_modal(cx, move |modal, _, _| {
modal.title("Test Modal").child("Hello from Modal!")
fn show_dialog(&mut self, _: &ClickEvent, window: &mut Window, cx: &mut Context<Self>) {
window.open_dialog(cx, move |dialog, _, _| {
dialog.title("Test dialog").child("Hello from dialog!")
});
}
@ -24,7 +24,7 @@ impl Render for HelloWorld {
div()
.bg(gpui::white())
.size_full()
.child(TitleBar::new().child("Modal & Drawer"))
.child(TitleBar::new().child("dialog & Drawer"))
.child(
div()
.p_8()
@ -37,8 +37,8 @@ impl Render for HelloWorld {
.child(
Button::new("btn1")
.outline()
.label("Open Modal")
.on_click(cx.listener(Self::show_modal)),
.label("Open dialog")
.on_click(cx.listener(Self::show_dialog)),
)
.child(
Button::new("btn2")
@ -72,7 +72,7 @@ impl Render for HelloWorld {
}),
),
)
.children(Root::render_modal_layer(window, cx))
.children(Root::render_dialog_layer(window, cx))
.children(Root::render_sheet_layer(window, cx))
}
}