1use dioxus::prelude::*;
19
20const SYMBOL_PALETTE_STYLE: &str = r#"
21.symbol-palette {
22 display: flex;
23 flex-direction: column;
24 height: 100%;
25 background: #12161c;
26 overflow: hidden;
27}
28
29.symbol-palette-header {
30 padding: 12px 14px;
31 border-bottom: 1px solid rgba(255, 255, 255, 0.08);
32 background: rgba(255, 255, 255, 0.02);
33}
34
35.symbol-palette-title {
36 font-size: 14px;
37 font-weight: 600;
38 color: rgba(255, 255, 255, 0.9);
39}
40
41.symbol-search {
42 margin-top: 8px;
43}
44
45.symbol-search input {
46 width: 100%;
47 padding: 8px 10px;
48 background: rgba(255, 255, 255, 0.04);
49 border: 1px solid rgba(255, 255, 255, 0.08);
50 border-radius: 6px;
51 color: rgba(255, 255, 255, 0.9);
52 font-size: 13px;
53}
54
55.symbol-search input::placeholder {
56 color: rgba(255, 255, 255, 0.3);
57}
58
59.symbol-search input:focus {
60 outline: none;
61 border-color: rgba(102, 126, 234, 0.5);
62}
63
64.symbol-categories {
65 flex: 1;
66 overflow: auto;
67 padding: 8px;
68}
69
70.symbol-category {
71 margin-bottom: 16px;
72}
73
74.symbol-category-header {
75 font-size: 11px;
76 font-weight: 600;
77 text-transform: uppercase;
78 letter-spacing: 0.5px;
79 color: rgba(255, 255, 255, 0.4);
80 margin-bottom: 8px;
81 padding: 0 4px;
82}
83
84.symbol-grid {
85 display: grid;
86 grid-template-columns: repeat(auto-fill, minmax(36px, 1fr));
87 gap: 4px;
88}
89
90.symbol-btn {
91 aspect-ratio: 1;
92 display: flex;
93 align-items: center;
94 justify-content: center;
95 background: rgba(255, 255, 255, 0.04);
96 border: 1px solid rgba(255, 255, 255, 0.06);
97 border-radius: 6px;
98 color: rgba(255, 255, 255, 0.8);
99 font-size: 16px;
100 cursor: pointer;
101 transition: all 0.15s ease;
102}
103
104.symbol-btn:hover {
105 background: rgba(102, 126, 234, 0.15);
106 border-color: rgba(102, 126, 234, 0.3);
107 color: #667eea;
108}
109
110.symbol-btn:active {
111 transform: scale(0.95);
112}
113
114.symbol-btn[title]:hover::after {
115 content: attr(title);
116 position: absolute;
117 bottom: 100%;
118 left: 50%;
119 transform: translateX(-50%);
120 padding: 4px 8px;
121 background: #1a1f27;
122 border: 1px solid rgba(255, 255, 255, 0.1);
123 border-radius: 4px;
124 font-size: 11px;
125 white-space: nowrap;
126 z-index: 10;
127}
128
129/* Mobile adjustments */
130@media (max-width: 768px) {
131 .symbol-grid {
132 grid-template-columns: repeat(auto-fill, minmax(44px, 1fr));
133 }
134
135 .symbol-btn {
136 font-size: 18px;
137 }
138}
139"#;
140
141struct SymbolCategory {
143 name: &'static str,
144 symbols: &'static [(&'static str, &'static str)], }
146
147const SYMBOL_CATEGORIES: &[SymbolCategory] = &[
148 SymbolCategory {
149 name: "Greek",
150 symbols: &[
151 ("\u{03B1}", "\\alpha"),
152 ("\u{03B2}", "\\beta"),
153 ("\u{03B3}", "\\gamma"),
154 ("\u{03B4}", "\\delta"),
155 ("\u{03B5}", "\\epsilon"),
156 ("\u{03B6}", "\\zeta"),
157 ("\u{03B7}", "\\eta"),
158 ("\u{03B8}", "\\theta"),
159 ("\u{03BB}", "\\lambda"),
160 ("\u{03BC}", "\\mu"),
161 ("\u{03C0}", "\\pi"),
162 ("\u{03C3}", "\\sigma"),
163 ("\u{03C6}", "\\phi"),
164 ("\u{03C8}", "\\psi"),
165 ("\u{03C9}", "\\omega"),
166 ("\u{0393}", "\\Gamma"),
167 ("\u{0394}", "\\Delta"),
168 ("\u{03A3}", "\\Sigma"),
169 ("\u{03A6}", "\\Phi"),
170 ("\u{03A9}", "\\Omega"),
171 ],
172 },
173 SymbolCategory {
174 name: "Logic",
175 symbols: &[
176 ("\u{2200}", "\\forall"),
177 ("\u{2203}", "\\exists"),
178 ("\u{2204}", "\\nexists"),
179 ("\u{00AC}", "\\neg"),
180 ("\u{2227}", "\\land"),
181 ("\u{2228}", "\\lor"),
182 ("\u{2192}", "\\to"),
183 ("\u{2194}", "\\leftrightarrow"),
184 ("\u{21D2}", "\\Rightarrow"),
185 ("\u{21D4}", "\\Leftrightarrow"),
186 ("\u{22A2}", "\\vdash"),
187 ("\u{22A8}", "\\models"),
188 ("\u{22A4}", "\\top"),
189 ("\u{22A5}", "\\bot"),
190 ("\u{25A1}", "\\Box"),
191 ("\u{25C7}", "\\Diamond"),
192 ],
193 },
194 SymbolCategory {
195 name: "Sets",
196 symbols: &[
197 ("\u{2208}", "\\in"),
198 ("\u{2209}", "\\notin"),
199 ("\u{2286}", "\\subseteq"),
200 ("\u{2287}", "\\supseteq"),
201 ("\u{2282}", "\\subset"),
202 ("\u{2283}", "\\supset"),
203 ("\u{222A}", "\\cup"),
204 ("\u{2229}", "\\cap"),
205 ("\u{2205}", "\\emptyset"),
206 ("\u{2119}", "\\mathbb{P}"),
207 ("\u{2115}", "\\mathbb{N}"),
208 ("\u{2124}", "\\mathbb{Z}"),
209 ("\u{211A}", "\\mathbb{Q}"),
210 ("\u{211D}", "\\mathbb{R}"),
211 ("\u{2102}", "\\mathbb{C}"),
212 ],
213 },
214 SymbolCategory {
215 name: "Relations",
216 symbols: &[
217 ("\u{2260}", "\\neq"),
218 ("\u{2264}", "\\leq"),
219 ("\u{2265}", "\\geq"),
220 ("\u{226A}", "\\ll"),
221 ("\u{226B}", "\\gg"),
222 ("\u{2248}", "\\approx"),
223 ("\u{2261}", "\\equiv"),
224 ("\u{223C}", "\\sim"),
225 ("\u{2245}", "\\cong"),
226 ("\u{221D}", "\\propto"),
227 ],
228 },
229 SymbolCategory {
230 name: "Operators",
231 symbols: &[
232 ("\u{00D7}", "\\times"),
233 ("\u{00F7}", "\\div"),
234 ("\u{00B1}", "\\pm"),
235 ("\u{2213}", "\\mp"),
236 ("\u{22C5}", "\\cdot"),
237 ("\u{2218}", "\\circ"),
238 ("\u{2295}", "\\oplus"),
239 ("\u{2297}", "\\otimes"),
240 ("\u{221A}", "\\sqrt{}"),
241 ("\u{221E}", "\\infty"),
242 ("\u{2202}", "\\partial"),
243 ("\u{2207}", "\\nabla"),
244 ("\u{222B}", "\\int"),
245 ("\u{220F}", "\\prod"),
246 ("\u{2211}", "\\sum"),
247 ],
248 },
249];
250
251#[component]
252pub fn SymbolPalette(
253 on_insert: EventHandler<String>,
254) -> Element {
255 let mut search = use_signal(String::new);
256
257 let search_lower = search.read().to_lowercase();
258 let filtered_categories: Vec<_> = if search_lower.is_empty() {
259 SYMBOL_CATEGORIES.iter().collect()
260 } else {
261 SYMBOL_CATEGORIES
262 .iter()
263 .filter(|cat| {
264 cat.name.to_lowercase().contains(&search_lower)
265 || cat.symbols.iter().any(|(_, latex)| latex.contains(&search_lower))
266 })
267 .collect()
268 };
269
270 rsx! {
271 style { "{SYMBOL_PALETTE_STYLE}" }
272
273 div { class: "symbol-palette",
274 div { class: "symbol-palette-header",
276 span { class: "symbol-palette-title", "Symbols" }
277 div { class: "symbol-search",
278 input {
279 r#type: "text",
280 placeholder: "Search symbols...",
281 value: "{search}",
282 oninput: move |e| search.set(e.value()),
283 }
284 }
285 }
286
287 div { class: "symbol-categories",
289 for category in filtered_categories {
290 div { class: "symbol-category", key: "{category.name}",
291 div { class: "symbol-category-header", "{category.name}" }
292 div { class: "symbol-grid",
293 for (symbol, latex) in category.symbols.iter() {
294 button {
295 class: "symbol-btn",
296 key: "{latex}",
297 title: "{latex}",
298 onclick: move |_| on_insert.call(latex.to_string()),
299 "{symbol}"
300 }
301 }
302 }
303 }
304 }
305 }
306 }
307 }
308}