56 lines
2.8 KiB
TypeScript
56 lines
2.8 KiB
TypeScript
import * as Tooltip from '@radix-ui/react-tooltip';
|
|
import { Sparkles } from 'lucide-react';
|
|
import { useRef, type ReactElement, type Ref } from 'react';
|
|
import type { TeacherDefinition } from '../../../shared/coding-teacher';
|
|
import { TeacherAvatar } from './TeacherAvatar';
|
|
|
|
/** A temporary introduction, separate from the agent's unread messages. */
|
|
export function TeacherIntroduction({ definition, children, contentRef, open, onOpenChange }: {
|
|
definition: TeacherDefinition | null;
|
|
children: ReactElement;
|
|
contentRef?: Ref<HTMLDivElement>;
|
|
open: boolean;
|
|
onOpenChange(open: boolean): void;
|
|
}) {
|
|
const description = definition?.description?.trim();
|
|
const clicked = useRef(false);
|
|
return <Tooltip.Root open={Boolean(description) && open} onOpenChange={(next) => {
|
|
if (!next || !clicked.current) onOpenChange(next);
|
|
}}>
|
|
<Tooltip.Trigger asChild onPointerEnter={(event) => {
|
|
// Adjacent entries must switch immediately, even inside the previous
|
|
// tooltip's pointer grace area used when moving into its readable card.
|
|
if (event.pointerType === 'touch') return;
|
|
if (event.relatedTarget instanceof Node && event.currentTarget.contains(event.relatedTarget)) return;
|
|
clicked.current = false;
|
|
onOpenChange(true);
|
|
}} onClick={() => {
|
|
// A delayed Radix hover callback must not reopen the card after selection.
|
|
clicked.current = true;
|
|
onOpenChange(false);
|
|
}} onFocus={() => {
|
|
clicked.current = false;
|
|
}} onPointerMove={(event) => {
|
|
// pointerEnter owns hover opening. Skip Radix's second opening path so
|
|
// moving inside an entry after clicking it cannot reopen its introduction.
|
|
if (event.pointerType !== 'touch') event.preventDefault();
|
|
}}>{children}</Tooltip.Trigger>
|
|
{definition && description && <Tooltip.Portal>
|
|
<Tooltip.Content ref={contentRef} side="bottom" align="end" sideOffset={10} collisionPadding={12}
|
|
className="teacher-introduction no-drag" data-testid="teacher-introduction" aria-label={description}>
|
|
<div className="teacher-introduction-heading">
|
|
<TeacherAvatar definition={definition} className="teacher-introduction-avatar" />
|
|
<div className="min-w-0 flex-1">
|
|
<span className="teacher-introduction-eyebrow">认识一下</span>
|
|
<p className="teacher-introduction-name">{definition.name}</p>
|
|
</div>
|
|
<Sparkles className="teacher-introduction-sparkles" aria-hidden="true" />
|
|
</div>
|
|
<p className="teacher-introduction-description">{description}</p>
|
|
<p className="teacher-introduction-hint">点点头像,一起聊聊</p>
|
|
<Tooltip.Arrow className="teacher-introduction-arrow" width={18} height={9} />
|
|
</Tooltip.Content>
|
|
</Tooltip.Portal>}
|
|
</Tooltip.Root>;
|
|
}
|