Skip to main content

orbital_core_components/flex/
flex.rs

1use leptos::prelude::*;
2use orbital_base_components::BaseFlex;
3use orbital_macros::component_doc;
4
5pub use orbital_base_components::{FlexAlign, FlexGap, FlexJustify, FlexWrap, SpacingInset};
6
7/// Flexbox layout container for arranging children in a row or column.
8///
9/// The canonical one-dimensional layout primitive. [`Stack`](crate::Stack) and [`Space`](crate::Space) are convenience wrappers with opinionated defaults.
10///
11/// Orbital-only props beyond basic flex direction and gap: **`wrap`**, **`fill`** (height 100%), **`full_width`**, and token-based **`padding`** / **`margin`** via [`SpacingInset`].
12///
13/// # When to use
14///
15/// - Full control over direction, wrap, inline, fill, and inset padding - Toolbars and form rows that need alignment along both axes - Inline clusters beside text or other inline content (`inline=true`) - When [`Stack`](crate::Stack) (even-gap column) or [`Space`](crate::Space) (space-between) defaults do not fit
16///
17/// # Usage
18///
19/// 1. Choose direction: default row, or `vertical=true` for a column stack. 2. Set `gap` to a [`FlexGap`] preset or custom size — avoid margin hacks. 3. Tune `align` (cross-axis) and `justify` (main-axis) for centering or distribution. 4. Set `inline=true` when the flex container should sit in flowing text or inline UI.
20///
21/// # Best Practices
22///
23/// ## Do's
24///
25/// * Use `gap` instead of margin hacks between items * Set `vertical` for stacked form fields or list actions * Pair with `align` / `justify` for centering and distribution * Use `FlexGap::Size` or `FlexGap::WH` when presets do not match your spacing rhythm * Put borders, fixed heights, and backgrounds on a native wrapper `div`; use `Flex` for direction, gap, and alignment * Use `wrap`, `fill`, and `full_width` props instead of inline flex CSS on `Flex` * Use [`SpacingInset`] with [`SpacingHorizontal`] / [`SpacingVertical`] for theme-aware padding and margin * Prefer [`Stack`](crate::Stack) for simple even-gap vertical sections — `Stack` defaults to column + full-width * Prefer [`Space`](crate::Space) for opposite-edge distribution — `Space` defaults to space-between + full-width
26///
27/// ## Don'ts
28///
29/// * Do not use Flex for two-dimensional page grids — prefer [`Grid`](crate::Grid) * Avoid nesting many Flex containers when a single grid suffices * Do not reach for Flex when [`Stack`](crate::Stack) or [`Space`](crate::Space) defaults already match your layout
30///
31/// # Layout primitives
32///
33/// When `Flex` is not the right fit:
34///
35/// - **Even gap between every sibling on one axis** — [`Stack`](crate::Stack). - **Opposite edges / space-between on one axis** — [`Space`](crate::Space). - **Full flex control** (wrap, inline, fill, inset padding) — `Flex` (this component). - **Fixed column count with span/offset per cell** — [`Grid`](crate::Grid) + [`GridItem`](crate::GridItem). - **Fluid card tiles that reflow by viewport width** — [AutoGrid](/auto-grid) in the orbital crate. - **Single node with padding/surface tokens, no sibling gaps** — [`Box`](crate::Box). - **Page max-width centering inside the shell** — [Container](/container) in the orbital crate. - **Doc-style content + sticky aside rail** — [`ContentWithAside`](crate::ContentWithAside). - **Application shell** (header, sidebar, main) — [`Layout`](crate::Layout).
36///
37/// **Spacing vocabulary:** [`FlexGap`] presets for `Flex`, `Stack`, and `Space`; pixel gaps on [`Grid`](crate::Grid); [`SpacingSize`](/auto-grid) on AutoGrid. Pick the token type each component expects.
38///
39/// # Examples
40///
41/// ## Default
42/// Horizontal flex row with medium gap between items—the baseline for toolbars, button groups, and inline control rows.
43/// <!-- default -->
44/// <!-- preview -->
45/// ```rust
46/// use crate::DemoBox;
47/// view! {
48///     <div data-testid="flex-preview">
49///         <Flex gap=FlexGap::Medium>
50///             <DemoBox data_testid="flex-item-a">"Item A"</DemoBox>
51///             <DemoBox data_testid="flex-item-b">"Item B"</DemoBox>
52///         </Flex>
53///     </div>
54/// }
55/// ```
56///
57/// ## Vertical stack
58/// Column direction stacks children for form fields, settings sections, and vertically listed actions.
59/// <!-- preview -->
60/// ```rust
61/// use crate::DemoBox;
62/// view! {
63///     <div data-testid="flex-vertical">
64///         <Flex vertical=true gap=FlexGap::Small>
65///             <DemoBox data_testid="flex-stack-1">"First"</DemoBox>
66///             <DemoBox data_testid="flex-stack-2">"Second"</DemoBox>
67///         </Flex>
68///     </div>
69/// }
70/// ```
71///
72/// ## Inline flex
73/// `inline-flex` keeps the container in the text flow so compact clusters sit beside surrounding copy without breaking the line.
74/// <!-- preview -->
75/// ```rust
76/// view! {
77///     <div data-testid="flex-inline">
78///         <span>"Before "</span>
79///         <Flex inline=true gap=FlexGap::Small>
80///             <span data-testid="inline-a" style="padding: var(--orb-space-inline-sm); border: 1px dashed var(--orb-color-border-default); border-radius: var(--orb-radius-md);">"A"</span>
81///             <span data-testid="inline-b" style="padding: var(--orb-space-inline-sm); border: 1px dashed var(--orb-color-border-default); border-radius: var(--orb-radius-md);">"B"</span>
82///         </Flex>
83///         <span>" after"</span>
84///     </div>
85/// }
86/// ```
87///
88/// ## Gap matrix
89/// Compare Small, Medium, and Large presets plus custom `Size` and `WH` values so spacing stays consistent without margin hacks.
90/// <!-- preview -->
91/// ```rust
92/// use crate::DemoBox;
93/// view! {
94///     <div data-testid="flex-gap-matrix">
95///         <Flex vertical=true gap=FlexGap::Medium>
96///             <Flex gap=FlexGap::Small>
97///                 <DemoBox data_testid="gap-small-a">"Small"</DemoBox>
98///                 <DemoBox data_testid="gap-small-b">"Small"</DemoBox>
99///             </Flex>
100///             <Flex gap=FlexGap::Medium>
101///                 <DemoBox data_testid="gap-medium-a">"Medium"</DemoBox>
102///                 <DemoBox data_testid="gap-medium-b">"Medium"</DemoBox>
103///             </Flex>
104///             <Flex gap=FlexGap::Large>
105///                 <DemoBox data_testid="gap-large-a">"Large"</DemoBox>
106///                 <DemoBox data_testid="gap-large-b">"Large"</DemoBox>
107///             </Flex>
108///             <Flex gap=FlexGap::Size(20)>
109///                 <DemoBox data_testid="gap-size-a">"Size(20)"</DemoBox>
110///                 <DemoBox data_testid="gap-size-b">"Size(20)"</DemoBox>
111///             </Flex>
112///             <Flex gap=FlexGap::WH(8, 24)>
113///                 <DemoBox data_testid="gap-wh-a">"WH(8,24)"</DemoBox>
114///                 <DemoBox data_testid="gap-wh-b">"WH(8,24)"</DemoBox>
115///             </Flex>
116///         </Flex>
117///     </div>
118/// }
119/// ```
120///
121/// ## Centered content
122/// Center on both axes when a single block should sit in the middle of a fixed-height region (empty states, compact panels).
123/// <!-- preview -->
124/// ```rust
125/// use crate::DemoBox;
126/// view! {
127///     <div data-testid="flex-centered" style="width: 100%; max-width: 560px; height: 160px; border: 1px dashed var(--orb-color-border-default); border-radius: var(--orb-radius-md);">
128///         <Flex
129///             fill=true
130///             full_width=true
131///             justify=FlexJustify::Center
132///             align=FlexAlign::Center
133///         >
134///             <DemoBox data_testid="flex-center-label">"Centered"</DemoBox>
135///         </Flex>
136///     </div>
137/// }
138/// ```
139///
140/// ## Align (cross-axis)
141/// Start, Center, and End alignment along the cross axis when row height exceeds item height.
142/// <!-- preview -->
143/// ```rust
144/// use crate::{DemoBox, Flex, FlexAlign, FlexGap};
145/// view! {
146///     <div data-testid="flex-align" style="height: 120px;">
147///         <Flex vertical=true gap=FlexGap::Medium>
148///             <Flex align=FlexAlign::Start gap=FlexGap::Small>
149///                 <DemoBox height="24px">"Start"</DemoBox>
150///                 <DemoBox height="48px">"Start"</DemoBox>
151///             </Flex>
152///             <Flex align=FlexAlign::Center gap=FlexGap::Small>
153///                 <DemoBox height="24px">"Center"</DemoBox>
154///                 <DemoBox height="48px">"Center"</DemoBox>
155///             </Flex>
156///             <Flex align=FlexAlign::End gap=FlexGap::Small>
157///                 <DemoBox height="24px">"End"</DemoBox>
158///                 <DemoBox height="48px">"End"</DemoBox>
159///             </Flex>
160///         </Flex>
161///     </div>
162/// }
163/// ```
164///
165/// ## Justify (main-axis)
166/// Start, Center, End, and SpaceBetween distribute items along the main axis—SpaceBetween is common for footer action bars.
167/// <!-- preview -->
168/// ```rust
169/// use crate::{DemoBox, Flex, FlexGap, FlexJustify};
170/// view! {
171///     <div data-testid="flex-justify">
172///         <Flex vertical=true gap=FlexGap::Medium>
173///             <Flex justify=FlexJustify::Start gap=FlexGap::Small full_width=true>
174///                 <DemoBox>"Start"</DemoBox>
175///                 <DemoBox>"Start"</DemoBox>
176///             </Flex>
177///             <Flex justify=FlexJustify::Center gap=FlexGap::Small full_width=true>
178///                 <DemoBox>"Center"</DemoBox>
179///                 <DemoBox>"Center"</DemoBox>
180///             </Flex>
181///             <Flex justify=FlexJustify::End gap=FlexGap::Small full_width=true>
182///                 <DemoBox>"End"</DemoBox>
183///                 <DemoBox>"End"</DemoBox>
184///             </Flex>
185///             <Flex justify=FlexJustify::SpaceBetween gap=FlexGap::Small full_width=true>
186///                 <DemoBox>"Between"</DemoBox>
187///                 <DemoBox>"Between"</DemoBox>
188///             </Flex>
189///         </Flex>
190///     </div>
191/// }
192/// ```
193#[component_doc(
194    category = "Layout",
195    preview_slug = "flex",
196    preview_label = "Flex",
197    preview_icon = icondata::AiColumnWidthOutlined,
198)]
199#[component]
200pub fn Flex(
201    /// Optional CSS class names merged onto the flex container.
202    #[prop(optional, into)]
203    class: MaybeProp<String>,
204    /// Spacing between flex items. Presets: `Small`, `Medium` (default), `Large`; or custom via `Size(px)` / `WH(row_px, col_px)`.
205    #[prop(optional)]
206    gap: FlexGap,
207    /// When `true`, lays out children in a column (`flex-direction: column`).
208    #[prop(optional)]
209    vertical: bool,
210    /// When `true`, uses `display: inline-flex` so the container flows inline.
211    #[prop(optional, into)]
212    inline: Signal<bool>,
213    /// Cross-axis alignment (`align-items`).
214    #[prop(optional, into)]
215    align: MaybeProp<FlexAlign>,
216    /// Main-axis distribution (`justify-content`).
217    #[prop(optional, into)]
218    justify: MaybeProp<FlexJustify>,
219    /// Whether flex items wrap onto multiple lines.
220    #[prop(optional, default = FlexWrap::NoWrap)]
221    wrap: FlexWrap,
222    /// When `true`, the container fills the height of its parent (`height: 100%`).
223    #[prop(optional, default = false)]
224    fill: bool,
225    /// When `true`, the container spans the full width of its parent.
226    #[prop(optional, default = false)]
227    full_width: bool,
228    /// Theme-aware padding using Orbital spacing tokens.
229    #[prop(optional, into)]
230    padding: MaybeProp<SpacingInset>,
231    /// Theme-aware margin using Orbital spacing tokens.
232    #[prop(optional, into)]
233    margin: MaybeProp<SpacingInset>,
234    /// Flex item children.
235    children: Children,
236) -> impl IntoView {
237    view! {
238        <BaseFlex
239            class=class
240            gap=gap
241            vertical=vertical
242            inline=inline
243            align=align
244            justify=justify
245            wrap=wrap
246            fill=fill
247            full_width=full_width
248            padding=padding
249            margin=margin
250        >
251            {children()}
252        </BaseFlex>
253    }
254}