Skip to content

Commit fbb14a3

Browse files
committed
feat: enhance CodePanel with mobile sections and responsive layout
- Added separate syntax highlighting for mobile code sections - Implemented mobile-specific UI for code display and step details - Introduced mobile event stream and explanation panels with slide-up animations - Updated layout styles for responsiveness and full-height views - Added mobile toggle buttons for event stream and explanation panels - Modified flow code data structure to include mobileCode variants - Improved page structure with sticky header and adaptive grid layouts
1 parent c82be00 commit fbb14a3

File tree

6 files changed

+735
-400
lines changed

6 files changed

+735
-400
lines changed

apps/demo/src/lib/components/CodePanel.svelte

Lines changed: 130 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
}>();
2020
2121
let highlightedCode = $state('');
22+
let highlightedSections = $state<Record<string, string>>({});
2223
let codeContainer: HTMLElement | undefined = $state(undefined);
24+
let isMobile = $state(false);
2325
2426
// Calculate step blocks (groups of lines) for status icon positioning
2527
const stepBlocks = $derived.by(() => {
@@ -45,35 +47,69 @@
4547
return null;
4648
}
4749
48-
// If flow has started but this step has no status yet, show as created
50+
// If flow has started but this step has no status yet, don't show indicator
4951
if (!status) {
50-
return 'created';
52+
return null;
53+
}
54+
55+
// Only show indicators for started and completed
56+
if (status === 'started' || status === 'completed') {
57+
return status;
5158
}
5259
53-
return status;
60+
return null;
5461
}
5562
5663
onMount(async () => {
57-
// Generate syntax highlighted HTML using Shiki
64+
// Detect mobile
65+
isMobile = window.innerWidth < 768;
66+
window.addEventListener('resize', () => {
67+
isMobile = window.innerWidth < 768;
68+
});
69+
70+
// Generate syntax highlighted HTML for full code
5871
highlightedCode = await codeToHtml(FLOW_CODE, {
5972
lang: 'typescript',
6073
theme: 'night-owl',
6174
transformers: [
6275
{
6376
line(node) {
64-
// Add .line class to each line for click handling
6577
node.properties.class = 'line';
6678
}
6779
}
6880
]
6981
});
7082
71-
// Add click handlers to lines after rendering - need small delay
83+
// Generate separate highlighted sections for mobile (use mobileCode if available)
84+
for (const [slug, section] of Object.entries(FLOW_SECTIONS)) {
85+
const codeToRender = section.mobileCode || section.code;
86+
highlightedSections[slug] = await codeToHtml(codeToRender, {
87+
lang: 'typescript',
88+
theme: 'night-owl'
89+
});
90+
}
91+
92+
// Add click handlers to lines after rendering
93+
setupClickHandlersDelayed();
94+
});
95+
96+
function setupClickHandlersDelayed() {
7297
setTimeout(() => {
7398
if (codeContainer) {
7499
setupClickHandlers();
75100
}
76101
}, 50);
102+
}
103+
104+
// Re-setup handlers when view changes
105+
$effect(() => {
106+
const mobile = isMobile;
107+
const selected = selectedStep;
108+
109+
// Setup handlers for full code view (desktop or mobile with no selection)
110+
if (codeContainer && (!mobile || !selected || selected === 'flow_config')) {
111+
setupClickHandlersDelayed();
112+
}
77113
});
78114
79115
function setupClickHandlers() {
@@ -92,23 +128,26 @@
92128
(line as HTMLElement).style.cursor = 'pointer';
93129
94130
// Click handler
95-
line.addEventListener('click', () => {
131+
const clickHandler = () => {
96132
console.log('CodePanel: Line clicked, stepSlug:', stepSlug);
97133
// Clear hover state before navigating
98134
dispatch('step-hovered', { stepSlug: null });
99135
100136
// All sections (including flow_config) dispatch their slug
101137
dispatch('step-selected', { stepSlug });
102-
});
103-
104-
// Hover handlers - dispatch hover events
105-
line.addEventListener('mouseenter', () => {
106-
dispatch('step-hovered', { stepSlug });
107-
});
108-
109-
line.addEventListener('mouseleave', () => {
110-
dispatch('step-hovered', { stepSlug: null });
111-
});
138+
};
139+
line.addEventListener('click', clickHandler);
140+
141+
// Hover handlers - dispatch hover events (desktop only)
142+
if (!isMobile) {
143+
line.addEventListener('mouseenter', () => {
144+
dispatch('step-hovered', { stepSlug });
145+
});
146+
147+
line.addEventListener('mouseleave', () => {
148+
dispatch('step-hovered', { stepSlug: null });
149+
});
150+
}
112151
}
113152
});
114153
}
@@ -124,6 +163,7 @@
124163
if (!codeContainer) return;
125164
126165
const lines = codeContainer.querySelectorAll('.line');
166+
127167
lines.forEach((line) => {
128168
const stepSlug = (line as HTMLElement).getAttribute('data-step');
129169
(line as HTMLElement).classList.remove('line-selected', 'line-hovered', 'line-dimmed');
@@ -149,30 +189,51 @@
149189
</script>
150190

151191
<div class="code-panel-wrapper">
152-
<div class="code-panel" bind:this={codeContainer}>
153-
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
154-
{@html highlightedCode}
155-
156-
<!-- Step status icons overlaid on code blocks -->
157-
{#each stepBlocks as block (block.stepSlug)}
158-
{@const stepStatus = getStepStatus(block.stepSlug)}
159-
{#if stepStatus}
160-
{@const blockHeight = (block.endLine - block.startLine + 1) * 1.5}
161-
{@const blockTop = (block.startLine - 1) * 1.5}
162-
{@const iconTop = blockTop + blockHeight / 2}
163-
{@const isDimmed = selectedStep && block.stepSlug !== selectedStep}
164-
<div
165-
class="step-status-container"
166-
class:status-dimmed={isDimmed}
167-
data-step={block.stepSlug}
168-
data-start-line={block.startLine}
169-
style="top: calc({iconTop}em + 12px);"
170-
>
171-
<StatusBadge status={stepStatus} variant="icon-only" size="xl" />
172-
</div>
192+
{#if isMobile && selectedStep}
193+
<!-- Mobile: Show only selected section (including flow_config) -->
194+
<div class="code-panel mobile-selected">
195+
{#if highlightedSections[selectedStep]}
196+
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
197+
{@html highlightedSections[selectedStep]}
173198
{/if}
174-
{/each}
175-
</div>
199+
</div>
200+
{:else}
201+
<!-- Desktop or no selection: Show full code -->
202+
<div class="code-panel" bind:this={codeContainer}>
203+
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
204+
{@html highlightedCode}
205+
206+
<!-- Step status indicators -->
207+
{#each stepBlocks as block (block.stepSlug)}
208+
{@const stepStatus = getStepStatus(block.stepSlug)}
209+
{#if stepStatus}
210+
{@const blockHeight = (block.endLine - block.startLine + 1) * 1.5}
211+
{@const blockTop = (block.startLine - 1) * 1.5}
212+
{@const iconTop = blockTop + blockHeight / 2}
213+
{@const isDimmed = selectedStep && block.stepSlug !== selectedStep}
214+
215+
<!-- Desktop: Icon badge -->
216+
<div
217+
class="step-status-container hidden md:block"
218+
class:status-dimmed={isDimmed}
219+
data-step={block.stepSlug}
220+
data-start-line={block.startLine}
221+
style="top: calc({iconTop}em + 12px);"
222+
>
223+
<StatusBadge status={stepStatus} variant="icon-only" size="xl" />
224+
</div>
225+
226+
<!-- Mobile: Vertical colored border -->
227+
<div
228+
class="step-status-border md:hidden status-{stepStatus}"
229+
class:status-dimmed={isDimmed}
230+
data-step={block.stepSlug}
231+
style="top: calc({blockTop}em + 12px); height: calc({blockHeight}em);"
232+
></div>
233+
{/if}
234+
{/each}
235+
</div>
236+
{/if}
176237
</div>
177238

178239
<style>
@@ -183,6 +244,11 @@
183244
.code-panel {
184245
overflow-x: auto;
185246
border-radius: 5px;
247+
}
248+
249+
.code-panel.mobile-selected {
250+
/* Compact height when showing only selected step on mobile */
251+
min-height: auto;
186252
font-size: 15px;
187253
background: #0d1117;
188254
position: relative;
@@ -208,7 +274,7 @@
208274
/* Mobile: Smaller padding */
209275
@media (max-width: 768px) {
210276
.code-panel :global(pre) {
211-
padding: 8px 0;
277+
padding: 16px 8px;
212278
}
213279
}
214280
@@ -289,4 +355,27 @@
289355
.step-status-container.status-dimmed {
290356
opacity: 0.4;
291357
}
358+
359+
/* Step status border (mobile - vertical bar) */
360+
.step-status-border {
361+
position: absolute;
362+
left: 0;
363+
width: 2px;
364+
pointer-events: none;
365+
transition: opacity 200ms ease;
366+
opacity: 1;
367+
}
368+
369+
/* Status colors for border based on step status */
370+
.step-status-border.status-completed {
371+
background: #10b981; /* Green */
372+
}
373+
374+
.step-status-border.status-started {
375+
background: #3b82f6; /* Blue */
376+
}
377+
378+
.step-status-border.status-dimmed {
379+
opacity: 0.3;
380+
}
292381
</style>

0 commit comments

Comments
 (0)