|
1 | 1 | <script lang="ts"> |
2 | 2 | import { clickoutside } from '@svelte-put/clickoutside'; |
3 | | - import { toc, createTocStore } from '@svelte-put/toc'; |
| 3 | + import { toc, toclink, createTocStore } from '@svelte-put/toc'; |
4 | 4 | import { slide } from '@svelte-put/transitions'; |
5 | 5 | import gruvboxDark from 'svelte-highlight/styles/gruvbox-dark-soft'; |
6 | 6 | import gruvboxLight from 'svelte-highlight/styles/gruvbox-light-soft'; |
7 | | - import { fly, fade } from 'svelte/transition'; |
| 7 | + import { fade } from 'svelte/transition'; |
8 | 8 |
|
9 | 9 | import { browser } from '$app/environment'; |
10 | 10 | import { invalidate } from '$app/navigation'; |
|
49 | 49 | rightSidebarTogglerClick++; |
50 | 50 | } |
51 | 51 |
|
52 | | - let tocChangePaused = false; |
53 | | - let activeTocId: string; |
54 | 52 | const tocStore = createTocStore(); |
55 | | - $: if (!tocChangePaused) activeTocId = $tocStore.activeItem?.id ?? ''; |
56 | | -
|
57 | | - let intersectResetTimeoutId: ReturnType<typeof setTimeout>; |
58 | | - function handleTocItemClick(id: string) { |
59 | | - clearTimeout(intersectResetTimeoutId); |
60 | | - activeTocId = id; |
61 | | - tocChangePaused = true; |
62 | | - intersectResetTimeoutId = setTimeout(() => { |
63 | | - tocChangePaused = false; |
64 | | - }, 800); |
65 | | - } |
66 | 53 |
|
67 | 54 | $: rColorScheme = |
68 | 55 | $page.data.colorScheme === 'system' && browser |
|
130 | 117 | </div> |
131 | 118 | </header> |
132 | 119 |
|
133 | | - <div class="c-container relative flex w-full flex-1 items-stretch"> |
134 | | - {#key leftSidebarTogglerClick} |
135 | | - <nav |
136 | | - class="sidebar sidebar-left" |
137 | | - transition:fly|local={{ x: -200, duration: 200 }} |
138 | | - use:clickoutside={{ enabled: leftSidebarOpen && !isLg }} |
139 | | - on:clickoutside={toggleLeftSidebar} |
140 | | - data-open={leftSidebarOpen} |
141 | | - aria-label="Pages" |
142 | | - > |
143 | | - <ul class="sidebar-content"> |
144 | | - <li> |
145 | | - <a |
146 | | - href={APP_ROUTE_TREE.docs.$.path()} |
147 | | - data-current={data.pathname === APP_ROUTE_TREE.docs.$.path()} |
148 | | - class="c-link block py-2 font-bold" |
149 | | - on:click={closeLeftSidebar} |
150 | | - data-sveltekit-preload-data="hover" |
151 | | - > |
152 | | - Introduction |
153 | | - </a> |
| 120 | + <div class="relative flex w-full flex-1 items-stretch"> |
| 121 | + <nav |
| 122 | + class="sidebar sidebar-left" |
| 123 | + use:clickoutside={{ enabled: leftSidebarOpen && !isLg }} |
| 124 | + on:clickoutside={toggleLeftSidebar} |
| 125 | + data-open={leftSidebarOpen} |
| 126 | + aria-label="Pages" |
| 127 | + > |
| 128 | + <ul class="sidebar-content"> |
| 129 | + <li> |
| 130 | + <a |
| 131 | + href={APP_ROUTE_TREE.docs.$.path()} |
| 132 | + data-current={data.pathname === APP_ROUTE_TREE.docs.$.path()} |
| 133 | + class="c-link block py-2 font-bold" |
| 134 | + on:click={closeLeftSidebar} |
| 135 | + data-sveltekit-preload-data="hover" |
| 136 | + > |
| 137 | + Introduction |
| 138 | + </a> |
| 139 | + </li> |
| 140 | + {#each Object.entries(packagesByCategory) as [category, packages]} |
| 141 | + <li class="py-2"> |
| 142 | + <p class="font-bold">{capitalize(category)}</p> |
| 143 | + <ul class="mt-2 space-y-1 border-l border-border"> |
| 144 | + {#each packages as { path, status, id }} |
| 145 | + <li> |
| 146 | + <a |
| 147 | + data-sveltekit-preload-data="hover" |
| 148 | + href={path} |
| 149 | + data-current={data.pathname.includes(id)} |
| 150 | + class="c-link -ml-px block border-l border-transparent py-1 pl-3 data-current:border-primary" |
| 151 | + on:click={closeLeftSidebar} |
| 152 | + > |
| 153 | + <span class="h-full w-1 bg-primary" /> |
| 154 | + {id} |
| 155 | + <sup> |
| 156 | + {#if status !== 'stable'} |
| 157 | + <StatusBadge {status} /> |
| 158 | + {/if} |
| 159 | + </sup> |
| 160 | + </a> |
| 161 | + </li> |
| 162 | + {/each} |
| 163 | + </ul> |
154 | 164 | </li> |
155 | | - {#each Object.entries(packagesByCategory) as [category, packages]} |
156 | | - <li class="py-2"> |
157 | | - <p class="font-bold">{capitalize(category)}</p> |
158 | | - <ul class="mt-2 space-y-1 border-l border-border"> |
159 | | - {#each packages as { path, status, id }} |
160 | | - <li> |
161 | | - <a |
162 | | - data-sveltekit-preload-data="hover" |
163 | | - href={path} |
164 | | - data-current={data.pathname.includes(id)} |
165 | | - class="c-link -ml-px block border-l border-transparent py-1 pl-3 data-current:border-primary" |
166 | | - on:click={closeLeftSidebar} |
167 | | - > |
168 | | - <span class="h-full w-1 bg-primary" /> |
169 | | - {id} |
170 | | - <sup> |
171 | | - {#if status !== 'stable'} |
172 | | - <StatusBadge {status} /> |
173 | | - {/if} |
174 | | - </sup> |
175 | | - </a> |
176 | | - </li> |
177 | | - {/each} |
178 | | - </ul> |
179 | | - </li> |
180 | | - {/each} |
181 | | - </ul> |
182 | | - </nav> |
183 | | - {/key} |
| 165 | + {/each} |
| 166 | + </ul> |
| 167 | + </nav> |
184 | 168 | {#if leftSidebarOpen} |
185 | 169 | <div class="sidebar-backdrop" transition:fade|local={{ duration: 150 }} /> |
186 | 170 | {/if} |
|
198 | 182 | </main> |
199 | 183 | {/key} |
200 | 184 |
|
201 | | - {#key rightSidebarTogglerClick} |
202 | | - <nav |
203 | | - class="sidebar sidebar-right" |
204 | | - transition:fly|local={{ x: 200, duration: 200 }} |
205 | | - use:clickoutside={{ enabled: rightSidebarOpen && !isXl }} |
206 | | - on:clickoutside={toggleRightSidebar} |
207 | | - data-open={rightSidebarOpen} |
208 | | - aria-label="Table of Contents" |
209 | | - > |
210 | | - <div class="sidebar-content text-sm"> |
211 | | - {#if Object.values($tocStore.items).length} |
212 | | - <p class="py-2 font-bold uppercase">On This Page</p> |
213 | | - <ul class="space-y-1 border-l border-border"> |
214 | | - {#each Object.values($tocStore.items) as { id, text, element }} |
215 | | - {@const level = element.tagName.slice(1)} |
216 | | - {@const current = id === activeTocId} |
217 | | - <li> |
218 | | - <a |
219 | | - href={`#${id}`} |
220 | | - data-current={current} |
221 | | - class="c-link -ml-px block border-l border-transparent py-1 data-current:border-primary" |
222 | | - class:pl-3={level === '2'} |
223 | | - class:pl-5={level === '3'} |
224 | | - class:pl-7={level === '4'} |
225 | | - class:pl-9={level === '5'} |
226 | | - class:pl-11={level === '6'} |
227 | | - on:click={() => handleTocItemClick(id)} |
228 | | - > |
229 | | - {text} |
230 | | - </a> |
231 | | - </li> |
232 | | - {/each} |
233 | | - </ul> |
234 | | - {/if} |
235 | | - </div> |
236 | | - </nav> |
237 | | - {/key} |
| 185 | + <nav |
| 186 | + class="sidebar sidebar-right" |
| 187 | + use:clickoutside={{ enabled: rightSidebarOpen && !isXl }} |
| 188 | + on:clickoutside={toggleRightSidebar} |
| 189 | + data-open={rightSidebarOpen} |
| 190 | + aria-label="Table of Contents" |
| 191 | + > |
| 192 | + <div class="sidebar-content text-sm"> |
| 193 | + {#if Object.values($tocStore.items).length} |
| 194 | + <p class="py-2 font-bold uppercase">On This Page</p> |
| 195 | + <ul class="space-y-1 border-l border-border"> |
| 196 | + {#each Object.values($tocStore.items) as tocItem} |
| 197 | + {@const level = tocItem.element.tagName.slice(1)} |
| 198 | + <li> |
| 199 | + <!-- svelte-ignore a11y-missing-attribute --> |
| 200 | + <a |
| 201 | + use:toclink={{ |
| 202 | + tocItem, |
| 203 | + store: tocStore, |
| 204 | + observe: { |
| 205 | + attribute: 'data-current', |
| 206 | + }, |
| 207 | + }} |
| 208 | + class="c-link -ml-px block border-l border-transparent py-1 data-current:border-primary" |
| 209 | + class:pl-3={level === '2'} |
| 210 | + class:pl-5={level === '3'} |
| 211 | + class:pl-7={level === '4'} |
| 212 | + class:pl-9={level === '5'} |
| 213 | + class:pl-11={level === '6'}>{tocItem.text}</a |
| 214 | + > |
| 215 | + </li> |
| 216 | + {/each} |
| 217 | + </ul> |
| 218 | + {/if} |
| 219 | + </div> |
| 220 | + </nav> |
238 | 221 | </div> |
239 | 222 |
|
240 | 223 | <footer class="border-t border-border bg-bg-soft py-6 text-center font-fira text-xs"> |
241 | 224 | <div class="c-container grid md:grid-cols-[auto,1fr,auto]"> |
242 | 225 | <div |
243 | | - class="max-md:mb-4 max-md:flex max-md:items-center max-md:justify-center max-md:space-x-8 md:space-y-2 md:text-left" |
| 226 | + class="md:space-y-2 md:text-left max-md:mb-4 max-md:flex max-md:items-center max-md:justify-center max-md:space-x-8" |
244 | 227 | > |
245 | 228 | <p> |
246 | 229 | <a href="/sitemap.xml" class="c-link" target="_blank"> |
|
287 | 270 | <style lang="postcss"> |
288 | 271 | .sidebar { |
289 | 272 | flex-shrink: 0; |
| 273 | +
|
290 | 274 | width: theme('spacing.sidebar'); |
| 275 | +
|
291 | 276 | font-size: theme('fontSize.sm'); |
| 277 | +
|
292 | 278 | border-color: theme('borderColor.border'); |
293 | 279 |
|
| 280 | + transition-timing-function: theme('transitionTimingFunction.DEFAULT'); |
| 281 | + transition-duration: 200ms; |
| 282 | + transition-property: transform, opacity; |
| 283 | +
|
294 | 284 | & .sidebar-content { |
295 | 285 | position: sticky; |
296 | 286 | top: theme('spacing.header'); |
| 287 | +
|
| 288 | + overflow: auto; |
| 289 | +
|
| 290 | + width: 100%; |
297 | 291 | padding-top: theme('spacing.10'); |
298 | 292 | } |
299 | 293 | } |
300 | 294 |
|
301 | 295 | .sidebar-left { |
302 | | - @media (max-width: theme('screens.lg')) { |
| 296 | + & .sidebar-content { |
| 297 | + max-height: calc(100vh - theme('spacing.header')); |
| 298 | + padding-right: theme('spacing.4'); |
| 299 | + padding-left: theme('spacing.4'); |
| 300 | + } |
| 301 | +
|
| 302 | + @media (max-width: theme('screens.max-md')) { |
303 | 303 | position: fixed; |
304 | 304 | z-index: theme('zIndex.sidebar'); |
305 | 305 | top: 0; |
306 | 306 | bottom: 0; |
307 | 307 | left: 0; |
| 308 | + transform: translateX(-100%); |
308 | 309 |
|
309 | | - display: none; |
310 | | - justify-content: center; |
| 310 | + display: flex; |
311 | 311 |
|
| 312 | + opacity: 0; |
312 | 313 | background-color: theme('backgroundColor.bg.DEFAULT'); |
313 | 314 | border-right: theme('borderWidth.DEFAULT'); |
314 | 315 | box-shadow: theme('boxShadow.lg'); |
315 | 316 |
|
316 | 317 | &[data-open='true'] { |
317 | | - display: flex; |
| 318 | + transform: translateX(0); |
| 319 | + opacity: 1; |
| 320 | + } |
| 321 | +
|
| 322 | + & .sidebar-content { |
| 323 | + max-height: 100vh; |
318 | 324 | } |
319 | 325 | } |
320 | 326 | } |
|
332 | 338 | @screen lg { |
333 | 339 | display: none; |
334 | 340 | } |
| 341 | +
|
| 342 | + &::-webkit-scrollbar { |
| 343 | + display: none; |
| 344 | + } |
335 | 345 | } |
336 | 346 |
|
337 | 347 | .sidebar-right { |
338 | | - @media (max-width: theme('screens.xl')) { |
| 348 | + & .sidebar-content { |
| 349 | + max-height: calc(100vh - theme('spacing.header')); |
| 350 | + padding: theme('spacing.2'); |
| 351 | + padding-top: theme('spacing.10'); |
| 352 | + } |
| 353 | +
|
| 354 | + @media (max-width: theme('screens.max-lg')) { |
339 | 355 | position: fixed; |
340 | 356 | z-index: theme('zIndex.sidebar'); |
341 | 357 | top: theme('spacing.header'); |
342 | 358 | right: 0; |
| 359 | + transform: translateX(100%); |
343 | 360 |
|
344 | | - display: none; |
| 361 | + display: flex; |
345 | 362 | justify-content: center; |
346 | 363 |
|
347 | 364 | width: auto; |
348 | 365 | max-width: 80vw; |
349 | | - padding: theme('spacing.4'); |
350 | 366 |
|
| 367 | + opacity: 0; |
351 | 368 | background-color: theme('backgroundColor.bg.DEFAULT'); |
352 | 369 | border-bottom-right-radius: theme('borderRadius.DEFAULT'); |
353 | 370 | border-bottom-left-radius: theme('borderRadius.DEFAULT'); |
354 | 371 | box-shadow: theme('boxShadow.lg'); |
355 | 372 |
|
356 | 373 | &[data-open='true'] { |
357 | | - display: flex; |
| 374 | + transform: translateX(0); |
| 375 | + opacity: 1; |
358 | 376 | } |
359 | 377 |
|
360 | 378 | & .sidebar-content { |
361 | | - padding: theme('spacing.4'); |
| 379 | + max-height: calc(95vh - theme('spacing.header')); |
| 380 | + padding: theme('spacing.8') theme('spacing.2') theme('spacing.2') theme('spacing.8'); |
362 | 381 | } |
363 | 382 | } |
364 | 383 | } |
|
0 commit comments