增加AI页面在其它页面的展示
This commit is contained in:
@@ -6,6 +6,8 @@
|
||||
- Inactive: muted icon + label
|
||||
============================================================ */
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useAIDrawer } from "@/contexts/AIDrawerContext";
|
||||
import { useIsLgUp } from "@/hooks/useIsLgUp";
|
||||
import {
|
||||
Activity,
|
||||
Bot,
|
||||
@@ -31,6 +33,8 @@ const NAV_ITEMS: NavItem[] = [
|
||||
|
||||
export default function BottomNav() {
|
||||
const [location] = useLocation();
|
||||
const isLgUp = useIsLgUp();
|
||||
const { open: aiDrawerOpen, setOpen: setAiDrawerOpen } = useAIDrawer();
|
||||
|
||||
return (
|
||||
<nav
|
||||
@@ -46,14 +50,16 @@ export default function BottomNav() {
|
||||
>
|
||||
<div className="flex items-stretch justify-around">
|
||||
{NAV_ITEMS.map((item) => {
|
||||
const isActive = location === item.path ||
|
||||
(item.path === "/" && location === "/") ||
|
||||
(item.path !== "/" && location.startsWith(item.path));
|
||||
const isActive =
|
||||
item.path === "/ai"
|
||||
? location === "/ai" || (isLgUp && aiDrawerOpen)
|
||||
: location === item.path ||
|
||||
(item.path === "/" && location === "/") ||
|
||||
(item.path !== "/" && location.startsWith(item.path));
|
||||
const Icon = item.icon;
|
||||
|
||||
return (
|
||||
<Link key={item.path} href={item.path}>
|
||||
<div className="flex flex-col items-center justify-center relative pt-2.5 pb-1.5 px-3">
|
||||
const inner = (
|
||||
<div className="flex flex-col items-center justify-center relative pt-2.5 pb-1.5 px-3">
|
||||
{/* Active indicator bar */}
|
||||
<div
|
||||
className="absolute top-0 left-1/2 -translate-x-1/2 rounded-b-full transition-all duration-300"
|
||||
@@ -99,6 +105,41 @@ export default function BottomNav() {
|
||||
{item.label}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (item.path === "/ai") {
|
||||
if (!isLgUp) {
|
||||
return (
|
||||
<Link key={item.path} href={item.path}>
|
||||
{inner}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div
|
||||
key={item.path}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className="cursor-pointer select-none"
|
||||
onClick={() => {
|
||||
if (location === "/ai") return;
|
||||
setAiDrawerOpen((o) => !o);
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key !== "Enter" && e.key !== " ") return;
|
||||
e.preventDefault();
|
||||
if (location === "/ai") return;
|
||||
setAiDrawerOpen((o) => !o);
|
||||
}}
|
||||
>
|
||||
{inner}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Link key={item.path} href={item.path}>
|
||||
{inner}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
import AIPage from "@/pages/AIPage";
|
||||
import { useAIDrawer } from "@/contexts/AIDrawerContext";
|
||||
import { useIsLgUp } from "@/hooks/useIsLgUp";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useLocation } from "wouter";
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
type ReactNode,
|
||||
type RefObject,
|
||||
} from "react";
|
||||
|
||||
function SplitDivider({
|
||||
containerRef,
|
||||
onResize,
|
||||
}: {
|
||||
containerRef: RefObject<HTMLDivElement | null>;
|
||||
onResize: (aiPercent: number) => void;
|
||||
}) {
|
||||
const dragging = useRef(false);
|
||||
|
||||
const onMove = useCallback(
|
||||
(e: MouseEvent) => {
|
||||
if (!dragging.current || !containerRef.current) return;
|
||||
const r = containerRef.current.getBoundingClientRect();
|
||||
const w = r.width;
|
||||
if (w <= 0) return;
|
||||
const aiPct = ((r.right - e.clientX) / w) * 100;
|
||||
onResize(aiPct);
|
||||
},
|
||||
[containerRef, onResize],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const onUp = () => {
|
||||
dragging.current = false;
|
||||
};
|
||||
window.addEventListener("mouseup", onUp);
|
||||
window.addEventListener("mousemove", onMove);
|
||||
return () => {
|
||||
window.removeEventListener("mouseup", onUp);
|
||||
window.removeEventListener("mousemove", onMove);
|
||||
};
|
||||
}, [onMove]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"group relative z-10 w-2 shrink-0 cursor-col-resize bg-transparent",
|
||||
"hover:bg-[#00FFF6]/10",
|
||||
)}
|
||||
onMouseDown={() => {
|
||||
dragging.current = true;
|
||||
}}
|
||||
role="separator"
|
||||
aria-orientation="vertical"
|
||||
aria-label="Resize panels"
|
||||
>
|
||||
<div className="absolute inset-y-0 left-1/2 w-px -translate-x-1/2 bg-white/15 transition-colors group-hover:bg-[#00FFF6]/45" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* lg+: optional side-by-side AI panel with draggable divider. Mobile unchanged (router only).
|
||||
*/
|
||||
export default function DesktopAIShell({ children }: { children: ReactNode }) {
|
||||
const isLgUp = useIsLgUp();
|
||||
const [location] = useLocation();
|
||||
const { open, setOpen, splitPercent, setSplitPercent } = useAIDrawer();
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const showSplit = Boolean(isLgUp && open && location !== "/ai");
|
||||
|
||||
useEffect(() => {
|
||||
if (location === "/ai" && isLgUp) setOpen(false);
|
||||
}, [location, isLgUp, setOpen]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={cn(
|
||||
"w-full min-w-0",
|
||||
showSplit
|
||||
? "flex h-dvh max-h-dvh flex-row overflow-hidden"
|
||||
: "min-h-dvh",
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"min-w-0",
|
||||
showSplit
|
||||
? "h-full min-h-0 overflow-y-auto overflow-x-hidden"
|
||||
: "min-h-dvh overflow-x-hidden",
|
||||
)}
|
||||
style={
|
||||
showSplit
|
||||
? { flex: `${100 - splitPercent} 1 0%`, minWidth: 0 }
|
||||
: { width: "100%" }
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
{showSplit && (
|
||||
<>
|
||||
<SplitDivider
|
||||
containerRef={containerRef}
|
||||
onResize={(aiPct) => setSplitPercent(aiPct)}
|
||||
/>
|
||||
<div
|
||||
className="flex h-full min-h-0 min-w-0 flex-col overflow-hidden border-l border-white/[0.08] bg-[#0d1117]"
|
||||
style={{ flex: `${splitPercent} 1 0%` }}
|
||||
>
|
||||
<AIPage variant="split" onClose={() => setOpen(false)} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user