diff --git a/.theme-schema.json b/.theme-schema.json index 122cba23..fab8a069 100644 --- a/.theme-schema.json +++ b/.theme-schema.json @@ -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 }, diff --git a/Cargo.lock b/Cargo.lock index 687428ff..cde69f64 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index c616c215..a705047f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ members = [ "examples/hello_world", "examples/input", "examples/window_title", - "examples/modal_overlay", + "examples/dialog_overlay", ] resolver = "2" diff --git a/crates/story/examples/dock.rs b/crates/story/examples/dock.rs index 0d0242f8..4e36d60c 100644 --- a/crates/story/examples/dock.rs +++ b/crates/story/examples/dock.rs @@ -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::(window, cx)), Arc::new(StoryContainer::panel::(window, cx)), Arc::new(StoryContainer::panel::(window, cx)), - Arc::new(StoryContainer::panel::(window, cx)), + Arc::new(StoryContainer::panel::(window, cx)), Arc::new(StoryContainer::panel::(window, cx)), Arc::new(StoryContainer::panel::(window, cx)), Arc::new(StoryContainer::panel::(window, cx)), @@ -435,7 +435,7 @@ impl StoryWorkspace { 1 => Arc::new(StoryContainer::panel::(window, cx)), 2 => Arc::new(StoryContainer::panel::(window, cx)), 3 => Arc::new(StoryContainer::panel::(window, cx)), - 4 => Arc::new(StoryContainer::panel::(window, cx)), + 4 => Arc::new(StoryContainer::panel::(window, cx)), 5 => Arc::new(StoryContainer::panel::(window, cx)), 6 => Arc::new(StoryContainer::panel::(window, cx)), 7 => Arc::new(StoryContainer::panel::(window, cx)), @@ -507,7 +507,7 @@ pub fn open_new( impl Render for StoryWorkspace { fn render(&mut self, window: &mut Window, cx: &mut Context) -> 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) } } diff --git a/crates/story/examples/editor.rs b/crates/story/examples/editor.rs index ff347d17..843ba47e 100644 --- a/crates/story/examples/editor.rs +++ b/crates/story/examples/editor.rs @@ -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() diff --git a/crates/story/examples/large-text.rs b/crates/story/examples/large-text.rs index 03ab7a82..102925d6 100644 --- a/crates/story/examples/large-text.rs +++ b/crates/story/examples/large-text.rs @@ -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() diff --git a/crates/story/examples/tiles.rs b/crates/story/examples/tiles.rs index 4d90f22a..dea66f4a 100644 --- a/crates/story/examples/tiles.rs +++ b/crates/story/examples/tiles.rs @@ -406,7 +406,7 @@ pub fn open_new( impl Render for StoryTiles { fn render(&mut self, window: &mut Window, cx: &mut Context) -> 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) } } diff --git a/crates/story/src/modal_story.rs b/crates/story/src/dialog_story.rs similarity index 74% rename from crates/story/src/modal_story.rs rename to crates/story/src/dialog_story.rs index 4afef210..94554c43 100644 --- a/crates/story/src/modal_story.rs +++ b/crates/story/src/dialog_story.rs @@ -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, input1: Entity, input2: Entity, date: Entity, select: Entity>>, - 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 { @@ -45,7 +45,7 @@ impl super::Story for ModalStory { } } -impl ModalStory { +impl DialogStory { pub fn view(window: &mut Window, cx: &mut App) -> Entity { cx.new(|cx| Self::new(window, cx)) } @@ -53,7 +53,7 @@ impl ModalStory { fn new(window: &mut Window, cx: &mut Context) -> 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) { - let overlay = self.modal_overlay; - let modal_show_close = self.model_show_close; + fn show_dialog(&mut self, window: &mut Window, cx: &mut Context) { + 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) -> 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.") }); })), ), diff --git a/crates/story/src/lib.rs b/crates/story/src/lib.rs index d685e999..d358930f 100644 --- a/crates/story/src/lib.rs +++ b/crates/story/src/lib.rs @@ -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) -> 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), diff --git a/crates/story/src/main.rs b/crates/story/src/main.rs index 413fb54d..411651a7 100644 --- a/crates/story/src/main.rs +++ b/crates/story/src/main.rs @@ -49,6 +49,7 @@ impl Gallery { StoryContainer::panel::(window, cx), StoryContainer::panel::(window, cx), StoryContainer::panel::(window, cx), + StoryContainer::panel::(window, cx), StoryContainer::panel::(window, cx), StoryContainer::panel::(window, cx), StoryContainer::panel::(window, cx), @@ -58,7 +59,6 @@ impl Gallery { StoryContainer::panel::(window, cx), StoryContainer::panel::(window, cx), StoryContainer::panel::(window, cx), - StoryContainer::panel::(window, cx), StoryContainer::panel::(window, cx), StoryContainer::panel::(window, cx), StoryContainer::panel::(window, cx), diff --git a/crates/story/src/sheet_story.rs b/crates/story/src/sheet_story.rs index 95ab4d8f..fc1d1b5e 100644 --- a/crates/story/src/sheet_story.rs +++ b/crates/story/src/sheet_story.rs @@ -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.", ), ), diff --git a/crates/ui/locales/ui.yml b/crates/ui/locales/ui.yml index 9f154a6a..1267ef45 100644 --- a/crates/ui/locales/ui.yml +++ b/crates/ui/locales/ui.yml @@ -138,7 +138,7 @@ Dock: zh-CN: 展开 zh-HK: 展開 it: Espandi -Modal: +Dialog: ok: en: OK zh-CN: 确定 diff --git a/crates/ui/src/modal.rs b/crates/ui/src/dialog.rs similarity index 86% rename from crates/ui/src/modal.rs rename to crates/ui/src/dialog.rs index 793a070a..9a9ee03b 100644 --- a/crates/ui/src/modal.rs +++ b/crates/ui/src/dialog.rs @@ -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 AnyElement>; type FooterFn = Box Vec>; -/// Modal button props. -pub struct ModalButtonProps { +/// Dialog button props. +pub struct DialogButtonProps { ok_text: Option, ok_variant: ButtonVariant, cancel_text: Option, 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) -> 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, footer: Option, @@ -86,13 +86,13 @@ pub struct Modal { on_close: Rc, on_ok: Option bool + 'static>>, on_cancel: Rc 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) { 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); } }) })) diff --git a/crates/ui/src/input/state.rs b/crates/ui/src/input/state.rs index 58545566..edaa80bc 100644 --- a/crates/ui/src/input/state.rs +++ b/crates/ui/src/input/state.rs @@ -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(); } diff --git a/crates/ui/src/lib.rs b/crates/ui/src/lib.rs index 5c53c338..05a65fde 100644 --- a/crates/ui/src/lib.rs +++ b/crates/ui/src/lib.rs @@ -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); diff --git a/crates/ui/src/popover.rs b/crates/ui/src/popover.rs index 133c7539..a23a325c 100644 --- a/crates/ui/src/popover.rs +++ b/crates/ui/src/popover.rs @@ -418,8 +418,8 @@ impl Element for Popover { .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() { diff --git a/crates/ui/src/root.rs b/crates/ui/src/root.rs index 3bda5e6c..f17d7470 100644 --- a/crates/ui/src/root.rs +++ b/crates/ui/src/root.rs @@ -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(&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(&mut self, cx: &mut App, build: F) + /// Opens a Dialog. + fn open_dialog(&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, cx: &mut App); @@ -114,21 +114,21 @@ impl WindowExt for Window { }) } - fn open_modal(&mut self, cx: &mut App, build: F) + fn open_dialog(&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, active_sheet: Option, - pub(crate) active_modals: Vec, + pub(crate) active_dialogs: Vec, pub(super) focused_input: Option>, pub notification: Entity, sheet_size: Option, @@ -229,9 +229,9 @@ struct ActiveSheet { } #[derive(Clone)] -pub(crate) struct ActiveModal { +pub(crate) struct ActiveDialog { focus_handle: FocusHandle, - builder: Rc Modal + 'static>, + builder: Rc 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 { + /// Render the Dialog layer. + pub fn render_dialog_layer(window: &mut Window, cx: &mut App) -> Option { let root = window.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::>(); 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. diff --git a/crates/ui/src/select.rs b/crates/ui/src/select.rs index 242f2da6..41b63528 100644 --- a/crates/ui/src/select.rs +++ b/crates/ui/src/select.rs @@ -685,7 +685,7 @@ where } fn enter(&mut self, _: &Confirm, window: &mut Window, cx: &mut Context) { - // 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 { diff --git a/crates/ui/src/sheet.rs b/crates/ui/src/sheet.rs index d6ebb326..ed287618 100644 --- a/crates/ui/src/sheet.rs +++ b/crates/ui/src/sheet.rs @@ -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 _, }; diff --git a/crates/ui/src/theme/mod.rs b/crates/ui/src/theme/mod.rs index f6e66fc4..cf2fe448 100644 --- a/crates/ui/src/theme/mod.rs +++ b/crates/ui/src/theme/mod.rs @@ -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, diff --git a/crates/ui/src/theme/schema.rs b/crates/ui/src/theme/schema.rs index a0185037..0b920757 100644 --- a/crates/ui/src/theme/schema.rs +++ b/crates/ui/src/theme/schema.rs @@ -45,7 +45,7 @@ pub struct ThemeConfig { /// The border radius for general elements, default is 6. #[serde(rename = "radius")] pub radius: Option, - /// 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, /// Set shadows in the theme, for example the Input and Button, default is true. diff --git a/crates/ui/tests/fixtures/layout.json b/crates/ui/tests/fixtures/layout.json index b95848fa..17d4807a 100644 --- a/crates/ui/tests/fixtures/layout.json +++ b/crates/ui/tests/fixtures/layout.json @@ -46,7 +46,7 @@ "children": [], "info": { "panel": { - "story_klass": "ModalStory" + "story_klass": "DialogStory" } } }, diff --git a/docs/docs/components/dialog.md b/docs/docs/components/dialog.md new file mode 100644 index 00000000..25c5ef3f --- /dev/null +++ b/docs/docs/components/dialog.md @@ -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) -> 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 + }) +}) +``` diff --git a/docs/docs/components/group-box.md b/docs/docs/components/group-box.md index 1890b9dd..50cbede3 100644 --- a/docs/docs/components/group-box.md +++ b/docs/docs/components/group-box.md @@ -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 diff --git a/docs/docs/components/index.md b/docs/docs/components/index.md index c5f5b1e3..ea6a9c4e 100644 --- a/docs/docs/components/index.md +++ b/docs/docs/components/index.md @@ -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 diff --git a/docs/docs/components/modal.md b/docs/docs/components/modal.md deleted file mode 100644 index 1d7f4514..00000000 --- a/docs/docs/components/modal.md +++ /dev/null @@ -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) -> 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 - }) -}) -``` diff --git a/docs/docs/getting-started.md b/docs/docs/getting-started.md index bc7b0a12..c11d51eb 100644 --- a/docs/docs/getting-started.md +++ b/docs/docs/getting-started.md @@ -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) diff --git a/docs/docs/root.md b/docs/docs/root.md index 44215658..87709780 100644 --- a/docs/docs/root.md +++ b/docs/docs/root.md @@ -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 diff --git a/examples/modal_overlay/Cargo.toml b/examples/dialog_overlay/Cargo.toml similarity index 64% rename from examples/modal_overlay/Cargo.toml rename to examples/dialog_overlay/Cargo.toml index b0869a45..0487a205 100644 --- a/examples/modal_overlay/Cargo.toml +++ b/examples/dialog_overlay/Cargo.toml @@ -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" diff --git a/examples/modal_overlay/src/main.rs b/examples/dialog_overlay/src/main.rs similarity index 89% rename from examples/modal_overlay/src/main.rs rename to examples/dialog_overlay/src/main.rs index 830cd26d..0c0a6399 100644 --- a/examples/modal_overlay/src/main.rs +++ b/examples/dialog_overlay/src/main.rs @@ -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) { - 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) { + 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)) } }