@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voidzero-dev/vitepress-theme",
3
- "version": "4.7.1",
3
+ "version": "4.8.0",
4
4
  "description": "Shared VitePress theme for VoidZero projects",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -1,8 +1,7 @@
1
1
  <script setup lang="ts">
2
- import {computed, inject} from 'vue'
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: 48px 32px 0;
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: fixed;
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: fixed;
153
+ position: absolute;
161
154
  bottom: 0;
155
+ left: 0;
162
156
  z-index: 10;
163
- width: 224px;
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: 0 32px 128px;
178
+ padding: 48px 32px 128px;
185
179
  }
186
180
  }
187
181
 
@@ -88,6 +88,7 @@ const classes = computed(() => {
88
88
  }
89
89
 
90
90
  .VPLocalNav.has-sidebar {
91
+ grid-column: 1 / -1;
91
92
  padding-left: var(--vp-sidebar-width);
92
93
  border-bottom: none;
93
94
  }
@@ -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 > .items,
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: 6px;
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
 
@@ -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