@umituz/react-native-ai-generation-content 1.17.21 → 1.17.23

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 (44) hide show
  1. package/package.json +1 -1
  2. package/src/domains/creations/domain/types/creation-filter.ts +16 -18
  3. package/src/domains/creations/domain/value-objects/CreationsConfig.ts +12 -0
  4. package/src/domains/creations/presentation/components/FilterSheets.tsx +63 -0
  5. package/src/domains/creations/presentation/components/GalleryHeader.tsx +95 -93
  6. package/src/domains/creations/presentation/components/index.ts +1 -0
  7. package/src/domains/creations/presentation/hooks/index.ts +3 -0
  8. package/src/domains/creations/presentation/hooks/useCreationsFilter.ts +35 -48
  9. package/src/domains/creations/presentation/hooks/useGalleryFilters.ts +78 -0
  10. package/src/domains/creations/presentation/hooks/useMediaFilter.ts +54 -0
  11. package/src/domains/creations/presentation/hooks/useStatusFilter.ts +54 -0
  12. package/src/domains/creations/presentation/screens/CreationsGalleryScreen.tsx +81 -156
  13. package/src/features/image-to-video/index.ts +90 -3
  14. package/src/features/image-to-video/presentation/components/AnimationStyleSelector.tsx +135 -0
  15. package/src/features/image-to-video/presentation/components/DurationSelector.tsx +110 -0
  16. package/src/features/image-to-video/presentation/components/GenerateButton.tsx +95 -0
  17. package/src/features/image-to-video/presentation/components/HeroSection.tsx +89 -0
  18. package/src/features/image-to-video/presentation/components/ImageSelectionGrid.tsx +234 -0
  19. package/src/features/image-to-video/presentation/components/MusicMoodSelector.tsx +181 -0
  20. package/src/features/image-to-video/presentation/components/index.ts +30 -0
  21. package/src/features/image-to-video/presentation/hooks/index.ts +22 -0
  22. package/src/features/image-to-video/presentation/hooks/useFormState.ts +116 -0
  23. package/src/features/image-to-video/presentation/hooks/useGeneration.ts +85 -0
  24. package/src/features/image-to-video/presentation/hooks/useImageToVideoForm.ts +93 -0
  25. package/src/features/image-to-video/presentation/index.ts +4 -0
  26. package/src/features/text-to-video/domain/types/callback.types.ts +50 -0
  27. package/src/features/text-to-video/domain/types/component.types.ts +106 -0
  28. package/src/features/text-to-video/domain/types/config.types.ts +61 -0
  29. package/src/features/text-to-video/domain/types/index.ts +48 -4
  30. package/src/features/text-to-video/domain/types/request.types.ts +36 -0
  31. package/src/features/text-to-video/domain/types/state.types.ts +53 -0
  32. package/src/features/text-to-video/index.ts +41 -3
  33. package/src/features/text-to-video/infrastructure/services/text-to-video-executor.ts +1 -1
  34. package/src/features/text-to-video/presentation/components/FrameSelector.tsx +153 -0
  35. package/src/features/text-to-video/presentation/components/GenerationTabs.tsx +73 -0
  36. package/src/features/text-to-video/presentation/components/HeroSection.tsx +67 -0
  37. package/src/features/text-to-video/presentation/components/HintCarousel.tsx +96 -0
  38. package/src/features/text-to-video/presentation/components/OptionsPanel.tsx +123 -0
  39. package/src/features/text-to-video/presentation/components/index.ts +10 -0
  40. package/src/features/text-to-video/presentation/hooks/index.ts +11 -0
  41. package/src/features/text-to-video/presentation/hooks/useTextToVideoFeature.ts +77 -20
  42. package/src/features/text-to-video/presentation/hooks/useTextToVideoForm.ts +134 -0
  43. package/src/features/text-to-video/presentation/index.ts +6 -0
  44. package/src/features/text-to-video/domain/types/text-to-video.types.ts +0 -65
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Duration Selector Component
3
+ * Generic component for video duration selection
4
+ */
5
+
6
+ import React from "react";
7
+ import { View, TouchableOpacity, StyleSheet } from "react-native";
8
+ import {
9
+ AtomicText,
10
+ useAppDesignTokens,
11
+ } from "@umituz/react-native-design-system";
12
+ import type { VideoDuration, DurationOption } from "../../domain/types";
13
+
14
+ export interface DurationSelectorProps {
15
+ options: DurationOption[];
16
+ selectedDuration: VideoDuration;
17
+ onDurationSelect: (duration: VideoDuration) => void;
18
+ label: string;
19
+ imageCount: number;
20
+ totalVideoLabel: string;
21
+ }
22
+
23
+ export const DurationSelector: React.FC<DurationSelectorProps> = ({
24
+ options,
25
+ selectedDuration,
26
+ onDurationSelect,
27
+ label,
28
+ imageCount,
29
+ totalVideoLabel,
30
+ }) => {
31
+ const tokens = useAppDesignTokens();
32
+
33
+ return (
34
+ <View style={componentStyles.section}>
35
+ <AtomicText
36
+ type="bodyMedium"
37
+ style={[componentStyles.label, { color: tokens.colors.textPrimary }]}
38
+ >
39
+ {label}
40
+ </AtomicText>
41
+ <View style={componentStyles.grid}>
42
+ {options.map((option) => {
43
+ const isSelected = selectedDuration === option.value;
44
+ return (
45
+ <TouchableOpacity
46
+ key={option.value}
47
+ style={[
48
+ componentStyles.button,
49
+ {
50
+ backgroundColor: isSelected
51
+ ? tokens.colors.primary
52
+ : tokens.colors.surface,
53
+ borderColor: isSelected
54
+ ? tokens.colors.primary
55
+ : tokens.colors.borderLight,
56
+ },
57
+ ]}
58
+ onPress={() => onDurationSelect(option.value)}
59
+ activeOpacity={0.7}
60
+ >
61
+ <AtomicText
62
+ type="bodyMedium"
63
+ style={{
64
+ color: isSelected
65
+ ? tokens.colors.textInverse
66
+ : tokens.colors.textPrimary,
67
+ fontWeight: isSelected ? "600" : "400",
68
+ }}
69
+ >
70
+ {option.label ?? `${option.value}s`}
71
+ </AtomicText>
72
+ </TouchableOpacity>
73
+ );
74
+ })}
75
+ </View>
76
+ <AtomicText
77
+ type="labelSmall"
78
+ style={[componentStyles.hint, { color: tokens.colors.textSecondary }]}
79
+ >
80
+ {totalVideoLabel.replace("{duration}", String(imageCount * selectedDuration))}
81
+ </AtomicText>
82
+ </View>
83
+ );
84
+ };
85
+
86
+ const componentStyles = StyleSheet.create({
87
+ section: {
88
+ padding: 16,
89
+ marginBottom: 8,
90
+ },
91
+ label: {
92
+ fontWeight: "600",
93
+ marginBottom: 12,
94
+ },
95
+ grid: {
96
+ flexDirection: "row",
97
+ gap: 12,
98
+ },
99
+ button: {
100
+ flex: 1,
101
+ paddingVertical: 16,
102
+ borderRadius: 12,
103
+ borderWidth: 1,
104
+ alignItems: "center",
105
+ justifyContent: "center",
106
+ },
107
+ hint: {
108
+ marginTop: 8,
109
+ },
110
+ });
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Generate Button Component
3
+ * Generic generate button with loading state
4
+ */
5
+
6
+ import React from "react";
7
+ import { View, TouchableOpacity, ActivityIndicator, StyleSheet } from "react-native";
8
+ import {
9
+ AtomicText,
10
+ AtomicIcon,
11
+ useAppDesignTokens,
12
+ } from "@umituz/react-native-design-system";
13
+
14
+ export interface GenerateButtonProps {
15
+ onPress: () => void;
16
+ isGenerating: boolean;
17
+ isDisabled?: boolean;
18
+ buttonText: string;
19
+ generatingText: string;
20
+ }
21
+
22
+ export const GenerateButton: React.FC<GenerateButtonProps> = ({
23
+ onPress,
24
+ isGenerating,
25
+ isDisabled = false,
26
+ buttonText,
27
+ generatingText,
28
+ }) => {
29
+ const tokens = useAppDesignTokens();
30
+ const disabled = isGenerating || isDisabled;
31
+
32
+ return (
33
+ <View style={componentStyles.container}>
34
+ <TouchableOpacity
35
+ style={[
36
+ componentStyles.button,
37
+ {
38
+ backgroundColor: disabled
39
+ ? tokens.colors.primary + "60"
40
+ : tokens.colors.primary,
41
+ },
42
+ ]}
43
+ onPress={onPress}
44
+ disabled={disabled}
45
+ activeOpacity={0.8}
46
+ >
47
+ {isGenerating ? (
48
+ <>
49
+ <ActivityIndicator
50
+ size="small"
51
+ color={tokens.colors.textInverse}
52
+ style={componentStyles.loader}
53
+ />
54
+ <AtomicText
55
+ type="bodyMedium"
56
+ style={[componentStyles.text, { color: tokens.colors.textInverse }]}
57
+ >
58
+ {generatingText}
59
+ </AtomicText>
60
+ </>
61
+ ) : (
62
+ <>
63
+ <AtomicIcon name="videocam-outline" size="md" color="onSurface" />
64
+ <AtomicText
65
+ type="bodyMedium"
66
+ style={[componentStyles.text, { color: tokens.colors.textInverse }]}
67
+ >
68
+ {buttonText}
69
+ </AtomicText>
70
+ </>
71
+ )}
72
+ </TouchableOpacity>
73
+ </View>
74
+ );
75
+ };
76
+
77
+ const componentStyles = StyleSheet.create({
78
+ container: {
79
+ padding: 16,
80
+ },
81
+ button: {
82
+ flexDirection: "row",
83
+ alignItems: "center",
84
+ justifyContent: "center",
85
+ paddingVertical: 16,
86
+ borderRadius: 16,
87
+ },
88
+ loader: {
89
+ marginRight: 8,
90
+ },
91
+ text: {
92
+ fontWeight: "600",
93
+ marginLeft: 8,
94
+ },
95
+ });
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Hero Section Component
3
+ * Generic hero section with gradient background
4
+ */
5
+
6
+ import React from "react";
7
+ import { View, StyleSheet } from "react-native";
8
+ import { LinearGradient } from "expo-linear-gradient";
9
+ import {
10
+ AtomicText,
11
+ AtomicIcon,
12
+ useAppDesignTokens,
13
+ } from "@umituz/react-native-design-system";
14
+
15
+ export interface HeroSectionProps {
16
+ title: string;
17
+ subtitle: string;
18
+ iconName?: string;
19
+ }
20
+
21
+ export const HeroSection: React.FC<HeroSectionProps> = ({
22
+ title,
23
+ subtitle,
24
+ iconName = "image-outline",
25
+ }) => {
26
+ const tokens = useAppDesignTokens();
27
+
28
+ return (
29
+ <View style={componentStyles.container}>
30
+ <LinearGradient
31
+ colors={[
32
+ tokens.colors.secondary ?? tokens.colors.info,
33
+ tokens.colors.primary,
34
+ ]}
35
+ start={{ x: 0, y: 0 }}
36
+ end={{ x: 1, y: 1 }}
37
+ style={componentStyles.gradient}
38
+ >
39
+ <View style={componentStyles.iconContainer}>
40
+ <AtomicIcon name={iconName as never} size="xl" color="onSurface" />
41
+ </View>
42
+ <AtomicText
43
+ type="headlineSmall"
44
+ style={[componentStyles.title, { color: tokens.colors.textInverse }]}
45
+ >
46
+ {title}
47
+ </AtomicText>
48
+ <AtomicText
49
+ type="bodyMedium"
50
+ style={[componentStyles.subtitle, { color: tokens.colors.textInverse }]}
51
+ >
52
+ {subtitle}
53
+ </AtomicText>
54
+ </LinearGradient>
55
+ </View>
56
+ );
57
+ };
58
+
59
+ const componentStyles = StyleSheet.create({
60
+ container: {
61
+ marginHorizontal: 16,
62
+ marginBottom: 24,
63
+ borderRadius: 20,
64
+ overflow: "hidden",
65
+ },
66
+ gradient: {
67
+ padding: 32,
68
+ alignItems: "center",
69
+ justifyContent: "center",
70
+ },
71
+ iconContainer: {
72
+ width: 80,
73
+ height: 80,
74
+ borderRadius: 40,
75
+ backgroundColor: "rgba(255, 255, 255, 0.2)",
76
+ alignItems: "center",
77
+ justifyContent: "center",
78
+ },
79
+ title: {
80
+ textAlign: "center",
81
+ marginTop: 16,
82
+ fontWeight: "700",
83
+ },
84
+ subtitle: {
85
+ textAlign: "center",
86
+ marginTop: 8,
87
+ opacity: 0.9,
88
+ },
89
+ });
@@ -0,0 +1,234 @@
1
+ /**
2
+ * Image Selection Grid Component
3
+ * Generic component for image selection display
4
+ */
5
+
6
+ import React from "react";
7
+ import {
8
+ View,
9
+ ScrollView,
10
+ TouchableOpacity,
11
+ Image,
12
+ StyleSheet,
13
+ } from "react-native";
14
+ import {
15
+ AtomicText,
16
+ AtomicIcon,
17
+ useAppDesignTokens,
18
+ } from "@umituz/react-native-design-system";
19
+
20
+ export interface ImageSelectionGridTranslations {
21
+ selectedImages: string;
22
+ selectImages: string;
23
+ chooseUpTo: string;
24
+ addMore: string;
25
+ }
26
+
27
+ export interface ImageSelectionGridProps {
28
+ images: string[];
29
+ maxImages: number;
30
+ onSelectImages: () => void;
31
+ onRemoveImage: (index: number) => void;
32
+ translations: ImageSelectionGridTranslations;
33
+ }
34
+
35
+ export const ImageSelectionGrid: React.FC<ImageSelectionGridProps> = ({
36
+ images,
37
+ maxImages,
38
+ onSelectImages,
39
+ onRemoveImage,
40
+ translations,
41
+ }) => {
42
+ const tokens = useAppDesignTokens();
43
+
44
+ if (images.length === 0) {
45
+ return (
46
+ <View style={componentStyles.section}>
47
+ <AtomicText
48
+ type="bodyMedium"
49
+ style={[componentStyles.label, { color: tokens.colors.textPrimary }]}
50
+ >
51
+ {translations.selectedImages} (0/{maxImages})
52
+ </AtomicText>
53
+ <TouchableOpacity
54
+ style={[
55
+ componentStyles.uploadBox,
56
+ {
57
+ backgroundColor: tokens.colors.surface,
58
+ borderColor: tokens.colors.borderLight,
59
+ },
60
+ ]}
61
+ onPress={onSelectImages}
62
+ activeOpacity={0.7}
63
+ >
64
+ <AtomicIcon name="Upload" size="xl" color="primary" />
65
+ <AtomicText
66
+ type="bodyMedium"
67
+ style={[
68
+ componentStyles.uploadText,
69
+ { color: tokens.colors.primary },
70
+ ]}
71
+ >
72
+ {translations.selectImages}
73
+ </AtomicText>
74
+ <AtomicText
75
+ type="labelSmall"
76
+ style={{ color: tokens.colors.textSecondary }}
77
+ >
78
+ {translations.chooseUpTo.replace("{max}", String(maxImages))}
79
+ </AtomicText>
80
+ </TouchableOpacity>
81
+ </View>
82
+ );
83
+ }
84
+
85
+ return (
86
+ <View style={componentStyles.section}>
87
+ <AtomicText
88
+ type="bodyMedium"
89
+ style={[componentStyles.label, { color: tokens.colors.textPrimary }]}
90
+ >
91
+ {translations.selectedImages} ({images.length}/{maxImages})
92
+ </AtomicText>
93
+ <ScrollView
94
+ horizontal
95
+ showsHorizontalScrollIndicator={false}
96
+ style={componentStyles.scroll}
97
+ >
98
+ {images.map((uri, index) => (
99
+ <View key={index} style={componentStyles.imageCard}>
100
+ <Image
101
+ source={{ uri }}
102
+ style={componentStyles.imagePreview}
103
+ resizeMode="cover"
104
+ />
105
+ <TouchableOpacity
106
+ style={[
107
+ componentStyles.removeButton,
108
+ { backgroundColor: tokens.colors.error },
109
+ ]}
110
+ onPress={() => onRemoveImage(index)}
111
+ >
112
+ <AtomicIcon name="close-circle" size="sm" color="onSurface" />
113
+ </TouchableOpacity>
114
+ <View
115
+ style={[
116
+ componentStyles.imageBadge,
117
+ { backgroundColor: tokens.colors.primary },
118
+ ]}
119
+ >
120
+ <AtomicText
121
+ type="labelSmall"
122
+ style={[
123
+ componentStyles.badgeText,
124
+ { color: tokens.colors.textInverse },
125
+ ]}
126
+ >
127
+ {index + 1}
128
+ </AtomicText>
129
+ </View>
130
+ </View>
131
+ ))}
132
+
133
+ {images.length < maxImages && (
134
+ <TouchableOpacity
135
+ style={[
136
+ componentStyles.addMoreCard,
137
+ {
138
+ backgroundColor: tokens.colors.surface,
139
+ borderColor: tokens.colors.borderLight,
140
+ },
141
+ ]}
142
+ onPress={onSelectImages}
143
+ activeOpacity={0.7}
144
+ >
145
+ <AtomicIcon name="add" size="lg" color="primary" />
146
+ <AtomicText
147
+ type="labelSmall"
148
+ style={[
149
+ componentStyles.addMoreText,
150
+ { color: tokens.colors.primary },
151
+ ]}
152
+ >
153
+ {translations.addMore}
154
+ </AtomicText>
155
+ </TouchableOpacity>
156
+ )}
157
+ </ScrollView>
158
+ </View>
159
+ );
160
+ };
161
+
162
+ const componentStyles = StyleSheet.create({
163
+ section: {
164
+ padding: 16,
165
+ marginBottom: 8,
166
+ },
167
+ label: {
168
+ fontWeight: "600",
169
+ marginBottom: 12,
170
+ },
171
+ uploadBox: {
172
+ padding: 48,
173
+ borderRadius: 16,
174
+ alignItems: "center",
175
+ justifyContent: "center",
176
+ borderWidth: 2,
177
+ borderStyle: "dashed",
178
+ },
179
+ uploadText: {
180
+ fontWeight: "600",
181
+ marginTop: 12,
182
+ },
183
+ scroll: {
184
+ marginHorizontal: -16,
185
+ paddingHorizontal: 16,
186
+ },
187
+ imageCard: {
188
+ width: 120,
189
+ height: 120,
190
+ borderRadius: 12,
191
+ marginRight: 12,
192
+ position: "relative",
193
+ },
194
+ imagePreview: {
195
+ width: "100%",
196
+ height: "100%",
197
+ borderRadius: 12,
198
+ },
199
+ removeButton: {
200
+ position: "absolute",
201
+ top: 6,
202
+ right: 6,
203
+ width: 24,
204
+ height: 24,
205
+ borderRadius: 12,
206
+ alignItems: "center",
207
+ justifyContent: "center",
208
+ },
209
+ imageBadge: {
210
+ position: "absolute",
211
+ bottom: 6,
212
+ left: 6,
213
+ width: 24,
214
+ height: 24,
215
+ borderRadius: 12,
216
+ alignItems: "center",
217
+ justifyContent: "center",
218
+ },
219
+ badgeText: {
220
+ fontSize: 10,
221
+ },
222
+ addMoreCard: {
223
+ width: 120,
224
+ height: 120,
225
+ borderRadius: 12,
226
+ alignItems: "center",
227
+ justifyContent: "center",
228
+ borderWidth: 2,
229
+ borderStyle: "dashed",
230
+ },
231
+ addMoreText: {
232
+ marginTop: 4,
233
+ },
234
+ });
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Music Mood Selector Component
3
+ * Generic component for music mood selection
4
+ */
5
+
6
+ import React from "react";
7
+ import { View, ScrollView, TouchableOpacity, StyleSheet } from "react-native";
8
+ import {
9
+ AtomicText,
10
+ AtomicIcon,
11
+ useAppDesignTokens,
12
+ } from "@umituz/react-native-design-system";
13
+ import type { MusicMood, MusicMoodId } from "../../domain/types";
14
+
15
+ export interface MusicMoodSelectorProps {
16
+ moods: MusicMood[];
17
+ selectedMood: MusicMoodId;
18
+ onMoodSelect: (moodId: MusicMoodId) => void;
19
+ label: string;
20
+ hasCustomAudio?: boolean;
21
+ customAudioLabel?: string;
22
+ }
23
+
24
+ export const MusicMoodSelector: React.FC<MusicMoodSelectorProps> = ({
25
+ moods,
26
+ selectedMood,
27
+ onMoodSelect,
28
+ label,
29
+ hasCustomAudio = false,
30
+ customAudioLabel,
31
+ }) => {
32
+ const tokens = useAppDesignTokens();
33
+
34
+ return (
35
+ <View style={componentStyles.section}>
36
+ <AtomicText
37
+ type="bodyMedium"
38
+ style={[componentStyles.label, { color: tokens.colors.textPrimary }]}
39
+ >
40
+ {label}
41
+ </AtomicText>
42
+ <ScrollView
43
+ horizontal
44
+ showsHorizontalScrollIndicator={false}
45
+ style={componentStyles.scroll}
46
+ >
47
+ {moods.map((mood) => {
48
+ const isSelected = selectedMood === mood.id;
49
+ return (
50
+ <TouchableOpacity
51
+ key={mood.id}
52
+ style={[
53
+ componentStyles.card,
54
+ {
55
+ backgroundColor: isSelected
56
+ ? tokens.colors.primary
57
+ : tokens.colors.surface,
58
+ borderColor: isSelected
59
+ ? tokens.colors.primary
60
+ : tokens.colors.borderLight,
61
+ },
62
+ ]}
63
+ onPress={() => onMoodSelect(mood.id)}
64
+ activeOpacity={0.7}
65
+ >
66
+ <View
67
+ style={[
68
+ componentStyles.iconContainer,
69
+ {
70
+ backgroundColor: isSelected
71
+ ? "rgba(255, 255, 255, 0.2)"
72
+ : "rgba(0, 0, 0, 0.05)",
73
+ },
74
+ ]}
75
+ >
76
+ <AtomicIcon
77
+ name={mood.icon as never}
78
+ size="lg"
79
+ color={isSelected ? "onSurface" : "primary"}
80
+ />
81
+ </View>
82
+ <AtomicText
83
+ type="bodySmall"
84
+ style={[
85
+ componentStyles.moodName,
86
+ {
87
+ color: isSelected
88
+ ? tokens.colors.textInverse
89
+ : tokens.colors.textPrimary,
90
+ },
91
+ ]}
92
+ >
93
+ {mood.name}
94
+ </AtomicText>
95
+ <AtomicText
96
+ type="labelSmall"
97
+ style={[
98
+ componentStyles.moodDescription,
99
+ {
100
+ color: isSelected
101
+ ? tokens.colors.textInverse
102
+ : tokens.colors.textSecondary,
103
+ opacity: isSelected ? 0.9 : 0.7,
104
+ },
105
+ ]}
106
+ >
107
+ {mood.description}
108
+ </AtomicText>
109
+ </TouchableOpacity>
110
+ );
111
+ })}
112
+ </ScrollView>
113
+ {selectedMood === "custom" && hasCustomAudio && customAudioLabel && (
114
+ <View
115
+ style={[
116
+ componentStyles.customBadge,
117
+ { backgroundColor: tokens.colors.primary + "20" },
118
+ ]}
119
+ >
120
+ <AtomicIcon name="checkmark-circle-outline" size="sm" color="primary" />
121
+ <AtomicText
122
+ type="labelSmall"
123
+ style={[componentStyles.customBadgeText, { color: tokens.colors.primary }]}
124
+ >
125
+ {customAudioLabel}
126
+ </AtomicText>
127
+ </View>
128
+ )}
129
+ </View>
130
+ );
131
+ };
132
+
133
+ const componentStyles = StyleSheet.create({
134
+ section: {
135
+ padding: 16,
136
+ marginBottom: 8,
137
+ },
138
+ label: {
139
+ fontWeight: "600",
140
+ marginBottom: 12,
141
+ },
142
+ scroll: {
143
+ marginHorizontal: -16,
144
+ paddingHorizontal: 16,
145
+ },
146
+ card: {
147
+ width: 140,
148
+ padding: 16,
149
+ borderRadius: 16,
150
+ borderWidth: 2,
151
+ marginRight: 12,
152
+ alignItems: "center",
153
+ },
154
+ iconContainer: {
155
+ width: 56,
156
+ height: 56,
157
+ borderRadius: 28,
158
+ alignItems: "center",
159
+ justifyContent: "center",
160
+ },
161
+ moodName: {
162
+ fontWeight: "600",
163
+ marginTop: 8,
164
+ textAlign: "center",
165
+ },
166
+ moodDescription: {
167
+ marginTop: 4,
168
+ textAlign: "center",
169
+ },
170
+ customBadge: {
171
+ flexDirection: "row",
172
+ alignItems: "center",
173
+ padding: 12,
174
+ borderRadius: 12,
175
+ marginTop: 12,
176
+ },
177
+ customBadgeText: {
178
+ marginLeft: 8,
179
+ flex: 1,
180
+ },
181
+ });