@umituz/react-native-photo-editor 2.0.1 → 2.0.3

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": "2.0.1",
3
+ "version": "2.0.3",
4
4
  "description": "A powerful, generic photo editor for React Native",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -1,4 +1,4 @@
1
- import React, { useState } from "react";
1
+ import React, { useState, useMemo } from "react";
2
2
  import { View, ScrollView, TouchableOpacity, StyleSheet } from "react-native";
3
3
  import { AtomicText, AtomicIcon, AtomicButton } from "@umituz/react-native-design-system/atoms";
4
4
  import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
@@ -21,7 +21,7 @@ export const AIMagicSheet: React.FC<AIMagicSheetProps> = ({
21
21
  const [selected, setSelected] = useState<string | null>(null);
22
22
  const [loading, setLoading] = useState(false);
23
23
 
24
- const styles = StyleSheet.create({
24
+ const styles = useMemo(() => StyleSheet.create({
25
25
  container: { padding: tokens.spacing.md, gap: tokens.spacing.md },
26
26
  header: { flexDirection: "row", alignItems: "center", gap: tokens.spacing.sm },
27
27
  grid: { gap: tokens.spacing.sm },
@@ -39,7 +39,7 @@ export const AIMagicSheet: React.FC<AIMagicSheetProps> = ({
39
39
  backgroundColor: tokens.colors.primary + "10",
40
40
  },
41
41
  info: { flex: 1, marginLeft: tokens.spacing.sm },
42
- });
42
+ }), [tokens]);
43
43
 
44
44
  const handleGenerate = async () => {
45
45
  if (!selected || !onGenerateCaption) return;
@@ -3,7 +3,7 @@ import { View, StyleSheet } from "react-native";
3
3
  import { Image } from "expo-image";
4
4
  import { DraggableText, LayerTransform } from "./DraggableText";
5
5
  import { DraggableSticker } from "./DraggableSticker";
6
- import { Layer, TextLayer, StickerLayer, ImageFilters } from "../types";
6
+ import { Layer, ImageFilters } from "../types";
7
7
 
8
8
  interface EditorCanvasProps {
9
9
  imageUrl: string;
@@ -60,39 +60,37 @@ export const EditorCanvas: React.FC<EditorCanvasProps> = ({
60
60
 
61
61
  {layers.map((layer) => {
62
62
  if (layer.type === "text") {
63
- const textLayer = layer as TextLayer;
64
63
  return (
65
64
  <DraggableText
66
65
  key={layer.id}
67
- text={textLayer.text || "Tap to edit"}
68
- color={textLayer.color}
69
- fontSize={textLayer.fontSize}
70
- fontFamily={textLayer.fontFamily}
71
- textAlign={textLayer.textAlign}
72
- rotation={textLayer.rotation}
73
- scale={textLayer.scale}
74
- opacity={textLayer.opacity}
75
- backgroundColor={textLayer.backgroundColor}
76
- isBold={textLayer.isBold}
77
- isItalic={textLayer.isItalic}
78
- initialX={textLayer.x}
79
- initialY={textLayer.y}
66
+ text={layer.text || "Tap to edit"}
67
+ color={layer.color}
68
+ fontSize={layer.fontSize}
69
+ fontFamily={layer.fontFamily}
70
+ textAlign={layer.textAlign}
71
+ rotation={layer.rotation}
72
+ scale={layer.scale}
73
+ opacity={layer.opacity}
74
+ backgroundColor={layer.backgroundColor}
75
+ isBold={layer.isBold}
76
+ isItalic={layer.isItalic}
77
+ initialX={layer.x}
78
+ initialY={layer.y}
80
79
  onTransformEnd={(t) => onLayerTransform(layer.id, t)}
81
80
  onPress={() => onLayerTap(layer.id)}
82
81
  isSelected={activeLayerId === layer.id}
83
82
  />
84
83
  );
85
84
  } else if (layer.type === "sticker") {
86
- const stickerLayer = layer as StickerLayer;
87
85
  return (
88
86
  <DraggableSticker
89
87
  key={layer.id}
90
- uri={stickerLayer.uri}
91
- initialX={stickerLayer.x}
92
- initialY={stickerLayer.y}
93
- rotation={stickerLayer.rotation}
94
- scale={stickerLayer.scale}
95
- opacity={stickerLayer.opacity}
88
+ uri={layer.uri}
89
+ initialX={layer.x}
90
+ initialY={layer.y}
91
+ rotation={layer.rotation}
92
+ scale={layer.scale}
93
+ opacity={layer.opacity}
96
94
  onTransformEnd={(t) => onLayerTransform(layer.id, t)}
97
95
  onPress={() => onLayerTap(layer.id)}
98
96
  isSelected={activeLayerId === layer.id}
@@ -30,7 +30,6 @@ const ToolButton = ({
30
30
  onPress,
31
31
  isActive,
32
32
  disabled,
33
- tokens,
34
33
  parentStyles,
35
34
  }: {
36
35
  icon: string;
@@ -38,7 +37,6 @@ const ToolButton = ({
38
37
  onPress: () => void;
39
38
  isActive?: boolean;
40
39
  disabled?: boolean;
41
- tokens: ReturnType<typeof useAppDesignTokens>;
42
40
  parentStyles: EditorToolbarProps["styles"];
43
41
  }) => (
44
42
  <TouchableOpacity
@@ -84,28 +82,25 @@ export const EditorToolbar: React.FC<EditorToolbarProps> = ({
84
82
  {onUndo && (
85
83
  <ToolButton
86
84
  icon="arrow-back"
87
- label={t("editor.undo") || "Undo"}
85
+ label={t("photo_editor.undo") || "Undo"}
88
86
  onPress={onUndo}
89
87
  disabled={!canUndo}
90
- tokens={tokens}
91
88
  parentStyles={parentStyles}
92
89
  />
93
90
  )}
94
91
 
95
92
  <ToolButton
96
93
  icon="edit"
97
- label={t("editor.text") || "Text"}
94
+ label={t("photo_editor.text") || "Text"}
98
95
  onPress={onAddText}
99
- tokens={tokens}
100
96
  parentStyles={parentStyles}
101
97
  />
102
98
 
103
99
  {onAddSticker && (
104
100
  <ToolButton
105
101
  icon="sparkles"
106
- label={t("editor.sticker") || "Sticker"}
102
+ label={t("photo_editor.sticker") || "Sticker"}
107
103
  onPress={onAddSticker}
108
- tokens={tokens}
109
104
  parentStyles={parentStyles}
110
105
  />
111
106
  )}
@@ -124,9 +119,8 @@ export const EditorToolbar: React.FC<EditorToolbarProps> = ({
124
119
  {onOpenAdjustments && (
125
120
  <ToolButton
126
121
  icon="flash"
127
- label={t("editor.adjust") || "Adjust"}
122
+ label={t("photo_editor.adjust") || "Adjust"}
128
123
  onPress={onOpenAdjustments}
129
- tokens={tokens}
130
124
  parentStyles={parentStyles}
131
125
  />
132
126
  )}
@@ -134,28 +128,25 @@ export const EditorToolbar: React.FC<EditorToolbarProps> = ({
134
128
  {onOpenFilters && (
135
129
  <ToolButton
136
130
  icon="brush"
137
- label={t("editor.filters") || "Filters"}
131
+ label={t("photo_editor.filters") || "Filters"}
138
132
  onPress={onOpenFilters}
139
- tokens={tokens}
140
133
  parentStyles={parentStyles}
141
134
  />
142
135
  )}
143
136
 
144
137
  <ToolButton
145
138
  icon="copy"
146
- label={t("editor.layers") || "Layers"}
139
+ label={t("photo_editor.layers") || "Layers"}
147
140
  onPress={onOpenLayers}
148
- tokens={tokens}
149
141
  parentStyles={parentStyles}
150
142
  />
151
143
 
152
144
  {onRedo && (
153
145
  <ToolButton
154
146
  icon="chevron-forward"
155
- label={t("editor.redo") || "Redo"}
147
+ label={t("photo_editor.redo") || "Redo"}
156
148
  onPress={onRedo}
157
149
  disabled={!canRedo}
158
- tokens={tokens}
159
150
  parentStyles={parentStyles}
160
151
  />
161
152
  )}
@@ -1,4 +1,4 @@
1
- import React from "react";
1
+ import React, { useMemo } from "react";
2
2
  import { View, TouchableOpacity, StyleSheet } from "react-native";
3
3
  import { AtomicText, AtomicIcon } from "@umituz/react-native-design-system/atoms";
4
4
  import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
@@ -17,7 +17,7 @@ export const FilterPicker: React.FC<FilterPickerProps> = ({
17
17
  }) => {
18
18
  const tokens = useAppDesignTokens();
19
19
 
20
- const styles = StyleSheet.create({
20
+ const styles = useMemo(() => StyleSheet.create({
21
21
  container: { padding: tokens.spacing.md, gap: tokens.spacing.md },
22
22
  grid: { flexDirection: "row", flexWrap: "wrap", gap: tokens.spacing.sm },
23
23
  filter: {
@@ -34,7 +34,7 @@ export const FilterPicker: React.FC<FilterPickerProps> = ({
34
34
  borderColor: tokens.colors.primary,
35
35
  backgroundColor: tokens.colors.primary + "10",
36
36
  },
37
- });
37
+ }), [tokens]);
38
38
 
39
39
  return (
40
40
  <View style={styles.container}>
@@ -1,4 +1,4 @@
1
- import React from "react";
1
+ import React, { useMemo } from "react";
2
2
  import { View, ScrollView, TouchableOpacity, StyleSheet } from "react-native";
3
3
  import { AtomicText, AtomicIcon } from "@umituz/react-native-design-system/atoms";
4
4
  import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
@@ -32,7 +32,7 @@ export const FontControls: React.FC<FontControlsProps> = ({
32
32
  }) => {
33
33
  const tokens = useAppDesignTokens();
34
34
 
35
- const styles = StyleSheet.create({
35
+ const styles = useMemo(() => StyleSheet.create({
36
36
  stepRow: {
37
37
  flexDirection: "row",
38
38
  gap: tokens.spacing.sm,
@@ -58,7 +58,7 @@ export const FontControls: React.FC<FontControlsProps> = ({
58
58
  alignItems: "center",
59
59
  gap: tokens.spacing.xs,
60
60
  },
61
- });
61
+ }), [tokens]);
62
62
 
63
63
  return (
64
64
  <View style={externalStyles.controlsPanel}>
@@ -1,4 +1,4 @@
1
- import React from "react";
1
+ import React, { useMemo } from "react";
2
2
  import { View, ScrollView, TouchableOpacity, StyleSheet } from "react-native";
3
3
  import { AtomicText, AtomicIcon } from "@umituz/react-native-design-system/atoms";
4
4
  import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
@@ -27,7 +27,7 @@ export const LayerManager: React.FC<LayerManagerProps> = ({
27
27
  }) => {
28
28
  const tokens = useAppDesignTokens();
29
29
 
30
- const styles = StyleSheet.create({
30
+ const styles = useMemo(() => StyleSheet.create({
31
31
  container: { padding: tokens.spacing.md, gap: tokens.spacing.md },
32
32
  item: {
33
33
  flexDirection: "row",
@@ -53,7 +53,7 @@ export const LayerManager: React.FC<LayerManagerProps> = ({
53
53
  padding: tokens.spacing.xs,
54
54
  borderRadius: tokens.borders.radius.sm,
55
55
  },
56
- });
56
+ }), [tokens]);
57
57
 
58
58
  const sortedLayers = [...layers].reverse(); // top layer first in list
59
59
 
@@ -73,7 +73,7 @@ export const LayerManager: React.FC<LayerManagerProps> = ({
73
73
  const isActive = activeLayerId === layer.id;
74
74
  const label =
75
75
  layer.type === "text"
76
- ? (layer as TextLayer).text || t("editor.untitled") || "Untitled"
76
+ ? (layer as TextLayer).text || t("photo_editor.untitled") || "Untitled"
77
77
  : "Sticker";
78
78
  const isTop = idx === 0;
79
79
  const isBottom = idx === sortedLayers.length - 1;
@@ -1,4 +1,4 @@
1
- import React from "react";
1
+ import React, { useMemo } from "react";
2
2
  import { View, ScrollView, TouchableOpacity, StyleSheet } from "react-native";
3
3
  import { AtomicText } from "@umituz/react-native-design-system/atoms";
4
4
  import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
@@ -15,7 +15,7 @@ export const StickerPicker: React.FC<StickerPickerProps> = ({
15
15
  }) => {
16
16
  const tokens = useAppDesignTokens();
17
17
 
18
- const styles = StyleSheet.create({
18
+ const styles = useMemo(() => StyleSheet.create({
19
19
  container: { padding: tokens.spacing.md, gap: tokens.spacing.md },
20
20
  grid: { flexDirection: "row", flexWrap: "wrap", gap: tokens.spacing.sm },
21
21
  sticker: {
@@ -26,7 +26,7 @@ export const StickerPicker: React.FC<StickerPickerProps> = ({
26
26
  alignItems: "center",
27
27
  justifyContent: "center",
28
28
  },
29
- });
29
+ }), [tokens]);
30
30
 
31
31
  return (
32
32
  <View style={styles.container}>
@@ -1,4 +1,4 @@
1
- import React from "react";
1
+ import React, { useMemo } from "react";
2
2
  import { View, TextInput, TouchableOpacity, StyleSheet } from "react-native";
3
3
  import { AtomicText, AtomicButton } from "@umituz/react-native-design-system/atoms";
4
4
  import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
@@ -42,7 +42,7 @@ export const TextEditorSheet: React.FC<TextEditorSheetProps> = ({
42
42
  }) => {
43
43
  const tokens = useAppDesignTokens();
44
44
 
45
- const styles = StyleSheet.create({
45
+ const styles = useMemo(() => StyleSheet.create({
46
46
  container: { padding: tokens.spacing.md, gap: tokens.spacing.md },
47
47
  input: {
48
48
  backgroundColor: tokens.colors.surfaceVariant,
@@ -72,16 +72,16 @@ export const TextEditorSheet: React.FC<TextEditorSheetProps> = ({
72
72
  borderColor: tokens.colors.primary,
73
73
  backgroundColor: tokens.colors.primary + "20",
74
74
  },
75
- });
75
+ }), [tokens]);
76
76
 
77
77
  return (
78
78
  <View style={styles.container}>
79
- <AtomicText type="headlineSmall">{t("editor.add_text") || "Edit Text"}</AtomicText>
79
+ <AtomicText type="headlineSmall">{t("photo_editor.add_text") || "Edit Text"}</AtomicText>
80
80
 
81
81
  <TextInput
82
82
  value={value}
83
83
  onChangeText={onChange}
84
- placeholder={t("editor.tap_to_edit") || "Enter text…"}
84
+ placeholder={t("photo_editor.tap_to_edit") || "Enter text…"}
85
85
  placeholderTextColor={tokens.colors.textSecondary}
86
86
  style={styles.input}
87
87
  multiline
@@ -1,4 +1,4 @@
1
- import { useState } from "react";
1
+ import { useState, useCallback } from "react";
2
2
  import * as ImagePicker from "expo-image-picker";
3
3
 
4
4
  export interface ImagePickerResult {
@@ -16,7 +16,7 @@ export interface UseImagePickerReturn {
16
16
  export const useImagePicker = (): UseImagePickerReturn => {
17
17
  const [loading, setLoading] = useState(false);
18
18
 
19
- const pickFromGallery = async (
19
+ const pickFromGallery = useCallback(async (
20
20
  options?: ImagePicker.ImagePickerOptions,
21
21
  ): Promise<ImagePickerResult | null> => {
22
22
  setLoading(true);
@@ -39,9 +39,9 @@ export const useImagePicker = (): UseImagePickerReturn => {
39
39
  } finally {
40
40
  setLoading(false);
41
41
  }
42
- };
42
+ }, []);
43
43
 
44
- const takePhoto = async (
44
+ const takePhoto = useCallback(async (
45
45
  options?: ImagePicker.ImagePickerOptions,
46
46
  ): Promise<ImagePickerResult | null> => {
47
47
  setLoading(true);
@@ -63,7 +63,7 @@ export const useImagePicker = (): UseImagePickerReturn => {
63
63
  } finally {
64
64
  setLoading(false);
65
65
  }
66
- };
66
+ }, []);
67
67
 
68
68
  return { pickFromGallery, takePhoto, loading };
69
69
  };
@@ -40,7 +40,6 @@ export const usePhotoEditorUI = (initialCaption?: string) => {
40
40
  initialCaptionApplied.current = true;
41
41
  editor.addTextLayer(tokens.colors.textPrimary, { text: initialCaption });
42
42
  }
43
- // eslint-disable-next-line react-hooks/exhaustive-deps
44
43
  }, []);
45
44
 
46
45
  const handleTextLayerTap = useCallback(
@@ -152,12 +151,10 @@ export const usePhotoEditorUI = (initialCaption?: string) => {
152
151
  fontFamily: selectedFont,
153
152
  });
154
153
  textEditorSheetRef.current?.present();
155
- // eslint-disable-next-line react-hooks/exhaustive-deps
156
154
  }, [editor, fontSize, selectedFont, tokens.colors.textPrimary]),
157
155
  handleSelectSticker: useCallback((uri: string) => {
158
156
  editor.addStickerLayer(uri);
159
157
  stickerSheetRef.current?.dismiss();
160
- // eslint-disable-next-line react-hooks/exhaustive-deps
161
158
  }, [editor]),
162
159
  };
163
160
  };