@umituz/react-native-ai-generation-content 1.12.3 → 1.12.5

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 (82) hide show
  1. package/package.json +30 -6
  2. package/src/domains/content-moderation/domain/entities/moderation.types.ts +84 -0
  3. package/src/domains/content-moderation/domain/interfaces/content-filter.interface.ts +24 -0
  4. package/src/domains/content-moderation/index.ts +67 -0
  5. package/src/domains/content-moderation/infrastructure/rules/default-rules.data.ts +144 -0
  6. package/src/domains/content-moderation/infrastructure/rules/rules-registry.ts +75 -0
  7. package/src/domains/content-moderation/infrastructure/services/content-moderation.service.ts +150 -0
  8. package/src/domains/content-moderation/infrastructure/services/index.ts +8 -0
  9. package/src/domains/content-moderation/infrastructure/services/moderators/base.moderator.ts +62 -0
  10. package/src/domains/content-moderation/infrastructure/services/moderators/image.moderator.ts +64 -0
  11. package/src/domains/content-moderation/infrastructure/services/moderators/index.ts +10 -0
  12. package/src/domains/content-moderation/infrastructure/services/moderators/text.moderator.ts +144 -0
  13. package/src/domains/content-moderation/infrastructure/services/moderators/video.moderator.ts +64 -0
  14. package/src/domains/content-moderation/infrastructure/services/moderators/voice.moderator.ts +74 -0
  15. package/src/domains/content-moderation/infrastructure/services/pattern-matcher.service.ts +51 -0
  16. package/src/domains/content-moderation/presentation/exceptions/content-policy-violation.exception.ts +48 -0
  17. package/src/domains/creations/application/services/CreationsService.ts +71 -0
  18. package/src/domains/creations/domain/entities/Creation.ts +51 -0
  19. package/src/domains/creations/domain/entities/index.ts +6 -0
  20. package/src/domains/creations/domain/repositories/ICreationsRepository.ts +23 -0
  21. package/src/domains/creations/domain/repositories/index.ts +5 -0
  22. package/src/domains/creations/domain/services/ICreationsStorageService.ts +13 -0
  23. package/src/domains/creations/domain/value-objects/CreationsConfig.ts +76 -0
  24. package/src/domains/creations/domain/value-objects/index.ts +12 -0
  25. package/src/domains/creations/index.ts +84 -0
  26. package/src/domains/creations/infrastructure/adapters/createRepository.ts +54 -0
  27. package/src/domains/creations/infrastructure/adapters/index.ts +5 -0
  28. package/src/domains/creations/infrastructure/repositories/CreationsRepository.ts +233 -0
  29. package/src/domains/creations/infrastructure/repositories/index.ts +8 -0
  30. package/src/domains/creations/infrastructure/services/CreationsStorageService.ts +48 -0
  31. package/src/domains/creations/presentation/components/CreationCard.tsx +136 -0
  32. package/src/domains/creations/presentation/components/CreationDetail/DetailActions.tsx +76 -0
  33. package/src/domains/creations/presentation/components/CreationDetail/DetailHeader.tsx +81 -0
  34. package/src/domains/creations/presentation/components/CreationDetail/DetailImage.tsx +41 -0
  35. package/src/domains/creations/presentation/components/CreationDetail/DetailStory.tsx +67 -0
  36. package/src/domains/creations/presentation/components/CreationDetail/index.ts +4 -0
  37. package/src/domains/creations/presentation/components/CreationImageViewer.tsx +43 -0
  38. package/src/domains/creations/presentation/components/CreationThumbnail.tsx +63 -0
  39. package/src/domains/creations/presentation/components/CreationsGrid.tsx +75 -0
  40. package/src/domains/creations/presentation/components/CreationsHomeCard.tsx +176 -0
  41. package/src/domains/creations/presentation/components/EmptyState.tsx +75 -0
  42. package/src/domains/creations/presentation/components/FilterBottomSheet.tsx +158 -0
  43. package/src/domains/creations/presentation/components/FilterChips.tsx +105 -0
  44. package/src/domains/creations/presentation/components/GalleryHeader.tsx +106 -0
  45. package/src/domains/creations/presentation/components/index.ts +19 -0
  46. package/src/domains/creations/presentation/hooks/index.ts +7 -0
  47. package/src/domains/creations/presentation/hooks/useCreations.ts +33 -0
  48. package/src/domains/creations/presentation/hooks/useCreationsFilter.ts +70 -0
  49. package/src/domains/creations/presentation/hooks/useDeleteCreation.ts +51 -0
  50. package/src/domains/creations/presentation/screens/CreationDetailScreen.tsx +71 -0
  51. package/src/domains/creations/presentation/screens/CreationsGalleryScreen.tsx +217 -0
  52. package/src/domains/creations/presentation/screens/index.ts +5 -0
  53. package/src/domains/creations/presentation/utils/filterUtils.ts +52 -0
  54. package/src/domains/creations/types.d.ts +107 -0
  55. package/src/domains/face-detection/domain/constants/faceDetectionConstants.ts +16 -0
  56. package/src/domains/face-detection/domain/entities/FaceDetection.ts +19 -0
  57. package/src/domains/face-detection/index.ts +26 -0
  58. package/src/domains/face-detection/infrastructure/analyzers/faceAnalyzer.ts +36 -0
  59. package/src/domains/face-detection/infrastructure/validators/faceValidator.ts +52 -0
  60. package/src/domains/face-detection/presentation/components/FaceValidationStatus.tsx +111 -0
  61. package/src/domains/face-detection/presentation/hooks/useFaceDetection.ts +58 -0
  62. package/src/domains/feature-background/domain/entities/background.types.ts +77 -0
  63. package/src/domains/feature-background/domain/entities/component.types.ts +96 -0
  64. package/src/domains/feature-background/domain/entities/config.types.ts +41 -0
  65. package/src/domains/feature-background/domain/entities/index.ts +31 -0
  66. package/src/domains/feature-background/index.ts +72 -0
  67. package/src/domains/feature-background/infrastructure/constants/index.ts +5 -0
  68. package/src/domains/feature-background/infrastructure/constants/prompts.constants.ts +15 -0
  69. package/src/domains/feature-background/presentation/components/BackgroundFeature.tsx +145 -0
  70. package/src/domains/feature-background/presentation/components/ComparisonSlider.tsx +199 -0
  71. package/src/domains/feature-background/presentation/components/ErrorDisplay.tsx +58 -0
  72. package/src/domains/feature-background/presentation/components/FeatureHeader.tsx +80 -0
  73. package/src/domains/feature-background/presentation/components/GenerateButton.tsx +86 -0
  74. package/src/domains/feature-background/presentation/components/ImagePicker.tsx +136 -0
  75. package/src/domains/feature-background/presentation/components/ModeSelector.tsx +78 -0
  76. package/src/domains/feature-background/presentation/components/ProcessingModal.tsx +113 -0
  77. package/src/domains/feature-background/presentation/components/PromptInput.tsx +142 -0
  78. package/src/domains/feature-background/presentation/components/ResultDisplay.tsx +123 -0
  79. package/src/domains/feature-background/presentation/components/index.ts +16 -0
  80. package/src/domains/feature-background/presentation/hooks/index.ts +7 -0
  81. package/src/domains/feature-background/presentation/hooks/useBackgroundFeature.ts +118 -0
  82. package/src/index.ts +24 -0
@@ -0,0 +1,199 @@
1
+ /**
2
+ * Comparison Slider Component
3
+ * @description Before/After comparison slider for images
4
+ */
5
+
6
+ import React, { memo, useState, useRef } from "react";
7
+ import {
8
+ View,
9
+ StyleSheet,
10
+ Image,
11
+ PanResponder,
12
+ Dimensions,
13
+ } from "react-native";
14
+ import {
15
+ AtomicText,
16
+ useAppDesignTokens,
17
+ } from "@umituz/react-native-design-system";
18
+ import type { ComparisonSliderProps } from "../../domain/entities";
19
+
20
+ const { width: SCREEN_WIDTH } = Dimensions.get("window");
21
+
22
+ export const ComparisonSlider: React.FC<ComparisonSliderProps> = memo(
23
+ function ComparisonSlider({
24
+ originalUri,
25
+ processedUri,
26
+ beforeLabel,
27
+ afterLabel,
28
+ }) {
29
+ const tokens = useAppDesignTokens();
30
+ const [sliderPosition, setSliderPosition] = useState(50);
31
+ const containerWidth = useRef(SCREEN_WIDTH - 32);
32
+
33
+ const panResponder = useRef(
34
+ PanResponder.create({
35
+ onStartShouldSetPanResponder: () => true,
36
+ onMoveShouldSetPanResponder: () => true,
37
+ onPanResponderMove: (_, gestureState) => {
38
+ const newPosition =
39
+ ((gestureState.moveX - 16) / containerWidth.current) * 100;
40
+ setSliderPosition(Math.max(0, Math.min(100, newPosition)));
41
+ },
42
+ })
43
+ ).current;
44
+
45
+ return (
46
+ <View
47
+ style={styles.container}
48
+ onLayout={(e) => {
49
+ containerWidth.current = e.nativeEvent.layout.width;
50
+ }}
51
+ >
52
+ <View style={styles.imageContainer}>
53
+ <Image
54
+ source={{ uri: processedUri }}
55
+ style={styles.image}
56
+ resizeMode="cover"
57
+ />
58
+
59
+ <View
60
+ style={[styles.originalContainer, { width: `${sliderPosition}%` }]}
61
+ >
62
+ <Image
63
+ source={{ uri: originalUri }}
64
+ style={[styles.image, { width: containerWidth.current }]}
65
+ resizeMode="cover"
66
+ />
67
+ </View>
68
+
69
+ <View
70
+ style={[styles.sliderLine, { left: `${sliderPosition}%` }]}
71
+ {...panResponder.panHandlers}
72
+ >
73
+ <View
74
+ style={[
75
+ styles.sliderHandle,
76
+ { backgroundColor: tokens.colors.backgroundPrimary },
77
+ ]}
78
+ >
79
+ <View style={styles.handleBars}>
80
+ <View
81
+ style={[
82
+ styles.handleBar,
83
+ { backgroundColor: tokens.colors.primary },
84
+ ]}
85
+ />
86
+ <View
87
+ style={[
88
+ styles.handleBar,
89
+ { backgroundColor: tokens.colors.primary },
90
+ ]}
91
+ />
92
+ </View>
93
+ </View>
94
+ </View>
95
+
96
+ {beforeLabel && (
97
+ <View
98
+ style={[
99
+ styles.label,
100
+ styles.labelLeft,
101
+ { backgroundColor: tokens.colors.surface },
102
+ ]}
103
+ >
104
+ <AtomicText
105
+ type="bodySmall"
106
+ style={{ color: tokens.colors.textPrimary }}
107
+ >
108
+ {beforeLabel}
109
+ </AtomicText>
110
+ </View>
111
+ )}
112
+
113
+ {afterLabel && (
114
+ <View
115
+ style={[
116
+ styles.label,
117
+ styles.labelRight,
118
+ { backgroundColor: tokens.colors.primary },
119
+ ]}
120
+ >
121
+ <AtomicText
122
+ type="bodySmall"
123
+ style={{ color: tokens.colors.backgroundPrimary }}
124
+ >
125
+ {afterLabel}
126
+ </AtomicText>
127
+ </View>
128
+ )}
129
+ </View>
130
+ </View>
131
+ );
132
+ }
133
+ );
134
+
135
+ const styles = StyleSheet.create({
136
+ container: {
137
+ width: "100%",
138
+ aspectRatio: 1,
139
+ borderRadius: 20,
140
+ overflow: "hidden",
141
+ },
142
+ imageContainer: {
143
+ flex: 1,
144
+ position: "relative",
145
+ },
146
+ image: {
147
+ width: "100%",
148
+ height: "100%",
149
+ },
150
+ originalContainer: {
151
+ position: "absolute",
152
+ top: 0,
153
+ left: 0,
154
+ bottom: 0,
155
+ overflow: "hidden",
156
+ borderRightWidth: 2,
157
+ borderRightColor: "#FFFFFF",
158
+ },
159
+ sliderLine: {
160
+ position: "absolute",
161
+ top: 0,
162
+ bottom: 0,
163
+ width: 2,
164
+ marginLeft: -1,
165
+ },
166
+ sliderHandle: {
167
+ position: "absolute",
168
+ top: "50%",
169
+ left: -20,
170
+ width: 40,
171
+ height: 40,
172
+ borderRadius: 20,
173
+ justifyContent: "center",
174
+ alignItems: "center",
175
+ marginTop: -20,
176
+ },
177
+ handleBars: {
178
+ flexDirection: "row",
179
+ gap: 4,
180
+ },
181
+ handleBar: {
182
+ width: 3,
183
+ height: 16,
184
+ borderRadius: 2,
185
+ },
186
+ label: {
187
+ position: "absolute",
188
+ top: 12,
189
+ paddingHorizontal: 8,
190
+ paddingVertical: 4,
191
+ borderRadius: 12,
192
+ },
193
+ labelLeft: {
194
+ left: 12,
195
+ },
196
+ labelRight: {
197
+ right: 12,
198
+ },
199
+ });
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Error Display Component
3
+ * @description Displays error messages
4
+ */
5
+
6
+ import React, { memo } from "react";
7
+ import { View, StyleSheet } from "react-native";
8
+ import {
9
+ AtomicText,
10
+ AtomicIcon,
11
+ useAppDesignTokens,
12
+ } from "@umituz/react-native-design-system";
13
+ import type { ErrorDisplayProps } from "../../domain/entities";
14
+
15
+ export const ErrorDisplay: React.FC<ErrorDisplayProps> = memo(
16
+ function ErrorDisplay({ error }) {
17
+ const tokens = useAppDesignTokens();
18
+
19
+ if (!error) {
20
+ return null;
21
+ }
22
+
23
+ return (
24
+ <View
25
+ style={[
26
+ styles.container,
27
+ { backgroundColor: tokens.colors.errorContainer },
28
+ ]}
29
+ >
30
+ <AtomicIcon
31
+ name="alert-circle"
32
+ size={20}
33
+ color="error"
34
+ />
35
+ <AtomicText
36
+ type="bodyMedium"
37
+ style={[styles.errorText, { color: tokens.colors.error }]}
38
+ >
39
+ {error}
40
+ </AtomicText>
41
+ </View>
42
+ );
43
+ }
44
+ );
45
+
46
+ const styles = StyleSheet.create({
47
+ container: {
48
+ flexDirection: "row",
49
+ alignItems: "center",
50
+ gap: 12,
51
+ padding: 16,
52
+ borderRadius: 12,
53
+ marginVertical: 12,
54
+ },
55
+ errorText: {
56
+ flex: 1,
57
+ },
58
+ });
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Feature Header Component
3
+ * @description Header with hero image and description
4
+ */
5
+
6
+ import React, { memo } from "react";
7
+ import { View, StyleSheet, Image, Dimensions } from "react-native";
8
+ import {
9
+ AtomicText,
10
+ useAppDesignTokens,
11
+ } from "@umituz/react-native-design-system";
12
+ import type { FeatureHeaderProps } from "../../domain/entities";
13
+
14
+ const { width: SCREEN_WIDTH } = Dimensions.get("window");
15
+ const IMAGE_WIDTH = SCREEN_WIDTH - 32;
16
+ const IMAGE_HEIGHT = IMAGE_WIDTH * 0.5;
17
+
18
+ export const FeatureHeader: React.FC<FeatureHeaderProps> = memo(
19
+ function FeatureHeader({ heroImage, description }) {
20
+ const tokens = useAppDesignTokens();
21
+
22
+ if (!heroImage && !description) {
23
+ return null;
24
+ }
25
+
26
+ return (
27
+ <View style={styles.container}>
28
+ {heroImage && (
29
+ <View
30
+ style={[
31
+ styles.imageContainer,
32
+ { backgroundColor: tokens.colors.surface },
33
+ ]}
34
+ >
35
+ <Image
36
+ source={heroImage}
37
+ style={[
38
+ styles.heroImage,
39
+ { width: IMAGE_WIDTH, height: IMAGE_HEIGHT },
40
+ ]}
41
+ resizeMode="cover"
42
+ />
43
+ </View>
44
+ )}
45
+ {description && (
46
+ <AtomicText
47
+ type="bodyMedium"
48
+ style={[
49
+ styles.description,
50
+ {
51
+ color: tokens.colors.textSecondary,
52
+ marginTop: tokens.spacing.md,
53
+ },
54
+ ]}
55
+ >
56
+ {description}
57
+ </AtomicText>
58
+ )}
59
+ </View>
60
+ );
61
+ }
62
+ );
63
+
64
+ const styles = StyleSheet.create({
65
+ container: {
66
+ marginBottom: 8,
67
+ },
68
+ imageContainer: {
69
+ borderRadius: 16,
70
+ overflow: "hidden",
71
+ },
72
+ heroImage: {
73
+ borderRadius: 16,
74
+ },
75
+ description: {
76
+ textAlign: "center",
77
+ lineHeight: 22,
78
+ paddingHorizontal: 8,
79
+ },
80
+ });
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Generate Button Component
3
+ * @description Action button to trigger processing
4
+ */
5
+
6
+ import React, { memo } from "react";
7
+ import { StyleSheet, TouchableOpacity, ActivityIndicator } from "react-native";
8
+ import {
9
+ AtomicText,
10
+ AtomicIcon,
11
+ useAppDesignTokens,
12
+ } from "@umituz/react-native-design-system";
13
+ import type { GenerateButtonProps } from "../../domain/entities";
14
+
15
+ export const GenerateButton: React.FC<GenerateButtonProps> = memo(
16
+ function GenerateButton({
17
+ isDisabled,
18
+ isProcessing,
19
+ onPress,
20
+ buttonText,
21
+ }) {
22
+ const tokens = useAppDesignTokens();
23
+
24
+ const disabled = isDisabled || isProcessing;
25
+
26
+ return (
27
+ <TouchableOpacity
28
+ onPress={onPress}
29
+ disabled={disabled}
30
+ activeOpacity={0.8}
31
+ style={[
32
+ styles.container,
33
+ {
34
+ backgroundColor: disabled
35
+ ? tokens.colors.surfaceSecondary
36
+ : tokens.colors.primary,
37
+ },
38
+ ]}
39
+ >
40
+ {isProcessing ? (
41
+ <ActivityIndicator color={tokens.colors.backgroundPrimary} />
42
+ ) : (
43
+ <>
44
+ <AtomicIcon
45
+ name="sparkles"
46
+ size={20}
47
+ color={disabled ? "surfaceVariant" : "onPrimary"}
48
+ style={styles.icon}
49
+ />
50
+ <AtomicText
51
+ type="headlineSmall"
52
+ style={[
53
+ styles.text,
54
+ {
55
+ color: disabled
56
+ ? tokens.colors.textTertiary
57
+ : tokens.colors.backgroundPrimary,
58
+ },
59
+ ]}
60
+ >
61
+ {buttonText}
62
+ </AtomicText>
63
+ </>
64
+ )}
65
+ </TouchableOpacity>
66
+ );
67
+ }
68
+ );
69
+
70
+ const styles = StyleSheet.create({
71
+ container: {
72
+ marginVertical: 24,
73
+ borderRadius: 28,
74
+ flexDirection: "row",
75
+ alignItems: "center",
76
+ justifyContent: "center",
77
+ paddingVertical: 16,
78
+ paddingHorizontal: 32,
79
+ },
80
+ icon: {
81
+ marginRight: 8,
82
+ },
83
+ text: {
84
+ fontWeight: "bold",
85
+ },
86
+ });
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Image Picker Component
3
+ * @description Image selection component with placeholder state
4
+ */
5
+
6
+ import React, { memo } from "react";
7
+ import { View, StyleSheet, TouchableOpacity, Image } from "react-native";
8
+ import {
9
+ AtomicText,
10
+ AtomicIcon,
11
+ useAppDesignTokens,
12
+ } from "@umituz/react-native-design-system";
13
+ import type { ImagePickerProps } from "../../domain/entities";
14
+
15
+ export const ImagePicker: React.FC<ImagePickerProps> = memo(
16
+ function ImagePicker({
17
+ imageUri,
18
+ isProcessing,
19
+ onSelectImage,
20
+ placeholderText,
21
+ }) {
22
+ const tokens = useAppDesignTokens();
23
+
24
+ return (
25
+ <View style={styles.container}>
26
+ <TouchableOpacity
27
+ style={[
28
+ styles.imagePickerBox,
29
+ { backgroundColor: tokens.colors.surface },
30
+ ]}
31
+ onPress={onSelectImage}
32
+ disabled={isProcessing}
33
+ activeOpacity={0.8}
34
+ >
35
+ {imageUri ? (
36
+ <View style={styles.imageContainer}>
37
+ <Image source={{ uri: imageUri }} style={styles.image} />
38
+ <View style={styles.imageOverlay}>
39
+ <View
40
+ style={[
41
+ styles.editBadge,
42
+ { backgroundColor: tokens.colors.primary },
43
+ ]}
44
+ >
45
+ <AtomicIcon
46
+ name="image-plus"
47
+ size={20}
48
+ color="onPrimary"
49
+ />
50
+ </View>
51
+ </View>
52
+ </View>
53
+ ) : (
54
+ <View
55
+ style={[
56
+ styles.placeholderContainer,
57
+ { backgroundColor: tokens.colors.surface },
58
+ ]}
59
+ >
60
+ <View
61
+ style={[
62
+ styles.uploadIconContainer,
63
+ { backgroundColor: tokens.colors.surfaceSecondary },
64
+ ]}
65
+ >
66
+ <AtomicIcon
67
+ name="upload"
68
+ size={40}
69
+ color="primary"
70
+ />
71
+ </View>
72
+ <AtomicText
73
+ type="bodyLarge"
74
+ style={[
75
+ styles.placeholderText,
76
+ { color: tokens.colors.primary },
77
+ ]}
78
+ >
79
+ {placeholderText}
80
+ </AtomicText>
81
+ </View>
82
+ )}
83
+ </TouchableOpacity>
84
+ </View>
85
+ );
86
+ }
87
+ );
88
+
89
+ const styles = StyleSheet.create({
90
+ container: {
91
+ marginVertical: 16,
92
+ alignItems: "center",
93
+ },
94
+ imagePickerBox: {
95
+ width: "100%",
96
+ aspectRatio: 1,
97
+ borderRadius: 20,
98
+ overflow: "hidden",
99
+ },
100
+ imageContainer: {
101
+ flex: 1,
102
+ position: "relative",
103
+ },
104
+ image: {
105
+ width: "100%",
106
+ height: "100%",
107
+ },
108
+ imageOverlay: {
109
+ position: "absolute",
110
+ bottom: 0,
111
+ left: 0,
112
+ right: 0,
113
+ height: "25%",
114
+ justifyContent: "flex-end",
115
+ alignItems: "flex-end",
116
+ padding: 16,
117
+ },
118
+ editBadge: {
119
+ borderRadius: 20,
120
+ padding: 10,
121
+ },
122
+ placeholderContainer: {
123
+ flex: 1,
124
+ justifyContent: "center",
125
+ alignItems: "center",
126
+ },
127
+ uploadIconContainer: {
128
+ borderRadius: 50,
129
+ padding: 24,
130
+ marginBottom: 16,
131
+ },
132
+ placeholderText: {
133
+ textAlign: "center",
134
+ fontWeight: "600",
135
+ },
136
+ });
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Mode Selector Component
3
+ * @description Horizontal scrollable mode selection toolbar
4
+ */
5
+
6
+ import React, { memo } from "react";
7
+ import { View, StyleSheet, ScrollView, TouchableOpacity } from "react-native";
8
+ import {
9
+ AtomicText,
10
+ AtomicIcon,
11
+ useAppDesignTokens,
12
+ } from "@umituz/react-native-design-system";
13
+ import type { ModeSelectorProps } from "../../domain/entities";
14
+
15
+ export const ModeSelector: React.FC<ModeSelectorProps> = memo(
16
+ function ModeSelector({ activeMode, onModeChange, isProcessing, modes }) {
17
+ const tokens = useAppDesignTokens();
18
+
19
+ return (
20
+ <View
21
+ style={[
22
+ styles.container,
23
+ { backgroundColor: tokens.colors.surface },
24
+ ]}
25
+ >
26
+ <ScrollView
27
+ horizontal
28
+ showsHorizontalScrollIndicator={false}
29
+ contentContainerStyle={styles.scrollContent}
30
+ >
31
+ {modes.map((mode) => {
32
+ const isActive = activeMode === mode.id;
33
+ return (
34
+ <TouchableOpacity
35
+ key={mode.id}
36
+ style={[
37
+ styles.modeButton,
38
+ isActive && {
39
+ backgroundColor: tokens.colors.primary,
40
+ },
41
+ ]}
42
+ onPress={() => !isProcessing && onModeChange(mode.id)}
43
+ disabled={isProcessing}
44
+ activeOpacity={0.7}
45
+ >
46
+ <AtomicIcon
47
+ name={mode.icon}
48
+ size={20}
49
+ color={isActive ? "onPrimary" : "onSurface"}
50
+ />
51
+ </TouchableOpacity>
52
+ );
53
+ })}
54
+ </ScrollView>
55
+ </View>
56
+ );
57
+ }
58
+ );
59
+
60
+ const styles = StyleSheet.create({
61
+ container: {
62
+ borderRadius: 24,
63
+ paddingVertical: 8,
64
+ paddingHorizontal: 4,
65
+ },
66
+ scrollContent: {
67
+ flexDirection: "row",
68
+ gap: 8,
69
+ paddingHorizontal: 8,
70
+ },
71
+ modeButton: {
72
+ width: 48,
73
+ height: 48,
74
+ borderRadius: 16,
75
+ justifyContent: "center",
76
+ alignItems: "center",
77
+ },
78
+ });