@umituz/react-native-photo-editor 1.0.1

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.
@@ -0,0 +1,54 @@
1
+ import React from "react";
2
+ import { View, TouchableOpacity } from "react-native";
3
+ import { AtomicText, AtomicIcon } from "@umituz/react-native-design-system";
4
+
5
+ interface EditorToolbarProps {
6
+ onAddText: () => void;
7
+ onAddSticker: () => void;
8
+ onAIMagic?: () => void; // Optional AI integration
9
+ onOpenFilters: () => void;
10
+ onOpenLayers: () => void;
11
+ styles: Record<string, object>;
12
+ t: (key: string) => string;
13
+ }
14
+
15
+ export const EditorToolbar: React.FC<EditorToolbarProps> = ({
16
+ onAddText,
17
+ onAddSticker,
18
+ onAIMagic,
19
+ onOpenFilters,
20
+ onOpenLayers,
21
+ styles,
22
+ t,
23
+ }) => {
24
+ return (
25
+ <View style={styles.bottomToolbar}>
26
+ <TouchableOpacity
27
+ style={[styles.toolButton, styles.toolButtonActive]}
28
+ onPress={onAddText}
29
+ >
30
+ <AtomicIcon name="text" size="md" color="primary" />
31
+ <AtomicText style={[styles.toolLabel, styles.toolLabelActive]}>
32
+ Text
33
+ </AtomicText>
34
+ </TouchableOpacity>
35
+ <TouchableOpacity style={styles.toolButton} onPress={onAddSticker}>
36
+ <AtomicIcon name="happy" size="md" color="textSecondary" />
37
+ <AtomicText style={styles.toolLabel}>{t("editor.sticker")}</AtomicText>
38
+ </TouchableOpacity>
39
+ {onAIMagic && (
40
+ <TouchableOpacity style={styles.aiMagicButton} onPress={onAIMagic}>
41
+ <AtomicIcon name="sparkles" size="lg" customColor="#fff" />
42
+ </TouchableOpacity>
43
+ )}
44
+ <TouchableOpacity style={styles.toolButton} onPress={onOpenFilters}>
45
+ <AtomicIcon name="color-filter" size="md" color="textSecondary" />
46
+ <AtomicText style={styles.toolLabel}>{t("editor.filters")}</AtomicText>
47
+ </TouchableOpacity>
48
+ <TouchableOpacity style={styles.toolButton} onPress={onOpenLayers}>
49
+ <AtomicIcon name="layers" size="md" color="textSecondary" />
50
+ <AtomicText style={styles.toolLabel}>Layer</AtomicText>
51
+ </TouchableOpacity>
52
+ </View>
53
+ );
54
+ };
@@ -0,0 +1,102 @@
1
+ import React from "react";
2
+ import { View, TouchableOpacity, StyleSheet } from "react-native";
3
+ import {
4
+ AtomicText,
5
+ AtomicIcon,
6
+ useAppDesignTokens,
7
+ } from "@umituz/react-native-design-system";
8
+
9
+ interface FilterOption {
10
+ id: string;
11
+ name: string;
12
+ icon: string;
13
+ value: number;
14
+ }
15
+
16
+ const FILTERS: FilterOption[] = [
17
+ { id: "none", name: "None", icon: "close-circle", value: 0 },
18
+ { id: "sepia", name: "Sepia", icon: "color-palette", value: 0.5 },
19
+ { id: "grayscale", name: "B&W", icon: "contrast", value: 1 },
20
+ { id: "vintage", name: "Vintage", icon: "time", value: 0.7 },
21
+ { id: "warm", name: "Warm", icon: "sunny", value: 0.3 },
22
+ { id: "cool", name: "Cool", icon: "snow", value: 0.3 },
23
+ ];
24
+
25
+ interface FilterPickerProps {
26
+ selectedFilter: string;
27
+ onSelectFilter: (filterId: string, value: number) => void;
28
+ }
29
+
30
+ export const FilterPicker: React.FC<FilterPickerProps> = ({
31
+ selectedFilter,
32
+ onSelectFilter,
33
+ }) => {
34
+ const tokens = useAppDesignTokens();
35
+
36
+ const styles = StyleSheet.create({
37
+ container: { padding: 16 },
38
+ title: {
39
+ fontSize: 18,
40
+ fontWeight: "bold",
41
+ color: tokens.colors.textPrimary,
42
+ marginBottom: 16,
43
+ },
44
+ grid: {
45
+ flexDirection: "row",
46
+ flexWrap: "wrap",
47
+ gap: 12,
48
+ },
49
+ filter: {
50
+ width: 80,
51
+ height: 80,
52
+ borderRadius: 12,
53
+ backgroundColor: tokens.colors.surfaceVariant,
54
+ alignItems: "center",
55
+ justifyContent: "center",
56
+ borderWidth: 2,
57
+ borderColor: "transparent",
58
+ },
59
+ filterActive: {
60
+ borderColor: tokens.colors.primary,
61
+ backgroundColor: tokens.colors.primaryContainer,
62
+ },
63
+ filterName: {
64
+ marginTop: 4,
65
+ fontSize: 12,
66
+ color: tokens.colors.textSecondary,
67
+ },
68
+ filterNameActive: { color: tokens.colors.primary },
69
+ });
70
+
71
+ return (
72
+ <View style={styles.container}>
73
+ <AtomicText style={styles.title}>Filters</AtomicText>
74
+ <View style={styles.grid}>
75
+ {FILTERS.map((filter) => (
76
+ <TouchableOpacity
77
+ key={filter.id}
78
+ style={[
79
+ styles.filter,
80
+ selectedFilter === filter.id && styles.filterActive,
81
+ ]}
82
+ onPress={() => onSelectFilter(filter.id, filter.value)}
83
+ >
84
+ <AtomicIcon
85
+ name={filter.icon as any}
86
+ size="lg"
87
+ color={selectedFilter === filter.id ? "primary" : "textSecondary"}
88
+ />
89
+ <AtomicText
90
+ style={[
91
+ styles.filterName,
92
+ selectedFilter === filter.id && styles.filterNameActive,
93
+ ]}
94
+ >
95
+ {filter.name}
96
+ </AtomicText>
97
+ </TouchableOpacity>
98
+ ))}
99
+ </View>
100
+ </View>
101
+ );
102
+ };
@@ -0,0 +1,82 @@
1
+ import React from "react";
2
+ import { View, ScrollView, TouchableOpacity } from "react-native";
3
+ import { AtomicText, AtomicIcon } from "@umituz/react-native-design-system";
4
+
5
+ interface FontControlsProps {
6
+ fontSize: number;
7
+ selectedFont: string;
8
+ fonts: readonly string[];
9
+ onFontSizeChange: (size: number) => void;
10
+ onFontSelect: (font: string) => void;
11
+ styles: Record<string, object>;
12
+ }
13
+
14
+ export const FontControls: React.FC<FontControlsProps> = ({
15
+ fontSize,
16
+ selectedFont,
17
+ fonts,
18
+ onFontSizeChange,
19
+ onFontSelect,
20
+ styles,
21
+ }) => {
22
+ return (
23
+ <View style={styles.controlsPanel}>
24
+ <View style={styles.sliderRow}>
25
+ <View style={styles.sliderLabel}>
26
+ <AtomicIcon name="text" size="sm" color="textSecondary" />
27
+ <AtomicText style={styles.sliderLabelText}>Text Size</AtomicText>
28
+ </View>
29
+ <AtomicText style={styles.sliderValue}>{fontSize}px</AtomicText>
30
+ </View>
31
+ <View style={styles.sliderTrack}>
32
+ <View
33
+ style={[
34
+ styles.sliderFill,
35
+ { width: `${((fontSize - 12) / 84) * 100}%` },
36
+ ]}
37
+ />
38
+ </View>
39
+ <View style={{ flexDirection: "row", gap: 8, marginBottom: 16 }}>
40
+ <TouchableOpacity
41
+ onPress={() => onFontSizeChange(fontSize - 4)}
42
+ style={styles.fontChip}
43
+ >
44
+ <AtomicText style={styles.fontChipText}>-</AtomicText>
45
+ </TouchableOpacity>
46
+ <TouchableOpacity
47
+ onPress={() => onFontSizeChange(fontSize + 4)}
48
+ style={styles.fontChip}
49
+ >
50
+ <AtomicText style={styles.fontChipText}>+</AtomicText>
51
+ </TouchableOpacity>
52
+ </View>
53
+
54
+ <AtomicText style={styles.fontLabel}>Font Style</AtomicText>
55
+ <ScrollView
56
+ horizontal
57
+ showsHorizontalScrollIndicator={false}
58
+ contentContainerStyle={styles.fontRow}
59
+ >
60
+ {fonts.map((font) => (
61
+ <TouchableOpacity
62
+ key={font}
63
+ style={[
64
+ styles.fontChip,
65
+ selectedFont === font && styles.fontChipActive,
66
+ ]}
67
+ onPress={() => onFontSelect(font)}
68
+ >
69
+ <AtomicText
70
+ style={[
71
+ styles.fontChipText,
72
+ selectedFont === font && styles.fontChipTextActive,
73
+ ]}
74
+ >
75
+ {font}
76
+ </AtomicText>
77
+ </TouchableOpacity>
78
+ ))}
79
+ </ScrollView>
80
+ </View>
81
+ );
82
+ };
@@ -0,0 +1,111 @@
1
+ import React from "react";
2
+ import { View, ScrollView, TouchableOpacity, StyleSheet } from "react-native";
3
+ import {
4
+ AtomicText,
5
+ AtomicIcon,
6
+ useAppDesignTokens,
7
+ } from "@umituz/react-native-design-system";
8
+ import { Layer, TextLayer } from "../types";
9
+
10
+ interface LayerManagerProps {
11
+ layers: Layer[];
12
+ activeLayerId: string | null;
13
+ onSelectLayer: (id: string) => void;
14
+ onDeleteLayer: (id: string) => void;
15
+ t: (key: string) => string;
16
+ }
17
+
18
+ export const LayerManager: React.FC<LayerManagerProps> = ({
19
+ layers,
20
+ activeLayerId,
21
+ onSelectLayer,
22
+ onDeleteLayer,
23
+ t,
24
+ }) => {
25
+ const tokens = useAppDesignTokens();
26
+
27
+ const styles = StyleSheet.create({
28
+ container: { padding: 16 },
29
+ title: {
30
+ fontSize: 18,
31
+ fontWeight: "bold",
32
+ color: tokens.colors.textPrimary,
33
+ marginBottom: 16,
34
+ },
35
+ emptyText: {
36
+ color: tokens.colors.textSecondary,
37
+ textAlign: "center",
38
+ padding: 24,
39
+ },
40
+ layerItem: {
41
+ flexDirection: "row",
42
+ alignItems: "center",
43
+ padding: 12,
44
+ backgroundColor: tokens.colors.surfaceVariant,
45
+ borderRadius: 12,
46
+ marginBottom: 8,
47
+ borderWidth: 2,
48
+ borderColor: "transparent",
49
+ },
50
+ layerItemActive: {
51
+ borderColor: tokens.colors.primary,
52
+ backgroundColor: tokens.colors.primaryContainer,
53
+ },
54
+ layerInfo: { flex: 1, marginLeft: 12 },
55
+ layerType: { fontSize: 12, color: tokens.colors.textSecondary },
56
+ layerText: {
57
+ fontSize: 14,
58
+ color: tokens.colors.textPrimary,
59
+ fontWeight: "500",
60
+ },
61
+ deleteButton: { padding: 8 },
62
+ });
63
+
64
+ return (
65
+ <View style={styles.container}>
66
+ <AtomicText style={styles.title}>Layers</AtomicText>
67
+ {layers.length === 0 ? (
68
+ <AtomicText style={styles.emptyText}>
69
+ No layers yet. Add text or stickers!
70
+ </AtomicText>
71
+ ) : (
72
+ <ScrollView showsVerticalScrollIndicator={false}>
73
+ {layers.map((layer) => (
74
+ <TouchableOpacity
75
+ key={layer.id}
76
+ style={[
77
+ styles.layerItem,
78
+ activeLayerId === layer.id && styles.layerItemActive,
79
+ ]}
80
+ onPress={() => onSelectLayer(layer.id)}
81
+ >
82
+ <AtomicIcon
83
+ name={layer.type === "text" ? "text" : "happy"}
84
+ size="md"
85
+ color={activeLayerId === layer.id ? "primary" : "textSecondary"}
86
+ />
87
+ <View style={styles.layerInfo}>
88
+ <AtomicText style={styles.layerType}>
89
+ {layer.type === "text" ? "Text" : "Sticker"}
90
+ </AtomicText>
91
+ <AtomicText style={styles.layerText} numberOfLines={1}>
92
+ {layer.type === "text"
93
+ ? (layer as TextLayer).text || t("editor.untitled")
94
+ : "Emoji"}
95
+ </AtomicText>
96
+ </View>
97
+ {layers.length > 1 && (
98
+ <TouchableOpacity
99
+ style={styles.deleteButton}
100
+ onPress={() => onDeleteLayer(layer.id)}
101
+ >
102
+ <AtomicIcon name="trash" size="sm" color="error" />
103
+ </TouchableOpacity>
104
+ )}
105
+ </TouchableOpacity>
106
+ ))}
107
+ </ScrollView>
108
+ )}
109
+ </View>
110
+ );
111
+ };
@@ -0,0 +1,94 @@
1
+ import React from "react";
2
+ import { View, ScrollView, TouchableOpacity, StyleSheet } from "react-native";
3
+ import {
4
+ AtomicText,
5
+ useAppDesignTokens,
6
+ } from "@umituz/react-native-design-system";
7
+
8
+ const STICKERS = [
9
+ "😀",
10
+ "😂",
11
+ "🤣",
12
+ "😍",
13
+ "🥰",
14
+ "😎",
15
+ "🤯",
16
+ "🥳",
17
+ "😤",
18
+ "💀",
19
+ "🔥",
20
+ "❤️",
21
+ "💯",
22
+ "✨",
23
+ "🎉",
24
+ "🤡",
25
+ "👀",
26
+ "🙌",
27
+ "👏",
28
+ "💪",
29
+ "🤝",
30
+ "🙈",
31
+ "🐶",
32
+ "🐱",
33
+ "🦊",
34
+ "🐸",
35
+ "🌟",
36
+ "⭐",
37
+ "🌈",
38
+ "☀️",
39
+ "🌙",
40
+ "💫",
41
+ ];
42
+
43
+ interface StickerPickerProps {
44
+ onSelectSticker: (sticker: string) => void;
45
+ }
46
+
47
+ export const StickerPicker: React.FC<StickerPickerProps> = ({
48
+ onSelectSticker,
49
+ }) => {
50
+ const tokens = useAppDesignTokens();
51
+
52
+ const styles = StyleSheet.create({
53
+ container: { padding: 16 },
54
+ title: {
55
+ fontSize: 18,
56
+ fontWeight: "bold",
57
+ color: tokens.colors.textPrimary,
58
+ marginBottom: 16,
59
+ },
60
+ grid: {
61
+ flexDirection: "row",
62
+ flexWrap: "wrap",
63
+ gap: 12,
64
+ },
65
+ sticker: {
66
+ width: 56,
67
+ height: 56,
68
+ borderRadius: 12,
69
+ backgroundColor: tokens.colors.surfaceVariant,
70
+ alignItems: "center",
71
+ justifyContent: "center",
72
+ },
73
+ stickerText: { fontSize: 32 },
74
+ });
75
+
76
+ return (
77
+ <View style={styles.container}>
78
+ <AtomicText style={styles.title}>Emoji</AtomicText>
79
+ <ScrollView showsVerticalScrollIndicator={false}>
80
+ <View style={styles.grid}>
81
+ {STICKERS.map((sticker, index) => (
82
+ <TouchableOpacity
83
+ key={index}
84
+ style={styles.sticker}
85
+ onPress={() => onSelectSticker(sticker)}
86
+ >
87
+ <AtomicText style={styles.stickerText}>{sticker}</AtomicText>
88
+ </TouchableOpacity>
89
+ ))}
90
+ </View>
91
+ </ScrollView>
92
+ </View>
93
+ );
94
+ };
@@ -0,0 +1,63 @@
1
+ import React from "react";
2
+ import { View, TextInput, TouchableOpacity, StyleSheet } from "react-native";
3
+ import {
4
+ AtomicText,
5
+ useAppDesignTokens,
6
+ } from "@umituz/react-native-design-system";
7
+
8
+ interface TextEditorSheetProps {
9
+ value: string;
10
+ onChange: (text: string) => void;
11
+ onSave: () => void;
12
+ t: (key: string) => string;
13
+ }
14
+
15
+ export const TextEditorSheet: React.FC<TextEditorSheetProps> = ({
16
+ value,
17
+ onChange,
18
+ onSave,
19
+ t,
20
+ }) => {
21
+ const tokens = useAppDesignTokens();
22
+
23
+ const styles = StyleSheet.create({
24
+ container: { padding: 16 },
25
+ title: { fontSize: 18, fontWeight: "bold", marginBottom: 16 },
26
+ input: {
27
+ backgroundColor: tokens.colors.surfaceVariant,
28
+ borderRadius: 12,
29
+ padding: 16,
30
+ fontSize: 18,
31
+ color: tokens.colors.textPrimary,
32
+ marginBottom: 16,
33
+ textAlign: "center",
34
+ },
35
+ saveButton: {
36
+ backgroundColor: tokens.colors.primary,
37
+ borderRadius: 999,
38
+ padding: 16,
39
+ alignItems: "center",
40
+ },
41
+ saveButtonText: { color: tokens.colors.onPrimary, fontWeight: "bold" },
42
+ });
43
+
44
+ return (
45
+ <View style={styles.container}>
46
+ <AtomicText style={styles.title}>{t("editor.add_text")}</AtomicText>
47
+ <TextInput
48
+ value={value}
49
+ onChangeText={onChange}
50
+ placeholder={t("editor.tap_to_edit")}
51
+ placeholderTextColor={tokens.colors.textSecondary}
52
+ style={styles.input}
53
+ multiline
54
+ autoFocus
55
+ />
56
+ <TouchableOpacity onPress={onSave} style={styles.saveButton}>
57
+ <AtomicText style={styles.saveButtonText}>
58
+ {t("common.save")}
59
+ </AtomicText>
60
+ </TouchableOpacity>
61
+ </View>
62
+ );
63
+ };
@@ -0,0 +1,75 @@
1
+ /**
2
+ * History Manager for Undo/Redo functionality
3
+ */
4
+
5
+ export interface HistoryState<T> {
6
+ past: T[];
7
+ present: T;
8
+ future: T[];
9
+ }
10
+
11
+ export class HistoryManager<T> {
12
+ private maxHistory = 20;
13
+
14
+ createInitialState(initialValue: T): HistoryState<T> {
15
+ return {
16
+ past: [],
17
+ present: initialValue,
18
+ future: [],
19
+ };
20
+ }
21
+
22
+ push(history: HistoryState<T>, newValue: T): HistoryState<T> {
23
+ const { past, present } = history;
24
+
25
+ return {
26
+ past: [...past.slice(-this.maxHistory + 1), present],
27
+ present: newValue,
28
+ future: [],
29
+ };
30
+ }
31
+
32
+ undo(history: HistoryState<T>): HistoryState<T> {
33
+ const { past, present, future } = history;
34
+
35
+ if (past.length === 0) {
36
+ return history;
37
+ }
38
+
39
+ const previous = past[past.length - 1];
40
+ const newPast = past.slice(0, past.length - 1);
41
+
42
+ return {
43
+ past: newPast,
44
+ present: previous,
45
+ future: [present, ...future],
46
+ };
47
+ }
48
+
49
+ redo(history: HistoryState<T>): HistoryState<T> {
50
+ const { past, present, future } = history;
51
+
52
+ if (future.length === 0) {
53
+ return history;
54
+ }
55
+
56
+ const next = future[0];
57
+ const newFuture = future.slice(1);
58
+
59
+ return {
60
+ past: [...past, present],
61
+ present: next,
62
+ future: newFuture,
63
+ };
64
+ }
65
+
66
+ canUndo(history: HistoryState<T>): boolean {
67
+ return history.past.length > 0;
68
+ }
69
+
70
+ canRedo(history: HistoryState<T>): boolean {
71
+ return history.future.length > 0;
72
+ }
73
+ }
74
+
75
+ export const historyManager = new HistoryManager();