logicaffeine_web/ui/components/
mode_selector.rs1use dioxus::prelude::*;
2use crate::ui::components::icon::{Icon, IconVariant, IconSize};
3
4const MODE_SELECTOR_STYLE: &str = r#"
5.mode-overlay {
6 position: fixed;
7 top: 0;
8 left: 0;
9 right: 0;
10 bottom: 0;
11 background: rgba(0, 0, 0, 0.8);
12 display: flex;
13 align-items: center;
14 justify-content: center;
15 z-index: 1000;
16 animation: fade-in 0.2s ease-out;
17}
18
19@keyframes fade-in {
20 from { opacity: 0; }
21 to { opacity: 1; }
22}
23
24.mode-dialog {
25 background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
26 border: 1px solid rgba(255, 255, 255, 0.1);
27 border-radius: 20px;
28 padding: 32px;
29 max-width: 600px;
30 width: 90%;
31 animation: slide-up 0.3s ease-out;
32}
33
34@keyframes slide-up {
35 from { transform: translateY(20px); opacity: 0; }
36 to { transform: translateY(0); opacity: 1; }
37}
38
39.mode-dialog h2 {
40 color: #e8e8e8;
41 font-size: 24px;
42 margin-bottom: 8px;
43 text-align: center;
44}
45
46.mode-dialog p {
47 color: #888;
48 font-size: 14px;
49 text-align: center;
50 margin-bottom: 24px;
51}
52
53.mode-options {
54 display: flex;
55 flex-direction: column;
56 gap: 12px;
57}
58
59.mode-option {
60 display: flex;
61 align-items: center;
62 gap: 16px;
63 padding: 20px;
64 background: rgba(255, 255, 255, 0.03);
65 border: 2px solid rgba(255, 255, 255, 0.08);
66 border-radius: 12px;
67 cursor: pointer;
68 transition: all 0.2s ease;
69}
70
71.mode-option:hover {
72 background: rgba(255, 255, 255, 0.06);
73 border-color: rgba(255, 255, 255, 0.15);
74 transform: translateX(4px);
75}
76
77.mode-option.textbook:hover {
78 border-color: rgba(96, 165, 250, 0.5);
79}
80
81.mode-option.learning:hover {
82 border-color: rgba(74, 222, 128, 0.5);
83}
84
85.mode-option.testing:hover {
86 border-color: rgba(251, 146, 60, 0.5);
87}
88
89.mode-icon {
90 width: 48px;
91 height: 48px;
92 display: flex;
93 align-items: center;
94 justify-content: center;
95 border-radius: 12px;
96 font-size: 24px;
97}
98
99.mode-icon.textbook {
100 background: rgba(96, 165, 250, 0.2);
101}
102
103.mode-icon.learning {
104 background: rgba(74, 222, 128, 0.2);
105}
106
107.mode-icon.testing {
108 background: rgba(251, 146, 60, 0.2);
109}
110
111.mode-info {
112 flex: 1;
113}
114
115.mode-info h3 {
116 color: #e8e8e8;
117 font-size: 18px;
118 margin-bottom: 4px;
119}
120
121.mode-info p {
122 color: #888;
123 font-size: 13px;
124 text-align: left;
125 margin: 0;
126}
127
128.mode-arrow {
129 color: #666;
130 font-size: 20px;
131}
132
133.mode-cancel {
134 display: block;
135 width: 100%;
136 margin-top: 16px;
137 padding: 12px;
138 background: transparent;
139 border: 1px solid rgba(255, 255, 255, 0.1);
140 border-radius: 8px;
141 color: #888;
142 font-size: 14px;
143 cursor: pointer;
144 transition: all 0.2s ease;
145}
146
147.mode-cancel:hover {
148 border-color: rgba(255, 255, 255, 0.2);
149 color: #aaa;
150}
151
152.recommended-badge {
153 background: rgba(74, 222, 128, 0.2);
154 color: #4ade80;
155 padding: 2px 8px;
156 border-radius: 4px;
157 font-size: 10px;
158 font-weight: 600;
159 text-transform: uppercase;
160 margin-left: 8px;
161}
162"#;
163
164#[derive(Clone, PartialEq)]
165pub struct ModeInfo {
166 pub era: String,
167 pub module: String,
168 pub title: String,
169}
170
171#[component]
172pub fn ModeSelector(
173 info: ModeInfo,
174 on_select: EventHandler<String>,
175 on_cancel: EventHandler<()>,
176) -> Element {
177 rsx! {
178 style { "{MODE_SELECTOR_STYLE}" }
179 div {
180 class: "mode-overlay",
181 onclick: move |_| on_cancel.call(()),
182 div {
183 class: "mode-dialog",
184 onclick: move |e| e.stop_propagation(),
185 h2 { "{info.title}" }
186 p { "Choose how you want to approach this module" }
187
188 div { class: "mode-options",
189 button {
190 class: "mode-option textbook",
191 onclick: move |_| on_select.call("textbook".to_string()),
192 div { class: "mode-icon textbook",
193 Icon { variant: IconVariant::Book, size: IconSize::Large }
194 }
195 div { class: "mode-info",
196 h3 { "Read" }
197 p { "Study the concepts and examples before practicing" }
198 }
199 span { class: "mode-arrow",
200 Icon { variant: IconVariant::ChevronRight, size: IconSize::Medium }
201 }
202 }
203
204 button {
205 class: "mode-option learning",
206 onclick: move |_| on_select.call("learning".to_string()),
207 div { class: "mode-icon learning",
208 Icon { variant: IconVariant::GraduationCap, size: IconSize::Large }
209 }
210 div { class: "mode-info",
211 h3 {
212 "Practice"
213 span { class: "recommended-badge", "Recommended" }
214 }
215 p { "Learn with hints and immediate feedback on your answers" }
216 }
217 span { class: "mode-arrow",
218 Icon { variant: IconVariant::ChevronRight, size: IconSize::Medium }
219 }
220 }
221
222 button {
223 class: "mode-option testing",
224 onclick: move |_| on_select.call("testing".to_string()),
225 div { class: "mode-icon testing",
226 Icon { variant: IconVariant::Document, size: IconSize::Large }
227 }
228 div { class: "mode-info",
229 h3 { "Test" }
230 p { "Prove your knowledge with no hints - see results at the end" }
231 }
232 span { class: "mode-arrow",
233 Icon { variant: IconVariant::ChevronRight, size: IconSize::Medium }
234 }
235 }
236 }
237
238 button {
239 class: "mode-cancel",
240 onclick: move |_| on_cancel.call(()),
241 "Cancel"
242 }
243 }
244 }
245 }
246}