Skip to main content

logicaffeine_web/ui/components/
symbol_dictionary.rs

1//! Symbol Dictionary component for displaying FOL symbol meanings.
2//!
3//! Automatically extracts symbols from a First-Order Logic formula
4//! and displays them grouped by category with descriptions.
5
6use dioxus::prelude::*;
7use logicaffeine_language::symbol_dict::{extract_symbols, group_symbols_by_kind, SymbolKind};
8
9const SYMBOL_DICT_STYLE: &str = r#"
10.symbol-dictionary {
11    background: rgba(255, 255, 255, 0.03);
12    border: 1px solid rgba(255, 255, 255, 0.08);
13    border-radius: 12px;
14    padding: 16px;
15    margin-top: 16px;
16}
17
18.symbol-dictionary.collapsed {
19    padding: 12px 16px;
20    cursor: pointer;
21}
22
23.symbol-dictionary.collapsed:hover {
24    background: rgba(255, 255, 255, 0.05);
25}
26
27.symbol-dict-header {
28    display: flex;
29    align-items: center;
30    justify-content: space-between;
31    font-size: 12px;
32    font-weight: 600;
33    text-transform: uppercase;
34    letter-spacing: 0.5px;
35    color: rgba(229, 231, 235, 0.56);
36    margin-bottom: 12px;
37}
38
39.symbol-dictionary.collapsed .symbol-dict-header {
40    margin-bottom: 0;
41}
42
43.symbol-dict-toggle {
44    color: rgba(229, 231, 235, 0.4);
45    font-size: 14px;
46    cursor: pointer;
47    transition: transform 0.2s ease;
48}
49
50.symbol-dictionary.collapsed .symbol-dict-toggle {
51    transform: rotate(-90deg);
52}
53
54.symbol-dict-content {
55    display: flex;
56    flex-direction: column;
57    gap: 16px;
58}
59
60.symbol-group {
61    margin-bottom: 0;
62}
63
64.symbol-group-title {
65    font-size: 11px;
66    font-weight: 600;
67    color: #a78bfa;
68    margin-bottom: 8px;
69    text-transform: uppercase;
70    letter-spacing: 0.5px;
71}
72
73.symbol-group-title.quantifier {
74    color: #60a5fa;
75}
76
77.symbol-group-title.connective {
78    color: #f472b6;
79}
80
81.symbol-group-title.modal {
82    color: #c084fc;
83}
84
85.symbol-group-title.predicate {
86    color: #4ade80;
87}
88
89.symbol-group-title.variable {
90    color: #fbbf24;
91}
92
93.symbol-group-title.constant {
94    color: #fb923c;
95}
96
97.symbol-entries {
98    display: flex;
99    flex-direction: column;
100    gap: 4px;
101}
102
103.symbol-entry {
104    display: flex;
105    align-items: center;
106    gap: 12px;
107    padding: 8px 10px;
108    border-radius: 6px;
109    transition: background 0.15s ease;
110}
111
112.symbol-entry:hover {
113    background: rgba(255, 255, 255, 0.05);
114}
115
116.symbol-glyph {
117    font-size: 18px;
118    font-family: 'SF Mono', 'Fira Code', 'JetBrains Mono', monospace;
119    color: #60a5fa;
120    min-width: 32px;
121    text-align: center;
122}
123
124.symbol-glyph.quantifier {
125    color: #60a5fa;
126}
127
128.symbol-glyph.connective {
129    color: #f472b6;
130}
131
132.symbol-glyph.modal {
133    color: #c084fc;
134}
135
136.symbol-glyph.predicate {
137    color: #4ade80;
138}
139
140.symbol-glyph.variable {
141    color: #fbbf24;
142}
143
144.symbol-glyph.constant {
145    color: #fb923c;
146}
147
148.symbol-desc {
149    font-size: 13px;
150    color: rgba(229, 231, 235, 0.72);
151}
152
153.symbol-dictionary-empty {
154    color: rgba(229, 231, 235, 0.4);
155    font-size: 13px;
156    text-align: center;
157    padding: 8px;
158}
159
160/* Compact inline variant */
161.symbol-dictionary.inline {
162    padding: 12px;
163    margin-top: 8px;
164}
165
166.symbol-dictionary.inline .symbol-dict-header {
167    margin-bottom: 8px;
168}
169
170.symbol-dictionary.inline .symbol-entries {
171    flex-direction: row;
172    flex-wrap: wrap;
173    gap: 8px;
174}
175
176.symbol-dictionary.inline .symbol-entry {
177    padding: 4px 8px;
178    background: rgba(255, 255, 255, 0.03);
179    border-radius: 4px;
180}
181
182.symbol-dictionary.inline .symbol-glyph {
183    font-size: 14px;
184    min-width: 20px;
185}
186
187.symbol-dictionary.inline .symbol-desc {
188    font-size: 11px;
189}
190"#;
191
192/// Props for the SymbolDictionary component
193#[derive(Props, Clone, PartialEq)]
194pub struct SymbolDictionaryProps {
195    /// The FOL formula to extract symbols from
196    logic: String,
197    /// Whether to start collapsed
198    #[props(default = false)]
199    collapsed: bool,
200    /// Use compact inline variant
201    #[props(default = false)]
202    inline: bool,
203}
204
205/// Symbol Dictionary component that auto-generates from FOL output
206#[component]
207pub fn SymbolDictionary(props: SymbolDictionaryProps) -> Element {
208    let mut is_collapsed = use_signal(|| props.collapsed);
209
210    let symbols = extract_symbols(&props.logic);
211
212    if symbols.is_empty() {
213        return rsx! { "" };
214    }
215
216    let grouped = group_symbols_by_kind(&symbols);
217
218    let container_class = format!(
219        "symbol-dictionary{}{}",
220        if *is_collapsed.read() { " collapsed" } else { "" },
221        if props.inline { " inline" } else { "" }
222    );
223
224    rsx! {
225        style { "{SYMBOL_DICT_STYLE}" }
226        div {
227            class: "{container_class}",
228            onclick: move |_| {
229                if *is_collapsed.read() {
230                    is_collapsed.set(false);
231                }
232            },
233
234            div { class: "symbol-dict-header",
235                span { "Symbol Dictionary" }
236                span {
237                    class: "symbol-dict-toggle",
238                    onclick: move |e| {
239                        e.stop_propagation();
240                        let current = *is_collapsed.read();
241                        is_collapsed.set(!current);
242                    },
243                    "▼"
244                }
245            }
246
247            if !*is_collapsed.read() {
248                div { class: "symbol-dict-content",
249                    for (kind, entries) in grouped {
250                        {
251                            let kind_class = match kind {
252                                SymbolKind::Quantifier => "quantifier",
253                                SymbolKind::Connective => "connective",
254                                SymbolKind::Modal => "modal",
255                                SymbolKind::Predicate => "predicate",
256                                SymbolKind::Variable => "variable",
257                                SymbolKind::Constant => "constant",
258                                SymbolKind::Temporal => "temporal",
259                                SymbolKind::Identity => "connective",
260                                SymbolKind::Punctuation => "variable",
261                            };
262
263                            rsx! {
264                                div { class: "symbol-group",
265                                    key: "{kind_class}",
266                                    div { class: "symbol-group-title {kind_class}",
267                                        "{kind.label()}"
268                                    }
269                                    div { class: "symbol-entries",
270                                        for entry in entries {
271                                            div { class: "symbol-entry",
272                                                key: "{entry.symbol}",
273                                                span { class: "symbol-glyph {kind_class}",
274                                                    "{entry.symbol}"
275                                                }
276                                                span { class: "symbol-desc",
277                                                    "{entry.description}"
278                                                }
279                                            }
280                                        }
281                                    }
282                                }
283                            }
284                        }
285                    }
286                }
287            }
288        }
289    }
290}
291
292/// Compact inline symbol legend for quick reference
293#[component]
294pub fn SymbolLegend(logic: String) -> Element {
295    rsx! {
296        SymbolDictionary {
297            logic: logic,
298            inline: true,
299            collapsed: false,
300        }
301    }
302}
303
304#[cfg(test)]
305mod tests {
306    use super::*;
307
308    #[test]
309    fn test_symbol_kind_class_names() {
310        assert_eq!(SymbolKind::Quantifier.label(), "Quantifier");
311        assert_eq!(SymbolKind::Connective.label(), "Connective");
312    }
313}