modal: Add scrollbar to Modal for overflow contents. (#960)
- Add `overlay` variable to Theme. <img width="536" alt="image" src="https://github.com/user-attachments/assets/12ed43c8-00b5-41b1-8cf6-0f31b533525c" />
This commit is contained in:
parent
61283a71f0
commit
d2633faf82
4 changed files with 89 additions and 50 deletions
|
|
@ -12,6 +12,7 @@ use gpui_component::{
|
|||
h_flex,
|
||||
input::{InputState, TextInput},
|
||||
modal::ModalButtonProps,
|
||||
text::TextView,
|
||||
v_flex, ContextModal as _,
|
||||
};
|
||||
|
||||
|
|
@ -184,6 +185,9 @@ impl Focusable for ModalStory {
|
|||
|
||||
impl Render for ModalStory {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let modal_overlay = self.modal_overlay;
|
||||
let overlay_closable = self.overlay_closable;
|
||||
|
||||
div()
|
||||
.id("modal-story")
|
||||
.track_focus(&self.focus_handle)
|
||||
|
|
@ -270,10 +274,12 @@ impl Render for ModalStory {
|
|||
Button::new("confirm-modal0")
|
||||
.primary()
|
||||
.label("Submit")
|
||||
.on_click(cx.listener(|_, _, window, cx| {
|
||||
window.open_modal(cx, |modal, _, _| {
|
||||
.on_click(cx.listener(move |_, _, window, cx| {
|
||||
window.open_modal(cx, move |modal, _, _| {
|
||||
modal
|
||||
.confirm()
|
||||
.overlay(modal_overlay)
|
||||
.overlay_closable(overlay_closable)
|
||||
.child("Are you sure to submit?")
|
||||
.on_ok(|_, window, cx| {
|
||||
window
|
||||
|
|
@ -296,10 +302,14 @@ impl Render for ModalStory {
|
|||
Button::new("confirm-modal1")
|
||||
.danger()
|
||||
.label("Delete Item")
|
||||
.on_click(cx.listener(|_, _, window, cx| {
|
||||
window.open_modal(cx, |modal, _, _| {
|
||||
.on_click(cx.listener(move |_, _, window, cx| {
|
||||
window.open_modal(cx, move |modal, _, _| {
|
||||
modal
|
||||
.rounded_lg()
|
||||
.p_3()
|
||||
.confirm()
|
||||
.overlay(modal_overlay)
|
||||
.overlay_closable(overlay_closable)
|
||||
.child("Are you sure to delete this item?")
|
||||
.button_props(
|
||||
ModalButtonProps::default()
|
||||
|
|
@ -330,10 +340,12 @@ impl Render for ModalStory {
|
|||
section("Alert Modal").child(
|
||||
Button::new("alert-modal")
|
||||
.label("Alert")
|
||||
.on_click(cx.listener(|_, _, window, cx| {
|
||||
window.open_modal(cx, |modal, _, _| {
|
||||
.on_click(cx.listener(move |_, _, window, cx| {
|
||||
window.open_modal(cx, move |modal, _, _| {
|
||||
modal
|
||||
.confirm()
|
||||
.overlay(modal_overlay)
|
||||
.overlay_closable(overlay_closable)
|
||||
.child("You are successfully logged in.")
|
||||
.alert()
|
||||
.on_close(|_, window, cx| {
|
||||
|
|
@ -343,6 +355,25 @@ impl Render for ModalStory {
|
|||
});
|
||||
})),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
section("Scrollable Modal").child(
|
||||
Button::new("scrollable-modal")
|
||||
.label("Scrollable Modal")
|
||||
.on_click(cx.listener(move |_, _, window, cx| {
|
||||
window.open_modal(cx, move |modal, _, _| {
|
||||
modal
|
||||
.h(px(450.))
|
||||
.overlay(modal_overlay)
|
||||
.overlay_closable(overlay_closable)
|
||||
.title("Modal with scrollbar")
|
||||
.child(TextView::markdown(
|
||||
"markdown1",
|
||||
include_str!("../../../README.md"),
|
||||
))
|
||||
});
|
||||
})),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ impl RenderOnce for Drawer {
|
|||
.occlude()
|
||||
.w(size.width)
|
||||
.h(size.height - titlebar_height)
|
||||
.bg(overlay_color(self.overlay, window, cx))
|
||||
.bg(overlay_color(self.overlay, cx))
|
||||
.when(self.overlay, |this| {
|
||||
this.on_mouse_down(MouseButton::Left, {
|
||||
let on_close = self.on_close.clone();
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ use std::{rc::Rc, time::Duration};
|
|||
use gpui::{
|
||||
anchored, div, hsla, point, prelude::FluentBuilder, px, relative, Animation, AnimationExt as _,
|
||||
AnyElement, App, Bounds, ClickEvent, Div, FocusHandle, Hsla, InteractiveElement, IntoElement,
|
||||
KeyBinding, MouseButton, ParentElement, Pixels, Point, RenderOnce, SharedString, Styled,
|
||||
Window,
|
||||
KeyBinding, MouseButton, ParentElement, Pixels, Point, RenderOnce, SharedString,
|
||||
StyleRefinement, Styled, Window,
|
||||
};
|
||||
use rust_i18n::t;
|
||||
|
||||
|
|
@ -12,7 +12,9 @@ use crate::{
|
|||
actions::{Cancel, Confirm},
|
||||
animation::cubic_bezier,
|
||||
button::{Button, ButtonVariant, ButtonVariants as _},
|
||||
h_flex, v_flex, ActiveTheme as _, ContextModal, IconName, Root, Sizable as _, StyledExt,
|
||||
h_flex,
|
||||
scroll::ScrollbarAxis,
|
||||
v_flex, ActiveTheme as _, ContextModal, IconName, Root, Sizable as _, StyledExt,
|
||||
};
|
||||
|
||||
const CONTEXT: &str = "Modal";
|
||||
|
|
@ -74,7 +76,7 @@ impl ModalButtonProps {
|
|||
|
||||
#[derive(IntoElement)]
|
||||
pub struct Modal {
|
||||
base: Div,
|
||||
style: StyleRefinement,
|
||||
title: Option<AnyElement>,
|
||||
footer: Option<FooterFn>,
|
||||
content: Div,
|
||||
|
|
@ -97,35 +99,19 @@ pub struct Modal {
|
|||
pub(crate) overlay_visible: bool,
|
||||
}
|
||||
|
||||
pub(crate) fn overlay_color(overlay: bool, _: &Window, cx: &App) -> Hsla {
|
||||
pub(crate) fn overlay_color(overlay: bool, cx: &App) -> Hsla {
|
||||
if !overlay {
|
||||
return hsla(0., 0., 0., 0.);
|
||||
}
|
||||
|
||||
if cx.theme().mode.is_dark() {
|
||||
hsla(0., 1., 1., 0.06)
|
||||
} else {
|
||||
hsla(0., 0., 0., 0.06)
|
||||
}
|
||||
cx.theme().overlay
|
||||
}
|
||||
|
||||
impl Modal {
|
||||
pub fn new(_: &mut Window, cx: &mut App) -> Self {
|
||||
let radius = (cx.theme().radius * 2.).min(px(20.));
|
||||
|
||||
let base = v_flex()
|
||||
.bg(cx.theme().background)
|
||||
.border_1()
|
||||
.border_color(cx.theme().border)
|
||||
.rounded(radius)
|
||||
.shadow_xl()
|
||||
.min_h_24()
|
||||
.p_4()
|
||||
.gap_4();
|
||||
|
||||
Self {
|
||||
base,
|
||||
focus_handle: cx.focus_handle(),
|
||||
style: StyleRefinement::default(),
|
||||
title: None,
|
||||
footer: None,
|
||||
content: v_flex(),
|
||||
|
|
@ -287,7 +273,7 @@ impl ParentElement for Modal {
|
|||
|
||||
impl Styled for Modal {
|
||||
fn style(&mut self) -> &mut gpui::StyleRefinement {
|
||||
self.base.style()
|
||||
&mut self.style
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -369,16 +355,18 @@ impl RenderOnce for Modal {
|
|||
let offset_top = px(layer_ix as f32 * 16.);
|
||||
let y = self.margin_top.unwrap_or(view_size.height / 10.) + offset_top;
|
||||
let x = bounds.center().x - self.width / 2.;
|
||||
let border_radius = (cx.theme().radius * 2.).min(px(20.));
|
||||
|
||||
anchored()
|
||||
.position(point(window_paddings.left, window_paddings.top))
|
||||
.snap_to_window()
|
||||
.child(
|
||||
div()
|
||||
.id("modal")
|
||||
.w(view_size.width)
|
||||
.h(view_size.height)
|
||||
.when(self.overlay_visible, |this| {
|
||||
this.occlude().bg(overlay_color(self.overlay, window, cx))
|
||||
this.occlude().bg(overlay_color(self.overlay, cx))
|
||||
})
|
||||
.when(self.overlay_closable, |this| {
|
||||
// Only the last modal owns the `mouse down - close modal` event.
|
||||
|
|
@ -397,8 +385,17 @@ impl RenderOnce for Modal {
|
|||
})
|
||||
})
|
||||
.child(
|
||||
self.base
|
||||
.id(SharedString::from(format!("modal-{layer_ix}")))
|
||||
v_flex()
|
||||
.id(layer_ix)
|
||||
.bg(cx.theme().background)
|
||||
.border_1()
|
||||
.border_color(cx.theme().border)
|
||||
.rounded(border_radius)
|
||||
.shadow_xl()
|
||||
.min_h_24()
|
||||
.py_4()
|
||||
.gap_4()
|
||||
.refine_style(&self.style)
|
||||
.key_context(CONTEXT)
|
||||
.track_focus(&self.focus_handle)
|
||||
.when(self.keyboard, |this| {
|
||||
|
|
@ -440,34 +437,41 @@ impl RenderOnce for Modal {
|
|||
.when_some(self.max_width, |this, w| this.max_w(w))
|
||||
.when_some(self.title, |this, title| {
|
||||
this.child(
|
||||
div().font_semibold().line_height(relative(1.)).child(title),
|
||||
div()
|
||||
.font_semibold()
|
||||
.px_4()
|
||||
.line_height(relative(1.))
|
||||
.child(title),
|
||||
)
|
||||
})
|
||||
.when(self.show_close, |this| {
|
||||
this.child(
|
||||
Button::new(SharedString::from(format!(
|
||||
"modal-close-{layer_ix}"
|
||||
)))
|
||||
.absolute()
|
||||
.top_2()
|
||||
.right_2()
|
||||
.small()
|
||||
.ghost()
|
||||
.icon(IconName::Close)
|
||||
.on_click(
|
||||
move |_, window, cx| {
|
||||
Button::new("close")
|
||||
.absolute()
|
||||
.top_2()
|
||||
.right_2()
|
||||
.small()
|
||||
.ghost()
|
||||
.icon(IconName::Close)
|
||||
.on_click(move |_, window, cx| {
|
||||
on_cancel(&ClickEvent::default(), window, cx);
|
||||
on_close(&ClickEvent::default(), window, cx);
|
||||
window.close_modal(cx);
|
||||
},
|
||||
),
|
||||
}),
|
||||
)
|
||||
})
|
||||
.child(div().w_full().flex_1().child(self.content))
|
||||
.child(
|
||||
div().w_full().flex_1().overflow_hidden().child(
|
||||
v_flex()
|
||||
.scrollable(window.current_view(), ScrollbarAxis::Vertical)
|
||||
.px_4()
|
||||
.child(self.content),
|
||||
),
|
||||
)
|
||||
.when(self.footer.is_some(), |this| {
|
||||
let footer = self.footer.unwrap();
|
||||
|
||||
this.child(h_flex().gap_2().justify_end().children(footer(
|
||||
this.child(h_flex().px_4().gap_2().justify_end().children(footer(
|
||||
render_ok,
|
||||
render_cancel,
|
||||
window,
|
||||
|
|
|
|||
|
|
@ -243,6 +243,8 @@ pub struct ThemeColor {
|
|||
pub warning_hover: Hsla,
|
||||
/// Warning foreground color.
|
||||
pub warning_foreground: Hsla,
|
||||
/// Overlay background color.
|
||||
pub overlay: Hsla,
|
||||
/// Window border color.
|
||||
///
|
||||
/// # Platform specific:
|
||||
|
|
@ -346,6 +348,7 @@ impl ThemeColor {
|
|||
warning_active: crate::yellow_600(),
|
||||
warning_hover: crate::yellow_500().opacity(0.9),
|
||||
warning_foreground: crate::gray_50(),
|
||||
overlay: hsl(0., 0., 0.).opacity(0.06),
|
||||
window_border: hsl(240.0, 5.9, 78.0),
|
||||
}
|
||||
}
|
||||
|
|
@ -444,6 +447,7 @@ impl ThemeColor {
|
|||
warning_active: crate::yellow_800().darken(0.2),
|
||||
warning_foreground: crate::yellow_50(),
|
||||
warning_hover: crate::yellow_800().opacity(0.9),
|
||||
overlay: hsl(0., 100., 100.).opacity(0.06),
|
||||
window_border: hsl(240.0, 3.7, 28.0),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue