fc_dioxus/components/
search.rs1use crate::{
3 core::{
4 assets,
5 attrs::{availability_attributes, component_attributes, open_attribute},
6 },
7 prelude::*,
8 theme::use_stylesheets,
9};
10const BEM: Bem = Bem::new("fc-search");
11const INPUT: Bem = Bem::new("fc-search__input");
13const VIEW: Bem = Bem::new("fc-search__view");
14fc_modifier! { enum ClearButton for INPUT { Native => None, Own => "own-clear" } }
15fc_modifier! { enum ViewPosition for VIEW { Floating => None, Docked => "docked" } }
16
17#[component]
22pub fn Search(
23 label: AccessibleName,
24 #[props(default)] value: Option<ReadSignal<String>>,
25 #[props(default)] default_value: String,
26 #[props(default)] oninput: Option<EventHandler<String>>,
27 #[props(default)] onsubmit: Option<EventHandler<String>>,
28 #[props(default)] placeholder: Option<String>,
29 #[props(default)]
31 trailing: Option<Element>,
32 #[props(default)]
34 own_clear: bool,
35 #[props(default)] view: Option<Element>,
36 #[props(default)] invalid: bool,
37 #[props(default)] availability: Availability,
38 #[props(default)] class: Option<String>,
39 #[props(extends = GlobalAttributes)] attributes: Vec<Attribute>,
40) -> Element {
41 let state = use_controllable(value, || default_value, oninput);
42 let attributes = component_attributes(
43 attributes,
44 ClassList::new(BEM.block()).extend(class.as_deref()),
45 &["role"],
46 );
47 let input_class = ClassList::new(INPUT.block())
48 .with_modifier(
49 INPUT,
50 if own_clear {
51 ClearButton::Own
52 } else {
53 ClearButton::Native
54 },
55 )
56 .to_string();
57 let input_state = availability_attributes(&availability);
58 let body = rsx! {
59 div { class: BEM.element("bar"),
60 span { class: BEM.element("icon"), aria_hidden: "true", "⚲" }
61 input { class: input_class, r#type: "search", aria_label: label.to_string(), placeholder,
62 aria_invalid: invalid.then_some("true"), value: state.get(),
63 oninput: move |event| state.set(event.value()), ..input_state }
64 if let Some(trailing) = trailing { span { class: BEM.element("trailing"), {trailing} } }
65 }
66 {view}
67 };
68 rsx! {
69 if use_stylesheets() { document::Stylesheet { href: assets::COMPONENTS_SEARCH_CSS } }
70 if let Some(onsubmit) = onsubmit {
71 form { role: "search",
72 onsubmit: move |event| { event.prevent_default(); onsubmit.call(state.get()); },
73 ..attributes, {body}
74 }
75 } else {
76 div { role: "search", ..attributes, {body} }
77 }
78 }
79}
80
81#[component]
84pub fn SearchView(
85 label: AccessibleName,
86 heading: Element,
87 #[props(default)] docked: bool,
88 #[props(default)] open: Option<bool>,
89 #[props(default)]
91 busy: bool,
92 children: Element,
93) -> Element {
94 let class = ClassList::new(VIEW.block())
95 .with_modifier(
96 VIEW,
97 if docked {
98 ViewPosition::Docked
99 } else {
100 ViewPosition::Floating
101 },
102 )
103 .to_string();
104 let open = open_attribute(open);
105 rsx! {
106 div { class, role: "region", aria_label: label.to_string(), aria_busy: busy.then_some("true"), ..open,
107 p { class: BEM.element("view-label"), {heading} }
108 {children}
109 }
110 }
111}