@umituz/react-native-ai-generation-content 1.17.108 → 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 +1 -1
- package/src/domains/creations/presentation/components/CreationImagePreview.tsx +124 -0
- package/src/domains/creations/presentation/components/CreationPreview.tsx +39 -85
- package/src/domains/creations/presentation/components/CreationVideoPreview.tsx +149 -0
- package/src/domains/creations/presentation/components/index.ts +2 -0
- package/src/index.ts +0 -2
- package/src/presentation/components/AIGenerationForm.tsx +12 -8
- package/src/presentation/components/AIGenerationForm.types.ts +6 -1
- package/src/presentation/components/GenerationProgressContent.tsx +29 -0
- package/src/presentation/components/GenerationProgressModal.tsx +6 -0
- package/src/presentation/components/index.ts +0 -2
- package/src/presentation/components/AIGenerationProgressInline.tsx +0 -106
package/package.json
CHANGED
|
@@ -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
|
-
*
|
|
3
|
+
* Smart wrapper that delegates to CreationImagePreview or CreationVideoPreview
|
|
4
|
+
* based on creation type
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
|
-
import 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 {
|
|
15
|
-
import {
|
|
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
|
|
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
|
-
|
|
41
|
-
|
|
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
|
-
<
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
//
|
|
61
|
+
// For image types, use CreationImagePreview
|
|
111
62
|
return (
|
|
112
|
-
<
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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 {
|
package/src/index.ts
CHANGED
|
@@ -306,7 +306,6 @@ export {
|
|
|
306
306
|
ImagePickerBox,
|
|
307
307
|
DualImagePicker,
|
|
308
308
|
// New Generic Sections
|
|
309
|
-
AIGenerationProgressInline,
|
|
310
309
|
PromptInput,
|
|
311
310
|
AIGenerationHero,
|
|
312
311
|
ExamplePrompts,
|
|
@@ -361,7 +360,6 @@ export type {
|
|
|
361
360
|
// Image Picker
|
|
362
361
|
ImagePickerBoxProps,
|
|
363
362
|
DualImagePickerProps,
|
|
364
|
-
AIGenerationProgressInlineProps,
|
|
365
363
|
PromptInputProps,
|
|
366
364
|
AIGenerationHeroProps,
|
|
367
365
|
ExamplePromptsProps,
|
|
@@ -13,8 +13,8 @@ import { AspectRatioSelector } from "./selectors/AspectRatioSelector";
|
|
|
13
13
|
import { PromptInput } from "./PromptInput";
|
|
14
14
|
import { GenerateButton } from "./buttons/GenerateButton";
|
|
15
15
|
import { ExamplePrompts } from "./prompts/ExamplePrompts";
|
|
16
|
-
import { AIGenerationProgressInline } from "./AIGenerationProgressInline";
|
|
17
16
|
import { StylePresetsGrid } from "./StylePresetsGrid";
|
|
17
|
+
import { GenerationProgressModal } from "./GenerationProgressModal";
|
|
18
18
|
import type { AIGenerationFormProps } from "./AIGenerationForm.types";
|
|
19
19
|
|
|
20
20
|
export const AIGenerationForm: React.FC<AIGenerationFormProps> = ({
|
|
@@ -36,6 +36,8 @@ export const AIGenerationForm: React.FC<AIGenerationFormProps> = ({
|
|
|
36
36
|
isGenerating,
|
|
37
37
|
hideGenerateButton,
|
|
38
38
|
progress,
|
|
39
|
+
progressIcon,
|
|
40
|
+
onCloseProgressModal,
|
|
39
41
|
generateButtonProps,
|
|
40
42
|
showAdvanced,
|
|
41
43
|
onAdvancedToggle,
|
|
@@ -155,13 +157,15 @@ export const AIGenerationForm: React.FC<AIGenerationFormProps> = ({
|
|
|
155
157
|
</>
|
|
156
158
|
)}
|
|
157
159
|
|
|
158
|
-
{
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
160
|
+
{/* MANDATORY: Progress Modal shows automatically when isGenerating */}
|
|
161
|
+
<GenerationProgressModal
|
|
162
|
+
visible={isGenerating}
|
|
163
|
+
progress={progress ?? 0}
|
|
164
|
+
icon={progressIcon || "sparkles-outline"}
|
|
165
|
+
title={translations.progressTitle || translations.generatingButton}
|
|
166
|
+
message={translations.progressMessage || translations.progressHint}
|
|
167
|
+
onClose={onCloseProgressModal}
|
|
168
|
+
/>
|
|
165
169
|
</>
|
|
166
170
|
);
|
|
167
171
|
};
|
|
@@ -14,7 +14,9 @@ export interface AIGenerationFormTranslations {
|
|
|
14
14
|
examplePromptsTitle?: string;
|
|
15
15
|
generateButton: string;
|
|
16
16
|
generatingButton: string;
|
|
17
|
+
// Progress Modal (mandatory when isGenerating)
|
|
17
18
|
progressTitle?: string;
|
|
19
|
+
progressMessage?: string;
|
|
18
20
|
progressHint?: string;
|
|
19
21
|
presetsTitle?: string;
|
|
20
22
|
showAdvancedLabel?: string;
|
|
@@ -54,7 +56,10 @@ export interface AIGenerationFormProps extends PropsWithChildren {
|
|
|
54
56
|
|
|
55
57
|
// Optional: Generation Progress
|
|
56
58
|
progress?: number;
|
|
57
|
-
|
|
59
|
+
progressIcon?: string;
|
|
60
|
+
/** Callback when user closes the progress modal (for background generation) */
|
|
61
|
+
onCloseProgressModal?: () => void;
|
|
62
|
+
|
|
58
63
|
// Custom Generate Button Props
|
|
59
64
|
generateButtonProps?: {
|
|
60
65
|
costLabel?: string;
|
|
@@ -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}
|
|
@@ -4,7 +4,6 @@ export { GenerationProgressBar } from "./GenerationProgressBar";
|
|
|
4
4
|
export { PendingJobCard } from "./PendingJobCard";
|
|
5
5
|
export { PendingJobProgressBar } from "./PendingJobProgressBar";
|
|
6
6
|
export { PendingJobCardActions } from "./PendingJobCardActions";
|
|
7
|
-
export { AIGenerationProgressInline } from "./AIGenerationProgressInline";
|
|
8
7
|
export { PromptInput } from "./PromptInput";
|
|
9
8
|
export { AIGenerationHero } from "./AIGenerationHero";
|
|
10
9
|
export * from "./StylePresetsGrid";
|
|
@@ -26,7 +25,6 @@ export type {
|
|
|
26
25
|
|
|
27
26
|
export type { PendingJobProgressBarProps } from "./PendingJobProgressBar";
|
|
28
27
|
export type { PendingJobCardActionsProps } from "./PendingJobCardActions";
|
|
29
|
-
export type { AIGenerationProgressInlineProps } from "./AIGenerationProgressInline";
|
|
30
28
|
export type { PromptInputProps } from "./PromptInput";
|
|
31
29
|
export type { AIGenerationHeroProps } from "./AIGenerationHero";
|
|
32
30
|
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AIGenerationProgressInline Component
|
|
3
|
-
* Generic inline generation progress display
|
|
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
|
-
useAppDesignTokens,
|
|
12
|
-
AtomicProgress,
|
|
13
|
-
AtomicSpinner,
|
|
14
|
-
} from "@umituz/react-native-design-system";
|
|
15
|
-
|
|
16
|
-
export interface AIGenerationProgressInlineProps {
|
|
17
|
-
readonly progress: number;
|
|
18
|
-
readonly title?: string;
|
|
19
|
-
readonly message?: string;
|
|
20
|
-
readonly hint?: string;
|
|
21
|
-
readonly backgroundColor?: string;
|
|
22
|
-
readonly accentColor?: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export const AIGenerationProgressInline: React.FC<
|
|
26
|
-
AIGenerationProgressInlineProps
|
|
27
|
-
> = ({
|
|
28
|
-
progress,
|
|
29
|
-
title,
|
|
30
|
-
message,
|
|
31
|
-
hint,
|
|
32
|
-
backgroundColor,
|
|
33
|
-
accentColor,
|
|
34
|
-
}) => {
|
|
35
|
-
const tokens = useAppDesignTokens();
|
|
36
|
-
const primaryColor = accentColor || tokens.colors.primary;
|
|
37
|
-
const bgColor = backgroundColor || tokens.colors.surface;
|
|
38
|
-
|
|
39
|
-
const clampedProgress = Math.max(0, Math.min(100, progress));
|
|
40
|
-
|
|
41
|
-
return (
|
|
42
|
-
<View style={[styles.container, { backgroundColor: bgColor }]}>
|
|
43
|
-
<AtomicSpinner size="lg" color={primaryColor} />
|
|
44
|
-
|
|
45
|
-
{title && (
|
|
46
|
-
<AtomicText
|
|
47
|
-
type="bodyMedium"
|
|
48
|
-
style={[styles.title, { color: tokens.colors.textPrimary }]}
|
|
49
|
-
>
|
|
50
|
-
{title}
|
|
51
|
-
</AtomicText>
|
|
52
|
-
)}
|
|
53
|
-
|
|
54
|
-
{message && (
|
|
55
|
-
<AtomicText
|
|
56
|
-
type="bodySmall"
|
|
57
|
-
style={[styles.message, { color: tokens.colors.textSecondary }]}
|
|
58
|
-
>
|
|
59
|
-
{message}
|
|
60
|
-
</AtomicText>
|
|
61
|
-
)}
|
|
62
|
-
|
|
63
|
-
<View style={styles.progressBarWrapper}>
|
|
64
|
-
<AtomicProgress
|
|
65
|
-
value={clampedProgress}
|
|
66
|
-
height={8}
|
|
67
|
-
color={primaryColor}
|
|
68
|
-
backgroundColor={tokens.colors.surfaceVariant}
|
|
69
|
-
shape="rounded"
|
|
70
|
-
showPercentage={false}
|
|
71
|
-
/>
|
|
72
|
-
</View>
|
|
73
|
-
|
|
74
|
-
<AtomicText
|
|
75
|
-
type="labelSmall"
|
|
76
|
-
style={[styles.progressLabel, { color: tokens.colors.textSecondary }]}
|
|
77
|
-
>
|
|
78
|
-
{clampedProgress}%{hint ? ` • ${hint}` : ""}
|
|
79
|
-
</AtomicText>
|
|
80
|
-
</View>
|
|
81
|
-
);
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
const styles = StyleSheet.create({
|
|
85
|
-
container: {
|
|
86
|
-
margin: 16,
|
|
87
|
-
padding: 24,
|
|
88
|
-
borderRadius: 16,
|
|
89
|
-
alignItems: "center",
|
|
90
|
-
},
|
|
91
|
-
title: {
|
|
92
|
-
marginTop: 16,
|
|
93
|
-
fontWeight: "600",
|
|
94
|
-
},
|
|
95
|
-
message: {
|
|
96
|
-
marginTop: 8,
|
|
97
|
-
textAlign: "center",
|
|
98
|
-
},
|
|
99
|
-
progressBarWrapper: {
|
|
100
|
-
width: "100%",
|
|
101
|
-
marginTop: 16,
|
|
102
|
-
},
|
|
103
|
-
progressLabel: {
|
|
104
|
-
marginTop: 8,
|
|
105
|
-
},
|
|
106
|
-
});
|