Skip to content

Commit

Permalink
feat(VPSidebarItem): collapsible groups use 'details' [vuejs#3517, vu…
Browse files Browse the repository at this point in the history
  • Loading branch information
olets committed Apr 18, 2024
1 parent 2a651c8 commit d6e2e79
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 96 deletions.
160 changes: 82 additions & 78 deletions src/client/theme-default/components/VPSidebarItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,10 @@ const props = defineProps<{
}>()
const {
collapsed,
collapsible,
isLink,
isActiveLink,
hasActiveLink,
hasChildren,
toggle
hasChildren
} = useSidebarControl(computed(() => props.item))
const sectionTag = computed(() => (hasChildren.value ? 'section' : `div`))
Expand All @@ -31,44 +28,59 @@ const textTag = computed(() => {
: `h${props.depth + 2}`
})
const itemRole = computed(() => (isLink.value ? undefined : 'button'))
const classes = computed(() => [
[`level-${props.depth}`],
{ collapsible: collapsible.value },
{ collapsed: collapsed.value },
{ 'is-link': isLink.value },
{ 'is-active': isActiveLink.value },
{ 'has-active': hasActiveLink.value }
])
function onItemInteraction(e: MouseEvent | Event) {
if ('key' in e && e.key !== 'Enter') {
return
}
!props.item.link && toggle()
}
function onCaretClick() {
props.item.link && toggle()
}
</script>

<template>
<component :is="sectionTag" class="VPSidebarItem" :class="classes">
<details
v-if="item.text && item.collapsed != null && hasChildren"
class="item"
:open="!item.collapsed"
>
<summary>
<div class="indicator" />

<VPLink
v-if="item.link"
:tag="linkTag"
class="link"
:href="item.link"
:rel="item.rel"
:target="item.target"
>
<component :is="textTag" class="text" v-html="item.text" />
</VPLink>
<component v-else :is="textTag" class="text" v-html="item.text" />

<div class="caret">
<span class="vpi-chevron-right caret-icon" />
</div>
</summary>

<div class="items">
<template v-if="depth < 5">
<VPSidebarItem
v-for="i in item.items"
:key="i.text"
:item="i"
:depth="depth + 1"
/>
</template>
</div>
</details>
<div
v-if="item.text"
v-else
:is="item.text"
class="item"
:role="itemRole"
v-on="
hasChildren
? { click: onItemInteraction, keydown: onItemInteraction }
: {}
"
:tabindex="hasChildren ? 0 : undefined"
>
<div class="indicator" />

<VPLink
v-if="item.link"
:tag="linkTag"
Expand All @@ -80,21 +92,9 @@ function onCaretClick() {
<component :is="textTag" class="text" v-html="item.text" />
</VPLink>
<component v-else :is="textTag" class="text" v-html="item.text" />

<div
v-if="item.collapsed != null && hasChildren"
class="caret"
role="button"
aria-label="toggle section"
@click="onCaretClick"
@keydown.enter="onCaretClick"
tabindex="0"
>
<span class="vpi-chevron-right caret-icon" />
</div>
</div>

<div v-if="hasChildren" class="items">
<div v-if="(item.collapsed == null || !item.text) && hasChildren" class="items">
<template v-if="depth < 5">
<VPSidebarItem
v-for="i in item.items"
Expand All @@ -112,7 +112,7 @@ function onCaretClick() {
padding-bottom: 24px;
}
.VPSidebarItem.collapsed.level-0 {
.VPSidebarItem.level-0:has(> details:not([open])) {
padding-bottom: 10px;
}
Expand All @@ -122,8 +122,22 @@ function onCaretClick() {
width: 100%;
}
.VPSidebarItem.collapsible > .item {
.VPSidebarItem details summary {
cursor: pointer;
display: flex;
justify-content: space-between;
.link {
flex-grow: 0;
}
&::-webkit-details-marker {
display: none;
}
&::marker {
content: '';
}
}
.indicator {
Expand Down Expand Up @@ -171,36 +185,30 @@ function onCaretClick() {
color: var(--vp-c-text-2);
}
.VPSidebarItem.level-0.is-link > .item > .link:hover .text,
.VPSidebarItem.level-1.is-link > .item > .link:hover .text,
.VPSidebarItem.level-2.is-link > .item > .link:hover .text,
.VPSidebarItem.level-3.is-link > .item > .link:hover .text,
.VPSidebarItem.level-4.is-link > .item > .link:hover .text,
.VPSidebarItem.level-5.is-link > .item > .link:hover .text {
color: var(--vp-c-brand-1);
}
.VPSidebarItem.level-0.has-active > .item > .text,
.VPSidebarItem.level-1.has-active > .item > .text,
.VPSidebarItem.level-2.has-active > .item > .text,
.VPSidebarItem.level-3.has-active > .item > .text,
.VPSidebarItem.level-4.has-active > .item > .text,
.VPSidebarItem.level-5.has-active > .item > .text,
.VPSidebarItem.level-0.has-active > .item > .link > .text,
.VPSidebarItem.level-1.has-active > .item > .link > .text,
.VPSidebarItem.level-2.has-active > .item > .link > .text,
.VPSidebarItem.level-3.has-active > .item > .link > .text,
.VPSidebarItem.level-4.has-active > .item > .link > .text,
.VPSidebarItem.level-5.has-active > .item > .link > .text {
color: var(--vp-c-text-1);
}
.VPSidebarItem.level-0.is-active > .item .link > .text,
.VPSidebarItem.level-1.is-active > .item .link > .text,
.VPSidebarItem.level-2.is-active > .item .link > .text,
.VPSidebarItem.level-3.is-active > .item .link > .text,
.VPSidebarItem.level-4.is-active > .item .link > .text,
.VPSidebarItem.level-5.is-active > .item .link > .text {
.VPSidebarItem.level-0.is-active > .item > summary .text,
.VPSidebarItem.level-1.is-active > .item > summary .text,
.VPSidebarItem.level-2.is-active > .item > summary .text,
.VPSidebarItem.level-3.is-active > .item > summary .text,
.VPSidebarItem.level-4.is-active > .item > summary .text,
.VPSidebarItem.level-5.is-active > .item > summary .text,
.VPSidebarItem.level-0.is-active > .item > .link > .text,
.VPSidebarItem.level-1.is-active > .item > .link > .text,
.VPSidebarItem.level-2.is-active > .item > .link > .text,
.VPSidebarItem.level-3.is-active > .item > .link > .text,
.VPSidebarItem.level-4.is-active > .item > .link > .text,
.VPSidebarItem.level-5.is-active > .item > .link > .text,
.VPSidebarItem.level-0 .link:hover >.text,
.VPSidebarItem.level-1 .link:hover >.text,
.VPSidebarItem.level-2 .link:hover >.text,
.VPSidebarItem.level-3 .link:hover >.text,
.VPSidebarItem.level-4 .link:hover >.text,
.VPSidebarItem.level-5 .link:hover >.text,
.VPSidebarItem.level-0 .link:focus >.text,
.VPSidebarItem.level-1 .link:focus >.text,
.VPSidebarItem.level-2 .link:focus >.text,
.VPSidebarItem.level-3 .link:focus >.text,
.VPSidebarItem.level-4 .link:focus >.text,
.VPSidebarItem.level-5 .link:focus >.text {
color: var(--vp-c-brand-1);
}
Expand Down Expand Up @@ -231,7 +239,7 @@ function onCaretClick() {
transition: transform 0.25s;
}
.VPSidebarItem.collapsed .caret-icon {
.VPSidebarItem > details:not([open]) > summary .caret-icon {
transform: rotate(0);
}
Expand All @@ -243,8 +251,4 @@ function onCaretClick() {
border-left: 1px solid var(--vp-c-divider);
padding-left: 16px;
}
.VPSidebarItem.collapsed .items {
display: none;
}
</style>
20 changes: 2 additions & 18 deletions src/client/theme-default/composables/sidebar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,11 @@ import {
import { useData } from './data'

export interface SidebarControl {
collapsed: Ref<boolean>
collapsible: ComputedRef<boolean>
isLink: ComputedRef<boolean>
isActiveLink: Ref<boolean>
hasActiveLink: ComputedRef<boolean>
hasChildren: ComputedRef<boolean>
toggle(): void
}

export function useSidebar() {
Expand Down Expand Up @@ -139,8 +137,6 @@ export function useSidebarControl(
): SidebarControl {
const { page, hash } = useData()

const collapsed = ref(false)

const collapsible = computed(() => {
return item.value.collapsed != null
})
Expand Down Expand Up @@ -171,27 +167,15 @@ export function useSidebarControl(
return !!(item.value.items && item.value.items.length)
})

watchEffect(() => {
collapsed.value = !!(collapsible.value && item.value.collapsed)
})

watchPostEffect(() => {
;(isActiveLink.value || hasActiveLink.value) && (collapsed.value = false)
isActiveLink.value || hasActiveLink.value
})

function toggle() {
if (collapsible.value) {
collapsed.value = !collapsed.value
}
}

return {
collapsed,
collapsible,
isLink,
isActiveLink,
hasActiveLink,
hasChildren,
toggle
hasChildren
}
}

0 comments on commit d6e2e79

Please sign in to comment.