logicaffeine_web/ui/components/
context_view.rs1use dioxus::prelude::*;
17
18const CONTEXT_VIEW_STYLE: &str = r#"
19.context-view {
20 display: flex;
21 flex-direction: column;
22 height: 100%;
23 background: #12161c;
24 font-family: 'SF Mono', 'Fira Code', monospace;
25}
26
27.context-view-header {
28 padding: 12px 16px;
29 border-bottom: 1px solid rgba(255, 255, 255, 0.08);
30 background: rgba(255, 255, 255, 0.02);
31}
32
33.context-view-title {
34 font-size: 14px;
35 font-weight: 600;
36 color: rgba(255, 255, 255, 0.9);
37}
38
39.context-sections {
40 flex: 1;
41 overflow: auto;
42 padding: 12px;
43}
44
45.context-section {
46 margin-bottom: 20px;
47}
48
49.context-section-header {
50 font-size: 11px;
51 font-weight: 600;
52 text-transform: uppercase;
53 letter-spacing: 0.5px;
54 color: rgba(255, 255, 255, 0.4);
55 margin-bottom: 8px;
56 padding-bottom: 4px;
57 border-bottom: 1px solid rgba(255, 255, 255, 0.06);
58}
59
60.context-item {
61 padding: 8px 10px;
62 margin-bottom: 4px;
63 background: rgba(255, 255, 255, 0.02);
64 border-radius: 4px;
65 font-size: 12px;
66 line-height: 1.5;
67}
68
69.context-item-name {
70 color: #61afef;
71 font-weight: 500;
72}
73
74.context-item-type {
75 color: rgba(255, 255, 255, 0.6);
76 margin-left: 8px;
77}
78
79.context-item-type::before {
80 content: ": ";
81 color: rgba(255, 255, 255, 0.3);
82}
83
84.context-item-body {
85 margin-top: 4px;
86 padding-top: 4px;
87 border-top: 1px dashed rgba(255, 255, 255, 0.06);
88 color: rgba(255, 255, 255, 0.5);
89 font-size: 11px;
90}
91
92.context-empty {
93 display: flex;
94 flex-direction: column;
95 align-items: center;
96 justify-content: center;
97 height: 100%;
98 color: rgba(255, 255, 255, 0.4);
99 text-align: center;
100 padding: 20px;
101 font-size: 13px;
102}
103
104/* Type keywords highlighting */
105.type-keyword {
106 color: #c678dd;
107}
108
109.type-name {
110 color: #e5c07b;
111}
112
113.type-arrow {
114 color: rgba(255, 255, 255, 0.4);
115}
116"#;
117
118#[derive(Clone, PartialEq)]
120pub struct ContextEntry {
121 pub name: String,
122 pub ty: String,
123 pub body: Option<String>,
124 pub kind: EntryKind,
125}
126
127#[derive(Clone, Copy, PartialEq)]
128pub enum EntryKind {
129 Definition,
130 Inductive,
131 Constructor,
132}
133
134#[component]
135pub fn ContextView(
136 definitions: Vec<ContextEntry>,
137 inductives: Vec<ContextEntry>,
138) -> Element {
139 rsx! {
140 style { "{CONTEXT_VIEW_STYLE}" }
141
142 div { class: "context-view",
143 div { class: "context-view-header",
145 span { class: "context-view-title", "Context" }
146 }
147
148 div { class: "context-sections",
150 if definitions.is_empty() && inductives.is_empty() {
151 div { class: "context-empty",
152 "No definitions in scope"
153 }
154 } else {
155 if !inductives.is_empty() {
157 div { class: "context-section",
158 div { class: "context-section-header", "Inductive Types" }
159 for entry in inductives.iter() {
160 ContextItem {
161 key: "{entry.name}",
162 entry: entry.clone(),
163 }
164 }
165 }
166 }
167
168 if !definitions.is_empty() {
170 div { class: "context-section",
171 div { class: "context-section-header", "Definitions" }
172 for entry in definitions.iter() {
173 ContextItem {
174 key: "{entry.name}",
175 entry: entry.clone(),
176 }
177 }
178 }
179 }
180 }
181 }
182 }
183 }
184}
185
186#[component]
187fn ContextItem(entry: ContextEntry) -> Element {
188 rsx! {
189 div { class: "context-item",
190 div {
191 span { class: "context-item-name", "{entry.name}" }
192 span { class: "context-item-type", "{entry.ty}" }
193 }
194 if let Some(body) = &entry.body {
195 div { class: "context-item-body", ":= {body}" }
196 }
197 }
198 }
199}