orbital_core_components/stack/stack.rs
1use leptos::prelude::*;
2use orbital_base_components::BaseStack;
3use orbital_macros::component_doc;
4
5use super::types::StackConfig;
6
7/// One-dimensional layout with a consistent gap between every child.
8///
9/// Convenience wrapper over [`Flex`](crate::Flex) with even-gap, full-width defaults. Equivalent to `Flex(vertical=true, full_width=true)` with optional direction override. Set `config.horizontal=true` for a row direction.
10///
11/// Default presets: column direction, `FlexGap::Medium`, `full_width=true`, no justify override. Reach for [`Space`](crate::Space) to push children to opposite edges. Reach for [`Flex`](crate::Flex) when you need wrap, inline, fill, or inset padding.
12///
13/// # When to use
14///
15/// - Vertical form sections, settings blocks, and button rows - Even gaps between every child on one axis - Opinionated defaults over raw flex props
16///
17/// # Usage
18///
19/// 1. Pass `config=StackConfig::vertical(FlexGap::Medium)` or customize fields on `StackConfig`. 2. Set `config.horizontal=true` for row direction. 3. Tune `config.align` / `config.justify` for distribution.
20///
21/// # Best Practices
22///
23/// ## Do's
24///
25/// * Use `Stack` for simple vertical sections and button groups with even gaps * Reach for [`Space`](crate::Space) for opposite-edge distribution (space-between) * Reach for [`Flex`](crate::Flex) when you need wrap, inline, fill, or padding props
26///
27/// ## Don'ts
28///
29/// * Do not use `Stack` for two-dimensional page grids — prefer [`Grid`](crate::Grid)
30///
31/// # Examples
32///
33/// ## Horizontal cluster
34/// Default row direction spaces inline items with medium gap.
35/// <!-- preview -->
36/// ```rust
37/// use crate::{DemoBox, Stack, StackConfig, FlexGap};
38/// view! {
39/// <div data-testid="stack-preview">
40/// <Stack config=StackConfig::horizontal(FlexGap::Medium)>
41/// <DemoBox data_testid="stack-item-1">"One"</DemoBox>
42/// <DemoBox data_testid="stack-item-2">"Two"</DemoBox>
43/// </Stack>
44/// </div>
45/// }
46/// ```
47///
48/// ## Vertical stack
49/// Column direction stacks children with explicit medium gap.
50/// <!-- preview -->
51/// ```rust
52/// use crate::{DemoBox, Stack, StackConfig, FlexGap};
53/// view! {
54/// <div data-testid="stack-vertical">
55/// <Stack config=StackConfig::vertical(FlexGap::Medium)>
56/// <DemoBox data_testid="stack-v-1">"First"</DemoBox>
57/// <DemoBox data_testid="stack-v-2">"Second"</DemoBox>
58/// </Stack>
59/// </div>
60/// }
61/// ```
62///
63/// ## Gap preset matrix
64/// Compare small and large gap presets side by side.
65/// <!-- preview -->
66/// ```rust
67/// use crate::{DemoBox, Stack, StackConfig, FlexGap};
68/// view! {
69/// <div data-testid="stack-gap-matrix" style="width: 100%; max-width: 560px;">
70/// <Stack config=StackConfig { gap: FlexGap::Small, horizontal: true, ..Default::default() }>
71/// <DemoBox data_testid="stack-gap-small">"Small"</DemoBox>
72/// <DemoBox>"gap"</DemoBox>
73/// </Stack>
74/// <Stack config=StackConfig { gap: FlexGap::Large, horizontal: true, ..Default::default() }>
75/// <DemoBox data_testid="stack-gap-large">"Large"</DemoBox>
76/// <DemoBox>"gap"</DemoBox>
77/// </Stack>
78/// </div>
79/// }
80/// ```
81///
82/// ## Align center
83/// Cross-axis centering with mixed-height children in a bounded frame.
84/// <!-- preview -->
85/// ```rust
86/// use crate::{DemoBox, Stack, StackConfig, FlexGap, FlexAlign};
87/// view! {
88/// <div data-testid="stack-align" style="width: 100%; max-width: 560px; height: 120px; border: 1px solid var(--orb-color-border-default); padding: 8px;">
89/// <Stack config=StackConfig { gap: FlexGap::Medium, horizontal: true, align: Some(FlexAlign::Center), ..Default::default() }>
90/// <DemoBox height="32px">"Short"</DemoBox>
91/// <DemoBox height="72px" data_testid="stack-align-tall">"Tall"</DemoBox>
92/// </Stack>
93/// </div>
94/// }
95/// ```
96///
97/// ## Justify space-between
98/// Main-axis distribution pushes items to opposite edges.
99/// <!-- preview -->
100/// ```rust
101/// use crate::{DemoBox, Stack, StackConfig, FlexGap, FlexJustify};
102/// view! {
103/// <div data-testid="stack-justify" style="width: 100%; max-width: 560px;">
104/// <Stack config=StackConfig { gap: FlexGap::Medium, horizontal: true, justify: Some(FlexJustify::SpaceBetween), ..Default::default() }>
105/// <DemoBox data_testid="stack-justify-start">"Start"</DemoBox>
106/// <DemoBox data_testid="stack-justify-end">"End"</DemoBox>
107/// </Stack>
108/// </div>
109/// }
110/// ```
111///
112/// ## Mixed child types
113/// Buttons, text, and badges share the same stack gap rhythm.
114/// <!-- preview -->
115/// ```rust
116/// use crate::{Stack, StackConfig, FlexGap, Button, ButtonAppearance, Badge};
117/// view! {
118/// <div data-testid="stack-mixed">
119/// <Stack config=StackConfig::horizontal(FlexGap::Medium)>
120/// <Button appearance=ButtonAppearance::Primary>"Action"</Button>
121/// <span>"Status"</span>
122/// <Badge>"New"</Badge>
123/// </Stack>
124/// </div>
125/// }
126/// ```
127#[component_doc(
128 category = "Layout",
129 preview_slug = "stack",
130 preview_label = "Stack",
131 preview_icon = icondata::AiColumnHeightOutlined,
132)]
133#[component]
134pub fn Stack(
135 /// Gap, direction, and alignment settings for the stack.
136 #[prop(default = StackConfig::default())]
137 config: StackConfig,
138 /// Extra CSS class names merged onto the flex stack container.
139 #[prop(optional, into)]
140 class: MaybeProp<String>,
141 /// Flex item children laid out with even gaps between each sibling.
142 children: Children,
143) -> impl IntoView {
144 view! {
145 <BaseStack
146 class=class
147 gap=config.gap
148 horizontal=config.horizontal
149 align=MaybeProp::from(config.align)
150 justify=MaybeProp::from(config.justify)
151 >
152 {children()}
153 </BaseStack>
154 }
155}