mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
feat(site): restore docs sidebar state on navigation (#160)
This commit is contained in:
@@ -4,7 +4,6 @@ import type { SupportedFramework, SupportedStyle } from '@/types/docs';
|
||||
import { PreferenceUpdater } from '@/components/docs/PreferenceUpdater';
|
||||
import SidebarItem from '@/components/docs/SidebarItem.astro';
|
||||
import { filterSidebar } from '@/utils/docs/sidebar';
|
||||
import FilmGrain from '../FilmGrain';
|
||||
import clsx from 'clsx';
|
||||
|
||||
type Props<F extends SupportedFramework = SupportedFramework> = {
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
---
|
||||
/**
|
||||
* Sidebar state persistence using sessionStorage
|
||||
* Progressive enhancement - only runs in browsers with view transitions
|
||||
*/
|
||||
|
||||
interface Props {
|
||||
docsSidebarId: string;
|
||||
}
|
||||
const { docsSidebarId } = Astro.props;
|
||||
---
|
||||
|
||||
<script is:inline define:vars={{ docsSidebarId }}>
|
||||
const STORAGE_KEY = 'vjs-sidebar-state';
|
||||
|
||||
window.addEventListener('pagereveal', () => {
|
||||
const aside = document.getElementById(docsSidebarId);
|
||||
if (!aside) return;
|
||||
const stored = sessionStorage.getItem(STORAGE_KEY);
|
||||
|
||||
if (stored) {
|
||||
try {
|
||||
const state = JSON.parse(stored);
|
||||
|
||||
if (state.sidebarScroll !== undefined) {
|
||||
aside.scrollTop = state.sidebarScroll;
|
||||
}
|
||||
|
||||
if (state.detailsState) {
|
||||
document.querySelectorAll(`#${docsSidebarId} details[id]`).forEach((details) => {
|
||||
const savedState = state.detailsState[details.id];
|
||||
if (savedState !== undefined) {
|
||||
details.open = savedState;
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[Sidebar] Failed to restore state', e);
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-expand sections containing active link (override saved state)
|
||||
const activeLink = aside.querySelector('a[aria-current="page"]');
|
||||
if (activeLink) {
|
||||
let parent = activeLink.closest('details');
|
||||
while (parent && aside.contains(parent)) {
|
||||
parent.open = true;
|
||||
parent = parent.parentElement?.closest('details') ?? null;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('pageswap', () => {
|
||||
const aside = document.getElementById(docsSidebarId);
|
||||
if (!aside) return;
|
||||
const detailsState = {};
|
||||
document.querySelectorAll(`#${docsSidebarId} details[id]`).forEach((details) => {
|
||||
detailsState[details.id] = details.open;
|
||||
});
|
||||
|
||||
const state = {
|
||||
sidebarScroll: aside.scrollTop,
|
||||
detailsState,
|
||||
};
|
||||
|
||||
try {
|
||||
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(state));
|
||||
} catch (e) {
|
||||
console.error('[Sidebar] Failed to save state', e);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
{
|
||||
/*
|
||||
We need to enable view transitions to get access to pageswap and pagereveal
|
||||
However, something in our app is causing Safari to get all flickery during transitions,
|
||||
so that's where view-transition-name: none comes in.
|
||||
(btw, I think it's related to FilmGrain. Stuff doesn't seem to flicker when that's not present)
|
||||
*/
|
||||
}
|
||||
<style is:global>
|
||||
@view-transition {
|
||||
navigation: auto;
|
||||
}
|
||||
html,
|
||||
body {
|
||||
view-transition-name: none;
|
||||
}
|
||||
</style>
|
||||
@@ -2,6 +2,7 @@
|
||||
import type { Guide, Section, SupportedFramework, SupportedStyle } from '@/types/docs';
|
||||
import { isSection } from '@/types/docs';
|
||||
import { ChevronDown } from 'lucide-react';
|
||||
import GithubSlugger from 'github-slugger';
|
||||
|
||||
type Props<F extends SupportedFramework = SupportedFramework> = {
|
||||
item: Guide | Section;
|
||||
@@ -13,6 +14,7 @@ type Props<F extends SupportedFramework = SupportedFramework> = {
|
||||
|
||||
const { item, framework, style, docTitles, depth = 0 } = Astro.props;
|
||||
const currentPath = Astro.url.pathname;
|
||||
const slugger = new GithubSlugger();
|
||||
|
||||
// Helper to check if this item or any of its children contains the active path
|
||||
function containsActivePath(item: Guide | Section): boolean {
|
||||
@@ -29,7 +31,12 @@ const isActive = containsActivePath(item);
|
||||
|
||||
{
|
||||
isSection(item) ? (
|
||||
<details open class="group my-4 first:mt-0" style={`padding-left: calc(var(--spacing) * ${depth * 4})`}>
|
||||
<details
|
||||
id={slugger.slug(item.sidebarLabel)}
|
||||
open
|
||||
class="group my-4 first:mt-0"
|
||||
style={depth ? `padding-left: calc(var(--spacing) * ${depth * 4})` : ``}
|
||||
>
|
||||
<summary
|
||||
class:list={[
|
||||
'py-2 flex items-center gap-4 ',
|
||||
@@ -50,6 +57,7 @@ const isActive = containsActivePath(item);
|
||||
</details>
|
||||
) : (
|
||||
<a
|
||||
aria-current={isActive ? 'page' : undefined}
|
||||
class:list={[
|
||||
'py-2 flex items-center gap-4 ',
|
||||
'intent:text-dark-100 dark:intent:text-light-100 cursor-pointer',
|
||||
|
||||
Reference in New Issue
Block a user