1use crate::{ModalFooter, ModalHeader, prelude::*};
2use gpui::{AnyElement, ScrollHandle};
3use smallvec::SmallVec;
4
5pub const DRAWER_WIDTH: Pixels = px(384.);
7
8#[derive(IntoElement, RegisterComponent)]
17pub struct Drawer {
18 id: ElementId,
19 body_id: ElementId,
20 header: ModalHeader,
21 children: SmallVec<[AnyElement; 2]>,
22 footer: Option<ModalFooter>,
23 width: Pixels,
24 container_scroll_handle: Option<ScrollHandle>,
25 animate: bool,
26}
27
28impl Drawer {
29 pub fn new(id: impl Into<SharedString>) -> Self {
30 let id = id.into();
31 let body_id = ElementId::Name(format!("{}_body", id).into());
32
33 Self {
34 id: ElementId::Name(id),
35 body_id,
36 header: ModalHeader::new(),
37 children: SmallVec::new(),
38 footer: None,
39 width: DRAWER_WIDTH,
40 container_scroll_handle: None,
41 animate: true,
42 }
43 }
44
45 pub fn header(mut self, header: ModalHeader) -> Self {
46 self.header = header;
47 self
48 }
49
50 pub fn footer(mut self, footer: ModalFooter) -> Self {
51 self.footer = Some(footer);
52 self
53 }
54
55 pub fn width(mut self, width: Pixels) -> Self {
57 self.width = width;
58 self
59 }
60
61 pub fn show_dismiss(mut self, show: bool) -> Self {
62 self.header = self.header.show_dismiss_button(show);
63 self
64 }
65
66 pub fn scroll_handle(mut self, handle: ScrollHandle) -> Self {
67 self.container_scroll_handle = Some(handle);
68 self
69 }
70
71 pub fn animate(mut self, animate: bool) -> Self {
73 self.animate = animate;
74 self
75 }
76}
77
78impl ParentElement for Drawer {
79 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
80 self.children.extend(elements)
81 }
82}
83
84impl RenderOnce for Drawer {
85 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
86 let panel = v_flex()
87 .id(self.id.clone())
88 .absolute()
89 .top_0()
90 .right_0()
91 .h_full()
92 .w(self.width)
93 .bg(semantic::elevated_surface(cx))
94 .border_l_1()
95 .border_color(semantic::border(cx))
96 .shadow_level(Shadow::Xl)
97 .overflow_hidden()
98 .child(self.header)
99 .child(
100 v_flex()
101 .id(self.body_id)
102 .flex_1()
103 .w_full()
104 .p(DynamicSpacing::Base24.rems(cx))
105 .gap(DynamicSpacing::Base08.rems(cx))
106 .when_some(self.container_scroll_handle, |this, handle| {
107 this.overflow_y_scroll().track_scroll(&handle)
108 })
109 .children(self.children),
110 )
111 .children(self.footer);
112
113 if self.animate {
114 panel.animate_in_from_right(false).into_any_element()
115 } else {
116 panel.into_any_element()
117 }
118 }
119}
120
121impl Component for Drawer {
122 fn scope() -> ComponentScope {
123 ComponentScope::Overlays
124 }
125
126 fn description() -> Option<&'static str> {
127 Some(
128 "A side panel (slide-over) anchored to the right edge, for detail views or forms that shouldn't fully replace the current context.",
129 )
130 }
131
132 fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
133 Some(
134 example_group(vec![single_example(
135 "Basic",
136 div()
137 .relative()
138 .w(px(480.))
139 .h(px(320.))
140 .overflow_hidden()
141 .child(
142 Drawer::new("drawer-preview")
143 .animate(false)
144 .header(
145 ModalHeader::new()
146 .headline("Drawer title")
147 .show_dismiss_button(true),
148 )
149 .child(Label::new("Drawer body content goes here."))
150 .footer(
151 ModalFooter::new()
152 .end_slot(Button::new("drawer-confirm", "Save").primary()),
153 ),
154 )
155 .into_any_element(),
156 )])
157 .into_any_element(),
158 )
159 }
160}