|
1 | 1 | <script setup> |
2 | 2 | import { computed, onMounted, onUnmounted, ref } from "vue" |
| 3 | + import { Terminal, FileText, Copy } from "lucide-vue-next" |
3 | 4 |
|
4 | 5 | // ── Raw Python snippet imports ────────────────────────────────────────────── |
5 | 6 | import applicationRaw from "../snippets/application.py?raw" |
|
78 | 79 | const currentTabData = computed(() => tabData[activeCategory.value]) |
79 | 80 |
|
80 | 81 | const currentCode = computed(() => { |
81 | | - if (!highlighterReady.value) return "" |
| 82 | + if (!highlighterReady.value) return escapeHtml(currentTabData.value.raw[activeFileIndex.value]) |
82 | 83 | const h = highlighted.value[activeCategory.value] |
83 | 84 | if (h) return h[activeFileIndex.value] ?? escapeHtml(currentTabData.value.raw[activeFileIndex.value]) |
84 | 85 | return escapeHtml(currentTabData.value.raw[activeFileIndex.value]) |
|
109 | 110 | isTransitioning.value = false |
110 | 111 | }, 150) |
111 | 112 | } |
| 113 | +
|
| 114 | + // ── Synchronized horizontal scroll (file tabs ↔ code body) ───────────────── |
| 115 | + const fileTabsRef = ref(null) |
| 116 | + const codeBodyRef = ref(null) |
| 117 | +
|
| 118 | + function onCodeScroll() { |
| 119 | + if (fileTabsRef.value && codeBodyRef.value) |
| 120 | + fileTabsRef.value.scrollLeft = codeBodyRef.value.scrollLeft |
| 121 | + } |
| 122 | +
|
| 123 | + function onTabsScroll() { |
| 124 | + if (fileTabsRef.value && codeBodyRef.value) |
| 125 | + codeBodyRef.value.scrollLeft = fileTabsRef.value.scrollLeft |
| 126 | + } |
112 | 127 | </script> |
113 | 128 |
|
114 | 129 | <template> |
|
149 | 164 | <div class="flex flex-col sm:flex-row gap-4"> |
150 | 165 | <a href="/docs/getting-started" class="bg-brand-teal text-white px-8 py-4 rounded font-label-md font-bold flex items-center justify-center gap-2 transition-all hover:brightness-110 shadow-lg shadow-brand-teal/20 active:scale-[0.98]"> |
151 | 166 | Initialize Project |
152 | | - <span class="material-symbols-outlined text-[18px]">terminal</span> |
| 167 | + <Terminal :size="18" /> |
153 | 168 | </a> |
154 | 169 | </div> |
155 | 170 |
|
|
192 | 207 |
|
193 | 208 | <!-- Chrome bar --> |
194 | 209 | <div class="flex items-center justify-between px-4 py-3 bg-editor-chrome border-b border-brand-teal/10"> |
195 | | - <div class="flex items-center gap-6"> |
| 210 | + <div class="flex items-center gap-6 min-w-0 flex-1 overflow-hidden"> |
196 | 211 | <!-- Traffic lights --> |
197 | | - <div class="flex gap-1.5"> |
| 212 | + <div class="flex gap-1.5 shrink-0"> |
198 | 213 | <div class="w-3 h-3 rounded-full bg-[#ff5f56]"></div> |
199 | 214 | <div class="w-3 h-3 rounded-full bg-[#ffbd2e]"></div> |
200 | 215 | <div class="w-3 h-3 rounded-full bg-[#27c93f]"></div> |
201 | 216 | </div> |
202 | 217 | <!-- File tabs --> |
203 | | - <div class="flex gap-4"> |
| 218 | + <div ref="fileTabsRef" class="flex gap-4 overflow-x-auto scrollbar-none min-w-0 flex-1" @scroll="onTabsScroll"> |
204 | 219 | <button |
205 | 220 | v-for="(file, idx) in currentTabData.files" |
206 | 221 | :key="file" |
207 | | - class="px-3 py-1 flex items-center gap-2 rounded transition-all" |
| 222 | + class="px-3 py-1 flex items-center gap-2 rounded transition-all shrink-0" |
208 | 223 | :class="activeFileIndex === idx ? 'bg-brand-teal/10 border-b-2 border-brand-teal' : 'opacity-50 hover:opacity-75'" |
209 | 224 | @click="switchFile(idx)" |
210 | 225 | > |
211 | | - <span class="material-symbols-outlined text-xs" :class="activeFileIndex === idx ? 'text-brand-teal' : 'text-outline-variant'">description</span> |
| 226 | + <FileText :size="12" :class="activeFileIndex === idx ? 'text-brand-teal' : 'text-outline-variant'" /> |
212 | 227 | <span class="text-xs font-mono tracking-tight" :class="activeFileIndex === idx ? 'text-white' : 'text-outline-variant'">{{ file }}</span> |
213 | 228 | </button> |
214 | 229 | </div> |
215 | 230 | </div> |
216 | | - <span class="material-symbols-outlined text-outline-variant text-sm hover:text-brand-teal cursor-pointer transition-colors">content_copy</span> |
| 231 | + <Copy :size="14" class="text-outline-variant hover:text-brand-teal cursor-pointer transition-colors" /> |
217 | 232 | </div> |
218 | 233 |
|
219 | 234 | <!-- Code body --> |
220 | | - <div class="p-6 md:p-8 font-mono text-[13px] leading-relaxed h-[410px] overflow-y-auto overflow-x-hidden"> |
| 235 | + <div ref="codeBodyRef" class="p-6 md:p-8 font-mono text-[13px] leading-relaxed h-[410px] overflow-y-auto overflow-x-auto" @scroll="onCodeScroll"> |
221 | 236 | <pre |
222 | | - class="text-white transition-all duration-200 m-0 p-0 bg-transparent" |
| 237 | + class="text-white transition-all duration-200 m-0 p-0 bg-transparent whitespace-pre" |
223 | 238 | :class="{ 'opacity-0 translate-y-1': isTransitioning }" |
224 | 239 | ><code v-html="currentCode" class="bg-transparent"></code></pre> |
225 | 240 | </div> |
|
0 commit comments