@umituz/react-native-photo-editor 1.0.9 → 1.0.11
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 +1 -1
- package/src/PhotoEditor.tsx +24 -106
- package/src/components/AIMagicSheet.tsx +28 -119
- package/src/components/DraggableSticker.tsx +30 -39
- package/src/components/DraggableText.tsx +43 -77
- package/src/components/EditorToolbar.tsx +52 -26
- package/src/components/FilterPicker.tsx +19 -51
- package/src/components/FontControls.tsx +48 -51
- package/src/components/LayerManager.tsx +29 -61
- package/src/components/StickerPicker.tsx +10 -28
- package/src/components/TextEditorSheet.tsx +12 -23
- package/src/constants.ts +7 -0
- package/src/core/HistoryManager.ts +1 -1
- package/src/hooks/usePhotoEditor.ts +25 -80
- package/src/hooks/usePhotoEditorUI.ts +29 -74
- package/src/styles.ts +26 -106
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { View, TouchableOpacity } from "react-native";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
AtomicText,
|
|
5
|
+
AtomicIcon,
|
|
6
|
+
useAppDesignTokens
|
|
7
|
+
} from "@umituz/react-native-design-system";
|
|
4
8
|
|
|
5
9
|
interface EditorToolbarProps {
|
|
6
10
|
onAddText: () => void;
|
|
7
|
-
onAddSticker
|
|
8
|
-
onAIMagic?: () => void;
|
|
9
|
-
onOpenFilters
|
|
11
|
+
onAddSticker?: () => void;
|
|
12
|
+
onAIMagic?: () => void;
|
|
13
|
+
onOpenFilters?: () => void;
|
|
10
14
|
onOpenLayers: () => void;
|
|
11
15
|
styles: Record<string, object>;
|
|
12
16
|
t: (key: string) => string;
|
|
@@ -21,34 +25,56 @@ export const EditorToolbar: React.FC<EditorToolbarProps> = ({
|
|
|
21
25
|
styles,
|
|
22
26
|
t,
|
|
23
27
|
}) => {
|
|
28
|
+
const tokens = useAppDesignTokens();
|
|
29
|
+
|
|
30
|
+
const ToolButton = ({
|
|
31
|
+
icon,
|
|
32
|
+
label,
|
|
33
|
+
onPress,
|
|
34
|
+
isActive
|
|
35
|
+
}: {
|
|
36
|
+
icon: string;
|
|
37
|
+
label: string;
|
|
38
|
+
onPress: () => void;
|
|
39
|
+
isActive?: boolean;
|
|
40
|
+
}) => (
|
|
41
|
+
<TouchableOpacity
|
|
42
|
+
style={[styles.toolButton, isActive && styles.toolButtonActive]}
|
|
43
|
+
onPress={onPress}
|
|
44
|
+
>
|
|
45
|
+
<AtomicIcon
|
|
46
|
+
name={icon}
|
|
47
|
+
size="md"
|
|
48
|
+
color={isActive ? "primary" : "textSecondary"}
|
|
49
|
+
/>
|
|
50
|
+
<AtomicText
|
|
51
|
+
type="labelSmall"
|
|
52
|
+
color={isActive ? "primary" : "textSecondary"}
|
|
53
|
+
>
|
|
54
|
+
{label}
|
|
55
|
+
</AtomicText>
|
|
56
|
+
</TouchableOpacity>
|
|
57
|
+
);
|
|
58
|
+
|
|
24
59
|
return (
|
|
25
60
|
<View style={styles.bottomToolbar}>
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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>
|
|
61
|
+
<ToolButton icon="text" label="Text" onPress={onAddText} />
|
|
62
|
+
|
|
63
|
+
{onAddSticker && (
|
|
64
|
+
<ToolButton icon="happy" label={t("editor.sticker")} onPress={onAddSticker} />
|
|
65
|
+
)}
|
|
66
|
+
|
|
39
67
|
{onAIMagic && (
|
|
40
68
|
<TouchableOpacity style={styles.aiMagicButton} onPress={onAIMagic}>
|
|
41
|
-
<AtomicIcon name="sparkles" size="lg" customColor=
|
|
69
|
+
<AtomicIcon name="sparkles" size="lg" customColor={tokens.colors.onPrimary} />
|
|
42
70
|
</TouchableOpacity>
|
|
43
71
|
)}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
<
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
<AtomicText style={styles.toolLabel}>Layer</AtomicText>
|
|
51
|
-
</TouchableOpacity>
|
|
72
|
+
|
|
73
|
+
{onOpenFilters && (
|
|
74
|
+
<ToolButton icon="color-filter" label={t("editor.filters")} onPress={onOpenFilters} />
|
|
75
|
+
)}
|
|
76
|
+
|
|
77
|
+
<ToolButton icon="layers" label="Layers" onPress={onOpenLayers} />
|
|
52
78
|
</View>
|
|
53
79
|
);
|
|
54
80
|
};
|
|
@@ -1,18 +1,7 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { View, TouchableOpacity, StyleSheet } from "react-native";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
AtomicIcon,
|
|
6
|
-
useAppDesignTokens,
|
|
7
|
-
} from "@umituz/react-native-design-system";
|
|
8
|
-
import { DEFAULT_FILTERS, type FilterType } from "../constants";
|
|
9
|
-
|
|
10
|
-
interface FilterOption {
|
|
11
|
-
id: FilterType;
|
|
12
|
-
name: string;
|
|
13
|
-
icon: string;
|
|
14
|
-
value: number;
|
|
15
|
-
}
|
|
3
|
+
import { AtomicText, AtomicIcon, useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
4
|
+
import { DEFAULT_FILTERS, type FilterOption } from "../constants";
|
|
16
5
|
|
|
17
6
|
interface FilterPickerProps {
|
|
18
7
|
selectedFilter: string;
|
|
@@ -28,65 +17,44 @@ export const FilterPicker: React.FC<FilterPickerProps> = ({
|
|
|
28
17
|
const tokens = useAppDesignTokens();
|
|
29
18
|
|
|
30
19
|
const styles = StyleSheet.create({
|
|
31
|
-
container: { padding:
|
|
32
|
-
|
|
33
|
-
fontSize: 18,
|
|
34
|
-
fontWeight: "bold",
|
|
35
|
-
color: tokens.colors.textPrimary,
|
|
36
|
-
marginBottom: 16,
|
|
37
|
-
},
|
|
38
|
-
grid: {
|
|
39
|
-
flexDirection: "row",
|
|
40
|
-
flexWrap: "wrap",
|
|
41
|
-
gap: 12,
|
|
42
|
-
},
|
|
20
|
+
container: { padding: tokens.spacing.md, gap: tokens.spacing.md },
|
|
21
|
+
grid: { flexDirection: "row", flexWrap: "wrap", gap: tokens.spacing.sm },
|
|
43
22
|
filter: {
|
|
44
|
-
width:
|
|
45
|
-
height:
|
|
46
|
-
borderRadius:
|
|
23
|
+
width: 75,
|
|
24
|
+
height: 75,
|
|
25
|
+
borderRadius: tokens.borders.radius.md,
|
|
47
26
|
backgroundColor: tokens.colors.surfaceVariant,
|
|
48
27
|
alignItems: "center",
|
|
49
28
|
justifyContent: "center",
|
|
50
29
|
borderWidth: 2,
|
|
51
30
|
borderColor: "transparent",
|
|
52
31
|
},
|
|
53
|
-
|
|
32
|
+
active: {
|
|
54
33
|
borderColor: tokens.colors.primary,
|
|
55
|
-
backgroundColor: tokens.colors.
|
|
56
|
-
},
|
|
57
|
-
filterName: {
|
|
58
|
-
marginTop: 4,
|
|
59
|
-
fontSize: 12,
|
|
60
|
-
color: tokens.colors.textSecondary,
|
|
34
|
+
backgroundColor: tokens.colors.primary + "10",
|
|
61
35
|
},
|
|
62
|
-
filterNameActive: { color: tokens.colors.primary },
|
|
63
36
|
});
|
|
64
37
|
|
|
65
38
|
return (
|
|
66
39
|
<View style={styles.container}>
|
|
67
|
-
<AtomicText
|
|
40
|
+
<AtomicText type="headlineSmall">Filters</AtomicText>
|
|
68
41
|
<View style={styles.grid}>
|
|
69
|
-
{filters.map((
|
|
42
|
+
{filters.map((f) => (
|
|
70
43
|
<TouchableOpacity
|
|
71
|
-
key={
|
|
72
|
-
style={[
|
|
73
|
-
|
|
74
|
-
selectedFilter === filter.id && styles.filterActive,
|
|
75
|
-
]}
|
|
76
|
-
onPress={() => onSelectFilter(filter.id, filter.value)}
|
|
44
|
+
key={f.id}
|
|
45
|
+
style={[styles.filter, selectedFilter === f.id && styles.active]}
|
|
46
|
+
onPress={() => onSelectFilter(f.id, f.value)}
|
|
77
47
|
>
|
|
78
48
|
<AtomicIcon
|
|
79
|
-
name={
|
|
49
|
+
name={f.icon as "close-circle" | "color-palette" | "contrast" | "time" | "sunny" | "snow"}
|
|
80
50
|
size="lg"
|
|
81
|
-
color={selectedFilter ===
|
|
51
|
+
color={selectedFilter === f.id ? "primary" : "textSecondary"}
|
|
82
52
|
/>
|
|
83
53
|
<AtomicText
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
selectedFilter === filter.id && styles.filterNameActive,
|
|
87
|
-
]}
|
|
54
|
+
type="labelSmall"
|
|
55
|
+
color={selectedFilter === f.id ? "primary" : "textSecondary"}
|
|
88
56
|
>
|
|
89
|
-
{
|
|
57
|
+
{f.name}
|
|
90
58
|
</AtomicText>
|
|
91
59
|
</TouchableOpacity>
|
|
92
60
|
))}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { View, ScrollView, TouchableOpacity } from "react-native";
|
|
3
|
-
import { AtomicText, AtomicIcon } from "@umituz/react-native-design-system";
|
|
2
|
+
import { View, ScrollView, TouchableOpacity, StyleSheet } from "react-native";
|
|
3
|
+
import { AtomicText, AtomicIcon, useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
4
4
|
|
|
5
5
|
interface FontControlsProps {
|
|
6
6
|
fontSize: number;
|
|
@@ -17,65 +17,62 @@ export const FontControls: React.FC<FontControlsProps> = ({
|
|
|
17
17
|
fonts,
|
|
18
18
|
onFontSizeChange,
|
|
19
19
|
onFontSelect,
|
|
20
|
-
styles,
|
|
20
|
+
styles: externalStyles,
|
|
21
21
|
}) => {
|
|
22
|
+
const tokens = useAppDesignTokens();
|
|
23
|
+
|
|
24
|
+
const styles = StyleSheet.create({
|
|
25
|
+
btn: {
|
|
26
|
+
padding: tokens.spacing.sm,
|
|
27
|
+
backgroundColor: tokens.colors.surface,
|
|
28
|
+
borderRadius: tokens.borders.radius.sm,
|
|
29
|
+
borderWidth: 1,
|
|
30
|
+
borderColor: tokens.colors.border,
|
|
31
|
+
minWidth: 44,
|
|
32
|
+
alignItems: "center",
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
22
36
|
return (
|
|
23
|
-
<View style={
|
|
24
|
-
<View style={
|
|
25
|
-
<View style={
|
|
37
|
+
<View style={externalStyles.controlsPanel}>
|
|
38
|
+
<View style={externalStyles.sliderRow}>
|
|
39
|
+
<View style={externalStyles.sliderLabel}>
|
|
26
40
|
<AtomicIcon name="text" size="sm" color="textSecondary" />
|
|
27
|
-
<AtomicText
|
|
41
|
+
<AtomicText type="labelMedium" color="textSecondary">Text Size</AtomicText>
|
|
28
42
|
</View>
|
|
29
|
-
<AtomicText
|
|
30
|
-
</View>
|
|
31
|
-
<View style={styles.sliderTrack}>
|
|
32
|
-
<View
|
|
33
|
-
style={[
|
|
34
|
-
styles.sliderFill,
|
|
35
|
-
{ width: `${((fontSize - 12) / 84) * 100}%` },
|
|
36
|
-
]}
|
|
37
|
-
/>
|
|
43
|
+
<AtomicText fontWeight="bold" color="primary">{fontSize}px</AtomicText>
|
|
38
44
|
</View>
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
>
|
|
44
|
-
<AtomicText style={styles.fontChipText}>-</AtomicText>
|
|
45
|
+
|
|
46
|
+
<View style={{ flexDirection: "row", gap: tokens.spacing.sm, marginBottom: tokens.spacing.md }}>
|
|
47
|
+
<TouchableOpacity onPress={() => onFontSizeChange(fontSize - 4)} style={styles.btn}>
|
|
48
|
+
<AtomicText fontWeight="bold">-</AtomicText>
|
|
45
49
|
</TouchableOpacity>
|
|
46
|
-
<TouchableOpacity
|
|
47
|
-
|
|
48
|
-
style={styles.fontChip}
|
|
49
|
-
>
|
|
50
|
-
<AtomicText style={styles.fontChipText}>+</AtomicText>
|
|
50
|
+
<TouchableOpacity onPress={() => onFontSizeChange(fontSize + 4)} style={styles.btn}>
|
|
51
|
+
<AtomicText fontWeight="bold">+</AtomicText>
|
|
51
52
|
</TouchableOpacity>
|
|
52
53
|
</View>
|
|
53
54
|
|
|
54
|
-
<AtomicText style={
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
]}
|
|
55
|
+
<AtomicText type="labelMedium" color="textSecondary" style={{ marginBottom: tokens.spacing.xs }}>
|
|
56
|
+
Font Style
|
|
57
|
+
</AtomicText>
|
|
58
|
+
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
|
|
59
|
+
<View style={externalStyles.fontRow}>
|
|
60
|
+
{fonts.map((font) => (
|
|
61
|
+
<TouchableOpacity
|
|
62
|
+
key={font}
|
|
63
|
+
style={[externalStyles.fontChip, selectedFont === font && externalStyles.fontChipActive]}
|
|
64
|
+
onPress={() => onFontSelect(font)}
|
|
74
65
|
>
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
66
|
+
<AtomicText
|
|
67
|
+
fontWeight="bold"
|
|
68
|
+
color={selectedFont === font ? "onPrimary" : "textSecondary"}
|
|
69
|
+
style={{ fontFamily: font }}
|
|
70
|
+
>
|
|
71
|
+
{font}
|
|
72
|
+
</AtomicText>
|
|
73
|
+
</TouchableOpacity>
|
|
74
|
+
))}
|
|
75
|
+
</View>
|
|
79
76
|
</ScrollView>
|
|
80
77
|
</View>
|
|
81
78
|
);
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { View, ScrollView, TouchableOpacity, StyleSheet } from "react-native";
|
|
3
|
-
import {
|
|
4
|
-
AtomicText,
|
|
5
|
-
AtomicIcon,
|
|
6
|
-
useAppDesignTokens,
|
|
7
|
-
} from "@umituz/react-native-design-system";
|
|
3
|
+
import { AtomicText, AtomicIcon, useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
8
4
|
import { Layer, TextLayer } from "../types";
|
|
9
5
|
|
|
10
6
|
interface LayerManagerProps {
|
|
@@ -25,58 +21,37 @@ export const LayerManager: React.FC<LayerManagerProps> = ({
|
|
|
25
21
|
const tokens = useAppDesignTokens();
|
|
26
22
|
|
|
27
23
|
const styles = StyleSheet.create({
|
|
28
|
-
container: { padding:
|
|
29
|
-
|
|
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: {
|
|
24
|
+
container: { padding: tokens.spacing.md, gap: tokens.spacing.md },
|
|
25
|
+
item: {
|
|
41
26
|
flexDirection: "row",
|
|
42
27
|
alignItems: "center",
|
|
43
|
-
padding:
|
|
28
|
+
padding: tokens.spacing.md,
|
|
44
29
|
backgroundColor: tokens.colors.surfaceVariant,
|
|
45
|
-
borderRadius:
|
|
46
|
-
marginBottom:
|
|
30
|
+
borderRadius: tokens.borders.radius.md,
|
|
31
|
+
marginBottom: tokens.spacing.xs,
|
|
47
32
|
borderWidth: 2,
|
|
48
33
|
borderColor: "transparent",
|
|
49
34
|
},
|
|
50
|
-
|
|
35
|
+
active: {
|
|
51
36
|
borderColor: tokens.colors.primary,
|
|
52
|
-
backgroundColor: tokens.colors.
|
|
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",
|
|
37
|
+
backgroundColor: tokens.colors.primary + "10",
|
|
60
38
|
},
|
|
61
|
-
|
|
39
|
+
info: { flex: 1, marginLeft: tokens.spacing.sm },
|
|
62
40
|
});
|
|
63
41
|
|
|
64
42
|
return (
|
|
65
43
|
<View style={styles.container}>
|
|
66
|
-
<AtomicText
|
|
67
|
-
{
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
44
|
+
<AtomicText type="headlineSmall">Layers</AtomicText>
|
|
45
|
+
<ScrollView showsVerticalScrollIndicator={false}>
|
|
46
|
+
{layers.length === 0 ? (
|
|
47
|
+
<AtomicText color="textSecondary" style={{ textAlign: "center", padding: tokens.spacing.xl }}>
|
|
48
|
+
No layers yet
|
|
49
|
+
</AtomicText>
|
|
50
|
+
) : (
|
|
51
|
+
layers.map((layer) => (
|
|
74
52
|
<TouchableOpacity
|
|
75
53
|
key={layer.id}
|
|
76
|
-
style={[
|
|
77
|
-
styles.layerItem,
|
|
78
|
-
activeLayerId === layer.id && styles.layerItemActive,
|
|
79
|
-
]}
|
|
54
|
+
style={[styles.item, activeLayerId === layer.id && styles.active]}
|
|
80
55
|
onPress={() => onSelectLayer(layer.id)}
|
|
81
56
|
>
|
|
82
57
|
<AtomicIcon
|
|
@@ -84,28 +59,21 @@ export const LayerManager: React.FC<LayerManagerProps> = ({
|
|
|
84
59
|
size="md"
|
|
85
60
|
color={activeLayerId === layer.id ? "primary" : "textSecondary"}
|
|
86
61
|
/>
|
|
87
|
-
<View style={styles.
|
|
88
|
-
<AtomicText
|
|
89
|
-
{layer.type
|
|
62
|
+
<View style={styles.info}>
|
|
63
|
+
<AtomicText type="labelSmall" color="textSecondary">
|
|
64
|
+
{layer.type.toUpperCase()}
|
|
90
65
|
</AtomicText>
|
|
91
|
-
<AtomicText
|
|
92
|
-
{layer.type === "text"
|
|
93
|
-
? (layer as TextLayer).text || t("editor.untitled")
|
|
94
|
-
: "Emoji"}
|
|
66
|
+
<AtomicText fontWeight="bold" numberOfLines={1}>
|
|
67
|
+
{layer.type === "text" ? (layer as TextLayer).text || t("editor.untitled") : "Sticker"}
|
|
95
68
|
</AtomicText>
|
|
96
69
|
</View>
|
|
97
|
-
{
|
|
98
|
-
<
|
|
99
|
-
|
|
100
|
-
onPress={() => onDeleteLayer(layer.id)}
|
|
101
|
-
>
|
|
102
|
-
<AtomicIcon name="trash" size="sm" color="error" />
|
|
103
|
-
</TouchableOpacity>
|
|
104
|
-
)}
|
|
70
|
+
<TouchableOpacity onPress={() => onDeleteLayer(layer.id)} style={{ padding: tokens.spacing.xs }}>
|
|
71
|
+
<AtomicIcon name="trash" size="sm" color="error" />
|
|
72
|
+
</TouchableOpacity>
|
|
105
73
|
</TouchableOpacity>
|
|
106
|
-
))
|
|
107
|
-
|
|
108
|
-
|
|
74
|
+
))
|
|
75
|
+
)}
|
|
76
|
+
</ScrollView>
|
|
109
77
|
</View>
|
|
110
78
|
);
|
|
111
79
|
};
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { View, ScrollView, TouchableOpacity, StyleSheet } from "react-native";
|
|
3
|
-
import {
|
|
4
|
-
AtomicText,
|
|
5
|
-
useAppDesignTokens,
|
|
6
|
-
} from "@umituz/react-native-design-system";
|
|
3
|
+
import { AtomicText, useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
7
4
|
import { DEFAULT_STICKERS } from "../constants";
|
|
8
5
|
|
|
9
6
|
interface StickerPickerProps {
|
|
@@ -18,41 +15,26 @@ export const StickerPicker: React.FC<StickerPickerProps> = ({
|
|
|
18
15
|
const tokens = useAppDesignTokens();
|
|
19
16
|
|
|
20
17
|
const styles = StyleSheet.create({
|
|
21
|
-
container: { padding:
|
|
22
|
-
|
|
23
|
-
fontSize: 18,
|
|
24
|
-
fontWeight: "bold",
|
|
25
|
-
color: tokens.colors.textPrimary,
|
|
26
|
-
marginBottom: 16,
|
|
27
|
-
},
|
|
28
|
-
grid: {
|
|
29
|
-
flexDirection: "row",
|
|
30
|
-
flexWrap: "wrap",
|
|
31
|
-
gap: 12,
|
|
32
|
-
},
|
|
18
|
+
container: { padding: tokens.spacing.md, gap: tokens.spacing.md },
|
|
19
|
+
grid: { flexDirection: "row", flexWrap: "wrap", gap: tokens.spacing.sm },
|
|
33
20
|
sticker: {
|
|
34
|
-
width:
|
|
35
|
-
height:
|
|
36
|
-
borderRadius:
|
|
21
|
+
width: 50,
|
|
22
|
+
height: 50,
|
|
23
|
+
borderRadius: tokens.borders.radius.sm,
|
|
37
24
|
backgroundColor: tokens.colors.surfaceVariant,
|
|
38
25
|
alignItems: "center",
|
|
39
26
|
justifyContent: "center",
|
|
40
27
|
},
|
|
41
|
-
stickerText: { fontSize: 32 },
|
|
42
28
|
});
|
|
43
29
|
|
|
44
30
|
return (
|
|
45
31
|
<View style={styles.container}>
|
|
46
|
-
<AtomicText
|
|
32
|
+
<AtomicText type="headlineSmall">Stickers</AtomicText>
|
|
47
33
|
<ScrollView showsVerticalScrollIndicator={false}>
|
|
48
34
|
<View style={styles.grid}>
|
|
49
|
-
{stickers.map((
|
|
50
|
-
<TouchableOpacity
|
|
51
|
-
|
|
52
|
-
style={styles.sticker}
|
|
53
|
-
onPress={() => onSelectSticker(sticker)}
|
|
54
|
-
>
|
|
55
|
-
<AtomicText style={styles.stickerText}>{sticker}</AtomicText>
|
|
35
|
+
{stickers.map((s, i) => (
|
|
36
|
+
<TouchableOpacity key={`${s}-${i}`} style={styles.sticker} onPress={() => onSelectSticker(s)}>
|
|
37
|
+
<AtomicText style={{ fontSize: 32 }}>{s}</AtomicText>
|
|
56
38
|
</TouchableOpacity>
|
|
57
39
|
))}
|
|
58
40
|
</View>
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { View, TextInput,
|
|
3
|
-
import {
|
|
4
|
-
AtomicText,
|
|
5
|
-
useAppDesignTokens,
|
|
6
|
-
} from "@umituz/react-native-design-system";
|
|
2
|
+
import { View, TextInput, StyleSheet } from "react-native";
|
|
3
|
+
import { AtomicText, useAppDesignTokens, AtomicButton } from "@umituz/react-native-design-system";
|
|
7
4
|
|
|
8
5
|
interface TextEditorSheetProps {
|
|
9
6
|
value: string;
|
|
@@ -21,29 +18,22 @@ export const TextEditorSheet: React.FC<TextEditorSheetProps> = ({
|
|
|
21
18
|
const tokens = useAppDesignTokens();
|
|
22
19
|
|
|
23
20
|
const styles = StyleSheet.create({
|
|
24
|
-
container: { padding:
|
|
25
|
-
title: { fontSize: 18, fontWeight: "bold", marginBottom: 16 },
|
|
21
|
+
container: { padding: tokens.spacing.md, gap: tokens.spacing.md },
|
|
26
22
|
input: {
|
|
27
23
|
backgroundColor: tokens.colors.surfaceVariant,
|
|
28
|
-
borderRadius:
|
|
29
|
-
padding:
|
|
24
|
+
borderRadius: tokens.borders.radius.md,
|
|
25
|
+
padding: tokens.spacing.md,
|
|
30
26
|
fontSize: 18,
|
|
31
27
|
color: tokens.colors.textPrimary,
|
|
32
|
-
marginBottom: 16,
|
|
33
28
|
textAlign: "center",
|
|
29
|
+
minHeight: 120,
|
|
34
30
|
},
|
|
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
31
|
});
|
|
43
32
|
|
|
44
33
|
return (
|
|
45
34
|
<View style={styles.container}>
|
|
46
|
-
<AtomicText
|
|
35
|
+
<AtomicText type="headlineSmall">{t("editor.add_text")}</AtomicText>
|
|
36
|
+
|
|
47
37
|
<TextInput
|
|
48
38
|
value={value}
|
|
49
39
|
onChangeText={onChange}
|
|
@@ -53,11 +43,10 @@ export const TextEditorSheet: React.FC<TextEditorSheetProps> = ({
|
|
|
53
43
|
multiline
|
|
54
44
|
autoFocus
|
|
55
45
|
/>
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
</TouchableOpacity>
|
|
46
|
+
|
|
47
|
+
<AtomicButton variant="primary" onPress={onSave}>
|
|
48
|
+
{t("common.save")}
|
|
49
|
+
</AtomicButton>
|
|
61
50
|
</View>
|
|
62
51
|
);
|
|
63
52
|
};
|
package/src/constants.ts
CHANGED
|
@@ -42,6 +42,13 @@ export const DEFAULT_STICKERS = [
|
|
|
42
42
|
|
|
43
43
|
export type FilterType = "none" | "sepia" | "grayscale" | "vintage" | "warm" | "cool";
|
|
44
44
|
|
|
45
|
+
export interface FilterOption {
|
|
46
|
+
id: FilterType;
|
|
47
|
+
name: string;
|
|
48
|
+
icon: string;
|
|
49
|
+
value: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
45
52
|
export const DEFAULT_FILTERS = [
|
|
46
53
|
{ id: "none" as FilterType, name: "None", icon: "close-circle", value: 0 },
|
|
47
54
|
{ id: "sepia" as FilterType, name: "Sepia", icon: "color-palette", value: 0.5 },
|