Skip to main content

fc_dioxus/components/
mod.rs

1//! Components implemented against the pinned upstream CSS and fixtures.
2
3pub mod divider;
4
5pub mod alert;
6pub mod avatar;
7pub mod badge;
8pub mod breadcrumb;
9pub mod button;
10pub mod button_group;
11pub mod card;
12pub mod carousel;
13pub mod chat;
14pub mod check;
15pub mod citation;
16pub mod code;
17pub mod composer;
18pub mod context_strip;
19pub mod datepicker;
20pub mod description_list;
21pub mod dialog;
22pub mod diff;
23pub mod disclosure;
24pub mod editor;
25pub mod empty_state;
26pub mod fab;
27pub mod field;
28pub mod git;
29pub mod grid;
30pub mod icon;
31pub mod list;
32pub mod loading;
33pub mod masthead;
34pub mod menu;
35pub mod multiselect;
36pub mod nav;
37pub mod navbar;
38pub mod pagination;
39pub mod popover;
40pub mod reasoning;
41pub mod search;
42pub mod segmented;
43pub mod select;
44pub mod semantic;
45pub mod slider;
46pub mod spinner;
47pub mod split_button;
48pub mod splitter;
49pub mod suggestion;
50pub mod table;
51pub mod tabs;
52pub mod terminal;
53pub mod theme_picker;
54pub mod toast;
55pub mod tool_call;
56pub mod toolbar;
57pub mod webview;
58
59use crate::core::state::HeadingLevel;
60use dioxus::prelude::*;
61
62/// The part class a group gives the controls inside it, so `ButtonGroup { Button {..} }`
63/// needs no class from the author. A nested group replaces it for its own children.
64#[derive(Debug, Clone, PartialEq, Eq)]
65pub(crate) struct GroupItem {
66    /// Class for every item.
67    pub(crate) item: String,
68    /// Extra class for a text control, which a joined group sizes to fill.
69    pub(crate) control: Option<String>,
70}
71
72pub(crate) fn provide_group_item(item: String, control: Option<String>) {
73    use_context_provider(|| GroupItem { item, control });
74}
75
76/// The item class from the nearest group, for a button-like control.
77pub(crate) fn group_item_class() -> Option<String> {
78    try_consume_context::<GroupItem>().map(|group| group.item)
79}
80
81/// The item and control classes from the nearest group, for a text control.
82pub(crate) fn group_control_class() -> Option<String> {
83    try_consume_context::<GroupItem>().map(|group| match group.control {
84        Some(control) => format!("{} {control}", group.item),
85        None => group.item,
86    })
87}
88
89/// A heading with an id, for components whose container is labelled by it.
90pub(crate) fn heading_with_id(
91    level: HeadingLevel,
92    class: String,
93    id: &crate::core::id::Id,
94    children: Element,
95) -> Element {
96    let id = id.to_string();
97    match level {
98        HeadingLevel::H1 => rsx! { h1 { class, id, {children} } },
99        HeadingLevel::H2 => rsx! { h2 { class, id, {children} } },
100        HeadingLevel::H3 => rsx! { h3 { class, id, {children} } },
101        HeadingLevel::H4 => rsx! { h4 { class, id, {children} } },
102        HeadingLevel::H5 => rsx! { h5 { class, id, {children} } },
103        HeadingLevel::H6 => rsx! { h6 { class, id, {children} } },
104    }
105}
106
107/// A heading at a caller-chosen level, so components never hard-code document outline.
108pub(crate) fn heading(level: HeadingLevel, class: String, children: Element) -> Element {
109    match level {
110        HeadingLevel::H1 => rsx! { h1 { class, {children} } },
111        HeadingLevel::H2 => rsx! { h2 { class, {children} } },
112        HeadingLevel::H3 => rsx! { h3 { class, {children} } },
113        HeadingLevel::H4 => rsx! { h4 { class, {children} } },
114        HeadingLevel::H5 => rsx! { h5 { class, {children} } },
115        HeadingLevel::H6 => rsx! { h6 { class, {children} } },
116    }
117}
118
119/// A navigation destination: a link, or a button when there is no URL to go to.
120///
121/// Unavailable links lose their `href` and carry `aria-disabled`, since `<a>` has no
122/// disabled state; buttons use the native attribute. `current` marks the page.
123#[allow(clippy::too_many_arguments)]
124pub(crate) fn nav_target(
125    class: String,
126    href: Option<String>,
127    onclick: Option<EventHandler<MouseEvent>>,
128    current: bool,
129    availability: &crate::core::state::Availability,
130    extra: impl Into<crate::core::attrs::Spread>,
131    children: Element,
132) -> Element {
133    use crate::core::{attrs::availability_attributes, state::Availability};
134    let extra = extra.into();
135    let blocked = *availability != Availability::Enabled;
136    if href.is_none() && onclick.is_some() {
137        let state = availability_attributes(availability);
138        return rsx! {
139            button { class, r#type: "button", aria_current: current.then_some("page"),
140                onclick: move |event| if !blocked { if let Some(onclick) = onclick { onclick.call(event); } },
141                ..state, ..extra, {children}
142            }
143        };
144    }
145    let state = match availability {
146        Availability::Disabled => {
147            crate::spread![Attribute::new("aria-disabled", "true", None, false)]
148        }
149        other => availability_attributes(other),
150    };
151    let link = crate::core::link::use_link(href.filter(|_| !blocked));
152    let current = current || link.current;
153    let href = link.href.clone();
154    rsx! {
155        a { class, href, aria_current: current.then_some("page"),
156            onclick: move |event| {
157                if blocked {
158                    event.prevent_default();
159                    return;
160                }
161                link.follow(&event);
162                if let Some(onclick) = onclick { onclick.call(event); }
163            },
164            ..state, ..extra, {children}
165        }
166    }
167}