@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,213 @@
1
+ import React, { useState, useEffect, useRef } from 'react';
2
+ import { useAiAnnotation } from '../store';
3
+ import { Draggable } from './Draggable';
4
+ import { GripVertical, MousePointer2, List, Copy, Minus, Maximize2, Ban, Check } from 'lucide-react';
5
+ import { Highlighter } from './Highlighter';
6
+ import { AnnotationList } from './AnnotationList';
7
+
8
+ export function Toolbar() {
9
+ const { state, dispatch } = useAiAnnotation();
10
+ const [showCopied, setShowCopied] = useState(false);
11
+ const [isAnimating, setIsAnimating] = useState(false);
12
+ const contentRef = useRef<HTMLDivElement>(null);
13
+ const [contentWidth, setContentWidth] = useState<number | null>(null);
14
+
15
+ // Measure content width for smooth animation
16
+ useEffect(() => {
17
+ if (contentRef.current && !state.isMinimized) {
18
+ // Use requestAnimationFrame to ensure DOM is updated before measuring
19
+ requestAnimationFrame(() => {
20
+ if (contentRef.current) {
21
+ setContentWidth(contentRef.current.scrollWidth);
22
+ }
23
+ });
24
+ }
25
+ }, [state.isMinimized, showCopied, state.annotations.length]);
26
+
27
+ // Handle minimize toggle with animation
28
+ const handleToggleMinimized = () => {
29
+ setIsAnimating(true);
30
+ dispatch({ type: 'TOGGLE_MINIMIZED' });
31
+ setTimeout(() => setIsAnimating(false), 300);
32
+ };
33
+
34
+ const handleCopy = () => {
35
+ if (state.annotations.length === 0) return;
36
+
37
+ // Format annotations as readable text with sections
38
+ const sections = state.annotations.map((a, index) => {
39
+ const lines: string[] = [];
40
+
41
+ // Section header
42
+ lines.push(`${'='.repeat(50)}`);
43
+ lines.push(`Annotation ${index + 1}: ${a.componentName}`);
44
+ lines.push(`${'='.repeat(50)}`);
45
+ lines.push('');
46
+
47
+ // Instruction/Note
48
+ lines.push('## Instruction');
49
+ lines.push(a.note);
50
+
51
+ return lines.join('\n');
52
+ });
53
+
54
+ const text = sections.join('\n\n');
55
+ navigator.clipboard.writeText(text).then(() => {
56
+ setShowCopied(true);
57
+ setTimeout(() => setShowCopied(false), 2000);
58
+ });
59
+ };
60
+
61
+ const toggleMode = () => {
62
+ dispatch({
63
+ type: 'SET_MODE',
64
+ payload: state.mode === 'disabled' ? 'inspecting' : 'disabled',
65
+ });
66
+ };
67
+
68
+ return (
69
+ <>
70
+ <Highlighter />
71
+ <AnnotationList />
72
+ <Draggable>
73
+ <div
74
+ style={{
75
+ backgroundColor: '#1e1e1e',
76
+ border: '1px solid #333',
77
+ borderRadius: '8px',
78
+ padding: '8px',
79
+ boxShadow: '0 4px 12px rgba(0,0,0,0.3)',
80
+ display: 'flex',
81
+ alignItems: 'center',
82
+ gap: '8px',
83
+ color: '#e5e7eb',
84
+ transition: 'width 0.2s',
85
+ }}
86
+ data-ai-annotation-ui
87
+ >
88
+ {/* Drag Handle */}
89
+ <div style={{ cursor: 'grab', color: '#666', display: 'flex' }} title="Drag">
90
+ <GripVertical size={20} />
91
+ </div>
92
+
93
+ <div
94
+ ref={contentRef}
95
+ style={{
96
+ display: 'flex',
97
+ alignItems: 'center',
98
+ gap: '8px',
99
+ overflow: 'hidden',
100
+ transition: 'max-width 0.3s ease, opacity 0.2s ease',
101
+ maxWidth: state.isMinimized ? 0 : (contentWidth || 300),
102
+ opacity: state.isMinimized ? 0 : 1,
103
+ paddingTop: '4px',
104
+ paddingBottom: '4px',
105
+ marginTop: '-4px',
106
+ marginBottom: '-4px',
107
+ }}
108
+ >
109
+ <>
110
+ <div style={{ width: '1px', height: '24px', backgroundColor: '#333' }} />
111
+
112
+ <button
113
+ onClick={toggleMode}
114
+ title={state.mode === 'inspecting' ? "Disable Inspection" : "Enable Inspection"}
115
+ style={{
116
+ background: state.mode === 'inspecting' ? '#3b82f6' : 'transparent',
117
+ border: 'none',
118
+ borderRadius: '4px',
119
+ padding: '6px',
120
+ color: state.mode === 'inspecting' ? 'white' : 'inherit',
121
+ cursor: 'pointer',
122
+ display: 'flex'
123
+ }}
124
+ >
125
+ {state.mode === 'inspecting' ? <MousePointer2 size={18} /> : <Ban size={18} />}
126
+ </button>
127
+
128
+ <button
129
+ onClick={() => dispatch({ type: 'TOGGLE_LIST' })}
130
+ title="List Annotations"
131
+ style={{
132
+ background: 'transparent',
133
+ border: 'none',
134
+ borderRadius: '4px',
135
+ padding: '6px',
136
+ color: 'inherit',
137
+ cursor: 'pointer',
138
+ display: 'flex',
139
+ position: 'relative'
140
+ }}
141
+ >
142
+ <List size={18} />
143
+ {state.annotations.length > 0 && (
144
+ <span style={{
145
+ position: 'absolute',
146
+ top: -2,
147
+ right: -2,
148
+ background: '#ef4444',
149
+ color: 'white',
150
+ fontSize: '9px',
151
+ width: '14px',
152
+ height: '14px',
153
+ borderRadius: '50%',
154
+ display: 'flex',
155
+ alignItems: 'center',
156
+ justifyContent: 'center'
157
+ }}>
158
+ {state.annotations.length}
159
+ </span>
160
+ )}
161
+ </button>
162
+
163
+ <button
164
+ onClick={handleCopy}
165
+ title="Copy Annotations"
166
+ style={{
167
+ background: showCopied ? '#22c55e' : 'transparent',
168
+ border: 'none',
169
+ borderRadius: '4px',
170
+ padding: '6px',
171
+ color: showCopied ? 'white' : 'inherit',
172
+ cursor: 'pointer',
173
+ display: 'flex',
174
+ alignItems: 'center',
175
+ gap: '4px',
176
+ transition: 'background-color 0.2s, color 0.2s',
177
+ minWidth: showCopied ? '75px' : 'auto',
178
+ }}
179
+ >
180
+ {showCopied ? (
181
+ <>
182
+ <Check size={18} />
183
+ <span style={{ fontSize: '12px', whiteSpace: 'nowrap' }}>Copied!</span>
184
+ </>
185
+ ) : (
186
+ <Copy size={18} />
187
+ )}
188
+ </button>
189
+ </>
190
+ </div>
191
+
192
+ <div style={{ width: '1px', height: '24px', backgroundColor: '#333', marginLeft: state.isMinimized ? 0 : 'auto' }} />
193
+
194
+ <button
195
+ onClick={handleToggleMinimized}
196
+ title={state.isMinimized ? "Expand" : "Minimize"}
197
+ style={{
198
+ background: 'transparent',
199
+ border: 'none',
200
+ borderRadius: '4px',
201
+ padding: '6px',
202
+ color: 'inherit',
203
+ cursor: 'pointer',
204
+ display: 'flex'
205
+ }}
206
+ >
207
+ {state.isMinimized ? <Maximize2 size={18} /> : <Minus size={18} />}
208
+ </button>
209
+ </div>
210
+ </Draggable>
211
+ </>
212
+ );
213
+ }
@@ -0,0 +1,227 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import {
3
+ View,
4
+ Text,
5
+ Modal,
6
+ TextInput,
7
+ TouchableOpacity,
8
+ StyleSheet,
9
+ Animated,
10
+ KeyboardAvoidingView,
11
+ Platform,
12
+ } from 'react-native';
13
+ import { useAiAnnotation } from '../../store';
14
+
15
+ interface AnnotationInputProps {
16
+ onClose: () => void;
17
+ componentName: string;
18
+ }
19
+
20
+ export function AnnotationInput({ onClose, componentName }: AnnotationInputProps) {
21
+ const { dispatch } = useAiAnnotation();
22
+ const [note, setNote] = useState('');
23
+ const [fadeAnim] = useState(new Animated.Value(0));
24
+ const [scaleAnim] = useState(new Animated.Value(0.95));
25
+
26
+ useEffect(() => {
27
+ Animated.parallel([
28
+ Animated.timing(fadeAnim, {
29
+ toValue: 1,
30
+ duration: 200,
31
+ useNativeDriver: true,
32
+ }),
33
+ Animated.spring(scaleAnim, {
34
+ toValue: 1,
35
+ friction: 8,
36
+ useNativeDriver: true,
37
+ }),
38
+ ]).start();
39
+ }, []);
40
+
41
+ const handleClose = () => {
42
+ Animated.parallel([
43
+ Animated.timing(fadeAnim, {
44
+ toValue: 0,
45
+ duration: 150,
46
+ useNativeDriver: true,
47
+ }),
48
+ Animated.timing(scaleAnim, {
49
+ toValue: 0.95,
50
+ duration: 150,
51
+ useNativeDriver: true,
52
+ }),
53
+ ]).start(() => {
54
+ onClose();
55
+ });
56
+ };
57
+
58
+ const handleSubmit = () => {
59
+ if (!note.trim()) return;
60
+
61
+ dispatch({
62
+ type: 'ADD_ANNOTATION',
63
+ payload: {
64
+ id: Date.now().toString(),
65
+ componentName,
66
+ note: note.trim(),
67
+ timestamp: Date.now(),
68
+ },
69
+ });
70
+ handleClose();
71
+ };
72
+
73
+ return (
74
+ <Modal
75
+ visible
76
+ transparent
77
+ animationType="none"
78
+ onRequestClose={handleClose}
79
+ >
80
+ <KeyboardAvoidingView
81
+ behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
82
+ style={styles.keyboardView}
83
+ >
84
+ <Animated.View style={[styles.overlay, { opacity: fadeAnim }]}>
85
+ <TouchableOpacity
86
+ style={styles.backdropTouchable}
87
+ activeOpacity={1}
88
+ onPress={handleClose}
89
+ >
90
+ <Animated.View
91
+ style={[
92
+ styles.container,
93
+ {
94
+ opacity: fadeAnim,
95
+ transform: [{ scale: scaleAnim }],
96
+ },
97
+ ]}
98
+ >
99
+ <TouchableOpacity activeOpacity={1}>
100
+ <View style={styles.header}>
101
+ <Text style={styles.title}>Add Annotation</Text>
102
+ <TouchableOpacity onPress={handleClose} style={styles.closeButton}>
103
+ <Text style={styles.closeButtonText}>✕</Text>
104
+ </TouchableOpacity>
105
+ </View>
106
+
107
+ <Text style={styles.componentLabel}>
108
+ Component: <Text style={styles.componentName}>{componentName}</Text>
109
+ </Text>
110
+
111
+ <TextInput
112
+ style={styles.textInput}
113
+ value={note}
114
+ onChangeText={setNote}
115
+ placeholder="Describe what this component does..."
116
+ placeholderTextColor="#6b7280"
117
+ multiline
118
+ numberOfLines={4}
119
+ textAlignVertical="top"
120
+ autoFocus
121
+ />
122
+
123
+ <View style={styles.buttonContainer}>
124
+ <TouchableOpacity style={styles.cancelButton} onPress={handleClose}>
125
+ <Text style={styles.cancelButtonText}>Cancel</Text>
126
+ </TouchableOpacity>
127
+ <TouchableOpacity style={styles.submitButton} onPress={handleSubmit}>
128
+ <Text style={styles.submitButtonText}>Save Annotation</Text>
129
+ </TouchableOpacity>
130
+ </View>
131
+ </TouchableOpacity>
132
+ </Animated.View>
133
+ </TouchableOpacity>
134
+ </Animated.View>
135
+ </KeyboardAvoidingView>
136
+ </Modal>
137
+ );
138
+ }
139
+
140
+ const styles = StyleSheet.create({
141
+ keyboardView: {
142
+ flex: 1,
143
+ },
144
+ overlay: {
145
+ flex: 1,
146
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
147
+ justifyContent: 'center',
148
+ alignItems: 'center',
149
+ },
150
+ backdropTouchable: {
151
+ flex: 1,
152
+ width: '100%',
153
+ justifyContent: 'center',
154
+ alignItems: 'center',
155
+ },
156
+ container: {
157
+ backgroundColor: '#1e1e1e',
158
+ borderRadius: 8,
159
+ padding: 20,
160
+ width: '90%',
161
+ maxWidth: 400,
162
+ },
163
+ header: {
164
+ flexDirection: 'row',
165
+ justifyContent: 'space-between',
166
+ alignItems: 'center',
167
+ marginBottom: 16,
168
+ },
169
+ title: {
170
+ color: '#e5e7eb',
171
+ fontSize: 18,
172
+ fontWeight: 'bold',
173
+ },
174
+ closeButton: {
175
+ padding: 4,
176
+ },
177
+ closeButtonText: {
178
+ color: '#e5e7eb',
179
+ fontSize: 20,
180
+ },
181
+ componentLabel: {
182
+ color: '#9ca3af',
183
+ fontSize: 14,
184
+ marginBottom: 12,
185
+ },
186
+ componentName: {
187
+ color: '#e5e7eb',
188
+ fontWeight: 'bold',
189
+ },
190
+ textInput: {
191
+ backgroundColor: '#2d2d2d',
192
+ borderWidth: 1,
193
+ borderColor: '#404040',
194
+ borderRadius: 4,
195
+ padding: 8,
196
+ color: 'white',
197
+ height: 100,
198
+ marginBottom: 16,
199
+ fontSize: 14,
200
+ },
201
+ buttonContainer: {
202
+ flexDirection: 'row',
203
+ justifyContent: 'flex-end',
204
+ gap: 8,
205
+ },
206
+ cancelButton: {
207
+ paddingHorizontal: 12,
208
+ paddingVertical: 8,
209
+ borderRadius: 4,
210
+ borderWidth: 1,
211
+ borderColor: '#404040',
212
+ },
213
+ cancelButtonText: {
214
+ color: 'white',
215
+ fontSize: 14,
216
+ },
217
+ submitButton: {
218
+ backgroundColor: '#3b82f6',
219
+ paddingHorizontal: 12,
220
+ paddingVertical: 8,
221
+ borderRadius: 4,
222
+ },
223
+ submitButtonText: {
224
+ color: 'white',
225
+ fontSize: 14,
226
+ },
227
+ });
@@ -0,0 +1,157 @@
1
+ import React from 'react';
2
+ import {
3
+ View,
4
+ Text,
5
+ Modal,
6
+ TouchableOpacity,
7
+ ScrollView,
8
+ StyleSheet,
9
+ } from 'react-native';
10
+ import { useAiAnnotation } from '../../store';
11
+
12
+ export function AnnotationList() {
13
+ const { state, dispatch } = useAiAnnotation();
14
+
15
+ if (!state.showList) return null;
16
+
17
+ return (
18
+ <Modal
19
+ visible={state.showList}
20
+ transparent
21
+ animationType="fade"
22
+ onRequestClose={() => dispatch({ type: 'TOGGLE_LIST' })}
23
+ >
24
+ <View style={styles.overlay}>
25
+ <View style={styles.container}>
26
+ <View style={styles.header}>
27
+ <Text style={styles.title}>Annotations ({state.annotations.length})</Text>
28
+ <View style={styles.headerActions}>
29
+ {state.annotations.length > 0 && (
30
+ <TouchableOpacity
31
+ style={styles.clearButton}
32
+ onPress={() => dispatch({ type: 'CLEAR_ALL_ANNOTATIONS' })}
33
+ >
34
+ <Text style={styles.clearButtonText}>Clear All</Text>
35
+ </TouchableOpacity>
36
+ )}
37
+ <TouchableOpacity
38
+ style={styles.closeButton}
39
+ onPress={() => dispatch({ type: 'TOGGLE_LIST' })}
40
+ >
41
+ <Text style={styles.closeButtonText}>✕</Text>
42
+ </TouchableOpacity>
43
+ </View>
44
+ </View>
45
+
46
+ <ScrollView style={styles.listContainer}>
47
+ {state.annotations.length === 0 ? (
48
+ <Text style={styles.emptyText}>No annotations yet.</Text>
49
+ ) : (
50
+ state.annotations.map((ann) => (
51
+ <View key={ann.id} style={styles.annotationItem}>
52
+ <View style={styles.annotationContent}>
53
+ <Text style={styles.componentName}>{ann.componentName}</Text>
54
+ <Text style={styles.annotationNote}>{ann.note}</Text>
55
+ </View>
56
+ <TouchableOpacity
57
+ style={styles.deleteButton}
58
+ onPress={() => dispatch({ type: 'REMOVE_ANNOTATION', payload: ann.id })}
59
+ >
60
+ <Text style={styles.deleteButtonText}>🗑</Text>
61
+ </TouchableOpacity>
62
+ </View>
63
+ ))
64
+ )}
65
+ </ScrollView>
66
+ </View>
67
+ </View>
68
+ </Modal>
69
+ );
70
+ }
71
+
72
+ const styles = StyleSheet.create({
73
+ overlay: {
74
+ flex: 1,
75
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
76
+ justifyContent: 'center',
77
+ alignItems: 'center',
78
+ },
79
+ container: {
80
+ backgroundColor: '#1e1e1e',
81
+ borderRadius: 8,
82
+ width: '90%',
83
+ maxWidth: 500,
84
+ maxHeight: '80%',
85
+ padding: 20,
86
+ },
87
+ header: {
88
+ flexDirection: 'row',
89
+ justifyContent: 'space-between',
90
+ alignItems: 'center',
91
+ marginBottom: 16,
92
+ },
93
+ title: {
94
+ color: '#e5e7eb',
95
+ fontSize: 18,
96
+ fontWeight: 'bold',
97
+ },
98
+ headerActions: {
99
+ flexDirection: 'row',
100
+ alignItems: 'center',
101
+ gap: 8,
102
+ },
103
+ clearButton: {
104
+ backgroundColor: '#ef4444',
105
+ paddingHorizontal: 12,
106
+ paddingVertical: 6,
107
+ borderRadius: 4,
108
+ },
109
+ clearButtonText: {
110
+ color: 'white',
111
+ fontSize: 12,
112
+ },
113
+ closeButton: {
114
+ padding: 4,
115
+ },
116
+ closeButtonText: {
117
+ color: '#e5e7eb',
118
+ fontSize: 20,
119
+ },
120
+ listContainer: {
121
+ flex: 1,
122
+ },
123
+ emptyText: {
124
+ color: '#9ca3af',
125
+ textAlign: 'center',
126
+ padding: 20,
127
+ },
128
+ annotationItem: {
129
+ backgroundColor: '#2d2d2d',
130
+ padding: 12,
131
+ borderRadius: 4,
132
+ marginBottom: 8,
133
+ flexDirection: 'row',
134
+ justifyContent: 'space-between',
135
+ alignItems: 'flex-start',
136
+ },
137
+ annotationContent: {
138
+ flex: 1,
139
+ marginRight: 12,
140
+ },
141
+ componentName: {
142
+ color: '#60a5fa',
143
+ fontWeight: 'bold',
144
+ fontSize: 14,
145
+ marginBottom: 4,
146
+ },
147
+ annotationNote: {
148
+ color: '#e5e7eb',
149
+ fontSize: 14,
150
+ },
151
+ deleteButton: {
152
+ padding: 4,
153
+ },
154
+ deleteButtonText: {
155
+ fontSize: 16,
156
+ },
157
+ });
@@ -0,0 +1,65 @@
1
+ import React, { useRef, ReactNode } from 'react';
2
+ import {
3
+ View,
4
+ PanResponder,
5
+ Animated,
6
+ StyleSheet,
7
+ ViewStyle,
8
+ GestureResponderEvent,
9
+ PanResponderGestureState,
10
+ } from 'react-native';
11
+
12
+ interface DraggableProps {
13
+ children: ReactNode;
14
+ initialPos?: { x: number; y: number };
15
+ style?: ViewStyle;
16
+ }
17
+
18
+ export function Draggable({ children, initialPos = { x: 20, y: 20 }, style }: DraggableProps) {
19
+ const pan = useRef(new Animated.ValueXY(initialPos)).current;
20
+ const lastOffset = useRef(initialPos);
21
+
22
+ const panResponder = useRef(
23
+ PanResponder.create({
24
+ onStartShouldSetPanResponder: () => true,
25
+ onMoveShouldSetPanResponder: () => true,
26
+ onPanResponderGrant: () => {
27
+ pan.setOffset(lastOffset.current);
28
+ pan.setValue({ x: 0, y: 0 });
29
+ },
30
+ onPanResponderMove: Animated.event(
31
+ [null, { dx: pan.x, dy: pan.y }],
32
+ { useNativeDriver: false }
33
+ ),
34
+ onPanResponderRelease: (_e: GestureResponderEvent, gestureState: PanResponderGestureState) => {
35
+ lastOffset.current = {
36
+ x: lastOffset.current.x + gestureState.dx,
37
+ y: lastOffset.current.y + gestureState.dy,
38
+ };
39
+ pan.flattenOffset();
40
+ },
41
+ })
42
+ ).current;
43
+
44
+ return (
45
+ <Animated.View
46
+ {...panResponder.panHandlers}
47
+ style={[
48
+ styles.container,
49
+ style,
50
+ {
51
+ transform: [{ translateX: pan.x }, { translateY: pan.y }],
52
+ },
53
+ ]}
54
+ >
55
+ {children}
56
+ </Animated.View>
57
+ );
58
+ }
59
+
60
+ const styles = StyleSheet.create({
61
+ container: {
62
+ position: 'absolute',
63
+ zIndex: 9999,
64
+ },
65
+ });