@umituz/react-native-ai-feature-background 1.0.0

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.
@@ -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,113 @@
1
+ /**
2
+ * Processing Modal Component
3
+ * @description Modal shown during processing with progress
4
+ */
5
+
6
+ import React, { memo } from "react";
7
+ import { Modal, View, StyleSheet, ActivityIndicator } from "react-native";
8
+ import {
9
+ AtomicText,
10
+ useAppDesignTokens,
11
+ } from "@umituz/react-native-design-system";
12
+ import type { ProcessingModalProps } from "../../domain/entities";
13
+
14
+ export const ProcessingModal: React.FC<ProcessingModalProps> = memo(
15
+ function ProcessingModal({ visible, progress = 0, title }) {
16
+ const tokens = useAppDesignTokens();
17
+
18
+ return (
19
+ <Modal
20
+ visible={visible}
21
+ transparent
22
+ animationType="fade"
23
+ statusBarTranslucent
24
+ >
25
+ <View style={styles.overlay}>
26
+ <View
27
+ style={[
28
+ styles.content,
29
+ { backgroundColor: tokens.colors.surface },
30
+ ]}
31
+ >
32
+ <ActivityIndicator
33
+ size="large"
34
+ color={tokens.colors.primary}
35
+ />
36
+ {title && (
37
+ <AtomicText
38
+ type="bodyLarge"
39
+ style={[
40
+ styles.title,
41
+ { color: tokens.colors.textPrimary },
42
+ ]}
43
+ >
44
+ {title}
45
+ </AtomicText>
46
+ )}
47
+ {progress > 0 && (
48
+ <View style={styles.progressContainer}>
49
+ <View
50
+ style={[
51
+ styles.progressBar,
52
+ { backgroundColor: tokens.colors.surfaceSecondary },
53
+ ]}
54
+ >
55
+ <View
56
+ style={[
57
+ styles.progressFill,
58
+ {
59
+ backgroundColor: tokens.colors.primary,
60
+ width: `${Math.min(progress, 100)}%`,
61
+ },
62
+ ]}
63
+ />
64
+ </View>
65
+ <AtomicText
66
+ type="bodySmall"
67
+ style={{ color: tokens.colors.textSecondary }}
68
+ >
69
+ {Math.round(progress)}%
70
+ </AtomicText>
71
+ </View>
72
+ )}
73
+ </View>
74
+ </View>
75
+ </Modal>
76
+ );
77
+ }
78
+ );
79
+
80
+ const styles = StyleSheet.create({
81
+ overlay: {
82
+ flex: 1,
83
+ backgroundColor: "rgba(0, 0, 0, 0.7)",
84
+ justifyContent: "center",
85
+ alignItems: "center",
86
+ },
87
+ content: {
88
+ padding: 32,
89
+ borderRadius: 20,
90
+ alignItems: "center",
91
+ minWidth: 200,
92
+ },
93
+ title: {
94
+ marginTop: 16,
95
+ textAlign: "center",
96
+ },
97
+ progressContainer: {
98
+ marginTop: 16,
99
+ alignItems: "center",
100
+ width: "100%",
101
+ },
102
+ progressBar: {
103
+ width: "100%",
104
+ height: 6,
105
+ borderRadius: 3,
106
+ overflow: "hidden",
107
+ marginBottom: 8,
108
+ },
109
+ progressFill: {
110
+ height: "100%",
111
+ borderRadius: 3,
112
+ },
113
+ });
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Prompt Input Component
3
+ * @description Text input with sample prompt chips
4
+ */
5
+
6
+ import React, { memo } from "react";
7
+ import { View, TextInput, StyleSheet, TouchableOpacity } from "react-native";
8
+ import {
9
+ AtomicText,
10
+ useAppDesignTokens,
11
+ } from "@umituz/react-native-design-system";
12
+ import type { PromptInputProps } from "../../domain/entities";
13
+ import { DEFAULT_SAMPLE_PROMPTS } from "../../infrastructure/constants";
14
+
15
+ export const PromptInput: React.FC<PromptInputProps> = memo(
16
+ function PromptInput({
17
+ value,
18
+ onChangeText,
19
+ isProcessing,
20
+ label,
21
+ placeholder,
22
+ samplePrompts = DEFAULT_SAMPLE_PROMPTS,
23
+ samplePromptsLabel,
24
+ }) {
25
+ const tokens = useAppDesignTokens();
26
+
27
+ return (
28
+ <View style={styles.container}>
29
+ {label && (
30
+ <AtomicText
31
+ type="labelLarge"
32
+ style={[
33
+ styles.label,
34
+ {
35
+ color: tokens.colors.textPrimary,
36
+ marginBottom: tokens.spacing.sm,
37
+ },
38
+ ]}
39
+ >
40
+ {label}
41
+ </AtomicText>
42
+ )}
43
+
44
+ <TextInput
45
+ value={value}
46
+ onChangeText={onChangeText}
47
+ placeholder={placeholder}
48
+ placeholderTextColor={tokens.colors.textTertiary}
49
+ multiline
50
+ numberOfLines={4}
51
+ editable={!isProcessing}
52
+ style={[
53
+ styles.input,
54
+ {
55
+ backgroundColor: tokens.colors.surface,
56
+ borderColor: tokens.colors.border,
57
+ color: tokens.colors.textPrimary,
58
+ },
59
+ ]}
60
+ />
61
+
62
+ {samplePrompts.length > 0 && (
63
+ <>
64
+ {samplePromptsLabel && (
65
+ <AtomicText
66
+ type="labelMedium"
67
+ style={[
68
+ styles.sampleLabel,
69
+ {
70
+ color: tokens.colors.textSecondary,
71
+ marginTop: tokens.spacing.md,
72
+ marginBottom: tokens.spacing.sm,
73
+ },
74
+ ]}
75
+ >
76
+ {samplePromptsLabel}
77
+ </AtomicText>
78
+ )}
79
+
80
+ <View style={styles.sampleContainer}>
81
+ {samplePrompts.map((prompt) => (
82
+ <TouchableOpacity
83
+ key={prompt.id}
84
+ style={[
85
+ styles.sampleChip,
86
+ {
87
+ backgroundColor: tokens.colors.surfaceSecondary,
88
+ borderColor: tokens.colors.border,
89
+ },
90
+ ]}
91
+ onPress={() => onChangeText(prompt.text)}
92
+ disabled={isProcessing}
93
+ >
94
+ <AtomicText
95
+ type="bodySmall"
96
+ style={{ color: tokens.colors.textSecondary }}
97
+ numberOfLines={1}
98
+ >
99
+ {prompt.text}
100
+ </AtomicText>
101
+ </TouchableOpacity>
102
+ ))}
103
+ </View>
104
+ </>
105
+ )}
106
+ </View>
107
+ );
108
+ }
109
+ );
110
+
111
+ const styles = StyleSheet.create({
112
+ container: {
113
+ marginVertical: 16,
114
+ },
115
+ label: {
116
+ fontWeight: "600",
117
+ },
118
+ input: {
119
+ minHeight: 120,
120
+ borderWidth: 1,
121
+ borderRadius: 16,
122
+ paddingHorizontal: 16,
123
+ paddingVertical: 14,
124
+ fontSize: 16,
125
+ textAlignVertical: "top",
126
+ lineHeight: 24,
127
+ },
128
+ sampleLabel: {
129
+ fontWeight: "500",
130
+ },
131
+ sampleContainer: {
132
+ flexDirection: "row",
133
+ flexWrap: "wrap",
134
+ gap: 8,
135
+ },
136
+ sampleChip: {
137
+ paddingHorizontal: 12,
138
+ paddingVertical: 8,
139
+ borderRadius: 20,
140
+ borderWidth: 1,
141
+ },
142
+ });
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Result Display Component
3
+ * @description Displays processed image with save/reset actions
4
+ */
5
+
6
+ import React, { memo } from "react";
7
+ import { View, StyleSheet, Image, TouchableOpacity } from "react-native";
8
+ import {
9
+ AtomicText,
10
+ AtomicIcon,
11
+ useAppDesignTokens,
12
+ } from "@umituz/react-native-design-system";
13
+ import type { ResultDisplayProps } from "../../domain/entities";
14
+
15
+ export const ResultDisplay: React.FC<ResultDisplayProps> = memo(
16
+ function ResultDisplay({
17
+ imageUrl,
18
+ isProcessing,
19
+ onSave,
20
+ onReset,
21
+ saveButtonText,
22
+ resetButtonText,
23
+ }) {
24
+ const tokens = useAppDesignTokens();
25
+
26
+ if (!imageUrl || isProcessing) {
27
+ return null;
28
+ }
29
+
30
+ return (
31
+ <View style={styles.container}>
32
+ <View style={styles.resultContainer}>
33
+ <Image
34
+ source={{ uri: imageUrl }}
35
+ style={styles.resultImage}
36
+ resizeMode="cover"
37
+ />
38
+ </View>
39
+
40
+ <View style={styles.actionsContainer}>
41
+ <TouchableOpacity
42
+ style={[
43
+ styles.actionButton,
44
+ { backgroundColor: tokens.colors.backgroundSecondary },
45
+ ]}
46
+ onPress={onReset}
47
+ >
48
+ <AtomicIcon
49
+ name="refresh-cw"
50
+ size={20}
51
+ color="onSurface"
52
+ />
53
+ <AtomicText
54
+ type="labelLarge"
55
+ style={[styles.actionText, { color: tokens.colors.textPrimary }]}
56
+ >
57
+ {resetButtonText}
58
+ </AtomicText>
59
+ </TouchableOpacity>
60
+
61
+ <TouchableOpacity
62
+ style={[
63
+ styles.actionButton,
64
+ { backgroundColor: tokens.colors.success },
65
+ ]}
66
+ onPress={onSave}
67
+ >
68
+ <AtomicIcon
69
+ name="download"
70
+ size={20}
71
+ color="onPrimary"
72
+ />
73
+ <AtomicText
74
+ type="labelLarge"
75
+ style={[
76
+ styles.actionText,
77
+ { color: tokens.colors.backgroundPrimary },
78
+ ]}
79
+ >
80
+ {saveButtonText}
81
+ </AtomicText>
82
+ </TouchableOpacity>
83
+ </View>
84
+ </View>
85
+ );
86
+ }
87
+ );
88
+
89
+ const styles = StyleSheet.create({
90
+ container: {
91
+ marginTop: 24,
92
+ marginBottom: 16,
93
+ },
94
+ resultContainer: {
95
+ width: "100%",
96
+ aspectRatio: 1,
97
+ borderRadius: 24,
98
+ overflow: "hidden",
99
+ marginBottom: 16,
100
+ borderWidth: 1,
101
+ borderColor: "rgba(255,255,255,0.1)",
102
+ },
103
+ resultImage: {
104
+ width: "100%",
105
+ height: "100%",
106
+ },
107
+ actionsContainer: {
108
+ flexDirection: "row",
109
+ gap: 12,
110
+ },
111
+ actionButton: {
112
+ flex: 1,
113
+ flexDirection: "row",
114
+ alignItems: "center",
115
+ justifyContent: "center",
116
+ paddingVertical: 14,
117
+ borderRadius: 16,
118
+ gap: 8,
119
+ },
120
+ actionText: {
121
+ fontWeight: "600",
122
+ },
123
+ });
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Presentation Components Export
3
+ */
4
+
5
+ export { BackgroundFeature } from "./BackgroundFeature";
6
+ export type { BackgroundFeatureProps } from "./BackgroundFeature";
7
+
8
+ export { ImagePicker } from "./ImagePicker";
9
+ export { PromptInput } from "./PromptInput";
10
+ export { GenerateButton } from "./GenerateButton";
11
+ export { ResultDisplay } from "./ResultDisplay";
12
+ export { ErrorDisplay } from "./ErrorDisplay";
13
+ export { ProcessingModal } from "./ProcessingModal";
14
+ export { FeatureHeader } from "./FeatureHeader";