← back to Norma
components/email-analyzer/ReferencePanel.tsx
1022 lines
'use client';
import { useState, useRef, useEffect } from 'react';
import {
FileText,
Link2,
StickyNote,
X,
ChevronDown,
ChevronUp,
ChevronRight,
Loader2,
Plus,
ExternalLink,
GripVertical,
Globe,
AlertCircle,
} from 'lucide-react';
import { useToast } from '@/components/ToastProvider';
import { useReferences, type Reference, REFS_EXTERNAL_CHANGE_EVENT } from './useReferences';
/* ─── Types ──────────────────────────────────────────────────────────────── */
interface ReferencePanelProps {
sessionId: string;
/** Called whenever references change so parent can access getContextString */
onReferencesChange?: (refs: Reference[], getContextString: () => string) => void;
}
type AddMode = 'text' | 'url' | 'notes' | null;
/* ─── Border colors by type ──────────────────────────────────────────────── */
const TYPE_CONFIG = {
article: {
label: 'Article',
Icon: FileText,
borderColor: 'var(--color-lane-b)', // indigo
bgTint: 'rgba(79, 70, 229, 0.06)',
badgeBg: 'rgba(79, 70, 229, 0.10)',
badgeBorder: 'rgba(79, 70, 229, 0.25)',
},
link: {
label: 'Link',
Icon: Link2,
borderColor: 'var(--color-primary)', // emerald
bgTint: 'rgba(10, 124, 89, 0.06)',
badgeBg: 'rgba(10, 124, 89, 0.10)',
badgeBorder: 'rgba(10, 124, 89, 0.25)',
},
notes: {
label: 'Notes',
Icon: StickyNote,
borderColor: 'var(--color-secondary)', // amber
bgTint: 'rgba(217, 119, 6, 0.06)',
badgeBg: 'rgba(217, 119, 6, 0.10)',
badgeBorder: 'rgba(217, 119, 6, 0.25)',
},
} as const;
/* ─── Component ──────────────────────────────────────────────────────────── */
export default function ReferencePanel({
sessionId,
onReferencesChange,
}: ReferencePanelProps) {
const { addToast } = useToast();
const {
references,
loading,
fetchingUrl,
error,
loadReferences,
addTextReference,
addNotesReference,
addUrlReference,
removeReference,
toggleInclude,
getContextString,
} = useReferences(sessionId);
const [collapsed, setCollapsed] = useState(true);
const [addMode, setAddMode] = useState<AddMode>(null);
const [expandedRef, setExpandedRef] = useState<string | null>(null);
const [showAddDropdown, setShowAddDropdown] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);
// Text/URL input state
const [textInput, setTextInput] = useState('');
const [textTitle, setTextTitle] = useState('');
const [urlInput, setUrlInput] = useState('');
const [notesInput, setNotesInput] = useState('');
const [saving, setSaving] = useState(false);
// Drag state
const [dragIdx, setDragIdx] = useState<number | null>(null);
const [dragOverIdx, setDragOverIdx] = useState<number | null>(null);
// Load references on mount
useEffect(() => {
loadReferences();
}, [loadReferences]);
// Refresh when an outside component (SuggestionsPanel, etc.) adds refs
useEffect(() => {
function handler(e: Event) {
const detail = (e as CustomEvent).detail as { sessionId?: string } | undefined;
if (!detail?.sessionId || detail.sessionId === sessionId) {
loadReferences();
}
}
window.addEventListener(REFS_EXTERNAL_CHANGE_EVENT, handler);
return () => window.removeEventListener(REFS_EXTERNAL_CHANGE_EVENT, handler);
}, [loadReferences, sessionId]);
// Notify parent on changes
useEffect(() => {
onReferencesChange?.(references, getContextString);
}, [references, getContextString, onReferencesChange]);
// Close dropdown on outside click
useEffect(() => {
function handleClickOutside(e: MouseEvent) {
if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {
setShowAddDropdown(false);
}
}
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);
/* ── Add handlers ──────────────────────────────────────────────────────── */
async function handleAddText() {
if (!textInput.trim()) return;
setSaving(true);
const ref = await addTextReference(textInput.trim(), textTitle.trim() || undefined);
setSaving(false);
if (ref) {
setTextInput('');
setTextTitle('');
setAddMode(null);
addToast('Article text added', 'success');
} else if (error) {
addToast(error, 'error');
}
}
async function handleAddUrl() {
if (!urlInput.trim()) return;
// Basic URL validation
try {
new URL(urlInput.trim());
} catch {
addToast('Please enter a valid URL', 'error');
return;
}
const ref = await addUrlReference(urlInput.trim());
if (ref) {
setUrlInput('');
setAddMode(null);
addToast(`Fetched: ${ref.title || 'article'}`, 'success');
} else if (error) {
addToast(error, 'error');
}
}
async function handleAddNotes() {
if (!notesInput.trim()) return;
setSaving(true);
const ref = await addNotesReference(notesInput.trim());
setSaving(false);
if (ref) {
setNotesInput('');
setAddMode(null);
addToast('Notes added', 'success');
} else if (error) {
addToast(error, 'error');
}
}
async function handleRemove(id: string) {
const ok = await removeReference(id);
if (ok) {
addToast('Reference removed', 'info');
}
}
async function handleToggleInclude(id: string) {
await toggleInclude(id);
}
/* ── Drag reorder ──────────────────────────────────────────────────────── */
function handleDragStart(idx: number) {
setDragIdx(idx);
}
function handleDragOver(e: React.DragEvent, idx: number) {
e.preventDefault();
setDragOverIdx(idx);
}
function handleDrop(idx: number) {
if (dragIdx === null || dragIdx === idx) {
setDragIdx(null);
setDragOverIdx(null);
return;
}
const newRefs = [...references];
const [moved] = newRefs.splice(dragIdx, 1);
newRefs.splice(idx, 0, moved);
newRefs.forEach((r, i) => {
r.sort_order = i;
});
// Persist reordered sort_order values via PATCH, then reload
(async () => {
for (let i = 0; i < newRefs.length; i++) {
if (newRefs[i].sort_order !== references[i]?.sort_order || newRefs[i].id !== references[i]?.id) {
fetch('/api/email-analyzer/references', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: newRefs[i].id, sort_order: i }),
}).catch(() => {
/* best effort */
});
}
}
})();
// Optimistic local update — reload to sync
loadReferences();
setDragIdx(null);
setDragOverIdx(null);
}
/* ── Render ────────────────────────────────────────────────────────────── */
const activeCount = references.filter((r) => r.include_in_rewrite).length;
return (
<div
className="card"
style={{
padding: 0,
overflow: 'hidden',
}}
>
{/* ── Header (collapsible) ─────────────────────────────────────────── */}
<button
type="button"
className="collapsible-header"
style={{
width: '100%',
padding: '12px 16px',
display: 'flex',
alignItems: 'center',
gap: 8,
}}
onClick={() => setCollapsed((p) => !p)}
aria-expanded={!collapsed}
>
<div
style={{
width: 28,
height: 28,
borderRadius: 'var(--radius-sm)',
background: 'linear-gradient(135deg, rgba(79,70,229,0.12), rgba(10,124,89,0.12))',
border: '1px solid rgba(79,70,229,0.2)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0,
}}
>
<FileText size={14} style={{ color: 'var(--color-lane-b)' }} />
</div>
<span
style={{
fontSize: 14,
fontWeight: 700,
color: 'var(--color-text)',
flex: 1,
textAlign: 'left',
}}
>
Reference Materials
</span>
{references.length > 0 && (
<span
className="badge"
style={{
fontSize: 11,
padding: '1px 8px',
backgroundColor: 'var(--color-primary-dim)',
color: 'var(--color-primary)',
border: '1px solid rgba(10,124,89,0.25)',
}}
>
{activeCount}/{references.length}
</span>
)}
<ChevronDown
size={16}
className={`collapsible-chevron${collapsed ? ' collapsed' : ''}`}
style={{ color: 'var(--color-text-muted)' }}
/>
</button>
{/* ── Collapsible body ─────────────────────────────────────────────── */}
<div
className={`collapsible-content${collapsed ? ' collapsed' : ''}`}
style={{
maxHeight: collapsed ? 0 : 'none',
padding: collapsed ? '0 16px' : '0 16px 16px',
overflow: collapsed ? 'hidden' : 'visible',
}}
>
{/* Error banner */}
{error && (
<div
style={{
display: 'flex',
alignItems: 'center',
gap: 6,
padding: '8px 12px',
borderRadius: 'var(--radius-sm)',
backgroundColor: 'rgba(220, 38, 38, 0.08)',
border: '1px solid rgba(220, 38, 38, 0.2)',
fontSize: 13,
color: 'var(--color-error)',
marginBottom: 12,
}}
>
<AlertCircle size={14} />
{error}
</div>
)}
{/* Loading state */}
{loading && references.length === 0 && (
<div style={{ display: 'flex', justifyContent: 'center', padding: '16px 0' }}>
<Loader2 size={20} className="spinner" />
</div>
)}
{/* ── Reference Cards ────────────────────────────────────────────── */}
{references.length > 0 && (
<div style={{ display: 'flex', flexDirection: 'column', gap: 8, marginBottom: 12 }}>
{references.map((ref, idx) => {
const config = TYPE_CONFIG[ref.ref_type];
const TypeIcon = config.Icon;
const isExpanded = expandedRef === ref.id;
const keyPoints = Array.isArray(ref.key_points) ? ref.key_points : [];
return (
<div
key={ref.id}
draggable
onDragStart={() => handleDragStart(idx)}
onDragOver={(e) => handleDragOver(e, idx)}
onDrop={() => handleDrop(idx)}
onDragEnd={() => { setDragIdx(null); setDragOverIdx(null); }}
style={{
borderRadius: 'var(--radius-md)',
border: `1px solid ${dragOverIdx === idx ? config.borderColor : 'var(--color-border)'}`,
borderLeft: `3px solid ${config.borderColor}`,
backgroundColor: dragOverIdx === idx ? config.bgTint : 'var(--color-surface)',
opacity: dragIdx === idx ? 0.5 : 1,
transition: 'all 0.15s',
}}
>
{/* Card header */}
<div
style={{
display: 'flex',
alignItems: 'center',
gap: 8,
padding: '10px 12px',
cursor: 'pointer',
}}
onClick={() => setExpandedRef(isExpanded ? null : ref.id)}
role="button"
tabIndex={0}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
setExpandedRef(isExpanded ? null : ref.id);
}
}}
>
{/* Drag handle */}
<GripVertical
size={14}
style={{
color: 'var(--color-text-muted)',
cursor: 'grab',
flexShrink: 0,
opacity: 0.5,
}}
/>
{/* Type badge */}
<span
className="badge"
style={{
fontSize: 10,
padding: '1px 6px',
backgroundColor: config.badgeBg,
color: config.borderColor,
border: `1px solid ${config.badgeBorder}`,
flexShrink: 0,
}}
>
<TypeIcon size={10} style={{ marginRight: 2 }} />
{config.label}
</span>
{/* Title */}
<span
style={{
flex: 1,
fontSize: 13,
fontWeight: 600,
color: 'var(--color-text)',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
minWidth: 0,
}}
>
{ref.title || ref.content.substring(0, 60)}
</span>
{/* Source domain for links */}
{ref.source_domain && (
<span
style={{
fontSize: 11,
color: 'var(--color-text-muted)',
display: 'flex',
alignItems: 'center',
gap: 3,
flexShrink: 0,
}}
>
<Globe size={10} />
{ref.source_domain}
</span>
)}
{/* Include toggle */}
<label
style={{
display: 'flex',
alignItems: 'center',
gap: 4,
flexShrink: 0,
cursor: 'pointer',
fontSize: 11,
color: 'var(--color-text-muted)',
}}
onClick={(e) => e.stopPropagation()}
title={ref.include_in_rewrite ? 'Included in rewrites' : 'Excluded from rewrites'}
>
<input
type="checkbox"
checked={ref.include_in_rewrite}
onChange={() => handleToggleInclude(ref.id)}
style={{
accentColor: 'var(--color-primary)',
width: 14,
height: 14,
cursor: 'pointer',
}}
aria-label={`Include "${ref.title || 'reference'}" in rewrite`}
/>
</label>
{/* Remove button */}
<button
type="button"
onClick={(e) => {
e.stopPropagation();
handleRemove(ref.id);
}}
style={{
background: 'none',
border: 'none',
cursor: 'pointer',
padding: 2,
color: 'var(--color-text-muted)',
flexShrink: 0,
display: 'flex',
borderRadius: 4,
transition: 'color 0.15s',
}}
title="Remove reference"
aria-label={`Remove "${ref.title || 'reference'}"`}
onMouseEnter={(e) => { (e.currentTarget as HTMLElement).style.color = 'var(--color-error)'; }}
onMouseLeave={(e) => { (e.currentTarget as HTMLElement).style.color = 'var(--color-text-muted)'; }}
>
<X size={14} />
</button>
{/* Expand/collapse chevron */}
{isExpanded ? (
<ChevronUp size={14} style={{ color: 'var(--color-text-muted)', flexShrink: 0 }} />
) : (
<ChevronDown size={14} style={{ color: 'var(--color-text-muted)', flexShrink: 0 }} />
)}
</div>
{/* Collapsed preview (2-3 lines) */}
{!isExpanded && (
<div
style={{
padding: '0 12px 10px 38px',
fontSize: 12,
color: 'var(--color-text-muted)',
lineHeight: 1.5,
display: '-webkit-box',
WebkitLineClamp: 2,
WebkitBoxOrient: 'vertical',
overflow: 'hidden',
}}
>
{ref.content.substring(0, 200)}
</div>
)}
{/* Expanded content */}
{isExpanded && (
<div
style={{
padding: '0 12px 12px 38px',
borderTop: '1px solid var(--color-border)',
marginTop: 0,
}}
>
{/* Full content */}
<div
style={{
fontSize: 13,
color: 'var(--color-text-secondary)',
lineHeight: 1.6,
marginTop: 10,
maxHeight: 200,
overflowY: 'auto',
whiteSpace: 'pre-wrap',
wordBreak: 'break-word',
}}
>
{ref.content}
</div>
{/* Key points */}
{keyPoints.length > 0 && (
<div style={{ marginTop: 10 }}>
<div
style={{
fontSize: 11,
fontWeight: 700,
color: 'var(--color-text-muted)',
textTransform: 'uppercase',
letterSpacing: '0.06em',
marginBottom: 4,
}}
>
Key Points
</div>
<ul
style={{
margin: 0,
paddingLeft: 16,
listStyleType: 'disc',
}}
>
{keyPoints.map((point, i) => (
<li
key={i}
style={{
fontSize: 12,
color: 'var(--color-text-secondary)',
lineHeight: 1.5,
marginBottom: 3,
}}
>
{point}
</li>
))}
</ul>
</div>
)}
{/* Source link */}
{ref.source_url && (
<a
href={ref.source_url}
target="_blank"
rel="noopener noreferrer"
style={{
display: 'inline-flex',
alignItems: 'center',
gap: 4,
fontSize: 12,
color: 'var(--color-primary)',
marginTop: 8,
textDecoration: 'underline',
}}
>
<ExternalLink size={11} />
View original
</a>
)}
</div>
)}
</div>
);
})}
</div>
)}
{/* ── Always-visible quick-add bar ─────────────────────────────── */}
{addMode === null && (
<div
style={{
padding: references.length === 0 ? '12px 0' : '4px 0 12px',
display: 'flex',
flexDirection: 'column',
gap: 8,
}}
>
{references.length === 0 && (
<p style={{ fontSize: 12, color: 'var(--color-text-muted)', textAlign: 'center', marginBottom: 4 }}>
Add articles, URLs, or notes to provide context for AI rewrites
</p>
)}
<div style={{ display: 'flex', gap: 6 }}>
<button
type="button"
className="btn btn-ghost btn-sm"
style={{ flex: 1, fontSize: 12, justifyContent: 'center', border: '1px solid var(--color-border)' }}
onClick={() => setAddMode('text')}
>
<FileText size={13} /> Paste Text
</button>
<button
type="button"
className="btn btn-ghost btn-sm"
style={{ flex: 1, fontSize: 12, justifyContent: 'center', border: '1px solid var(--color-border)' }}
onClick={() => setAddMode('url')}
>
<Link2 size={13} /> Add URL
</button>
<button
type="button"
className="btn btn-ghost btn-sm"
style={{ flex: 1, fontSize: 12, justifyContent: 'center', border: '1px solid var(--color-border)' }}
onClick={() => setAddMode('notes')}
>
<StickyNote size={13} /> Notes
</button>
</div>
</div>
)}
{/* ── Add inputs ─────────────────────────────────────────────────── */}
{addMode === 'text' && (
<div
style={{
padding: 12,
borderRadius: 'var(--radius-md)',
border: '1px solid var(--color-border)',
backgroundColor: 'var(--color-surface-el)',
marginBottom: 12,
}}
>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8 }}>
<span style={{ fontSize: 13, fontWeight: 600, color: 'var(--color-text)' }}>
Paste Article Text
</span>
<button
type="button"
onClick={() => { setAddMode(null); setTextInput(''); setTextTitle(''); }}
style={{
background: 'none',
border: 'none',
cursor: 'pointer',
padding: 2,
color: 'var(--color-text-muted)',
display: 'flex',
}}
aria-label="Cancel"
>
<X size={14} />
</button>
</div>
<input
type="text"
className="input"
style={{ fontSize: 13, minHeight: 36, padding: '6px 10px', marginBottom: 6 }}
placeholder="Title (optional)"
value={textTitle}
onChange={(e) => setTextTitle(e.target.value)}
/>
<textarea
className="input"
style={{
fontSize: 13,
minHeight: 100,
resize: 'vertical',
padding: '8px 10px',
marginBottom: 8,
}}
placeholder="Paste article text, talking points, or notes here..."
value={textInput}
onChange={(e) => setTextInput(e.target.value)}
autoFocus
/>
<button
type="button"
className="btn btn-primary btn-sm"
style={{ fontSize: 13 }}
disabled={!textInput.trim() || saving}
onClick={handleAddText}
>
{saving ? (
<>
<Loader2 size={14} className="spinner" /> Saving...
</>
) : (
<>
<Plus size={14} /> Add Article
</>
)}
</button>
</div>
)}
{addMode === 'url' && (
<div
style={{
padding: 12,
borderRadius: 'var(--radius-md)',
border: '1px solid var(--color-border)',
backgroundColor: 'var(--color-surface-el)',
marginBottom: 12,
}}
>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8 }}>
<span style={{ fontSize: 13, fontWeight: 600, color: 'var(--color-text)' }}>
Add URL
</span>
<button
type="button"
onClick={() => { setAddMode(null); setUrlInput(''); }}
style={{
background: 'none',
border: 'none',
cursor: 'pointer',
padding: 2,
color: 'var(--color-text-muted)',
display: 'flex',
}}
aria-label="Cancel"
>
<X size={14} />
</button>
</div>
<div style={{ display: 'flex', gap: 6 }}>
<input
type="url"
className="input"
style={{ fontSize: 13, minHeight: 36, padding: '6px 10px', flex: 1 }}
placeholder="https://example.com/article"
value={urlInput}
onChange={(e) => setUrlInput(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.preventDefault();
handleAddUrl();
}
}}
autoFocus
/>
<button
type="button"
className="btn btn-primary btn-sm"
style={{ fontSize: 13, flexShrink: 0 }}
disabled={!urlInput.trim() || fetchingUrl}
onClick={handleAddUrl}
>
{fetchingUrl ? (
<>
<Loader2 size={14} className="spinner" /> Fetching...
</>
) : (
<>
<Globe size={14} /> Fetch
</>
)}
</button>
</div>
{fetchingUrl && (
<div
style={{
fontSize: 12,
color: 'var(--color-text-muted)',
marginTop: 6,
display: 'flex',
alignItems: 'center',
gap: 6,
}}
>
<Loader2 size={12} className="spinner" />
Fetching article content and extracting key points...
</div>
)}
</div>
)}
{addMode === 'notes' && (
<div
style={{
padding: 12,
borderRadius: 'var(--radius-md)',
border: '1px solid var(--color-border)',
backgroundColor: 'var(--color-surface-el)',
marginBottom: 12,
}}
>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8 }}>
<span style={{ fontSize: 13, fontWeight: 600, color: 'var(--color-text)' }}>
Quick Notes
</span>
<button
type="button"
onClick={() => { setAddMode(null); setNotesInput(''); }}
style={{
background: 'none',
border: 'none',
cursor: 'pointer',
padding: 2,
color: 'var(--color-text-muted)',
display: 'flex',
}}
aria-label="Cancel"
>
<X size={14} />
</button>
</div>
<textarea
className="input"
style={{
fontSize: 13,
minHeight: 80,
resize: 'vertical',
padding: '8px 10px',
marginBottom: 8,
}}
placeholder="Quick notes, talking points, key messages..."
value={notesInput}
onChange={(e) => setNotesInput(e.target.value)}
autoFocus
/>
<button
type="button"
className="btn btn-primary btn-sm"
style={{ fontSize: 13 }}
disabled={!notesInput.trim() || saving}
onClick={handleAddNotes}
>
{saving ? (
<>
<Loader2 size={14} className="spinner" /> Saving...
</>
) : (
<>
<Plus size={14} /> Add Notes
</>
)}
</button>
</div>
)}
{/* ── Add Reference dropdown button ──────────────────────────────── */}
{addMode === null && (
<div ref={dropdownRef} style={{ position: 'relative' }}>
<button
type="button"
className="btn btn-secondary btn-sm"
style={{
fontSize: 13,
width: '100%',
justifyContent: 'center',
}}
onClick={() => setShowAddDropdown((p) => !p)}
>
<Plus size={14} />
Add Reference
<ChevronRight
size={12}
style={{
marginLeft: 'auto',
transform: showAddDropdown ? 'rotate(90deg)' : 'none',
transition: 'transform 0.15s',
}}
/>
</button>
{showAddDropdown && (
<div
style={{
position: 'absolute',
top: '100%',
left: 0,
right: 0,
marginTop: 4,
backgroundColor: 'var(--color-surface)',
border: '1px solid var(--color-border)',
borderRadius: 'var(--radius-md)',
boxShadow: '0 4px 12px rgba(0,0,0,0.08)',
zIndex: 10,
overflow: 'hidden',
}}
>
<button
type="button"
onClick={() => { setAddMode('text'); setShowAddDropdown(false); }}
style={{
display: 'flex',
alignItems: 'center',
gap: 8,
width: '100%',
padding: '10px 14px',
border: 'none',
background: 'none',
cursor: 'pointer',
fontSize: 13,
color: 'var(--color-text)',
transition: 'background 0.15s',
textAlign: 'left',
}}
onMouseEnter={(e) => { (e.currentTarget as HTMLElement).style.backgroundColor = 'var(--color-surface-el)'; }}
onMouseLeave={(e) => { (e.currentTarget as HTMLElement).style.backgroundColor = 'transparent'; }}
>
<FileText size={14} style={{ color: 'var(--color-lane-b)' }} />
<div>
<div style={{ fontWeight: 600 }}>Paste Text</div>
<div style={{ fontSize: 11, color: 'var(--color-text-muted)' }}>
Article text, talking points, excerpts
</div>
</div>
</button>
<div style={{ height: 1, backgroundColor: 'var(--color-border)' }} />
<button
type="button"
onClick={() => { setAddMode('url'); setShowAddDropdown(false); }}
style={{
display: 'flex',
alignItems: 'center',
gap: 8,
width: '100%',
padding: '10px 14px',
border: 'none',
background: 'none',
cursor: 'pointer',
fontSize: 13,
color: 'var(--color-text)',
transition: 'background 0.15s',
textAlign: 'left',
}}
onMouseEnter={(e) => { (e.currentTarget as HTMLElement).style.backgroundColor = 'var(--color-surface-el)'; }}
onMouseLeave={(e) => { (e.currentTarget as HTMLElement).style.backgroundColor = 'transparent'; }}
>
<Link2 size={14} style={{ color: 'var(--color-primary)' }} />
<div>
<div style={{ fontWeight: 600 }}>Add URL</div>
<div style={{ fontSize: 11, color: 'var(--color-text-muted)' }}>
Fetch article from a web page
</div>
</div>
</button>
<div style={{ height: 1, backgroundColor: 'var(--color-border)' }} />
<button
type="button"
onClick={() => { setAddMode('notes'); setShowAddDropdown(false); }}
style={{
display: 'flex',
alignItems: 'center',
gap: 8,
width: '100%',
padding: '10px 14px',
border: 'none',
background: 'none',
cursor: 'pointer',
fontSize: 13,
color: 'var(--color-text)',
transition: 'background 0.15s',
textAlign: 'left',
}}
onMouseEnter={(e) => { (e.currentTarget as HTMLElement).style.backgroundColor = 'var(--color-surface-el)'; }}
onMouseLeave={(e) => { (e.currentTarget as HTMLElement).style.backgroundColor = 'transparent'; }}
>
<StickyNote size={14} style={{ color: 'var(--color-secondary)' }} />
<div>
<div style={{ fontWeight: 600 }}>Quick Notes</div>
<div style={{ fontSize: 11, color: 'var(--color-text-muted)' }}>
Jot down key messages or talking points
</div>
</div>
</button>
</div>
)}
</div>
)}
</div>
</div>
);
}