refactor: implement scroll container context and flex layout
Switch from fixed positioning to a flexbox-based layout for the main application structure. This introduces a `ScrollContainerCtx` to provide access to the main scrollable element, allowing child components like `FileTree` and `RevealManager` to perform accurate scrolling and positioning relative to the container instead of the window. refactor: set JournalPanel height to full
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { Component, createMemo, createSignal, Show } from "solid-js";
|
||||
import { type FileNode, type TocNode } from "../data-loader";
|
||||
import { useNavigateWithParams } from "./useNavigateWithParams";
|
||||
import { useScrollContainer } from "../App";
|
||||
|
||||
/**
|
||||
* 检查当前文件路径是否在文件夹内
|
||||
@@ -98,6 +99,8 @@ export const HeadingNode: Component<{
|
||||
setIsExpanded(!isExpanded());
|
||||
}
|
||||
};
|
||||
const scrollContainer = useScrollContainer();
|
||||
|
||||
const handleClick = (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
navigate(href);
|
||||
@@ -105,10 +108,18 @@ export const HeadingNode: Component<{
|
||||
requestAnimationFrame(() => {
|
||||
const element = document.getElementById(anchor);
|
||||
if (element) {
|
||||
const scroller = scrollContainer();
|
||||
const navBarHeight = 80;
|
||||
const elementPosition = element.getBoundingClientRect().top;
|
||||
const offsetPosition = window.scrollY + elementPosition - navBarHeight;
|
||||
window.scrollTo({ top: offsetPosition, behavior: "instant" });
|
||||
const containerTop = scroller?.getBoundingClientRect().top ?? 0;
|
||||
const relativePosition = elementPosition - containerTop;
|
||||
const currentScroll = scroller?.scrollTop ?? window.scrollY;
|
||||
const offsetPosition = currentScroll + relativePosition - navBarHeight;
|
||||
if (scroller) {
|
||||
scroller.scrollTo({ top: offsetPosition, behavior: "instant" });
|
||||
} else {
|
||||
window.scrollTo({ top: offsetPosition, behavior: "instant" });
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
import { Portal } from "solid-js/web";
|
||||
import { useLocation } from "@solidjs/router";
|
||||
import { useArticleDom } from "./Article";
|
||||
import { useScrollContainer } from "../App";
|
||||
import { journalStreamState } from "./stores/journalStream";
|
||||
import { useJournalCompletions } from "./journal/completions";
|
||||
import {
|
||||
@@ -75,6 +76,7 @@ function hide() {
|
||||
|
||||
export const RevealManager: Component = () => {
|
||||
const contentDom = useArticleDom();
|
||||
const scrollContainer = useScrollContainer();
|
||||
const location = useLocation();
|
||||
const comp = useJournalCompletions();
|
||||
|
||||
@@ -179,8 +181,16 @@ export const RevealManager: Component = () => {
|
||||
<button
|
||||
class={`fixed z-50 inline-flex items-center justify-center w-5 h-5 rounded cursor-pointer ${btn().color}`}
|
||||
style={{
|
||||
top: `${btn().rect.top + window.scrollY - 6}px`,
|
||||
left: `${btn().rect.left + window.scrollX - 20}px`,
|
||||
top: `${
|
||||
btn().rect.top +
|
||||
(scrollContainer()?.scrollTop ?? window.scrollY) -
|
||||
6
|
||||
}px`,
|
||||
left: `${
|
||||
btn().rect.left +
|
||||
(scrollContainer()?.scrollLeft ?? window.scrollX) -
|
||||
20
|
||||
}px`,
|
||||
}}
|
||||
title={btn().title}
|
||||
innerHTML={btn().svg}
|
||||
|
||||
@@ -158,7 +158,7 @@ export const MobileSidebar: Component<SidebarProps> = (props) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 桌面端固定侧边栏
|
||||
* 桌面端侧边栏 — flex child, no fixed positioning
|
||||
*/
|
||||
export const DesktopSidebar: Component<{
|
||||
fileTree?: FileNode[];
|
||||
@@ -182,7 +182,7 @@ export const DesktopSidebar: Component<{
|
||||
});
|
||||
|
||||
return (
|
||||
<aside class="hidden md:block fixed top-0 left-0 h-full w-64 bg-white shadow-lg z-30 overflow-hidden pt-16">
|
||||
<aside class="hidden md:flex flex-col w-64 shrink-0 bg-white shadow-lg overflow-hidden">
|
||||
<SidebarContent
|
||||
fileTree={fileTree()}
|
||||
pathHeadings={pathHeadings()}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* JournalPanel — right panel container for the journal stream
|
||||
*
|
||||
* Fixed overlay. Shows stream when connected, connect dialog when not.
|
||||
* On mobile: fixed overlay with backdrop.
|
||||
* On desktop: positioned by the parent flex container (width transition).
|
||||
* Auto-connects to ws://{current host}:{current port} — no URL input needed.
|
||||
*/
|
||||
|
||||
@@ -33,19 +34,19 @@ export const JournalPanel: Component<JournalPanelProps> = (props) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Backdrop — only on mobile, starts below header */}
|
||||
{/* Mobile backdrop — overlay that appears below the header */}
|
||||
<div
|
||||
class="fixed top-16 inset-x-0 bottom-0 bg-black/30 z-40 md:hidden"
|
||||
classList={{ hidden: !props.open }}
|
||||
onClick={props.onClose}
|
||||
/>
|
||||
{/* Panel — always mounted for exit animation, visibility toggled */}
|
||||
{/* Panel — on mobile: fixed overlay; on desktop: fills the parent wrapper */}
|
||||
<aside
|
||||
class={`fixed top-16 right-0 bottom-0 z-50 bg-white border-l border-gray-200
|
||||
flex flex-col w-full max-w-md md:w-105 shadow-lg
|
||||
transition-transform duration-300 ease-in-out ${
|
||||
props.open ? "translate-x-0" : "translate-x-full"
|
||||
}`}
|
||||
class={`fixed top-16 right-0 bottom-0 z-50 bg-white
|
||||
flex flex-col w-full h-full max-w-md shadow-lg
|
||||
transition-transform duration-300 ease-in-out
|
||||
md:static md:inset-auto md:w-105 md:max-w-none md:shadow-none md:border-l md:border-gray-200
|
||||
${props.open ? "translate-x-0" : "translate-x-full"}`}
|
||||
inert={!props.open}
|
||||
>
|
||||
<JournalContext.Provider value={{ onClose: props.onClose }}>
|
||||
|
||||
Reference in New Issue
Block a user