1use ferrox_front_core::dom::{div, span, button as dom_button, DomBuilder};
7
8pub fn card() -> DomBuilder {
10 div()
11 .attr("class", "ferrox-glass-card")
12 .attr("style", "background: var(--bg-glass); backdrop-filter: blur(12px); border: var(--border-glass); border-radius: 12px; padding: 1.25rem; box-shadow: 0 8px 32px rgba(0,0,0,0.37); display: flex; flex-direction: column;")
13}
14
15pub fn card_header(title: &str) -> DomBuilder {
17 div()
18 .attr("style", "font-weight: 700; font-size: 1.15rem; margin-bottom: 1rem; border-bottom: var(--border-glass); padding-bottom: 0.6rem; color: var(--text-primary);")
19 .text(title)
20}
21
22pub fn card_body() -> DomBuilder {
24 div().attr("style", "flex: 1;")
25}
26
27pub fn card_footer() -> DomBuilder {
29 div().attr("style", "margin-top: 1rem; padding-top: 0.6rem; border-top: var(--border-glass); display: flex; gap: 8px; justify-content: flex-end;")
30}
31
32pub fn button(text: &str, variant: &str) -> DomBuilder {
34 let (bg, color, border) = match variant {
35 "primary" => ("var(--btn-bg, linear-gradient(135deg, #7e22ce, #3b82f6))", "#ffffff", "none"),
36 "secondary" => ("rgba(255,255,255,0.1)", "var(--text-primary)", "var(--border-glass)"),
37 "danger" => ("linear-gradient(135deg, #dc2626, #991b1b)", "#ffffff", "none"),
38 "outline" => ("transparent", "var(--accent-cyan, #06b6d4)", "1px solid var(--accent-cyan, #06b6d4)"),
39 "ghost" => ("transparent", "var(--text-primary)", "none"),
40 _ => ("var(--bg-glass)", "var(--text-primary)", "var(--border-glass)"), };
42
43 let style = format!(
44 "background: {}; color: {}; border: {}; padding: 8px 16px; border-radius: 6px; font-weight: 600; font-size: 0.9rem; cursor: pointer; transition: all 0.2s ease; display: inline-flex; align-items: center; justify-content: center; gap: 6px;",
45 bg, color, border
46 );
47
48 dom_button().attr("class", &format!("ferrox-btn ferrox-btn-{}", variant)).attr("style", &style).text(text)
49}
50
51pub fn badge(text: &str, variant: &str) -> DomBuilder {
53 let (bg, color) = match variant {
54 "success" => ("rgba(16, 185, 129, 0.2)", "#34d399"),
55 "danger" => ("rgba(239, 68, 68, 0.2)", "#fca5a5"),
56 "warning" => ("rgba(245, 158, 11, 0.2)", "#fde047"),
57 "info" => ("rgba(14, 165, 233, 0.2)", "#7dd3fc"),
58 _ => ("rgba(147, 51, 234, 0.2)", "#c084fc"),
59 };
60
61 let style = format!(
62 "background: {}; color: {}; border: var(--border-glass); padding: 3px 10px; border-radius: 9999px; font-size: 0.78rem; font-weight: 700; display: inline-block;",
63 bg, color
64 );
65
66 span().attr("class", &format!("ferrox-badge ferrox-badge-{}", variant)).attr("style", &style).text(text)
67}
68
69pub fn alert(message: &str, variant: &str) -> DomBuilder {
71 let (bg, color) = match variant {
72 "danger" => ("rgba(239, 68, 68, 0.15)", "#fca5a5"),
73 "success" => ("rgba(16, 185, 129, 0.15)", "#34d399"),
74 "warning" => ("rgba(245, 158, 11, 0.15)", "#fde047"),
75 _ => ("rgba(147, 51, 234, 0.15)", "#c084fc"),
76 };
77
78 let style = format!(
79 "padding: 1rem; border-radius: 8px; background: {}; color: {}; border: var(--border-glass); margin-bottom: 1rem; font-weight: 500; font-size: 0.92rem;",
80 bg, color
81 );
82
83 div().attr("class", &format!("ferrox-alert ferrox-alert-{}", variant)).attr("style", &style).text(message)
84}
85
86pub fn input(input_type: &str, placeholder: &str) -> DomBuilder {
88 let style = "width: 100%; padding: 10px 14px; border-radius: 6px; background: var(--bg-primary); color: var(--text-primary); border: var(--border-glass); font-size: 0.9rem; outline: none;";
89 DomBuilder::new("input")
90 .attr("type", input_type)
91 .attr("placeholder", placeholder)
92 .attr("class", "ferrox-input")
93 .attr("style", style)
94}
95
96pub fn form_group(label_text: &str, input_element: DomBuilder) -> DomBuilder {
98 div()
99 .attr("class", "ferrox-form-group")
100 .attr("style", "margin-bottom: 1rem; display: flex; flex-direction: column; gap: 6px;")
101 .child(
102 DomBuilder::new("label")
103 .attr("style", "font-size: 0.85rem; font-weight: 600; color: var(--accent-cyan);")
104 .text(label_text)
105 )
106 .child(input_element)
107}
108
109pub fn progress_bar(percentage: f64) -> DomBuilder {
111 let pct = percentage.clamp(0.0, 100.0);
112 div()
113 .attr("class", "ferrox-progress-track")
114 .attr("style", "width: 100%; height: 8px; background: rgba(255,255,255,0.1); border-radius: 9999px; overflow: hidden;")
115 .child(
116 div()
117 .attr("class", "ferrox-progress-fill")
118 .attr("style", &format!("width: {}%; height: 100%; background: var(--btn-bg, linear-gradient(135deg, #7e22ce, #3b82f6)); transition: width 0.3s ease;", pct))
119 )
120}
121
122pub fn avatar(initials: &str) -> DomBuilder {
124 div()
125 .attr("class", "ferrox-avatar")
126 .attr("style", "width: 38px; height: 38px; border-radius: 50%; background: var(--btn-bg); color: #fff; font-weight: 700; font-size: 0.88rem; display: inline-flex; align-items: center; justify-content: center; border: var(--border-glass);")
127 .text(initials)
128}
129
130pub fn user_pill(name: &str, role: &str) -> DomBuilder {
132 let initials = name.split_whitespace().filter_map(|w| w.chars().next()).collect::<String>();
133 div()
134 .attr("class", "ferrox-user-pill")
135 .attr("style", "display: inline-flex; align-items: center; gap: 10px; padding: 4px 12px 4px 4px; background: var(--bg-glass); border: var(--border-glass); border-radius: 9999px;")
136 .child(avatar(&initials))
137 .child(
138 div()
139 .attr("style", "display: flex; flex-direction: column;")
140 .child(span().attr("style", "font-weight: 600; font-size: 0.85rem; color: var(--text-primary);").text(name))
141 .child(span().attr("style", "font-size: 0.72rem; opacity: 0.7; color: var(--accent-cyan);").text(role))
142 )
143}
144
145pub fn spinner() -> DomBuilder {
147 div()
148 .attr("class", "ferrox-spinner")
149 .attr("style", "width: 24px; height: 24px; border: 3px solid rgba(255,255,255,0.2); border-top-color: var(--accent-cyan); border-radius: 50%; animation: ferrox-spin 0.8s linear infinite;")
150}
151
152pub fn textarea(placeholder: &str, rows: u32) -> DomBuilder {
154 let style = "width: 100%; padding: 10px 14px; border-radius: 6px; background: var(--bg-primary); color: var(--text-primary); border: var(--border-glass); font-size: 0.9rem; outline: none; font-family: inherit; resize: vertical;";
155 DomBuilder::new("textarea")
156 .attr("rows", &rows.to_string())
157 .attr("placeholder", placeholder)
158 .attr("class", "ferrox-textarea")
159 .attr("style", style)
160}
161
162pub fn select(options: Vec<(&str, &str)>) -> DomBuilder {
164 let style = "width: 100%; padding: 10px 14px; border-radius: 6px; background: var(--bg-primary); color: var(--text-primary); border: var(--border-glass); font-size: 0.9rem; outline: none; cursor: pointer;";
165 let mut sel = DomBuilder::new("select")
166 .attr("class", "ferrox-select")
167 .attr("style", style);
168
169 for (val, label_text) in options {
170 let opt = DomBuilder::new("option")
171 .attr("value", val)
172 .text(label_text);
173 sel = sel.child(opt);
174 }
175 sel
176}
177
178pub fn checkbox(label_text: &str, checked: bool) -> DomBuilder {
180 let mut cb = DomBuilder::new("input")
181 .attr("type", "checkbox")
182 .attr("style", "accent-color: var(--accent-cyan); width: 16px; height: 16px; cursor: pointer;");
183 if checked {
184 cb = cb.attr("checked", "true");
185 }
186
187 DomBuilder::new("label")
188 .attr("style", "display: inline-flex; align-items: center; gap: 8px; font-size: 0.88rem; cursor: pointer; color: var(--text-primary);")
189 .child(cb)
190 .child(span().text(label_text))
191}
192
193pub fn radio(name: &str, value: &str, label_text: &str, checked: bool) -> DomBuilder {
195 let mut rb = DomBuilder::new("input")
196 .attr("type", "radio")
197 .attr("name", name)
198 .attr("value", value)
199 .attr("style", "accent-color: var(--accent-cyan); width: 16px; height: 16px; cursor: pointer;");
200 if checked {
201 rb = rb.attr("checked", "true");
202 }
203
204 DomBuilder::new("label")
205 .attr("style", "display: inline-flex; align-items: center; gap: 8px; font-size: 0.88rem; cursor: pointer; color: var(--text-primary);")
206 .child(rb)
207 .child(span().text(label_text))
208}
209
210pub fn toggle_switch(label_text: &str, checked: bool) -> DomBuilder {
212 let bg = if checked { "var(--accent-cyan, #06b6d4)" } else { "rgba(255,255,255,0.2)" };
213 let translate = if checked { "transform: translateX(18px);" } else { "transform: translateX(0px);" };
214
215 let track = div()
216 .attr("style", &format!("width: 38px; height: 20px; border-radius: 9999px; background: {}; position: relative; transition: background 0.3s; padding: 2px; cursor: pointer;", bg))
217 .child(
218 div()
219 .attr("style", &format!("width: 16px; height: 16px; border-radius: 50%; background: #ffffff; transition: transform 0.3s; {}", translate))
220 );
221
222 div()
223 .attr("style", "display: inline-flex; align-items: center; gap: 10px; cursor: pointer;")
224 .child(track)
225 .child(span().attr("style", "font-size: 0.88rem; font-weight: 500;").text(label_text))
226}
227
228pub fn range_slider(min: i32, max: i32, value: i32) -> DomBuilder {
230 DomBuilder::new("input")
231 .attr("type", "range")
232 .attr("min", &min.to_string())
233 .attr("max", &max.to_string())
234 .attr("value", &value.to_string())
235 .attr("class", "ferrox-range")
236 .attr("style", "width: 100%; accent-color: var(--accent-cyan); cursor: pointer;")
237}
238
239pub fn search_input(placeholder: &str) -> DomBuilder {
241 div()
242 .attr("style", "position: relative; width: 100%;")
243 .child(
244 DomBuilder::new("input")
245 .attr("type", "search")
246 .attr("placeholder", placeholder)
247 .attr("style", "width: 100%; padding: 10px 14px 10px 36px; border-radius: 6px; background: var(--bg-primary); color: var(--text-primary); border: var(--border-glass); font-size: 0.9rem; outline: none;")
248 )
249 .child(
250 span()
251 .attr("style", "position: absolute; left: 12px; top: 50%; transform: translateY(-50%); opacity: 0.6; font-size: 0.9rem;")
252 .text("🔍")
253 )
254}
255
256pub fn file_upload(accept: &str) -> DomBuilder {
258 div()
259 .attr("style", "border: 2px dashed var(--border-glass); border-radius: 8px; padding: 1.5rem; text-align: center; background: rgba(0,0,0,0.2); cursor: pointer; display: flex; flex-direction: column; align-items: center; gap: 6px;")
260 .child(span().attr("style", "font-size: 1.5rem;").text("📁"))
261 .child(span().attr("style", "font-size: 0.88rem; font-weight: 600; color: var(--accent-cyan);").text("Click or drop files to upload"))
262 .child(span().attr("style", "font-size: 0.75rem; opacity: 0.6;").text(&format!("Supported formats: {}", accept)))
263 .child(
264 DomBuilder::new("input")
265 .attr("type", "file")
266 .attr("accept", accept)
267 .attr("style", "display: none;")
268 )
269}