Skip to main content

orbital_base_components/
demo_box.rs

1//! Themed dashed-border box for layout demos and unimplemented primitive stubs.
2
3use leptos::{prelude::*, tachys::view::any_view::IntoAny};
4use turf::inline_style_sheet_values;
5
6use crate::{BorderRadius, SpacingInset, StrokeWidth, ThemeColor};
7
8/// A themed dashed-border box for preview demos and primitive stubs awaiting implementation.
9///
10/// Use inside layout previews to show container bounds, or as the body of a gap stub with `label=placeholder_label("ComponentName")`.
11#[component]
12pub fn DemoBox(
13    /// Optional label text when no children are provided.
14    #[prop(optional, into)]
15    label: MaybeProp<String>,
16    /// Extra CSS class names merged onto the root element.
17    #[prop(optional, into)]
18    class: MaybeProp<String>,
19    /// CSS width value (for example `280px` or `100%`). Ignored when `fill` is true.
20    #[prop(optional, into)]
21    width: MaybeProp<String>,
22    /// CSS height value (for example `160px` or `100%`). Ignored when `fill` is true.
23    #[prop(optional, into)]
24    height: MaybeProp<String>,
25    /// Theme-aware padding; defaults to medium inset on all sides.
26    #[prop(optional, into)]
27    padding: MaybeProp<SpacingInset>,
28    /// When true, sets `width: 100%; height: 100%` to fill the parent container.
29    #[prop(optional, default = false)]
30    fill: bool,
31    /// `data-testid` attribute for e2e tests.
32    #[prop(optional, into)]
33    data_testid: MaybeProp<String>,
34    #[prop(optional)] children: Option<Children>,
35) -> impl IntoView {
36    let (style_sheet, class_names) = inline_style_sheet_values! {
37        .Root {
38            box-sizing: border-box;
39        }
40    };
41
42    let merged_style = move || {
43        let mut parts = Vec::new();
44        if fill {
45            parts.push("width: 100%".to_string());
46            parts.push("height: 100%".to_string());
47        } else {
48            if let Some(w) = width.get() {
49                if !w.is_empty() {
50                    parts.push(format!("width: {w}"));
51                }
52            }
53            if let Some(h) = height.get() {
54                if !h.is_empty() {
55                    parts.push(format!("height: {h}"));
56                }
57            }
58        }
59        let pad = padding.get().unwrap_or(SpacingInset::all_m());
60        parts.push(pad.padding_css().trim_end_matches(';').to_string());
61        parts.push(format!(
62            "border: {} dashed {}",
63            StrokeWidth::Thin.css_var(),
64            ThemeColor::NeutralStroke1.css_var()
65        ));
66        parts.push(format!("border-radius: {}", BorderRadius::Medium.css_var()));
67        parts.push(format!(
68            "color: {}",
69            ThemeColor::NeutralForeground1.css_var()
70        ));
71        parts.join("; ")
72    };
73
74    view! {
75        <style>{style_sheet}</style>
76        <div
77            class=move || {
78                match class.get() {
79                    Some(extra) if !extra.trim().is_empty() => {
80                        format!("{} {}", class_names.root, extra)
81                    }
82                    _ => class_names.root.to_string(),
83                }
84            }
85            style=merged_style
86            data-testid=move || data_testid.get()
87        >
88            {if let Some(children) = children {
89                children().into_any()
90            } else if let Some(label_text) = label.get() {
91                view! { {label_text} }.into_any()
92            } else {
93                ().into_any()
94            }}
95        </div>
96    }
97}