Skip to main content

logicaffeine_web/ui/components/
page_layout.rs

1//! Page layout wrapper component.
2//!
3//! Provides consistent structure for pages with:
4//! - MainNav header with configurable options
5//! - Responsive footer (optional, based on variant)
6//! - Proper min-height for full viewport coverage
7//!
8//! # Variants
9//! - `Standard`: Full header + full footer (most pages)
10//! - `Minimal`: Full header + minimal footer (legal pages)
11//! - `NoFooter`: Full header + no footer (Studio, Workspace)
12//! - `Landing`: Landing page variant with custom footer handling
13
14use dioxus::prelude::*;
15use crate::ui::components::main_nav::{MainNav, ActivePage};
16use crate::ui::components::footer::{Footer, FooterVariant};
17
18const PAGE_LAYOUT_STYLES: &str = r#"
19.page-layout {
20    display: flex;
21    flex-direction: column;
22    min-height: 100vh;
23    background: var(--bg-dark, #060814);
24}
25
26.page-layout-content {
27    flex: 1;
28    display: flex;
29    flex-direction: column;
30}
31
32/* For full-height app pages (Studio, Workspace) that need no footer */
33.page-layout.no-footer .page-layout-content {
34    flex: 1;
35    overflow: hidden;
36}
37"#;
38
39/// Layout variant determining header/footer configuration
40#[derive(Clone, Copy, PartialEq, Default)]
41pub enum LayoutVariant {
42    #[default]
43    Standard,
44    Minimal,
45    NoFooter,
46    Landing,
47}
48
49#[component]
50pub fn PageLayout(
51    #[props(default)]
52    active_page: ActivePage,
53    #[props(default)]
54    subtitle: Option<&'static str>,
55    #[props(default = true)]
56    show_nav_links: bool,
57    #[props(default)]
58    variant: LayoutVariant,
59    children: Element,
60) -> Element {
61    let layout_class = match variant {
62        LayoutVariant::NoFooter => "page-layout no-footer",
63        _ => "page-layout",
64    };
65
66    rsx! {
67        style { "{PAGE_LAYOUT_STYLES}" }
68        div { class: layout_class,
69            MainNav {
70                active: active_page,
71                subtitle: subtitle,
72                show_nav_links: show_nav_links,
73            }
74
75            main { class: "page-layout-content",
76                {children}
77            }
78
79            // Render footer based on variant
80            match variant {
81                LayoutVariant::Standard => rsx! { Footer { variant: FooterVariant::Full } },
82                LayoutVariant::Minimal => rsx! { Footer { variant: FooterVariant::Minimal } },
83                LayoutVariant::NoFooter => rsx! {},
84                LayoutVariant::Landing => rsx! {}, // Landing has its own custom footer
85            }
86        }
87    }
88}