vitepress-theme-element-plus 1.3.1 → 1.4.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.
Files changed (45) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +3 -3
  3. package/client/components/A11yTag.vue +29 -29
  4. package/client/components/ApiTyping.vue +54 -54
  5. package/client/components/Backdrop.vue +41 -41
  6. package/client/components/Bili.vue +94 -94
  7. package/client/components/Content.vue +148 -148
  8. package/client/components/DeprecatedTag.vue +19 -19
  9. package/client/components/Doc.vue +219 -185
  10. package/client/components/DocAside.vue +46 -46
  11. package/client/components/DocAsideOutline.vue +145 -145
  12. package/client/components/DocFooter.vue +162 -162
  13. package/client/components/Footer.vue +100 -100
  14. package/client/components/FooterCopyright.vue +27 -27
  15. package/client/components/Layout.vue +159 -156
  16. package/client/components/Link.vue +41 -41
  17. package/client/components/LocalNav.vue +160 -160
  18. package/client/components/MobilePreviewFrame.vue +175 -0
  19. package/client/components/MobilePreviewLayout.vue +140 -0
  20. package/client/components/Nav.vue +69 -69
  21. package/client/components/NavBar.vue +203 -203
  22. package/client/components/NavBarTitle.vue +89 -89
  23. package/client/components/Sidebar.vue +129 -129
  24. package/client/components/SidebarGroup.vue +51 -51
  25. package/client/components/SidebarItem.vue +304 -304
  26. package/client/components/ThemeToggler.vue +129 -129
  27. package/client/components/VPNavBarSearch.vue +23 -23
  28. package/client/hooks/useBackTop.ts +71 -71
  29. package/client/hooks/useLangs.ts +50 -50
  30. package/client/hooks/useSidebarControl.ts +78 -78
  31. package/client/hooks/useSize.ts +69 -69
  32. package/client/icons/dark.vue +8 -8
  33. package/client/icons/light.vue +8 -8
  34. package/client/mobile-preview.ts +60 -0
  35. package/client/utils/client/common.ts +49 -49
  36. package/client/utils/client/outline.ts +116 -116
  37. package/client/utils/common.ts +90 -90
  38. package/index.ts +57 -45
  39. package/package.json +2 -2
  40. package/shared/constants.ts +3 -3
  41. package/styles/base.scss +105 -105
  42. package/styles/code.scss +290 -290
  43. package/styles/doc-content.scss +363 -363
  44. package/styles/index.scss +71 -71
  45. package/styles/tag-content.scss +30 -30
@@ -1,160 +1,160 @@
1
- <script lang="ts" setup>
2
- import { useWindowScroll } from '@vueuse/core'
3
- import { ElButton } from 'element-plus'
4
- import { useData } from 'vitepress'
5
- import { useLayout } from 'vitepress/theme'
6
- import { computed, onMounted, ref } from 'vue'
7
- import { useBackTop } from '../hooks/useBackTop'
8
- import 'element-plus/theme-chalk/el-button.css'
9
-
10
- defineProps<{
11
- open: boolean
12
- }>()
13
-
14
- defineEmits<{
15
- (e: 'openMenu'): void
16
- }>()
17
-
18
- const { theme } = useData()
19
- const { isHome, hasSidebar, hasLocalNav } = useLayout()
20
- const { y } = useWindowScroll()
21
-
22
- const navHeight = ref(0)
23
-
24
- onMounted(() => {
25
- navHeight.value = Number.parseInt(
26
- getComputedStyle(document.documentElement).getPropertyValue(
27
- '--vp-nav-height',
28
- ),
29
- )
30
- })
31
-
32
- const classes = computed(() => {
33
- return {
34
- 'VPLocalNav': true,
35
- 'has-sidebar': hasSidebar.value,
36
- 'empty': !hasLocalNav.value,
37
- 'fixed': !hasLocalNav.value && !hasSidebar.value,
38
- }
39
- })
40
-
41
- const { shouldShow, scrollToTop } = useBackTop()
42
- </script>
43
-
44
- <template>
45
- <div
46
- v-if="!isHome && (hasLocalNav || hasSidebar || y >= navHeight)"
47
- :class="classes"
48
- >
49
- <div class="container">
50
- <button
51
- v-if="hasSidebar"
52
- class="menu"
53
- :aria-expanded="open"
54
- aria-controls="VPSidebarNav"
55
- @click="$emit('openMenu')"
56
- >
57
- <span class="vpi-align-left menu-icon" />
58
- <span class="menu-text">
59
- {{ theme.sidebarMenuLabel || 'Menu' }}
60
- </span>
61
- </button>
62
- <Transition name="shifting">
63
- <ElButton
64
- :class="{ show: shouldShow }"
65
- link
66
- class="height-5 go-back-top"
67
- @click.prevent.stop="scrollToTop"
68
- >
69
- Back to top
70
- </ElButton>
71
- </Transition>
72
- </div>
73
- </div>
74
- </template>
75
-
76
- <style scoped>
77
- .VPLocalNav {
78
- position: sticky;
79
- top: 0;
80
- /*rtl:ignore*/
81
- left: 0;
82
- z-index: var(--vp-z-index-local-nav);
83
- border-bottom: 1px solid var(--vp-c-gutter);
84
- padding-top: var(--vp-layout-top-height, 0px);
85
- width: 100%;
86
- background-color: var(--vp-local-nav-bg-color);
87
- overflow: hidden;
88
- }
89
-
90
- .VPLocalNav.fixed {
91
- position: fixed;
92
- }
93
-
94
- @media (min-width: 960px) {
95
- .VPLocalNav {
96
- display: none;
97
- }
98
- }
99
-
100
- @media (min-width: 1440px) {
101
- .VPLocalNav {
102
- display: none;
103
- }
104
- }
105
-
106
- .container {
107
- display: flex;
108
- justify-content: space-between;
109
- align-items: center;
110
- }
111
-
112
- .menu {
113
- display: flex;
114
- align-items: center;
115
- line-height: 24px;
116
- font-size: 16px;
117
- font-weight: 500;
118
- color: var(--vp-c-text-2);
119
- transition: color 0.5s;
120
- }
121
-
122
- .menu:hover {
123
- color: var(--vp-c-text-1);
124
- transition: color 0.25s;
125
- }
126
-
127
- @media (min-width: 960px) {
128
- .menu {
129
- display: none;
130
- }
131
- }
132
-
133
- .menu-icon {
134
- margin-right: 8px;
135
- font-size: 16px;
136
- }
137
-
138
- .menu,
139
- :deep(.VPLocalNavOutlineDropdown > button) {
140
- padding: 12px 24px 11px;
141
- }
142
-
143
- @media (min-width: 768px) {
144
- .menu,
145
- :deep(.VPLocalNavOutlineDropdown > button) {
146
- padding: 12px 32px 11px;
147
- }
148
- }
149
-
150
- .go-back-top {
151
- transform: translateY(100%);
152
- opacity: 0;
153
- padding: 12px 32px 11px;
154
-
155
- &.show {
156
- transform: translateY(0);
157
- opacity: 1;
158
- }
159
- }
160
- </style>
1
+ <script lang="ts" setup>
2
+ import { useWindowScroll } from '@vueuse/core'
3
+ import { ElButton } from 'element-plus'
4
+ import { useData } from 'vitepress'
5
+ import { useLayout } from 'vitepress/theme'
6
+ import { computed, onMounted, ref } from 'vue'
7
+ import { useBackTop } from '../hooks/useBackTop'
8
+ import 'element-plus/theme-chalk/el-button.css'
9
+
10
+ defineProps<{
11
+ open: boolean
12
+ }>()
13
+
14
+ defineEmits<{
15
+ (e: 'openMenu'): void
16
+ }>()
17
+
18
+ const { theme } = useData()
19
+ const { isHome, hasSidebar, hasLocalNav } = useLayout()
20
+ const { y } = useWindowScroll()
21
+
22
+ const navHeight = ref(0)
23
+
24
+ onMounted(() => {
25
+ navHeight.value = Number.parseInt(
26
+ getComputedStyle(document.documentElement).getPropertyValue(
27
+ '--vp-nav-height',
28
+ ),
29
+ )
30
+ })
31
+
32
+ const classes = computed(() => {
33
+ return {
34
+ 'VPLocalNav': true,
35
+ 'has-sidebar': hasSidebar.value,
36
+ 'empty': !hasLocalNav.value,
37
+ 'fixed': !hasLocalNav.value && !hasSidebar.value,
38
+ }
39
+ })
40
+
41
+ const { shouldShow, scrollToTop } = useBackTop()
42
+ </script>
43
+
44
+ <template>
45
+ <div
46
+ v-if="!isHome && (hasLocalNav || hasSidebar || y >= navHeight)"
47
+ :class="classes"
48
+ >
49
+ <div class="container">
50
+ <button
51
+ v-if="hasSidebar"
52
+ class="menu"
53
+ :aria-expanded="open"
54
+ aria-controls="VPSidebarNav"
55
+ @click="$emit('openMenu')"
56
+ >
57
+ <span class="vpi-align-left menu-icon" />
58
+ <span class="menu-text">
59
+ {{ theme.sidebarMenuLabel || 'Menu' }}
60
+ </span>
61
+ </button>
62
+ <Transition name="shifting">
63
+ <ElButton
64
+ :class="{ show: shouldShow }"
65
+ link
66
+ class="height-5 go-back-top"
67
+ @click.prevent.stop="scrollToTop"
68
+ >
69
+ Back to top
70
+ </ElButton>
71
+ </Transition>
72
+ </div>
73
+ </div>
74
+ </template>
75
+
76
+ <style scoped>
77
+ .VPLocalNav {
78
+ position: sticky;
79
+ top: 0;
80
+ /*rtl:ignore*/
81
+ left: 0;
82
+ z-index: var(--vp-z-index-local-nav);
83
+ border-bottom: 1px solid var(--vp-c-gutter);
84
+ padding-top: var(--vp-layout-top-height, 0px);
85
+ width: 100%;
86
+ background-color: var(--vp-local-nav-bg-color);
87
+ overflow: hidden;
88
+ }
89
+
90
+ .VPLocalNav.fixed {
91
+ position: fixed;
92
+ }
93
+
94
+ @media (min-width: 960px) {
95
+ .VPLocalNav {
96
+ display: none;
97
+ }
98
+ }
99
+
100
+ @media (min-width: 1440px) {
101
+ .VPLocalNav {
102
+ display: none;
103
+ }
104
+ }
105
+
106
+ .container {
107
+ display: flex;
108
+ justify-content: space-between;
109
+ align-items: center;
110
+ }
111
+
112
+ .menu {
113
+ display: flex;
114
+ align-items: center;
115
+ line-height: 24px;
116
+ font-size: 16px;
117
+ font-weight: 500;
118
+ color: var(--vp-c-text-2);
119
+ transition: color 0.5s;
120
+ }
121
+
122
+ .menu:hover {
123
+ color: var(--vp-c-text-1);
124
+ transition: color 0.25s;
125
+ }
126
+
127
+ @media (min-width: 960px) {
128
+ .menu {
129
+ display: none;
130
+ }
131
+ }
132
+
133
+ .menu-icon {
134
+ margin-right: 8px;
135
+ font-size: 16px;
136
+ }
137
+
138
+ .menu,
139
+ :deep(.VPLocalNavOutlineDropdown > button) {
140
+ padding: 12px 24px 11px;
141
+ }
142
+
143
+ @media (min-width: 768px) {
144
+ .menu,
145
+ :deep(.VPLocalNavOutlineDropdown > button) {
146
+ padding: 12px 32px 11px;
147
+ }
148
+ }
149
+
150
+ .go-back-top {
151
+ transform: translateY(100%);
152
+ opacity: 0;
153
+ padding: 12px 32px 11px;
154
+
155
+ &.show {
156
+ transform: translateY(0);
157
+ opacity: 1;
158
+ }
159
+ }
160
+ </style>
@@ -0,0 +1,175 @@
1
+ <script setup lang="ts">
2
+ import { useData, useRoute } from 'vitepress'
3
+ import { computed, ref, watch } from 'vue'
4
+ import { resolveMobilePreviewId } from '../mobile-preview'
5
+
6
+ const { frontmatter, isDark, theme } = useData()
7
+ const route = useRoute()
8
+ const frameRef = ref<HTMLIFrameElement>()
9
+ const previewConfig = computed(() => theme.value.mobilePreview ?? {})
10
+
11
+ const demoId = computed(() => {
12
+ const value = frontmatter.value.mobileDemo
13
+ return typeof value === 'string'
14
+ ? resolveMobilePreviewId(value, previewConfig.value.demoRoot)
15
+ : ''
16
+ })
17
+
18
+ const frameWidth = computed(() => `${previewConfig.value.deviceWidth ?? 390}px`)
19
+ const viewportHeight = computed(() => `${previewConfig.value.deviceHeight ?? 760}px`)
20
+
21
+ const previewHref = computed(() => {
22
+ const previewPath = normalizePreviewPath(previewConfig.value.previewPath)
23
+ const search = new URLSearchParams({
24
+ demo: demoId.value,
25
+ theme: isDark.value ? 'dark' : 'light',
26
+ })
27
+
28
+ return `${resolveLocalePreviewPath(route.path, previewPath)}?${search.toString()}`
29
+ })
30
+
31
+ const frameStyle = computed(() => ({
32
+ '--vp-mobile-preview-width': frameWidth.value,
33
+ '--vp-mobile-preview-height': viewportHeight.value,
34
+ }))
35
+
36
+ function normalizePreviewPath(value: unknown): string {
37
+ if (typeof value !== 'string' || !value.trim())
38
+ return 'preview/'
39
+
40
+ return value
41
+ .trim()
42
+ .replace(/^\/+/, '')
43
+ .replace(/\/?$/, '/')
44
+ }
45
+
46
+ function resolveLocalePreviewPath(path: string, previewPath: string): string {
47
+ const matchedLocale = path.match(/^\/([^/]+)\//)
48
+ const localePrefix = matchedLocale ? `/${matchedLocale[1]}/` : '/'
49
+
50
+ return `${localePrefix}${previewPath}`
51
+ }
52
+
53
+ function syncTheme(): void {
54
+ frameRef.value?.contentWindow?.postMessage({
55
+ type: 'vp-mobile-preview-theme',
56
+ value: isDark.value ? 'dark' : 'light',
57
+ }, '*')
58
+ }
59
+
60
+ watch(isDark, syncTheme)
61
+ </script>
62
+
63
+ <template>
64
+ <section v-if="demoId" class="VPMobilePreviewFrame" :style="frameStyle">
65
+ <div class="preview-actions">
66
+ <span class="preview-actions__label">Mobile Preview</span>
67
+ <a class="preview-actions__link" :href="previewHref" target="_blank" rel="noreferrer">
68
+ Open
69
+ </a>
70
+ </div>
71
+
72
+ <div class="preview-phone">
73
+ <div class="preview-phone__camera" />
74
+ <iframe
75
+ ref="frameRef"
76
+ class="preview-phone__viewport"
77
+ :src="previewHref"
78
+ title="Mobile demo preview"
79
+ loading="lazy"
80
+ @load="syncTheme"
81
+ />
82
+ </div>
83
+
84
+ <a class="preview-mobile-link" :href="previewHref" target="_blank" rel="noreferrer">
85
+ Open mobile preview
86
+ </a>
87
+ </section>
88
+ </template>
89
+
90
+ <style scoped lang="scss">
91
+ .preview-actions {
92
+ display: none;
93
+ align-items: center;
94
+ justify-content: space-between;
95
+ margin-bottom: 12px;
96
+ padding: 0 4px;
97
+ }
98
+
99
+ .preview-actions__label {
100
+ color: var(--vp-c-text-2);
101
+ font-size: 12px;
102
+ font-weight: 600;
103
+ letter-spacing: 0.08em;
104
+ text-transform: uppercase;
105
+ }
106
+
107
+ .preview-actions__link,
108
+ .preview-mobile-link {
109
+ color: var(--vp-c-brand-1);
110
+ font-size: 13px;
111
+ font-weight: 600;
112
+ text-decoration: none;
113
+ }
114
+
115
+ .preview-phone {
116
+ display: none;
117
+ position: relative;
118
+ width: var(--vp-mobile-preview-width);
119
+ padding: 14px 10px;
120
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.86), rgba(255, 255, 255, 0.64)), rgba(255, 255, 255, 0.7);
121
+ border: 1px solid rgba(15, 23, 42, 0.08);
122
+ border-radius: 32px;
123
+ box-shadow:
124
+ 0 24px 80px rgba(15, 23, 42, 0.12),
125
+ inset 0 1px 0 rgba(255, 255, 255, 0.7);
126
+ backdrop-filter: blur(20px);
127
+ }
128
+
129
+ :global(.dark) .preview-phone {
130
+ background: linear-gradient(180deg, rgba(24, 24, 27, 0.92), rgba(24, 24, 27, 0.8)), rgba(24, 24, 27, 0.86);
131
+ border-color: rgba(255, 255, 255, 0.12);
132
+ box-shadow:
133
+ 0 24px 80px rgba(0, 0, 0, 0.45),
134
+ inset 0 1px 0 rgba(255, 255, 255, 0.06);
135
+ }
136
+
137
+ .preview-phone__camera {
138
+ position: absolute;
139
+ top: 8px;
140
+ left: 50%;
141
+ width: 104px;
142
+ height: 18px;
143
+ border-radius: 999px;
144
+ background: rgba(15, 23, 42, 0.92);
145
+ transform: translateX(-50%);
146
+ }
147
+
148
+ .preview-phone__viewport {
149
+ display: block;
150
+ width: 100%;
151
+ height: var(--vp-mobile-preview-height);
152
+ overflow: hidden;
153
+ background: var(--vp-c-bg);
154
+ border: 0;
155
+ border-radius: 24px;
156
+ }
157
+
158
+ .preview-mobile-link {
159
+ display: inline-flex;
160
+ }
161
+
162
+ @media (min-width: 1440px) {
163
+ .preview-actions {
164
+ display: flex;
165
+ }
166
+
167
+ .preview-phone {
168
+ display: block;
169
+ }
170
+
171
+ .preview-mobile-link {
172
+ display: none;
173
+ }
174
+ }
175
+ </style>
@@ -0,0 +1,140 @@
1
+ <script setup lang="ts">
2
+ import type { Component } from 'vue'
3
+ import { useData } from 'vitepress'
4
+ import { computed, inject, markRaw, onBeforeUnmount, onMounted, ref, shallowRef } from 'vue'
5
+ import { mobilePreviewRegistryKey, resolveMobilePreviewId } from '../mobile-preview'
6
+
7
+ const { frontmatter, theme } = useData()
8
+ const registry = inject(mobilePreviewRegistryKey)
9
+ const demoId = ref('')
10
+ const errorMessage = ref('')
11
+ const isLoading = ref(true)
12
+ const demoComponent = shallowRef<Component>()
13
+ const previewConfig = computed(() => theme.value.mobilePreview ?? {})
14
+
15
+ const statusMessage = computed(() => {
16
+ if (errorMessage.value)
17
+ return errorMessage.value
18
+
19
+ return isLoading.value ? 'Loading mobile preview...' : 'No preview loaded.'
20
+ })
21
+
22
+ function applyTheme(theme: string | null): void {
23
+ if (typeof document === 'undefined')
24
+ return
25
+
26
+ document.documentElement.classList.toggle('dark', theme === 'dark')
27
+ }
28
+
29
+ async function loadDemo(value: string | null): Promise<void> {
30
+ demoId.value = resolveMobilePreviewId(value ?? '', previewConfig.value.demoRoot)
31
+ demoComponent.value = undefined
32
+ errorMessage.value = ''
33
+ isLoading.value = true
34
+
35
+ if (!demoId.value) {
36
+ errorMessage.value = 'Missing demo id.'
37
+ isLoading.value = false
38
+ return
39
+ }
40
+
41
+ if (!registry) {
42
+ errorMessage.value = 'No mobile preview registry was provided.'
43
+ isLoading.value = false
44
+ return
45
+ }
46
+
47
+ const loader = registry[demoId.value]
48
+ if (!loader) {
49
+ errorMessage.value = `Unknown demo: ${demoId.value}`
50
+ isLoading.value = false
51
+ return
52
+ }
53
+
54
+ try {
55
+ const module = await loader()
56
+ const resolvedComponent = (module as { default?: Component }).default ?? module
57
+ demoComponent.value = markRaw(resolvedComponent as Component)
58
+ }
59
+ catch (error) {
60
+ errorMessage.value = error instanceof Error
61
+ ? error.message
62
+ : 'Failed to load the requested demo.'
63
+ }
64
+ finally {
65
+ isLoading.value = false
66
+ }
67
+ }
68
+
69
+ function syncFromLocation(): void {
70
+ if (typeof window === 'undefined')
71
+ return
72
+
73
+ const search = new URLSearchParams(window.location.search)
74
+ const fallbackDemo = typeof frontmatter.value.mobileDemo === 'string'
75
+ ? frontmatter.value.mobileDemo
76
+ : null
77
+ applyTheme(search.get('theme'))
78
+ void loadDemo(search.get('demo') ?? fallbackDemo)
79
+ }
80
+
81
+ function handleMessage(event: MessageEvent): void {
82
+ if (event.data?.type !== 'vp-mobile-preview-theme')
83
+ return
84
+
85
+ applyTheme(event.data.value)
86
+ }
87
+
88
+ onMounted(() => {
89
+ syncFromLocation()
90
+ window.addEventListener('message', handleMessage)
91
+ })
92
+
93
+ onBeforeUnmount(() => {
94
+ window.removeEventListener('message', handleMessage)
95
+ })
96
+ </script>
97
+
98
+ <template>
99
+ <main class="VPMobilePreviewLayout">
100
+ <component :is="demoComponent" v-if="demoComponent" />
101
+ <div v-else class="preview-status">
102
+ <p class="preview-status__title">
103
+ Mobile Preview
104
+ </p>
105
+ <p class="preview-status__body">
106
+ {{ statusMessage }}
107
+ </p>
108
+ </div>
109
+ </main>
110
+ </template>
111
+
112
+ <style scoped lang="scss">
113
+ .VPMobilePreviewLayout {
114
+ min-height: 100%;
115
+ height: 100vh;
116
+ overflow-x: hidden;
117
+ overflow-y: auto;
118
+ }
119
+
120
+ .preview-status {
121
+ display: grid;
122
+ place-items: center;
123
+ align-content: center;
124
+ min-height: 100vh;
125
+ padding: 24px;
126
+ color: var(--vp-c-text-2);
127
+ text-align: center;
128
+ }
129
+
130
+ .preview-status__title {
131
+ margin: 0 0 12px;
132
+ color: var(--vp-c-text-1);
133
+ font-weight: 600;
134
+ }
135
+
136
+ .preview-status__body {
137
+ margin: 0;
138
+ font-size: 14px;
139
+ }
140
+ </style>