Skip to content

Commit ea26b9f

Browse files
committed
feat(VPSidebarItem): collapsible groups use 'details' [vuejs#3517, vuejs#3804, vuejs#3805]
1 parent 1263417 commit ea26b9f

File tree

2 files changed

+84
-96
lines changed

2 files changed

+84
-96
lines changed

src/client/theme-default/components/VPSidebarItem.vue

Lines changed: 82 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,10 @@ const props = defineProps<{
1010
}>()
1111
1212
const {
13-
collapsed,
14-
collapsible,
1513
isLink,
1614
isActiveLink,
1715
hasActiveLink,
18-
hasChildren,
19-
toggle
16+
hasChildren
2017
} = useSidebarControl(computed(() => props.item))
2118
2219
const sectionTag = computed(() => (hasChildren.value ? 'section' : `div`))
@@ -31,44 +28,59 @@ const textTag = computed(() => {
3128
: `h${props.depth + 2}`
3229
})
3330
34-
const itemRole = computed(() => (isLink.value ? undefined : 'button'))
35-
3631
const classes = computed(() => [
3732
[`level-${props.depth}`],
38-
{ collapsible: collapsible.value },
39-
{ collapsed: collapsed.value },
4033
{ 'is-link': isLink.value },
4134
{ 'is-active': isActiveLink.value },
4235
{ 'has-active': hasActiveLink.value }
4336
])
44-
45-
function onItemInteraction(e: MouseEvent | Event) {
46-
if ('key' in e && e.key !== 'Enter') {
47-
return
48-
}
49-
!props.item.link && toggle()
50-
}
51-
52-
function onCaretClick() {
53-
props.item.link && toggle()
54-
}
5537
</script>
5638

5739
<template>
5840
<component :is="sectionTag" class="VPSidebarItem" :class="classes">
41+
<details
42+
v-if="item.text && item.collapsed != null && hasChildren"
43+
class="item"
44+
:open="!item.collapsed"
45+
>
46+
<summary>
47+
<div class="indicator" />
48+
49+
<VPLink
50+
v-if="item.link"
51+
:tag="linkTag"
52+
class="link"
53+
:href="item.link"
54+
:rel="item.rel"
55+
:target="item.target"
56+
>
57+
<component :is="textTag" class="text" v-html="item.text" />
58+
</VPLink>
59+
<component v-else :is="textTag" class="text" v-html="item.text" />
60+
61+
<div class="caret">
62+
<span class="vpi-chevron-right caret-icon" />
63+
</div>
64+
</summary>
65+
66+
<div class="items">
67+
<template v-if="depth < 5">
68+
<VPSidebarItem
69+
v-for="i in item.items"
70+
:key="i.text"
71+
:item="i"
72+
:depth="depth + 1"
73+
/>
74+
</template>
75+
</div>
76+
</details>
5977
<div
60-
v-if="item.text"
78+
v-else
79+
:is="item.text"
6180
class="item"
62-
:role="itemRole"
63-
v-on="
64-
hasChildren
65-
? { click: onItemInteraction, keydown: onItemInteraction }
66-
: {}
67-
"
68-
:tabindex="hasChildren ? 0 : undefined"
6981
>
7082
<div class="indicator" />
71-
83+
7284
<VPLink
7385
v-if="item.link"
7486
:tag="linkTag"
@@ -80,21 +92,9 @@ function onCaretClick() {
8092
<component :is="textTag" class="text" v-html="item.text" />
8193
</VPLink>
8294
<component v-else :is="textTag" class="text" v-html="item.text" />
83-
84-
<div
85-
v-if="item.collapsed != null && hasChildren"
86-
class="caret"
87-
role="button"
88-
aria-label="toggle section"
89-
@click="onCaretClick"
90-
@keydown.enter="onCaretClick"
91-
tabindex="0"
92-
>
93-
<span class="vpi-chevron-right caret-icon" />
94-
</div>
9595
</div>
9696

97-
<div v-if="hasChildren" class="items">
97+
<div v-if="(item.collapsed == null || !item.text) && hasChildren" class="items">
9898
<template v-if="depth < 5">
9999
<VPSidebarItem
100100
v-for="i in item.items"
@@ -112,7 +112,7 @@ function onCaretClick() {
112112
padding-bottom: 24px;
113113
}
114114
115-
.VPSidebarItem.collapsed.level-0 {
115+
.VPSidebarItem.level-0:has(> details:not([open])) {
116116
padding-bottom: 10px;
117117
}
118118
@@ -122,8 +122,22 @@ function onCaretClick() {
122122
width: 100%;
123123
}
124124
125-
.VPSidebarItem.collapsible > .item {
125+
.VPSidebarItem details summary {
126126
cursor: pointer;
127+
display: flex;
128+
justify-content: space-between;
129+
130+
.link {
131+
flex-grow: 0;
132+
}
133+
134+
&::-webkit-details-marker {
135+
display: none;
136+
}
137+
138+
&::marker {
139+
content: '';
140+
}
127141
}
128142
129143
.indicator {
@@ -171,36 +185,30 @@ function onCaretClick() {
171185
color: var(--vp-c-text-2);
172186
}
173187
174-
.VPSidebarItem.level-0.is-link > .item > .link:hover .text,
175-
.VPSidebarItem.level-1.is-link > .item > .link:hover .text,
176-
.VPSidebarItem.level-2.is-link > .item > .link:hover .text,
177-
.VPSidebarItem.level-3.is-link > .item > .link:hover .text,
178-
.VPSidebarItem.level-4.is-link > .item > .link:hover .text,
179-
.VPSidebarItem.level-5.is-link > .item > .link:hover .text {
180-
color: var(--vp-c-brand-1);
181-
}
182-
183-
.VPSidebarItem.level-0.has-active > .item > .text,
184-
.VPSidebarItem.level-1.has-active > .item > .text,
185-
.VPSidebarItem.level-2.has-active > .item > .text,
186-
.VPSidebarItem.level-3.has-active > .item > .text,
187-
.VPSidebarItem.level-4.has-active > .item > .text,
188-
.VPSidebarItem.level-5.has-active > .item > .text,
189-
.VPSidebarItem.level-0.has-active > .item > .link > .text,
190-
.VPSidebarItem.level-1.has-active > .item > .link > .text,
191-
.VPSidebarItem.level-2.has-active > .item > .link > .text,
192-
.VPSidebarItem.level-3.has-active > .item > .link > .text,
193-
.VPSidebarItem.level-4.has-active > .item > .link > .text,
194-
.VPSidebarItem.level-5.has-active > .item > .link > .text {
195-
color: var(--vp-c-text-1);
196-
}
197-
198-
.VPSidebarItem.level-0.is-active > .item .link > .text,
199-
.VPSidebarItem.level-1.is-active > .item .link > .text,
200-
.VPSidebarItem.level-2.is-active > .item .link > .text,
201-
.VPSidebarItem.level-3.is-active > .item .link > .text,
202-
.VPSidebarItem.level-4.is-active > .item .link > .text,
203-
.VPSidebarItem.level-5.is-active > .item .link > .text {
188+
.VPSidebarItem.level-0.is-active > .item > summary .text,
189+
.VPSidebarItem.level-1.is-active > .item > summary .text,
190+
.VPSidebarItem.level-2.is-active > .item > summary .text,
191+
.VPSidebarItem.level-3.is-active > .item > summary .text,
192+
.VPSidebarItem.level-4.is-active > .item > summary .text,
193+
.VPSidebarItem.level-5.is-active > .item > summary .text,
194+
.VPSidebarItem.level-0.is-active > .item > .link > .text,
195+
.VPSidebarItem.level-1.is-active > .item > .link > .text,
196+
.VPSidebarItem.level-2.is-active > .item > .link > .text,
197+
.VPSidebarItem.level-3.is-active > .item > .link > .text,
198+
.VPSidebarItem.level-4.is-active > .item > .link > .text,
199+
.VPSidebarItem.level-5.is-active > .item > .link > .text,
200+
.VPSidebarItem.level-0 .link:hover >.text,
201+
.VPSidebarItem.level-1 .link:hover >.text,
202+
.VPSidebarItem.level-2 .link:hover >.text,
203+
.VPSidebarItem.level-3 .link:hover >.text,
204+
.VPSidebarItem.level-4 .link:hover >.text,
205+
.VPSidebarItem.level-5 .link:hover >.text,
206+
.VPSidebarItem.level-0 .link:focus >.text,
207+
.VPSidebarItem.level-1 .link:focus >.text,
208+
.VPSidebarItem.level-2 .link:focus >.text,
209+
.VPSidebarItem.level-3 .link:focus >.text,
210+
.VPSidebarItem.level-4 .link:focus >.text,
211+
.VPSidebarItem.level-5 .link:focus >.text {
204212
color: var(--vp-c-brand-1);
205213
}
206214
@@ -231,7 +239,7 @@ function onCaretClick() {
231239
transition: transform 0.25s;
232240
}
233241
234-
.VPSidebarItem.collapsed .caret-icon {
242+
.VPSidebarItem > details:not([open]) > summary .caret-icon {
235243
transform: rotate(0);
236244
}
237245
@@ -243,8 +251,4 @@ function onCaretClick() {
243251
border-left: 1px solid var(--vp-c-divider);
244252
padding-left: 16px;
245253
}
246-
247-
.VPSidebarItem.collapsed .items {
248-
display: none;
249-
}
250254
</style>

src/client/theme-default/composables/sidebar.ts

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,11 @@ import {
2020
import { useData } from './data'
2121

2222
export interface SidebarControl {
23-
collapsed: Ref<boolean>
2423
collapsible: ComputedRef<boolean>
2524
isLink: ComputedRef<boolean>
2625
isActiveLink: Ref<boolean>
2726
hasActiveLink: ComputedRef<boolean>
2827
hasChildren: ComputedRef<boolean>
29-
toggle(): void
3028
}
3129

3230
export function useSidebar() {
@@ -139,8 +137,6 @@ export function useSidebarControl(
139137
): SidebarControl {
140138
const { page, hash } = useData()
141139

142-
const collapsed = ref(false)
143-
144140
const collapsible = computed(() => {
145141
return item.value.collapsed != null
146142
})
@@ -171,27 +167,15 @@ export function useSidebarControl(
171167
return !!(item.value.items && item.value.items.length)
172168
})
173169

174-
watchEffect(() => {
175-
collapsed.value = !!(collapsible.value && item.value.collapsed)
176-
})
177-
178170
watchPostEffect(() => {
179-
;(isActiveLink.value || hasActiveLink.value) && (collapsed.value = false)
171+
isActiveLink.value || hasActiveLink.value
180172
})
181173

182-
function toggle() {
183-
if (collapsible.value) {
184-
collapsed.value = !collapsed.value
185-
}
186-
}
187-
188174
return {
189-
collapsed,
190175
collapsible,
191176
isLink,
192177
isActiveLink,
193178
hasActiveLink,
194-
hasChildren,
195-
toggle
179+
hasChildren
196180
}
197181
}

0 commit comments

Comments
 (0)