feat(chat): show playful teacher introductions on hover
This commit is contained in:
@@ -47,6 +47,7 @@ export interface AgentBrowserPanelProps {
|
||||
onOpenChange(open: boolean): void;
|
||||
embedded?: boolean;
|
||||
teacherBubble?: HTMLElement | null;
|
||||
teacherIntroduction?: HTMLElement | null;
|
||||
conversationId?: string | null;
|
||||
onWorkConversation?(conversation: CodingConversationMetadata): void;
|
||||
workRevision?: number;
|
||||
@@ -133,6 +134,7 @@ export function AgentBrowserPanel({
|
||||
onOpenChange,
|
||||
embedded = false,
|
||||
teacherBubble,
|
||||
teacherIntroduction,
|
||||
conversationId,
|
||||
onWorkConversation,
|
||||
workRevision,
|
||||
@@ -149,7 +151,7 @@ export function AgentBrowserPanel({
|
||||
const [events, setEvents] = useState<AgentBrowserCdpEvent[]>([]);
|
||||
const viewportRef = useRef<HTMLDivElement | null>(null);
|
||||
const panelRef = useRef<HTMLElement | null>(null);
|
||||
usePreviewClearance(panelRef, viewportRef, teacherBubble, embedded && open);
|
||||
usePreviewClearance(panelRef, viewportRef, teacherBubble, embedded && open, teacherIntroduction);
|
||||
const addressInputRef = useRef<HTMLInputElement | null>(null);
|
||||
const eventCursorRef = useRef(0);
|
||||
const snapshotGenerationRef = useRef(0);
|
||||
|
||||
@@ -245,6 +245,7 @@ export function CodingChatPanel({
|
||||
const running = ['queued', 'running', 'retrying', 'compacting', 'aborting'].includes(runStatus);
|
||||
const teacherCompanion = useTeacherCompanion({ projectId: activeProject?.id ?? null, sourceId: targetConversationId, sourceBusy: running, sourceArchived: Boolean(selectedConversation?.archivedAt), teacherOpen: consultationOpen, teacherComposing });
|
||||
const [teacherBubble, setTeacherBubble] = useState<HTMLDivElement | null>(null);
|
||||
const [teacherIntroduction, setTeacherIntroduction] = useState<HTMLDivElement | null>(null);
|
||||
const openTeacher = (agentId?: string) => {
|
||||
if (agentId) teacherCompanion.selectAgent(agentId);
|
||||
const selectedText = window.getSelection()?.toString().trim();
|
||||
@@ -253,6 +254,19 @@ export function CodingChatPanel({
|
||||
setConsultationOpen(true);
|
||||
setAgentBrowserOpen(true);
|
||||
};
|
||||
const closeTeacher = () => {
|
||||
setConsultationOpen(false);
|
||||
const entry = document.querySelector<HTMLButtonElement>('.consultation-agent-tabs [aria-pressed="true"]')
|
||||
?? document.querySelector<HTMLButtonElement>('.consultation-agent-tabs button');
|
||||
entry?.focus();
|
||||
};
|
||||
const toggleTeacher = (agentId?: string) => {
|
||||
if (consultationOpen && (!agentId || agentId === teacherCompanion.selectedAgent?.teacher_id)) {
|
||||
closeTeacher();
|
||||
return;
|
||||
}
|
||||
openTeacher(agentId);
|
||||
};
|
||||
const promptMode = running && draftKey ? modesByDraftKey[draftKey] ?? 'prompt' : 'prompt';
|
||||
const entryLoadState = useCodingConversationStore((state) => (
|
||||
targetConversationId
|
||||
@@ -761,7 +775,7 @@ export function CodingChatPanel({
|
||||
onToggleBrowser={() => setAgentBrowserOpen((current) => !current)}
|
||||
projectName={activeProject?.name}
|
||||
consultationOpen={consultationOpen}
|
||||
teacherEntry={activeProject ? <TeacherCompanion companion={teacherCompanion} open={consultationOpen} onOpen={openTeacher} bubbleRef={setTeacherBubble} /> : undefined}
|
||||
teacherEntry={activeProject ? <TeacherCompanion companion={teacherCompanion} open={consultationOpen} onOpen={openTeacher} onToggle={toggleTeacher} bubbleRef={setTeacherBubble} introductionRef={setTeacherIntroduction} /> : undefined}
|
||||
/>
|
||||
|
||||
<div className="classroom-tabs" role="tablist" aria-label="当前工作">
|
||||
@@ -771,6 +785,7 @@ export function CodingChatPanel({
|
||||
<div id="classroom-work-view" role="tabpanel" aria-labelledby="classroom-work-tab" hidden={!agentBrowserOpen} className="classroom-work-view min-h-0 flex-1 flex-col p-5">
|
||||
<AgentBrowserPanel projectId={activeProject?.id ?? null} open={agentBrowserOpen} onOpenChange={setAgentBrowserOpen} embedded
|
||||
teacherBubble={teacherBubble}
|
||||
teacherIntroduction={teacherIntroduction}
|
||||
workRevision={workRevision}
|
||||
onRecovery={handleRecovery}
|
||||
onWorkResult={(result) => setReadyWorkProjectId(result.status === 'ready' ? activeProject?.id ?? null : null)}
|
||||
@@ -948,7 +963,7 @@ export function CodingChatPanel({
|
||||
|
||||
{consultationOpen && activeProject && (
|
||||
<div id="coding-consultation-dock" className="classroom-consultation-dock relative min-h-0 shrink-0 border-l" style={{ width: consultationWidth }}
|
||||
onKeyDown={(event) => { if (event.key === 'Escape') { setConsultationOpen(false); document.querySelector<HTMLButtonElement>('.consultation-agent-tabs [aria-pressed="true"], .consultation-agent-tabs button')?.focus(); } }}>
|
||||
onKeyDown={(event) => { if (event.key === 'Escape') closeTeacher(); }}>
|
||||
<div role="separator" aria-label="调整咨询栏宽度" aria-orientation="vertical" aria-valuemin={320} aria-valuemax={640} aria-valuenow={consultationWidth} tabIndex={0} className="classroom-consultation-resizer"
|
||||
onPointerDown={(event) => { if (event.button === 0) { event.preventDefault(); event.currentTarget.setPointerCapture(event.pointerId); } }}
|
||||
onPointerMove={(event) => { if (event.currentTarget.hasPointerCapture(event.pointerId)) setConsultationWidth(Math.max(320, Math.min(640, window.innerWidth - event.clientX))); }}
|
||||
@@ -960,9 +975,7 @@ export function CodingChatPanel({
|
||||
externalTopic={teacherCompanion.topic}
|
||||
onTopicChange={teacherCompanion.noteTopic}
|
||||
onComposingChange={setTeacherComposing}
|
||||
expanded={consultationWidth > 508} onExpand={() => setConsultationWidth((width) => width > 508 ? 508 : 640)}
|
||||
quote={consultationOpen && teacherQuoteSource === `${activeProject.id}:${targetConversationId}` ? teacherQuote : undefined}
|
||||
onClose={() => { setConsultationOpen(false); document.querySelector<HTMLButtonElement>('.consultation-agent-tabs [aria-pressed="true"], .consultation-agent-tabs button')?.focus(); }} />
|
||||
quote={consultationOpen && teacherQuoteSource === `${activeProject.id}:${targetConversationId}` ? teacherQuote : undefined} />
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { ArrowUp, ChevronDown, ChevronRight, Loader2, Maximize2, Plus, X } from 'lucide-react';
|
||||
import { ArrowUp, ChevronDown, ChevronRight, Loader2 } from 'lucide-react';
|
||||
import { useAuthStore } from '@/stores/auth';
|
||||
import { teacherApi, teacherTopicsPath, legacyTopicBase } from '@/lib/coding-teacher';
|
||||
import type {
|
||||
@@ -18,13 +18,10 @@ import { TeacherReply } from './TeacherReply';
|
||||
export function TeacherChatPanel({
|
||||
projectId,
|
||||
sourceId,
|
||||
onClose,
|
||||
onBringBack,
|
||||
quote,
|
||||
draftRevision,
|
||||
sampleContext,
|
||||
expanded,
|
||||
onExpand,
|
||||
externalTopic,
|
||||
onTopicChange,
|
||||
onComposingChange,
|
||||
@@ -32,14 +29,11 @@ export function TeacherChatPanel({
|
||||
}: {
|
||||
projectId: string;
|
||||
sourceId: string;
|
||||
onClose?(): void;
|
||||
onBringBack?(text: string): void;
|
||||
quote?: TeacherReference;
|
||||
draftRevision?: number;
|
||||
sampleContext?: string;
|
||||
projectName?: string;
|
||||
expanded?: boolean;
|
||||
onExpand?(): void;
|
||||
externalTopic?: TeacherTopic | null;
|
||||
onTopicChange?(topic: TeacherTopic): void;
|
||||
onComposingChange?(composing: boolean): void;
|
||||
@@ -384,10 +378,7 @@ export function TeacherChatPanel({
|
||||
<header className="consultation-header flex h-[68px] shrink-0 items-center gap-3 px-5">
|
||||
<TeacherAvatar className="consultation-avatar h-9 w-9 rounded-xl" definition={definition} />
|
||||
<div className="min-w-0 flex-1"><h2 className="truncate text-sm font-semibold">{definition?.name ?? '智能体'}</h2>
|
||||
<p className="mt-1 text-[11px] text-muted-foreground">{draftRevision ? '运营草稿试聊' : definition?.description}</p></div>
|
||||
<button aria-label={`${label}新话题`} title="新话题" disabled={busy || !enabled} onClick={() => void newTopic()} className="consult-icon"><Plus className="h-4 w-4" /></button>
|
||||
{onExpand && <button className="consult-icon" aria-label={expanded ? '恢复咨询栏宽度' : '展开咨询栏'} onClick={onExpand}><Maximize2 className="h-4 w-4" /></button>}
|
||||
{onClose && <button className="consult-icon" aria-label={`关闭${label}`} onClick={onClose}><X className="h-4 w-4" /></button>}
|
||||
{draftRevision && <p className="mt-1 text-[11px] text-muted-foreground">运营草稿试聊</p>}</div>
|
||||
</header>
|
||||
{(topics.items.length > 0 || draftRevision) && <div className="px-5 pt-2">
|
||||
<button className="text-[11px] text-muted-foreground" onClick={() => setHistoryOpen(!historyOpen)} aria-expanded={historyOpen}>以往讨论 <ChevronDown className="inline h-3 w-3" /></button>
|
||||
@@ -395,6 +386,8 @@ export function TeacherChatPanel({
|
||||
<select aria-label={`${label}话题`} value={topic?.id ?? ''} disabled={busy} onChange={(e) => void choose(e.target.value)} className="min-w-0 flex-1 rounded-lg border bg-background p-2 text-xs">
|
||||
{!topic && <option value="">选择话题</option>}{topics.items.map((item) => <option key={item.id} value={item.id}>{item.title}{item.legacyRole ? '(旧版,仅查看)' : ''}</option>)}
|
||||
</select>
|
||||
<button type="button" aria-label={`${label}新话题`} disabled={busy || !enabled} onClick={() => void newTopic()}
|
||||
className="shrink-0 rounded-lg px-2 text-xs text-muted-foreground hover:bg-white hover:text-foreground disabled:opacity-40">新话题</button>
|
||||
</div>}
|
||||
</div>}
|
||||
{discussionEnabled && topic?.discussion && <TeacherDiscussionPanel key={topic.id} discussion={topic.discussion}
|
||||
|
||||
@@ -1,16 +1,25 @@
|
||||
import { ArrowUpRight, Loader2, X } from 'lucide-react';
|
||||
import type { Ref } from 'react';
|
||||
import { useState, type Ref } from 'react';
|
||||
import { Provider as TooltipProvider } from '@radix-ui/react-tooltip';
|
||||
import { TeacherAvatar } from './TeacherAvatar';
|
||||
import { TeacherIntroduction } from './TeacherIntroduction';
|
||||
import type { useTeacherCompanion } from './use-teacher-companion';
|
||||
|
||||
type Companion = ReturnType<typeof useTeacherCompanion>;
|
||||
export function TeacherCompanion({ companion, open, onOpen, bubbleRef }: {
|
||||
export function TeacherCompanion({ companion, open, onOpen, onToggle, bubbleRef, introductionRef }: {
|
||||
companion: Companion;
|
||||
open: boolean;
|
||||
onOpen(agentId?: string): void;
|
||||
onToggle?(agentId?: string): void;
|
||||
bubbleRef?: Ref<HTMLDivElement>;
|
||||
introductionRef?: Ref<HTMLDivElement>;
|
||||
}) {
|
||||
const { definition, invitation, welcome, loading, enabled, checking, error } = companion;
|
||||
const [introductionId, setIntroductionId] = useState<string | null>(null);
|
||||
const introductionProps = (id: string) => ({
|
||||
open: introductionId === id,
|
||||
onOpenChange: (next: boolean) => setIntroductionId((current) => next ? id : current === id ? null : current),
|
||||
});
|
||||
const name = definition?.name || '智能体';
|
||||
const activeId = companion.selectedAgent?.teacher_id;
|
||||
const agents = companion.selectedAgent && !companion.agents.some((item) => item.teacher_id === activeId)
|
||||
@@ -20,23 +29,26 @@ export function TeacherCompanion({ companion, open, onOpen, bubbleRef }: {
|
||||
: checking ? '智能体在看看你最近的进展…'
|
||||
: error || (!enabled ? '智能体暂未开放,仍可查看以往讨论' : open ? '我们慢慢聊,想到什么都可以说' : '点我聊聊,想到什么都可以说');
|
||||
return (
|
||||
<TooltipProvider delayDuration={180} skipDelayDuration={100}>
|
||||
<section className="teacher-companion no-drag" aria-label="智能体陪伴" data-testid="teacher-companion">
|
||||
<nav className="consultation-agent-tabs" aria-label="已下发智能体">
|
||||
{agents.map((agent) => <button key={agent.teacher_id} type="button" className="teacher-companion-person"
|
||||
title={agent.definition.description || agent.definition.name} aria-label={agents.length === 1 ? '智能体' : `与${agent.definition.name}聊天`}
|
||||
{agents.map((agent) => <TeacherIntroduction key={agent.teacher_id} definition={agent.definition} contentRef={introductionRef} {...introductionProps(`agent:${agent.teacher_id}`)}>
|
||||
<button type="button" className="teacher-companion-person"
|
||||
aria-label={agents.length === 1 ? '智能体' : `与${agent.definition.name}聊天`}
|
||||
aria-expanded={open && activeId === agent.teacher_id} aria-pressed={open && activeId === agent.teacher_id}
|
||||
aria-controls="coding-consultation-dock" data-agent-id={agent.teacher_id}
|
||||
onMouseDown={(event) => event.preventDefault()} onClick={() => onOpen(agent.teacher_id)}>
|
||||
onMouseDown={(event) => event.preventDefault()} onClick={() => (onToggle ?? onOpen)(agent.teacher_id)}>
|
||||
<span className="teacher-companion-avatar"><TeacherAvatar definition={agent.definition} />
|
||||
{activeId === agent.teacher_id && invitation && <span className="teacher-companion-unread" aria-hidden="true" />}</span>
|
||||
<span className="teacher-companion-name">{agent.definition.name}</span>
|
||||
{activeId === agent.teacher_id && checking && <Loader2 className="h-3 w-3 shrink-0 animate-spin text-muted-foreground motion-reduce:animate-none" aria-label="智能体正在思考" />}
|
||||
</button>)}
|
||||
{(!agents.length || (!activeId && companion.topic)) && <button type="button" className="teacher-companion-person" title={status} aria-label="智能体" aria-expanded={open} aria-controls="coding-consultation-dock" onMouseDown={(event) => event.preventDefault()} onClick={() => onOpen()}>
|
||||
</button></TeacherIntroduction>)}
|
||||
{(!agents.length || (!activeId && companion.topic)) && <TeacherIntroduction definition={definition} contentRef={introductionRef} {...introductionProps('history')}>
|
||||
<button type="button" className="teacher-companion-person" aria-description={status} aria-label="智能体" aria-expanded={open} aria-controls="coding-consultation-dock" onMouseDown={(event) => event.preventDefault()} onClick={() => (onToggle ?? onOpen)()}>
|
||||
<span className="teacher-companion-avatar"><TeacherAvatar definition={definition} />{invitation && <span className="teacher-companion-unread" aria-hidden="true" />}</span>
|
||||
<span className="teacher-companion-name">{name}</span>
|
||||
{checking && <Loader2 className="h-3 w-3 shrink-0 animate-spin text-muted-foreground motion-reduce:animate-none" aria-label="智能体正在思考" />}
|
||||
</button>}
|
||||
</button></TeacherIntroduction>}
|
||||
</nav>
|
||||
{(invitation || welcome) && <div ref={bubbleRef} className="teacher-companion-presence" data-testid="teacher-presence">
|
||||
<div className="teacher-companion-bubble" data-testid="teacher-invitation" data-bubble-kind={invitation ? 'check-in' : 'welcome'}>
|
||||
@@ -55,5 +67,6 @@ export function TeacherCompanion({ companion, open, onOpen, bubbleRef }: {
|
||||
</button>
|
||||
</div>}
|
||||
</section>
|
||||
</TooltipProvider>
|
||||
);
|
||||
}
|
||||
|
||||
43
src/pages/Chat/TeacherIntroduction.tsx
Normal file
43
src/pages/Chat/TeacherIntroduction.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import * as Tooltip from '@radix-ui/react-tooltip';
|
||||
import { Sparkles } from 'lucide-react';
|
||||
import type { ReactElement, 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();
|
||||
return <Tooltip.Root open={Boolean(description) && open} onOpenChange={onOpenChange}>
|
||||
<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') onOpenChange(true);
|
||||
}} 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>;
|
||||
}
|
||||
@@ -70,6 +70,17 @@
|
||||
/* The teacher speaks from the title bar without taking the student's focus. */
|
||||
.classroom-composer-view[hidden] { display: none; }
|
||||
.teacher-companion { position: relative; min-width: 0; max-width: 65%; }
|
||||
.teacher-introduction { z-index: 200; width: min(304px, calc(100vw - 24px)); max-height: var(--radix-tooltip-content-available-height); padding: 18px; border: 2px solid #ead6af; border-radius: 22px; background: #fff9ed; color: #30465f; box-shadow: 0 5px 0 #ecdcc1, 0 12px 30px #354d6c18; transform-origin: var(--radix-tooltip-content-transform-origin); animation: teacher-introduction-in 150ms ease-out; }
|
||||
.teacher-introduction-heading { display: flex; align-items: center; gap: 11px; }
|
||||
.teacher-introduction-avatar { width: 48px; height: 48px; flex-shrink: 0; border: 2px solid #fff; border-radius: 15px; background: #e8f1fb; box-shadow: 0 3px 0 #d6e3ee; }
|
||||
.teacher-introduction-eyebrow { display: block; margin-bottom: 3px; color: #856637; font-size: 11px; font-weight: 500; }
|
||||
.teacher-introduction-name { overflow-wrap: anywhere; font-size: 15px; line-height: 1.5; font-weight: 600; }
|
||||
.teacher-introduction-sparkles { align-self: flex-start; width: 19px; height: 19px; flex-shrink: 0; color: #b9842f; }
|
||||
.teacher-introduction-description { max-height: min(240px, calc(var(--radix-tooltip-content-available-height) - 152px)); overflow-y: auto; overflow-wrap: anywhere; white-space: pre-wrap; margin-top: 16px; padding: 12px 13px; border: 1px solid #eee1c9; border-radius: 14px; background: #fffdf8; color: #48596e; font-size: 13px; line-height: 1.8; }
|
||||
.teacher-introduction-hint { margin-top: 12px; color: #776649; font-size: 11px; text-align: center; }
|
||||
.teacher-introduction-arrow { fill: #ead6af; }
|
||||
@keyframes teacher-introduction-in { from { opacity: 0; } to { opacity: 1; } }
|
||||
@media (prefers-reduced-motion: reduce) { .teacher-introduction { animation: none; } }
|
||||
.consultation-agent-tabs { display: flex; align-items: center; gap: 4px; max-width: 100%; overflow-x: auto; scrollbar-width: thin; }
|
||||
.consultation-agent-tabs .teacher-companion-person { flex-shrink: 0; }
|
||||
.teacher-companion-person { display: flex; align-items: center; gap: 6px; height: 36px; padding: 0 8px; border-radius: 8px; color: #747d6c; transition: background 150ms; }
|
||||
|
||||
@@ -6,24 +6,27 @@ export function usePreviewClearance(
|
||||
viewportRef: RefObject<HTMLElement | null>,
|
||||
bubble: HTMLElement | null | undefined,
|
||||
enabled: boolean,
|
||||
introduction?: HTMLElement | null,
|
||||
) {
|
||||
useLayoutEffect(() => {
|
||||
const panel = panelRef.current;
|
||||
const viewport = viewportRef.current;
|
||||
if (!enabled || !panel || !viewport || !bubble) return;
|
||||
const overlays = [bubble, introduction].filter((item): item is HTMLElement => Boolean(item));
|
||||
if (!enabled || !panel || !viewport || !overlays.length) return;
|
||||
|
||||
let applied = 0;
|
||||
const update = () => {
|
||||
const message = bubble.getBoundingClientRect();
|
||||
const page = viewport.getBoundingClientRect();
|
||||
// Subtract our own previous offset so ResizeObserver cannot create a
|
||||
// feedback loop that alternately adds and removes the same clearance.
|
||||
const naturalTop = page.top - applied;
|
||||
const overlapsHorizontally = message.left < page.right && message.right > page.left;
|
||||
const visible = message.width > 0 && message.height > 0 && page.width > 0;
|
||||
const next = visible && overlapsHorizontally && message.top < page.bottom
|
||||
? Math.max(0, Math.ceil(message.bottom + 8 - naturalTop))
|
||||
: 0;
|
||||
const next = Math.max(0, ...overlays.map((overlay) => {
|
||||
const message = overlay.getBoundingClientRect();
|
||||
const overlapsHorizontally = message.left < page.right && message.right > page.left;
|
||||
const visible = message.width > 0 && message.height > 0 && page.width > 0;
|
||||
return visible && overlapsHorizontally && message.top < page.bottom
|
||||
? Math.ceil(message.bottom + 8 - naturalTop) : 0;
|
||||
}));
|
||||
if (next === applied) return;
|
||||
applied = next;
|
||||
panel.style.marginTop = next ? `${next}px` : '';
|
||||
@@ -31,7 +34,7 @@ export function usePreviewClearance(
|
||||
|
||||
update();
|
||||
const observer = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(update);
|
||||
observer?.observe(bubble);
|
||||
overlays.forEach((overlay) => observer?.observe(overlay));
|
||||
observer?.observe(viewport);
|
||||
window.addEventListener('resize', update);
|
||||
return () => {
|
||||
@@ -39,5 +42,5 @@ export function usePreviewClearance(
|
||||
window.removeEventListener('resize', update);
|
||||
panel.style.marginTop = '';
|
||||
};
|
||||
}, [bubble, enabled, panelRef, viewportRef]);
|
||||
}, [bubble, introduction, enabled, panelRef, viewportRef]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user