Skip to content

Commit 8f6228d

Browse files
committed
Add component CodeTabs
1 parent bdf04d8 commit 8f6228d

11 files changed

Lines changed: 286 additions & 0 deletions

.vitepress/theme/CodeTabs.vue

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
<script setup lang="ts">
2+
import { ref } from 'vue'
3+
import { VPImage } from 'vitepress/theme'
4+
5+
interface Tab {
6+
name: string
7+
slot: string
8+
icon?: string
9+
}
10+
11+
const props = defineProps<{
12+
tabs: Tab[]
13+
}>()
14+
15+
const activeIndex = ref(0)
16+
17+
// Icon alias to light/dark paths mapping
18+
const iconMap: Record<string, { light: string; dark: string }> = {
19+
'testo-class': {
20+
light: '/icon/light-testo-class.svg',
21+
dark: '/icon/dark-testo-class.svg',
22+
},
23+
'testo-function': {
24+
light: '/icon/light-testo-function.svg',
25+
dark: '/icon/dark-testo-function.svg',
26+
},
27+
'testo-php': {
28+
light: '/icon/light-testo-php.svg',
29+
dark: '/icon/dark-testo-php.svg',
30+
},
31+
'testo': {
32+
light: '/icon/light-khinkali-bordered.svg',
33+
dark: '/icon/dark-khinkali-bordered.svg',
34+
},
35+
}
36+
37+
const getIcon = (tab: Tab) => {
38+
const alias = tab.icon || 'testo'
39+
return iconMap[alias]
40+
}
41+
</script>
42+
43+
<template>
44+
<div class="code-tabs-ide">
45+
<!-- Tabs Bar -->
46+
<div class="ide-tabs">
47+
<button
48+
v-for="(tab, index) in tabs"
49+
:key="index"
50+
class="ide-tab"
51+
:class="{ active: activeIndex === index }"
52+
@click="activeIndex = index"
53+
>
54+
<span class="tab-icon">
55+
<VPImage :image="getIcon(tab)" />
56+
</span>
57+
<span class="tab-name">{{ tab.name }}</span>
58+
</button>
59+
</div>
60+
61+
<!-- Code Content via slots -->
62+
<div class="ide-content">
63+
<template v-for="(tab, index) in tabs" :key="tab.slot">
64+
<div v-show="activeIndex === index" class="code-slot">
65+
<slot :name="tab.slot"></slot>
66+
</div>
67+
</template>
68+
</div>
69+
</div>
70+
</template>
71+
72+
<style scoped>
73+
.code-tabs-ide {
74+
border-radius: 12px;
75+
overflow: hidden;
76+
background: var(--vp-code-block-bg);
77+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
78+
margin: 24px 0;
79+
}
80+
81+
/* Tabs */
82+
.ide-tabs {
83+
display: flex;
84+
background: #252526;
85+
border-bottom: 1px solid rgba(255,255,255,0.1);
86+
overflow-x: auto;
87+
border-radius: 12px 12px 0 0;
88+
scrollbar-width: none; /* Firefox */
89+
-ms-overflow-style: none; /* IE/Edge */
90+
}
91+
92+
.ide-tabs::-webkit-scrollbar {
93+
display: none; /* Chrome/Safari */
94+
}
95+
96+
.dark .ide-tabs {
97+
background: #1e1e1e;
98+
}
99+
100+
.ide-tab:first-child {
101+
border-radius: 12px 0 0 0;
102+
}
103+
104+
.ide-tab {
105+
display: flex;
106+
align-items: center;
107+
gap: 8px;
108+
padding: 10px 16px;
109+
background: transparent;
110+
border: none;
111+
border-right: 1px solid rgba(255,255,255,0.05);
112+
color: #888;
113+
font-size: 13px;
114+
font-family: var(--vp-font-family-base);
115+
cursor: pointer;
116+
transition: all 0.2s ease;
117+
white-space: nowrap;
118+
}
119+
120+
.ide-tab:hover {
121+
background: rgba(255,255,255,0.05);
122+
color: #ccc;
123+
}
124+
125+
.ide-tab.active {
126+
background: var(--vp-code-block-bg);
127+
color: #fff;
128+
border-bottom: 2px solid var(--vp-c-brand-1);
129+
margin-bottom: -1px;
130+
}
131+
132+
.tab-icon {
133+
width: 16px;
134+
height: 16px;
135+
flex-shrink: 0;
136+
display: flex;
137+
align-items: center;
138+
justify-content: center;
139+
}
140+
141+
.tab-icon :deep(img) {
142+
width: 16px;
143+
height: 16px;
144+
max-width: 16px;
145+
max-height: 16px;
146+
object-fit: contain;
147+
}
148+
149+
/* Content - reset VitePress code block styles */
150+
.ide-content {
151+
position: relative;
152+
}
153+
154+
.code-slot {
155+
width: 100%;
156+
}
157+
158+
/* Override VitePress code block styles inside our component */
159+
.code-slot :deep(div[class*="language-"]) {
160+
margin: 0 !important;
161+
border-radius: 0 !important;
162+
box-shadow: none !important;
163+
}
164+
165+
.code-slot :deep(.vp-code-group) {
166+
margin: 0 !important;
167+
border-radius: 0 !important;
168+
}
169+
170+
.code-slot :deep(div[class*="language-"]:hover) {
171+
transform: none !important;
172+
box-shadow: none !important;
173+
}
174+
175+
/* Hide the language label */
176+
.code-slot :deep(span.lang) {
177+
display: none;
178+
}
179+
180+
/* Hide copy button or style it */
181+
.code-slot :deep(button.copy) {
182+
top: 8px;
183+
right: 8px;
184+
}
185+
186+
/* Responsive */
187+
@media (max-width: 640px) {
188+
.ide-tab {
189+
padding: 8px 12px;
190+
font-size: 12px;
191+
}
192+
}
193+
</style>

.vitepress/theme/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { Theme } from 'vitepress'
33
import DefaultTheme from 'vitepress/theme'
44
import BlogSponsor from './BlogSponsor.vue'
55
import GitHubStars from './GitHubStars.vue'
6+
import CodeTabs from './CodeTabs.vue'
67
import './style.css'
78

89
export default {
@@ -13,4 +14,7 @@ export default {
1314
'nav-bar-content-after': () => h(GitHubStars),
1415
})
1516
},
17+
enhanceApp({ app }) {
18+
app.component('CodeTabs', CodeTabs)
19+
},
1620
} satisfies Theme

.vitepress/theme/style.css

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,3 +230,60 @@
230230
color: var(--vp-c-divider);
231231
font-size: 14px;
232232
}
233+
234+
/* Home page code demo section */
235+
.home-code-demo {
236+
max-width: 800px;
237+
margin: 32px auto 0;
238+
padding: 0 24px;
239+
}
240+
241+
/* Home page text sections */
242+
.home-section {
243+
max-width: 800px;
244+
margin: 80px auto 0;
245+
padding: 0 24px;
246+
text-align: center;
247+
}
248+
249+
.home-section-title {
250+
font-size: 32px;
251+
font-weight: 600;
252+
margin-bottom: 16px;
253+
color: var(--vp-c-text-1);
254+
}
255+
256+
.home-section-text {
257+
font-size: 18px;
258+
line-height: 1.7;
259+
color: var(--vp-c-text-2);
260+
max-width: 600px;
261+
margin: 0 auto;
262+
}
263+
264+
.home-section-text code {
265+
background: var(--vp-c-bg-soft);
266+
padding: 2px 6px;
267+
border-radius: 4px;
268+
font-size: 16px;
269+
color: var(--vp-c-brand-1);
270+
}
271+
272+
@media (max-width: 960px) {
273+
.home-code-demo {
274+
max-width: 100%;
275+
margin-top: 24px;
276+
}
277+
278+
.home-section {
279+
margin-top: 60px;
280+
}
281+
282+
.home-section-title {
283+
font-size: 26px;
284+
}
285+
286+
.home-section-text {
287+
font-size: 16px;
288+
}
289+
}
Lines changed: 4 additions & 0 deletions
Loading

public/icon/dark-testo-class.svg

Lines changed: 4 additions & 0 deletions
Loading
Lines changed: 4 additions & 0 deletions
Loading

public/icon/dark-testo-php.svg

Lines changed: 4 additions & 0 deletions
Loading
Lines changed: 4 additions & 0 deletions
Loading

public/icon/light-testo-class.svg

Lines changed: 4 additions & 0 deletions
Loading
Lines changed: 4 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)