@voidzero-dev/vitepress-theme 4.7.1 → 4.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/components/oss/Footer.vue +3 -91
- package/src/components/oss/FooterNav.vue +101 -0
- package/src/components/vitepress-default/Layout.vue +10 -2
- package/src/components/vitepress-default/VPContent.vue +0 -8
- package/src/components/vitepress-default/VPDoc.vue +8 -14
- package/src/components/vitepress-default/VPLocalNav.vue +1 -0
- package/src/components/vitepress-default/VPSidebar.vue +6 -0
- package/src/components/vitepress-default/VPSidebarItem.vue +6 -3
- package/src/index.ts +1 -0
- package/src/styles/marketing.css +4 -2
package/package.json
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import {
|
|
3
|
-
import {useData} from 'vitepress'
|
|
4
|
-
import { Icon } from '@iconify/vue'
|
|
2
|
+
import {inject} from 'vue'
|
|
5
3
|
import { themeContextKey } from '../../types/theme-context'
|
|
4
|
+
import FooterNav from './FooterNav.vue'
|
|
6
5
|
|
|
7
6
|
// Props for CTA section
|
|
8
7
|
withDefaults(defineProps<{
|
|
@@ -17,62 +16,7 @@ withDefaults(defineProps<{
|
|
|
17
16
|
buttonLink: '/guide/'
|
|
18
17
|
})
|
|
19
18
|
|
|
20
|
-
const {theme} = useData()
|
|
21
19
|
const { footerBg: backgroundImage } = inject(themeContextKey)!
|
|
22
|
-
|
|
23
|
-
// Social icon name mapping
|
|
24
|
-
const socialIconName = (icon: string): string => {
|
|
25
|
-
const key = icon.toLowerCase()
|
|
26
|
-
if (key === 'twitter') return 'simple-icons:x'
|
|
27
|
-
return `simple-icons:${key}`
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Footer configuration from theme
|
|
31
|
-
interface FooterLink {
|
|
32
|
-
text: string
|
|
33
|
-
link: string
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
interface FooterColumn {
|
|
37
|
-
title: string
|
|
38
|
-
items: FooterLink[]
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
interface SocialLink {
|
|
42
|
-
icon: string
|
|
43
|
-
link: string
|
|
44
|
-
label?: string
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
interface FooterConfig {
|
|
48
|
-
message?: string
|
|
49
|
-
copyright?: string
|
|
50
|
-
nav?: FooterColumn[]
|
|
51
|
-
social?: SocialLink[]
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const footerConfig = computed<FooterConfig>(() => {
|
|
55
|
-
const footer = theme.value.footer || {}
|
|
56
|
-
return footer as FooterConfig
|
|
57
|
-
})
|
|
58
|
-
const footerNav = computed(() => footerConfig.value.nav || [])
|
|
59
|
-
const footerSocial = computed(() => footerConfig.value.social || [])
|
|
60
|
-
const footerCopyright = computed(() =>
|
|
61
|
-
footerConfig.value.copyright || `© ${new Date().getFullYear()} VoidZero Inc. All Rights Reserved.`
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
// Get display label for social link
|
|
65
|
-
const getSocialLabel = (social: SocialLink): string => {
|
|
66
|
-
if (social.label) return social.label
|
|
67
|
-
const labels: Record<string, string> = {
|
|
68
|
-
github: 'GitHub',
|
|
69
|
-
discord: 'Discord',
|
|
70
|
-
bluesky: 'Bluesky',
|
|
71
|
-
twitter: 'X.com',
|
|
72
|
-
x: 'X.com',
|
|
73
|
-
}
|
|
74
|
-
return labels[social.icon.toLowerCase()] || social.icon
|
|
75
|
-
}
|
|
76
20
|
</script>
|
|
77
21
|
|
|
78
22
|
<template>
|
|
@@ -91,39 +35,7 @@ const getSocialLabel = (social: SocialLink): string => {
|
|
|
91
35
|
</a>
|
|
92
36
|
</div>
|
|
93
37
|
</div>
|
|
94
|
-
<div
|
|
95
|
-
class="px-5 md:px-24 pt-10 md:pt-16 pb-16 md:pb-40 flex flex-col md:flex-row gap-10 md:gap-0 md:justify-between">
|
|
96
|
-
<div class="flex flex-col md:flex-row gap-10 md:gap-20">
|
|
97
|
-
<div v-for="column in footerNav" :key="column.title">
|
|
98
|
-
<p class="text-grey text-xs font-mono uppercase tracking-wide mb-8">{{ column.title }}</p>
|
|
99
|
-
<ul class="flex flex-col gap-3">
|
|
100
|
-
<li v-for="item in column.items" :key="item.link">
|
|
101
|
-
<a :href="item.link" class="text-white text-base">{{ item.text }}</a>
|
|
102
|
-
</li>
|
|
103
|
-
</ul>
|
|
104
|
-
</div>
|
|
105
|
-
</div>
|
|
106
|
-
<div v-if="footerSocial.length">
|
|
107
|
-
<p class="text-grey text-xs font-mono uppercase tracking-wide mb-8">Social</p>
|
|
108
|
-
<ul class="flex flex-col gap-3">
|
|
109
|
-
<li v-for="social in footerSocial" :key="social.link">
|
|
110
|
-
<a :href="social.link" class="text-white text-base flex gap-3 items-center" target="_blank"
|
|
111
|
-
rel="noopener noreferrer">
|
|
112
|
-
<Icon :icon="socialIconName(social.icon)" class="size-[18px]" :aria-label="getSocialLabel(social)" />
|
|
113
|
-
{{ getSocialLabel(social) }}
|
|
114
|
-
</a>
|
|
115
|
-
</li>
|
|
116
|
-
</ul>
|
|
117
|
-
</div>
|
|
118
|
-
</div>
|
|
119
|
-
</section>
|
|
120
|
-
<section
|
|
121
|
-
class="wrapper wrapper--ticks border-t py-5 px-5 md:px-24 flex flex-col md:flex-row gap-3 md:gap-0 justify-between items-start md:items-center">
|
|
122
|
-
<p class="text-sm">{{ footerCopyright }}</p>
|
|
123
|
-
<div class="gap-10 text-sm hidden md:flex">
|
|
124
|
-
<!-- <a href="https://voidzero.dev/terms" class="text-grey">Terms & Conditions</a>
|
|
125
|
-
<a href="https://voidzero.dev/privacy" class="text-grey">Privacy Policy</a>-->
|
|
126
|
-
</div>
|
|
127
38
|
</section>
|
|
39
|
+
<FooterNav />
|
|
128
40
|
</footer>
|
|
129
41
|
</template>
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import {computed} from 'vue'
|
|
3
|
+
import {useData} from 'vitepress'
|
|
4
|
+
import { Icon } from '@iconify/vue'
|
|
5
|
+
|
|
6
|
+
const {theme} = useData()
|
|
7
|
+
|
|
8
|
+
// Social icon name mapping
|
|
9
|
+
const socialIconName = (icon: string): string => {
|
|
10
|
+
const key = icon.toLowerCase()
|
|
11
|
+
if (key === 'twitter') return 'simple-icons:x'
|
|
12
|
+
return `simple-icons:${key}`
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Footer configuration from theme
|
|
16
|
+
interface FooterLink {
|
|
17
|
+
text: string
|
|
18
|
+
link: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface FooterColumn {
|
|
22
|
+
title: string
|
|
23
|
+
items: FooterLink[]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface SocialLink {
|
|
27
|
+
icon: string
|
|
28
|
+
link: string
|
|
29
|
+
label?: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface FooterConfig {
|
|
33
|
+
message?: string
|
|
34
|
+
copyright?: string
|
|
35
|
+
nav?: FooterColumn[]
|
|
36
|
+
social?: SocialLink[]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const footerConfig = computed<FooterConfig>(() => {
|
|
40
|
+
const footer = theme.value.footer || {}
|
|
41
|
+
return footer as FooterConfig
|
|
42
|
+
})
|
|
43
|
+
const footerNav = computed(() => footerConfig.value.nav || [])
|
|
44
|
+
const footerSocial = computed(() => footerConfig.value.social || [])
|
|
45
|
+
const footerCopyright = computed(() =>
|
|
46
|
+
footerConfig.value.copyright || `© ${new Date().getFullYear()} VoidZero Inc. All Rights Reserved.`
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
// Get display label for social link
|
|
50
|
+
const getSocialLabel = (social: SocialLink): string => {
|
|
51
|
+
if (social.label) return social.label
|
|
52
|
+
const labels: Record<string, string> = {
|
|
53
|
+
github: 'GitHub',
|
|
54
|
+
discord: 'Discord',
|
|
55
|
+
bluesky: 'Bluesky',
|
|
56
|
+
twitter: 'X.com',
|
|
57
|
+
x: 'X.com',
|
|
58
|
+
}
|
|
59
|
+
return labels[social.icon.toLowerCase()] || social.icon
|
|
60
|
+
}
|
|
61
|
+
</script>
|
|
62
|
+
|
|
63
|
+
<template>
|
|
64
|
+
<div>
|
|
65
|
+
<section class="wrapper wrapper--ticks border-t">
|
|
66
|
+
<div
|
|
67
|
+
class="px-5 md:px-24 pt-10 md:pt-16 pb-16 md:pb-40 flex flex-col md:flex-row gap-10 md:gap-0 md:justify-between">
|
|
68
|
+
<div class="flex flex-col md:flex-row gap-10 md:gap-20">
|
|
69
|
+
<div v-for="column in footerNav" :key="column.title">
|
|
70
|
+
<p class="text-[var(--vp-c-text-2)] text-xs font-mono uppercase tracking-wide mb-8">{{ column.title }}</p>
|
|
71
|
+
<ul class="flex flex-col gap-3">
|
|
72
|
+
<li v-for="item in column.items" :key="item.link">
|
|
73
|
+
<a :href="item.link" class="text-[var(--vp-c-text-1)] text-base">{{ item.text }}</a>
|
|
74
|
+
</li>
|
|
75
|
+
</ul>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
<div v-if="footerSocial.length">
|
|
79
|
+
<p class="text-[var(--vp-c-text-2)] text-xs font-mono uppercase tracking-wide mb-8">Social</p>
|
|
80
|
+
<ul class="flex flex-col gap-3">
|
|
81
|
+
<li v-for="social in footerSocial" :key="social.link">
|
|
82
|
+
<a :href="social.link" class="text-[var(--vp-c-text-1)] text-base flex gap-3 items-center" target="_blank"
|
|
83
|
+
rel="noopener noreferrer">
|
|
84
|
+
<Icon :icon="socialIconName(social.icon)" class="size-[18px]" :aria-label="getSocialLabel(social)" />
|
|
85
|
+
{{ getSocialLabel(social) }}
|
|
86
|
+
</a>
|
|
87
|
+
</li>
|
|
88
|
+
</ul>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
</section>
|
|
92
|
+
<section
|
|
93
|
+
class="wrapper wrapper--ticks border-t py-5 px-5 md:px-24 flex flex-col md:flex-row gap-3 md:gap-0 justify-between items-start md:items-center">
|
|
94
|
+
<p class="text-sm">{{ footerCopyright }}</p>
|
|
95
|
+
<div class="gap-10 text-sm hidden md:flex">
|
|
96
|
+
<!-- <a href="https://voidzero.dev/terms" class="text-grey">Terms & Conditions</a>
|
|
97
|
+
<a href="https://voidzero.dev/privacy" class="text-grey">Privacy Policy</a>-->
|
|
98
|
+
</div>
|
|
99
|
+
</section>
|
|
100
|
+
</div>
|
|
101
|
+
</template>
|
|
@@ -9,7 +9,7 @@ import TopBanner from '@components/oss/TopBanner.vue'
|
|
|
9
9
|
import VPSidebar from './VPSidebar.vue'
|
|
10
10
|
import VPSkipLink from './VPSkipLink.vue'
|
|
11
11
|
import { useData } from '@vp-composables/data'
|
|
12
|
-
import { layoutInfoInjectionKey, registerWatchers } from '@vp-composables/layout'
|
|
12
|
+
import { layoutInfoInjectionKey, registerWatchers, useLayout } from '@vp-composables/layout'
|
|
13
13
|
import { useSidebarControl } from '@vp-composables/sidebar'
|
|
14
14
|
|
|
15
15
|
const {
|
|
@@ -21,6 +21,7 @@ const {
|
|
|
21
21
|
registerWatchers({ closeSidebar })
|
|
22
22
|
|
|
23
23
|
const { frontmatter } = useData()
|
|
24
|
+
const { hasSidebar } = useLayout()
|
|
24
25
|
|
|
25
26
|
const slots = useSlots()
|
|
26
27
|
const heroImageSlotExists = computed(() => !!slots['home-hero-image'])
|
|
@@ -41,7 +42,7 @@ provide(layoutInfoInjectionKey, { heroImageSlotExists })
|
|
|
41
42
|
|
|
42
43
|
<div class="flex flex-col min-h-screen">
|
|
43
44
|
<!-- Content wrapper with borders -->
|
|
44
|
-
<div class="content-wrapper flex-1">
|
|
45
|
+
<div class="content-wrapper flex-1" :class="{ 'has-sidebar': hasSidebar }">
|
|
45
46
|
<VPLocalNav :open="isSidebarOpen" @open-menu="openSidebar" />
|
|
46
47
|
|
|
47
48
|
<VPSidebar :open="isSidebarOpen">
|
|
@@ -176,4 +177,11 @@ provide(layoutInfoInjectionKey, { heroImageSlotExists })
|
|
|
176
177
|
max-width: 90rem;
|
|
177
178
|
}
|
|
178
179
|
}
|
|
180
|
+
|
|
181
|
+
@media (min-width: 1024px) {
|
|
182
|
+
.content-wrapper.has-sidebar {
|
|
183
|
+
display: grid;
|
|
184
|
+
grid-template-columns: var(--vp-sidebar-width) 1fr;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
179
187
|
</style>
|
|
@@ -82,14 +82,6 @@ const { isHome, hasSidebar } = useLayout()
|
|
|
82
82
|
|
|
83
83
|
.VPContent.has-sidebar {
|
|
84
84
|
margin: var(--vp-layout-top-height, 0px) 0 0;
|
|
85
|
-
padding-left: var(--vp-sidebar-width);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
@media (min-width: 1440px) {
|
|
90
|
-
.VPContent.has-sidebar {
|
|
91
|
-
padding-right: calc((100% - var(--vp-layout-max-width)) / 2);
|
|
92
|
-
padding-left: calc((100% - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width));
|
|
93
85
|
}
|
|
94
86
|
}
|
|
95
87
|
</style>
|
|
@@ -76,7 +76,7 @@ const pageName = computed(() =>
|
|
|
76
76
|
|
|
77
77
|
@media (min-width: 1024px) {
|
|
78
78
|
.VPDoc {
|
|
79
|
-
padding:
|
|
79
|
+
padding: 0 32px 0;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
.VPDoc:not(.has-sidebar) .container {
|
|
@@ -133,34 +133,28 @@ const pageName = computed(() =>
|
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
.aside-container {
|
|
136
|
-
position:
|
|
137
|
-
top: var(--vp-nav-height);
|
|
136
|
+
position: sticky;
|
|
137
|
+
top: calc(var(--vp-nav-height) + var(--vp-banner-height, 0px));
|
|
138
138
|
padding-top: 20px;
|
|
139
139
|
padding-left: 32px;
|
|
140
140
|
width: 224px;
|
|
141
|
-
height: calc(100vh - var(--vp-nav-height));
|
|
141
|
+
height: calc(100vh - var(--vp-nav-height) - var(--vp-banner-height, 0px));
|
|
142
142
|
overflow-x: hidden;
|
|
143
143
|
overflow-y: auto;
|
|
144
144
|
scrollbar-width: none;
|
|
145
145
|
border-left: 1px solid var(--vp-c-divider);
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
-
@media (min-width: 1024px) {
|
|
149
|
-
.aside-container {
|
|
150
|
-
top: calc(var(--vp-nav-height) + var(--vp-banner-height, 0px));
|
|
151
|
-
height: calc(100vh - var(--vp-nav-height) - var(--vp-banner-height, 0px));
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
148
|
.aside-container::-webkit-scrollbar {
|
|
156
149
|
display: none;
|
|
157
150
|
}
|
|
158
151
|
|
|
159
152
|
.aside-curtain {
|
|
160
|
-
position:
|
|
153
|
+
position: absolute;
|
|
161
154
|
bottom: 0;
|
|
155
|
+
left: 0;
|
|
162
156
|
z-index: 10;
|
|
163
|
-
width:
|
|
157
|
+
width: 100%;
|
|
164
158
|
height: 32px;
|
|
165
159
|
background: linear-gradient(transparent, var(--vp-c-bg) 70%);
|
|
166
160
|
pointer-events: none;
|
|
@@ -181,7 +175,7 @@ const pageName = computed(() =>
|
|
|
181
175
|
|
|
182
176
|
@media (min-width: 1024px) {
|
|
183
177
|
.content {
|
|
184
|
-
padding:
|
|
178
|
+
padding: 48px 32px 128px;
|
|
185
179
|
}
|
|
186
180
|
}
|
|
187
181
|
|
|
@@ -92,16 +92,22 @@ watch(
|
|
|
92
92
|
|
|
93
93
|
@media (min-width: 1024px) {
|
|
94
94
|
.VPSidebar {
|
|
95
|
+
position: sticky;
|
|
95
96
|
top: calc(var(--vp-nav-height) + var(--vp-banner-height, 0px));
|
|
97
|
+
bottom: unset;
|
|
96
98
|
left: unset;
|
|
97
99
|
padding-left: 32px;
|
|
98
100
|
width: var(--vp-sidebar-width);
|
|
99
101
|
max-width: 100%;
|
|
102
|
+
height: calc(100vh - var(--vp-nav-height) - var(--vp-banner-height, 0px));
|
|
100
103
|
background-color: transparent;
|
|
101
104
|
opacity: 1;
|
|
102
105
|
visibility: visible;
|
|
103
106
|
box-shadow: none;
|
|
104
107
|
transform: translateX(0);
|
|
108
|
+
z-index: auto;
|
|
109
|
+
grid-row: 1 / -1;
|
|
110
|
+
align-self: start;
|
|
105
111
|
}
|
|
106
112
|
}
|
|
107
113
|
|
|
@@ -240,13 +240,16 @@ const itemIcon = useIcon(() => (props.item as any).icon)
|
|
|
240
240
|
transform: rotate(0)/*rtl:rotate(180deg)*/;
|
|
241
241
|
}
|
|
242
242
|
|
|
243
|
-
.VPSidebarItem.level-0.has-icon
|
|
243
|
+
.VPSidebarItem.level-0.has-icon .items {
|
|
244
|
+
border-left: 1px solid var(--vp-c-divider);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.VPSidebarItem.level-0 .items,
|
|
244
248
|
.VPSidebarItem.level-1 .items,
|
|
245
249
|
.VPSidebarItem.level-2 .items,
|
|
246
250
|
.VPSidebarItem.level-3 .items,
|
|
247
251
|
.VPSidebarItem.level-4 .items,
|
|
248
252
|
.VPSidebarItem.level-5 .items {
|
|
249
|
-
border-left: 1px solid var(--vp-c-divider);
|
|
250
253
|
padding-left: 16px;
|
|
251
254
|
}
|
|
252
255
|
|
|
@@ -269,6 +272,6 @@ const itemIcon = useIcon(() => (props.item as any).icon)
|
|
|
269
272
|
}
|
|
270
273
|
|
|
271
274
|
.VPSidebarItem.level-0.has-icon > .items {
|
|
272
|
-
margin-left:
|
|
275
|
+
margin-left: 8px;
|
|
273
276
|
}
|
|
274
277
|
</style>
|
package/src/index.ts
CHANGED
|
@@ -82,6 +82,7 @@ export { default as VPTeamPageSection } from "@vp-default/VPTeamPageSection.vue"
|
|
|
82
82
|
export { default as VPTeamPageTitle } from "@vp-default/VPTeamPageTitle.vue";
|
|
83
83
|
|
|
84
84
|
// Export custom layouts and components
|
|
85
|
+
export { default as FooterNav } from "@components/oss/FooterNav.vue";
|
|
85
86
|
export { VoidZeroTheme };
|
|
86
87
|
export default VoidZeroTheme;
|
|
87
88
|
|
package/src/styles/marketing.css
CHANGED
|
@@ -82,11 +82,13 @@
|
|
|
82
82
|
border-left: 5px solid transparent;
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
[data-theme=dark] .wrapper--ticks::before, [data-theme=dark] .tick-left::before
|
|
85
|
+
[data-theme=dark] .wrapper--ticks::before, [data-theme=dark] .tick-left::before,
|
|
86
|
+
.dark:not([data-theme]) .wrapper--ticks::before, .dark:not([data-theme]) .tick-left::before {
|
|
86
87
|
border-left-color: var(--color-nickel);
|
|
87
88
|
}
|
|
88
89
|
|
|
89
|
-
[data-theme=dark] .wrapper--ticks::after, [data-theme=dark] .tick-right::after
|
|
90
|
+
[data-theme=dark] .wrapper--ticks::after, [data-theme=dark] .tick-right::after,
|
|
91
|
+
.dark:not([data-theme]) .wrapper--ticks::after, .dark:not([data-theme]) .tick-right::after {
|
|
90
92
|
border-right-color: var(--color-nickel);
|
|
91
93
|
}
|
|
92
94
|
|