feat(annotations): Figma-style UI overhaul
- Redesign PinOverlay: teardrop pins with author initials, colors, numbers - Split FeedbackPanel into modular components: - feedback/FeedbackPanel.tsx (orchestrator) - feedback/ThreadList.tsx (list view with filters) - feedback/ThreadListItem.tsx (individual thread row) - feedback/ThreadDetail.tsx (expanded thread with comments) - feedback/CommentItem.tsx (single comment) - feedback/CommentInput.tsx (reusable input with Enter/Shift+Enter) - feedback/feedbackStyles.ts (shared design tokens) - Add status colors: red (open), green (resolved) - Add relative timestamps (2h ago, 3d, etc.) - Wire pin clicks to expand threads in panel - Pins follow media transforms in real-time - Add delete thread with inline confirmation - Register review mode shortcut (. key) - Better empty states and filter bar (Open/Resolved/All/Mine)
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
import React from 'react';
|
||||
import { Thread } from '../../stores/annotationStore';
|
||||
import { getAuthorColor, getAuthorInitial } from '../../utils/authorColors';
|
||||
import { relativeTime } from '../../utils/relativeTime';
|
||||
import {
|
||||
TEXT_PRIMARY,
|
||||
TEXT_SECONDARY,
|
||||
TEXT_MUTED,
|
||||
STATUS_OPEN,
|
||||
STATUS_RESOLVED,
|
||||
BORDER,
|
||||
HOVER_BG,
|
||||
} from './feedbackStyles';
|
||||
|
||||
interface ThreadListItemProps {
|
||||
thread: Thread;
|
||||
pinNumber: number;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
export default function ThreadListItem({ thread, pinNumber, onClick }: ThreadListItemProps) {
|
||||
const firstComment = thread.comments[0];
|
||||
const isResolved = thread.status === 'resolved';
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={onClick}
|
||||
style={{
|
||||
padding: '10px 14px',
|
||||
borderBottom: `1px solid ${BORDER}`,
|
||||
cursor: 'pointer',
|
||||
transition: 'background 0.1s ease',
|
||||
}}
|
||||
onMouseEnter={(e) => (e.currentTarget.style.background = HOVER_BG)}
|
||||
onMouseLeave={(e) => (e.currentTarget.style.background = 'transparent')}
|
||||
>
|
||||
{/* Row 1: author circle + name + timestamp */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '6px' }}>
|
||||
<span
|
||||
style={{
|
||||
width: '24px',
|
||||
height: '24px',
|
||||
borderRadius: '50%',
|
||||
background: isResolved ? '#333' : getAuthorColor(thread.created_by),
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontSize: '11px',
|
||||
fontWeight: 700,
|
||||
color: '#fff',
|
||||
flexShrink: 0,
|
||||
opacity: isResolved ? 0.6 : 1,
|
||||
}}
|
||||
>
|
||||
{getAuthorInitial(firstComment?.author_name || '?')}
|
||||
</span>
|
||||
<span
|
||||
style={{
|
||||
color: isResolved ? TEXT_MUTED : TEXT_PRIMARY,
|
||||
fontSize: '12px',
|
||||
fontWeight: 600,
|
||||
flex: 1,
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
{firstComment?.author_name || 'Unknown'}
|
||||
</span>
|
||||
<span style={{ color: TEXT_MUTED, fontSize: '10px', flexShrink: 0 }}>
|
||||
{relativeTime(thread.last_commented_at || thread.created_at)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Row 2: comment preview */}
|
||||
{firstComment && (
|
||||
<div
|
||||
style={{
|
||||
color: isResolved ? TEXT_MUTED : TEXT_SECONDARY,
|
||||
fontSize: '12px',
|
||||
lineHeight: 1.4,
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
marginLeft: '32px',
|
||||
}}
|
||||
>
|
||||
{firstComment.content}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Row 3: meta — pin #, replies, status dot */}
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px',
|
||||
marginTop: '6px',
|
||||
marginLeft: '32px',
|
||||
}}
|
||||
>
|
||||
<span style={{ color: TEXT_MUTED, fontSize: '10px', fontFamily: 'monospace' }}>
|
||||
#{pinNumber}
|
||||
</span>
|
||||
{thread.comment_count > 1 && (
|
||||
<span style={{ color: TEXT_MUTED, fontSize: '10px' }}>
|
||||
{thread.comment_count - 1} {thread.comment_count === 2 ? 'reply' : 'replies'}
|
||||
</span>
|
||||
)}
|
||||
<span
|
||||
style={{
|
||||
width: '6px',
|
||||
height: '6px',
|
||||
borderRadius: '50%',
|
||||
background: isResolved ? STATUS_RESOLVED : STATUS_OPEN,
|
||||
marginLeft: 'auto',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user