valaxy-theme-yun 1.0.0-rc.9 → 1.0.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/.npmignore +8 -0
- package/App.vue +9 -6
- package/components/ValaxyMain.vue +6 -0
- package/components/YunAside.vue +97 -7
- package/components/YunBackToTop.vue +86 -39
- package/components/YunBanner.vue +6 -2
- package/components/YunCard.vue +7 -1
- package/components/YunComment.vue +1 -1
- package/components/YunDocsSidebar.vue +53 -13
- package/components/YunDropdownMenu.vue +1 -1
- package/components/YunFullscreenMenu.vue +0 -3
- package/components/YunFuseSearch.vue +403 -68
- package/components/YunLocalOutlineNav.vue +133 -0
- package/components/YunOutline.vue +10 -16
- package/components/YunPostCard.vue +22 -9
- package/components/YunPrologue.vue +115 -10
- package/components/YunSay.vue +3 -3
- package/components/YunSearchBtn.vue +3 -4
- package/components/YunSelect.vue +160 -47
- package/components/YunTooltip.vue +2 -2
- package/components/animation/LineBurstEffects.vue +44 -46
- package/components/animation/YunAnimLineDraw.vue +1 -1
- package/components/collection/YunCollectionMobile.vue +118 -0
- package/components/collection/YunCollectionSidebar.vue +97 -74
- package/components/config/YunToggleDark.vue +67 -30
- package/components/layout/YunLayoutLeft.vue +7 -2
- package/components/layout/YunLayoutRight.vue +22 -3
- package/components/layout/YunLayoutWrapper.vue +53 -5
- package/components/prologue/YunPrologueNavigation.vue +221 -0
- package/components/prologue/YunPrologueSquare.vue +72 -37
- package/components/site/YunSiteLinkItem.vue +6 -4
- package/components/theme/nimbo/YunNimboNavMenu.vue +1 -6
- package/components/theme/strato/YunStratoNavMenu.vue +4 -8
- package/components/ui/YunExcerptBottomGradient.vue +1 -1
- package/composables/collection.ts +16 -0
- package/composables/localMenu.ts +21 -0
- package/layouts/archives.vue +2 -2
- package/layouts/categories.vue +2 -2
- package/layouts/collection.vue +19 -23
- package/layouts/default.vue +2 -2
- package/layouts/docs.vue +27 -3
- package/layouts/home.vue +49 -25
- package/layouts/moments.vue +6 -1
- package/layouts/post.vue +23 -6
- package/layouts/tags.vue +2 -2
- package/locales/en.yml +8 -0
- package/locales/zh-CN.yml +8 -0
- package/node/config.ts +13 -0
- package/package.json +6 -7
- package/styles/common/button.scss +21 -0
- package/styles/common/markdown.scss +1 -0
- package/styles/common/nav.scss +54 -0
- package/styles/css-vars.scss +10 -0
- package/styles/global.scss +0 -2
- package/styles/index.ts +1 -0
- package/styles/layout/post.scss +30 -2
- package/types/index.d.ts +39 -2
- package/utils/sidebar.ts +9 -0
package/.npmignore
ADDED
package/App.vue
CHANGED
|
@@ -2,13 +2,11 @@
|
|
|
2
2
|
import { useHead } from '@unhead/vue'
|
|
3
3
|
import { TooltipProvider } from 'reka-ui'
|
|
4
4
|
import { useAppStore } from 'valaxy'
|
|
5
|
-
import { computed, onMounted, watch } from 'vue'
|
|
5
|
+
import { computed, onBeforeUnmount, onMounted, watch } from 'vue'
|
|
6
6
|
import { useRoute } from 'vue-router'
|
|
7
7
|
import { useThemeConfig } from './composables'
|
|
8
8
|
import { useYunAppStore } from './stores'
|
|
9
9
|
|
|
10
|
-
const isDev = import.meta.env.DEV
|
|
11
|
-
|
|
12
10
|
const appStore = useAppStore()
|
|
13
11
|
|
|
14
12
|
// Use a safe default for SSR; real themeColor is applied after mount
|
|
@@ -52,17 +50,22 @@ watch(
|
|
|
52
50
|
{ immediate: true },
|
|
53
51
|
)
|
|
54
52
|
|
|
55
|
-
|
|
56
|
-
// for mobile vh
|
|
53
|
+
function updateViewportHeight() {
|
|
57
54
|
document.documentElement.style.setProperty('--vh', `${window.innerHeight * 0.01}px`)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
onMounted(() => {
|
|
58
|
+
// The viewport can be zero when an iframe mounts inside a hidden panel.
|
|
59
|
+
updateViewportHeight()
|
|
60
|
+
window.addEventListener('resize', updateViewportHeight)
|
|
58
61
|
app.showLoading = false
|
|
59
62
|
})
|
|
63
|
+
onBeforeUnmount(() => window.removeEventListener('resize', updateViewportHeight))
|
|
60
64
|
</script>
|
|
61
65
|
|
|
62
66
|
<template>
|
|
63
67
|
<TooltipProvider>
|
|
64
68
|
<YunStratoApp v-if="yun.isStrato" />
|
|
65
|
-
<ValaxyDebug v-if="isDev" />
|
|
66
69
|
|
|
67
70
|
<YunPageHeaderGradient />
|
|
68
71
|
<YunNavMenu />
|
package/components/YunAside.vue
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
2
|
import { useFrontmatter } from 'valaxy'
|
|
3
|
-
import { computed } from 'vue'
|
|
3
|
+
import { computed, nextTick, useTemplateRef, watch } from 'vue'
|
|
4
4
|
import { useI18n } from 'vue-i18n'
|
|
5
5
|
import { useYunAppStore } from '../stores'
|
|
6
6
|
|
|
7
|
+
defineProps<{
|
|
8
|
+
mobileTitle?: string
|
|
9
|
+
}>()
|
|
10
|
+
|
|
7
11
|
const fm = useFrontmatter()
|
|
8
12
|
const { t } = useI18n()
|
|
9
13
|
const yun = useYunAppStore()
|
|
@@ -13,17 +17,59 @@ const showToc = computed(() => {
|
|
|
13
17
|
})
|
|
14
18
|
|
|
15
19
|
const asideEnabled = computed(() => fm.value.aside !== false)
|
|
20
|
+
const closeButton = useTemplateRef<HTMLButtonElement>('closeButton')
|
|
21
|
+
let keepHeadingFocus = false
|
|
22
|
+
|
|
23
|
+
function closeAside(focusHeading = false) {
|
|
24
|
+
if (!yun.rightSidebar.isOpen)
|
|
25
|
+
return
|
|
26
|
+
keepHeadingFocus = focusHeading
|
|
27
|
+
yun.rightSidebar.toggle()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
watch(() => yun.rightSidebar.isOpen, (open) => {
|
|
31
|
+
if (open) {
|
|
32
|
+
nextTick(() => closeButton.value?.focus({ preventScroll: true }))
|
|
33
|
+
}
|
|
34
|
+
else if (keepHeadingFocus) {
|
|
35
|
+
keepHeadingFocus = false
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
nextTick(() => document.querySelector<HTMLElement>('[aria-controls="yun-page-outline"][aria-expanded="false"]')?.focus({ preventScroll: true }))
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
function onKeydown(event: KeyboardEvent) {
|
|
43
|
+
if (event.key === 'Escape' && yun.rightSidebar.isOpen) {
|
|
44
|
+
event.preventDefault()
|
|
45
|
+
closeAside()
|
|
46
|
+
}
|
|
47
|
+
}
|
|
16
48
|
</script>
|
|
17
49
|
|
|
18
50
|
<template>
|
|
19
51
|
<aside
|
|
20
52
|
v-if="asideEnabled"
|
|
53
|
+
id="yun-page-outline"
|
|
21
54
|
flex="~ col"
|
|
22
55
|
class="va-card yun-aside min-h-sm rounded-2"
|
|
23
56
|
:class="{ open: yun.rightSidebar.isOpen }"
|
|
24
57
|
text="center"
|
|
25
58
|
overflow="auto"
|
|
59
|
+
@keydown="onKeydown"
|
|
26
60
|
>
|
|
61
|
+
<div class="yun-aside-mobile-header">
|
|
62
|
+
<span>{{ mobileTitle || t('sidebar.toc') }}</span>
|
|
63
|
+
<button
|
|
64
|
+
ref="closeButton"
|
|
65
|
+
type="button"
|
|
66
|
+
class="yun-aside-close yun-icon-btn"
|
|
67
|
+
:aria-label="t('theme.closeOutline')"
|
|
68
|
+
@click="closeAside()"
|
|
69
|
+
>
|
|
70
|
+
<span i-ri-close-line aria-hidden="true" />
|
|
71
|
+
</button>
|
|
72
|
+
</div>
|
|
27
73
|
<div class="w-full" flex="~ col" pb-2>
|
|
28
74
|
<template v-if="showToc">
|
|
29
75
|
<h2
|
|
@@ -32,7 +78,7 @@ const asideEnabled = computed(() => fm.value.aside !== false)
|
|
|
32
78
|
>
|
|
33
79
|
{{ t('sidebar.toc') }}
|
|
34
80
|
</h2>
|
|
35
|
-
<YunOutline />
|
|
81
|
+
<YunOutline @select="closeAside(true)" />
|
|
36
82
|
</template>
|
|
37
83
|
|
|
38
84
|
<div class="flex-grow" />
|
|
@@ -56,10 +102,14 @@ const asideEnabled = computed(() => fm.value.aside !== false)
|
|
|
56
102
|
right: 0;
|
|
57
103
|
bottom: 0;
|
|
58
104
|
z-index: var(--yun-z-aside);
|
|
59
|
-
width:
|
|
105
|
+
width: min(360px, calc(100vw - 24px));
|
|
106
|
+
height: 100dvh;
|
|
107
|
+
max-height: 100dvh;
|
|
108
|
+
visibility: hidden;
|
|
60
109
|
transform: translateX(100%);
|
|
61
|
-
transition:
|
|
62
|
-
|
|
110
|
+
transition: transform var(--va-transition-duration-fast) map.get($cubic-bezier, 'ease-in-out'), visibility 0s linear var(--va-transition-duration-fast);
|
|
111
|
+
border-radius: 16px 0 0 16px;
|
|
112
|
+
box-shadow: -12px 0 40px rgb(0 0 0 / 0.12);
|
|
63
113
|
|
|
64
114
|
// float panel
|
|
65
115
|
&.float {
|
|
@@ -72,13 +122,39 @@ const asideEnabled = computed(() => fm.value.aside !== false)
|
|
|
72
122
|
}
|
|
73
123
|
|
|
74
124
|
&.show {
|
|
75
|
-
|
|
125
|
+
visibility: visible;
|
|
126
|
+
transform: translateX(0);
|
|
76
127
|
}
|
|
77
128
|
|
|
78
129
|
// Mobile/tablet: toggle open via JS
|
|
79
130
|
&.open {
|
|
80
|
-
|
|
131
|
+
visibility: visible;
|
|
81
132
|
transform: translateX(0);
|
|
133
|
+
transition: transform var(--va-transition-duration-fast) map.get($cubic-bezier, 'ease-in-out'), visibility 0s;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.yun-aside-mobile-header {
|
|
138
|
+
display: flex;
|
|
139
|
+
flex: none;
|
|
140
|
+
align-items: center;
|
|
141
|
+
justify-content: space-between;
|
|
142
|
+
min-height: 56px;
|
|
143
|
+
padding: 0 12px 0 20px;
|
|
144
|
+
border-bottom: 1px solid var(--yun-surface-line);
|
|
145
|
+
font-size: 16px;
|
|
146
|
+
font-weight: 600;
|
|
147
|
+
text-align: left;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.yun-aside-close {
|
|
151
|
+
color: var(--va-c-text);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
@media (width < 1280px) {
|
|
155
|
+
.yun-aside > div > h2,
|
|
156
|
+
.yun-aside .outline-title {
|
|
157
|
+
display: none;
|
|
82
158
|
}
|
|
83
159
|
}
|
|
84
160
|
|
|
@@ -89,18 +165,32 @@ const asideEnabled = computed(() => fm.value.aside !== false)
|
|
|
89
165
|
top: var(--yun-margin-top);
|
|
90
166
|
z-index: auto;
|
|
91
167
|
width: 320px;
|
|
168
|
+
height: auto;
|
|
92
169
|
max-height: calc(100vh - var(--yun-margin-top));
|
|
170
|
+
visibility: visible;
|
|
93
171
|
transform: translateX(0);
|
|
172
|
+
border-radius: 8px;
|
|
173
|
+
box-shadow: none;
|
|
94
174
|
|
|
95
175
|
// On xl, sidebar toggle should not affect layout
|
|
96
176
|
&.open {
|
|
97
177
|
width: 320px;
|
|
98
178
|
}
|
|
99
179
|
}
|
|
180
|
+
|
|
181
|
+
.yun-aside-mobile-header {
|
|
182
|
+
display: none;
|
|
183
|
+
}
|
|
100
184
|
}
|
|
101
185
|
|
|
102
186
|
.toc-btn {
|
|
103
187
|
color: var(--va-c-primary);
|
|
104
188
|
z-index: var(--yun-z-toc-btn);
|
|
105
189
|
}
|
|
190
|
+
|
|
191
|
+
@media (prefers-reduced-motion: reduce) {
|
|
192
|
+
.yun-aside {
|
|
193
|
+
transition: none;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
106
196
|
</style>
|
|
@@ -1,73 +1,120 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
2
|
import { useBackToTop } from 'valaxy'
|
|
3
3
|
import { computed } from 'vue'
|
|
4
|
+
import { useI18n } from 'vue-i18n'
|
|
4
5
|
|
|
5
6
|
const { show, percentage, backToTop } = useBackToTop({ offset: 100 })
|
|
6
7
|
|
|
7
|
-
const
|
|
8
|
-
const circumference = 2 * radius * Math.PI
|
|
8
|
+
const { t } = useI18n()
|
|
9
9
|
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
return val < 0 ? 0 : val
|
|
10
|
+
const progressAngle = computed(() => {
|
|
11
|
+
const progress = Number.isFinite(percentage.value) ? percentage.value : 0
|
|
12
|
+
return `${Math.min(1, Math.max(0, progress)) * 360}deg`
|
|
14
13
|
})
|
|
15
14
|
</script>
|
|
16
15
|
|
|
17
16
|
<template>
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
class="back-to-top yun-icon-btn
|
|
17
|
+
<button
|
|
18
|
+
type="button"
|
|
19
|
+
class="back-to-top yun-icon-btn"
|
|
20
|
+
:aria-label="t('theme.backToTop')"
|
|
21
|
+
:title="t('theme.backToTop')"
|
|
22
|
+
:tabindex="show ? 0 : -1"
|
|
23
|
+
:aria-hidden="!show"
|
|
21
24
|
:class="show && 'show'"
|
|
25
|
+
:style="{ '--yun-back-top-progress': progressAngle }"
|
|
22
26
|
@click="backToTop"
|
|
23
27
|
>
|
|
24
|
-
<div class="
|
|
25
|
-
|
|
26
|
-
<circle
|
|
27
|
-
:stroke-dasharray="`${circumference} ${circumference}`"
|
|
28
|
-
:stroke-dashoffset="strokeOffset"
|
|
29
|
-
stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
|
30
|
-
class="progress-circle" cx="50" cy="50" :r="radius" fill="none"
|
|
31
|
-
/>
|
|
32
|
-
</svg>
|
|
33
|
-
</a>
|
|
28
|
+
<div class="back-to-top-arrow" i-ri-arrow-up-s-line aria-hidden="true" />
|
|
29
|
+
</button>
|
|
34
30
|
</template>
|
|
35
31
|
|
|
36
32
|
<style lang="scss">
|
|
37
|
-
|
|
33
|
+
.back-to-top.yun-icon-btn {
|
|
34
|
+
--yun-back-top-bg: var(--va-c-bg-soft);
|
|
35
|
+
--yun-back-top-progress-opacity: 0.06;
|
|
38
36
|
|
|
39
|
-
.back-to-top {
|
|
40
37
|
position: fixed;
|
|
41
|
-
right: -
|
|
42
|
-
bottom: 1rem;
|
|
38
|
+
right: max(1rem, env(safe-area-inset-right));
|
|
39
|
+
bottom: max(1rem, env(safe-area-inset-bottom));
|
|
43
40
|
z-index: var(--yun-z-go-up-btn);
|
|
44
|
-
|
|
45
|
-
|
|
41
|
+
width: 3rem;
|
|
42
|
+
height: 3rem;
|
|
43
|
+
padding: 0;
|
|
44
|
+
box-sizing: border-box;
|
|
45
|
+
isolation: isolate;
|
|
46
|
+
overflow: visible;
|
|
47
|
+
border: 0;
|
|
48
|
+
border-radius: 50%;
|
|
46
49
|
color: var(--va-c-primary);
|
|
47
|
-
|
|
50
|
+
background: transparent;
|
|
48
51
|
|
|
49
|
-
//
|
|
50
|
-
|
|
52
|
+
// Keep the progress tint inside the face, underneath the arrow.
|
|
53
|
+
&::before {
|
|
54
|
+
content: '';
|
|
55
|
+
position: absolute;
|
|
56
|
+
inset: 0;
|
|
57
|
+
z-index: 0;
|
|
58
|
+
border: 1px solid rgb(var(--va-c-primary-rgb), 0.08);
|
|
59
|
+
border-radius: inherit;
|
|
60
|
+
background: var(--yun-back-top-bg);
|
|
61
|
+
box-shadow: 0 2px 8px rgb(0 0 0 / 0.08);
|
|
62
|
+
transition: background-color 180ms ease;
|
|
63
|
+
pointer-events: none;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
opacity: 0;
|
|
67
|
+
visibility: hidden;
|
|
68
|
+
pointer-events: none;
|
|
69
|
+
transition: opacity 180ms ease, visibility 180ms ease;
|
|
51
70
|
|
|
52
71
|
&.show {
|
|
53
|
-
transform: translateX(-40px) rotate(360deg);
|
|
54
72
|
opacity: 1;
|
|
55
|
-
|
|
73
|
+
visibility: visible;
|
|
74
|
+
pointer-events: auto;
|
|
56
75
|
}
|
|
57
76
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
77
|
+
@media (hover: hover) {
|
|
78
|
+
&.show:hover {
|
|
79
|
+
--yun-back-top-bg: color-mix(in srgb, var(--va-c-bg-soft) 94%, var(--va-c-primary));
|
|
80
|
+
}
|
|
61
81
|
}
|
|
62
|
-
}
|
|
63
82
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
transform-origin: 50% 50%;
|
|
83
|
+
&.show:active {
|
|
84
|
+
--yun-back-top-bg: color-mix(in srgb, var(--va-c-bg-soft) 88%, var(--va-c-primary));
|
|
85
|
+
}
|
|
68
86
|
|
|
69
|
-
|
|
87
|
+
.back-to-top-arrow {
|
|
88
|
+
position: relative;
|
|
89
|
+
z-index: 1;
|
|
90
|
+
width: 24px;
|
|
91
|
+
height: 24px;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
&::after {
|
|
95
|
+
content: '';
|
|
70
96
|
position: absolute;
|
|
97
|
+
inset: 1px;
|
|
98
|
+
z-index: 0;
|
|
99
|
+
border-radius: inherit;
|
|
100
|
+
background: conic-gradient(
|
|
101
|
+
from 0deg,
|
|
102
|
+
rgb(var(--va-c-primary-rgb), var(--yun-back-top-progress-opacity)) 0deg var(--yun-back-top-progress, 0deg),
|
|
103
|
+
transparent var(--yun-back-top-progress, 0deg) 360deg
|
|
104
|
+
);
|
|
105
|
+
pointer-events: none;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
html.dark .back-to-top.yun-icon-btn {
|
|
111
|
+
--yun-back-top-progress-opacity: 0.1;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
@media (prefers-reduced-motion: reduce) {
|
|
115
|
+
.back-to-top.yun-icon-btn,
|
|
116
|
+
.back-to-top.yun-icon-btn::before {
|
|
117
|
+
transition: none;
|
|
71
118
|
}
|
|
72
119
|
}
|
|
73
120
|
</style>
|
package/components/YunBanner.vue
CHANGED
|
@@ -26,7 +26,7 @@ const bannerStyles = computed<CSSProperties>(() => {
|
|
|
26
26
|
'justify-content': 'space-between',
|
|
27
27
|
}
|
|
28
28
|
if (yun.isStrato)
|
|
29
|
-
styles.borderBottom = `1px solid var(--banner-line-color)`
|
|
29
|
+
styles.borderBottom = `1px solid var(--yun-banner-divider-color, var(--banner-line-color))`
|
|
30
30
|
|
|
31
31
|
return styles
|
|
32
32
|
})
|
|
@@ -58,7 +58,7 @@ onMounted(async () => {
|
|
|
58
58
|
</script>
|
|
59
59
|
|
|
60
60
|
<template>
|
|
61
|
-
<div id="yun-banner" border="b-1px b-solid
|
|
61
|
+
<div id="yun-banner" border="b-1px b-solid" :style="bannerStyles">
|
|
62
62
|
<div class="banner-line-container">
|
|
63
63
|
<div
|
|
64
64
|
class="banner-line vertical-line-top"
|
|
@@ -94,6 +94,10 @@ onMounted(async () => {
|
|
|
94
94
|
@use "../styles/widgets/banner.scss" as *;
|
|
95
95
|
@use "../styles/modules/prologue.scss" as *;
|
|
96
96
|
|
|
97
|
+
#yun-banner {
|
|
98
|
+
border-bottom-color: var(--yun-banner-divider-color, var(--banner-line-color));
|
|
99
|
+
}
|
|
100
|
+
|
|
97
101
|
:root {
|
|
98
102
|
// banner
|
|
99
103
|
--banner-line-color: black;
|
package/components/YunCard.vue
CHANGED
|
@@ -4,7 +4,7 @@ defineProps<{ cover?: string }>()
|
|
|
4
4
|
|
|
5
5
|
<template>
|
|
6
6
|
<div
|
|
7
|
-
class="yun-card flex-center
|
|
7
|
+
class="yun-card flex-center overflow-hidden" flex="col"
|
|
8
8
|
min-h="100px"
|
|
9
9
|
bg="$va-c-bg-light"
|
|
10
10
|
>
|
|
@@ -23,3 +23,9 @@ defineProps<{ cover?: string }>()
|
|
|
23
23
|
</div>
|
|
24
24
|
</div>
|
|
25
25
|
</template>
|
|
26
|
+
|
|
27
|
+
<style scoped>
|
|
28
|
+
.yun-card {
|
|
29
|
+
border-radius: var(--va-card-border-radius, 0.5rem);
|
|
30
|
+
}
|
|
31
|
+
</style>
|
|
@@ -16,7 +16,7 @@ const activeComment = ref(commentSystems.value[0])
|
|
|
16
16
|
<YunCard w="full" p="4" class="comment yun-comment sm:p-6 lg:px-12 xl:px-16">
|
|
17
17
|
<ClientOnly>
|
|
18
18
|
<div v-if="commentSystems.length > 1" class="flex justify-end w-full mb-2">
|
|
19
|
-
<YunSelect v-model="activeComment" :options="commentSystems" />
|
|
19
|
+
<YunSelect v-model="activeComment" :options="commentSystems" compact />
|
|
20
20
|
</div>
|
|
21
21
|
<YunWaline v-if="activeComment === 'waline'" />
|
|
22
22
|
<YunTwikoo v-if="activeComment === 'twikoo'" />
|
|
@@ -1,55 +1,71 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { useEventListener } from '@vueuse/core'
|
|
3
|
-
import { computed, nextTick,
|
|
2
|
+
import { useEventListener, useMediaQuery } from '@vueuse/core'
|
|
3
|
+
import { computed, nextTick, useTemplateRef, watch } from 'vue'
|
|
4
4
|
import { useI18n } from 'vue-i18n'
|
|
5
5
|
import { useRoute } from 'vue-router'
|
|
6
6
|
import { useThemeConfig } from '../composables'
|
|
7
|
-
import { resolveYunDocsSidebar } from '../utils/sidebar'
|
|
7
|
+
import { hasYunDocsSidebarNavigation, resolveYunDocsSidebar } from '../utils/sidebar'
|
|
8
|
+
|
|
9
|
+
withDefaults(defineProps<{
|
|
10
|
+
floatingTrigger?: boolean
|
|
11
|
+
}>(), {
|
|
12
|
+
floatingTrigger: true,
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
const isOpen = defineModel<boolean>('open', { default: false })
|
|
8
16
|
|
|
9
17
|
const route = useRoute()
|
|
10
18
|
const themeConfig = useThemeConfig()
|
|
11
19
|
const { t } = useI18n()
|
|
12
20
|
|
|
13
21
|
const sidebarItems = computed(() => resolveYunDocsSidebar(themeConfig.value.sidebar, route.path))
|
|
14
|
-
const
|
|
22
|
+
const hasNavigation = computed(() => hasYunDocsSidebarNavigation(sidebarItems.value, route.path))
|
|
23
|
+
const isDesktop = useMediaQuery('(min-width: 1024px)', { ssrWidth: 1280 })
|
|
24
|
+
const closeButton = useTemplateRef<HTMLButtonElement>('closeButton')
|
|
15
25
|
const triggerRef = useTemplateRef<HTMLButtonElement>('trigger')
|
|
16
26
|
|
|
27
|
+
watch(isOpen, (open) => {
|
|
28
|
+
if (open)
|
|
29
|
+
nextTick(() => closeButton.value?.focus({ preventScroll: true }))
|
|
30
|
+
})
|
|
31
|
+
|
|
17
32
|
watch(
|
|
18
33
|
() => route.fullPath,
|
|
19
34
|
() => isOpen.value = false,
|
|
20
35
|
)
|
|
36
|
+
watch(isDesktop, (desktop) => {
|
|
37
|
+
if (desktop)
|
|
38
|
+
isOpen.value = false
|
|
39
|
+
})
|
|
21
40
|
|
|
22
41
|
useEventListener('keydown', (event: KeyboardEvent) => {
|
|
23
42
|
if (event.key === 'Escape' && isOpen.value)
|
|
24
43
|
close(true)
|
|
25
44
|
})
|
|
26
45
|
|
|
27
|
-
function toggle() {
|
|
28
|
-
isOpen.value = !isOpen.value
|
|
29
|
-
}
|
|
30
|
-
|
|
31
46
|
function close(restoreFocus = false) {
|
|
32
47
|
isOpen.value = false
|
|
33
48
|
if (restoreFocus)
|
|
34
|
-
nextTick(() => triggerRef.value?.focus())
|
|
49
|
+
nextTick(() => (triggerRef.value || document.querySelector<HTMLElement>('[aria-controls="yun-docs-sidebar"][aria-expanded="false"]'))?.focus({ preventScroll: true }))
|
|
35
50
|
}
|
|
36
51
|
</script>
|
|
37
52
|
|
|
38
53
|
<template>
|
|
39
|
-
<template v-if="
|
|
54
|
+
<template v-if="hasNavigation">
|
|
40
55
|
<button
|
|
56
|
+
v-if="floatingTrigger"
|
|
41
57
|
ref="trigger"
|
|
42
58
|
type="button"
|
|
43
59
|
class="yun-docs-sidebar-trigger yun-icon-btn lg:hidden"
|
|
44
60
|
:aria-label="t('theme.docsSidebar')"
|
|
45
61
|
:aria-expanded="isOpen"
|
|
46
62
|
aria-controls="yun-docs-sidebar"
|
|
47
|
-
@click="
|
|
63
|
+
@click="isOpen = !isOpen"
|
|
48
64
|
>
|
|
49
|
-
<span i-ri-
|
|
65
|
+
<span i-ri-side-bar-line aria-hidden="true" />
|
|
50
66
|
</button>
|
|
51
67
|
|
|
52
|
-
<YunOverlay :show="isOpen" @click="close()" />
|
|
68
|
+
<YunOverlay :show="isOpen" @click="close(true)" />
|
|
53
69
|
|
|
54
70
|
<aside
|
|
55
71
|
id="yun-docs-sidebar-desktop"
|
|
@@ -65,6 +81,18 @@ function close(restoreFocus = false) {
|
|
|
65
81
|
id="yun-docs-sidebar"
|
|
66
82
|
class="va-card yun-docs-sidebar yun-docs-sidebar-mobile"
|
|
67
83
|
>
|
|
84
|
+
<div class="yun-docs-sidebar-mobile-header">
|
|
85
|
+
<span>{{ t('theme.docsSidebar') }}</span>
|
|
86
|
+
<button
|
|
87
|
+
ref="closeButton"
|
|
88
|
+
type="button"
|
|
89
|
+
class="yun-icon-btn"
|
|
90
|
+
:aria-label="t('theme.closeMenu')"
|
|
91
|
+
@click="close(true)"
|
|
92
|
+
>
|
|
93
|
+
<span i-ri-close-line aria-hidden="true" />
|
|
94
|
+
</button>
|
|
95
|
+
</div>
|
|
68
96
|
<nav :aria-label="t('theme.docsSidebar')">
|
|
69
97
|
<YunDocsSidebarNav :items="sidebarItems" @navigate="close()" />
|
|
70
98
|
</nav>
|
|
@@ -92,6 +120,18 @@ function close(restoreFocus = false) {
|
|
|
92
120
|
border-radius: 0 0.5rem 0.5rem 0;
|
|
93
121
|
}
|
|
94
122
|
|
|
123
|
+
.yun-docs-sidebar-mobile-header {
|
|
124
|
+
display: flex;
|
|
125
|
+
align-items: center;
|
|
126
|
+
justify-content: space-between;
|
|
127
|
+
min-height: 56px;
|
|
128
|
+
margin: -1rem -1rem 1rem;
|
|
129
|
+
padding: 0 12px 0 20px;
|
|
130
|
+
border-bottom: 1px solid var(--yun-surface-line);
|
|
131
|
+
font-size: 16px;
|
|
132
|
+
font-weight: 600;
|
|
133
|
+
}
|
|
134
|
+
|
|
95
135
|
.yun-docs-sidebar-trigger {
|
|
96
136
|
position: fixed;
|
|
97
137
|
bottom: 4.75rem;
|
|
@@ -44,7 +44,7 @@ defineSlots<{
|
|
|
44
44
|
<DropdownMenuTrigger as-child>
|
|
45
45
|
<slot name="trigger" />
|
|
46
46
|
</DropdownMenuTrigger>
|
|
47
|
-
<DropdownMenuPortal>
|
|
47
|
+
<DropdownMenuPortal to="#valaxy-teleports">
|
|
48
48
|
<DropdownMenuContent class="yun-dropdown-menu-content" :side-offset="sideOffset" :align="align">
|
|
49
49
|
<template v-for="(item, index) in items" :key="`${item.key}-${index}`">
|
|
50
50
|
<DropdownMenuSeparator v-if="item.key === 'separator'" class="yun-dropdown-menu-separator" />
|
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { ref } from 'vue'
|
|
3
2
|
import { useYunAppStore } from '../stores'
|
|
4
3
|
|
|
5
4
|
const yunApp = useYunAppStore()
|
|
6
|
-
const fullscreenMenuRef = ref<HTMLElement>()
|
|
7
5
|
</script>
|
|
8
6
|
|
|
9
7
|
<template>
|
|
10
8
|
<Transition name="slide-left">
|
|
11
9
|
<div
|
|
12
10
|
v-if="yunApp.fullscreenMenu.isOpen"
|
|
13
|
-
ref="fullscreenMenuRef"
|
|
14
11
|
p="t-12 md:t-20"
|
|
15
12
|
class="yun-fullscreen-menu fixed left-0 right-0 bottom-0 top-0 bg-$va-c-bg-soft z-$yun-z-fullscreen-menu overflow-auto max-w-md shadow-2xl"
|
|
16
13
|
>
|