Skip to main content

maud_ui/primitives/
resizable.rs

1//! Resizable component — draggable panels that resize horizontally or vertically.
2
3use maud::{html, Markup};
4
5/// A single resizable panel.
6#[derive(Clone, Debug)]
7pub struct Panel {
8    pub content: Markup,
9    pub default_size: f64,
10    pub min_size: Option<f64>,
11}
12
13/// Layout direction for the resizable group.
14#[derive(Clone, Debug)]
15pub enum Direction {
16    Horizontal,
17    Vertical,
18}
19
20impl Direction {
21    fn as_str(&self) -> &'static str {
22        match self {
23            Direction::Horizontal => "horizontal",
24            Direction::Vertical => "vertical",
25        }
26    }
27
28    fn aria_orientation(&self) -> &'static str {
29        match self {
30            Direction::Horizontal => "horizontal",
31            Direction::Vertical => "vertical",
32        }
33    }
34}
35
36/// Resizable group rendering properties.
37#[derive(Clone, Debug)]
38pub struct Props {
39    pub id: String,
40    pub panels: Vec<Panel>,
41    pub direction: Direction,
42}
43
44impl Default for Props {
45    fn default() -> Self {
46        Self {
47            id: "resizable".to_string(),
48            panels: vec![],
49            direction: Direction::Horizontal,
50        }
51    }
52}
53
54/// Render a resizable panel group with the given properties.
55pub fn render(props: Props) -> Markup {
56    let dir = props.direction.as_str();
57    let orientation = props.direction.aria_orientation();
58    let panel_count = props.panels.len();
59
60    html! {
61        div class=(format!("mui-resizable mui-resizable--{}", dir))
62            data-mui="resizable"
63            data-direction=(dir)
64            id=(props.id)
65        {
66            @for (i, panel) in props.panels.iter().enumerate() {
67                @let min = panel.min_size.unwrap_or(10.0);
68                div class="mui-resizable__panel"
69                    style=(format!("flex: {} 1 0%", panel.default_size))
70                    data-min-size=(format!("{}", min))
71                {
72                    (panel.content)
73                }
74                @if i < panel_count - 1 {
75                    div class="mui-resizable__handle"
76                        data-index=(format!("{}", i))
77                        tabindex="0"
78                        role="separator"
79                        aria-orientation=(orientation)
80                        aria-valuenow=(format!("{}", panel.default_size))
81                    {
82                        div class="mui-resizable__handle-bar" {}
83                    }
84                }
85            }
86        }
87    }
88}
89
90/// Showcase resizable panel examples.
91pub fn showcase() -> Markup {
92    html! {
93        div class="mui-showcase__grid" {
94            // Two horizontal panels — sidebar + content
95            div {
96                h3 class="mui-showcase__caption" { "Sidebar + Content" }
97                (render(Props {
98                    id: "demo-resize-h2".to_string(),
99                    direction: Direction::Horizontal,
100                    panels: vec![
101                        Panel {
102                            content: html! {
103                                div class="mui-resizable__demo-content" {
104                                    h4 { "Navigation" }
105                                    ul class="mui-resizable__demo-nav" {
106                                        li { a class="active" href="#" { "Dashboard" } }
107                                        li { a href="#" { "Projects" } }
108                                        li { a href="#" { "Team" } }
109                                        li { a href="#" { "Settings" } }
110                                        li { a href="#" { "Analytics" } }
111                                    }
112                                }
113                            },
114                            default_size: 30.0,
115                            min_size: Some(15.0),
116                        },
117                        Panel {
118                            content: html! {
119                                div class="mui-resizable__demo-content" {
120                                    h4 { "Dashboard" }
121                                    p { "Welcome back. Here is an overview of your recent activity, key metrics, and pending tasks. Drag the handle to resize the sidebar." }
122                                }
123                            },
124                            default_size: 70.0,
125                            min_size: Some(30.0),
126                        },
127                    ],
128                }))
129            }
130
131            // Three horizontal panels — file explorer layout
132            div {
133                h3 class="mui-showcase__caption" { "Three-column Layout" }
134                (render(Props {
135                    id: "demo-resize-h3".to_string(),
136                    direction: Direction::Horizontal,
137                    panels: vec![
138                        Panel {
139                            content: html! {
140                                div class="mui-resizable__demo-content" {
141                                    h4 { "Explorer" }
142                                    ul class="mui-resizable__demo-nav" {
143                                        li { a class="active" href="#" { "src/" } }
144                                        li { a href="#" { "tests/" } }
145                                        li { a href="#" { "docs/" } }
146                                        li { a href="#" { "Cargo.toml" } }
147                                    }
148                                }
149                            },
150                            default_size: 20.0,
151                            min_size: Some(10.0),
152                        },
153                        Panel {
154                            content: html! {
155                                div class="mui-resizable__demo-content" {
156                                    h4 { "Editor" }
157                                    p { "Select a file from the explorer to view its contents here. This center panel occupies the majority of the available space." }
158                                }
159                            },
160                            default_size: 55.0,
161                            min_size: Some(20.0),
162                        },
163                        Panel {
164                            content: html! {
165                                div class="mui-resizable__demo-content" {
166                                    h4 { "Inspector" }
167                                    p { "Properties and metadata for the selected item will appear in this panel." }
168                                }
169                            },
170                            default_size: 25.0,
171                            min_size: Some(10.0),
172                        },
173                    ],
174                }))
175            }
176        }
177    }
178}