-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathLayout.vue
More file actions
237 lines (206 loc) · 6.54 KB
/
Layout.vue
File metadata and controls
237 lines (206 loc) · 6.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
<template>
<dt-root-layout
:fixed="false"
:header-sticky="true"
header-class="d-ol-none"
sidebar-class="dialtone-sidebar d-d-none lg:d-d-block d-ol-none"
footer-class="d-text-right d-ol-none"
content-class="d-ol-none dialtone-content"
>
<template #header>
<div class="dialtone-header">
<!-- <dialtone-logo /> -->
<router-link
class="d-pis-100"
title="Dialtone homepage"
to="/"
>
<dt-stack>
<dt-illustration name="dialpad-logo" />
</dt-stack>
</router-link>
<navbar
@search="openSearch"
/>
<mobile-sidebar
v-if="isMobile && route.path !== '/'"
/>
</div>
<!-- eslint-disable-next-line vue/no-undef-components -->
<div
id="docsearch"
ref="docSearchBtn"
class="d-d-none"
options=""
/>
</template>
<template
v-if="!$frontmatter.home"
#sidebar
>
<sidebar />
</template>
<template #default>
<home v-if="$frontmatter.home" />
<page
v-else
:prev="$frontmatter.prev || prev"
:next="$frontmatter.next || next"
:is-mobile="isMobile"
/>
</template>
</dt-root-layout>
</template>
<script setup>
import Navbar from '../components/Navbar.vue';
import Sidebar from '../components/Sidebar.vue';
import Home from '../components/Home.vue';
import Page from '../components/Page.vue';
import MobileSidebar from '../components/MobileSidebar.vue';
import { computed, ref, watch, onMounted, onUnmounted } from 'vue';
import { useRoute } from 'vue-router';
import { useThemeLocaleData } from '@vuepress/plugin-theme-data/client';
import { disableRootScrolling, enableRootScrolling } from '@dialpad/dialtone-vue';
const route = useRoute();
const prev = ref(null);
const next = ref(null);
const docSearchBtn = ref(null);
const items = useThemeLocaleData().value.sidebar;
const mobileBreakpoint = 980;
const evaluateWindowWidth = () => {
isMobile.value = window.innerWidth <= mobileBreakpoint;
};
let observer = null;
const isMobile = ref(false);
/**
* Determine which top-level group the current route belongs to
* @param {string} path Current route path
* @returns {string} The top-level group key
*/
function detectTopLevelGroup(path) {
// Map routes to top-level groups
const designSystemPaths = ['/components/', '/utilities/', '/tokens/', '/guides/', '/about/', '/functions-and-utilities/'];
if (designSystemPaths.some(p => path.includes(p))) {
return 'dialtone';
}
if (path.includes('/foundations/')) {
return 'foundations';
}
if (path.includes('/ui-kits/')) {
return 'ui-kits';
}
if (path.includes('/careers/')) {
return 'careers';
}
if (path.includes('/articles/')) {
return 'articles';
}
if (path.includes('/dialtone/')) {
return 'dialtone';
}
// Default to dialtone for any unknown paths
return 'dialtone';
}
/**
* Recursively extract all navigable pages from a tree structure
* Groups them by their parent category for pagination purposes
* Includes both parent pages with children AND leaf nodes
*/
function extractLeafNodes(items, planned = false) {
const groups = [];
function traverse(itemsList, currentGroup = []) {
itemsList.forEach(item => {
if (item.planned && !planned) return;
// Include this item if it has a link (it's a navigable page)
if (item.link) {
currentGroup.push(item);
}
// If it has children, recurse deeper
if (item.children && item.children.length > 0) {
traverse(item.children, currentGroup);
}
});
}
items.forEach(parentItem => {
const group = [];
// Include parent if it has a link
if (parentItem.link && (!parentItem.planned || planned)) {
group.push(parentItem);
}
// Also traverse children if they exist
if (parentItem.children) {
traverse(parentItem.children, group);
}
if (group.length > 0) {
groups.push(group);
}
});
return groups;
}
// Remove "planned" items to avoid errors
const currentItems = computed(() => {
// Check if using new top-level groups structure
if (items.topLevelGroups) {
const topLevelGroup = detectTopLevelGroup(route.path);
const sections = items.topLevelGroups[topLevelGroup]?.sections || {};
// Flatten all sections into a single array
const allSections = Object.values(sections).flat();
if (!allSections.length) return null;
// Extract all leaf nodes (actual pages) recursively
return extractLeafNodes(allSections);
}
// Fallback to old flat structure (for backwards compatibility)
const key = Object.keys(items).filter(item => route.path.includes(item));
if (!Array.isArray(items[key])) return null;
return items[key].map(item => item.children.filter(child => !child.planned));
});
// Finds the current item
const findCurrent = () => {
if (!currentItems.value) return;
prev.value = null;
next.value = null;
if (route.path.includes('/dialtone/whats-new/posts/')) {
prev.value = { link: '/dialtone/whats-new/', text: 'Back to what\'s new' };
return;
}
const parentIndex = currentItems.value.findIndex(item => item.find(child => child.link === route.path));
if (parentIndex === -1) return;
const filteredItems = currentItems.value[parentIndex];
const childIndex = Object.values(filteredItems).findIndex(child => child.link === route.path);
const isFirstItem = childIndex === 0;
const isLastItem = childIndex === filteredItems.length - 1;
const prevItems = currentItems.value[parentIndex - 1];
const nextItems = currentItems.value[parentIndex + 1];
prev.value = isFirstItem && prevItems ? prevItems[prevItems.length - 1] : filteredItems[childIndex - 1];
next.value = isLastItem && nextItems ? nextItems[0] : filteredItems[childIndex + 1];
};
const openSearch = () => {
docSearchBtn.value?.children[0]?.click();
};
watch(
() => route.path,
() => {
if (route.path === '/') return;
findCurrent();
},
{ immediate: true },
);
onMounted(() => {
evaluateWindowWidth();
window.addEventListener('resize', evaluateWindowWidth);
observer = new MutationObserver((mutationList) => {
for (const mutation of mutationList) {
if (mutation.type === 'attributes') {
mutation.target.classList.contains('DocSearch--active')
? disableRootScrolling()
: enableRootScrolling();
}
}
});
observer.observe(document.body, { attributes: true });
});
onUnmounted(() => {
window.removeEventListener('resize', evaluateWindowWidth);
observer?.disconnect();
});
</script>