@umituz/react-native-photo-editor 2.0.24 → 2.0.25

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 (55) hide show
  1. package/package.json +1 -1
  2. package/src/PhotoEditor.tsx +43 -137
  3. package/src/application/hooks/useEditor.ts +4 -6
  4. package/src/application/hooks/useEditorUI.ts +8 -5
  5. package/src/application/stores/EditorStore.ts +17 -6
  6. package/src/domain/entities/Layer.entity.ts +86 -0
  7. package/src/domain/entities/{Layer.ts → Layer.legacy.ts} +3 -3
  8. package/src/domain/entities/StickerLayer.entity.ts +37 -0
  9. package/src/domain/entities/TextLayer.entity.ts +58 -0
  10. package/src/domain/entities/index.ts +9 -0
  11. package/src/domain/services/History.service.ts +69 -0
  12. package/src/domain/services/LayerFactory.service.ts +81 -0
  13. package/src/domain/services/LayerRepository.service.ts +85 -0
  14. package/src/domain/services/LayerService.ts +1 -1
  15. package/src/domain/types.ts +39 -0
  16. package/src/domain/value-objects/FilterSettings.vo.ts +89 -0
  17. package/src/domain/value-objects/LayerDefaults.vo.ts +56 -0
  18. package/src/domain/value-objects/Transform.vo.ts +61 -0
  19. package/src/domain/value-objects/index.ts +13 -0
  20. package/src/index.ts +4 -4
  21. package/src/infrastructure/gesture/createTransformGesture.ts +127 -0
  22. package/src/infrastructure/gesture/useTransformGesture.ts +7 -13
  23. package/src/presentation/components/DraggableLayer.tsx +13 -13
  24. package/src/presentation/components/EditorCanvas.tsx +5 -5
  25. package/src/presentation/components/EditorContent.tsx +72 -0
  26. package/src/presentation/components/EditorHeader.tsx +48 -0
  27. package/src/presentation/components/EditorSheets.tsx +85 -0
  28. package/src/presentation/components/FontControls.tsx +2 -2
  29. package/src/presentation/components/sheets/AdjustmentsSheet.tsx +4 -4
  30. package/src/presentation/components/sheets/FilterSheet.tsx +1 -1
  31. package/src/presentation/components/sheets/LayerManager.tsx +3 -4
  32. package/src/presentation/components/sheets/TextEditorSheet.tsx +1 -1
  33. package/src/types.ts +8 -18
  34. package/src/utils/constants.ts +84 -0
  35. package/src/utils/formatters.ts +29 -0
  36. package/src/utils/helpers.ts +51 -0
  37. package/src/utils/index.ts +9 -0
  38. package/src/utils/validators.ts +38 -0
  39. package/src/components/AIMagicSheet.tsx +0 -107
  40. package/src/components/AdjustmentsSheet.tsx +0 -108
  41. package/src/components/ColorPicker.tsx +0 -77
  42. package/src/components/DraggableSticker.tsx +0 -161
  43. package/src/components/DraggableText.tsx +0 -181
  44. package/src/components/EditorCanvas.tsx +0 -106
  45. package/src/components/EditorToolbar.tsx +0 -155
  46. package/src/components/FilterPicker.tsx +0 -73
  47. package/src/components/FontControls.tsx +0 -132
  48. package/src/components/LayerManager.tsx +0 -164
  49. package/src/components/Slider.tsx +0 -112
  50. package/src/components/StickerPicker.tsx +0 -47
  51. package/src/components/TextEditorSheet.tsx +0 -160
  52. package/src/core/HistoryManager.ts +0 -53
  53. package/src/hooks/usePhotoEditor.ts +0 -172
  54. package/src/hooks/usePhotoEditorUI.ts +0 -162
  55. package/src/infrastructure/history/HistoryManager.ts +0 -38
@@ -1,107 +0,0 @@
1
- import React, { useState, useMemo } from "react";
2
- import { View, ScrollView, TouchableOpacity, StyleSheet } from "react-native";
3
- import { AtomicText, AtomicIcon, AtomicButton } from "@umituz/react-native-design-system/atoms";
4
- import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
5
- import { DEFAULT_AI_STYLES } from "../constants";
6
-
7
- interface AIMagicSheetProps {
8
- /**
9
- * Called with the selected style ID. Should return a generated caption string.
10
- * If undefined, the AI button is disabled.
11
- */
12
- onGenerateCaption?: (style: string) => Promise<string> | void;
13
- isLoading?: boolean;
14
- }
15
-
16
- export const AIMagicSheet: React.FC<AIMagicSheetProps> = ({
17
- onGenerateCaption,
18
- isLoading = false,
19
- }) => {
20
- const tokens = useAppDesignTokens();
21
- const [selected, setSelected] = useState<string | null>(null);
22
- const [loading, setLoading] = useState(false);
23
-
24
- const styles = useMemo(() => StyleSheet.create({
25
- container: { padding: tokens.spacing.md, gap: tokens.spacing.md },
26
- header: { flexDirection: "row", alignItems: "center", gap: tokens.spacing.sm },
27
- grid: { gap: tokens.spacing.sm },
28
- card: {
29
- flexDirection: "row",
30
- alignItems: "center",
31
- padding: tokens.spacing.md,
32
- backgroundColor: tokens.colors.surfaceVariant,
33
- borderRadius: tokens.borders.radius.md,
34
- borderWidth: 2,
35
- borderColor: "transparent",
36
- },
37
- cardActive: {
38
- borderColor: tokens.colors.primary,
39
- backgroundColor: tokens.colors.primary + "10",
40
- },
41
- info: { flex: 1, marginLeft: tokens.spacing.sm },
42
- }), [tokens]);
43
-
44
- const handleGenerate = async () => {
45
- if (!selected || !onGenerateCaption) return;
46
- setLoading(true);
47
- try {
48
- await onGenerateCaption(selected);
49
- } finally {
50
- setLoading(false);
51
- }
52
- };
53
-
54
- const isGenerating = isLoading || loading;
55
-
56
- return (
57
- <View style={styles.container}>
58
- <View style={styles.header}>
59
- <AtomicIcon name="sparkles" size="md" color="primary" />
60
- <AtomicText type="headlineSmall">AI Caption Magic</AtomicText>
61
- </View>
62
- <ScrollView showsVerticalScrollIndicator={false}>
63
- <View style={styles.grid}>
64
- {DEFAULT_AI_STYLES.map((style) => {
65
- const isActive = selected === style.id;
66
- const [emoji, ...words] = style.label.split(" ");
67
- return (
68
- <TouchableOpacity
69
- key={style.id}
70
- style={[styles.card, isActive && styles.cardActive]}
71
- onPress={() => setSelected(style.id)}
72
- accessibilityLabel={style.label}
73
- accessibilityRole="button"
74
- accessibilityState={{ selected: isActive }}
75
- >
76
- <AtomicText style={{ fontSize: 24 }}>{emoji}</AtomicText>
77
- <View style={styles.info}>
78
- <AtomicText
79
- fontWeight="bold"
80
- color={isActive ? "primary" : "textPrimary"}
81
- >
82
- {words.join(" ")}
83
- </AtomicText>
84
- <AtomicText type="labelSmall" color="textSecondary">
85
- {style.desc}
86
- </AtomicText>
87
- </View>
88
- {isActive && (
89
- <AtomicIcon name="checkmark-circle" size="md" color="primary" />
90
- )}
91
- </TouchableOpacity>
92
- );
93
- })}
94
- </View>
95
- </ScrollView>
96
- <AtomicButton
97
- variant="primary"
98
- disabled={!selected || !onGenerateCaption || isGenerating}
99
- onPress={handleGenerate}
100
- loading={isGenerating}
101
- icon="sparkles"
102
- >
103
- Generate Caption
104
- </AtomicButton>
105
- </View>
106
- );
107
- };
@@ -1,108 +0,0 @@
1
- import React from "react";
2
- import { View, TouchableOpacity } from "react-native";
3
- import { AtomicText, AtomicIcon } from "@umituz/react-native-design-system/atoms";
4
- import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
5
- import { Slider } from "./Slider";
6
- import { ImageFilters, DEFAULT_IMAGE_FILTERS } from "../types";
7
-
8
- interface AdjustmentsSheetProps {
9
- filters: ImageFilters;
10
- onFiltersChange: (filters: ImageFilters) => void;
11
- }
12
-
13
- export const AdjustmentsSheet: React.FC<AdjustmentsSheetProps> = ({
14
- filters,
15
- onFiltersChange,
16
- }) => {
17
- const tokens = useAppDesignTokens();
18
-
19
- const update = (key: keyof ImageFilters, val: number) => {
20
- onFiltersChange({ ...filters, [key]: val });
21
- };
22
-
23
- const handleReset = () => onFiltersChange(DEFAULT_IMAGE_FILTERS);
24
-
25
- return (
26
- <View style={{ padding: tokens.spacing.md, gap: tokens.spacing.lg }}>
27
- <View
28
- style={{
29
- flexDirection: "row",
30
- alignItems: "center",
31
- justifyContent: "space-between",
32
- }}
33
- >
34
- <View style={{ flexDirection: "row", alignItems: "center", gap: tokens.spacing.sm }}>
35
- <AtomicIcon name="brush" size="md" color="primary" />
36
- <AtomicText type="headlineSmall">Adjustments</AtomicText>
37
- </View>
38
- <TouchableOpacity
39
- onPress={handleReset}
40
- accessibilityLabel="Reset adjustments"
41
- accessibilityRole="button"
42
- style={{
43
- paddingHorizontal: tokens.spacing.md,
44
- paddingVertical: tokens.spacing.xs,
45
- backgroundColor: tokens.colors.surfaceVariant,
46
- borderRadius: tokens.borders.radius.sm,
47
- }}
48
- >
49
- <AtomicText type="labelSmall" color="textSecondary">
50
- Reset
51
- </AtomicText>
52
- </TouchableOpacity>
53
- </View>
54
-
55
- <Slider
56
- label="Brightness"
57
- value={filters.brightness}
58
- min={0.5}
59
- max={2}
60
- step={0.05}
61
- onValueChange={(v) => update("brightness", v)}
62
- formatValue={(v) => `${Math.round((v - 1) * 100) >= 0 ? "+" : ""}${Math.round((v - 1) * 100)}%`}
63
- />
64
-
65
- <Slider
66
- label="Contrast"
67
- value={filters.contrast}
68
- min={0.5}
69
- max={2}
70
- step={0.05}
71
- onValueChange={(v) => update("contrast", v)}
72
- formatValue={(v) => `${Math.round((v - 1) * 100) >= 0 ? "+" : ""}${Math.round((v - 1) * 100)}%`}
73
- />
74
-
75
- <Slider
76
- label="Saturation"
77
- value={filters.saturation}
78
- min={0}
79
- max={2}
80
- step={0.05}
81
- onValueChange={(v) => update("saturation", v)}
82
- formatValue={(v) => `${Math.round((v - 1) * 100) >= 0 ? "+" : ""}${Math.round((v - 1) * 100)}%`}
83
- />
84
-
85
- <Slider
86
- label="Hue Rotate"
87
- value={filters.hueRotate ?? 0}
88
- min={0}
89
- max={360}
90
- step={1}
91
- onValueChange={(v) => update("hueRotate", v)}
92
- formatValue={(v) => `${Math.round(v)}°`}
93
- />
94
-
95
- <Slider
96
- label="Sepia"
97
- value={filters.sepia}
98
- min={0}
99
- max={1}
100
- step={0.05}
101
- onValueChange={(v) => update("sepia", v)}
102
- formatValue={(v) => `${Math.round(v * 100)}%`}
103
- />
104
- </View>
105
- );
106
- };
107
-
108
- export default React.memo(AdjustmentsSheet);
@@ -1,77 +0,0 @@
1
- import React from "react";
2
- import { View, TouchableOpacity } from "react-native";
3
- import { AtomicText } from "@umituz/react-native-design-system/atoms";
4
- import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
5
- import { DEFAULT_TEXT_COLORS } from "../constants";
6
-
7
- interface ColorPickerProps {
8
- selectedColor: string;
9
- onSelectColor: (color: string) => void;
10
- label?: string;
11
- colors?: readonly string[];
12
- }
13
-
14
- export const ColorPicker: React.FC<ColorPickerProps> = ({
15
- selectedColor,
16
- onSelectColor,
17
- label,
18
- colors = DEFAULT_TEXT_COLORS,
19
- }) => {
20
- const tokens = useAppDesignTokens();
21
-
22
- return (
23
- <View style={{ gap: tokens.spacing.xs }}>
24
- {label && (
25
- <AtomicText type="labelMedium" color="textSecondary">
26
- {label}
27
- </AtomicText>
28
- )}
29
- <View
30
- style={{
31
- flexDirection: "row",
32
- flexWrap: "wrap",
33
- gap: tokens.spacing.xs,
34
- }}
35
- >
36
- {colors.map((color) => {
37
- const isSelected = selectedColor === color;
38
- return (
39
- <TouchableOpacity
40
- key={color}
41
- onPress={() => onSelectColor(color)}
42
- accessibilityLabel={`Color ${color}`}
43
- accessibilityRole="button"
44
- accessibilityState={{ selected: isSelected }}
45
- style={{
46
- width: 34,
47
- height: 34,
48
- borderRadius: 17,
49
- backgroundColor: color,
50
- borderWidth: isSelected ? 3 : 1.5,
51
- borderColor: isSelected
52
- ? tokens.colors.primary
53
- : tokens.colors.border,
54
- alignItems: "center",
55
- justifyContent: "center",
56
- }}
57
- >
58
- {isSelected && (
59
- <View
60
- style={{
61
- width: 10,
62
- height: 10,
63
- borderRadius: 5,
64
- backgroundColor:
65
- color === "#FFFFFF" ? "#000000" : "#FFFFFF",
66
- }}
67
- />
68
- )}
69
- </TouchableOpacity>
70
- );
71
- })}
72
- </View>
73
- </View>
74
- );
75
- };
76
-
77
- export default React.memo(ColorPicker);
@@ -1,161 +0,0 @@
1
- import React, { useState, useRef, useCallback, useEffect } from "react";
2
- import { View, StyleSheet } from "react-native";
3
- import { Gesture, GestureDetector } from "react-native-gesture-handler";
4
- import { Image } from "expo-image";
5
- import { AtomicText } from "@umituz/react-native-design-system/atoms";
6
- import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
7
- import type { LayerTransform } from "./DraggableText";
8
-
9
- interface DraggableStickerProps {
10
- uri: string;
11
- initialX: number;
12
- initialY: number;
13
- rotation?: number;
14
- scale?: number;
15
- opacity?: number;
16
- onTransformEnd: (transform: LayerTransform) => void;
17
- onPress: () => void;
18
- isSelected?: boolean;
19
- }
20
-
21
- const isEmojiString = (str: string) =>
22
- str.length <= 4 && !/^https?:\/\//i.test(str) && !str.startsWith("/");
23
-
24
- export const DraggableSticker: React.FC<DraggableStickerProps> = ({
25
- uri,
26
- initialX,
27
- initialY,
28
- rotation: rotationProp = 0,
29
- scale: scaleProp = 1,
30
- opacity = 1,
31
- onTransformEnd,
32
- onPress,
33
- isSelected,
34
- }) => {
35
- const tokens = useAppDesignTokens();
36
- const [position, setPosition] = useState({ x: initialX, y: initialY });
37
- const [scale, setScale] = useState(scaleProp);
38
- const [rotation, setRotation] = useState(rotationProp);
39
-
40
- // Sync when props change (e.g., undo/redo)
41
- useEffect(() => { setPosition({ x: initialX, y: initialY }); }, [initialX, initialY]);
42
- useEffect(() => { setScale(scaleProp); }, [scaleProp]);
43
- useEffect(() => { setRotation(rotationProp); }, [rotationProp]);
44
-
45
- const positionRef = useRef(position);
46
- positionRef.current = position;
47
- const scaleRef = useRef(scale);
48
- scaleRef.current = scale;
49
- const rotationRef = useRef(rotation);
50
- rotationRef.current = rotation;
51
- const onTransformEndRef = useRef(onTransformEnd);
52
- onTransformEndRef.current = onTransformEnd;
53
- const onPressRef = useRef(onPress);
54
- onPressRef.current = onPress;
55
-
56
- const offsetRef = useRef({ x: initialX, y: initialY });
57
- const scaleStartRef = useRef(scaleProp);
58
- const rotationStartRef = useRef(rotationProp);
59
-
60
- const emitTransform = useCallback(() => {
61
- onTransformEndRef.current({
62
- x: positionRef.current.x,
63
- y: positionRef.current.y,
64
- scale: scaleRef.current,
65
- rotation: rotationRef.current,
66
- });
67
- }, []);
68
-
69
- const panGesture = Gesture.Pan()
70
- .runOnJS(true)
71
- .averageTouches(true)
72
- .onStart(() => {
73
- offsetRef.current = { x: positionRef.current.x, y: positionRef.current.y };
74
- })
75
- .onUpdate((e) => {
76
- setPosition({
77
- x: offsetRef.current.x + e.translationX,
78
- y: offsetRef.current.y + e.translationY,
79
- });
80
- })
81
- .onEnd(emitTransform);
82
-
83
- const pinchGesture = Gesture.Pinch()
84
- .runOnJS(true)
85
- .onStart(() => {
86
- scaleStartRef.current = scaleRef.current;
87
- })
88
- .onUpdate((e) => {
89
- setScale(Math.max(0.2, Math.min(6, scaleStartRef.current * e.scale)));
90
- })
91
- .onEnd(emitTransform);
92
-
93
- const rotationGesture = Gesture.Rotation()
94
- .runOnJS(true)
95
- .onStart(() => {
96
- rotationStartRef.current = rotationRef.current;
97
- })
98
- .onUpdate((e) => {
99
- setRotation(rotationStartRef.current + (e.rotation * 180) / Math.PI);
100
- })
101
- .onEnd(emitTransform);
102
-
103
- const tapGesture = Gesture.Tap()
104
- .runOnJS(true)
105
- .onEnd(() => onPressRef.current());
106
-
107
- const composed = Gesture.Exclusive(
108
- Gesture.Simultaneous(panGesture, pinchGesture, rotationGesture),
109
- tapGesture,
110
- );
111
-
112
- const isEmoji = isEmojiString(uri);
113
-
114
- return (
115
- <GestureDetector gesture={composed}>
116
- <View
117
- accessibilityLabel={isEmoji ? `Sticker ${uri}` : "Image sticker"}
118
- accessibilityRole="button"
119
- style={[
120
- styles.container,
121
- {
122
- transform: [
123
- { translateX: position.x },
124
- { translateY: position.y },
125
- { rotate: `${rotation}deg` },
126
- { scale },
127
- ],
128
- opacity,
129
- zIndex: isSelected ? 100 : 50,
130
- },
131
- ]}
132
- >
133
- <View
134
- style={{
135
- padding: tokens.spacing.xs,
136
- borderRadius: tokens.borders.radius.sm,
137
- borderWidth: isSelected ? 2 : 0,
138
- borderColor: tokens.colors.primary,
139
- borderStyle: "dashed",
140
- backgroundColor: isSelected ? tokens.colors.primary + "10" : "transparent",
141
- }}
142
- >
143
- {isEmoji ? (
144
- <AtomicText style={{ fontSize: 48 }}>{uri}</AtomicText>
145
- ) : (
146
- <Image
147
- source={{ uri }}
148
- style={{ width: 80, height: 80 }}
149
- contentFit="contain"
150
- accessibilityIgnoresInvertColors
151
- />
152
- )}
153
- </View>
154
- </View>
155
- </GestureDetector>
156
- );
157
- };
158
-
159
- const styles = StyleSheet.create({
160
- container: { position: "absolute" },
161
- });
@@ -1,181 +0,0 @@
1
- import React, { useState, useRef, useCallback, useEffect } from "react";
2
- import { View, StyleSheet } from "react-native";
3
- import { Gesture, GestureDetector } from "react-native-gesture-handler";
4
- import { AtomicText } from "@umituz/react-native-design-system/atoms";
5
- import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
6
-
7
- export interface LayerTransform {
8
- x: number;
9
- y: number;
10
- scale: number;
11
- rotation: number;
12
- }
13
-
14
- interface DraggableTextProps {
15
- text: string;
16
- color: string;
17
- fontSize?: number;
18
- fontFamily?: string;
19
- initialX: number;
20
- initialY: number;
21
- rotation?: number;
22
- scale?: number;
23
- opacity?: number;
24
- textAlign?: "center" | "left" | "right";
25
- backgroundColor?: string;
26
- isBold?: boolean;
27
- isItalic?: boolean;
28
- onTransformEnd: (transform: LayerTransform) => void;
29
- onPress: () => void;
30
- isSelected?: boolean;
31
- }
32
-
33
- export const DraggableText: React.FC<DraggableTextProps> = ({
34
- text,
35
- color,
36
- fontSize = 24,
37
- fontFamily = "System",
38
- initialX,
39
- initialY,
40
- rotation: rotationProp = 0,
41
- scale: scaleProp = 1,
42
- opacity = 1,
43
- textAlign = "center",
44
- backgroundColor = "transparent",
45
- isBold = false,
46
- isItalic = false,
47
- onTransformEnd,
48
- onPress,
49
- isSelected,
50
- }) => {
51
- const tokens = useAppDesignTokens();
52
- const [position, setPosition] = useState({ x: initialX, y: initialY });
53
- const [scale, setScale] = useState(scaleProp);
54
- const [rotation, setRotation] = useState(rotationProp); // degrees
55
-
56
- // Sync when props change (e.g., undo/redo)
57
- useEffect(() => { setPosition({ x: initialX, y: initialY }); }, [initialX, initialY]);
58
- useEffect(() => { setScale(scaleProp); }, [scaleProp]);
59
- useEffect(() => { setRotation(rotationProp); }, [rotationProp]);
60
-
61
- // Refs for gesture callbacks to avoid stale closures
62
- const positionRef = useRef(position);
63
- positionRef.current = position;
64
- const scaleRef = useRef(scale);
65
- scaleRef.current = scale;
66
- const rotationRef = useRef(rotation);
67
- rotationRef.current = rotation;
68
- const onTransformEndRef = useRef(onTransformEnd);
69
- onTransformEndRef.current = onTransformEnd;
70
- const onPressRef = useRef(onPress);
71
- onPressRef.current = onPress;
72
-
73
- // Start-of-gesture saved values
74
- const offsetRef = useRef({ x: initialX, y: initialY });
75
- const scaleStartRef = useRef(scale);
76
- const rotationStartRef = useRef(rotation); // degrees
77
-
78
- const emitTransform = useCallback(() => {
79
- onTransformEndRef.current({
80
- x: positionRef.current.x,
81
- y: positionRef.current.y,
82
- scale: scaleRef.current,
83
- rotation: rotationRef.current,
84
- });
85
- }, []);
86
-
87
- const panGesture = Gesture.Pan()
88
- .runOnJS(true)
89
- .averageTouches(true)
90
- .onStart(() => {
91
- offsetRef.current = { x: positionRef.current.x, y: positionRef.current.y };
92
- })
93
- .onUpdate((e) => {
94
- setPosition({
95
- x: offsetRef.current.x + e.translationX,
96
- y: offsetRef.current.y + e.translationY,
97
- });
98
- })
99
- .onEnd(emitTransform);
100
-
101
- const pinchGesture = Gesture.Pinch()
102
- .runOnJS(true)
103
- .onStart(() => {
104
- scaleStartRef.current = scaleRef.current;
105
- })
106
- .onUpdate((e) => {
107
- setScale(Math.max(0.2, Math.min(6, scaleStartRef.current * e.scale)));
108
- })
109
- .onEnd(emitTransform);
110
-
111
- const rotationGesture = Gesture.Rotation()
112
- .runOnJS(true)
113
- .onStart(() => {
114
- rotationStartRef.current = rotationRef.current;
115
- })
116
- .onUpdate((e) => {
117
- setRotation(rotationStartRef.current + (e.rotation * 180) / Math.PI);
118
- })
119
- .onEnd(emitTransform);
120
-
121
- const tapGesture = Gesture.Tap()
122
- .runOnJS(true)
123
- .onEnd(() => onPressRef.current());
124
-
125
- const composed = Gesture.Exclusive(
126
- Gesture.Simultaneous(panGesture, pinchGesture, rotationGesture),
127
- tapGesture,
128
- );
129
-
130
- return (
131
- <GestureDetector gesture={composed}>
132
- <View
133
- accessibilityLabel={text || "Text layer"}
134
- accessibilityRole="button"
135
- style={[
136
- styles.container,
137
- {
138
- transform: [
139
- { translateX: position.x },
140
- { translateY: position.y },
141
- { rotate: `${rotation}deg` },
142
- { scale },
143
- ],
144
- opacity,
145
- zIndex: isSelected ? 100 : 10,
146
- },
147
- ]}
148
- >
149
- <View
150
- style={{
151
- padding: tokens.spacing.xs,
152
- borderRadius: tokens.borders.radius.sm,
153
- borderWidth: isSelected ? 2 : 0,
154
- borderColor: tokens.colors.primary,
155
- borderStyle: "dashed",
156
- backgroundColor: isSelected
157
- ? tokens.colors.primary + "10"
158
- : backgroundColor,
159
- }}
160
- >
161
- <AtomicText
162
- style={{
163
- fontSize,
164
- fontFamily: fontFamily === "System" ? undefined : fontFamily,
165
- color,
166
- textAlign,
167
- fontWeight: isBold ? "900" : "normal",
168
- fontStyle: isItalic ? "italic" : "normal",
169
- }}
170
- >
171
- {text || "TAP TO EDIT"}
172
- </AtomicText>
173
- </View>
174
- </View>
175
- </GestureDetector>
176
- );
177
- };
178
-
179
- const styles = StyleSheet.create({
180
- container: { position: "absolute" },
181
- });