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,77 @@
|
||||
import React from 'react';
|
||||
import { Comment } from '../../stores/annotationStore';
|
||||
import { getAuthorColor, getAuthorInitial } from '../../utils/authorColors';
|
||||
import { relativeTime } from '../../utils/relativeTime';
|
||||
import { TEXT_PRIMARY, TEXT_MUTED, BORDER } from './feedbackStyles';
|
||||
|
||||
interface CommentItemProps {
|
||||
comment: Comment;
|
||||
isOwn: boolean;
|
||||
onDelete?: () => void;
|
||||
}
|
||||
|
||||
export default function CommentItem({ comment, isOwn, onDelete }: CommentItemProps) {
|
||||
return (
|
||||
<div style={{ padding: '10px 0', borderBottom: `1px solid ${BORDER}` }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '6px' }}>
|
||||
<span
|
||||
style={{
|
||||
width: '22px',
|
||||
height: '22px',
|
||||
borderRadius: '50%',
|
||||
background: getAuthorColor(comment.user_id),
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontSize: '10px',
|
||||
fontWeight: 700,
|
||||
color: '#fff',
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
{getAuthorInitial(comment.author_name)}
|
||||
</span>
|
||||
<span style={{ color: TEXT_PRIMARY, fontSize: '12px', fontWeight: 600 }}>
|
||||
{comment.author_name}
|
||||
</span>
|
||||
<span style={{ color: TEXT_MUTED, fontSize: '10px' }}>
|
||||
{relativeTime(comment.created_at)}
|
||||
</span>
|
||||
{comment.edited_at && (
|
||||
<span style={{ color: TEXT_MUTED, fontSize: '10px' }}>(edited)</span>
|
||||
)}
|
||||
<div style={{ flex: 1 }} />
|
||||
{isOwn && onDelete && (
|
||||
<button
|
||||
onClick={onDelete}
|
||||
style={{
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
color: '#633',
|
||||
cursor: 'pointer',
|
||||
fontSize: '10px',
|
||||
opacity: 0.6,
|
||||
transition: 'opacity 0.1s',
|
||||
}}
|
||||
onMouseEnter={(e) => (e.currentTarget.style.opacity = '1')}
|
||||
onMouseLeave={(e) => (e.currentTarget.style.opacity = '0.6')}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
color: '#ccc',
|
||||
fontSize: '13px',
|
||||
lineHeight: 1.5,
|
||||
marginLeft: '30px',
|
||||
whiteSpace: 'pre-wrap',
|
||||
wordBreak: 'break-word',
|
||||
}}
|
||||
>
|
||||
{comment.content}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user