Skip to main content

maud_ui/primitives/
separator.rs

1//! Separator component — visual and semantic divider for organizing content.
2
3use maud::{html, Markup};
4
5/// Separator orientation — horizontal (default) or vertical
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum Orientation {
8    Horizontal,
9    Vertical,
10}
11
12impl Orientation {
13    fn class(&self) -> &'static str {
14        match self {
15            Self::Horizontal => "mui-separator--horizontal",
16            Self::Vertical => "mui-separator--vertical",
17        }
18    }
19
20    fn aria_orientation(&self) -> &'static str {
21        match self {
22            Self::Horizontal => "horizontal",
23            Self::Vertical => "vertical",
24        }
25    }
26}
27
28/// Separator rendering properties
29#[derive(Debug, Clone, Copy)]
30pub struct Props {
31    /// Orientation of the separator (default: Horizontal)
32    pub orientation: Orientation,
33    /// If true, render as purely decorative (aria-hidden). If false, semantic with role="separator"
34    pub decorative: bool,
35}
36
37impl Default for Props {
38    fn default() -> Self {
39        Self {
40            orientation: Orientation::Horizontal,
41            decorative: false,
42        }
43    }
44}
45
46/// Render a single separator with the given properties
47pub fn render(props: Props) -> Markup {
48    let class = format!("mui-separator {}", props.orientation.class());
49    html! {
50        @if props.decorative {
51            div class=(class) aria-hidden="true" {}
52        } @else {
53            div class=(class)
54                role="separator"
55                aria-orientation=(props.orientation.aria_orientation()) {}
56        }
57    }
58}
59
60/// Showcase all separator variants and use cases
61pub fn showcase() -> Markup {
62    html! {
63        div.mui-showcase__grid {
64            // Profile card — horizontal separator between Bio and Settings sections
65            section {
66                h2 { "Profile card sections" }
67                p.mui-showcase__caption { "Separator divides the Bio block from account Settings in a user profile." }
68                div style="border: 1px solid var(--mui-border); border-radius: 0.5rem; padding: 1.25rem; max-width: 24rem; background: var(--mui-card-bg, var(--mui-bg, transparent));" {
69                    div {
70                        div style="font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--mui-text-muted); margin-bottom: 0.25rem;" { "Bio" }
71                        div style="font-weight: 600; font-size: 1rem;" { "Henry Geldenhuys" }
72                        div style="font-size: 0.8125rem; color: var(--mui-text-muted); margin-top: 0.25rem;" {
73                            "Staff engineer at Kapable. Building Claude Conductor. Cape Town \u{2192} Remote."
74                        }
75                    }
76                    div style="margin: 1rem 0;" {
77                        (render(Props { orientation: Orientation::Horizontal, decorative: true }))
78                    }
79                    div {
80                        div style="font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--mui-text-muted); margin-bottom: 0.25rem;" { "Settings" }
81                        div style="display: flex; justify-content: space-between; font-size: 0.875rem; padding: 0.25rem 0;" {
82                            span { "Email" }
83                            span style="color: var(--mui-text-muted);" { "invoice@geldentech.ca" }
84                        }
85                        div style="display: flex; justify-content: space-between; font-size: 0.875rem; padding: 0.25rem 0;" {
86                            span { "Two-factor" }
87                            span style="color: var(--mui-text-muted);" { "Enabled" }
88                        }
89                    }
90                }
91            }
92
93            // "OR" divider — between Google sign-in and email form
94            section {
95                h2 { "Auth methods" }
96                p.mui-showcase__caption { "\u{201c}OR\u{201d} label separates social sign-in from email + password." }
97                div style="display: flex; flex-direction: column; gap: 0.75rem; max-width: 22rem;" {
98                    button class="mui-btn mui-btn--outline mui-btn--md" style="width: 100%;" {
99                        span aria-hidden="true" style="margin-right: 0.5rem;" { "G" }
100                        "Sign in with Google"
101                    }
102                    div style="display: flex; align-items: center; gap: 0.75rem; margin: 0.25rem 0;" {
103                        div style="flex: 1;" {
104                            (render(Props { orientation: Orientation::Horizontal, decorative: true }))
105                        }
106                        span style="font-size: 0.75rem; font-weight: 500; text-transform: uppercase; color: var(--mui-text-muted); letter-spacing: 0.08em;" { "OR" }
107                        div style="flex: 1;" {
108                            (render(Props { orientation: Orientation::Horizontal, decorative: true }))
109                        }
110                    }
111                    div class="mui-field" {
112                        input type="email" placeholder="you@company.com" class="mui-input" style="width: 100%;";
113                    }
114                    div class="mui-field" {
115                        input type="password" placeholder="Password" class="mui-input" style="width: 100%;";
116                    }
117                    button class="mui-btn mui-btn--primary mui-btn--md" type="submit" { "Sign in" }
118                }
119            }
120
121            // Vertical separator inside a nav bar
122            section {
123                h2 { "Navigation" }
124                p.mui-showcase__caption { "Vertical separator between primary nav and user menu." }
125                nav style="display: flex; align-items: center; gap: 1rem; padding: 0.5rem 0.75rem; border: 1px solid var(--mui-border); border-radius: 0.5rem; height: 2.75rem;" {
126                    a href="#" style="font-weight: 500; text-decoration: none; color: inherit;" { "Dashboard" }
127                    a href="#" style="color: var(--mui-text-muted); text-decoration: none;" { "Projects" }
128                    a href="#" style="color: var(--mui-text-muted); text-decoration: none;" { "Billing" }
129                    a href="#" style="color: var(--mui-text-muted); text-decoration: none;" { "Settings" }
130                    div style="flex: 1;" {}
131                    (render(Props {
132                        orientation: Orientation::Vertical,
133                        ..Default::default()
134                    }))
135                    a href="#" style="color: var(--mui-text-muted); text-decoration: none; font-size: 0.875rem;" { "Docs" }
136                    a href="#" style="font-weight: 500; text-decoration: none; color: inherit;" { "HG" }
137                }
138            }
139        }
140    }
141}