@umituz/react-native-video-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.
Files changed (97) hide show
  1. package/README.md +92 -0
  2. package/package.json +48 -0
  3. package/src/domain/entities/index.ts +50 -0
  4. package/src/domain/entities/video-project.types.ts +153 -0
  5. package/src/index.ts +100 -0
  6. package/src/infrastructure/constants/animation-layer.constants.ts +32 -0
  7. package/src/infrastructure/constants/audio-layer.constants.ts +14 -0
  8. package/src/infrastructure/constants/export.constants.ts +28 -0
  9. package/src/infrastructure/constants/image-layer.constants.ts +12 -0
  10. package/src/infrastructure/constants/index.ts +11 -0
  11. package/src/infrastructure/constants/shape-layer.constants.ts +29 -0
  12. package/src/infrastructure/constants/text-layer.constants.ts +40 -0
  13. package/src/infrastructure/services/export-orchestrator.service.ts +122 -0
  14. package/src/infrastructure/services/image-layer-operations.service.ts +108 -0
  15. package/src/infrastructure/services/layer-manipulation.service.ts +93 -0
  16. package/src/infrastructure/services/layer-operations/index.ts +9 -0
  17. package/src/infrastructure/services/layer-operations/layer-delete.service.ts +47 -0
  18. package/src/infrastructure/services/layer-operations/layer-duplicate.service.ts +66 -0
  19. package/src/infrastructure/services/layer-operations/layer-order.service.ts +82 -0
  20. package/src/infrastructure/services/layer-operations/layer-transform.service.ts +160 -0
  21. package/src/infrastructure/services/layer-operations.service.ts +198 -0
  22. package/src/infrastructure/services/scene-operations.service.ts +166 -0
  23. package/src/infrastructure/services/shape-layer-operations.service.ts +65 -0
  24. package/src/infrastructure/services/text-layer-operations.service.ts +114 -0
  25. package/src/presentation/components/AnimationEditor.tsx +103 -0
  26. package/src/presentation/components/AudioEditor.tsx +144 -0
  27. package/src/presentation/components/DraggableLayer.tsx +110 -0
  28. package/src/presentation/components/EditorHeader.tsx +107 -0
  29. package/src/presentation/components/EditorPreviewArea.tsx +221 -0
  30. package/src/presentation/components/EditorTimeline.tsx +136 -0
  31. package/src/presentation/components/EditorToolPanel.tsx +180 -0
  32. package/src/presentation/components/ExportDialog.tsx +135 -0
  33. package/src/presentation/components/ImageLayerEditor.tsx +95 -0
  34. package/src/presentation/components/LayerActionsMenu.tsx +197 -0
  35. package/src/presentation/components/SceneActionsMenu.tsx +69 -0
  36. package/src/presentation/components/ShapeLayerEditor.tsx +108 -0
  37. package/src/presentation/components/TextLayerEditor.tsx +104 -0
  38. package/src/presentation/components/animation-layer/AnimationEditorActions.tsx +104 -0
  39. package/src/presentation/components/animation-layer/AnimationInfoBanner.tsx +43 -0
  40. package/src/presentation/components/animation-layer/AnimationTypeSelector.tsx +105 -0
  41. package/src/presentation/components/animation-layer/index.ts +8 -0
  42. package/src/presentation/components/audio-layer/AudioEditorActions.tsx +115 -0
  43. package/src/presentation/components/audio-layer/AudioFileSelector.tsx +126 -0
  44. package/src/presentation/components/audio-layer/FadeEffectsSelector.tsx +151 -0
  45. package/src/presentation/components/audio-layer/InfoBanner.tsx +43 -0
  46. package/src/presentation/components/audio-layer/VolumeSelector.tsx +98 -0
  47. package/src/presentation/components/audio-layer/index.ts +10 -0
  48. package/src/presentation/components/draggable-layer/LayerContent.tsx +106 -0
  49. package/src/presentation/components/draggable-layer/ResizeHandles.tsx +97 -0
  50. package/src/presentation/components/draggable-layer/index.ts +7 -0
  51. package/src/presentation/components/export/ExportActions.tsx +101 -0
  52. package/src/presentation/components/export/ExportInfoBanner.tsx +44 -0
  53. package/src/presentation/components/export/ExportProgress.tsx +114 -0
  54. package/src/presentation/components/export/OptionSelectorRow.tsx +101 -0
  55. package/src/presentation/components/export/ProjectInfoBox.tsx +61 -0
  56. package/src/presentation/components/export/WatermarkToggle.tsx +87 -0
  57. package/src/presentation/components/export/index.ts +11 -0
  58. package/src/presentation/components/image-layer/ImagePreview.tsx +70 -0
  59. package/src/presentation/components/image-layer/ImageSelectionButtons.tsx +82 -0
  60. package/src/presentation/components/image-layer/OpacitySelector.tsx +91 -0
  61. package/src/presentation/components/image-layer/index.ts +8 -0
  62. package/src/presentation/components/index.ts +17 -0
  63. package/src/presentation/components/shape-layer/ColorPickerHorizontal.tsx +92 -0
  64. package/src/presentation/components/shape-layer/ShapePreview.tsx +57 -0
  65. package/src/presentation/components/shape-layer/ShapeTypeSelector.tsx +102 -0
  66. package/src/presentation/components/shape-layer/ValueSelector.tsx +106 -0
  67. package/src/presentation/components/shape-layer/index.ts +9 -0
  68. package/src/presentation/components/text-layer/ColorPicker.tsx +91 -0
  69. package/src/presentation/components/text-layer/EditorActions.tsx +95 -0
  70. package/src/presentation/components/text-layer/FontSizeSelector.tsx +86 -0
  71. package/src/presentation/components/text-layer/OptionSelector.tsx +98 -0
  72. package/src/presentation/components/text-layer/TextAlignSelector.tsx +87 -0
  73. package/src/presentation/components/text-layer/TextInputSection.tsx +70 -0
  74. package/src/presentation/components/text-layer/TextPreview.tsx +71 -0
  75. package/src/presentation/components/text-layer/index.ts +12 -0
  76. package/src/presentation/hooks/useAnimationLayerForm.ts +72 -0
  77. package/src/presentation/hooks/useAudioLayerForm.ts +76 -0
  78. package/src/presentation/hooks/useDraggableLayerGestures.ts +166 -0
  79. package/src/presentation/hooks/useEditorActions.tsx +93 -0
  80. package/src/presentation/hooks/useEditorBottomSheet.ts +43 -0
  81. package/src/presentation/hooks/useEditorHistory.ts +80 -0
  82. package/src/presentation/hooks/useEditorLayers.ts +97 -0
  83. package/src/presentation/hooks/useEditorPlayback.ts +90 -0
  84. package/src/presentation/hooks/useEditorScenes.ts +106 -0
  85. package/src/presentation/hooks/useExport.ts +67 -0
  86. package/src/presentation/hooks/useExportActions.tsx +51 -0
  87. package/src/presentation/hooks/useExportForm.ts +96 -0
  88. package/src/presentation/hooks/useImageLayerForm.ts +57 -0
  89. package/src/presentation/hooks/useImageLayerOperations.ts +71 -0
  90. package/src/presentation/hooks/useLayerActions.tsx +162 -0
  91. package/src/presentation/hooks/useLayerManipulation.ts +178 -0
  92. package/src/presentation/hooks/useMenuActions.tsx +92 -0
  93. package/src/presentation/hooks/useSceneActions.tsx +81 -0
  94. package/src/presentation/hooks/useShapeLayerForm.ts +84 -0
  95. package/src/presentation/hooks/useShapeLayerOperations.ts +52 -0
  96. package/src/presentation/hooks/useTextLayerForm.ts +100 -0
  97. package/src/presentation/hooks/useTextLayerOperations.ts +74 -0
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Editor Tool Panel Component
3
+ * Single Responsibility: Display editor tool buttons
4
+ */
5
+
6
+ import React from "react";
7
+ import {
8
+ View,
9
+ ScrollView,
10
+ TouchableOpacity,
11
+ StyleSheet,
12
+ Alert,
13
+ } from "react-native";
14
+ import {
15
+ AtomicText,
16
+ AtomicIcon,
17
+ useAppDesignTokens,
18
+ } from "@umituz/react-native-design-system";
19
+ import { useLocalization } from "@umituz/react-native-localization";
20
+ import type { Audio } from "@domains/video";
21
+
22
+ export interface EditorToolPanelProps {
23
+ onAddText: () => void;
24
+ onAddImage: () => void;
25
+ onAddShape: () => void;
26
+ onAudio: () => void;
27
+ hasAudio: boolean;
28
+ }
29
+
30
+ export const EditorToolPanel: React.FC<EditorToolPanelProps> = ({
31
+ onAddText,
32
+ onAddImage,
33
+ onAddShape,
34
+ onAudio,
35
+ hasAudio,
36
+ }) => {
37
+ const { t } = useLocalization();
38
+ const tokens = useAppDesignTokens();
39
+
40
+ return (
41
+ <View
42
+ style={[styles.toolPanel, { backgroundColor: tokens.colors.surface }]}
43
+ >
44
+ <AtomicText
45
+ type="bodyMedium"
46
+ style={{
47
+ color: tokens.colors.textPrimary,
48
+ fontWeight: "600",
49
+ marginBottom: 12,
50
+ }}
51
+ >
52
+ {t("editor.tools.title")}
53
+ </AtomicText>
54
+
55
+ <ScrollView horizontal showsHorizontalScrollIndicator={false}>
56
+ <TouchableOpacity
57
+ style={[
58
+ styles.toolButton,
59
+ { backgroundColor: tokens.colors.backgroundPrimary },
60
+ ]}
61
+ onPress={onAddText}
62
+ >
63
+ <AtomicIcon name="Type" size="md" color="primary" />
64
+ <AtomicText
65
+ type="labelSmall"
66
+ style={{ color: tokens.colors.textPrimary, marginTop: 4 }}
67
+ >
68
+ {t("editor.tools.text")}
69
+ </AtomicText>
70
+ </TouchableOpacity>
71
+
72
+ <TouchableOpacity
73
+ style={[
74
+ styles.toolButton,
75
+ { backgroundColor: tokens.colors.backgroundPrimary },
76
+ ]}
77
+ onPress={onAddImage}
78
+ >
79
+ <AtomicIcon name="image-outline" size="md" color="primary" />
80
+ <AtomicText
81
+ type="labelSmall"
82
+ style={{ color: tokens.colors.textPrimary, marginTop: 4 }}
83
+ >
84
+ {t("editor.tools.image")}
85
+ </AtomicText>
86
+ </TouchableOpacity>
87
+
88
+ <TouchableOpacity
89
+ style={[
90
+ styles.toolButton,
91
+ { backgroundColor: tokens.colors.backgroundPrimary },
92
+ ]}
93
+ onPress={onAddShape}
94
+ >
95
+ <AtomicIcon name="Square" size="md" color="primary" />
96
+ <AtomicText
97
+ type="labelSmall"
98
+ style={{ color: tokens.colors.textPrimary, marginTop: 4 }}
99
+ >
100
+ {t("editor.tools.shape")}
101
+ </AtomicText>
102
+ </TouchableOpacity>
103
+
104
+ <TouchableOpacity
105
+ style={[
106
+ styles.toolButton,
107
+ { backgroundColor: tokens.colors.backgroundPrimary },
108
+ ]}
109
+ onPress={onAudio}
110
+ >
111
+ <AtomicIcon name="Music" size="md" color="primary" />
112
+ <AtomicText
113
+ type="labelSmall"
114
+ style={{ color: tokens.colors.textPrimary, marginTop: 4 }}
115
+ >
116
+ {t("editor.tools.audio")}
117
+ </AtomicText>
118
+ {hasAudio && (
119
+ <View
120
+ style={[
121
+ styles.audioBadge,
122
+ {
123
+ backgroundColor: tokens.colors.success,
124
+ borderColor: tokens.colors.surface,
125
+ },
126
+ ]}
127
+ />
128
+ )}
129
+ </TouchableOpacity>
130
+
131
+ <TouchableOpacity
132
+ style={[
133
+ styles.toolButton,
134
+ { backgroundColor: tokens.colors.backgroundPrimary },
135
+ ]}
136
+ onPress={() =>
137
+ Alert.alert(
138
+ t("editor.tools.effects", "Effects"),
139
+ t("editor.tools.effectsComingSoon", "Coming soon!"),
140
+ )
141
+ }
142
+ >
143
+ <AtomicIcon name="sparkles-outline" size="md" color="primary" />
144
+ <AtomicText
145
+ type="labelSmall"
146
+ style={{ color: tokens.colors.textPrimary, marginTop: 4 }}
147
+ >
148
+ {t("editor.tools.effects")}
149
+ </AtomicText>
150
+ </TouchableOpacity>
151
+ </ScrollView>
152
+ </View>
153
+ );
154
+ };
155
+
156
+ const styles = StyleSheet.create({
157
+ toolPanel: {
158
+ padding: 16,
159
+ marginHorizontal: 16,
160
+ marginBottom: 16,
161
+ borderRadius: 12,
162
+ },
163
+ toolButton: {
164
+ width: 80,
165
+ height: 80,
166
+ borderRadius: 12,
167
+ alignItems: "center",
168
+ justifyContent: "center",
169
+ marginRight: 12,
170
+ },
171
+ audioBadge: {
172
+ position: "absolute",
173
+ top: 8,
174
+ right: 8,
175
+ width: 10,
176
+ height: 10,
177
+ borderRadius: 5,
178
+ borderWidth: 2,
179
+ },
180
+ });
@@ -0,0 +1,135 @@
1
+ /**
2
+ * ExportDialog Component
3
+ * Main component for video export dialog
4
+ */
5
+
6
+ import React, { useCallback } from "react";
7
+ import { View, ScrollView, StyleSheet } from "react-native";
8
+ import { useLocalization } from "@umituz/react-native-localization";
9
+ import type { ExportSettings, VideoProject } from "@domains/video";
10
+ import { useExportForm } from "../../hooks/useExportForm";
11
+ import { useExport } from "../../hooks/useExport";
12
+ import {
13
+ RESOLUTIONS,
14
+ QUALITIES,
15
+ FORMATS,
16
+ } from "../../constants/export.constants";
17
+ import {
18
+ ProjectInfoBox,
19
+ OptionSelectorRow,
20
+ WatermarkToggle,
21
+ ExportProgress,
22
+ ExportInfoBanner,
23
+ ExportActions,
24
+ } from "./export";
25
+
26
+ interface ExportDialogProps {
27
+ project: VideoProject;
28
+ onExport: (settings: ExportSettings, uri?: string) => void;
29
+ onCancel: () => void;
30
+ }
31
+
32
+ export const ExportDialog: React.FC<ExportDialogProps> = ({
33
+ project,
34
+ onExport,
35
+ onCancel,
36
+ }) => {
37
+ const { t } = useLocalization();
38
+ const {
39
+ formState,
40
+ setResolution,
41
+ setQuality,
42
+ setFormat,
43
+ setIncludeWatermark,
44
+ buildExportSettings,
45
+ estimatedSize,
46
+ projectDuration,
47
+ } = useExportForm(project);
48
+
49
+ const { isExporting, exportProgress, exportVideo, resetExport } = useExport();
50
+
51
+ const handleExport = useCallback(async () => {
52
+ const settings = buildExportSettings();
53
+ const result = await exportVideo(project, settings);
54
+
55
+ if (result.success) {
56
+ onExport(settings, result.uri);
57
+ } else {
58
+ resetExport();
59
+ }
60
+ }, [project, buildExportSettings, exportVideo, onExport, resetExport]);
61
+
62
+ const resolutionOptions = RESOLUTIONS.map((res) => ({
63
+ value: res,
64
+ label: res,
65
+ textTransform: "none" as const,
66
+ }));
67
+
68
+ const qualityOptions = QUALITIES.map((qual) => ({
69
+ value: qual,
70
+ label: qual,
71
+ textTransform: "capitalize" as const,
72
+ }));
73
+
74
+ const formatOptions = FORMATS.map((fmt) => ({
75
+ value: fmt,
76
+ label: fmt,
77
+ textTransform: "uppercase" as const,
78
+ }));
79
+
80
+ return (
81
+ <View style={styles.container}>
82
+ <ScrollView showsVerticalScrollIndicator={false}>
83
+ <ProjectInfoBox
84
+ project={project}
85
+ duration={projectDuration}
86
+ estimatedSize={estimatedSize}
87
+ />
88
+
89
+ <OptionSelectorRow
90
+ title={t("editor.export.resolution")}
91
+ options={resolutionOptions}
92
+ selectedValue={formState.resolution}
93
+ onValueChange={setResolution}
94
+ />
95
+
96
+ <OptionSelectorRow
97
+ title={t("editor.export.quality")}
98
+ options={qualityOptions}
99
+ selectedValue={formState.quality}
100
+ onValueChange={setQuality}
101
+ />
102
+
103
+ <OptionSelectorRow
104
+ title={t("editor.export.format")}
105
+ options={formatOptions}
106
+ selectedValue={formState.format}
107
+ onValueChange={setFormat}
108
+ />
109
+
110
+ <WatermarkToggle
111
+ includeWatermark={formState.includeWatermark}
112
+ onToggle={setIncludeWatermark}
113
+ />
114
+
115
+ {isExporting && exportProgress && (
116
+ <ExportProgress progress={exportProgress} />
117
+ )}
118
+
119
+ {!isExporting && <ExportInfoBanner />}
120
+ </ScrollView>
121
+
122
+ <ExportActions
123
+ isExporting={isExporting}
124
+ onCancel={onCancel}
125
+ onExport={handleExport}
126
+ />
127
+ </View>
128
+ );
129
+ };
130
+
131
+ const styles = StyleSheet.create({
132
+ container: {
133
+ paddingVertical: 16,
134
+ },
135
+ });
@@ -0,0 +1,95 @@
1
+ /**
2
+ * ImageLayerEditor Component
3
+ * Main component for editing image layers
4
+ */
5
+
6
+ import React, { useCallback } from "react";
7
+ import { View, ScrollView, StyleSheet, Alert } from "react-native";
8
+ import { useImagePicker } from "@/domains/media";
9
+ import type { ImageLayer } from "@domains/video";
10
+ import { useImageLayerForm } from "../../hooks/useImageLayerForm";
11
+ import { IMAGE_PICKER_OPTIONS } from "../../constants/image-layer.constants";
12
+ import {
13
+ ImagePreview,
14
+ ImageSelectionButtons,
15
+ OpacitySelector,
16
+ } from "./image-layer";
17
+ import { EditorActions } from "./text-layer/EditorActions";
18
+
19
+ interface ImageLayerEditorProps {
20
+ layer?: ImageLayer;
21
+ onSave: (layerData: Partial<ImageLayer>) => void;
22
+ onCancel: () => void;
23
+ }
24
+
25
+ export const ImageLayerEditor: React.FC<ImageLayerEditorProps> = ({
26
+ layer,
27
+ onSave,
28
+ onCancel,
29
+ }) => {
30
+ const { pickFromLibrary, pickFromCamera } = useImagePicker();
31
+ const { formState, setImageUri, setOpacity, buildLayerData, isValid } =
32
+ useImageLayerForm(layer);
33
+
34
+ const handlePickImage = useCallback(async () => {
35
+ const result = await pickFromLibrary(IMAGE_PICKER_OPTIONS);
36
+
37
+ if (!result.canceled && result.assets?.[0]) {
38
+ setImageUri(result.assets[0].uri);
39
+ }
40
+ }, [pickFromLibrary, setImageUri]);
41
+
42
+ const handleTakePhoto = useCallback(async () => {
43
+ const result = await pickFromCamera(IMAGE_PICKER_OPTIONS);
44
+
45
+ if (!result.canceled && result.assets?.[0]) {
46
+ setImageUri(result.assets[0].uri);
47
+ }
48
+ }, [pickFromCamera, setImageUri]);
49
+
50
+ const handleSave = useCallback(() => {
51
+ if (!isValid) {
52
+ Alert.alert("No Image", "Please select an image first.");
53
+ return;
54
+ }
55
+ onSave(buildLayerData());
56
+ }, [isValid, buildLayerData, onSave]);
57
+
58
+ return (
59
+ <View style={styles.container}>
60
+ <ScrollView showsVerticalScrollIndicator={false}>
61
+ <View style={{ marginBottom: 24 }}>
62
+ <ImagePreview
63
+ imageUri={formState.imageUri}
64
+ opacity={formState.opacity}
65
+ />
66
+ </View>
67
+
68
+ <ImageSelectionButtons
69
+ onPickFromGallery={handlePickImage}
70
+ onTakePhoto={handleTakePhoto}
71
+ />
72
+
73
+ {formState.imageUri && (
74
+ <OpacitySelector
75
+ opacity={formState.opacity}
76
+ onOpacityChange={setOpacity}
77
+ />
78
+ )}
79
+ </ScrollView>
80
+
81
+ <EditorActions
82
+ onCancel={onCancel}
83
+ onSave={handleSave}
84
+ saveLabel={layer ? "Update Image" : "Add Image"}
85
+ isValid={isValid}
86
+ />
87
+ </View>
88
+ );
89
+ };
90
+
91
+ const styles = StyleSheet.create({
92
+ container: {
93
+ paddingVertical: 16,
94
+ },
95
+ });
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Layer Actions Menu Component
3
+ * Single Responsibility: Display layer action menu
4
+ */
5
+
6
+ import React from "react";
7
+ import { View, TouchableOpacity, StyleSheet } from "react-native";
8
+ import {
9
+ AtomicText,
10
+ AtomicIcon,
11
+ useAppDesignTokens,
12
+ } from "@umituz/react-native-design-system";
13
+ import type { Layer, ImageLayer } from "@domains/video";
14
+
15
+ export interface LayerActionsMenuProps {
16
+ layer: Layer;
17
+ onEditText: () => void;
18
+ onEditImage: () => void;
19
+ onAnimate: () => void;
20
+ onDuplicate: () => void;
21
+ onMoveFront: () => void;
22
+ onMoveUp: () => void;
23
+ onMoveDown: () => void;
24
+ onMoveBack: () => void;
25
+ onDelete: () => void;
26
+ }
27
+
28
+ export const LayerActionsMenu: React.FC<LayerActionsMenuProps> = ({
29
+ layer,
30
+ onEditText,
31
+ onEditImage,
32
+ onAnimate,
33
+ onDuplicate,
34
+ onMoveFront,
35
+ onMoveUp,
36
+ onMoveDown,
37
+ onMoveBack,
38
+ onDelete,
39
+ }) => {
40
+ const tokens = useAppDesignTokens();
41
+
42
+ return (
43
+ <View style={{ paddingVertical: 8 }}>
44
+ {layer.type === "text" && (
45
+ <TouchableOpacity style={styles.actionMenuItem} onPress={onEditText}>
46
+ <AtomicIcon name="Edit" size="md" color="primary" />
47
+ <AtomicText
48
+ type="bodyMedium"
49
+ style={{
50
+ color: tokens.colors.textPrimary,
51
+ marginLeft: 12,
52
+ }}
53
+ >
54
+ Edit Text
55
+ </AtomicText>
56
+ </TouchableOpacity>
57
+ )}
58
+ {layer.type === "image" && (
59
+ <TouchableOpacity style={styles.actionMenuItem} onPress={onEditImage}>
60
+ <AtomicIcon name="Edit" size="md" color="primary" />
61
+ <AtomicText
62
+ type="bodyMedium"
63
+ style={{
64
+ color: tokens.colors.textPrimary,
65
+ marginLeft: 12,
66
+ }}
67
+ >
68
+ Edit Image
69
+ </AtomicText>
70
+ </TouchableOpacity>
71
+ )}
72
+
73
+ <TouchableOpacity style={styles.actionMenuItem} onPress={onAnimate}>
74
+ <AtomicIcon name="sparkles-outline" size="md" color="primary" />
75
+ <AtomicText
76
+ type="bodyMedium"
77
+ style={{
78
+ color: tokens.colors.textPrimary,
79
+ marginLeft: 12,
80
+ }}
81
+ >
82
+ {layer.animation ? "Edit Animation" : "Add Animation"}
83
+ </AtomicText>
84
+ {layer.animation && (
85
+ <View
86
+ style={[
87
+ styles.animationBadge,
88
+ { backgroundColor: tokens.colors.success },
89
+ ]}
90
+ />
91
+ )}
92
+ </TouchableOpacity>
93
+
94
+ <TouchableOpacity style={styles.actionMenuItem} onPress={onDuplicate}>
95
+ <AtomicIcon name="copy" size="md" color="primary" />
96
+ <AtomicText
97
+ type="bodyMedium"
98
+ style={{
99
+ color: tokens.colors.textPrimary,
100
+ marginLeft: 12,
101
+ }}
102
+ >
103
+ Duplicate Layer
104
+ </AtomicText>
105
+ </TouchableOpacity>
106
+
107
+ <View style={styles.divider} />
108
+
109
+ <TouchableOpacity style={styles.actionMenuItem} onPress={onMoveFront}>
110
+ <AtomicIcon name="ChevronsUp" size="md" color="secondary" />
111
+ <AtomicText
112
+ type="bodyMedium"
113
+ style={{
114
+ color: tokens.colors.textSecondary,
115
+ marginLeft: 12,
116
+ }}
117
+ >
118
+ Bring to Front
119
+ </AtomicText>
120
+ </TouchableOpacity>
121
+
122
+ <TouchableOpacity style={styles.actionMenuItem} onPress={onMoveUp}>
123
+ <AtomicIcon name="ChevronUp" size="md" color="secondary" />
124
+ <AtomicText
125
+ type="bodyMedium"
126
+ style={{
127
+ color: tokens.colors.textSecondary,
128
+ marginLeft: 12,
129
+ }}
130
+ >
131
+ Move Up
132
+ </AtomicText>
133
+ </TouchableOpacity>
134
+
135
+ <TouchableOpacity style={styles.actionMenuItem} onPress={onMoveDown}>
136
+ <AtomicIcon name="chevron-down" size="md" color="secondary" />
137
+ <AtomicText
138
+ type="bodyMedium"
139
+ style={{
140
+ color: tokens.colors.textSecondary,
141
+ marginLeft: 12,
142
+ }}
143
+ >
144
+ Move Down
145
+ </AtomicText>
146
+ </TouchableOpacity>
147
+
148
+ <TouchableOpacity style={styles.actionMenuItem} onPress={onMoveBack}>
149
+ <AtomicIcon name="ChevronsDown" size="md" color="secondary" />
150
+ <AtomicText
151
+ type="bodyMedium"
152
+ style={{
153
+ color: tokens.colors.textSecondary,
154
+ marginLeft: 12,
155
+ }}
156
+ >
157
+ Send to Back
158
+ </AtomicText>
159
+ </TouchableOpacity>
160
+
161
+ <View style={styles.divider} />
162
+
163
+ <TouchableOpacity style={styles.actionMenuItem} onPress={onDelete}>
164
+ <AtomicIcon name="trash-outline" size="md" color="error" />
165
+ <AtomicText
166
+ type="bodyMedium"
167
+ style={{
168
+ color: tokens.colors.error,
169
+ marginLeft: 12,
170
+ }}
171
+ >
172
+ Delete Layer
173
+ </AtomicText>
174
+ </TouchableOpacity>
175
+ </View>
176
+ );
177
+ };
178
+
179
+ const styles = StyleSheet.create({
180
+ actionMenuItem: {
181
+ flexDirection: "row",
182
+ alignItems: "center",
183
+ paddingVertical: 16,
184
+ paddingHorizontal: 16,
185
+ },
186
+ divider: {
187
+ height: 1,
188
+ backgroundColor: "#E5E7EB",
189
+ marginVertical: 8,
190
+ },
191
+ animationBadge: {
192
+ width: 8,
193
+ height: 8,
194
+ borderRadius: 4,
195
+ marginLeft: 8,
196
+ },
197
+ });
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Scene Actions Menu Component
3
+ * Single Responsibility: Display scene action menu
4
+ */
5
+
6
+ import React from "react";
7
+ import { View, TouchableOpacity, StyleSheet } from "react-native";
8
+ import {
9
+ AtomicText,
10
+ AtomicIcon,
11
+ useAppDesignTokens,
12
+ } from "@umituz/react-native-design-system";
13
+
14
+ export interface SceneActionsMenuProps {
15
+ sceneIndex: number;
16
+ canDelete: boolean;
17
+ onDuplicate: () => void;
18
+ onDelete: () => void;
19
+ }
20
+
21
+ export const SceneActionsMenu: React.FC<SceneActionsMenuProps> = ({
22
+ sceneIndex,
23
+ canDelete,
24
+ onDuplicate,
25
+ onDelete,
26
+ }) => {
27
+ const tokens = useAppDesignTokens();
28
+
29
+ return (
30
+ <View style={{ paddingVertical: 8 }}>
31
+ <TouchableOpacity style={styles.actionMenuItem} onPress={onDuplicate}>
32
+ <AtomicIcon name="copy" size="md" color="primary" />
33
+ <AtomicText
34
+ type="bodyMedium"
35
+ style={{
36
+ color: tokens.colors.textPrimary,
37
+ marginLeft: 12,
38
+ }}
39
+ >
40
+ Duplicate Scene
41
+ </AtomicText>
42
+ </TouchableOpacity>
43
+
44
+ {canDelete && (
45
+ <TouchableOpacity style={styles.actionMenuItem} onPress={onDelete}>
46
+ <AtomicIcon name="trash-outline" size="md" color="error" />
47
+ <AtomicText
48
+ type="bodyMedium"
49
+ style={{
50
+ color: tokens.colors.error,
51
+ marginLeft: 12,
52
+ }}
53
+ >
54
+ Delete Scene
55
+ </AtomicText>
56
+ </TouchableOpacity>
57
+ )}
58
+ </View>
59
+ );
60
+ };
61
+
62
+ const styles = StyleSheet.create({
63
+ actionMenuItem: {
64
+ flexDirection: "row",
65
+ alignItems: "center",
66
+ paddingVertical: 16,
67
+ paddingHorizontal: 16,
68
+ },
69
+ });