Files
doormilxpress_astryx/src/components/ai-assistant/AssistantPanel.jsx
2026-08-07 23:53:15 +05:30

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;