@umituz/react-native-ai-generation-content 1.13.1 → 1.15.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.
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * ResultActions Component
3
- * Action buttons for generation results
3
+ * Action buttons for generation results - fully configurable
4
4
  */
5
5
 
6
6
  import * as React from "react";
@@ -11,6 +11,8 @@ import {
11
11
  AtomicIcon,
12
12
  useAppDesignTokens,
13
13
  } from "@umituz/react-native-design-system";
14
+ import type { ResultActionsConfig } from "../../types/result-config.types";
15
+ import { DEFAULT_RESULT_CONFIG } from "../../types/result-config.types";
14
16
 
15
17
  export interface ResultActionsProps {
16
18
  onShare?: () => void;
@@ -24,6 +26,7 @@ export interface ResultActionsProps {
24
26
  save: string;
25
27
  retry: string;
26
28
  };
29
+ config?: ResultActionsConfig;
27
30
  }
28
31
 
29
32
  export const ResultActions: React.FC<ResultActionsProps> = ({
@@ -33,102 +36,186 @@ export const ResultActions: React.FC<ResultActionsProps> = ({
33
36
  isSharing = false,
34
37
  isSaving = false,
35
38
  translations,
39
+ config = DEFAULT_RESULT_CONFIG.actions,
36
40
  }) => {
37
41
  const tokens = useAppDesignTokens();
42
+ const cfg = { ...DEFAULT_RESULT_CONFIG.actions, ...config };
38
43
 
39
- const styles = useMemo(() => StyleSheet.create({
40
- container: {
41
- paddingHorizontal: 20,
42
- paddingBottom: 20,
43
- },
44
- retryButton: {
45
- flexDirection: "row",
46
- alignItems: "center",
47
- justifyContent: "center",
48
- gap: 6,
49
- paddingVertical: 12,
50
- marginBottom: 16,
51
- },
52
- retryText: {
53
- fontSize: 14,
54
- fontWeight: "600",
55
- color: tokens.colors.primary,
56
- },
57
- buttons: {
58
- flexDirection: "row",
59
- gap: 10,
60
- },
61
- button: {
62
- flex: 1,
63
- flexDirection: "row",
64
- alignItems: "center",
65
- justifyContent: "center",
66
- gap: 8,
67
- paddingVertical: 14,
68
- borderRadius: 14,
69
- },
70
- shareButton: {
71
- backgroundColor: tokens.colors.primary,
72
- },
73
- shareText: {
74
- fontSize: 15,
75
- fontWeight: "700",
76
- color: tokens.colors.onPrimary,
77
- },
78
- saveButton: {
79
- backgroundColor: tokens.colors.surface,
80
- borderWidth: 2,
81
- borderColor: tokens.colors.primary,
82
- },
83
- saveText: {
84
- fontSize: 15,
85
- fontWeight: "700",
86
- color: tokens.colors.primary,
87
- },
88
- }), [tokens]);
44
+ const styles = useMemo(
45
+ () =>
46
+ StyleSheet.create({
47
+ container: {
48
+ paddingHorizontal: cfg.spacing?.paddingHorizontal ?? 20,
49
+ paddingBottom: cfg.spacing?.paddingBottom ?? 20,
50
+ },
51
+ retryButton: {
52
+ flexDirection: "row",
53
+ alignItems: "center",
54
+ justifyContent: "center",
55
+ gap: 6,
56
+ paddingVertical: 12,
57
+ marginBottom: cfg.retry?.position === "top" ? 16 : 0,
58
+ marginTop: cfg.retry?.position === "bottom" ? 16 : 0,
59
+ },
60
+ retryText: {
61
+ fontSize: 14,
62
+ fontWeight: "600",
63
+ color: tokens.colors.primary,
64
+ },
65
+ buttons: {
66
+ flexDirection:
67
+ cfg.layout === "vertical" ? "column" : "row",
68
+ gap: cfg.buttonSpacing ?? 10,
69
+ },
70
+ button: {
71
+ flex: cfg.layout === "horizontal" ? 1 : undefined,
72
+ flexDirection: "row",
73
+ alignItems: "center",
74
+ justifyContent: "center",
75
+ gap: 8,
76
+ paddingVertical: 14,
77
+ borderRadius: 14,
78
+ },
79
+ }),
80
+ [tokens, cfg],
81
+ );
82
+
83
+ const getButtonStyle = (variant?: string) => {
84
+ switch (variant) {
85
+ case "primary":
86
+ return {
87
+ backgroundColor: tokens.colors.primary,
88
+ color: tokens.colors.onPrimary,
89
+ textColor: tokens.colors.onPrimary,
90
+ };
91
+ case "secondary":
92
+ return {
93
+ backgroundColor: tokens.colors.surface,
94
+ borderWidth: 2,
95
+ borderColor: tokens.colors.primary,
96
+ color: tokens.colors.primary,
97
+ textColor: tokens.colors.primary,
98
+ };
99
+ case "outline":
100
+ return {
101
+ backgroundColor: "transparent",
102
+ borderWidth: 1,
103
+ borderColor: tokens.colors.borderLight,
104
+ color: tokens.colors.textPrimary,
105
+ textColor: tokens.colors.textPrimary,
106
+ };
107
+ case "text":
108
+ return {
109
+ backgroundColor: "transparent",
110
+ color: tokens.colors.primary,
111
+ textColor: tokens.colors.primary,
112
+ };
113
+ default:
114
+ return {
115
+ backgroundColor: tokens.colors.primary,
116
+ color: tokens.colors.onPrimary,
117
+ textColor: tokens.colors.onPrimary,
118
+ };
119
+ }
120
+ };
121
+
122
+ const renderButton = (
123
+ key: string,
124
+ onPress?: () => void,
125
+ isProcessing?: boolean,
126
+ label?: string,
127
+ processingLabel?: string,
128
+ icon?: string,
129
+ variant?: string,
130
+ ) => {
131
+ if (!onPress) return null;
132
+
133
+ const buttonStyle = getButtonStyle(variant);
134
+ const displayLabel = isProcessing ? processingLabel : label;
135
+ const displayIcon = isProcessing ? "hourglass" : icon;
136
+
137
+ return (
138
+ <TouchableOpacity
139
+ key={key}
140
+ style={[styles.button, buttonStyle]}
141
+ onPress={onPress}
142
+ disabled={isProcessing}
143
+ >
144
+ {displayIcon && (
145
+ <AtomicIcon
146
+ name={displayIcon}
147
+ size="md"
148
+ customColor={buttonStyle.textColor}
149
+ />
150
+ )}
151
+ <AtomicText
152
+ style={{
153
+ fontSize: 15,
154
+ fontWeight: "700",
155
+ color: buttonStyle.textColor,
156
+ }}
157
+ >
158
+ {displayLabel}
159
+ </AtomicText>
160
+ </TouchableOpacity>
161
+ );
162
+ };
163
+
164
+ const topActions = cfg.retry?.enabled && cfg.retry?.position === "top" && onRetry;
165
+ const bottomActions =
166
+ cfg.retry?.enabled && cfg.retry?.position === "bottom" && onRetry;
89
167
 
90
168
  return (
91
169
  <View style={styles.container}>
92
- {onRetry && (
170
+ {topActions && (
93
171
  <TouchableOpacity style={styles.retryButton} onPress={onRetry}>
94
- <AtomicIcon name="refresh" size="sm" color="primary" />
95
- <AtomicText style={styles.retryText}>{translations.retry}</AtomicText>
172
+ <AtomicIcon
173
+ name={cfg.retry?.icon ?? "refresh"}
174
+ size="sm"
175
+ color="primary"
176
+ />
177
+ <AtomicText style={styles.retryText}>
178
+ {cfg.retry?.label ?? translations.retry}
179
+ </AtomicText>
96
180
  </TouchableOpacity>
97
181
  )}
98
182
 
99
183
  <View style={styles.buttons}>
100
- {onShare && (
101
- <TouchableOpacity
102
- style={[styles.button, styles.shareButton]}
103
- onPress={onShare}
104
- disabled={isSharing}
105
- >
106
- <AtomicIcon
107
- name={isSharing ? "hourglass" : "share-social"}
108
- size="md"
109
- color="onPrimary"
110
- />
111
- <AtomicText style={styles.shareText}>
112
- {isSharing ? translations.sharing : translations.share}
113
- </AtomicText>
114
- </TouchableOpacity>
115
- )}
184
+ {cfg.share?.enabled &&
185
+ renderButton(
186
+ "share",
187
+ onShare,
188
+ isSharing,
189
+ cfg.share?.label ?? translations.share,
190
+ translations.sharing,
191
+ cfg.share?.icon ?? "share-social",
192
+ cfg.share?.variant,
193
+ )}
116
194
 
117
- {onSave && (
118
- <TouchableOpacity
119
- style={[styles.button, styles.saveButton]}
120
- onPress={onSave}
121
- disabled={isSaving}
122
- >
123
- <AtomicIcon
124
- name={isSaving ? "hourglass" : "download"}
125
- size="md"
126
- color="primary"
127
- />
128
- <AtomicText style={styles.saveText}>{translations.save}</AtomicText>
129
- </TouchableOpacity>
130
- )}
195
+ {cfg.save?.enabled &&
196
+ renderButton(
197
+ "save",
198
+ onSave,
199
+ isSaving,
200
+ cfg.save?.label ?? translations.save,
201
+ translations.save,
202
+ cfg.save?.icon ?? "download",
203
+ cfg.save?.variant,
204
+ )}
131
205
  </View>
206
+
207
+ {bottomActions && (
208
+ <TouchableOpacity style={styles.retryButton} onPress={onRetry}>
209
+ <AtomicIcon
210
+ name={cfg.retry?.icon ?? "refresh"}
211
+ size="sm"
212
+ color="primary"
213
+ />
214
+ <AtomicText style={styles.retryText}>
215
+ {cfg.retry?.label ?? translations.retry}
216
+ </AtomicText>
217
+ </TouchableOpacity>
218
+ )}
132
219
  </View>
133
220
  );
134
221
  };
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * ResultHeader Component
3
- * Header with title and date badge
3
+ * Header with title and date badge - fully configurable
4
4
  */
5
5
 
6
6
  import * as React from "react";
@@ -11,57 +11,88 @@ import {
11
11
  AtomicIcon,
12
12
  useAppDesignTokens,
13
13
  } from "@umituz/react-native-design-system";
14
+ import type { ResultHeaderConfig } from "../../types/result-config.types";
15
+ import { DEFAULT_RESULT_CONFIG } from "../../types/result-config.types";
14
16
 
15
17
  export interface ResultHeaderProps {
16
18
  title?: string;
17
19
  date?: string;
20
+ config?: ResultHeaderConfig;
18
21
  }
19
22
 
20
- export const ResultHeader: React.FC<ResultHeaderProps> = ({ title, date }) => {
23
+ export const ResultHeader: React.FC<ResultHeaderProps> = ({
24
+ title,
25
+ date,
26
+ config = DEFAULT_RESULT_CONFIG.header,
27
+ }) => {
21
28
  const tokens = useAppDesignTokens();
29
+ const cfg = { ...DEFAULT_RESULT_CONFIG.header, ...config };
22
30
 
23
- const styles = useMemo(() => StyleSheet.create({
24
- container: {
25
- alignItems: "center",
26
- paddingHorizontal: 24,
27
- marginBottom: 20,
28
- },
29
- title: {
30
- fontSize: 24,
31
- lineHeight: 32,
32
- fontWeight: "800",
33
- color: tokens.colors.textPrimary,
34
- textAlign: "center",
35
- marginBottom: 12,
36
- },
37
- badge: {
38
- flexDirection: "row",
39
- alignItems: "center",
40
- gap: 6,
41
- paddingHorizontal: 14,
42
- paddingVertical: 6,
43
- backgroundColor: tokens.colors.primaryContainer,
44
- borderRadius: 16,
45
- },
46
- dateText: {
47
- fontSize: 12,
48
- fontWeight: "600",
49
- color: tokens.colors.primary,
50
- },
51
- }), [tokens]);
31
+ const styles = useMemo(() => {
32
+ const badgeStyles =
33
+ cfg.dateBadgeStyle === "outline"
34
+ ? {
35
+ backgroundColor: "transparent",
36
+ borderWidth: 1,
37
+ borderColor: tokens.colors.primary,
38
+ }
39
+ : cfg.dateBadgeStyle === "minimal"
40
+ ? {
41
+ backgroundColor: "transparent",
42
+ }
43
+ : {
44
+ backgroundColor: tokens.colors.primaryContainer,
45
+ };
46
+
47
+ return StyleSheet.create({
48
+ container: {
49
+ alignItems:
50
+ cfg.titleAlignment === "left"
51
+ ? "flex-start"
52
+ : cfg.titleAlignment === "right"
53
+ ? "flex-end"
54
+ : "center",
55
+ paddingHorizontal: cfg.spacing?.paddingHorizontal ?? 24,
56
+ marginBottom: cfg.spacing?.marginBottom ?? 20,
57
+ },
58
+ title: {
59
+ fontSize: cfg.titleFontSize ?? 24,
60
+ lineHeight: (cfg.titleFontSize ?? 24) * 1.33,
61
+ fontWeight: cfg.titleFontWeight ?? "800",
62
+ color: tokens.colors.textPrimary,
63
+ textAlign: cfg.titleAlignment ?? "center",
64
+ marginBottom: cfg.spacing?.titleMarginBottom ?? 12,
65
+ },
66
+ badge: {
67
+ flexDirection: "row",
68
+ alignItems: "center",
69
+ gap: 6,
70
+ paddingHorizontal: 14,
71
+ paddingVertical: 6,
72
+ borderRadius: 16,
73
+ ...badgeStyles,
74
+ },
75
+ dateText: {
76
+ fontSize: 12,
77
+ fontWeight: "600",
78
+ color: tokens.colors.primary,
79
+ },
80
+ });
81
+ }, [tokens, cfg]);
52
82
 
53
83
  if (!title && !date) return null;
84
+ if (!cfg.showTitle && !cfg.showDate) return null;
54
85
 
55
86
  return (
56
87
  <View style={styles.container}>
57
- {title && <AtomicText style={styles.title}>{title}</AtomicText>}
58
- {date && (
88
+ {cfg.showTitle && title && (
89
+ <AtomicText style={styles.title}>{title}</AtomicText>
90
+ )}
91
+ {cfg.showDate && date && (
59
92
  <View style={styles.badge}>
60
- <AtomicIcon
61
- name="calendar-outline"
62
- size="sm"
63
- color="primary"
64
- />
93
+ {cfg.showDateIcon && (
94
+ <AtomicIcon name="calendar-outline" size="sm" color="primary" />
95
+ )}
65
96
  <AtomicText style={styles.dateText}>{date}</AtomicText>
66
97
  </View>
67
98
  )}
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * ResultImageCard Component
3
- * Displays generated image with AI badge
3
+ * Displays generated image with AI badge - fully configurable
4
4
  */
5
5
 
6
6
  import * as React from "react";
@@ -11,60 +11,139 @@ import {
11
11
  AtomicIcon,
12
12
  useAppDesignTokens,
13
13
  } from "@umituz/react-native-design-system";
14
+ import { LinearGradient } from "expo-linear-gradient";
15
+ import type { ResultImageConfig } from "../../types/result-config.types";
16
+ import { DEFAULT_RESULT_CONFIG } from "../../types/result-config.types";
14
17
 
15
18
  export interface ResultImageCardProps {
16
19
  imageUrl: string;
17
20
  badgeText: string;
21
+ config?: ResultImageConfig;
18
22
  }
19
23
 
20
24
  export const ResultImageCard: React.FC<ResultImageCardProps> = ({
21
25
  imageUrl,
22
26
  badgeText,
27
+ config = DEFAULT_RESULT_CONFIG.image,
23
28
  }) => {
24
29
  const tokens = useAppDesignTokens();
30
+ const cfg = { ...DEFAULT_RESULT_CONFIG.image, ...config };
25
31
 
26
- const styles = useMemo(() => StyleSheet.create({
27
- container: {
28
- paddingHorizontal: 20,
29
- marginBottom: 20,
30
- },
31
- frame: {
32
- borderRadius: 20,
33
- overflow: "hidden",
34
- backgroundColor: tokens.colors.surface,
35
- },
36
- image: {
37
- width: "100%",
38
- aspectRatio: 1,
39
- },
40
- badge: {
41
- position: "absolute",
42
- top: 12,
43
- right: 12,
44
- flexDirection: "row",
45
- alignItems: "center",
46
- gap: 4,
47
- paddingHorizontal: 10,
48
- paddingVertical: 5,
49
- backgroundColor: "rgba(0, 0, 0, 0.6)",
50
- borderRadius: 12,
51
- },
52
- badgeText: {
53
- fontSize: 10,
54
- fontWeight: "700",
55
- color: "#FFFFFF",
56
- letterSpacing: 0.5,
57
- },
58
- }), [tokens]);
32
+ const badgePosition = useMemo(() => {
33
+ switch (cfg.badgePosition) {
34
+ case "top-left":
35
+ return { top: 12, left: 12 };
36
+ case "bottom-left":
37
+ return { bottom: 12, left: 12 };
38
+ case "bottom-right":
39
+ return { bottom: 12, right: 12 };
40
+ case "top-right":
41
+ default:
42
+ return { top: 12, right: 12 };
43
+ }
44
+ }, [cfg.badgePosition]);
45
+
46
+ const badgeBackground = useMemo(() => {
47
+ if (cfg.badgeStyle === "light") {
48
+ return "rgba(255, 255, 255, 0.9)";
49
+ } else if (cfg.badgeStyle === "dark") {
50
+ return "rgba(0, 0, 0, 0.6)";
51
+ }
52
+ return null;
53
+ }, [cfg.badgeStyle]);
54
+
55
+ const styles = useMemo(
56
+ () =>
57
+ StyleSheet.create({
58
+ container: {
59
+ paddingHorizontal: cfg.spacing?.paddingHorizontal ?? 20,
60
+ marginBottom: cfg.spacing?.marginBottom ?? 20,
61
+ },
62
+ frame: {
63
+ borderRadius: cfg.borderRadius ?? 20,
64
+ overflow: "hidden",
65
+ backgroundColor: tokens.colors.surface,
66
+ },
67
+ image: {
68
+ width: "100%",
69
+ aspectRatio: cfg.aspectRatio ?? 1,
70
+ },
71
+ badge: {
72
+ position: "absolute",
73
+ ...badgePosition,
74
+ flexDirection: "row",
75
+ alignItems: "center",
76
+ gap: 4,
77
+ paddingHorizontal: 10,
78
+ paddingVertical: 5,
79
+ backgroundColor: badgeBackground ?? undefined,
80
+ borderRadius: 12,
81
+ overflow: "hidden",
82
+ },
83
+ gradientBadge: {
84
+ flexDirection: "row",
85
+ alignItems: "center",
86
+ gap: 4,
87
+ paddingHorizontal: 10,
88
+ paddingVertical: 5,
89
+ },
90
+ badgeText: {
91
+ fontSize: 10,
92
+ fontWeight: "700",
93
+ color:
94
+ cfg.badgeStyle === "light"
95
+ ? tokens.colors.textPrimary
96
+ : "#FFFFFF",
97
+ letterSpacing: 0.5,
98
+ },
99
+ }),
100
+ [tokens, cfg, badgePosition, badgeBackground],
101
+ );
102
+
103
+ const renderBadge = () => {
104
+ if (!cfg.showBadge) return null;
105
+
106
+ const iconColor =
107
+ cfg.badgeStyle === "light" ? tokens.colors.primary : "#FFFFFF";
108
+
109
+ const badgeContent = (
110
+ <>
111
+ <AtomicIcon
112
+ name={cfg.badgeIcon ?? "sparkles"}
113
+ size="xs"
114
+ customColor={iconColor}
115
+ />
116
+ <AtomicText style={styles.badgeText}>{badgeText}</AtomicText>
117
+ </>
118
+ );
119
+
120
+ if (cfg.badgeStyle === "gradient") {
121
+ return (
122
+ <View style={styles.badge}>
123
+ <LinearGradient
124
+ colors={[tokens.colors.primary, tokens.colors.secondary]}
125
+ start={{ x: 0, y: 0 }}
126
+ end={{ x: 1, y: 0 }}
127
+ style={styles.gradientBadge}
128
+ >
129
+ {badgeContent}
130
+ </LinearGradient>
131
+ </View>
132
+ );
133
+ }
134
+
135
+ return <View style={styles.badge}>{badgeContent}</View>;
136
+ };
59
137
 
60
138
  return (
61
139
  <View style={styles.container}>
62
140
  <View style={styles.frame}>
63
- <Image source={{ uri: imageUrl }} style={styles.image} resizeMode="cover" />
64
- <View style={styles.badge}>
65
- <AtomicIcon name="sparkles" size="xs" color="onPrimary" />
66
- <AtomicText style={styles.badgeText}>{badgeText}</AtomicText>
67
- </View>
141
+ <Image
142
+ source={{ uri: imageUrl }}
143
+ style={styles.image}
144
+ resizeMode="cover"
145
+ />
146
+ {renderBadge()}
68
147
  </View>
69
148
  </View>
70
149
  );