aetna_core/widgets/
sheet.rs1use std::panic::Location;
8
9use crate::metrics::MetricsRole;
10use crate::style::StyleProfile;
11use crate::tokens;
12use crate::tree::*;
13use crate::widgets::overlay::{overlay, scrim};
14use crate::widgets::text::{h3, text};
15
16#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17#[non_exhaustive]
18pub enum SheetSide {
19 Left,
20 Right,
21 Top,
22 Bottom,
23}
24
25#[track_caller]
30pub fn sheet<I, E>(key: impl Into<String>, side: SheetSide, body: I) -> El
31where
32 I: IntoIterator<Item = E>,
33 E: Into<El>,
34{
35 let key = key.into();
36 let layer = overlay([
37 scrim(format!("{key}:dismiss")),
38 sheet_content(side, body).block_pointer(),
39 ]);
40
41 match side {
42 SheetSide::Left => layer.align(Align::Start).justify(Justify::Center),
43 SheetSide::Right => layer.align(Align::End).justify(Justify::Center),
44 SheetSide::Top => layer.align(Align::Center).justify(Justify::Start),
45 SheetSide::Bottom => layer.align(Align::Center).justify(Justify::End),
46 }
47}
48
49#[track_caller]
50pub fn sheet_content<I, E>(side: SheetSide, children: I) -> El
51where
52 I: IntoIterator<Item = E>,
53 E: Into<El>,
54{
55 let mut content = El::new(Kind::Custom("sheet_content"))
56 .at_loc(Location::caller())
57 .style_profile(StyleProfile::Surface)
58 .metrics_role(MetricsRole::Panel)
59 .surface_role(SurfaceRole::Popover)
60 .children(children)
61 .fill(tokens::POPOVER)
62 .stroke(tokens::BORDER)
63 .default_radius(0.0)
64 .shadow(tokens::SHADOW_LG)
65 .default_padding(tokens::SPACE_4)
66 .default_gap(tokens::SPACE_4)
67 .axis(Axis::Column)
68 .align(Align::Stretch)
69 .clip();
70
71 match side {
72 SheetSide::Left | SheetSide::Right => {
73 content.width = Size::Fixed(360.0);
74 content.height = Size::Fill(1.0);
75 }
76 SheetSide::Top | SheetSide::Bottom => {
77 content.width = Size::Fill(1.0);
78 content.height = Size::Hug;
79 }
80 }
81
82 content
83}
84
85#[track_caller]
86pub fn sheet_header<I, E>(children: I) -> El
87where
88 I: IntoIterator<Item = E>,
89 E: Into<El>,
90{
91 column(children)
92 .at_loc(Location::caller())
93 .width(Size::Fill(1.0))
94 .height(Size::Hug)
95 .gap(tokens::SPACE_1)
96}
97
98#[track_caller]
99pub fn sheet_footer<I, E>(children: I) -> El
100where
101 I: IntoIterator<Item = E>,
102 E: Into<El>,
103{
104 row(children)
105 .at_loc(Location::caller())
106 .width(Size::Fill(1.0))
107 .height(Size::Hug)
108 .gap(tokens::SPACE_2)
109 .align(Align::Center)
110 .justify(Justify::End)
111}
112
113#[track_caller]
114pub fn sheet_title(title: impl Into<String>) -> El {
115 h3(title)
116 .at_loc(Location::caller())
117 .line_height(tokens::TEXT_BASE.size)
118}
119
120#[track_caller]
121pub fn sheet_description(description: impl Into<String>) -> El {
122 text(description)
123 .at_loc(Location::caller())
124 .muted()
125 .wrap_text()
126 .width(Size::Fill(1.0))
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 #[test]
134 fn sheet_aligns_layer_by_side() {
135 let right = sheet("settings", SheetSide::Right, [sheet_title("Settings")]);
136 assert_eq!(right.align, Align::End);
137 assert_eq!(right.justify, Justify::Center);
138 assert_eq!(right.children[0].key.as_deref(), Some("settings:dismiss"));
139 assert!(right.children[1].block_pointer);
140
141 let bottom = sheet("activity", SheetSide::Bottom, [sheet_title("Activity")]);
142 assert_eq!(bottom.align, Align::Center);
143 assert_eq!(bottom.justify, Justify::End);
144 }
145
146 #[test]
147 fn vertical_sheets_fill_height_and_horizontal_sheets_fill_width() {
148 let side = sheet_content(SheetSide::Right, [sheet_title("Settings")]);
149 assert_eq!(side.width, Size::Fixed(360.0));
150 assert_eq!(side.height, Size::Fill(1.0));
151 assert_eq!(side.radius, crate::tree::Corners::ZERO);
152
153 let bottom = sheet_content(SheetSide::Bottom, [sheet_title("Activity")]);
154 assert_eq!(bottom.width, Size::Fill(1.0));
155 assert_eq!(bottom.height, Size::Hug);
156 }
157}