侧栏和对话列始终留在原来的容器里。分栏到达、窗口跨过手机断点,子组件只挂载一次。 检查:在 frontend 下跑 shell-frame-stable-mount、sidebar-contract、chat-chart-panel-state、class-name-definition-contract。 结果:52 通过,0 失败,退出码 0。 Co-Authored-By: Grok (agent loop)
79 lines
2.5 KiB
TypeScript
79 lines
2.5 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useRef, type PointerEvent as ReactPointerEvent, type ReactNode } from "react";
|
|
|
|
import { useSidebar } from "@/components/ui/sidebar";
|
|
import {
|
|
SIDEBAR_WIDTH_COLLAPSED,
|
|
SIDEBAR_WIDTH_DEFAULT,
|
|
SIDEBAR_WIDTH_MAX,
|
|
SIDEBAR_WIDTH_MIN,
|
|
SIDEBAR_WIDTH_TABLET,
|
|
} from "@/lib/sidebar-state";
|
|
import { useUiStore } from "@/stores/ui-store";
|
|
|
|
export function ShellFrame({ sidebar, children }: { sidebar: ReactNode; children: ReactNode }) {
|
|
const { isMobile, open, setOpen, viewport, ready } = useSidebar();
|
|
const width = useUiStore((state) => state.sidebarWidth);
|
|
const hydrated = useUiStore((state) => state.sidebarWidthHydrated);
|
|
const hydrate = useUiStore((state) => state.hydrateSidebarWidth);
|
|
const setSidebarWidth = useUiStore((state) => state.setSidebarWidth);
|
|
const dragStop = useRef<(() => void) | null>(null);
|
|
const desktop = ready && !isMobile;
|
|
const expandedWidth = hydrated
|
|
? width
|
|
: viewport === "tablet" ? SIDEBAR_WIDTH_TABLET : SIDEBAR_WIDTH_DEFAULT;
|
|
const pixelWidth = open ? expandedWidth : SIDEBAR_WIDTH_COLLAPSED;
|
|
|
|
useEffect(() => {
|
|
hydrate();
|
|
}, [hydrate]);
|
|
|
|
useEffect(() => () => dragStop.current?.(), []);
|
|
|
|
function onResizeStart(event: ReactPointerEvent<HTMLButtonElement>) {
|
|
if (event.button !== 0) return;
|
|
event.preventDefault();
|
|
const startX = event.clientX;
|
|
const startWidth = open ? expandedWidth : SIDEBAR_WIDTH_COLLAPSED;
|
|
const move = (ev: PointerEvent) => {
|
|
const next = startWidth + ev.clientX - startX;
|
|
if (next < SIDEBAR_WIDTH_MIN) {
|
|
setOpen(false);
|
|
return;
|
|
}
|
|
setOpen(true);
|
|
setSidebarWidth(Math.min(SIDEBAR_WIDTH_MAX, Math.round(next)));
|
|
};
|
|
const stop = () => {
|
|
dragStop.current = null;
|
|
window.removeEventListener("pointermove", move);
|
|
window.removeEventListener("pointerup", stop);
|
|
};
|
|
dragStop.current = stop;
|
|
window.addEventListener("pointermove", move);
|
|
window.addEventListener("pointerup", stop);
|
|
}
|
|
|
|
return (
|
|
<div
|
|
className="shell-frame"
|
|
data-shell-resize={desktop ? "live" : "mobile"}
|
|
style={desktop ? { ["--shell-sidebar-width" as string]: `${pixelWidth}px` } : undefined}
|
|
>
|
|
<div className="shell-sidebar-slot">
|
|
{sidebar}
|
|
{desktop ? (
|
|
<button
|
|
type="button"
|
|
className="shell-resize-handle"
|
|
aria-label="调整侧栏宽度"
|
|
onPointerDown={onResizeStart}
|
|
/>
|
|
) : null}
|
|
</div>
|
|
<div className="shell-main-slot">{children}</div>
|
|
</div>
|
|
);
|
|
}
|