fc_dioxus/components/
segmented.rs1use crate::{
4 core::{
5 assets,
6 attrs::{availability_attributes, component_attributes},
7 },
8 prelude::*,
9 theme::use_stylesheets,
10};
11const BEM: Bem = Bem::new("fc-segmented");
12const HIDDEN: Bem = Bem::new("fc-u-visually-hidden");
13
14#[derive(Debug, Clone, PartialEq)]
16pub enum Segments {
17 Single {
19 name: Id,
21 value: Option<String>,
23 onchange: Option<EventHandler<String>>,
25 },
26 Multiple {
28 values: Vec<String>,
30 onchange: Option<EventHandler<Vec<String>>>,
32 },
33}
34
35#[derive(Debug, Clone, PartialEq)]
36struct SegmentContext {
37 segments: Segments,
38 invalid: bool,
39 required: bool,
40}
41
42#[component]
45pub fn Segmented(
46 legend: AccessibleName,
47 segments: Segments,
48 #[props(default)] invalid: bool,
49 #[props(default)] required: bool,
50 #[props(default)] class: Option<String>,
51 #[props(extends = GlobalAttributes)] attributes: Vec<Attribute>,
52 children: Element,
53) -> Element {
54 let context = SegmentContext {
55 segments,
56 invalid,
57 required,
58 };
59 let mut shared = use_context_provider(|| Signal::new(context.clone()));
60 if *shared.peek() != context {
61 shared.set(context);
62 }
63 let attributes = component_attributes(
64 attributes,
65 ClassList::new(BEM.block()).extend(class.as_deref()),
66 &[],
67 );
68 rsx! {
69 if use_stylesheets() { document::Stylesheet { href: assets::COMPONENTS_SEGMENTED_CSS } }
70 fieldset { ..attributes,
71 legend { class: HIDDEN.block(), "{legend}" }
72 {children}
73 }
74 }
75}
76
77#[component]
79pub fn Segment(
80 value: String,
81 #[props(default)] availability: Availability,
82 children: Element,
83) -> Element {
84 let Some(context) = try_consume_context::<Signal<SegmentContext>>().map(|c| c.read().clone())
85 else {
86 return rsx! {};
87 };
88 let blocked = availability != Availability::Enabled;
89 let state = availability_attributes(&availability);
90 let (kind, name, checked) = match &context.segments {
91 Segments::Single {
92 name,
93 value: chosen,
94 ..
95 } => (
96 "radio",
97 Some(name.to_string()),
98 chosen.as_deref() == Some(value.as_str()),
99 ),
100 Segments::Multiple { values, .. } => ("checkbox", None, values.contains(&value)),
101 };
102 let segments = context.segments.clone();
103 let own = value.clone();
104 rsx! {
105 label { class: BEM.element("item"),
106 input { class: BEM.element("input"), r#type: kind, name, value: value.clone(), checked,
107 aria_invalid: context.invalid.then_some("true"), required: context.required,
108 onclick: move |event| if blocked { event.prevent_default(); },
109 onchange: move |event| match &segments {
110 Segments::Single { onchange: Some(onchange), .. } => onchange.call(own.clone()),
111 Segments::Multiple { values, onchange: Some(onchange) } => {
112 let mut next: Vec<String> = values.iter().filter(|v| **v != own).cloned().collect();
113 if event.checked() { next.push(own.clone()); }
114 onchange.call(next);
115 }
116 _ => {}
117 },
118 ..state,
119 }
120 span { class: BEM.element("check"), aria_hidden: "true" }
121 {children}
122 }
123 }
124}