petter2025 commited on
Commit
1084c42
·
verified ·
1 Parent(s): ea68e5a

Create enhancements/progressive_disclosure.py

Browse files
ui/enhancements/progressive_disclosure.py ADDED
@@ -0,0 +1,279 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Progressive disclosure components for better UX
3
+ Collapsible sections, tooltips, and conditional displays
4
+ """
5
+
6
+ class ProgressiveDisclosure:
7
+ """Components that reveal information gradually"""
8
+
9
+ @staticmethod
10
+ def create_collapsible_section(title: str, content: str, initially_open: bool = False,
11
+ badge_text: str = "", badge_color: str = "#3b82f6"):
12
+ """Create expandable/collapsible section with optional badge"""
13
+ section_id = f"section_{hash(title)}"
14
+
15
+ badge_html = ""
16
+ if badge_text:
17
+ badge_html = f"""
18
+ <span style="padding: 3px 10px; background: {badge_color}20; color: {badge_color};
19
+ border-radius: 12px; font-size: 11px; font-weight: bold; margin-left: 10px;">
20
+ {badge_text}
21
+ </span>
22
+ """
23
+
24
+ return f"""
25
+ <div class="collapsible-section" style="margin: 1rem 0;">
26
+ <button
27
+ onclick="toggleSection('{section_id}')"
28
+ class="section-toggle {'open' if initially_open else ''}"
29
+ aria-expanded="{str(initially_open).lower()}"
30
+ aria-controls="{section_id}"
31
+ style="width: 100%; padding: 1rem; background: var(--color-neutral-50);
32
+ border: 1px solid var(--color-border); border-radius: 0.5rem;
33
+ text-align: left; font-weight: 600; color: var(--color-text);
34
+ cursor: pointer; display: flex; justify-content: space-between;
35
+ align-items: center; transition: background 0.2s;"
36
+ >
37
+ <div style="display: flex; align-items: center; gap: 0.5rem;">
38
+ <span style="font-size: 1.25rem;">{'▼' if initially_open else '▶'}</span>
39
+ <span>{title}</span>
40
+ {badge_html}
41
+ </div>
42
+ </button>
43
+ <div
44
+ id="{section_id}"
45
+ class="section-content"
46
+ style="display: {'block' if initially_open else 'none'}; padding: 1rem;
47
+ background: white; border: 1px solid var(--color-border);
48
+ border-top: none; border-radius: 0 0 0.5rem 0.5rem;"
49
+ aria-hidden="{str(not initially_open).lower()}"
50
+ >
51
+ {content}
52
+ </div>
53
+ </div>
54
+
55
+ <script>
56
+ function toggleSection(id) {{
57
+ const section = document.getElementById(id);
58
+ const button = section.previousElementSibling;
59
+ const isExpanded = section.style.display === 'none';
60
+
61
+ // Toggle visibility
62
+ section.style.display = isExpanded ? 'block' : 'none';
63
+ section.setAttribute('aria-hidden', !isExpanded);
64
+
65
+ // Update button state
66
+ button.classList.toggle('open', isExpanded);
67
+ button.setAttribute('aria-expanded', isExpanded);
68
+
69
+ // Update toggle icon
70
+ const icon = button.querySelector('span:first-child');
71
+ icon.textContent = isExpanded ? '▼' : '▶';
72
+
73
+ // Smooth scroll if opening
74
+ if (isExpanded) {{
75
+ section.scrollIntoView({{ behavior: 'smooth', block: 'nearest' }});
76
+ }}
77
+ }}
78
+ </script>
79
+ """
80
+
81
+ @staticmethod
82
+ def create_tooltip(text: str, tooltip_content: str, position: str = "top"):
83
+ """Create text with a hover tooltip"""
84
+ position_classes = {
85
+ "top": "bottom: 125%; left: 50%; margin-left: -100px;",
86
+ "bottom": "top: 125%; left: 50%; margin-left: -100px;",
87
+ "left": "top: 50%; right: 125%; margin-top: -15px;",
88
+ "right": "top: 50%; left: 125%; margin-top: -15px;"
89
+ }
90
+
91
+ position_css = position_classes.get(position, position_classes["top"])
92
+
93
+ return f"""
94
+ <span class="tooltip-container" style="position: relative; display: inline-block;">
95
+ {text}
96
+ <span class="tooltip-text" style="
97
+ visibility: hidden; width: 200px; background: var(--color-neutral-800);
98
+ color: white; text-align: center; padding: 0.5rem; border-radius: 0.375rem;
99
+ position: absolute; z-index: 1000; {position_css} opacity: 0;
100
+ transition: opacity 0.3s; font-size: 0.875rem; font-weight: normal;
101
+ box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1);
102
+ ">
103
+ {tooltip_content}
104
+ </span>
105
+ </span>
106
+
107
+ <style>
108
+ .tooltip-container:hover .tooltip-text {{
109
+ visibility: visible;
110
+ opacity: 1;
111
+ }}
112
+ </style>
113
+ """
114
+
115
+ @staticmethod
116
+ def create_progressive_form(steps: list):
117
+ """Create a multi-step form with progressive disclosure"""
118
+ steps_html = ""
119
+ for i, step in enumerate(steps):
120
+ step_id = f"step_{i}"
121
+ steps_html += f"""
122
+ <div id="{step_id}" class="form-step"
123
+ style="display: {'block' if i == 0 else 'none'}; margin-bottom: 2rem;">
124
+ <div style="display: flex; align-items: center; gap: 0.5rem; margin-bottom: 1rem;">
125
+ <div style="width: 28px; height: 28px; background: {'var(--color-primary)' if i == 0 else 'var(--color-neutral-300)'};
126
+ color: white; border-radius: 50%; display: flex; align-items: center;
127
+ justify-content: center; font-weight: bold; font-size: 0.875rem;">
128
+ {i + 1}
129
+ </div>
130
+ <h3 style="margin: 0; font-size: 1.125rem; color: var(--color-text);">
131
+ {step['title']}
132
+ </h3>
133
+ </div>
134
+ <div style="background: var(--color-neutral-50); border-radius: 0.5rem; padding: 1.5rem;">
135
+ {step['content']}
136
+ </div>
137
+ </div>
138
+ """
139
+
140
+ # Navigation buttons HTML
141
+ nav_html = """
142
+ <div style="display: flex; justify-content: space-between; margin-top: 2rem;">
143
+ <button onclick="previousStep()"
144
+ style="padding: 0.5rem 1rem; background: var(--color-neutral-100);
145
+ border: 1px solid var(--color-border); border-radius: 0.375rem;
146
+ cursor: pointer; font-weight: 500;" id="prevBtn">
147
+ ← Previous
148
+ </button>
149
+ <button onclick="nextStep()"
150
+ style="padding: 0.5rem 1rem; background: var(--color-primary);
151
+ color: white; border: none; border-radius: 0.375rem;
152
+ cursor: pointer; font-weight: 500;" id="nextBtn">
153
+ Next →
154
+ </button>
155
+ </div>
156
+ """
157
+
158
+ # JavaScript for step navigation
159
+ js = f"""
160
+ <script>
161
+ let currentStep = 0;
162
+ const totalSteps = {len(steps)};
163
+
164
+ function updateButtons() {{
165
+ document.getElementById('prevBtn').style.display = currentStep === 0 ? 'none' : 'block';
166
+ document.getElementById('nextBtn').textContent = currentStep === totalSteps - 1 ? 'Finish' : 'Next →';
167
+ }}
168
+
169
+ function showStep(stepIndex) {{
170
+ // Hide all steps
171
+ document.querySelectorAll('.form-step').forEach(step => {{
172
+ step.style.display = 'none';
173
+ }});
174
+
175
+ // Show current step
176
+ document.getElementById('step_' + stepIndex).style.display = 'block';
177
+ currentStep = stepIndex;
178
+ updateButtons();
179
+
180
+ // Smooth scroll to step
181
+ document.getElementById('step_' + stepIndex).scrollIntoView({{
182
+ behavior: 'smooth',
183
+ block: 'start'
184
+ }});
185
+ }}
186
+
187
+ function nextStep() {{
188
+ if (currentStep < totalSteps - 1) {{
189
+ showStep(currentStep + 1);
190
+ }} else {{
191
+ // Form completion logic
192
+ alert('Form completed!');
193
+ }}
194
+ }}
195
+
196
+ function previousStep() {{
197
+ if (currentStep > 0) {{
198
+ showStep(currentStep - 1);
199
+ }}
200
+ }}
201
+
202
+ // Initialize
203
+ updateButtons();
204
+ </script>
205
+ """
206
+
207
+ return f"""
208
+ <div style="max-width: 800px; margin: 0 auto;">
209
+ {steps_html}
210
+ {nav_html}
211
+ {js}
212
+ </div>
213
+ """
214
+
215
+ @staticmethod
216
+ def create_accordion(items: list, allow_multiple: bool = False):
217
+ """Create an accordion with multiple collapsible items"""
218
+ accordion_id = f"accordion_{hash(str(items))}"
219
+
220
+ items_html = ""
221
+ for i, item in enumerate(items):
222
+ item_id = f"{accordion_id}_item_{i}"
223
+ items_html += f"""
224
+ <div style="border: 1px solid var(--color-border); border-radius: 0.5rem;
225
+ margin-bottom: 0.5rem; overflow: hidden;">
226
+ <button
227
+ onclick="toggleAccordionItem('{item_id}', {str(allow_multiple).lower()})"
228
+ style="width: 100%; padding: 1rem; background: var(--color-neutral-50);
229
+ border: none; text-align: left; font-weight: 600; color: var(--color-text);
230
+ cursor: pointer; display: flex; justify-content: space-between;
231
+ align-items: center; transition: background 0.2s;"
232
+ >
233
+ <span>{item['title']}</span>
234
+ <span style="font-size: 1.25rem; transition: transform 0.2s;" id="{item_id}_icon">
235
+
236
+ </span>
237
+ </button>
238
+ <div
239
+ id="{item_id}"
240
+ style="display: none; padding: 1rem; background: white;"
241
+ >
242
+ {item['content']}
243
+ </div>
244
+ </div>
245
+ """
246
+
247
+ return f"""
248
+ <div id="{accordion_id}">
249
+ {items_html}
250
+ </div>
251
+
252
+ <script>
253
+ function toggleAccordionItem(itemId, allowMultiple) {{
254
+ const content = document.getElementById(itemId);
255
+ const icon = document.getElementById(itemId + '_icon');
256
+
257
+ if (!allowMultiple) {{
258
+ // Close all other items in this accordion
259
+ const accordion = document.getElementById('{accordion_id}');
260
+ accordion.querySelectorAll('div[id^="{accordion_id}_item_"]').forEach(item => {{
261
+ if (item.id !== itemId) {{
262
+ item.style.display = 'none';
263
+ const otherIcon = document.getElementById(item.id + '_icon');
264
+ if (otherIcon) otherIcon.textContent = '▼';
265
+ }}
266
+ }});
267
+ }}
268
+
269
+ // Toggle current item
270
+ if (content.style.display === 'none') {{
271
+ content.style.display = 'block';
272
+ icon.textContent = '▲';
273
+ }} else {{
274
+ content.style.display = 'none';
275
+ icon.textContent = '▼';
276
+ }}
277
+ }}
278
+ </script>
279
+ """