79 lines
3.2 KiB
JavaScript
79 lines
3.2 KiB
JavaScript
import * as React from 'react';
|
|
import { AnimatePresence, motion } from 'framer-motion';
|
|
import { cn } from '@/lib/utils';
|
|
import KrowAssistant from './KrowAssistant';
|
|
import { useAssistantPanel } from './AssistantPanelContext';
|
|
|
|
/** Panel width, plus the gutter separating it from the dashboard. */
|
|
const PANEL_WIDTH = 380;
|
|
const GUTTER = 24;
|
|
|
|
/**
|
|
* AssistantPanel — the sliding column the layout renders.
|
|
*
|
|
* Animating the aside's `width` (rather than overlaying) is what makes the
|
|
* dashboard reflow smoothly instead of being covered, and what lets closing
|
|
* restore the original layout exactly.
|
|
*
|
|
* Two things about the structure matter, and both were bugs before:
|
|
*
|
|
* 1. The clip lives *inside* the sticky element, not around it. An ancestor with
|
|
* `overflow: hidden` becomes the nearest scroll container, which silently
|
|
* disables `position: sticky` on everything beneath it — the panel computed
|
|
* as sticky but never actually stuck.
|
|
*
|
|
* 2. The aside must stretch to the shell's full height. As a flex item under
|
|
* `items-start` it collapsed to its own height, so its sticky context ended
|
|
* partway down the page and the panel scrolled away, leaving empty space
|
|
* beside a long dashboard.
|
|
*
|
|
* With both fixed the panel stays pinned for the whole scroll, and because the
|
|
* assistant is a fixed-height frame, only its conversation scrolls.
|
|
*/
|
|
export function AssistantPanel({ stickyClassName, panelHeightClassName }) {
|
|
const { context, isOpen, close } = useAssistantPanel();
|
|
|
|
return (
|
|
<AnimatePresence initial={false}>
|
|
{isOpen && context && (
|
|
<motion.aside
|
|
key="assistant"
|
|
aria-label="Krow AI Assistant"
|
|
initial={{ width: 0, opacity: 0 }}
|
|
animate={{ width: PANEL_WIDTH + GUTTER, opacity: 1 }}
|
|
exit={{ width: 0, opacity: 0 }}
|
|
transition={{ duration: 0.3, ease: [0.65, 0, 0.35, 1] }}
|
|
/* No `overflow-hidden` here — see (1) above. `self-stretch` keeps the
|
|
sticky context running the full height of the shell — see (2). */
|
|
className="relative shrink-0 self-stretch"
|
|
>
|
|
<div className={cn('sticky', stickyClassName)}>
|
|
{/* The clip: contains the slide-in and the fixed panel width while
|
|
the track animates open, without breaking the sticky above it. */}
|
|
<div className="overflow-hidden" style={{ width: PANEL_WIDTH + GUTTER }}>
|
|
<motion.div
|
|
className="pl-6"
|
|
initial={{ x: 32, opacity: 0 }}
|
|
animate={{ x: 0, opacity: 1 }}
|
|
exit={{ x: 32, opacity: 0 }}
|
|
transition={{ duration: 0.3, ease: [0.65, 0, 0.35, 1] }}
|
|
>
|
|
{/* Keyed on the context so switching pages gives a clean panel
|
|
and aborts any response still streaming for the last one. */}
|
|
<KrowAssistant
|
|
key={context.id}
|
|
context={context}
|
|
onClose={close}
|
|
className={panelHeightClassName}
|
|
/>
|
|
</motion.div>
|
|
</div>
|
|
</div>
|
|
</motion.aside>
|
|
)}
|
|
</AnimatePresence>
|
|
);
|
|
}
|
|
|
|
export default AssistantPanel;
|