@@ -83,6 +85,11 @@ const { isHome, hasSidebar } = useLayout()
.VPContent.has-sidebar {
margin: var(--vp-layout-top-height, 0px) 0 0;
padding-left: var(--vp-sidebar-width);
+ transition: padding-left 0.25s;
+ }
+
+ .VPContent.has-sidebar.sidebar-collapsed {
+ padding-left: 0;
}
}
@@ -91,5 +98,9 @@ const { isHome, hasSidebar } = useLayout()
padding-right: calc((100% - var(--vp-layout-max-width)) / 2);
padding-left: calc((100% - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width));
}
+
+ .VPContent.has-sidebar.sidebar-collapsed {
+ padding-left: calc((100% - var(--vp-layout-max-width)) / 2);
+ }
}
diff --git a/src/client/theme-default/components/VPLocalNav.vue b/src/client/theme-default/components/VPLocalNav.vue
index f80bb361..f72e6e18 100644
--- a/src/client/theme-default/components/VPLocalNav.vue
+++ b/src/client/theme-default/components/VPLocalNav.vue
@@ -3,6 +3,7 @@ import { useWindowScroll } from '@vueuse/core'
import { computed, onMounted, ref } from 'vue'
import { useData } from '../composables/data'
import { useLayout } from '../composables/layout'
+import { useSidebarCollapse } from '../composables/sidebar'
import VPLocalNavOutlineDropdown from './VPLocalNavOutlineDropdown.vue'
defineProps<{
@@ -15,6 +16,7 @@ defineEmits<{
const { theme } = useData()
const { isHome, hasSidebar, headers, hasLocalNav } = useLayout()
+const { isCollapsed } = useSidebarCollapse()
const { y } = useWindowScroll()
const navHeight = ref(0)
@@ -31,6 +33,7 @@ const classes = computed(() => {
return {
VPLocalNav: true,
'has-sidebar': hasSidebar.value,
+ 'sidebar-collapsed': isCollapsed.value,
empty: !hasLocalNav.value,
fixed: !hasLocalNav.value && !hasSidebar.value
}
@@ -85,6 +88,11 @@ const classes = computed(() => {
.VPLocalNav.has-sidebar {
padding-left: var(--vp-sidebar-width);
+ transition: padding-left 0.3s ease;
+ }
+
+ .VPLocalNav.has-sidebar.sidebar-collapsed {
+ padding-left: 0;
}
.VPLocalNav.empty {
diff --git a/src/client/theme-default/components/VPNavBar.vue b/src/client/theme-default/components/VPNavBar.vue
index 963c4610..78f69241 100644
--- a/src/client/theme-default/components/VPNavBar.vue
+++ b/src/client/theme-default/components/VPNavBar.vue
@@ -2,6 +2,7 @@
import { useWindowScroll } from '@vueuse/core'
import { ref, watchPostEffect } from 'vue'
import { useLayout } from '../composables/layout'
+import { useSidebarCollapse } from '../composables/sidebar'
import VPNavBarAppearance from './VPNavBarAppearance.vue'
import VPNavBarExtra from './VPNavBarExtra.vue'
import VPNavBarHamburger from './VPNavBarHamburger.vue'
@@ -21,17 +22,29 @@ defineEmits<{
const { y } = useWindowScroll()
const { isHome, hasSidebar } = useLayout()
+const { isCollapsed, expand } = useSidebarCollapse()
+
+const searchRef = ref
| null>(null)
const classes = ref>({})
watchPostEffect(() => {
classes.value = {
'has-sidebar': hasSidebar.value,
+ 'sidebar-collapsed': isCollapsed.value,
'home': isHome.value,
'top': y.value === 0,
'screen-open': props.isScreenOpen
}
})
+
+function handleExpand() {
+ expand()
+}
+
+function handleCapsuleSearch() {
+ searchRef.value?.openSearch()
+}
@@ -43,12 +56,31 @@ watchPostEffect(() => {
+
-
+
@@ -140,6 +172,8 @@ watchPostEffect(() => {
flex-shrink: 0;
height: calc(var(--vp-nav-height) - 1px);
transition: background-color 0.5s;
+ display: flex;
+ align-items: center;
}
@media (min-width: 960px) {
@@ -152,6 +186,16 @@ watchPostEffect(() => {
width: var(--vp-sidebar-width);
height: var(--vp-nav-height);
background-color: transparent;
+ transition: width 0.3s ease, padding 0.3s ease;
+ }
+
+ .VPNavBar.has-sidebar.sidebar-collapsed .title {
+ padding-left: 24px;
+ width: auto;
+ }
+
+ .VPNavBar.has-sidebar.sidebar-collapsed .title :deep(.VPNavBarTitle) {
+ display: none;
}
}
@@ -160,6 +204,15 @@ watchPostEffect(() => {
padding-left: max(32px, calc((100% - (var(--vp-layout-max-width) - 64px)) / 2));
width: calc((100% - (var(--vp-layout-max-width) - 64px)) / 2 + var(--vp-sidebar-width) - 32px);
}
+
+ .VPNavBar.has-sidebar.sidebar-collapsed .title {
+ padding-left: 24px;
+ width: auto;
+ }
+
+ .VPNavBar.has-sidebar.sidebar-collapsed .title :deep(.VPNavBarTitle) {
+ display: none;
+ }
}
.content {
@@ -172,6 +225,12 @@ watchPostEffect(() => {
z-index: 1;
padding-left: var(--vp-sidebar-width);
padding-right: 32px;
+ transition: padding-left 0.3s ease;
+ }
+
+ .VPNavBar.has-sidebar.sidebar-collapsed .content {
+ padding-left: 0;
+ background-color: var(--vp-nav-bg-color);
}
}
@@ -180,6 +239,11 @@ watchPostEffect(() => {
padding-left: calc((100% - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width));
padding-right: calc((100% - var(--vp-layout-max-width)) / 2 + 32px);
}
+
+ .VPNavBar.has-sidebar.sidebar-collapsed .content {
+ padding-left: 0;
+ background-color: var(--vp-nav-bg-color);
+ }
}
.content-body {
@@ -204,6 +268,11 @@ watchPostEffect(() => {
margin-right: -100vw;
padding-right: 100vw;
}
+
+ .VPNavBar.has-sidebar.sidebar-collapsed .content-body {
+ margin-left: -32px;
+ padding-left: 32px;
+ }
}
@media (max-width: 767px) {
@@ -238,6 +307,55 @@ watchPostEffect(() => {
margin-right: -8px;
}
+/* Sidebar expand capsule - only show on desktop */
+.expand-capsule {
+ display: none;
+}
+
+@media (min-width: 960px) {
+ .expand-capsule {
+ display: flex;
+ align-items: center;
+ border: 1px solid var(--vp-c-divider);
+ border-radius: 20px;
+ background: var(--vp-c-bg);
+ padding: 4px;
+ margin-left: 12px;
+ gap: 2px;
+ box-shadow: var(--vp-shadow-1);
+ }
+
+ .capsule-btn {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 28px;
+ height: 28px;
+ border: none;
+ border-radius: 50%;
+ background: transparent;
+ color: var(--vp-c-text-2);
+ cursor: pointer;
+ transition: color 0.25s, background-color 0.25s;
+ }
+
+ .capsule-btn:hover {
+ color: var(--vp-c-text-1);
+ background: var(--vp-c-default-soft);
+ }
+
+ .capsule-icon {
+ width: 18px;
+ height: 18px;
+ }
+
+ .capsule-divider {
+ width: 1px;
+ height: 16px;
+ background: var(--vp-c-divider);
+ }
+}
+
.divider {
width: 100%;
height: 1px;
@@ -246,12 +364,25 @@ watchPostEffect(() => {
@media (min-width: 960px) {
.VPNavBar.has-sidebar .divider {
padding-left: var(--vp-sidebar-width);
+ transition: padding-left 0.3s ease;
+ }
+
+ .VPNavBar.has-sidebar.sidebar-collapsed .divider {
+ padding-left: 0;
+ }
+
+ .VPNavBar.has-sidebar.sidebar-collapsed .divider-line {
+ background-color: var(--vp-c-gutter);
}
}
@media (min-width: 1440px) {
.VPNavBar.has-sidebar .divider {
- padding-left: calc((100% - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width));
+ padding-left: calc((100% - (var(--vp-layout-max-width) - 64px)) / 2 + var(--vp-sidebar-width) - 32px);
+ }
+
+ .VPNavBar.has-sidebar.sidebar-collapsed .divider {
+ padding-left: 0;
}
}
diff --git a/src/client/theme-default/components/VPNavBarSearch.vue b/src/client/theme-default/components/VPNavBarSearch.vue
index 67c49300..cf062e63 100644
--- a/src/client/theme-default/components/VPNavBarSearch.vue
+++ b/src/client/theme-default/components/VPNavBarSearch.vue
@@ -2,10 +2,14 @@
import '@docsearch/css'
import { onKeyStroke } from '@vueuse/core'
import type { DefaultTheme } from 'vitepress/theme'
-import { defineAsyncComponent, onMounted, onUnmounted, ref } from 'vue'
+import { defineAsyncComponent, onMounted, onUnmounted, ref, watch } from 'vue'
import { useData } from '../composables/data'
import VPNavBarSearchButton from './VPNavBarSearchButton.vue'
+defineProps<{
+ iconOnly?: boolean
+}>()
+
const VPLocalSearchBox = __VP_LOCAL_SEARCH__
? defineAsyncComponent(() => import('./VPLocalSearchBox.vue'))
: () => null
@@ -21,6 +25,7 @@ const { theme } = useData()
// hit the hotkey to invoke it.
const loaded = ref(false)
const actuallyLoaded = ref(false)
+const pendingSearch = ref(false)
const preconnect = () => {
const id = 'VPAlgoliaPreconnect'
@@ -66,6 +71,14 @@ onMounted(() => {
onUnmounted(remove)
})
+function triggerAlgoliaSearch() {
+ const e = new KeyboardEvent('keydown', {
+ key: 'k',
+ metaKey: true,
+ })
+ window.dispatchEvent(e)
+}
+
function load() {
if (!loaded.value) {
loaded.value = true
@@ -75,12 +88,7 @@ function load() {
function poll() {
// programmatically open the search box after initialize
- const e = new Event('keydown') as any
-
- e.key = 'k'
- e.metaKey = true
-
- window.dispatchEvent(e)
+ triggerAlgoliaSearch()
setTimeout(() => {
if (!document.querySelector('.DocSearch-Modal')) {
@@ -122,17 +130,39 @@ if (__VP_LOCAL_SEARCH__) {
}
const provider = __ALGOLIA__ ? 'algolia' : __VP_LOCAL_SEARCH__ ? 'local' : ''
+
+watch(actuallyLoaded, (value) => {
+ if (value && pendingSearch.value) {
+ pendingSearch.value = false
+ poll()
+ }
+})
+
+function openSearch() {
+ if (__VP_LOCAL_SEARCH__) {
+ showSearch.value = true
+ } else if (__ALGOLIA__) {
+ if (actuallyLoaded.value) {
+ triggerAlgoliaSearch()
+ } else {
+ pendingSearch.value = true
+ loaded.value = true
+ }
+ }
+}
+
+defineExpose({ openSearch })
-
+
-
+
@@ -144,7 +174,7 @@ const provider = __ALGOLIA__ ? 'algolia' : __VP_LOCAL_SEARCH__ ? 'local' : ''
@vue:beforeMount="actuallyLoaded = true"
/>
-
+
@@ -169,4 +199,13 @@ const provider = __ALGOLIA__ ? 'algolia' : __VP_LOCAL_SEARCH__ ? 'local' : ''
padding-left: 32px;
}
}
+
+.VPNavBarSearch.icon-only {
+ flex-grow: 0;
+ padding-left: 0;
+}
+
+.VPNavBarSearch.icon-only #docsearch {
+ display: none;
+}
diff --git a/src/client/theme-default/components/VPNavBarTitle.vue b/src/client/theme-default/components/VPNavBarTitle.vue
index 9679f8a5..e9b249f0 100644
--- a/src/client/theme-default/components/VPNavBarTitle.vue
+++ b/src/client/theme-default/components/VPNavBarTitle.vue
@@ -3,12 +3,14 @@ import { computed } from 'vue'
import { useData } from '../composables/data'
import { useLangs } from '../composables/langs'
import { useLayout } from '../composables/layout'
+import { useSidebarCollapse } from '../composables/sidebar'
import { normalizeLink } from '../support/utils'
import VPImage from './VPImage.vue'
const { site, theme } = useData()
-const { hasSidebar } = useLayout()
+const { hasSidebar, isSidebarEnabled } = useLayout()
const { currentLang } = useLangs()
+const { isCollapsed, collapse } = useSidebarCollapse()
const link = computed(() =>
typeof theme.value.logoLink === 'string'
@@ -27,31 +29,63 @@ const target = computed(() =>
? undefined
: theme.value.logoLink?.target
)
+
+const showCollapseButton = computed(() => {
+ return hasSidebar.value && isSidebarEnabled.value && !isCollapsed.value
+})
+
+function handleCollapse() {
+ collapse()
+}
-
-
-
-
-
- {{ site.title }}
-
-
+
diff --git a/src/client/theme-default/components/VPSidebar.vue b/src/client/theme-default/components/VPSidebar.vue
index 7e6e2528..5e73655e 100644
--- a/src/client/theme-default/components/VPSidebar.vue
+++ b/src/client/theme-default/components/VPSidebar.vue
@@ -3,9 +3,11 @@ import { useScrollLock } from '@vueuse/core'
import { inBrowser } from 'vitepress'
import { ref, watch } from 'vue'
import { useLayout } from '../composables/layout'
+import { useSidebarCollapse } from '../composables/sidebar'
import VPSidebarGroup from './VPSidebarGroup.vue'
const { sidebarGroups, hasSidebar } = useLayout()
+const { isCollapsed } = useSidebarCollapse()
const props = defineProps<{
open: boolean
@@ -41,7 +43,7 @@ watch(