tablo_core/schema/fields/
custom.rs1use tablo_ui::{checkbox as ui_checkbox, switch as ui_switch};
5use topcoat::{context::Cx, view::*};
6
7pub trait Control: Send + Sync {
30 fn render<'a>(&self, cx: &'a Cx, input: ControlInput) -> BoxView<'a>;
31}
32
33#[derive(Debug, Clone)]
36pub struct ControlInput {
37 name: String,
38 id: String,
39 value: Option<String>,
40 required: bool,
41 invalid: bool,
42 described_by: Option<String>,
43 disabled: bool,
44}
45
46impl ControlInput {
47 pub(crate) fn new(
48 name: &str,
49 id: &str,
50 value: Option<&str>,
51 required: bool,
52 invalid: bool,
53 described_by: Option<String>,
54 ) -> Self {
55 Self {
56 name: name.to_string(),
57 id: id.to_string(),
58 value: value.map(str::to_string),
59 required,
60 invalid,
61 described_by,
62 disabled: false,
63 }
64 }
65
66 pub(crate) fn disabled(mut self, disabled: bool) -> Self {
67 self.disabled = disabled;
68 self
69 }
70
71 pub fn name(&self) -> &str {
72 &self.name
73 }
74
75 pub fn id(&self) -> &str {
78 &self.id
79 }
80
81 pub fn value(&self) -> Option<&str> {
82 self.value.as_deref()
83 }
84
85 pub fn required(&self) -> bool {
86 self.required
87 }
88
89 pub fn invalid(&self) -> bool {
90 self.invalid
91 }
92
93 pub fn described_by(&self) -> Option<&str> {
94 self.described_by.as_deref()
95 }
96
97 pub fn is_disabled(&self) -> bool {
99 self.disabled
100 }
101
102 pub fn attributes(&self, cx: &Cx) -> Attributes {
103 let required = self.required;
104 attributes! {
105 id=(self.id.clone())
106 name=(self.name.clone())
107 value=(self.value.clone())
108 required=(required)
109 disabled=(self.disabled)
110 aria-required=(required.then_some("true"))
111 aria-invalid=(if self.invalid { "true" } else { "false" })
112 aria-describedby=(self.described_by.clone())
113 }
114 }
115}
116
117#[derive(Debug, Clone, Copy, Default)]
119pub struct Toggle;
120
121impl Control for Toggle {
122 fn render<'a>(&self, cx: &'a Cx, input: ControlInput) -> BoxView<'a> {
123 boolean(cx, input, false)
124 }
125}
126
127#[derive(Debug, Clone, Copy, Default)]
131pub struct Switch;
132
133impl Control for Switch {
134 fn render<'a>(&self, cx: &'a Cx, input: ControlInput) -> BoxView<'a> {
135 boolean(cx, input, true)
136 }
137}
138
139fn boolean<'a>(cx: &'a Cx, input: ControlInput, switch: bool) -> BoxView<'a> {
142 let name = input.name().to_string();
143 let id = input.id().to_string();
144 let checked = input.value().is_some_and(|v| v.trim() == "true");
145 let invalid = if input.invalid() { "true" } else { "false" };
146 let described_by = input.described_by().map(str::to_string);
147 let disabled = input.is_disabled();
148 let required = input.required();
150 let attrs = attributes! {
151 id=(id)
152 name=(name.clone())
153 value="true"
154 checked=(checked)
155 required=(required)
156 aria-required=(required.then_some("true"))
157 disabled=(disabled)
158 aria-invalid=(invalid)
159 aria-describedby=(described_by)
160 };
161 view! {
162 <input type="hidden" name=(name) value="false" disabled=(disabled)>
163 if switch {
164 ui_switch(attrs: attrs)
165 } else {
166 ui_checkbox(attrs: attrs)
167 }
168 }
169 .boxed()
170}