@np-dev/ui-ai-anotation 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +245 -0
  2. package/dist/cjs/index.cjs +1550 -0
  3. package/dist/cjs/index.cjs.map +7 -0
  4. package/dist/cjs/index.native.cjs +1004 -0
  5. package/dist/cjs/index.native.cjs.map +7 -0
  6. package/dist/cjs/index.web.cjs +83 -0
  7. package/dist/cjs/index.web.cjs.map +7 -0
  8. package/dist/esm/index.js +1524 -0
  9. package/dist/esm/index.js.map +7 -0
  10. package/dist/esm/index.native.js +1012 -0
  11. package/dist/esm/index.native.js.map +7 -0
  12. package/dist/esm/index.web.js +62 -0
  13. package/dist/esm/index.web.js.map +7 -0
  14. package/dist/types/components/AnnotationInput.d.ts +8 -0
  15. package/dist/types/components/AnnotationList.d.ts +1 -0
  16. package/dist/types/components/Draggable.d.ts +10 -0
  17. package/dist/types/components/Highlighter.d.ts +1 -0
  18. package/dist/types/components/Toolbar.d.ts +1 -0
  19. package/dist/types/index.d.ts +20 -0
  20. package/dist/types/index.web.d.ts +69 -0
  21. package/dist/types/store.d.ts +66 -0
  22. package/dist/types/utils/fiber.d.ts +51 -0
  23. package/dist/types/utils/platform.d.ts +8 -0
  24. package/dist/types/utils/screenshot.d.ts +28 -0
  25. package/package.json +115 -0
  26. package/src/components/AnnotationInput.tsx +269 -0
  27. package/src/components/AnnotationList.tsx +248 -0
  28. package/src/components/Draggable.tsx +73 -0
  29. package/src/components/Highlighter.tsx +497 -0
  30. package/src/components/Toolbar.tsx +213 -0
  31. package/src/components/native/AnnotationInput.tsx +227 -0
  32. package/src/components/native/AnnotationList.tsx +157 -0
  33. package/src/components/native/Draggable.tsx +65 -0
  34. package/src/components/native/Highlighter.tsx +239 -0
  35. package/src/components/native/Toolbar.tsx +192 -0
  36. package/src/components/native/index.ts +6 -0
  37. package/src/components/web/AnnotationInput.tsx +150 -0
  38. package/src/components/web/AnnotationList.tsx +117 -0
  39. package/src/components/web/Draggable.tsx +74 -0
  40. package/src/components/web/Highlighter.tsx +329 -0
  41. package/src/components/web/Toolbar.tsx +198 -0
  42. package/src/components/web/index.ts +6 -0
  43. package/src/extension.tsx +15 -0
  44. package/src/index.native.tsx +50 -0
  45. package/src/index.tsx +41 -0
  46. package/src/index.web.tsx +124 -0
  47. package/src/store.tsx +120 -0
  48. package/src/utils/fiber.native.ts +90 -0
  49. package/src/utils/fiber.ts +255 -0
  50. package/src/utils/platform.ts +33 -0
  51. package/src/utils/screenshot.native.ts +139 -0
  52. package/src/utils/screenshot.ts +162 -0
@@ -0,0 +1,239 @@
1
+ import React, { useState, useCallback, useRef } from 'react';
2
+ import {
3
+ View,
4
+ Text,
5
+ TouchableOpacity,
6
+ StyleSheet,
7
+ Dimensions,
8
+ GestureResponderEvent,
9
+ } from 'react-native';
10
+ import { useAiAnnotation } from '../../store';
11
+ import { AnnotationInput } from './AnnotationInput';
12
+
13
+ interface HighlightRect {
14
+ x: number;
15
+ y: number;
16
+ width: number;
17
+ height: number;
18
+ }
19
+
20
+ interface HighlighterProps {
21
+ /**
22
+ * Called when a touch occurs in inspection mode.
23
+ * Return component info { name, rect } to highlight, or null to ignore.
24
+ */
25
+ onInspect?: (event: GestureResponderEvent) => {
26
+ name: string;
27
+ rect: HighlightRect;
28
+ } | null;
29
+ }
30
+
31
+ export function Highlighter({ onInspect }: HighlighterProps) {
32
+ const { state, dispatch } = useAiAnnotation();
33
+ const { mode } = state;
34
+
35
+ const [highlightRect, setHighlightRect] = useState<HighlightRect | null>(null);
36
+ const [componentName, setComponentName] = useState<string>('');
37
+ const [showInput, setShowInput] = useState(false);
38
+ const [showTooltip, setShowTooltip] = useState(false);
39
+ const [tooltipPos, setTooltipPos] = useState({ x: 0, y: 0 });
40
+
41
+ const screenDimensions = Dimensions.get('window');
42
+
43
+ const handleTouch = useCallback(
44
+ (event: GestureResponderEvent) => {
45
+ if (mode !== 'inspecting') return;
46
+
47
+ const { pageX, pageY } = event.nativeEvent;
48
+
49
+ if (onInspect) {
50
+ const result = onInspect(event);
51
+ if (result) {
52
+ setHighlightRect(result.rect);
53
+ setComponentName(result.name);
54
+ setTooltipPos({ x: pageX, y: pageY });
55
+ setShowTooltip(true);
56
+ return;
57
+ }
58
+ }
59
+
60
+ // Default behavior: show tooltip at touch location
61
+ setTooltipPos({ x: pageX, y: pageY });
62
+ setShowTooltip(true);
63
+ setComponentName('TouchedComponent');
64
+ setHighlightRect({
65
+ x: pageX - 50,
66
+ y: pageY - 50,
67
+ width: 100,
68
+ height: 100,
69
+ });
70
+ },
71
+ [mode, onInspect]
72
+ );
73
+
74
+ const handleAddAnnotation = () => {
75
+ setShowInput(true);
76
+ setShowTooltip(false);
77
+ };
78
+
79
+ const handleCloseInput = () => {
80
+ setShowInput(false);
81
+ setHighlightRect(null);
82
+ setComponentName('');
83
+ };
84
+
85
+ const handleDismiss = () => {
86
+ setShowTooltip(false);
87
+ setHighlightRect(null);
88
+ setComponentName('');
89
+ };
90
+
91
+ if (mode !== 'inspecting') return null;
92
+
93
+ // Calculate tooltip position to keep it on screen
94
+ const tooltipWidth = 120;
95
+ const tooltipHeight = 50;
96
+ let adjustedX = tooltipPos.x + 16;
97
+ let adjustedY = tooltipPos.y + 16;
98
+
99
+ if (adjustedX + tooltipWidth > screenDimensions.width) {
100
+ adjustedX = tooltipPos.x - tooltipWidth - 16;
101
+ }
102
+ if (adjustedY + tooltipHeight > screenDimensions.height) {
103
+ adjustedY = tooltipPos.y - tooltipHeight - 16;
104
+ }
105
+
106
+ return (
107
+ <View style={StyleSheet.absoluteFill} pointerEvents="box-none">
108
+ {/* Touch capture overlay */}
109
+ <TouchableOpacity
110
+ style={styles.touchOverlay}
111
+ activeOpacity={1}
112
+ onPress={handleTouch}
113
+ />
114
+
115
+ {/* Highlight rectangle */}
116
+ {highlightRect && (
117
+ <>
118
+ <View
119
+ style={[
120
+ styles.highlight,
121
+ {
122
+ left: highlightRect.x - 2,
123
+ top: highlightRect.y - 2,
124
+ width: highlightRect.width + 4,
125
+ height: highlightRect.height + 4,
126
+ },
127
+ ]}
128
+ pointerEvents="none"
129
+ />
130
+
131
+ {/* Component name label */}
132
+ {componentName && (
133
+ <View
134
+ style={[
135
+ styles.nameLabel,
136
+ {
137
+ left: highlightRect.x,
138
+ top: highlightRect.y - 24,
139
+ },
140
+ ]}
141
+ pointerEvents="none"
142
+ >
143
+ <Text style={styles.nameLabelText}>{componentName}</Text>
144
+ </View>
145
+ )}
146
+ </>
147
+ )}
148
+
149
+ {/* Floating tooltip */}
150
+ {showTooltip && (
151
+ <View
152
+ style={[
153
+ styles.tooltip,
154
+ {
155
+ left: adjustedX,
156
+ top: adjustedY,
157
+ },
158
+ ]}
159
+ >
160
+ <TouchableOpacity style={styles.tooltipButton} onPress={handleAddAnnotation}>
161
+ <Text style={styles.tooltipButtonText}>+</Text>
162
+ </TouchableOpacity>
163
+ <TouchableOpacity style={styles.dismissButton} onPress={handleDismiss}>
164
+ <Text style={styles.dismissButtonText}>✕</Text>
165
+ </TouchableOpacity>
166
+ </View>
167
+ )}
168
+
169
+ {/* Annotation input modal */}
170
+ {showInput && (
171
+ <AnnotationInput onClose={handleCloseInput} componentName={componentName || 'Unknown'} />
172
+ )}
173
+ </View>
174
+ );
175
+ }
176
+
177
+ const styles = StyleSheet.create({
178
+ touchOverlay: {
179
+ ...StyleSheet.absoluteFillObject,
180
+ backgroundColor: 'transparent',
181
+ },
182
+ highlight: {
183
+ position: 'absolute',
184
+ borderWidth: 2,
185
+ borderColor: '#3b82f6',
186
+ borderRadius: 4,
187
+ backgroundColor: 'rgba(59, 130, 246, 0.1)',
188
+ },
189
+ nameLabel: {
190
+ position: 'absolute',
191
+ backgroundColor: '#3b82f6',
192
+ paddingHorizontal: 8,
193
+ paddingVertical: 2,
194
+ borderRadius: 4,
195
+ },
196
+ nameLabelText: {
197
+ color: 'white',
198
+ fontSize: 12,
199
+ fontFamily: 'monospace',
200
+ },
201
+ tooltip: {
202
+ position: 'absolute',
203
+ flexDirection: 'row',
204
+ gap: 6,
205
+ padding: 6,
206
+ backgroundColor: 'rgba(0, 0, 0, 0.9)',
207
+ borderRadius: 8,
208
+ shadowColor: '#000',
209
+ shadowOffset: { width: 0, height: 4 },
210
+ shadowOpacity: 0.3,
211
+ shadowRadius: 12,
212
+ elevation: 8,
213
+ },
214
+ tooltipButton: {
215
+ width: 32,
216
+ height: 32,
217
+ borderRadius: 6,
218
+ backgroundColor: '#3b82f6',
219
+ justifyContent: 'center',
220
+ alignItems: 'center',
221
+ },
222
+ tooltipButtonText: {
223
+ color: 'white',
224
+ fontSize: 18,
225
+ fontWeight: 'bold',
226
+ },
227
+ dismissButton: {
228
+ width: 32,
229
+ height: 32,
230
+ borderRadius: 6,
231
+ backgroundColor: '#6b7280',
232
+ justifyContent: 'center',
233
+ alignItems: 'center',
234
+ },
235
+ dismissButtonText: {
236
+ color: 'white',
237
+ fontSize: 14,
238
+ },
239
+ });
@@ -0,0 +1,192 @@
1
+ import React, { useState } from 'react';
2
+ import {
3
+ View,
4
+ Text,
5
+ TouchableOpacity,
6
+ StyleSheet,
7
+ Platform,
8
+ } from 'react-native';
9
+ import { useAiAnnotation } from '../../store';
10
+ import { Draggable } from './Draggable';
11
+ import { Highlighter } from './Highlighter';
12
+ import { AnnotationList } from './AnnotationList';
13
+
14
+ // Dynamic clipboard import to handle different React Native versions
15
+ const copyToClipboard = async (text: string) => {
16
+ try {
17
+ // Try expo-clipboard first
18
+ const ExpoClipboard = await import('expo-clipboard');
19
+ await ExpoClipboard.setStringAsync(text);
20
+ } catch {
21
+ try {
22
+ // Fallback to @react-native-clipboard/clipboard
23
+ const RNClipboard = await import('@react-native-clipboard/clipboard');
24
+ RNClipboard.default.setString(text);
25
+ } catch {
26
+ console.warn('No clipboard module available');
27
+ }
28
+ }
29
+ };
30
+
31
+ export function Toolbar() {
32
+ const { state, dispatch } = useAiAnnotation();
33
+ const [showCopied, setShowCopied] = useState(false);
34
+
35
+ const handleCopy = async () => {
36
+ const data = state.annotations.map((a) => ({
37
+ component: a.componentName,
38
+ instruction: a.note,
39
+ }));
40
+ const text = JSON.stringify(data, null, 2);
41
+
42
+ await copyToClipboard(text);
43
+
44
+ setShowCopied(true);
45
+ setTimeout(() => setShowCopied(false), 2000);
46
+ };
47
+
48
+ const toggleMode = () => {
49
+ dispatch({
50
+ type: 'SET_MODE',
51
+ payload: state.mode === 'disabled' ? 'inspecting' : 'disabled',
52
+ });
53
+ };
54
+
55
+ const handleToggleMinimized = () => {
56
+ dispatch({ type: 'TOGGLE_MINIMIZED' });
57
+ };
58
+
59
+ return (
60
+ <>
61
+ <Highlighter />
62
+ <AnnotationList />
63
+ <Draggable>
64
+ <View style={styles.toolbar}>
65
+ {/* Drag Handle */}
66
+ <View style={styles.dragHandle}>
67
+ <Text style={styles.dragHandleText}>⋮⋮</Text>
68
+ </View>
69
+
70
+ {!state.isMinimized && (
71
+ <>
72
+ <View style={styles.separator} />
73
+
74
+ {/* Toggle inspection mode */}
75
+ <TouchableOpacity
76
+ style={[
77
+ styles.button,
78
+ state.mode === 'inspecting' && styles.buttonActive,
79
+ ]}
80
+ onPress={toggleMode}
81
+ >
82
+ <Text style={styles.buttonText}>
83
+ {state.mode === 'inspecting' ? '👆' : '🚫'}
84
+ </Text>
85
+ </TouchableOpacity>
86
+
87
+ {/* List annotations */}
88
+ <TouchableOpacity
89
+ style={styles.button}
90
+ onPress={() => dispatch({ type: 'TOGGLE_LIST' })}
91
+ >
92
+ <Text style={styles.buttonText}>📋</Text>
93
+ {state.annotations.length > 0 && (
94
+ <View style={styles.badge}>
95
+ <Text style={styles.badgeText}>{state.annotations.length}</Text>
96
+ </View>
97
+ )}
98
+ </TouchableOpacity>
99
+
100
+ {/* Copy annotations */}
101
+ <TouchableOpacity
102
+ style={[styles.button, showCopied && styles.buttonSuccess]}
103
+ onPress={handleCopy}
104
+ >
105
+ <Text style={styles.buttonText}>{showCopied ? '✓' : '📋'}</Text>
106
+ {showCopied && <Text style={styles.copiedText}>Copied!</Text>}
107
+ </TouchableOpacity>
108
+ </>
109
+ )}
110
+
111
+ <View style={styles.separator} />
112
+
113
+ {/* Minimize/Expand */}
114
+ <TouchableOpacity style={styles.button} onPress={handleToggleMinimized}>
115
+ <Text style={styles.buttonText}>{state.isMinimized ? '⬜' : '➖'}</Text>
116
+ </TouchableOpacity>
117
+ </View>
118
+ </Draggable>
119
+ </>
120
+ );
121
+ }
122
+
123
+ const styles = StyleSheet.create({
124
+ toolbar: {
125
+ backgroundColor: '#1e1e1e',
126
+ borderWidth: 1,
127
+ borderColor: '#333',
128
+ borderRadius: 8,
129
+ padding: 8,
130
+ flexDirection: 'row',
131
+ alignItems: 'center',
132
+ gap: 8,
133
+ shadowColor: '#000',
134
+ shadowOffset: { width: 0, height: 4 },
135
+ shadowOpacity: 0.3,
136
+ shadowRadius: 12,
137
+ elevation: 8,
138
+ },
139
+ dragHandle: {
140
+ paddingHorizontal: 4,
141
+ },
142
+ dragHandleText: {
143
+ color: '#666',
144
+ fontSize: 16,
145
+ },
146
+ separator: {
147
+ width: 1,
148
+ height: 24,
149
+ backgroundColor: '#333',
150
+ },
151
+ button: {
152
+ width: 32,
153
+ height: 32,
154
+ borderRadius: 4,
155
+ justifyContent: 'center',
156
+ alignItems: 'center',
157
+ position: 'relative',
158
+ },
159
+ buttonActive: {
160
+ backgroundColor: '#3b82f6',
161
+ },
162
+ buttonSuccess: {
163
+ backgroundColor: '#22c55e',
164
+ flexDirection: 'row',
165
+ width: 'auto',
166
+ paddingHorizontal: 8,
167
+ gap: 4,
168
+ },
169
+ buttonText: {
170
+ fontSize: 16,
171
+ },
172
+ copiedText: {
173
+ color: 'white',
174
+ fontSize: 12,
175
+ },
176
+ badge: {
177
+ position: 'absolute',
178
+ top: -4,
179
+ right: -4,
180
+ backgroundColor: '#ef4444',
181
+ borderRadius: 7,
182
+ width: 14,
183
+ height: 14,
184
+ justifyContent: 'center',
185
+ alignItems: 'center',
186
+ },
187
+ badgeText: {
188
+ color: 'white',
189
+ fontSize: 9,
190
+ fontWeight: 'bold',
191
+ },
192
+ });
@@ -0,0 +1,6 @@
1
+ // Native components export
2
+ export { Toolbar } from './Toolbar';
3
+ export { Highlighter } from './Highlighter';
4
+ export { AnnotationInput } from './AnnotationInput';
5
+ export { AnnotationList } from './AnnotationList';
6
+ export { Draggable } from './Draggable';
@@ -0,0 +1,150 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import { useAiAnnotation } from '../../store';
3
+ import { X } from 'lucide-react';
4
+
5
+ export function AnnotationInput({ onClose, componentName }: { onClose: () => void; componentName: string }) {
6
+ const { dispatch } = useAiAnnotation();
7
+ const [note, setNote] = useState('');
8
+ const [isVisible, setIsVisible] = useState(false);
9
+ const [isClosing, setIsClosing] = useState(false);
10
+
11
+ // Trigger entrance animation on mount
12
+ useEffect(() => {
13
+ requestAnimationFrame(() => {
14
+ setIsVisible(true);
15
+ });
16
+ }, []);
17
+
18
+ // ESC key to close
19
+ useEffect(() => {
20
+ const handleKeyDown = (e: KeyboardEvent) => {
21
+ if (e.key === 'Escape') {
22
+ e.preventDefault();
23
+ e.stopPropagation();
24
+ handleClose();
25
+ }
26
+ };
27
+ document.addEventListener('keydown', handleKeyDown);
28
+ return () => document.removeEventListener('keydown', handleKeyDown);
29
+ }, []);
30
+
31
+ // Handle close with exit animation
32
+ const handleClose = () => {
33
+ setIsClosing(true);
34
+ setTimeout(() => {
35
+ onClose();
36
+ }, 200);
37
+ };
38
+
39
+ const handleSubmit = (e: React.FormEvent) => {
40
+ e.preventDefault();
41
+ if (!note.trim()) return;
42
+
43
+ dispatch({
44
+ type: 'ADD_ANNOTATION',
45
+ payload: {
46
+ id: Date.now().toString(),
47
+ componentName,
48
+ note,
49
+ timestamp: Date.now(),
50
+ },
51
+ });
52
+ handleClose();
53
+ };
54
+
55
+ return (
56
+ <div
57
+ style={{
58
+ position: 'fixed',
59
+ top: 0,
60
+ left: 0,
61
+ right: 0,
62
+ bottom: 0,
63
+ backgroundColor: isVisible && !isClosing ? 'rgba(0,0,0,0.5)' : 'rgba(0,0,0,0)',
64
+ display: 'flex',
65
+ alignItems: 'center',
66
+ justifyContent: 'center',
67
+ zIndex: 10000,
68
+ transition: 'background-color 0.2s ease',
69
+ }}
70
+ data-ai-annotation-ui
71
+ onClick={handleClose}
72
+ >
73
+ <div
74
+ style={{
75
+ backgroundColor: '#1e1e1e',
76
+ color: '#e5e7eb',
77
+ padding: '20px',
78
+ borderRadius: '8px',
79
+ width: '400px',
80
+ maxWidth: '90%',
81
+ boxShadow: '0 4px 6px rgba(0,0,0,0.3)',
82
+ transform: isVisible && !isClosing ? 'scale(1) translateY(0)' : 'scale(0.95) translateY(-10px)',
83
+ opacity: isVisible && !isClosing ? 1 : 0,
84
+ transition: 'transform 0.2s ease, opacity 0.2s ease',
85
+ }}
86
+ onClick={(e) => e.stopPropagation()}
87
+ >
88
+ <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '16px' }}>
89
+ <h3 style={{ margin: 0, fontSize: '1.1rem' }}>Add Annotation</h3>
90
+ <button onClick={handleClose} style={{ background: 'none', border: 'none', color: 'inherit', cursor: 'pointer' }}>
91
+ <X size={20} />
92
+ </button>
93
+ </div>
94
+
95
+ <div style={{ marginBottom: '12px', fontSize: '0.9rem', opacity: 0.8 }}>
96
+ Component: <strong>{componentName}</strong>
97
+ </div>
98
+
99
+ <form onSubmit={handleSubmit}>
100
+ <textarea
101
+ value={note}
102
+ onChange={(e) => setNote(e.target.value)}
103
+ placeholder="Describe what this component does..."
104
+ style={{
105
+ width: '100%',
106
+ height: '100px',
107
+ padding: '8px',
108
+ borderRadius: '4px',
109
+ backgroundColor: '#2d2d2d',
110
+ border: '1px solid #404040',
111
+ color: 'white',
112
+ marginBottom: '16px',
113
+ resize: 'vertical',
114
+ }}
115
+ autoFocus
116
+ />
117
+ <div style={{ display: 'flex', justifyContent: 'flex-end', gap: '8px' }}>
118
+ <button
119
+ type="button"
120
+ onClick={handleClose}
121
+ style={{
122
+ padding: '6px 12px',
123
+ borderRadius: '4px',
124
+ backgroundColor: 'transparent',
125
+ border: '1px solid #404040',
126
+ color: 'white',
127
+ cursor: 'pointer',
128
+ }}
129
+ >
130
+ Cancel
131
+ </button>
132
+ <button
133
+ type="submit"
134
+ style={{
135
+ padding: '6px 12px',
136
+ borderRadius: '4px',
137
+ backgroundColor: '#3b82f6',
138
+ border: 'none',
139
+ color: 'white',
140
+ cursor: 'pointer',
141
+ }}
142
+ >
143
+ Save Annotation
144
+ </button>
145
+ </div>
146
+ </form>
147
+ </div>
148
+ </div>
149
+ );
150
+ }
@@ -0,0 +1,117 @@
1
+ import React from 'react';
2
+ import { useAiAnnotation } from '../../store';
3
+ import { X, Trash2, Trash } from 'lucide-react';
4
+
5
+ export function AnnotationList() {
6
+ const { state, dispatch } = useAiAnnotation();
7
+
8
+ if (!state.showList) return null;
9
+
10
+ return (
11
+ <div
12
+ style={{
13
+ position: 'fixed',
14
+ top: 0,
15
+ left: 0,
16
+ right: 0,
17
+ bottom: 0,
18
+ backgroundColor: 'rgba(0,0,0,0.5)',
19
+ display: 'flex',
20
+ alignItems: 'center',
21
+ justifyContent: 'center',
22
+ zIndex: 10000,
23
+ pointerEvents: 'auto',
24
+ }}
25
+ data-ai-annotation-ui
26
+ onClick={() => dispatch({ type: 'TOGGLE_LIST' })}
27
+ >
28
+ <div
29
+ style={{
30
+ backgroundColor: '#1e1e1e',
31
+ color: '#e5e7eb',
32
+ padding: '20px',
33
+ borderRadius: '8px',
34
+ width: '500px',
35
+ maxWidth: '90%',
36
+ maxHeight: '80vh',
37
+ display: 'flex',
38
+ flexDirection: 'column',
39
+ boxShadow: '0 4px 6px rgba(0,0,0,0.3)',
40
+ }}
41
+ onClick={(e) => e.stopPropagation()}
42
+ >
43
+ <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '16px', alignItems: 'center' }}>
44
+ <h3 style={{ margin: 0 }}>Annotations ({state.annotations.length})</h3>
45
+ <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
46
+ {state.annotations.length > 0 && (
47
+ <button
48
+ onClick={() => dispatch({ type: 'CLEAR_ALL_ANNOTATIONS' })}
49
+ style={{
50
+ background: '#ef4444',
51
+ border: 'none',
52
+ color: 'white',
53
+ cursor: 'pointer',
54
+ padding: '6px 12px',
55
+ borderRadius: '4px',
56
+ fontSize: '12px',
57
+ display: 'flex',
58
+ alignItems: 'center',
59
+ gap: '4px',
60
+ }}
61
+ title="Clear all annotations"
62
+ >
63
+ <Trash size={14} />
64
+ Clear All
65
+ </button>
66
+ )}
67
+ <button
68
+ onClick={() => dispatch({ type: 'TOGGLE_LIST' })}
69
+ style={{ background: 'none', border: 'none', color: 'inherit', cursor: 'pointer' }}
70
+ >
71
+ <X size={20} />
72
+ </button>
73
+ </div>
74
+ </div>
75
+
76
+ <div style={{ overflowY: 'auto', flex: 1, display: 'flex', flexDirection: 'column', gap: '8px' }}>
77
+ {state.annotations.length === 0 ? (
78
+ <p style={{ textAlign: 'center', opacity: 0.6, padding: '20px' }}>No annotations yet.</p>
79
+ ) : (
80
+ state.annotations.map((ann) => (
81
+ <div
82
+ key={ann.id}
83
+ style={{
84
+ backgroundColor: '#2d2d2d',
85
+ padding: '12px',
86
+ borderRadius: '4px',
87
+ display: 'flex',
88
+ justifyContent: 'space-between',
89
+ alignItems: 'flex-start',
90
+ gap: '12px'
91
+ }}
92
+ >
93
+ <div>
94
+ <div style={{ fontWeight: 'bold', fontSize: '0.9rem', color: '#60a5fa' }}>{ann.componentName}</div>
95
+ <div style={{ fontSize: '0.9rem', whiteSpace: 'pre-wrap' }}>{ann.note}</div>
96
+ </div>
97
+ <button
98
+ onClick={() => dispatch({ type: 'REMOVE_ANNOTATION', payload: ann.id })}
99
+ style={{
100
+ background: 'none',
101
+ border: 'none',
102
+ color: '#ef4444',
103
+ cursor: 'pointer',
104
+ padding: '4px',
105
+ }}
106
+ title="Remove annotation"
107
+ >
108
+ <Trash2 size={16} />
109
+ </button>
110
+ </div>
111
+ ))
112
+ )}
113
+ </div>
114
+ </div>
115
+ </div>
116
+ );
117
+ }