1use crate::{ModalFooter, ModalHeader, prelude::*};
2use gpui::{AnyElement, ScrollHandle};
3use smallvec::SmallVec;
4
5pub const DRAWER_WIDTH: Pixels = px(384.);
7
8pub const DRAWER_HEIGHT: Pixels = px(384.);
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
13pub enum SheetSide {
14 Top,
15 #[default]
16 Right,
17 Bottom,
18 Left,
19}
20
21#[derive(IntoElement, RegisterComponent)]
30pub struct Drawer {
31 id: ElementId,
32 body_id: ElementId,
33 header: ModalHeader,
34 children: SmallVec<[AnyElement; 2]>,
35 footer: Option<ModalFooter>,
36 width: Pixels,
37 height: Pixels,
38 side: SheetSide,
39 container_scroll_handle: Option<ScrollHandle>,
40 animate: bool,
41}
42
43impl Drawer {
44 pub fn new(id: impl Into<SharedString>) -> Self {
45 let id = id.into();
46 let body_id = ElementId::Name(format!("{}_body", id).into());
47
48 Self {
49 id: ElementId::Name(id),
50 body_id,
51 header: ModalHeader::new(),
52 children: SmallVec::new(),
53 footer: None,
54 width: DRAWER_WIDTH,
55 height: DRAWER_HEIGHT,
56 side: SheetSide::default(),
57 container_scroll_handle: None,
58 animate: true,
59 }
60 }
61
62 pub fn side(mut self, side: SheetSide) -> Self {
64 self.side = side;
65 self
66 }
67
68 pub fn header(mut self, header: ModalHeader) -> Self {
69 self.header = header;
70 self
71 }
72
73 pub fn footer(mut self, footer: ModalFooter) -> Self {
74 self.footer = Some(footer);
75 self
76 }
77
78 pub fn width(mut self, width: Pixels) -> Self {
80 self.width = width;
81 self
82 }
83
84 pub fn height(mut self, height: Pixels) -> Self {
86 self.height = height;
87 self
88 }
89
90 pub fn show_dismiss(mut self, show: bool) -> Self {
91 self.header = self.header.show_dismiss_button(show);
92 self
93 }
94
95 pub fn scroll_handle(mut self, handle: ScrollHandle) -> Self {
96 self.container_scroll_handle = Some(handle);
97 self
98 }
99
100 pub fn animate(mut self, animate: bool) -> Self {
102 self.animate = animate;
103 self
104 }
105}
106
107impl ParentElement for Drawer {
108 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
109 self.children.extend(elements)
110 }
111}
112
113impl RenderOnce for Drawer {
114 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
115 let border_color = semantic::border(cx);
116 let mut panel = v_flex()
117 .id(self.id.clone())
118 .absolute()
119 .bg(semantic::elevated_surface(cx))
120 .border_color(border_color)
121 .shadow_level(Shadow::Xl)
122 .overflow_hidden();
123
124 panel = match self.side {
125 SheetSide::Right => panel.top_0().right_0().h_full().w(self.width).border_l_1(),
126 SheetSide::Left => panel.top_0().left_0().h_full().w(self.width).border_r_1(),
127 SheetSide::Top => panel.top_0().left_0().w_full().h(self.height).border_b_1(),
128 SheetSide::Bottom => panel
129 .bottom_0()
130 .left_0()
131 .w_full()
132 .h(self.height)
133 .border_t_1(),
134 };
135
136 panel = panel
137 .child(self.header)
138 .child(
139 v_flex()
140 .id(self.body_id)
141 .flex_1()
142 .w_full()
143 .p(DynamicSpacing::Base24.rems(cx))
144 .gap(DynamicSpacing::Base08.rems(cx))
145 .when_some(self.container_scroll_handle, |this, handle| {
146 this.overflow_y_scroll().track_scroll(&handle)
147 })
148 .children(self.children),
149 )
150 .children(self.footer);
151
152 if self.animate {
153 match self.side {
154 SheetSide::Right => panel.animate_in_from_right(false).into_any_element(),
155 SheetSide::Left => panel.animate_in_from_left(false).into_any_element(),
156 SheetSide::Top => panel.animate_in_from_top(false).into_any_element(),
157 SheetSide::Bottom => panel.animate_in_from_bottom(false).into_any_element(),
158 }
159 } else {
160 panel.into_any_element()
161 }
162 }
163}
164
165impl Component for Drawer {
166 fn scope() -> ComponentScope {
167 ComponentScope::Overlays
168 }
169
170 fn description() -> Option<&'static str> {
171 Some(
172 "A side panel (slide-over) anchored to the right edge, for detail views or forms that shouldn't fully replace the current context.",
173 )
174 }
175
176 fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
177 Some(
178 example_group(vec![single_example(
179 "Basic",
180 div()
181 .relative()
182 .w(px(480.))
183 .h(px(320.))
184 .overflow_hidden()
185 .child(
186 Drawer::new("drawer-preview")
187 .animate(false)
188 .header(
189 ModalHeader::new()
190 .headline("Drawer title")
191 .show_dismiss_button(true),
192 )
193 .child(Label::new("Drawer body content goes here."))
194 .footer(
195 ModalFooter::new()
196 .end_slot(Button::new("drawer-confirm", "Save").primary()),
197 ),
198 )
199 .into_any_element(),
200 )])
201 .into_any_element(),
202 )
203 }
204}