@umituz/react-native-ai-generation-content 1.17.2 → 1.17.4

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-ai-generation-content",
3
- "version": "1.17.2",
3
+ "version": "1.17.4",
4
4
  "description": "Provider-agnostic AI generation orchestration for React Native",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -1,123 +1,161 @@
1
1
  /**
2
2
  * GenerationProgressContent
3
3
  * Content for the AI generation progress modal
4
+ * Props-driven for 100+ apps compatibility
4
5
  */
5
6
 
6
7
  import React from "react";
7
8
  import { View, TouchableOpacity, StyleSheet } from "react-native";
8
- import { AtomicText, useAppDesignTokens } from "@umituz/react-native-design-system";
9
+ import {
10
+ AtomicText,
11
+ AtomicIcon,
12
+ useAppDesignTokens,
13
+ } from "@umituz/react-native-design-system";
9
14
  import { GenerationProgressBar } from "./GenerationProgressBar";
10
15
 
11
16
  export interface GenerationProgressContentProps {
12
- progress: number;
13
- title?: string;
14
- message?: string;
15
- hint?: string;
16
- dismissLabel?: string;
17
- onDismiss?: () => void;
18
- backgroundColor?: string;
19
- textColor?: string;
20
- progressColor?: string;
21
- progressBackgroundColor?: string;
22
- dismissButtonColor?: string;
17
+ readonly progress: number;
18
+ readonly icon?: string;
19
+ readonly title?: string;
20
+ readonly message?: string;
21
+ readonly hint?: string;
22
+ readonly dismissLabel?: string;
23
+ readonly onDismiss?: () => void;
24
+ readonly backgroundColor?: string;
25
+ readonly textColor?: string;
26
+ readonly hintColor?: string;
27
+ readonly progressColor?: string;
28
+ readonly progressBackgroundColor?: string;
29
+ readonly dismissButtonColor?: string;
23
30
  }
24
31
 
25
32
  export const GenerationProgressContent: React.FC<
26
- GenerationProgressContentProps
33
+ GenerationProgressContentProps
27
34
  > = ({
28
- progress,
29
- title,
30
- message,
31
- hint,
32
- dismissLabel,
33
- onDismiss,
34
- backgroundColor,
35
- textColor,
36
- progressColor,
37
- progressBackgroundColor,
38
- dismissButtonColor,
35
+ progress,
36
+ icon,
37
+ title,
38
+ message,
39
+ hint,
40
+ dismissLabel,
41
+ onDismiss,
42
+ backgroundColor,
43
+ textColor,
44
+ hintColor,
45
+ progressColor,
46
+ progressBackgroundColor,
47
+ dismissButtonColor,
39
48
  }) => {
40
- const tokens = useAppDesignTokens();
49
+ const tokens = useAppDesignTokens();
41
50
 
42
- const activeTextColor = textColor || tokens.colors.textPrimary;
43
- const activeBgColor = backgroundColor || tokens.colors.surface;
51
+ const activeTextColor = textColor || tokens.colors.textPrimary;
52
+ const activeBgColor = backgroundColor || tokens.colors.surface;
53
+ const activeHintColor = hintColor || tokens.colors.textTertiary;
44
54
 
45
- return (
46
- <View style={[styles.modal, { backgroundColor: activeBgColor }]}>
47
- {title && (
48
- <AtomicText style={[styles.title, { color: activeTextColor }]}>{title}</AtomicText>
49
- )}
55
+ return (
56
+ <View
57
+ style={[
58
+ styles.modal,
59
+ {
60
+ backgroundColor: activeBgColor,
61
+ borderColor: tokens.colors.borderLight,
62
+ },
63
+ ]}
64
+ >
65
+ {icon && (
66
+ <View style={styles.iconContainer}>
67
+ <AtomicIcon name={icon} size="xl" color="primary" />
68
+ </View>
69
+ )}
50
70
 
51
- {message && (
52
- <AtomicText style={[styles.message, { color: activeTextColor }]}>
53
- {message}
54
- </AtomicText>
55
- )}
71
+ {title && (
72
+ <AtomicText
73
+ type="headlineSmall"
74
+ style={[styles.title, { color: activeTextColor }]}
75
+ >
76
+ {title}
77
+ </AtomicText>
78
+ )}
56
79
 
57
- <GenerationProgressBar
58
- progress={progress}
59
- textColor={activeTextColor}
60
- progressColor={progressColor}
61
- backgroundColor={progressBackgroundColor}
62
- />
80
+ {message && (
81
+ <AtomicText
82
+ type="bodyMedium"
83
+ style={[styles.message, { color: tokens.colors.textSecondary }]}
84
+ >
85
+ {message}
86
+ </AtomicText>
87
+ )}
63
88
 
64
- {hint && (
65
- <AtomicText style={[styles.hint, { color: activeTextColor }]}>{hint}</AtomicText>
66
- )}
89
+ <GenerationProgressBar
90
+ progress={progress}
91
+ textColor={tokens.colors.primary}
92
+ progressColor={progressColor}
93
+ backgroundColor={progressBackgroundColor}
94
+ />
67
95
 
68
- {onDismiss && (
69
- <TouchableOpacity
70
- style={[
71
- styles.dismissButton,
72
- { backgroundColor: dismissButtonColor || tokens.colors.primary },
73
- ]}
74
- onPress={onDismiss}
75
- >
76
- <AtomicText style={styles.dismissText}>{dismissLabel || "OK"}</AtomicText>
77
- </TouchableOpacity>
78
- )}
79
- </View>
80
- );
81
- };
96
+ {hint && (
97
+ <AtomicText
98
+ type="bodySmall"
99
+ style={[styles.hint, { color: activeHintColor }]}
100
+ >
101
+ {hint}
102
+ </AtomicText>
103
+ )}
104
+
105
+ {onDismiss && (
106
+ <TouchableOpacity
107
+ style={[
108
+ styles.dismissButton,
109
+ { backgroundColor: dismissButtonColor || tokens.colors.primary },
110
+ ]}
111
+ onPress={onDismiss}
112
+ >
113
+ <AtomicText type="bodyMedium" style={styles.dismissText}>
114
+ {dismissLabel || "OK"}
115
+ </AtomicText>
116
+ </TouchableOpacity>
117
+ )}
118
+ </View>
119
+ );
120
+ };
82
121
 
83
122
  const styles = StyleSheet.create({
84
- modal: {
85
- width: "100%",
86
- maxWidth: 400,
87
- borderRadius: 24,
88
- padding: 32,
89
- alignItems: "center",
90
- },
91
- title: {
92
- fontSize: 20,
93
- fontWeight: "700",
94
- marginBottom: 12,
95
- textAlign: "center",
96
- },
97
- message: {
98
- fontSize: 16,
99
- marginBottom: 24,
100
- textAlign: "center",
101
- opacity: 0.8,
102
- },
103
- hint: {
104
- fontSize: 14,
105
- textAlign: "center",
106
- fontStyle: "italic",
107
- opacity: 0.6,
108
- marginBottom: 16,
109
- },
110
- dismissButton: {
111
- marginTop: 8,
112
- paddingVertical: 14,
113
- paddingHorizontal: 32,
114
- borderRadius: 12,
115
- minWidth: 140,
116
- alignItems: "center",
117
- },
118
- dismissText: {
119
- color: "#FFFFFF",
120
- fontSize: 16,
121
- fontWeight: "600",
122
- },
123
+ modal: {
124
+ width: "100%",
125
+ maxWidth: 380,
126
+ borderRadius: 24,
127
+ padding: 32,
128
+ borderWidth: 1,
129
+ alignItems: "center",
130
+ },
131
+ iconContainer: {
132
+ marginBottom: 20,
133
+ },
134
+ title: {
135
+ fontWeight: "700",
136
+ marginBottom: 8,
137
+ textAlign: "center",
138
+ },
139
+ message: {
140
+ marginBottom: 28,
141
+ textAlign: "center",
142
+ lineHeight: 20,
143
+ },
144
+ hint: {
145
+ textAlign: "center",
146
+ lineHeight: 18,
147
+ paddingHorizontal: 8,
148
+ },
149
+ dismissButton: {
150
+ marginTop: 16,
151
+ paddingVertical: 14,
152
+ paddingHorizontal: 32,
153
+ borderRadius: 12,
154
+ minWidth: 140,
155
+ alignItems: "center",
156
+ },
157
+ dismissText: {
158
+ color: "#FFFFFF",
159
+ fontWeight: "600",
160
+ },
123
161
  });
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * GenerationProgressModal
3
3
  * Generic AI generation progress modal with customizable rendering
4
+ * Props-driven for 100+ apps compatibility
4
5
  */
5
6
 
6
7
  import React from "react";
@@ -8,23 +9,26 @@ import { Modal, View, StyleSheet } from "react-native";
8
9
  import { useAppDesignTokens } from "@umituz/react-native-design-system";
9
10
  import {
10
11
  GenerationProgressContent,
11
- GenerationProgressContentProps,
12
+ type GenerationProgressContentProps,
12
13
  } from "./GenerationProgressContent";
13
14
 
14
15
  export interface GenerationProgressRenderProps {
15
- progress: number;
16
- title?: string;
17
- message?: string;
18
- hint?: string;
19
- onDismiss?: () => void;
16
+ readonly progress: number;
17
+ readonly icon?: string;
18
+ readonly title?: string;
19
+ readonly message?: string;
20
+ readonly hint?: string;
21
+ readonly onDismiss?: () => void;
20
22
  }
21
23
 
22
24
  export interface GenerationProgressModalProps
23
25
  extends Omit<GenerationProgressContentProps, "backgroundColor"> {
24
- visible: boolean;
25
- overlayColor?: string;
26
- modalBackgroundColor?: string;
27
- renderContent?: (props: GenerationProgressRenderProps) => React.ReactNode;
26
+ readonly visible: boolean;
27
+ readonly overlayColor?: string;
28
+ readonly modalBackgroundColor?: string;
29
+ readonly renderContent?: (
30
+ props: GenerationProgressRenderProps
31
+ ) => React.ReactNode;
28
32
  }
29
33
 
30
34
  export const GenerationProgressModal: React.FC<
@@ -32,67 +36,73 @@ export const GenerationProgressModal: React.FC<
32
36
  > = ({
33
37
  visible,
34
38
  progress,
39
+ icon,
35
40
  title,
36
41
  message,
37
42
  hint,
38
43
  dismissLabel,
39
44
  onDismiss,
40
- overlayColor = "rgba(0, 0, 0, 0.7)",
45
+ overlayColor = "rgba(0, 0, 0, 0.75)",
41
46
  modalBackgroundColor,
42
47
  textColor,
48
+ hintColor,
43
49
  progressColor,
44
50
  progressBackgroundColor,
45
51
  dismissButtonColor,
46
52
  renderContent,
47
53
  }) => {
48
- const tokens = useAppDesignTokens();
49
- const clampedProgress = Math.max(0, Math.min(100, progress));
54
+ const tokens = useAppDesignTokens();
55
+ const clampedProgress = Math.max(0, Math.min(100, progress));
50
56
 
51
- const content = renderContent ? (
52
- renderContent({
53
- progress: clampedProgress,
54
- title,
55
- message,
56
- hint,
57
- onDismiss,
58
- })
59
- ) : (
60
- <GenerationProgressContent
61
- progress={clampedProgress}
62
- title={title}
63
- message={message}
64
- hint={hint}
65
- dismissLabel={dismissLabel}
66
- onDismiss={onDismiss}
67
- backgroundColor={modalBackgroundColor || tokens.colors.surface}
68
- textColor={textColor || tokens.colors.textPrimary}
69
- progressColor={progressColor || tokens.colors.primary}
70
- progressBackgroundColor={
71
- progressBackgroundColor || tokens.colors.borderLight
72
- }
73
- dismissButtonColor={dismissButtonColor || tokens.colors.primary}
74
- />
75
- );
57
+ const content = renderContent ? (
58
+ renderContent({
59
+ progress: clampedProgress,
60
+ icon,
61
+ title,
62
+ message,
63
+ hint,
64
+ onDismiss,
65
+ })
66
+ ) : (
67
+ <GenerationProgressContent
68
+ progress={clampedProgress}
69
+ icon={icon}
70
+ title={title}
71
+ message={message}
72
+ hint={hint}
73
+ dismissLabel={dismissLabel}
74
+ onDismiss={onDismiss}
75
+ backgroundColor={modalBackgroundColor || tokens.colors.surface}
76
+ textColor={textColor || tokens.colors.textPrimary}
77
+ hintColor={hintColor || tokens.colors.textTertiary}
78
+ progressColor={progressColor || tokens.colors.primary}
79
+ progressBackgroundColor={
80
+ progressBackgroundColor || tokens.colors.surfaceVariant
81
+ }
82
+ dismissButtonColor={dismissButtonColor || tokens.colors.primary}
83
+ />
84
+ );
76
85
 
77
- return (
78
- <Modal
79
- visible={visible}
80
- transparent
81
- animationType="fade"
82
- statusBarTranslucent
83
- >
84
- <View style={[styles.overlay, { backgroundColor: overlayColor }]}>
85
- {content}
86
- </View>
87
- </Modal>
88
- );
89
- };
86
+ return (
87
+ <Modal
88
+ visible={visible}
89
+ transparent
90
+ animationType="fade"
91
+ statusBarTranslucent
92
+ onRequestClose={onDismiss}
93
+ >
94
+ <View style={[styles.overlay, { backgroundColor: overlayColor }]}>
95
+ {content}
96
+ </View>
97
+ </Modal>
98
+ );
99
+ };
90
100
 
91
101
  const styles = StyleSheet.create({
92
102
  overlay: {
93
103
  flex: 1,
94
104
  justifyContent: "center",
95
105
  alignItems: "center",
96
- padding: 20,
106
+ padding: 24,
97
107
  },
98
108
  });
@@ -0,0 +1,141 @@
1
+ /**
2
+ * GenerateButton Component
3
+ * Generic AI generation button with gradient/solid variants
4
+ * Props-driven for 100+ apps compatibility
5
+ */
6
+
7
+ import React from "react";
8
+ import { View, StyleSheet, TouchableOpacity } from "react-native";
9
+ import {
10
+ AtomicText,
11
+ useAppDesignTokens,
12
+ AtomicIcon,
13
+ } from "@umituz/react-native-design-system";
14
+ import { LinearGradient } from "expo-linear-gradient";
15
+
16
+ export interface GenerateButtonProps {
17
+ readonly isDisabled?: boolean;
18
+ readonly isProcessing?: boolean;
19
+ readonly onPress: () => void;
20
+ readonly text: string;
21
+ readonly processingText?: string;
22
+ readonly variant?: "gradient" | "solid";
23
+ readonly gradientColors?: readonly [string, string, ...string[]];
24
+ readonly icon?: string;
25
+ readonly iconSize?: number;
26
+ }
27
+
28
+ export const GenerateButton: React.FC<GenerateButtonProps> = ({
29
+ isDisabled = false,
30
+ isProcessing = false,
31
+ onPress,
32
+ text,
33
+ processingText,
34
+ variant = "gradient",
35
+ gradientColors = ["#FF6B9D", "#C74375", "#FF6B9D"],
36
+ icon = "sparkles",
37
+ iconSize = 24,
38
+ }) => {
39
+ const tokens = useAppDesignTokens();
40
+ const disabled = isDisabled || isProcessing;
41
+ const displayText = isProcessing && processingText ? processingText : text;
42
+
43
+ if (variant === "solid") {
44
+ return (
45
+ <View style={[styles.solidContainer, { marginTop: tokens.spacing.xl }]}>
46
+ <TouchableOpacity
47
+ onPress={onPress}
48
+ disabled={disabled}
49
+ activeOpacity={0.8}
50
+ style={[
51
+ styles.solidButton,
52
+ {
53
+ backgroundColor: disabled
54
+ ? tokens.colors.surfaceSecondary
55
+ : tokens.colors.primary,
56
+ },
57
+ ]}
58
+ >
59
+ <View style={styles.buttonContent}>
60
+ <AtomicIcon name={icon} customSize={20} customColor="#FFFFFF" />
61
+ <AtomicText type="bodyLarge" style={styles.solidButtonText}>
62
+ {displayText}
63
+ </AtomicText>
64
+ </View>
65
+ </TouchableOpacity>
66
+ </View>
67
+ );
68
+ }
69
+
70
+ return (
71
+ <View style={[styles.gradientContainer, { marginTop: tokens.spacing.xl }]}>
72
+ <TouchableOpacity
73
+ onPress={onPress}
74
+ disabled={disabled}
75
+ activeOpacity={0.85}
76
+ style={styles.buttonWrapper}
77
+ >
78
+ <LinearGradient
79
+ colors={disabled ? ["#9CA3AF", "#6B7280"] : gradientColors}
80
+ start={[0, 0]}
81
+ end={[1, 0]}
82
+ style={[styles.gradientButton, disabled && styles.disabledButton]}
83
+ >
84
+ <View style={styles.buttonContent}>
85
+ <AtomicIcon name={icon} customSize={iconSize} customColor="#FFF" />
86
+ <AtomicText type="bodyLarge" style={styles.gradientButtonText}>
87
+ {displayText}
88
+ </AtomicText>
89
+ </View>
90
+ </LinearGradient>
91
+ </TouchableOpacity>
92
+ </View>
93
+ );
94
+ };
95
+
96
+ const styles = StyleSheet.create({
97
+ gradientContainer: {
98
+ alignItems: "center",
99
+ paddingHorizontal: 16,
100
+ },
101
+ solidContainer: {
102
+ paddingHorizontal: 16,
103
+ },
104
+ buttonWrapper: {
105
+ width: "100%",
106
+ maxWidth: 320,
107
+ borderRadius: 30,
108
+ },
109
+ gradientButton: {
110
+ paddingVertical: 18,
111
+ paddingHorizontal: 32,
112
+ borderRadius: 30,
113
+ alignItems: "center",
114
+ justifyContent: "center",
115
+ },
116
+ solidButton: {
117
+ borderRadius: 16,
118
+ paddingVertical: 16,
119
+ paddingHorizontal: 24,
120
+ alignItems: "center",
121
+ justifyContent: "center",
122
+ },
123
+ disabledButton: {
124
+ opacity: 0.5,
125
+ },
126
+ buttonContent: {
127
+ flexDirection: "row",
128
+ alignItems: "center",
129
+ justifyContent: "center",
130
+ gap: 12,
131
+ },
132
+ gradientButtonText: {
133
+ color: "#FFFFFF",
134
+ fontWeight: "700",
135
+ fontSize: 18,
136
+ },
137
+ solidButtonText: {
138
+ color: "#FFFFFF",
139
+ fontWeight: "600",
140
+ },
141
+ });
@@ -0,0 +1 @@
1
+ export { GenerateButton, type GenerateButtonProps } from "./GenerateButton";
@@ -0,0 +1,111 @@
1
+ /**
2
+ * ErrorDisplay Component
3
+ * Generic error display with retry action
4
+ * Props-driven for 100+ apps compatibility
5
+ */
6
+
7
+ import React from "react";
8
+ import { View, StyleSheet, TouchableOpacity } from "react-native";
9
+ import {
10
+ AtomicText,
11
+ useAppDesignTokens,
12
+ AtomicIcon,
13
+ } from "@umituz/react-native-design-system";
14
+
15
+ export interface ErrorDisplayProps {
16
+ readonly error: string | null;
17
+ readonly onRetry?: () => void;
18
+ readonly retryText?: string;
19
+ readonly icon?: string;
20
+ }
21
+
22
+ export const ErrorDisplay: React.FC<ErrorDisplayProps> = ({
23
+ error,
24
+ onRetry,
25
+ retryText,
26
+ icon = "alert-circle",
27
+ }) => {
28
+ const tokens = useAppDesignTokens();
29
+
30
+ if (!error) {
31
+ return null;
32
+ }
33
+
34
+ return (
35
+ <View
36
+ style={[
37
+ styles.container,
38
+ {
39
+ backgroundColor: `${tokens.colors.error}15`,
40
+ borderColor: `${tokens.colors.error}30`,
41
+ padding: tokens.spacing.md,
42
+ marginTop: tokens.spacing.md,
43
+ },
44
+ ]}
45
+ >
46
+ <View style={styles.header}>
47
+ <AtomicIcon
48
+ name={icon}
49
+ customSize={20}
50
+ customColor={tokens.colors.error}
51
+ />
52
+ <AtomicText
53
+ type="bodyMedium"
54
+ style={[styles.errorText, { color: tokens.colors.error }]}
55
+ >
56
+ {error}
57
+ </AtomicText>
58
+ </View>
59
+ {onRetry && retryText && (
60
+ <TouchableOpacity
61
+ onPress={onRetry}
62
+ style={[
63
+ styles.retryButton,
64
+ {
65
+ backgroundColor: tokens.colors.error,
66
+ marginTop: tokens.spacing.sm,
67
+ },
68
+ ]}
69
+ >
70
+ <AtomicIcon
71
+ name="refresh"
72
+ customSize={16}
73
+ customColor={tokens.colors.onError}
74
+ />
75
+ <AtomicText
76
+ type="bodySmall"
77
+ style={{ color: tokens.colors.onError, fontWeight: "600" }}
78
+ >
79
+ {retryText}
80
+ </AtomicText>
81
+ </TouchableOpacity>
82
+ )}
83
+ </View>
84
+ );
85
+ };
86
+
87
+ const styles = StyleSheet.create({
88
+ container: {
89
+ borderRadius: 12,
90
+ borderWidth: 1,
91
+ },
92
+ header: {
93
+ flexDirection: "row",
94
+ alignItems: "flex-start",
95
+ gap: 10,
96
+ },
97
+ errorText: {
98
+ flex: 1,
99
+ lineHeight: 20,
100
+ },
101
+ retryButton: {
102
+ flexDirection: "row",
103
+ alignItems: "center",
104
+ justifyContent: "center",
105
+ gap: 6,
106
+ paddingVertical: 10,
107
+ paddingHorizontal: 16,
108
+ borderRadius: 8,
109
+ alignSelf: "flex-start",
110
+ },
111
+ });
@@ -0,0 +1,122 @@
1
+ /**
2
+ * ResultDisplay Component
3
+ * Generic result display with save/reset actions
4
+ * Props-driven for 100+ apps compatibility
5
+ */
6
+
7
+ import React from "react";
8
+ import { View, StyleSheet } from "react-native";
9
+ import {
10
+ AtomicText,
11
+ AtomicButton,
12
+ useAppDesignTokens,
13
+ AtomicIcon,
14
+ } from "@umituz/react-native-design-system";
15
+
16
+ export interface ResultDisplayAction {
17
+ readonly id: string;
18
+ readonly label: string;
19
+ readonly onPress: () => void;
20
+ readonly variant?: "primary" | "outline";
21
+ readonly icon?: string;
22
+ }
23
+
24
+ export interface ResultDisplayProps {
25
+ readonly visible?: boolean;
26
+ readonly successText: string;
27
+ readonly actions: ResultDisplayAction[];
28
+ readonly successIcon?: string;
29
+ }
30
+
31
+ export const ResultDisplay: React.FC<ResultDisplayProps> = ({
32
+ visible = true,
33
+ successText,
34
+ actions,
35
+ successIcon = "checkmark-circle",
36
+ }) => {
37
+ const tokens = useAppDesignTokens();
38
+
39
+ if (!visible) {
40
+ return null;
41
+ }
42
+
43
+ return (
44
+ <View style={[styles.container, { marginTop: tokens.spacing.lg }]}>
45
+ <View style={styles.successHeader}>
46
+ <AtomicIcon
47
+ name={successIcon}
48
+ customSize={24}
49
+ customColor={tokens.colors.success}
50
+ />
51
+ <AtomicText
52
+ type="bodyMedium"
53
+ style={[styles.successText, { color: tokens.colors.success }]}
54
+ >
55
+ {successText}
56
+ </AtomicText>
57
+ </View>
58
+ <View style={[styles.actionButtons, { gap: tokens.spacing.sm }]}>
59
+ {actions.map((action) => (
60
+ <AtomicButton
61
+ key={action.id}
62
+ onPress={action.onPress}
63
+ variant={action.variant === "outline" ? "outline" : undefined}
64
+ style={styles.actionButton}
65
+ >
66
+ <View style={styles.buttonContent}>
67
+ {action.icon && (
68
+ <AtomicIcon
69
+ name={action.icon}
70
+ customSize={18}
71
+ customColor={
72
+ action.variant === "outline"
73
+ ? tokens.colors.primary
74
+ : tokens.colors.onPrimary
75
+ }
76
+ />
77
+ )}
78
+ <AtomicText
79
+ type="bodyMedium"
80
+ style={{
81
+ color:
82
+ action.variant === "outline"
83
+ ? tokens.colors.primary
84
+ : tokens.colors.onPrimary,
85
+ }}
86
+ >
87
+ {action.label}
88
+ </AtomicText>
89
+ </View>
90
+ </AtomicButton>
91
+ ))}
92
+ </View>
93
+ </View>
94
+ );
95
+ };
96
+
97
+ const styles = StyleSheet.create({
98
+ container: {
99
+ alignItems: "center",
100
+ },
101
+ successHeader: {
102
+ flexDirection: "row",
103
+ alignItems: "center",
104
+ gap: 8,
105
+ marginBottom: 16,
106
+ },
107
+ successText: {
108
+ textAlign: "center",
109
+ },
110
+ actionButtons: {
111
+ width: "100%",
112
+ maxWidth: 280,
113
+ },
114
+ actionButton: {
115
+ flex: 1,
116
+ },
117
+ buttonContent: {
118
+ flexDirection: "row",
119
+ alignItems: "center",
120
+ gap: 8,
121
+ },
122
+ });
@@ -0,0 +1,6 @@
1
+ export {
2
+ ResultDisplay,
3
+ type ResultDisplayProps,
4
+ type ResultDisplayAction,
5
+ } from "./ResultDisplay";
6
+ export { ErrorDisplay, type ErrorDisplayProps } from "./ErrorDisplay";
@@ -0,0 +1,85 @@
1
+ /**
2
+ * FeatureHeader Component
3
+ * Generic feature header with hero image and description
4
+ * Props-driven for 100+ apps compatibility
5
+ */
6
+
7
+ import React from "react";
8
+ import { View, StyleSheet, ImageBackground } from "react-native";
9
+ import type { ImageSourcePropType } from "react-native";
10
+ import {
11
+ AtomicText,
12
+ useAppDesignTokens,
13
+ } from "@umituz/react-native-design-system";
14
+ import { LinearGradient } from "expo-linear-gradient";
15
+
16
+ export interface FeatureHeaderProps {
17
+ readonly imageSource: ImageSourcePropType;
18
+ readonly description: string;
19
+ readonly gradientColors?: readonly [string, string, ...string[]];
20
+ readonly minHeight?: number;
21
+ }
22
+
23
+ export const FeatureHeader: React.FC<FeatureHeaderProps> = ({
24
+ imageSource,
25
+ description,
26
+ gradientColors = ["rgba(0,0,0,0.3)", "rgba(0,0,0,0.1)", "rgba(0,0,0,0.4)"],
27
+ minHeight = 200,
28
+ }) => {
29
+ const tokens = useAppDesignTokens();
30
+
31
+ return (
32
+ <>
33
+ <View style={styles.headerContainer}>
34
+ <ImageBackground
35
+ source={imageSource}
36
+ style={[styles.heroImage, { minHeight }]}
37
+ imageStyle={styles.heroImageStyle}
38
+ >
39
+ <LinearGradient
40
+ colors={gradientColors}
41
+ style={[styles.gradient, { minHeight }]}
42
+ />
43
+ </ImageBackground>
44
+ </View>
45
+ <AtomicText
46
+ type="bodyLarge"
47
+ style={[
48
+ styles.description,
49
+ {
50
+ color: tokens.colors.textSecondary,
51
+ marginTop: tokens.spacing.md,
52
+ marginBottom: tokens.spacing.md,
53
+ },
54
+ ]}
55
+ >
56
+ {description}
57
+ </AtomicText>
58
+ </>
59
+ );
60
+ };
61
+
62
+ const styles = StyleSheet.create({
63
+ headerContainer: {
64
+ marginBottom: 8,
65
+ borderRadius: 20,
66
+ overflow: "hidden",
67
+ borderWidth: 1,
68
+ borderColor: "rgba(255, 255, 255, 0.1)",
69
+ },
70
+ heroImage: {
71
+ width: "100%",
72
+ },
73
+ heroImageStyle: {
74
+ borderRadius: 20,
75
+ },
76
+ gradient: {
77
+ flex: 1,
78
+ },
79
+ description: {
80
+ textAlign: "center",
81
+ lineHeight: 22,
82
+ paddingHorizontal: 16,
83
+ fontWeight: "500",
84
+ },
85
+ });
@@ -0,0 +1 @@
1
+ export { FeatureHeader, type FeatureHeaderProps } from "./FeatureHeader";
@@ -0,0 +1,95 @@
1
+ /**
2
+ * DualImagePicker Component
3
+ * Two-image picker for face swap, AI hug/kiss features
4
+ * Props-driven for 100+ apps compatibility
5
+ */
6
+
7
+ import React from "react";
8
+ import { View, StyleSheet } from "react-native";
9
+ import { useAppDesignTokens } from "@umituz/react-native-design-system";
10
+ import { ImagePickerBox, type ImagePickerBoxProps } from "./ImagePickerBox";
11
+
12
+ export interface DualImagePickerProps {
13
+ readonly sourceImageUri: string | null;
14
+ readonly targetImageUri: string | null;
15
+ readonly isDisabled?: boolean;
16
+ readonly onSelectSource: () => void;
17
+ readonly onSelectTarget: () => void;
18
+ readonly sourcePlaceholder: string;
19
+ readonly targetPlaceholder: string;
20
+ readonly sourceGradient?: ImagePickerBoxProps["gradientColors"];
21
+ readonly targetGradient?: ImagePickerBoxProps["gradientColors"];
22
+ readonly variant?: ImagePickerBoxProps["variant"];
23
+ readonly layout?: "horizontal" | "vertical";
24
+ }
25
+
26
+ export const DualImagePicker: React.FC<DualImagePickerProps> = ({
27
+ sourceImageUri,
28
+ targetImageUri,
29
+ isDisabled = false,
30
+ onSelectSource,
31
+ onSelectTarget,
32
+ sourcePlaceholder,
33
+ targetPlaceholder,
34
+ sourceGradient = ["#667eea", "#764ba2"],
35
+ targetGradient = ["#f093fb", "#f5576c"],
36
+ variant = "portrait",
37
+ layout = "horizontal",
38
+ }) => {
39
+ const tokens = useAppDesignTokens();
40
+ const isHorizontal = layout === "horizontal";
41
+
42
+ return (
43
+ <View
44
+ style={[
45
+ styles.container,
46
+ isHorizontal ? styles.horizontal : styles.vertical,
47
+ { gap: tokens.spacing.md },
48
+ ]}
49
+ >
50
+ <View style={isHorizontal ? styles.pickerHalf : styles.pickerFull}>
51
+ <ImagePickerBox
52
+ imageUri={sourceImageUri}
53
+ isDisabled={isDisabled}
54
+ onPress={onSelectSource}
55
+ placeholderText={sourcePlaceholder}
56
+ gradientColors={sourceGradient}
57
+ variant={variant}
58
+ />
59
+ </View>
60
+
61
+ <View style={isHorizontal ? styles.pickerHalf : styles.pickerFull}>
62
+ <ImagePickerBox
63
+ imageUri={targetImageUri}
64
+ isDisabled={isDisabled}
65
+ onPress={onSelectTarget}
66
+ placeholderText={targetPlaceholder}
67
+ gradientColors={targetGradient}
68
+ variant={variant}
69
+ />
70
+ </View>
71
+ </View>
72
+ );
73
+ };
74
+
75
+ const styles = StyleSheet.create({
76
+ container: {
77
+ width: "100%",
78
+ },
79
+ horizontal: {
80
+ flexDirection: "row",
81
+ justifyContent: "center",
82
+ },
83
+ vertical: {
84
+ flexDirection: "column",
85
+ alignItems: "center",
86
+ },
87
+ pickerHalf: {
88
+ flex: 1,
89
+ alignItems: "center",
90
+ },
91
+ pickerFull: {
92
+ width: "100%",
93
+ alignItems: "center",
94
+ },
95
+ });
@@ -0,0 +1,165 @@
1
+ /**
2
+ * ImagePickerBox Component
3
+ * Generic image picker box with gradient design
4
+ * Props-driven for 100+ apps compatibility
5
+ */
6
+
7
+ import React from "react";
8
+ import {
9
+ View,
10
+ StyleSheet,
11
+ TouchableOpacity,
12
+ Image,
13
+ type ViewStyle,
14
+ } from "react-native";
15
+ import {
16
+ AtomicText,
17
+ useAppDesignTokens,
18
+ AtomicIcon,
19
+ } from "@umituz/react-native-design-system";
20
+ import { LinearGradient } from "expo-linear-gradient";
21
+
22
+ export interface ImagePickerBoxProps {
23
+ readonly imageUri: string | null;
24
+ readonly isDisabled?: boolean;
25
+ readonly onPress: () => void;
26
+ readonly placeholderText: string;
27
+ readonly gradientColors?: readonly [string, string, ...string[]];
28
+ readonly variant?: "portrait" | "square" | "landscape";
29
+ readonly size?: "sm" | "md" | "lg";
30
+ readonly uploadIcon?: string;
31
+ readonly editIcon?: string;
32
+ }
33
+
34
+ const VARIANT_STYLES: Record<string, ViewStyle> = {
35
+ portrait: { width: 200, height: 280, borderRadius: 20 },
36
+ square: { width: "100%", aspectRatio: 1, borderRadius: 24 },
37
+ landscape: { width: "100%", aspectRatio: 16 / 9, borderRadius: 16 },
38
+ };
39
+
40
+ const SIZE_MULTIPLIERS = { sm: 0.7, md: 1, lg: 1.3 };
41
+
42
+ export const ImagePickerBox: React.FC<ImagePickerBoxProps> = ({
43
+ imageUri,
44
+ isDisabled = false,
45
+ onPress,
46
+ placeholderText,
47
+ gradientColors = ["#667eea", "#764ba2"],
48
+ variant = "portrait",
49
+ size = "md",
50
+ uploadIcon = "cloud-upload-outline",
51
+ editIcon = "image-outline",
52
+ }) => {
53
+ const tokens = useAppDesignTokens();
54
+ const multiplier = SIZE_MULTIPLIERS[size];
55
+ const baseStyle = VARIANT_STYLES[variant];
56
+ const iconSize = Math.round(32 * multiplier);
57
+
58
+ return (
59
+ <View style={styles.container}>
60
+ <TouchableOpacity
61
+ style={[
62
+ styles.box,
63
+ baseStyle,
64
+ { backgroundColor: tokens.colors.backgroundSecondary },
65
+ ]}
66
+ onPress={onPress}
67
+ disabled={isDisabled}
68
+ activeOpacity={0.8}
69
+ >
70
+ {imageUri ? (
71
+ <View style={styles.imageContainer}>
72
+ <Image source={{ uri: imageUri }} style={styles.image} />
73
+ <LinearGradient
74
+ colors={["transparent", "rgba(0,0,0,0.3)"]}
75
+ style={styles.imageOverlay}
76
+ >
77
+ <View
78
+ style={[
79
+ styles.editBadge,
80
+ { backgroundColor: `${gradientColors[1]}E6` },
81
+ ]}
82
+ >
83
+ <AtomicIcon
84
+ name={editIcon}
85
+ customSize={Math.round(16 * multiplier)}
86
+ customColor="#FFFFFF"
87
+ />
88
+ </View>
89
+ </LinearGradient>
90
+ </View>
91
+ ) : (
92
+ <LinearGradient colors={gradientColors} style={styles.placeholder}>
93
+ <View style={styles.placeholderContent}>
94
+ <View style={styles.uploadIconContainer}>
95
+ <AtomicIcon
96
+ name={uploadIcon}
97
+ customSize={iconSize}
98
+ customColor="#FFFFFF"
99
+ />
100
+ </View>
101
+ <AtomicText
102
+ type="bodyMedium"
103
+ style={[styles.placeholderText, { color: "#FFFFFF" }]}
104
+ >
105
+ {placeholderText}
106
+ </AtomicText>
107
+ </View>
108
+ </LinearGradient>
109
+ )}
110
+ </TouchableOpacity>
111
+ </View>
112
+ );
113
+ };
114
+
115
+ const styles = StyleSheet.create({
116
+ container: {
117
+ marginVertical: 16,
118
+ alignItems: "center",
119
+ },
120
+ box: {
121
+ overflow: "hidden",
122
+ },
123
+ imageContainer: {
124
+ flex: 1,
125
+ position: "relative",
126
+ },
127
+ image: {
128
+ width: "100%",
129
+ height: "100%",
130
+ },
131
+ imageOverlay: {
132
+ position: "absolute",
133
+ bottom: 0,
134
+ left: 0,
135
+ right: 0,
136
+ height: "30%",
137
+ justifyContent: "flex-end",
138
+ alignItems: "flex-end",
139
+ padding: 12,
140
+ },
141
+ editBadge: {
142
+ borderRadius: 20,
143
+ padding: 8,
144
+ },
145
+ placeholder: {
146
+ flex: 1,
147
+ justifyContent: "center",
148
+ alignItems: "center",
149
+ },
150
+ placeholderContent: {
151
+ alignItems: "center",
152
+ justifyContent: "center",
153
+ paddingHorizontal: 16,
154
+ },
155
+ uploadIconContainer: {
156
+ backgroundColor: "rgba(255,255,255,0.2)",
157
+ borderRadius: 40,
158
+ padding: 16,
159
+ marginBottom: 12,
160
+ },
161
+ placeholderText: {
162
+ textAlign: "center",
163
+ fontWeight: "600",
164
+ },
165
+ });
@@ -0,0 +1,2 @@
1
+ export { ImagePickerBox, type ImagePickerBoxProps } from "./ImagePickerBox";
2
+ export { DualImagePicker, type DualImagePickerProps } from "./DualImagePicker";
@@ -23,3 +23,7 @@ export type { PendingJobCardActionsProps } from "./PendingJobCardActions";
23
23
 
24
24
  export * from "./result";
25
25
  export * from "./photo-step";
26
+ export * from "./image-picker";
27
+ export * from "./buttons";
28
+ export * from "./display";
29
+ export * from "./headers";