@umituz/react-native-photo-editor 1.0.10 → 1.0.12

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-photo-editor",
3
- "version": "1.0.10",
3
+ "version": "1.0.12",
4
4
  "description": "A powerful, generic photo editor for React Native",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -13,10 +13,11 @@ import {
13
13
  import { EditorCanvas } from "./components/EditorCanvas";
14
14
  import { EditorToolbar } from "./components/EditorToolbar";
15
15
  import { FontControls } from "./components/FontControls";
16
- import { StickerPicker } from "./components/StickerPicker";
17
- import { FilterPicker } from "./components/FilterPicker";
18
16
  import { LayerManager } from "./components/LayerManager";
19
17
  import { TextEditorSheet } from "./components/TextEditorSheet";
18
+ import { StickerPicker } from "./components/StickerPicker";
19
+ import { FilterPicker } from "./components/FilterPicker";
20
+ import { AIMagicSheet } from "./components/AIMagicSheet";
20
21
  import { createEditorStyles } from "./styles";
21
22
  import { usePhotoEditorUI } from "./hooks/usePhotoEditorUI";
22
23
  import { Layer } from "./types";
@@ -38,7 +39,6 @@ export interface PhotoEditorProps {
38
39
  initialCaption?: string;
39
40
  t: (key: string) => string;
40
41
  fonts?: readonly string[];
41
- stickers?: readonly string[];
42
42
  showAI?: boolean;
43
43
  }
44
44
 
@@ -51,124 +51,42 @@ export const PhotoEditor: React.FC<PhotoEditorProps> = ({
51
51
  initialCaption,
52
52
  t,
53
53
  fonts = DEFAULT_FONTS,
54
- stickers,
55
- showAI = false,
54
+ showAI = true,
56
55
  }) => {
57
56
  const tokens = useAppDesignTokens();
58
57
  const insets = useSafeAreaInsets();
59
58
  const styles = useMemo(() => createEditorStyles(tokens, insets), [tokens, insets]);
59
+ const ui = usePhotoEditorUI(initialCaption, tokens);
60
60
 
61
- const {
62
- // Refs
63
- textEditorSheetRef,
64
- stickerSheetRef,
65
- filterSheetRef,
66
- layerSheetRef,
67
- // State
68
- selectedFont,
69
- setSelectedFont,
70
- fontSize,
71
- setFontSize,
72
- editingText,
73
- setEditingText,
74
- selectedFilter,
75
- // Domain State
76
- layers,
77
- activeLayerId,
78
- // Actions
79
- updateLayer,
80
- deleteLayer,
81
- selectLayer,
82
- addTextLayer,
83
- handleAddText,
84
- handleTextLayerTap,
85
- handleSaveText,
86
- handleSelectFilter,
87
- handleSelectSticker,
88
- } = usePhotoEditorUI(initialCaption, tokens);
61
+ const actions: EditorActions = useMemo(() => ({
62
+ addTextLayer: ui.addTextLayer,
63
+ updateLayer: ui.updateLayer,
64
+ getLayers: () => ui.layers,
65
+ getActiveLayerId: () => ui.activeLayerId,
66
+ }), [ui.addTextLayer, ui.updateLayer, ui.layers, ui.activeLayerId]);
89
67
 
90
68
  return (
91
69
  <SafeBottomSheetModalProvider>
92
70
  <View style={styles.container}>
93
71
  <View style={styles.header}>
94
- <TouchableOpacity style={styles.headerButton} onPress={onClose}>
95
- <AtomicIcon name="close" size="md" color="textPrimary" />
96
- </TouchableOpacity>
97
- <AtomicText style={styles.headerTitle}>{title}</AtomicText>
98
- <TouchableOpacity style={styles.postButton} onPress={() => onSave?.(imageUri)}>
99
- <AtomicText style={styles.postButtonText}>{t("preview.share") || "Share"}</AtomicText>
100
- </TouchableOpacity>
72
+ <TouchableOpacity onPress={onClose}><AtomicIcon name="close" size="md" color="textPrimary" /></TouchableOpacity>
73
+ <AtomicText type="headlineSmall" style={styles.headerTitle}>{title}</AtomicText>
74
+ <TouchableOpacity onPress={() => onSave?.(imageUri)}><AtomicText fontWeight="bold" color="primary">{t("common.save")}</AtomicText></TouchableOpacity>
101
75
  </View>
102
76
 
103
- <ScrollView
104
- contentContainerStyle={styles.scrollContent}
105
- showsVerticalScrollIndicator={false}
106
- >
107
- <EditorCanvas
108
- imageUrl={imageUri}
109
- layers={layers}
110
- activeLayerId={activeLayerId}
111
- onLayerTap={handleTextLayerTap}
112
- onLayerMove={(id, x, y) => updateLayer(id, { x, y })}
113
- styles={styles}
114
- />
115
- {typeof customTools === "function"
116
- ? customTools({
117
- addTextLayer,
118
- updateLayer,
119
- getLayers: () => layers,
120
- getActiveLayerId: () => activeLayerId,
121
- })
122
- : customTools}
123
- <FontControls
124
- fontSize={fontSize}
125
- selectedFont={selectedFont}
126
- fonts={fonts}
127
- onFontSizeChange={(s) => setFontSize(Math.max(12, Math.min(96, s)))}
128
- onFontSelect={setSelectedFont}
129
- styles={styles}
130
- />
77
+ <ScrollView contentContainerStyle={styles.scrollContent}>
78
+ <EditorCanvas imageUrl={imageUri} layers={ui.layers} activeLayerId={ui.activeLayerId} onLayerTap={ui.handleTextLayerTap} onLayerMove={(id, x, y) => ui.updateLayer(id, { x, y })} styles={styles} />
79
+ {typeof customTools === "function" ? customTools(actions) : customTools}
80
+ <FontControls fontSize={ui.fontSize} selectedFont={ui.selectedFont} fonts={fonts} onFontSizeChange={ui.setFontSize} onFontSelect={ui.setSelectedFont} styles={styles} />
131
81
  </ScrollView>
132
82
 
133
- <EditorToolbar
134
- onAddText={handleAddText}
135
- onAddSticker={() => stickerSheetRef.current?.present()}
136
- onOpenFilters={() => filterSheetRef.current?.present()}
137
- onOpenLayers={() => layerSheetRef.current?.present()}
138
- onAIMagic={showAI ? undefined : undefined}
139
- styles={styles}
140
- t={t}
141
- />
142
-
143
- <BottomSheetModal ref={textEditorSheetRef} snapPoints={["40%"]}>
144
- <TextEditorSheet
145
- value={editingText}
146
- onChange={setEditingText}
147
- onSave={handleSaveText}
148
- t={t}
149
- />
150
- </BottomSheetModal>
151
-
152
- <BottomSheetModal ref={stickerSheetRef} snapPoints={["50%"]}>
153
- <StickerPicker stickers={stickers} onSelectSticker={handleSelectSticker} />
154
- </BottomSheetModal>
155
-
156
- <BottomSheetModal ref={filterSheetRef} snapPoints={["40%"]}>
157
- <FilterPicker selectedFilter={selectedFilter} onSelectFilter={handleSelectFilter} />
158
- </BottomSheetModal>
83
+ <EditorToolbar onAddText={ui.handleAddText} onAddSticker={() => ui.stickerSheetRef.current?.present()} onOpenFilters={() => ui.filterSheetRef.current?.present()} onOpenLayers={() => ui.layerSheetRef.current?.present()} onAIMagic={showAI ? () => ui.aiSheetRef.current?.present() : undefined} styles={styles} t={t} />
159
84
 
160
- <BottomSheetModal ref={layerSheetRef} snapPoints={["50%"]}>
161
- <LayerManager
162
- layers={layers}
163
- activeLayerId={activeLayerId}
164
- onSelectLayer={(id) => {
165
- selectLayer(id);
166
- layerSheetRef.current?.dismiss();
167
- }}
168
- onDeleteLayer={deleteLayer}
169
- t={t}
170
- />
171
- </BottomSheetModal>
85
+ <BottomSheetModal ref={ui.textEditorSheetRef} snapPoints={["40%"]}><TextEditorSheet value={ui.editingText} onChange={ui.setEditingText} onSave={ui.handleSaveText} t={t} /></BottomSheetModal>
86
+ <BottomSheetModal ref={ui.stickerSheetRef} snapPoints={["50%"]}><StickerPicker onSelectSticker={ui.handleSelectSticker} /></BottomSheetModal>
87
+ <BottomSheetModal ref={ui.filterSheetRef} snapPoints={["40%"]}><FilterPicker selectedFilter={ui.selectedFilter} onSelectFilter={ui.handleSelectFilter} /></BottomSheetModal>
88
+ <BottomSheetModal ref={ui.layerSheetRef} snapPoints={["50%"]}><LayerManager layers={ui.layers} activeLayerId={ui.activeLayerId} onSelectLayer={ui.selectLayer} onDeleteLayer={ui.deleteLayer} t={t} /></BottomSheetModal>
89
+ <BottomSheetModal ref={ui.aiSheetRef} snapPoints={["60%"]}><AIMagicSheet onGenerateCaption={(_s) => { ui.aiSheetRef.current?.dismiss(); /* AI trigger */ }} /></BottomSheetModal>
172
90
  </View>
173
91
  </SafeBottomSheetModalProvider>
174
92
  );
@@ -1,21 +1,10 @@
1
1
  import React, { useState } from "react";
2
- import {
3
- View,
4
- ScrollView,
5
- TouchableOpacity,
6
- StyleSheet,
7
- ActivityIndicator,
8
- } from "react-native";
9
- import {
10
- AtomicText,
11
- AtomicIcon,
12
- useAppDesignTokens,
13
- } from "@umituz/react-native-design-system";
2
+ import { View, ScrollView, TouchableOpacity, StyleSheet } from "react-native";
3
+ import { AtomicText, AtomicIcon, useAppDesignTokens, AtomicButton } from "@umituz/react-native-design-system";
14
4
 
15
5
  interface AIMagicSheetProps {
16
6
  onGenerateCaption: (style: string) => void;
17
7
  isLoading?: boolean;
18
- _t: (key: string) => string;
19
8
  }
20
9
 
21
10
  const AI_STYLES = [
@@ -30,145 +19,65 @@ const AI_STYLES = [
30
19
  export const AIMagicSheet: React.FC<AIMagicSheetProps> = ({
31
20
  onGenerateCaption,
32
21
  isLoading = false,
33
- _t,
34
22
  }) => {
35
23
  const tokens = useAppDesignTokens();
36
- const [selectedStyle, setSelectedStyle] = useState<string | null>(null);
24
+ const [selected, setSelected] = useState<string | null>(null);
37
25
 
38
26
  const styles = StyleSheet.create({
39
- container: { padding: 16 },
40
- header: {
27
+ container: { padding: tokens.spacing.md, gap: tokens.spacing.md },
28
+ header: { flexDirection: "row", alignItems: "center", gap: tokens.spacing.sm },
29
+ grid: { gap: tokens.spacing.sm },
30
+ card: {
41
31
  flexDirection: "row",
42
32
  alignItems: "center",
43
- gap: 8,
44
- marginBottom: 16,
45
- },
46
- title: {
47
- fontSize: 18,
48
- fontWeight: "bold",
49
- color: tokens.colors.textPrimary,
50
- },
51
- subtitle: {
52
- fontSize: 14,
53
- color: tokens.colors.textSecondary,
54
- marginBottom: 16,
55
- },
56
- grid: { gap: 12 },
57
- styleCard: {
58
- flexDirection: "row",
59
- alignItems: "center",
60
- padding: 16,
33
+ padding: tokens.spacing.md,
61
34
  backgroundColor: tokens.colors.surfaceVariant,
62
- borderRadius: 16,
35
+ borderRadius: tokens.borders.radius.md,
63
36
  borderWidth: 2,
64
37
  borderColor: "transparent",
65
38
  },
66
- styleCardActive: {
39
+ cardActive: {
67
40
  borderColor: tokens.colors.primary,
68
- backgroundColor: tokens.colors.primaryContainer,
69
- },
70
- styleInfo: { flex: 1, marginLeft: 12 },
71
- styleLabel: {
72
- fontSize: 16,
73
- fontWeight: "bold",
74
- color: tokens.colors.textPrimary,
75
- },
76
- styleLabelActive: { color: tokens.colors.primary },
77
- styleDesc: {
78
- fontSize: 12,
79
- color: tokens.colors.textSecondary,
80
- marginTop: 2,
81
- },
82
- generateButton: {
83
- backgroundColor: tokens.colors.primary,
84
- borderRadius: 999,
85
- padding: 16,
86
- flexDirection: "row",
87
- alignItems: "center",
88
- justifyContent: "center",
89
- gap: 8,
90
- marginTop: 16,
91
- },
92
- generateButtonDisabled: { opacity: 0.5 },
93
- generateButtonText: {
94
- color: tokens.colors.onPrimary,
95
- fontWeight: "bold",
96
- fontSize: 16,
41
+ backgroundColor: tokens.colors.primary + "10",
97
42
  },
43
+ info: { flex: 1, marginLeft: tokens.spacing.sm },
98
44
  });
99
45
 
100
- const handleGenerate = () => {
101
- if (selectedStyle) {
102
- onGenerateCaption(selectedStyle);
103
- }
104
- };
105
-
106
46
  return (
107
47
  <View style={styles.container}>
108
48
  <View style={styles.header}>
109
49
  <AtomicIcon name="sparkles" size="md" color="primary" />
110
- <AtomicText style={styles.title}>AI Caption Magic</AtomicText>
50
+ <AtomicText type="headlineSmall">AI Caption Magic</AtomicText>
111
51
  </View>
112
- <AtomicText style={styles.subtitle}>
113
- Choose a style and let AI create the perfect caption
114
- </AtomicText>
115
-
116
52
  <ScrollView showsVerticalScrollIndicator={false}>
117
53
  <View style={styles.grid}>
118
54
  {AI_STYLES.map((style) => (
119
55
  <TouchableOpacity
120
56
  key={style.id}
121
- style={[
122
- styles.styleCard,
123
- selectedStyle === style.id && styles.styleCardActive,
124
- ]}
125
- onPress={() => setSelectedStyle(style.id)}
57
+ style={[styles.card, selected === style.id && styles.cardActive]}
58
+ onPress={() => setSelected(style.id)}
126
59
  >
127
- <AtomicText style={{ fontSize: 28 }}>
128
- {style.label.split(" ")[0]}
129
- </AtomicText>
130
- <View style={styles.styleInfo}>
131
- <AtomicText
132
- style={[
133
- styles.styleLabel,
134
- selectedStyle === style.id && styles.styleLabelActive,
135
- ]}
136
- >
60
+ <AtomicText style={{ fontSize: 24 }}>{style.label.split(" ")[0]}</AtomicText>
61
+ <View style={styles.info}>
62
+ <AtomicText fontWeight="bold" color={selected === style.id ? "primary" : "textPrimary"}>
137
63
  {style.label.split(" ").slice(1).join(" ")}
138
64
  </AtomicText>
139
- <AtomicText style={styles.styleDesc}>{style.desc}</AtomicText>
65
+ <AtomicText type="labelSmall" color="textSecondary">{style.desc}</AtomicText>
140
66
  </View>
141
- {selectedStyle === style.id && (
142
- <AtomicIcon name="checkmark-circle" size="md" color="primary" />
143
- )}
67
+ {selected === style.id && <AtomicIcon name="checkmark-circle" size="md" color="primary" />}
144
68
  </TouchableOpacity>
145
69
  ))}
146
70
  </View>
147
71
  </ScrollView>
148
-
149
- <TouchableOpacity
150
- style={[
151
- styles.generateButton,
152
- !selectedStyle && styles.generateButtonDisabled,
153
- ]}
154
- onPress={handleGenerate}
155
- disabled={!selectedStyle || isLoading}
72
+ <AtomicButton
73
+ variant="primary"
74
+ disabled={!selected || isLoading}
75
+ onPress={() => selected && onGenerateCaption(selected)}
76
+ loading={isLoading}
77
+ icon="sparkles"
156
78
  >
157
- {isLoading ? (
158
- <ActivityIndicator color={tokens.colors.onPrimary} />
159
- ) : (
160
- <>
161
- <AtomicIcon
162
- name="sparkles"
163
- size="sm"
164
- customColor={tokens.colors.onPrimary}
165
- />
166
- <AtomicText style={styles.generateButtonText}>
167
- Generate Caption
168
- </AtomicText>
169
- </>
170
- )}
171
- </TouchableOpacity>
79
+ Generate Caption
80
+ </AtomicButton>
172
81
  </View>
173
82
  );
174
83
  };
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import { StyleSheet, View } from "react-native";
2
+ import { View } from "react-native";
3
3
  import { Gesture, GestureDetector } from "react-native-gesture-handler";
4
4
  import Animated, {
5
5
  useAnimatedStyle,
@@ -15,34 +15,31 @@ interface DraggableStickerProps {
15
15
  uri: string;
16
16
  initialX: number;
17
17
  initialY: number;
18
- rotation?: number;
19
- scale?: number;
20
- opacity?: number;
21
18
  onDragEnd: (x: number, y: number) => void;
22
19
  onPress: () => void;
23
20
  isSelected?: boolean;
21
+ rotation?: number;
22
+ scale?: number;
23
+ opacity?: number;
24
24
  }
25
25
 
26
26
  export const DraggableSticker: React.FC<DraggableStickerProps> = ({
27
27
  uri,
28
28
  initialX,
29
29
  initialY,
30
- rotation = 0,
31
- scale = 1,
32
- opacity = 1,
33
30
  onDragEnd,
34
31
  onPress,
35
32
  isSelected,
33
+ rotation = 0,
34
+ scale = 1,
35
+ opacity = 1,
36
36
  }) => {
37
37
  const tokens = useAppDesignTokens();
38
38
  const translateX = useSharedValue(initialX);
39
39
  const translateY = useSharedValue(initialY);
40
- const offset = useSharedValue({ x: 0, y: 0 });
40
+ const offset = useSharedValue({ x: initialX, y: initialY });
41
41
 
42
- const isEmoji = uri.length <= 4 && !uri.startsWith("http");
43
-
44
- const drag = Gesture.Pan()
45
- .minDistance(5)
42
+ const panGesture = Gesture.Pan()
46
43
  .onStart(() => {
47
44
  offset.value = { x: translateX.value, y: translateY.value };
48
45
  })
@@ -54,45 +51,39 @@ export const DraggableSticker: React.FC<DraggableStickerProps> = ({
54
51
  runOnJS(onDragEnd)(translateX.value, translateY.value);
55
52
  });
56
53
 
57
- const tap = Gesture.Tap()
58
- .maxDistance(5)
59
- .onEnd(() => {
60
- runOnJS(onPress)();
61
- });
62
-
63
- const gesture = Gesture.Exclusive(drag, tap);
54
+ const tapGesture = Gesture.Tap().onEnd(() => {
55
+ runOnJS(onPress)();
56
+ });
64
57
 
65
58
  const animatedStyle = useAnimatedStyle(() => ({
66
59
  transform: [
67
60
  { translateX: translateX.value },
68
61
  { translateY: translateY.value },
69
62
  { rotate: `${rotation}deg` },
70
- { scale: scale },
63
+ { scale },
71
64
  ],
72
- opacity: opacity,
65
+ opacity,
73
66
  zIndex: isSelected ? 100 : 50,
74
67
  }));
75
68
 
76
- const styles = StyleSheet.create({
77
- container: { position: "absolute", left: 0, top: 0 },
78
- emojiContainer: {
79
- padding: 4,
80
- borderRadius: 8,
81
- borderWidth: isSelected ? 2 : 0,
82
- borderColor: tokens.colors.primary,
83
- borderStyle: "dashed",
84
- backgroundColor: isSelected
85
- ? tokens.colors.primary + "20"
86
- : "transparent",
87
- },
88
- emoji: { fontSize: 64 },
89
- });
69
+ const isEmoji = uri.length <= 4 && !uri.startsWith("http");
90
70
 
91
71
  return (
92
- <GestureDetector gesture={gesture}>
93
- <Animated.View style={[styles.container, animatedStyle]}>
94
- <View style={styles.emojiContainer}>
95
- {isEmoji ? <AtomicText style={styles.emoji}>{uri}</AtomicText> : null}
72
+ <GestureDetector gesture={Gesture.Exclusive(panGesture, tapGesture)}>
73
+ <Animated.View style={[animatedStyle, { position: "absolute" }]}>
74
+ <View
75
+ style={{
76
+ padding: tokens.spacing.xs,
77
+ borderRadius: tokens.borders.radius.sm,
78
+ borderWidth: isSelected ? 2 : 0,
79
+ borderColor: tokens.colors.primary,
80
+ borderStyle: "dashed",
81
+ backgroundColor: isSelected ? tokens.colors.primary + "10" : "transparent",
82
+ }}
83
+ >
84
+ {isEmoji ? (
85
+ <AtomicText style={{ fontSize: 48 }}>{uri}</AtomicText>
86
+ ) : null}
96
87
  </View>
97
88
  </Animated.View>
98
89
  </GestureDetector>
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import { StyleSheet, View } from "react-native";
2
+ import { View } from "react-native";
3
3
  import { Gesture, GestureDetector } from "react-native-gesture-handler";
4
4
  import Animated, {
5
5
  useAnimatedStyle,
@@ -10,25 +10,24 @@ import {
10
10
  AtomicText,
11
11
  useAppDesignTokens,
12
12
  } from "@umituz/react-native-design-system";
13
- import { TextAlign } from "../types";
14
13
 
15
14
  interface DraggableTextProps {
16
15
  text: string;
17
16
  color: string;
18
17
  fontSize?: number;
19
18
  fontFamily?: string;
20
- textAlign?: TextAlign;
19
+ initialX: number;
20
+ initialY: number;
21
+ onDragEnd: (x: number, y: number) => void;
22
+ onPress: () => void;
23
+ isSelected?: boolean;
21
24
  rotation?: number;
22
25
  scale?: number;
23
26
  opacity?: number;
27
+ textAlign?: "center" | "left" | "right";
24
28
  backgroundColor?: string;
25
29
  _strokeColor?: string;
26
30
  _strokeWidth?: number;
27
- initialX: number;
28
- initialY: number;
29
- onDragEnd: (x: number, y: number) => void;
30
- onPress: () => void;
31
- isSelected?: boolean;
32
31
  }
33
32
 
34
33
  export const DraggableText: React.FC<DraggableTextProps> = ({
@@ -36,26 +35,23 @@ export const DraggableText: React.FC<DraggableTextProps> = ({
36
35
  color,
37
36
  fontSize = 24,
38
37
  fontFamily = "System",
39
- textAlign = "center",
40
- rotation = 0,
41
- scale = 1,
42
- opacity = 1,
43
- backgroundColor = "transparent",
44
- _strokeColor,
45
- _strokeWidth = 2,
46
38
  initialX,
47
39
  initialY,
48
40
  onDragEnd,
49
41
  onPress,
50
42
  isSelected,
43
+ rotation = 0,
44
+ scale = 1,
45
+ opacity = 1,
46
+ textAlign = "center",
47
+ backgroundColor = "transparent",
51
48
  }) => {
52
49
  const tokens = useAppDesignTokens();
53
50
  const translateX = useSharedValue(initialX);
54
51
  const translateY = useSharedValue(initialY);
55
- const offset = useSharedValue({ x: 0, y: 0 });
52
+ const offset = useSharedValue({ x: initialX, y: initialY });
56
53
 
57
- const drag = Gesture.Pan()
58
- .minDistance(5)
54
+ const panGesture = Gesture.Pan()
59
55
  .onStart(() => {
60
56
  offset.value = { x: translateX.value, y: translateY.value };
61
57
  })
@@ -67,77 +63,47 @@ export const DraggableText: React.FC<DraggableTextProps> = ({
67
63
  runOnJS(onDragEnd)(translateX.value, translateY.value);
68
64
  });
69
65
 
70
- const tap = Gesture.Tap()
71
- .maxDistance(5)
72
- .onEnd(() => {
73
- runOnJS(onPress)();
74
- });
75
-
76
- const gesture = Gesture.Exclusive(drag, tap);
77
-
78
- const animatedStyle = useAnimatedStyle(() => {
79
- return {
80
- transform: [
81
- { translateX: translateX.value },
82
- { translateY: translateY.value },
83
- { rotate: `${rotation}deg` },
84
- { scale: scale },
85
- ],
86
- opacity: opacity,
87
- zIndex: isSelected ? 100 : 10,
88
- };
66
+ const tapGesture = Gesture.Tap().onEnd(() => {
67
+ runOnJS(onPress)();
89
68
  });
90
69
 
70
+ const animatedStyle = useAnimatedStyle(() => ({
71
+ transform: [
72
+ { translateX: translateX.value },
73
+ { translateY: translateY.value },
74
+ { rotate: `${rotation}deg` },
75
+ { scale },
76
+ ],
77
+ opacity,
78
+ zIndex: isSelected ? 100 : 10,
79
+ }));
80
+
91
81
  return (
92
- <GestureDetector gesture={gesture}>
93
- <Animated.View
94
- style={[
95
- styles.container,
96
- animatedStyle,
97
- { position: "absolute", left: 0, top: 0 },
98
- ]}
99
- >
82
+ <GestureDetector gesture={Gesture.Exclusive(panGesture, tapGesture)}>
83
+ <Animated.View style={[animatedStyle, { position: "absolute" }]}>
100
84
  <View
101
- style={[
102
- {
103
- backgroundColor: backgroundColor,
104
- paddingHorizontal: 8,
105
- paddingVertical: 4,
106
- borderRadius: 4,
107
- },
108
- isSelected && {
109
- borderWidth: 2,
110
- borderColor: tokens.colors.primary,
111
- borderStyle: "dashed",
112
- backgroundColor:
113
- backgroundColor === "transparent"
114
- ? `${tokens.colors.primary}10`
115
- : backgroundColor,
116
- },
117
- ]}
85
+ style={{
86
+ padding: tokens.spacing.xs,
87
+ borderRadius: tokens.borders.radius.sm,
88
+ borderWidth: isSelected ? 2 : 0,
89
+ borderColor: tokens.colors.primary,
90
+ borderStyle: "dashed",
91
+ backgroundColor: isSelected ? tokens.colors.primary + "10" : backgroundColor,
92
+ }}
118
93
  >
119
94
  <AtomicText
95
+ fontWeight="900"
120
96
  style={{
121
- fontSize: fontSize,
122
- fontFamily: fontFamily,
123
- fontWeight: "900",
124
- textAlign: textAlign,
125
- textTransform: "uppercase",
126
- color: color,
127
- minWidth: text ? undefined : 100,
128
- minHeight: text ? undefined : 40,
97
+ fontSize,
98
+ fontFamily,
99
+ color,
100
+ textAlign,
129
101
  }}
130
102
  >
131
- {text}
103
+ {text || "TAP TO EDIT"}
132
104
  </AtomicText>
133
105
  </View>
134
106
  </Animated.View>
135
107
  </GestureDetector>
136
108
  );
137
109
  };
138
-
139
- const styles = StyleSheet.create({
140
- container: {
141
- zIndex: 10,
142
- },
143
- });