@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,6 +1,7 @@
|
|
|
1
1
|
import { useState, useCallback, useMemo } from "react";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { DesignTokens } from "@umituz/react-native-design-system";
|
|
3
|
+
import { Layer, TextLayer, StickerLayer, ImageFilters } from "../types";
|
|
4
|
+
import { HistoryManager, HistoryState } from "../core/HistoryManager";
|
|
4
5
|
|
|
5
6
|
const DEFAULT_FILTERS: ImageFilters = {
|
|
6
7
|
brightness: 1,
|
|
@@ -10,12 +11,9 @@ const DEFAULT_FILTERS: ImageFilters = {
|
|
|
10
11
|
grayscale: 0,
|
|
11
12
|
};
|
|
12
13
|
|
|
13
|
-
const historyManager = new HistoryManager<Layer[]>();
|
|
14
|
-
|
|
15
|
-
import { DesignTokens } from "@umituz/react-native-design-system";
|
|
16
|
-
|
|
17
14
|
export const usePhotoEditor = (initialLayers: Layer[] = []) => {
|
|
18
|
-
const
|
|
15
|
+
const historyManager = useMemo(() => new HistoryManager<Layer[]>(), []);
|
|
16
|
+
const [history, setHistory] = useState<HistoryState<Layer[]>>(() =>
|
|
19
17
|
historyManager.createInitialState(initialLayers),
|
|
20
18
|
);
|
|
21
19
|
const [activeLayerId, setActiveLayerId] = useState<string | null>(
|
|
@@ -25,15 +23,12 @@ export const usePhotoEditor = (initialLayers: Layer[] = []) => {
|
|
|
25
23
|
|
|
26
24
|
const layers = history.present;
|
|
27
25
|
|
|
28
|
-
const canUndo = historyManager.canUndo(history);
|
|
29
|
-
const canRedo = historyManager.canRedo(history);
|
|
30
|
-
|
|
31
26
|
const pushState = useCallback((newLayers: Layer[]) => {
|
|
32
27
|
setHistory((prev) => historyManager.push(prev, newLayers));
|
|
33
|
-
}, []);
|
|
28
|
+
}, [historyManager]);
|
|
34
29
|
|
|
35
30
|
const addTextLayer = useCallback(
|
|
36
|
-
(
|
|
31
|
+
(tokens: DesignTokens) => {
|
|
37
32
|
const id = `text-${Date.now()}`;
|
|
38
33
|
const newLayer: TextLayer = {
|
|
39
34
|
id,
|
|
@@ -47,16 +42,12 @@ export const usePhotoEditor = (initialLayers: Layer[] = []) => {
|
|
|
47
42
|
zIndex: layers.length,
|
|
48
43
|
fontSize: 32,
|
|
49
44
|
fontFamily: "System",
|
|
50
|
-
color:
|
|
45
|
+
color: tokens.colors.textPrimary,
|
|
51
46
|
backgroundColor: "transparent",
|
|
52
47
|
textAlign: "center",
|
|
53
|
-
strokeWidth: 2,
|
|
54
|
-
strokeColor: defaultTokens.colors.onBackground,
|
|
55
48
|
};
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
pushState(newLayers);
|
|
59
|
-
setActiveLayerId(newLayer.id);
|
|
49
|
+
pushState([...layers, newLayer]);
|
|
50
|
+
setActiveLayerId(id);
|
|
60
51
|
return id;
|
|
61
52
|
},
|
|
62
53
|
[layers, pushState],
|
|
@@ -65,10 +56,10 @@ export const usePhotoEditor = (initialLayers: Layer[] = []) => {
|
|
|
65
56
|
const addStickerLayer = useCallback(
|
|
66
57
|
(uri: string) => {
|
|
67
58
|
const id = `sticker-${Date.now()}`;
|
|
68
|
-
const newLayer:
|
|
59
|
+
const newLayer: StickerLayer = {
|
|
69
60
|
id,
|
|
70
61
|
type: "sticker",
|
|
71
|
-
uri
|
|
62
|
+
uri,
|
|
72
63
|
x: 100,
|
|
73
64
|
y: 100,
|
|
74
65
|
rotation: 0,
|
|
@@ -76,90 +67,44 @@ export const usePhotoEditor = (initialLayers: Layer[] = []) => {
|
|
|
76
67
|
opacity: 1,
|
|
77
68
|
zIndex: layers.length,
|
|
78
69
|
};
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
pushState(newLayers);
|
|
82
|
-
setActiveLayerId(newLayer.id);
|
|
70
|
+
pushState([...layers, newLayer]);
|
|
71
|
+
setActiveLayerId(id);
|
|
83
72
|
return id;
|
|
84
73
|
},
|
|
85
74
|
[layers, pushState],
|
|
86
75
|
);
|
|
87
76
|
|
|
88
77
|
const updateLayer = useCallback(
|
|
89
|
-
(id: string, updates: Partial<Layer
|
|
90
|
-
const newLayers
|
|
91
|
-
|
|
92
|
-
);
|
|
93
|
-
if (silent) {
|
|
94
|
-
// Just update state without pushing to history (hacky but works for init)
|
|
95
|
-
setHistory((prev) => ({ ...prev, present: newLayers }));
|
|
96
|
-
} else {
|
|
97
|
-
pushState(newLayers);
|
|
98
|
-
}
|
|
78
|
+
(id: string, updates: Partial<Layer>) => {
|
|
79
|
+
const newLayers = layers.map((l) => (l.id === id ? { ...l, ...updates } : l) as Layer);
|
|
80
|
+
pushState(newLayers);
|
|
99
81
|
},
|
|
100
82
|
[layers, pushState],
|
|
101
83
|
);
|
|
102
84
|
|
|
103
85
|
const deleteLayer = useCallback(
|
|
104
86
|
(id: string) => {
|
|
105
|
-
|
|
106
|
-
const newLayers = layers.filter((layer) => layer.id !== id);
|
|
87
|
+
const newLayers = layers.filter((l) => l.id !== id);
|
|
107
88
|
pushState(newLayers);
|
|
108
|
-
if (activeLayerId === id)
|
|
109
|
-
setActiveLayerId(newLayers[0]?.id || null);
|
|
110
|
-
}
|
|
89
|
+
if (activeLayerId === id) setActiveLayerId(newLayers[0]?.id || null);
|
|
111
90
|
},
|
|
112
91
|
[layers, activeLayerId, pushState],
|
|
113
92
|
);
|
|
114
93
|
|
|
115
|
-
const undo = useCallback(() => {
|
|
116
|
-
setHistory((prev) => historyManager.undo(prev));
|
|
117
|
-
}, []);
|
|
118
|
-
|
|
119
|
-
const redo = useCallback(() => {
|
|
120
|
-
setHistory((prev) => historyManager.redo(prev));
|
|
121
|
-
}, []);
|
|
122
|
-
|
|
123
|
-
const selectLayer = useCallback((id: string) => {
|
|
124
|
-
setActiveLayerId(id);
|
|
125
|
-
}, []);
|
|
126
|
-
|
|
127
|
-
const bringToFront = useCallback(
|
|
128
|
-
(id: string) => {
|
|
129
|
-
const maxZ = Math.max(...layers.map((l) => l.zIndex), 0);
|
|
130
|
-
updateLayer(id, { zIndex: maxZ + 1 });
|
|
131
|
-
},
|
|
132
|
-
[layers, updateLayer],
|
|
133
|
-
);
|
|
134
|
-
|
|
135
|
-
const captureImage = useCallback(
|
|
136
|
-
async (_viewRef: unknown, backgroundUrl: string) => {
|
|
137
|
-
return backgroundUrl;
|
|
138
|
-
},
|
|
139
|
-
[],
|
|
140
|
-
);
|
|
141
|
-
|
|
142
|
-
const activeLayer = useMemo(
|
|
143
|
-
() => layers.find((l) => l.id === activeLayerId),
|
|
144
|
-
[layers, activeLayerId],
|
|
145
|
-
);
|
|
146
|
-
|
|
147
94
|
return {
|
|
148
95
|
layers: [...layers].sort((a, b) => a.zIndex - b.zIndex),
|
|
149
|
-
activeLayer,
|
|
150
96
|
activeLayerId,
|
|
97
|
+
activeLayer: layers.find((l) => l.id === activeLayerId),
|
|
151
98
|
addTextLayer,
|
|
152
99
|
addStickerLayer,
|
|
153
100
|
updateLayer,
|
|
154
101
|
deleteLayer,
|
|
155
|
-
selectLayer,
|
|
156
|
-
undo,
|
|
157
|
-
redo,
|
|
158
|
-
canUndo,
|
|
159
|
-
canRedo,
|
|
160
|
-
bringToFront,
|
|
102
|
+
selectLayer: setActiveLayerId,
|
|
103
|
+
undo: useCallback(() => setHistory(historyManager.undo), [historyManager]),
|
|
104
|
+
redo: useCallback(() => setHistory(historyManager.redo), [historyManager]),
|
|
105
|
+
canUndo: historyManager.canUndo(history),
|
|
106
|
+
canRedo: historyManager.canRedo(history),
|
|
161
107
|
filters,
|
|
162
108
|
updateFilters: setFilters,
|
|
163
|
-
captureImage,
|
|
164
109
|
};
|
|
165
110
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useRef, useState, useCallback, useEffect } from "react";
|
|
2
2
|
import { BottomSheetModalRef, DesignTokens } from "@umituz/react-native-design-system";
|
|
3
3
|
import { usePhotoEditor } from "./usePhotoEditor";
|
|
4
|
-
import {
|
|
4
|
+
import { TextLayer } from "../types";
|
|
5
5
|
|
|
6
6
|
export const usePhotoEditorUI = (
|
|
7
7
|
initialCaption: string | undefined,
|
|
@@ -11,45 +11,26 @@ export const usePhotoEditorUI = (
|
|
|
11
11
|
const stickerSheetRef = useRef<BottomSheetModalRef>(null);
|
|
12
12
|
const filterSheetRef = useRef<BottomSheetModalRef>(null);
|
|
13
13
|
const layerSheetRef = useRef<BottomSheetModalRef>(null);
|
|
14
|
+
const aiSheetRef = useRef<BottomSheetModalRef>(null);
|
|
14
15
|
|
|
15
|
-
const [selectedFont, setSelectedFont] = useState<string>("
|
|
16
|
+
const [selectedFont, setSelectedFont] = useState<string>("System");
|
|
16
17
|
const [fontSize, setFontSize] = useState(48);
|
|
17
18
|
const [editingText, setEditingText] = useState("");
|
|
18
19
|
const [selectedFilter, setSelectedFilter] = useState("none");
|
|
19
20
|
|
|
20
|
-
const
|
|
21
|
-
layers,
|
|
22
|
-
activeLayerId,
|
|
23
|
-
addTextLayer,
|
|
24
|
-
addStickerLayer,
|
|
25
|
-
updateLayer,
|
|
26
|
-
deleteLayer,
|
|
27
|
-
selectLayer,
|
|
28
|
-
updateFilters,
|
|
29
|
-
filters,
|
|
30
|
-
} = usePhotoEditor([]);
|
|
21
|
+
const editor = usePhotoEditor([]);
|
|
31
22
|
|
|
32
|
-
// Handle initial caption
|
|
33
23
|
useEffect(() => {
|
|
34
24
|
if (initialCaption) {
|
|
35
|
-
const id = addTextLayer(tokens);
|
|
36
|
-
|
|
37
|
-
updateLayer(id, { text: initialCaption } as Partial<Layer>, true);
|
|
38
|
-
});
|
|
25
|
+
const id = editor.addTextLayer(tokens);
|
|
26
|
+
editor.updateLayer(id, { text: initialCaption });
|
|
39
27
|
}
|
|
40
|
-
}, [initialCaption
|
|
41
|
-
|
|
42
|
-
const handleAddText = useCallback(() => {
|
|
43
|
-
addTextLayer(tokens);
|
|
44
|
-
void Promise.resolve().then(() => {
|
|
45
|
-
textEditorSheetRef.current?.present();
|
|
46
|
-
});
|
|
47
|
-
}, [addTextLayer, tokens]);
|
|
28
|
+
}, [initialCaption]);
|
|
48
29
|
|
|
49
30
|
const handleTextLayerTap = useCallback(
|
|
50
31
|
(layerId: string) => {
|
|
51
|
-
selectLayer(layerId);
|
|
52
|
-
const layer = layers.find((l) => l.id === layerId);
|
|
32
|
+
editor.selectLayer(layerId);
|
|
33
|
+
const layer = editor.layers.find((l) => l.id === layerId);
|
|
53
34
|
if (layer?.type === "text") {
|
|
54
35
|
const textLayer = layer as TextLayer;
|
|
55
36
|
setEditingText(textLayer.text || "");
|
|
@@ -57,53 +38,27 @@ export const usePhotoEditorUI = (
|
|
|
57
38
|
textEditorSheetRef.current?.present();
|
|
58
39
|
}
|
|
59
40
|
},
|
|
60
|
-
[
|
|
41
|
+
[editor],
|
|
61
42
|
);
|
|
62
43
|
|
|
63
44
|
const handleSaveText = useCallback(() => {
|
|
64
|
-
if (activeLayerId) {
|
|
65
|
-
updateLayer(activeLayerId, {
|
|
45
|
+
if (editor.activeLayerId) {
|
|
46
|
+
editor.updateLayer(editor.activeLayerId, {
|
|
66
47
|
text: editingText,
|
|
67
48
|
fontSize,
|
|
68
49
|
fontFamily: selectedFont,
|
|
69
|
-
}
|
|
50
|
+
});
|
|
70
51
|
}
|
|
71
52
|
textEditorSheetRef.current?.dismiss();
|
|
72
|
-
}, [activeLayerId, editingText, fontSize, selectedFont, updateLayer
|
|
73
|
-
|
|
74
|
-
const handleSelectFilter = useCallback(
|
|
75
|
-
(filterId: string, value: number) => {
|
|
76
|
-
setSelectedFilter(filterId);
|
|
77
|
-
const base = {
|
|
78
|
-
brightness: 1,
|
|
79
|
-
contrast: 1,
|
|
80
|
-
saturation: 1,
|
|
81
|
-
sepia: 0,
|
|
82
|
-
grayscale: 0,
|
|
83
|
-
};
|
|
84
|
-
if (filterId === "sepia") updateFilters({ ...base, sepia: value });
|
|
85
|
-
else if (filterId === "grayscale")
|
|
86
|
-
updateFilters({ ...base, grayscale: value });
|
|
87
|
-
else updateFilters(base);
|
|
88
|
-
},
|
|
89
|
-
[updateFilters],
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
const handleSelectSticker = useCallback(
|
|
93
|
-
(sticker: string) => {
|
|
94
|
-
addStickerLayer(sticker);
|
|
95
|
-
stickerSheetRef.current?.dismiss();
|
|
96
|
-
},
|
|
97
|
-
[addStickerLayer],
|
|
98
|
-
);
|
|
53
|
+
}, [editor.activeLayerId, editingText, fontSize, selectedFont, editor.updateLayer]);
|
|
99
54
|
|
|
100
55
|
return {
|
|
101
|
-
|
|
56
|
+
...editor,
|
|
102
57
|
textEditorSheetRef,
|
|
103
58
|
stickerSheetRef,
|
|
104
59
|
filterSheetRef,
|
|
105
60
|
layerSheetRef,
|
|
106
|
-
|
|
61
|
+
aiSheetRef,
|
|
107
62
|
selectedFont,
|
|
108
63
|
setSelectedFont,
|
|
109
64
|
fontSize,
|
|
@@ -111,20 +66,20 @@ export const usePhotoEditorUI = (
|
|
|
111
66
|
editingText,
|
|
112
67
|
setEditingText,
|
|
113
68
|
selectedFilter,
|
|
114
|
-
// Domain State
|
|
115
|
-
layers,
|
|
116
|
-
activeLayerId,
|
|
117
|
-
filters,
|
|
118
|
-
// Domain Actions
|
|
119
|
-
updateLayer,
|
|
120
|
-
deleteLayer,
|
|
121
|
-
selectLayer,
|
|
122
|
-
addTextLayer,
|
|
123
|
-
// UI Actions
|
|
124
|
-
handleAddText,
|
|
125
69
|
handleTextLayerTap,
|
|
126
70
|
handleSaveText,
|
|
127
|
-
|
|
128
|
-
|
|
71
|
+
handleAddText: () => {
|
|
72
|
+
editor.addTextLayer(tokens);
|
|
73
|
+
textEditorSheetRef.current?.present();
|
|
74
|
+
},
|
|
75
|
+
handleSelectSticker: (s: string) => {
|
|
76
|
+
editor.addStickerLayer(s);
|
|
77
|
+
stickerSheetRef.current?.dismiss();
|
|
78
|
+
},
|
|
79
|
+
handleSelectFilter: (id: string, val: number) => {
|
|
80
|
+
setSelectedFilter(id);
|
|
81
|
+
editor.updateFilters({ ...editor.filters, [id]: val });
|
|
82
|
+
filterSheetRef.current?.dismiss();
|
|
83
|
+
}
|
|
129
84
|
};
|
|
130
85
|
};
|
package/src/styles.ts
CHANGED
|
@@ -12,89 +12,41 @@ export const createEditorStyles = (tokens: DesignTokens, insets: EdgeInsets) =>
|
|
|
12
12
|
paddingHorizontal: tokens.spacing.md,
|
|
13
13
|
paddingTop: insets.top + tokens.spacing.sm,
|
|
14
14
|
paddingBottom: tokens.spacing.sm,
|
|
15
|
-
|
|
16
|
-
headerButton: {
|
|
17
|
-
width: 48,
|
|
18
|
-
height: 48,
|
|
19
|
-
borderRadius: 24,
|
|
20
|
-
alignItems: "center",
|
|
21
|
-
justifyContent: "center",
|
|
15
|
+
gap: tokens.spacing.md,
|
|
22
16
|
},
|
|
23
17
|
headerTitle: {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
color: tokens.colors.textPrimary,
|
|
27
|
-
},
|
|
28
|
-
postButton: {
|
|
29
|
-
backgroundColor: tokens.colors.primary,
|
|
30
|
-
paddingHorizontal: 20,
|
|
31
|
-
paddingVertical: 10,
|
|
32
|
-
borderRadius: 999,
|
|
18
|
+
flex: 1,
|
|
19
|
+
textAlign: "center",
|
|
33
20
|
},
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
21
|
+
scrollContent: {
|
|
22
|
+
paddingHorizontal: tokens.spacing.md,
|
|
23
|
+
paddingBottom: 120,
|
|
24
|
+
gap: tokens.spacing.lg,
|
|
38
25
|
},
|
|
39
|
-
scrollContent: { paddingHorizontal: tokens.spacing.md, paddingBottom: 120 },
|
|
40
26
|
canvas: {
|
|
41
27
|
width: "100%",
|
|
42
|
-
aspectRatio:
|
|
43
|
-
borderRadius:
|
|
28
|
+
aspectRatio: 1,
|
|
29
|
+
borderRadius: tokens.borders.radius.lg,
|
|
44
30
|
overflow: "hidden",
|
|
45
31
|
backgroundColor: tokens.colors.surfaceVariant,
|
|
46
|
-
marginTop: tokens.spacing.sm,
|
|
47
32
|
},
|
|
48
33
|
canvasImage: { width: "100%", height: "100%" },
|
|
49
|
-
// Slider & Controls
|
|
50
34
|
controlsPanel: {
|
|
51
|
-
marginTop: tokens.spacing.md,
|
|
52
|
-
backgroundColor: tokens.colors.surfaceVariant + "80", // Opacity handled by token if possible, else hex
|
|
53
|
-
borderRadius: 16,
|
|
54
35
|
padding: tokens.spacing.md,
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
sliderRow: {
|
|
59
|
-
flexDirection: "row",
|
|
60
|
-
alignItems: "center",
|
|
61
|
-
justifyContent: "space-between",
|
|
62
|
-
marginBottom: tokens.spacing.sm,
|
|
63
|
-
},
|
|
64
|
-
sliderLabel: {
|
|
65
|
-
flexDirection: "row",
|
|
66
|
-
alignItems: "center",
|
|
67
|
-
gap: tokens.spacing.xs,
|
|
68
|
-
},
|
|
69
|
-
sliderLabelText: { fontSize: 14, color: tokens.colors.textSecondary },
|
|
70
|
-
sliderValue: {
|
|
71
|
-
fontSize: 14,
|
|
72
|
-
fontWeight: "bold",
|
|
73
|
-
color: tokens.colors.primary,
|
|
74
|
-
},
|
|
75
|
-
sliderTrack: {
|
|
76
|
-
height: 6,
|
|
77
|
-
backgroundColor: tokens.colors.border,
|
|
78
|
-
borderRadius: 3,
|
|
79
|
-
marginBottom: tokens.spacing.lg,
|
|
80
|
-
},
|
|
81
|
-
sliderFill: {
|
|
82
|
-
height: "100%",
|
|
83
|
-
width: "65%",
|
|
84
|
-
backgroundColor: tokens.colors.primary,
|
|
85
|
-
borderRadius: 3,
|
|
36
|
+
backgroundColor: tokens.colors.surfaceVariant,
|
|
37
|
+
borderRadius: tokens.borders.radius.md,
|
|
38
|
+
gap: tokens.spacing.md,
|
|
86
39
|
},
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
40
|
+
fontRow: {
|
|
41
|
+
flexDirection: "row",
|
|
42
|
+
gap: tokens.spacing.sm,
|
|
43
|
+
flexWrap: "wrap",
|
|
91
44
|
},
|
|
92
|
-
fontRow: { flexDirection: "row", gap: tokens.spacing.sm },
|
|
93
45
|
fontChip: {
|
|
94
46
|
paddingHorizontal: tokens.spacing.md,
|
|
95
47
|
paddingVertical: tokens.spacing.sm,
|
|
96
|
-
backgroundColor: tokens.colors.
|
|
97
|
-
borderRadius:
|
|
48
|
+
backgroundColor: tokens.colors.surface,
|
|
49
|
+
borderRadius: tokens.borders.radius.sm,
|
|
98
50
|
borderWidth: 1,
|
|
99
51
|
borderColor: tokens.colors.border,
|
|
100
52
|
},
|
|
@@ -102,59 +54,27 @@ export const createEditorStyles = (tokens: DesignTokens, insets: EdgeInsets) =>
|
|
|
102
54
|
backgroundColor: tokens.colors.primary,
|
|
103
55
|
borderColor: tokens.colors.primary,
|
|
104
56
|
},
|
|
105
|
-
fontChipText: {
|
|
106
|
-
fontWeight: "bold",
|
|
107
|
-
fontSize: 14,
|
|
108
|
-
color: tokens.colors.textSecondary,
|
|
109
|
-
},
|
|
110
|
-
fontChipTextActive: { color: tokens.colors.onPrimary },
|
|
111
|
-
// Bottom Toolbar
|
|
112
57
|
bottomToolbar: {
|
|
113
58
|
position: "absolute",
|
|
114
59
|
bottom: insets.bottom + tokens.spacing.md,
|
|
115
|
-
left:
|
|
116
|
-
right:
|
|
117
|
-
backgroundColor: tokens.colors.
|
|
60
|
+
left: tokens.spacing.md,
|
|
61
|
+
right: tokens.spacing.md,
|
|
62
|
+
backgroundColor: tokens.colors.surface,
|
|
118
63
|
borderRadius: 999,
|
|
119
|
-
padding: tokens.spacing.
|
|
64
|
+
padding: tokens.spacing.xs,
|
|
120
65
|
flexDirection: "row",
|
|
121
66
|
justifyContent: "space-around",
|
|
122
67
|
alignItems: "center",
|
|
123
68
|
borderWidth: 1,
|
|
124
69
|
borderColor: tokens.colors.border,
|
|
125
|
-
shadowColor: "#000",
|
|
126
|
-
shadowOffset: {
|
|
127
|
-
width: 0,
|
|
128
|
-
height: 2,
|
|
129
|
-
},
|
|
130
|
-
shadowOpacity: 0.1,
|
|
131
|
-
shadowRadius: 3.84,
|
|
132
|
-
elevation: 5,
|
|
133
70
|
},
|
|
134
71
|
toolButton: {
|
|
135
72
|
alignItems: "center",
|
|
136
73
|
justifyContent: "center",
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
borderRadius: 28,
|
|
140
|
-
},
|
|
141
|
-
toolButtonActive: { backgroundColor: tokens.colors.primary + "20" },
|
|
142
|
-
toolLabel: {
|
|
143
|
-
fontSize: 10,
|
|
144
|
-
fontWeight: "500",
|
|
145
|
-
color: tokens.colors.textSecondary,
|
|
146
|
-
marginTop: 2,
|
|
74
|
+
padding: tokens.spacing.sm,
|
|
75
|
+
borderRadius: 999,
|
|
147
76
|
},
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
width: 64,
|
|
151
|
-
height: 64,
|
|
152
|
-
borderRadius: 32,
|
|
153
|
-
backgroundColor: tokens.colors.primary,
|
|
154
|
-
alignItems: "center",
|
|
155
|
-
justifyContent: "center",
|
|
156
|
-
marginTop: -32,
|
|
157
|
-
borderWidth: 4,
|
|
158
|
-
borderColor: tokens.colors.background,
|
|
77
|
+
toolButtonActive: {
|
|
78
|
+
backgroundColor: tokens.colors.primary + "20",
|
|
159
79
|
},
|
|
160
80
|
});
|