@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,106 +0,0 @@
1
- import React from "react";
2
- import { View, StyleSheet } from "react-native";
3
- import { Image } from "expo-image";
4
- import { DraggableText, LayerTransform } from "./DraggableText";
5
- import { DraggableSticker } from "./DraggableSticker";
6
- import { Layer, ImageFilters } from "../types";
7
-
8
- interface EditorCanvasProps {
9
- imageUrl: string;
10
- layers: Layer[];
11
- activeLayerId: string | null;
12
- filters: ImageFilters;
13
- onLayerTap: (layerId: string) => void;
14
- onLayerTransform: (layerId: string, transform: LayerTransform) => void;
15
- styles: {
16
- canvas: object;
17
- canvasImage: object;
18
- };
19
- }
20
-
21
- export const EditorCanvas: React.FC<EditorCanvasProps> = ({
22
- imageUrl,
23
- layers,
24
- activeLayerId,
25
- filters,
26
- onLayerTap,
27
- onLayerTransform,
28
- styles: externalStyles,
29
- }) => {
30
- // Basic brightness preview: dark overlay for < 1, light for > 1
31
- const brightness = filters.brightness ?? 1;
32
- const brightnessOverlay =
33
- brightness < 1
34
- ? { color: "black", opacity: Math.min(0.6, 1 - brightness) }
35
- : brightness > 1
36
- ? { color: "white", opacity: Math.min(0.4, brightness - 1) }
37
- : null;
38
-
39
- return (
40
- <View style={externalStyles.canvas}>
41
- <Image
42
- source={{ uri: imageUrl }}
43
- style={externalStyles.canvasImage}
44
- contentFit="cover"
45
- />
46
-
47
- {/* Brightness visual overlay */}
48
- {brightnessOverlay && (
49
- <View
50
- style={[
51
- StyleSheet.absoluteFill,
52
- {
53
- backgroundColor: brightnessOverlay.color,
54
- opacity: brightnessOverlay.opacity,
55
- },
56
- ]}
57
- pointerEvents="none"
58
- />
59
- )}
60
-
61
- {layers.map((layer) => {
62
- if (layer.type === "text") {
63
- return (
64
- <DraggableText
65
- key={layer.id}
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}
79
- onTransformEnd={(t) => onLayerTransform(layer.id, t)}
80
- onPress={() => onLayerTap(layer.id)}
81
- isSelected={activeLayerId === layer.id}
82
- />
83
- );
84
- } else if (layer.type === "sticker") {
85
- return (
86
- <DraggableSticker
87
- key={layer.id}
88
- uri={layer.uri}
89
- initialX={layer.x}
90
- initialY={layer.y}
91
- rotation={layer.rotation}
92
- scale={layer.scale}
93
- opacity={layer.opacity}
94
- onTransformEnd={(t) => onLayerTransform(layer.id, t)}
95
- onPress={() => onLayerTap(layer.id)}
96
- isSelected={activeLayerId === layer.id}
97
- />
98
- );
99
- }
100
- return null;
101
- })}
102
- </View>
103
- );
104
- };
105
-
106
- export default React.memo(EditorCanvas);
@@ -1,155 +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
-
6
- interface EditorToolbarProps {
7
- onAddText: () => void;
8
- onAddSticker?: () => void;
9
- onOpenFilters?: () => void;
10
- onOpenAdjustments?: () => void;
11
- onOpenLayers: () => void;
12
- onAIMagic?: () => void;
13
- onUndo?: () => void;
14
- onRedo?: () => void;
15
- canUndo?: boolean;
16
- canRedo?: boolean;
17
- t: (key: string) => string;
18
- styles: {
19
- bottomToolbar: object;
20
- toolButton: object;
21
- toolButtonActive: object;
22
- aiMagicButton: object;
23
- [key: string]: object;
24
- };
25
- }
26
-
27
- const ToolButton = ({
28
- icon,
29
- label,
30
- onPress,
31
- isActive,
32
- disabled,
33
- parentStyles,
34
- }: {
35
- icon: string;
36
- label: string;
37
- onPress: () => void;
38
- isActive?: boolean;
39
- disabled?: boolean;
40
- parentStyles: EditorToolbarProps["styles"];
41
- }) => (
42
- <TouchableOpacity
43
- style={[parentStyles.toolButton, isActive && parentStyles.toolButtonActive]}
44
- onPress={onPress}
45
- disabled={disabled}
46
- accessibilityLabel={label}
47
- accessibilityRole="button"
48
- accessibilityState={{ selected: isActive, disabled }}
49
- >
50
- <AtomicIcon
51
- name={icon as "edit"}
52
- size="md"
53
- color={disabled ? "textSecondary" : isActive ? "primary" : "textSecondary"}
54
- />
55
- <AtomicText
56
- type="labelSmall"
57
- color={disabled ? "textSecondary" : isActive ? "primary" : "textSecondary"}
58
- >
59
- {label}
60
- </AtomicText>
61
- </TouchableOpacity>
62
- );
63
-
64
- export const EditorToolbar: React.FC<EditorToolbarProps> = ({
65
- onAddText,
66
- onAddSticker,
67
- onOpenFilters,
68
- onOpenAdjustments,
69
- onOpenLayers,
70
- onAIMagic,
71
- onUndo,
72
- onRedo,
73
- canUndo = false,
74
- canRedo = false,
75
- styles: parentStyles,
76
- t,
77
- }) => {
78
- const tokens = useAppDesignTokens();
79
-
80
- return (
81
- <View style={parentStyles.bottomToolbar}>
82
- {onUndo && (
83
- <ToolButton
84
- icon="arrow-back"
85
- label={t("photo_editor.undo") || "Undo"}
86
- onPress={onUndo}
87
- disabled={!canUndo}
88
- parentStyles={parentStyles}
89
- />
90
- )}
91
-
92
- <ToolButton
93
- icon="edit"
94
- label={t("photo_editor.text") || "Text"}
95
- onPress={onAddText}
96
- parentStyles={parentStyles}
97
- />
98
-
99
- {onAddSticker && (
100
- <ToolButton
101
- icon="sparkles"
102
- label={t("photo_editor.sticker") || "Sticker"}
103
- onPress={onAddSticker}
104
- parentStyles={parentStyles}
105
- />
106
- )}
107
-
108
- {onAIMagic && (
109
- <TouchableOpacity
110
- style={parentStyles.aiMagicButton}
111
- onPress={onAIMagic}
112
- accessibilityLabel="AI Magic"
113
- accessibilityRole="button"
114
- >
115
- <AtomicIcon name="sparkles" size="lg" customColor={tokens.colors.onPrimary} />
116
- </TouchableOpacity>
117
- )}
118
-
119
- {onOpenAdjustments && (
120
- <ToolButton
121
- icon="flash"
122
- label={t("photo_editor.adjust") || "Adjust"}
123
- onPress={onOpenAdjustments}
124
- parentStyles={parentStyles}
125
- />
126
- )}
127
-
128
- {onOpenFilters && (
129
- <ToolButton
130
- icon="brush"
131
- label={t("photo_editor.filters") || "Filters"}
132
- onPress={onOpenFilters}
133
- parentStyles={parentStyles}
134
- />
135
- )}
136
-
137
- <ToolButton
138
- icon="copy"
139
- label={t("photo_editor.layers") || "Layers"}
140
- onPress={onOpenLayers}
141
- parentStyles={parentStyles}
142
- />
143
-
144
- {onRedo && (
145
- <ToolButton
146
- icon="chevron-forward"
147
- label={t("photo_editor.redo") || "Redo"}
148
- onPress={onRedo}
149
- disabled={!canRedo}
150
- parentStyles={parentStyles}
151
- />
152
- )}
153
- </View>
154
- );
155
- };
@@ -1,73 +0,0 @@
1
- import React, { useMemo } from "react";
2
- import { View, TouchableOpacity, StyleSheet } 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 { DEFAULT_FILTERS, type FilterOption } from "../constants";
6
-
7
- interface FilterPickerProps {
8
- selectedFilter: string;
9
- onSelectFilter: (option: FilterOption) => void;
10
- filters?: FilterOption[];
11
- }
12
-
13
- export const FilterPicker: React.FC<FilterPickerProps> = ({
14
- selectedFilter,
15
- onSelectFilter,
16
- filters = DEFAULT_FILTERS,
17
- }) => {
18
- const tokens = useAppDesignTokens();
19
-
20
- const styles = useMemo(() => StyleSheet.create({
21
- container: { padding: tokens.spacing.md, gap: tokens.spacing.md },
22
- grid: { flexDirection: "row", flexWrap: "wrap", gap: tokens.spacing.sm },
23
- filter: {
24
- width: 75,
25
- height: 75,
26
- borderRadius: tokens.borders.radius.md,
27
- backgroundColor: tokens.colors.surfaceVariant,
28
- alignItems: "center",
29
- justifyContent: "center",
30
- borderWidth: 2,
31
- borderColor: "transparent",
32
- },
33
- active: {
34
- borderColor: tokens.colors.primary,
35
- backgroundColor: tokens.colors.primary + "10",
36
- },
37
- }), [tokens]);
38
-
39
- return (
40
- <View style={styles.container}>
41
- <AtomicText type="headlineSmall">Filters</AtomicText>
42
- <View style={styles.grid}>
43
- {filters.map((f) => {
44
- const isActive = selectedFilter === f.id;
45
- return (
46
- <TouchableOpacity
47
- key={f.id}
48
- style={[styles.filter, isActive && styles.active]}
49
- onPress={() => onSelectFilter(f)}
50
- accessibilityLabel={f.name}
51
- accessibilityRole="button"
52
- accessibilityState={{ selected: isActive }}
53
- >
54
- <AtomicIcon
55
- name={f.icon as "close"}
56
- size="lg"
57
- color={isActive ? "primary" : "textSecondary"}
58
- />
59
- <AtomicText
60
- type="labelSmall"
61
- color={isActive ? "primary" : "textSecondary"}
62
- >
63
- {f.name}
64
- </AtomicText>
65
- </TouchableOpacity>
66
- );
67
- })}
68
- </View>
69
- </View>
70
- );
71
- };
72
-
73
- export default React.memo(FilterPicker);
@@ -1,132 +0,0 @@
1
- import React, { useMemo } from "react";
2
- import { View, ScrollView, TouchableOpacity, StyleSheet } 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
-
6
- interface FontControlsProps {
7
- fontSize: number;
8
- selectedFont: string;
9
- fonts: readonly string[];
10
- onFontSizeChange: (size: number) => void;
11
- onFontSelect: (font: string) => void;
12
- styles: {
13
- controlsPanel: object;
14
- fontRow: object;
15
- fontChip: object;
16
- fontChipActive: object;
17
- [key: string]: object;
18
- };
19
- }
20
-
21
- const MIN_FONT_SIZE = 12;
22
- const MAX_FONT_SIZE = 128;
23
- const FONT_SIZE_STEP = 4;
24
-
25
- export const FontControls: React.FC<FontControlsProps> = ({
26
- fontSize,
27
- selectedFont,
28
- fonts,
29
- onFontSizeChange,
30
- onFontSelect,
31
- styles: externalStyles,
32
- }) => {
33
- const tokens = useAppDesignTokens();
34
-
35
- const styles = useMemo(() => StyleSheet.create({
36
- stepRow: {
37
- flexDirection: "row",
38
- gap: tokens.spacing.sm,
39
- marginBottom: tokens.spacing.md,
40
- },
41
- stepBtn: {
42
- padding: tokens.spacing.sm,
43
- backgroundColor: tokens.colors.surface,
44
- borderRadius: tokens.borders.radius.sm,
45
- borderWidth: 1,
46
- borderColor: tokens.colors.border,
47
- minWidth: 44,
48
- alignItems: "center",
49
- },
50
- sizeRow: {
51
- flexDirection: "row",
52
- alignItems: "center",
53
- justifyContent: "space-between",
54
- marginBottom: tokens.spacing.sm,
55
- },
56
- sizeLabel: {
57
- flexDirection: "row",
58
- alignItems: "center",
59
- gap: tokens.spacing.xs,
60
- },
61
- }), [tokens]);
62
-
63
- return (
64
- <View style={externalStyles.controlsPanel}>
65
- <View style={styles.sizeRow}>
66
- <View style={styles.sizeLabel}>
67
- <AtomicIcon name="edit" size="sm" color="textSecondary" />
68
- <AtomicText type="labelMedium" color="textSecondary">
69
- Text Size
70
- </AtomicText>
71
- </View>
72
- <AtomicText fontWeight="bold" color="primary">
73
- {fontSize}px
74
- </AtomicText>
75
- </View>
76
-
77
- <View style={styles.stepRow}>
78
- <TouchableOpacity
79
- style={styles.stepBtn}
80
- onPress={() => onFontSizeChange(Math.max(MIN_FONT_SIZE, fontSize - FONT_SIZE_STEP))}
81
- accessibilityLabel="Decrease font size"
82
- accessibilityRole="button"
83
- >
84
- <AtomicText fontWeight="bold">−</AtomicText>
85
- </TouchableOpacity>
86
- <TouchableOpacity
87
- style={styles.stepBtn}
88
- onPress={() => onFontSizeChange(Math.min(MAX_FONT_SIZE, fontSize + FONT_SIZE_STEP))}
89
- accessibilityLabel="Increase font size"
90
- accessibilityRole="button"
91
- >
92
- <AtomicText fontWeight="bold">+</AtomicText>
93
- </TouchableOpacity>
94
- </View>
95
-
96
- <AtomicText
97
- type="labelMedium"
98
- color="textSecondary"
99
- style={{ marginBottom: tokens.spacing.xs }}
100
- >
101
- Font Style
102
- </AtomicText>
103
- <ScrollView horizontal showsHorizontalScrollIndicator={false}>
104
- <View style={externalStyles.fontRow}>
105
- {fonts.map((font) => (
106
- <TouchableOpacity
107
- key={font}
108
- style={[
109
- externalStyles.fontChip,
110
- selectedFont === font && externalStyles.fontChipActive,
111
- ]}
112
- onPress={() => onFontSelect(font)}
113
- accessibilityLabel={`Font: ${font}`}
114
- accessibilityRole="button"
115
- accessibilityState={{ selected: selectedFont === font }}
116
- >
117
- <AtomicText
118
- fontWeight="bold"
119
- color={selectedFont === font ? "onPrimary" : "textSecondary"}
120
- style={{ fontFamily: font === "System" ? undefined : font }}
121
- >
122
- {font}
123
- </AtomicText>
124
- </TouchableOpacity>
125
- ))}
126
- </View>
127
- </ScrollView>
128
- </View>
129
- );
130
- };
131
-
132
- export default React.memo(FontControls);
@@ -1,164 +0,0 @@
1
- import React, { useMemo } from "react";
2
- import { View, ScrollView, TouchableOpacity, StyleSheet } 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 { Layer, TextLayer } from "../types";
6
-
7
- interface LayerManagerProps {
8
- layers: Layer[];
9
- activeLayerId: string | null;
10
- onSelectLayer: (id: string) => void;
11
- onDeleteLayer: (id: string) => void;
12
- onDuplicateLayer?: (id: string) => void;
13
- onMoveLayerUp?: (id: string) => void;
14
- onMoveLayerDown?: (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
- onDuplicateLayer,
24
- onMoveLayerUp,
25
- onMoveLayerDown,
26
- t,
27
- }) => {
28
- const tokens = useAppDesignTokens();
29
-
30
- const styles = useMemo(() => StyleSheet.create({
31
- container: { padding: tokens.spacing.md, gap: tokens.spacing.md },
32
- item: {
33
- flexDirection: "row",
34
- alignItems: "center",
35
- padding: tokens.spacing.sm,
36
- backgroundColor: tokens.colors.surfaceVariant,
37
- borderRadius: tokens.borders.radius.md,
38
- marginBottom: tokens.spacing.xs,
39
- borderWidth: 2,
40
- borderColor: "transparent",
41
- },
42
- active: {
43
- borderColor: tokens.colors.primary,
44
- backgroundColor: tokens.colors.primary + "10",
45
- },
46
- info: { flex: 1, marginLeft: tokens.spacing.sm },
47
- actions: {
48
- flexDirection: "row",
49
- alignItems: "center",
50
- gap: tokens.spacing.xs,
51
- },
52
- actionBtn: {
53
- padding: tokens.spacing.xs,
54
- borderRadius: tokens.borders.radius.sm,
55
- },
56
- }), [tokens]);
57
-
58
- const sortedLayers = [...layers].reverse(); // top layer first in list
59
-
60
- return (
61
- <View style={styles.container}>
62
- <AtomicText type="headlineSmall">Layers</AtomicText>
63
- <ScrollView showsVerticalScrollIndicator={false}>
64
- {sortedLayers.length === 0 ? (
65
- <AtomicText
66
- color="textSecondary"
67
- style={{ textAlign: "center", padding: tokens.spacing.xl }}
68
- >
69
- No layers yet
70
- </AtomicText>
71
- ) : (
72
- sortedLayers.map((layer, idx) => {
73
- const isActive = activeLayerId === layer.id;
74
- const label =
75
- layer.type === "text"
76
- ? (layer as TextLayer).text || t("photo_editor.untitled") || "Untitled"
77
- : "Sticker";
78
- const isTop = idx === 0;
79
- const isBottom = idx === sortedLayers.length - 1;
80
-
81
- return (
82
- <TouchableOpacity
83
- key={layer.id}
84
- style={[styles.item, isActive && styles.active]}
85
- onPress={() => onSelectLayer(layer.id)}
86
- accessibilityLabel={`${layer.type} layer: ${label}`}
87
- accessibilityRole="button"
88
- accessibilityState={{ selected: isActive }}
89
- >
90
- <AtomicIcon
91
- name={layer.type === "text" ? "edit" : "image"}
92
- size="sm"
93
- color={isActive ? "primary" : "textSecondary"}
94
- />
95
- <View style={styles.info}>
96
- <AtomicText type="labelSmall" color="textSecondary">
97
- {layer.type.toUpperCase()}
98
- </AtomicText>
99
- <AtomicText fontWeight="bold" numberOfLines={1}>
100
- {label}
101
- </AtomicText>
102
- </View>
103
-
104
- <View style={styles.actions}>
105
- {onMoveLayerUp && (
106
- <TouchableOpacity
107
- style={styles.actionBtn}
108
- onPress={() => onMoveLayerUp(layer.id)}
109
- disabled={isTop}
110
- accessibilityLabel="Move layer up"
111
- accessibilityRole="button"
112
- >
113
- <AtomicIcon
114
- name="chevron-forward"
115
- size="sm"
116
- color={isTop ? "textSecondary" : "textPrimary"}
117
- />
118
- </TouchableOpacity>
119
- )}
120
-
121
- {onMoveLayerDown && (
122
- <TouchableOpacity
123
- style={styles.actionBtn}
124
- onPress={() => onMoveLayerDown(layer.id)}
125
- disabled={isBottom}
126
- accessibilityLabel="Move layer down"
127
- accessibilityRole="button"
128
- >
129
- <AtomicIcon
130
- name="chevron-back"
131
- size="sm"
132
- color={isBottom ? "textSecondary" : "textPrimary"}
133
- />
134
- </TouchableOpacity>
135
- )}
136
-
137
- {onDuplicateLayer && (
138
- <TouchableOpacity
139
- style={styles.actionBtn}
140
- onPress={() => onDuplicateLayer(layer.id)}
141
- accessibilityLabel={`Duplicate ${label}`}
142
- accessibilityRole="button"
143
- >
144
- <AtomicIcon name="copy" size="sm" color="textSecondary" />
145
- </TouchableOpacity>
146
- )}
147
-
148
- <TouchableOpacity
149
- style={styles.actionBtn}
150
- onPress={() => onDeleteLayer(layer.id)}
151
- accessibilityLabel={`Delete ${label}`}
152
- accessibilityRole="button"
153
- >
154
- <AtomicIcon name="trash-outline" size="sm" color="error" />
155
- </TouchableOpacity>
156
- </View>
157
- </TouchableOpacity>
158
- );
159
- })
160
- )}
161
- </ScrollView>
162
- </View>
163
- );
164
- };