@umituz/react-native-ai-generation-content 1.17.109 → 1.17.110

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.109",
3
+ "version": "1.17.110",
4
4
  "description": "Provider-agnostic AI generation orchestration for React Native",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -0,0 +1,124 @@
1
+ /**
2
+ * CreationImagePreview Component
3
+ * Displays image preview with loading/placeholder states
4
+ */
5
+
6
+ import React, { useMemo, useState } from "react";
7
+ import { View, StyleSheet, Image, ImageErrorEventData, NativeSyntheticEvent } from "react-native";
8
+ import {
9
+ useAppDesignTokens,
10
+ AtomicIcon,
11
+ AtomicSpinner,
12
+ } from "@umituz/react-native-design-system";
13
+ import type { CreationStatus, CreationTypeId } from "../../domain/types";
14
+ import { isInProgress, getTypeIcon } from "../../domain/utils";
15
+
16
+ export interface CreationImagePreviewProps {
17
+ /** Preview image URL */
18
+ readonly uri?: string | null;
19
+ /** Creation status */
20
+ readonly status?: CreationStatus;
21
+ /** Creation type for placeholder icon */
22
+ readonly type?: CreationTypeId;
23
+ /** Aspect ratio (default: 16/9) */
24
+ readonly aspectRatio?: number;
25
+ /** Custom height (overrides aspectRatio) */
26
+ readonly height?: number;
27
+ /** Show loading indicator when in progress */
28
+ readonly showLoadingIndicator?: boolean;
29
+ }
30
+
31
+ export function CreationImagePreview({
32
+ uri,
33
+ status = "completed",
34
+ type = "text-to-image",
35
+ aspectRatio = 16 / 9,
36
+ height,
37
+ showLoadingIndicator = true,
38
+ }: CreationImagePreviewProps) {
39
+ const tokens = useAppDesignTokens();
40
+ const inProgress = isInProgress(status);
41
+ const typeIcon = getTypeIcon(type);
42
+ const [imageError, setImageError] = useState(false);
43
+
44
+ const hasPreview = !!uri && !inProgress && !imageError;
45
+
46
+ const handleImageError = (_error: NativeSyntheticEvent<ImageErrorEventData>) => {
47
+ setImageError(true);
48
+ };
49
+
50
+ const styles = useMemo(
51
+ () =>
52
+ StyleSheet.create({
53
+ container: {
54
+ width: "100%",
55
+ aspectRatio: height ? undefined : aspectRatio,
56
+ height: height,
57
+ backgroundColor: tokens.colors.backgroundSecondary,
58
+ position: "relative",
59
+ overflow: "hidden",
60
+ },
61
+ image: {
62
+ width: "100%",
63
+ height: "100%",
64
+ },
65
+ placeholder: {
66
+ width: "100%",
67
+ height: "100%",
68
+ justifyContent: "center",
69
+ alignItems: "center",
70
+ },
71
+ loadingContainer: {
72
+ width: "100%",
73
+ height: "100%",
74
+ justifyContent: "center",
75
+ alignItems: "center",
76
+ },
77
+ loadingIcon: {
78
+ width: 64,
79
+ height: 64,
80
+ borderRadius: 32,
81
+ backgroundColor: tokens.colors.primary + "20",
82
+ justifyContent: "center",
83
+ alignItems: "center",
84
+ },
85
+ }),
86
+ [tokens, aspectRatio, height]
87
+ );
88
+
89
+ // Show loading state
90
+ if (inProgress && showLoadingIndicator) {
91
+ return (
92
+ <View style={styles.container}>
93
+ <View style={styles.loadingContainer}>
94
+ <View style={styles.loadingIcon}>
95
+ <AtomicSpinner size="lg" color="primary" />
96
+ </View>
97
+ </View>
98
+ </View>
99
+ );
100
+ }
101
+
102
+ // Show image preview
103
+ if (hasPreview) {
104
+ return (
105
+ <View style={styles.container}>
106
+ <Image
107
+ source={{ uri }}
108
+ style={styles.image}
109
+ resizeMode="cover"
110
+ onError={handleImageError}
111
+ />
112
+ </View>
113
+ );
114
+ }
115
+
116
+ // Show placeholder
117
+ return (
118
+ <View style={styles.container}>
119
+ <View style={styles.placeholder}>
120
+ <AtomicIcon name={typeIcon} color="secondary" size="xl" />
121
+ </View>
122
+ </View>
123
+ );
124
+ }
@@ -1,26 +1,31 @@
1
1
  /**
2
2
  * CreationPreview Component
3
- * Displays creation preview image with loading/placeholder states
3
+ * Smart wrapper that delegates to CreationImagePreview or CreationVideoPreview
4
+ * based on creation type
4
5
  */
5
6
 
6
- import React, { useMemo } from "react";
7
- import { View, StyleSheet, Image } from "react-native";
8
- import {
9
- useAppDesignTokens,
10
- AtomicIcon,
11
- AtomicSpinner,
12
- } from "@umituz/react-native-design-system";
7
+ import React from "react";
13
8
  import type { CreationStatus, CreationTypeId } from "../../domain/types";
14
- import { isInProgress } from "../../domain/utils";
15
- import { getTypeIcon } from "../../domain/utils";
9
+ import { CreationImagePreview } from "./CreationImagePreview";
10
+ import { CreationVideoPreview } from "./CreationVideoPreview";
11
+
12
+ /** Video creation types */
13
+ const VIDEO_TYPES: CreationTypeId[] = ["text-to-video", "image-to-video"];
14
+
15
+ /** Check if creation type is a video type */
16
+ function isVideoType(type?: CreationTypeId | string): boolean {
17
+ return VIDEO_TYPES.includes(type as CreationTypeId);
18
+ }
16
19
 
17
20
  interface CreationPreviewProps {
18
- /** Preview image URL */
21
+ /** Preview image/thumbnail URL */
19
22
  readonly uri?: string | null;
23
+ /** Thumbnail URL for videos (optional, if different from uri) */
24
+ readonly thumbnailUrl?: string | null;
20
25
  /** Creation status */
21
26
  readonly status?: CreationStatus;
22
- /** Creation type for placeholder icon */
23
- readonly type?: CreationTypeId;
27
+ /** Creation type for determining preview type */
28
+ readonly type?: CreationTypeId | string;
24
29
  /** Aspect ratio (default: 16/9) */
25
30
  readonly aspectRatio?: number;
26
31
  /** Custom height (overrides aspectRatio) */
@@ -31,88 +36,37 @@ interface CreationPreviewProps {
31
36
 
32
37
  export function CreationPreview({
33
38
  uri,
39
+ thumbnailUrl,
34
40
  status = "completed",
35
41
  type = "text-to-image",
36
42
  aspectRatio = 16 / 9,
37
43
  height,
38
44
  showLoadingIndicator = true,
39
45
  }: CreationPreviewProps) {
40
- const tokens = useAppDesignTokens();
41
- const inProgress = isInProgress(status);
42
- const typeIcon = getTypeIcon(type);
43
- const hasPreview = !!uri && !inProgress;
44
-
45
- const styles = useMemo(
46
- () =>
47
- StyleSheet.create({
48
- container: {
49
- width: "100%",
50
- aspectRatio: height ? undefined : aspectRatio,
51
- height: height,
52
- backgroundColor: tokens.colors.backgroundSecondary,
53
- position: "relative",
54
- overflow: "hidden",
55
- },
56
- image: {
57
- width: "100%",
58
- height: "100%",
59
- },
60
- placeholder: {
61
- width: "100%",
62
- height: "100%",
63
- justifyContent: "center",
64
- alignItems: "center",
65
- },
66
- loadingContainer: {
67
- width: "100%",
68
- height: "100%",
69
- justifyContent: "center",
70
- alignItems: "center",
71
- },
72
- loadingIcon: {
73
- width: 64,
74
- height: 64,
75
- borderRadius: 32,
76
- backgroundColor: tokens.colors.primary + "20",
77
- justifyContent: "center",
78
- alignItems: "center",
79
- },
80
- }),
81
- [tokens, aspectRatio, height]
82
- );
83
-
84
- // Show loading state
85
- if (inProgress && showLoadingIndicator) {
86
- return (
87
- <View style={styles.container}>
88
- <View style={styles.loadingContainer}>
89
- <View style={styles.loadingIcon}>
90
- <AtomicSpinner size="lg" color="primary" />
91
- </View>
92
- </View>
93
- </View>
94
- );
95
- }
96
-
97
- // Show image preview
98
- if (hasPreview) {
46
+ // For video types, use CreationVideoPreview
47
+ if (isVideoType(type)) {
99
48
  return (
100
- <View style={styles.container}>
101
- <Image
102
- source={{ uri }}
103
- style={styles.image}
104
- resizeMode="cover"
105
- />
106
- </View>
49
+ <CreationVideoPreview
50
+ thumbnailUrl={thumbnailUrl || uri}
51
+ videoUrl={uri}
52
+ status={status}
53
+ type={type as CreationTypeId}
54
+ aspectRatio={aspectRatio}
55
+ height={height}
56
+ showLoadingIndicator={showLoadingIndicator}
57
+ />
107
58
  );
108
59
  }
109
60
 
110
- // Show placeholder
61
+ // For image types, use CreationImagePreview
111
62
  return (
112
- <View style={styles.container}>
113
- <View style={styles.placeholder}>
114
- <AtomicIcon name={typeIcon} color="secondary" size="xl" />
115
- </View>
116
- </View>
63
+ <CreationImagePreview
64
+ uri={uri}
65
+ status={status}
66
+ type={type as CreationTypeId}
67
+ aspectRatio={aspectRatio}
68
+ height={height}
69
+ showLoadingIndicator={showLoadingIndicator}
70
+ />
117
71
  );
118
72
  }
@@ -0,0 +1,149 @@
1
+ /**
2
+ * CreationVideoPreview Component
3
+ * Displays video preview with thumbnail and play icon overlay
4
+ */
5
+
6
+ import React, { useMemo } from "react";
7
+ import { View, StyleSheet, Image } from "react-native";
8
+ import {
9
+ useAppDesignTokens,
10
+ AtomicIcon,
11
+ AtomicSpinner,
12
+ } from "@umituz/react-native-design-system";
13
+ import type { CreationStatus, CreationTypeId } from "../../domain/types";
14
+ import { isInProgress } from "../../domain/utils";
15
+
16
+ export interface CreationVideoPreviewProps {
17
+ /** Thumbnail image URL (optional) */
18
+ readonly thumbnailUrl?: string | null;
19
+ /** Video URL (for display purposes only) */
20
+ readonly videoUrl?: string | null;
21
+ /** Creation status */
22
+ readonly status?: CreationStatus;
23
+ /** Creation type for placeholder icon */
24
+ readonly type?: CreationTypeId;
25
+ /** Aspect ratio (default: 16/9) */
26
+ readonly aspectRatio?: number;
27
+ /** Custom height (overrides aspectRatio) */
28
+ readonly height?: number;
29
+ /** Show loading indicator when in progress */
30
+ readonly showLoadingIndicator?: boolean;
31
+ }
32
+
33
+ /** Check if URL is a video URL (mp4, mov, etc.) */
34
+ function isVideoUrl(url?: string | null): boolean {
35
+ if (!url) return false;
36
+ const videoExtensions = [".mp4", ".mov", ".avi", ".webm", ".mkv"];
37
+ const lowerUrl = url.toLowerCase();
38
+ return videoExtensions.some((ext) => lowerUrl.includes(ext));
39
+ }
40
+
41
+ export function CreationVideoPreview({
42
+ thumbnailUrl,
43
+ videoUrl,
44
+ status = "completed",
45
+ aspectRatio = 16 / 9,
46
+ height,
47
+ showLoadingIndicator = true,
48
+ }: CreationVideoPreviewProps) {
49
+ const tokens = useAppDesignTokens();
50
+ const inProgress = isInProgress(status);
51
+
52
+ // Only use thumbnail if it's a real image URL, not a video URL
53
+ const hasThumbnail = !!thumbnailUrl && !inProgress && !isVideoUrl(thumbnailUrl);
54
+
55
+ const styles = useMemo(
56
+ () =>
57
+ StyleSheet.create({
58
+ container: {
59
+ width: "100%",
60
+ aspectRatio: height ? undefined : aspectRatio,
61
+ height: height,
62
+ backgroundColor: tokens.colors.backgroundSecondary,
63
+ position: "relative",
64
+ overflow: "hidden",
65
+ },
66
+ thumbnail: {
67
+ width: "100%",
68
+ height: "100%",
69
+ },
70
+ placeholder: {
71
+ width: "100%",
72
+ height: "100%",
73
+ justifyContent: "center",
74
+ alignItems: "center",
75
+ },
76
+ loadingContainer: {
77
+ width: "100%",
78
+ height: "100%",
79
+ justifyContent: "center",
80
+ alignItems: "center",
81
+ },
82
+ loadingIcon: {
83
+ width: 64,
84
+ height: 64,
85
+ borderRadius: 32,
86
+ backgroundColor: tokens.colors.primary + "20",
87
+ justifyContent: "center",
88
+ alignItems: "center",
89
+ },
90
+ playIconOverlay: {
91
+ ...StyleSheet.absoluteFillObject,
92
+ justifyContent: "center",
93
+ alignItems: "center",
94
+ },
95
+ playIconContainer: {
96
+ width: 56,
97
+ height: 56,
98
+ borderRadius: 28,
99
+ backgroundColor: tokens.colors.primary,
100
+ justifyContent: "center",
101
+ alignItems: "center",
102
+ paddingLeft: 4,
103
+ },
104
+ }),
105
+ [tokens, aspectRatio, height]
106
+ );
107
+
108
+ // Show loading state
109
+ if (inProgress && showLoadingIndicator) {
110
+ return (
111
+ <View style={styles.container}>
112
+ <View style={styles.loadingContainer}>
113
+ <View style={styles.loadingIcon}>
114
+ <AtomicSpinner size="lg" color="primary" />
115
+ </View>
116
+ </View>
117
+ </View>
118
+ );
119
+ }
120
+
121
+ // Show thumbnail with play icon overlay
122
+ if (hasThumbnail) {
123
+ return (
124
+ <View style={styles.container}>
125
+ <Image
126
+ source={{ uri: thumbnailUrl }}
127
+ style={styles.thumbnail}
128
+ resizeMode="cover"
129
+ />
130
+ <View style={styles.playIconOverlay}>
131
+ <View style={styles.playIconContainer}>
132
+ <AtomicIcon name="play" customSize={24} color="onPrimary" />
133
+ </View>
134
+ </View>
135
+ </View>
136
+ );
137
+ }
138
+
139
+ // Show placeholder with play icon (no thumbnail available)
140
+ return (
141
+ <View style={styles.container}>
142
+ <View style={styles.placeholder}>
143
+ <View style={styles.playIconContainer}>
144
+ <AtomicIcon name="play" customSize={24} color="onPrimary" />
145
+ </View>
146
+ </View>
147
+ </View>
148
+ );
149
+ }
@@ -4,6 +4,8 @@
4
4
 
5
5
  // Core Components
6
6
  export { CreationPreview } from "./CreationPreview";
7
+ export { CreationImagePreview, type CreationImagePreviewProps } from "./CreationImagePreview";
8
+ export { CreationVideoPreview, type CreationVideoPreviewProps } from "./CreationVideoPreview";
7
9
  export { CreationBadges } from "./CreationBadges";
8
10
  export { CreationActions, type CreationAction } from "./CreationActions";
9
11
  export {
@@ -37,6 +37,7 @@ export const AIGenerationForm: React.FC<AIGenerationFormProps> = ({
37
37
  hideGenerateButton,
38
38
  progress,
39
39
  progressIcon,
40
+ onCloseProgressModal,
40
41
  generateButtonProps,
41
42
  showAdvanced,
42
43
  onAdvancedToggle,
@@ -163,6 +164,7 @@ export const AIGenerationForm: React.FC<AIGenerationFormProps> = ({
163
164
  icon={progressIcon || "sparkles-outline"}
164
165
  title={translations.progressTitle || translations.generatingButton}
165
166
  message={translations.progressMessage || translations.progressHint}
167
+ onClose={onCloseProgressModal}
166
168
  />
167
169
  </>
168
170
  );
@@ -57,6 +57,8 @@ export interface AIGenerationFormProps extends PropsWithChildren {
57
57
  // Optional: Generation Progress
58
58
  progress?: number;
59
59
  progressIcon?: string;
60
+ /** Callback when user closes the progress modal (for background generation) */
61
+ onCloseProgressModal?: () => void;
60
62
 
61
63
  // Custom Generate Button Props
62
64
  generateButtonProps?: {
@@ -21,6 +21,10 @@ export interface GenerationProgressContentProps {
21
21
  readonly hint?: string;
22
22
  readonly dismissLabel?: string;
23
23
  readonly onDismiss?: () => void;
24
+ /** Close button in top-right corner for background generation */
25
+ readonly onClose?: () => void;
26
+ /** Close button label (default: "Continue in background") */
27
+ readonly closeLabel?: string;
24
28
  readonly backgroundColor?: string;
25
29
  readonly textColor?: string;
26
30
  readonly hintColor?: string;
@@ -39,6 +43,8 @@ export const GenerationProgressContent: React.FC<
39
43
  hint,
40
44
  dismissLabel,
41
45
  onDismiss,
46
+ onClose,
47
+ closeLabel,
42
48
  backgroundColor,
43
49
  textColor,
44
50
  hintColor,
@@ -62,6 +68,17 @@ export const GenerationProgressContent: React.FC<
62
68
  },
63
69
  ]}
64
70
  >
71
+ {/* Close button in top-right corner */}
72
+ {onClose && (
73
+ <TouchableOpacity
74
+ style={styles.closeButton}
75
+ onPress={onClose}
76
+ hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
77
+ >
78
+ <AtomicIcon name="close" size="md" color="secondary" />
79
+ </TouchableOpacity>
80
+ )}
81
+
65
82
  {icon && (
66
83
  <View style={styles.iconContainer}>
67
84
  <AtomicIcon name={icon} size="xl" color="primary" />
@@ -130,6 +147,18 @@ const styles = StyleSheet.create({
130
147
  padding: 32,
131
148
  borderWidth: 1,
132
149
  alignItems: "center",
150
+ position: "relative",
151
+ },
152
+ closeButton: {
153
+ position: "absolute",
154
+ top: 16,
155
+ right: 16,
156
+ width: 32,
157
+ height: 32,
158
+ borderRadius: 16,
159
+ justifyContent: "center",
160
+ alignItems: "center",
161
+ zIndex: 1,
133
162
  },
134
163
  iconContainer: {
135
164
  marginBottom: 20,
@@ -22,6 +22,7 @@ export interface GenerationProgressRenderProps {
22
22
  readonly message?: string;
23
23
  readonly hint?: string;
24
24
  readonly onDismiss?: () => void;
25
+ readonly onClose?: () => void;
25
26
  }
26
27
 
27
28
  export interface GenerationProgressModalProps
@@ -44,6 +45,8 @@ export const GenerationProgressModal: React.FC<
44
45
  hint,
45
46
  dismissLabel,
46
47
  onDismiss,
48
+ onClose,
49
+ closeLabel,
47
50
  modalBackgroundColor,
48
51
  textColor,
49
52
  hintColor,
@@ -67,6 +70,7 @@ export const GenerationProgressModal: React.FC<
67
70
  message,
68
71
  hint,
69
72
  onDismiss,
73
+ onClose,
70
74
  })
71
75
  ) : (
72
76
  <GenerationProgressContent
@@ -77,6 +81,8 @@ export const GenerationProgressModal: React.FC<
77
81
  hint={hint}
78
82
  dismissLabel={dismissLabel}
79
83
  onDismiss={onDismiss}
84
+ onClose={onClose}
85
+ closeLabel={closeLabel}
80
86
  backgroundColor={modalBackgroundColor || tokens.colors.surface}
81
87
  textColor={textColor || tokens.colors.textPrimary}
82
88
  hintColor={hintColor || tokens.colors.textTertiary}