@umituz/react-native-ai-generation-content 1.17.16 → 1.17.17
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/screens/CreationsGalleryScreen.tsx +98 -69
- package/src/features/text-to-image/index.ts +92 -4
- package/src/features/text-to-image/presentation/components/AspectRatioSelector.tsx +98 -0
- package/src/features/text-to-image/presentation/components/ExamplePrompts.tsx +88 -0
- package/src/features/text-to-image/presentation/components/ImageSizeSelector.tsx +98 -0
- package/src/features/text-to-image/presentation/components/NumImagesSelector.tsx +93 -0
- package/src/features/text-to-image/presentation/components/OutputFormatSelector.tsx +98 -0
- package/src/features/text-to-image/presentation/components/PromptInput.tsx +90 -0
- package/src/features/text-to-image/presentation/components/SettingsSheet.tsx +139 -0
- package/src/features/text-to-image/presentation/components/StyleSelector.tsx +110 -0
- package/src/features/text-to-image/presentation/components/TextToImageGenerateButton.tsx +84 -0
- package/src/features/text-to-image/presentation/components/index.ts +41 -0
- package/src/features/text-to-image/presentation/hooks/index.ts +25 -0
- package/src/features/text-to-image/presentation/hooks/useFormState.ts +103 -0
- package/src/features/text-to-image/presentation/hooks/useGeneration.ts +99 -0
- package/src/features/text-to-image/presentation/hooks/useTextToImageForm.ts +58 -0
- package/src/features/text-to-image/presentation/index.ts +6 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Number of Images Selector Component
|
|
3
|
+
* Grid of buttons to select number of images to generate
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { View, TouchableOpacity, StyleSheet } from "react-native";
|
|
8
|
+
import {
|
|
9
|
+
AtomicText,
|
|
10
|
+
useAppDesignTokens,
|
|
11
|
+
} from "@umituz/react-native-design-system";
|
|
12
|
+
import type { NumImages } from "../../domain/types/form.types";
|
|
13
|
+
|
|
14
|
+
export interface NumImagesSelectorProps {
|
|
15
|
+
value: NumImages;
|
|
16
|
+
onChange: (num: NumImages) => void;
|
|
17
|
+
options: NumImages[];
|
|
18
|
+
label: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const NumImagesSelector: React.FC<NumImagesSelectorProps> = ({
|
|
22
|
+
value,
|
|
23
|
+
onChange,
|
|
24
|
+
options,
|
|
25
|
+
label,
|
|
26
|
+
}) => {
|
|
27
|
+
const tokens = useAppDesignTokens();
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<View style={styles.container}>
|
|
31
|
+
<AtomicText
|
|
32
|
+
type="bodyMedium"
|
|
33
|
+
style={[styles.label, { color: tokens.colors.textPrimary }]}
|
|
34
|
+
>
|
|
35
|
+
{label}
|
|
36
|
+
</AtomicText>
|
|
37
|
+
<View style={styles.grid}>
|
|
38
|
+
{options.map((num) => {
|
|
39
|
+
const isSelected = value === num;
|
|
40
|
+
return (
|
|
41
|
+
<TouchableOpacity
|
|
42
|
+
key={num}
|
|
43
|
+
style={[
|
|
44
|
+
styles.button,
|
|
45
|
+
{
|
|
46
|
+
backgroundColor: isSelected
|
|
47
|
+
? tokens.colors.primary
|
|
48
|
+
: tokens.colors.surface,
|
|
49
|
+
borderColor: isSelected
|
|
50
|
+
? tokens.colors.primary
|
|
51
|
+
: tokens.colors.borderLight,
|
|
52
|
+
},
|
|
53
|
+
]}
|
|
54
|
+
onPress={() => onChange(num)}
|
|
55
|
+
activeOpacity={0.7}
|
|
56
|
+
>
|
|
57
|
+
<AtomicText
|
|
58
|
+
type="bodyLarge"
|
|
59
|
+
style={{
|
|
60
|
+
color: isSelected ? "#FFFFFF" : tokens.colors.textPrimary,
|
|
61
|
+
fontWeight: isSelected ? "700" : "400",
|
|
62
|
+
}}
|
|
63
|
+
>
|
|
64
|
+
{num}
|
|
65
|
+
</AtomicText>
|
|
66
|
+
</TouchableOpacity>
|
|
67
|
+
);
|
|
68
|
+
})}
|
|
69
|
+
</View>
|
|
70
|
+
</View>
|
|
71
|
+
);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const styles = StyleSheet.create({
|
|
75
|
+
container: {
|
|
76
|
+
marginBottom: 24,
|
|
77
|
+
},
|
|
78
|
+
label: {
|
|
79
|
+
fontWeight: "600",
|
|
80
|
+
marginBottom: 12,
|
|
81
|
+
},
|
|
82
|
+
grid: {
|
|
83
|
+
flexDirection: "row",
|
|
84
|
+
gap: 12,
|
|
85
|
+
},
|
|
86
|
+
button: {
|
|
87
|
+
flex: 1,
|
|
88
|
+
padding: 16,
|
|
89
|
+
borderRadius: 12,
|
|
90
|
+
borderWidth: 2,
|
|
91
|
+
alignItems: "center",
|
|
92
|
+
},
|
|
93
|
+
});
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output Format Selector Component
|
|
3
|
+
* Selection for image output format
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { View, TouchableOpacity, StyleSheet } from "react-native";
|
|
8
|
+
import {
|
|
9
|
+
AtomicText,
|
|
10
|
+
useAppDesignTokens,
|
|
11
|
+
} from "@umituz/react-native-design-system";
|
|
12
|
+
import type { OutputFormat } from "../../domain/types/form.types";
|
|
13
|
+
|
|
14
|
+
export interface OutputFormatOption {
|
|
15
|
+
value: OutputFormat;
|
|
16
|
+
label: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface OutputFormatSelectorProps {
|
|
20
|
+
value: OutputFormat;
|
|
21
|
+
onChange: (format: OutputFormat) => void;
|
|
22
|
+
options: OutputFormatOption[];
|
|
23
|
+
label: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const OutputFormatSelector: React.FC<OutputFormatSelectorProps> = ({
|
|
27
|
+
value,
|
|
28
|
+
onChange,
|
|
29
|
+
options,
|
|
30
|
+
label,
|
|
31
|
+
}) => {
|
|
32
|
+
const tokens = useAppDesignTokens();
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<View style={styles.container}>
|
|
36
|
+
<AtomicText
|
|
37
|
+
type="bodyMedium"
|
|
38
|
+
style={[styles.label, { color: tokens.colors.textPrimary }]}
|
|
39
|
+
>
|
|
40
|
+
{label}
|
|
41
|
+
</AtomicText>
|
|
42
|
+
<View style={styles.optionsRow}>
|
|
43
|
+
{options.map((option) => {
|
|
44
|
+
const isSelected = value === option.value;
|
|
45
|
+
return (
|
|
46
|
+
<TouchableOpacity
|
|
47
|
+
key={option.value}
|
|
48
|
+
style={[
|
|
49
|
+
styles.option,
|
|
50
|
+
{
|
|
51
|
+
backgroundColor: isSelected
|
|
52
|
+
? tokens.colors.primary
|
|
53
|
+
: tokens.colors.surface,
|
|
54
|
+
borderColor: isSelected
|
|
55
|
+
? tokens.colors.primary
|
|
56
|
+
: tokens.colors.borderLight,
|
|
57
|
+
},
|
|
58
|
+
]}
|
|
59
|
+
onPress={() => onChange(option.value)}
|
|
60
|
+
activeOpacity={0.7}
|
|
61
|
+
>
|
|
62
|
+
<AtomicText
|
|
63
|
+
type="bodyMedium"
|
|
64
|
+
style={{
|
|
65
|
+
color: isSelected ? "#FFFFFF" : tokens.colors.textPrimary,
|
|
66
|
+
fontWeight: isSelected ? "600" : "400",
|
|
67
|
+
}}
|
|
68
|
+
>
|
|
69
|
+
{option.label}
|
|
70
|
+
</AtomicText>
|
|
71
|
+
</TouchableOpacity>
|
|
72
|
+
);
|
|
73
|
+
})}
|
|
74
|
+
</View>
|
|
75
|
+
</View>
|
|
76
|
+
);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const styles = StyleSheet.create({
|
|
80
|
+
container: {
|
|
81
|
+
marginBottom: 20,
|
|
82
|
+
},
|
|
83
|
+
label: {
|
|
84
|
+
fontWeight: "600",
|
|
85
|
+
marginBottom: 12,
|
|
86
|
+
},
|
|
87
|
+
optionsRow: {
|
|
88
|
+
flexDirection: "row",
|
|
89
|
+
gap: 12,
|
|
90
|
+
},
|
|
91
|
+
option: {
|
|
92
|
+
flex: 1,
|
|
93
|
+
padding: 14,
|
|
94
|
+
borderRadius: 8,
|
|
95
|
+
borderWidth: 1,
|
|
96
|
+
alignItems: "center",
|
|
97
|
+
},
|
|
98
|
+
});
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompt Input Component
|
|
3
|
+
* Text input for entering generation prompts
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { View, TextInput, StyleSheet } from "react-native";
|
|
8
|
+
import {
|
|
9
|
+
AtomicText,
|
|
10
|
+
useAppDesignTokens,
|
|
11
|
+
} from "@umituz/react-native-design-system";
|
|
12
|
+
|
|
13
|
+
export interface PromptInputProps {
|
|
14
|
+
value: string;
|
|
15
|
+
onChangeText: (text: string) => void;
|
|
16
|
+
label: string;
|
|
17
|
+
placeholder: string;
|
|
18
|
+
characterCountLabel?: string;
|
|
19
|
+
minHeight?: number;
|
|
20
|
+
maxLength?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const PromptInput: React.FC<PromptInputProps> = ({
|
|
24
|
+
value,
|
|
25
|
+
onChangeText,
|
|
26
|
+
label,
|
|
27
|
+
placeholder,
|
|
28
|
+
characterCountLabel,
|
|
29
|
+
minHeight = 100,
|
|
30
|
+
maxLength,
|
|
31
|
+
}) => {
|
|
32
|
+
const tokens = useAppDesignTokens();
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<View style={styles.container}>
|
|
36
|
+
<AtomicText
|
|
37
|
+
type="bodyMedium"
|
|
38
|
+
style={[styles.label, { color: tokens.colors.textPrimary }]}
|
|
39
|
+
>
|
|
40
|
+
{label}
|
|
41
|
+
</AtomicText>
|
|
42
|
+
<TextInput
|
|
43
|
+
style={[
|
|
44
|
+
styles.input,
|
|
45
|
+
{
|
|
46
|
+
backgroundColor: tokens.colors.surface,
|
|
47
|
+
color: tokens.colors.textPrimary,
|
|
48
|
+
borderColor: tokens.colors.borderLight,
|
|
49
|
+
minHeight,
|
|
50
|
+
},
|
|
51
|
+
]}
|
|
52
|
+
placeholder={placeholder}
|
|
53
|
+
placeholderTextColor={tokens.colors.textSecondary}
|
|
54
|
+
value={value}
|
|
55
|
+
onChangeText={onChangeText}
|
|
56
|
+
multiline
|
|
57
|
+
numberOfLines={4}
|
|
58
|
+
textAlignVertical="top"
|
|
59
|
+
maxLength={maxLength}
|
|
60
|
+
/>
|
|
61
|
+
{characterCountLabel && (
|
|
62
|
+
<AtomicText
|
|
63
|
+
type="labelSmall"
|
|
64
|
+
style={[styles.charCount, { color: tokens.colors.textSecondary }]}
|
|
65
|
+
>
|
|
66
|
+
{characterCountLabel}
|
|
67
|
+
</AtomicText>
|
|
68
|
+
)}
|
|
69
|
+
</View>
|
|
70
|
+
);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const styles = StyleSheet.create({
|
|
74
|
+
container: {
|
|
75
|
+
marginBottom: 24,
|
|
76
|
+
},
|
|
77
|
+
label: {
|
|
78
|
+
fontWeight: "600",
|
|
79
|
+
marginBottom: 12,
|
|
80
|
+
},
|
|
81
|
+
input: {
|
|
82
|
+
borderWidth: 1,
|
|
83
|
+
borderRadius: 12,
|
|
84
|
+
padding: 16,
|
|
85
|
+
fontSize: 16,
|
|
86
|
+
},
|
|
87
|
+
charCount: {
|
|
88
|
+
marginTop: 8,
|
|
89
|
+
},
|
|
90
|
+
});
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings Sheet Component
|
|
3
|
+
* Modal sheet for advanced image generation settings
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from "react";
|
|
7
|
+
import {
|
|
8
|
+
View,
|
|
9
|
+
Modal,
|
|
10
|
+
Pressable,
|
|
11
|
+
StyleSheet,
|
|
12
|
+
ScrollView,
|
|
13
|
+
type GestureResponderEvent,
|
|
14
|
+
} from "react-native";
|
|
15
|
+
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
16
|
+
import {
|
|
17
|
+
AtomicText,
|
|
18
|
+
AtomicButton,
|
|
19
|
+
useAppDesignTokens,
|
|
20
|
+
} from "@umituz/react-native-design-system";
|
|
21
|
+
|
|
22
|
+
export interface SettingsSheetProps {
|
|
23
|
+
visible: boolean;
|
|
24
|
+
onClose: () => void;
|
|
25
|
+
title: string;
|
|
26
|
+
doneLabel: string;
|
|
27
|
+
children: React.ReactNode;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const SettingsSheet: React.FC<SettingsSheetProps> = ({
|
|
31
|
+
visible,
|
|
32
|
+
onClose,
|
|
33
|
+
title,
|
|
34
|
+
doneLabel,
|
|
35
|
+
children,
|
|
36
|
+
}) => {
|
|
37
|
+
const tokens = useAppDesignTokens();
|
|
38
|
+
const insets = useSafeAreaInsets();
|
|
39
|
+
|
|
40
|
+
const handleBackdropPress = () => {
|
|
41
|
+
onClose();
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const handleSheetPress = (e: GestureResponderEvent) => {
|
|
45
|
+
e.stopPropagation();
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<Modal
|
|
50
|
+
visible={visible}
|
|
51
|
+
transparent
|
|
52
|
+
animationType="slide"
|
|
53
|
+
onRequestClose={onClose}
|
|
54
|
+
>
|
|
55
|
+
<Pressable style={styles.backdrop} onPress={handleBackdropPress}>
|
|
56
|
+
<Pressable
|
|
57
|
+
style={[
|
|
58
|
+
styles.sheet,
|
|
59
|
+
{
|
|
60
|
+
backgroundColor: tokens.colors.surface,
|
|
61
|
+
paddingBottom: insets.bottom + 16,
|
|
62
|
+
},
|
|
63
|
+
]}
|
|
64
|
+
onPress={handleSheetPress}
|
|
65
|
+
>
|
|
66
|
+
<View
|
|
67
|
+
style={[styles.handle, { backgroundColor: tokens.colors.border }]}
|
|
68
|
+
/>
|
|
69
|
+
|
|
70
|
+
<View style={styles.header}>
|
|
71
|
+
<AtomicText
|
|
72
|
+
type="titleMedium"
|
|
73
|
+
style={[styles.title, { color: tokens.colors.textPrimary }]}
|
|
74
|
+
>
|
|
75
|
+
{title}
|
|
76
|
+
</AtomicText>
|
|
77
|
+
<AtomicButton
|
|
78
|
+
variant="secondary"
|
|
79
|
+
size="sm"
|
|
80
|
+
onPress={onClose}
|
|
81
|
+
style={styles.doneButton}
|
|
82
|
+
>
|
|
83
|
+
<AtomicText
|
|
84
|
+
type="bodyMedium"
|
|
85
|
+
style={[styles.doneText, { color: tokens.colors.primary }]}
|
|
86
|
+
>
|
|
87
|
+
{doneLabel}
|
|
88
|
+
</AtomicText>
|
|
89
|
+
</AtomicButton>
|
|
90
|
+
</View>
|
|
91
|
+
|
|
92
|
+
<ScrollView contentContainerStyle={styles.content}>
|
|
93
|
+
{children}
|
|
94
|
+
</ScrollView>
|
|
95
|
+
</Pressable>
|
|
96
|
+
</Pressable>
|
|
97
|
+
</Modal>
|
|
98
|
+
);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const styles = StyleSheet.create({
|
|
102
|
+
backdrop: {
|
|
103
|
+
flex: 1,
|
|
104
|
+
backgroundColor: "rgba(0, 0, 0, 0.5)",
|
|
105
|
+
justifyContent: "flex-end",
|
|
106
|
+
},
|
|
107
|
+
sheet: {
|
|
108
|
+
borderTopLeftRadius: 16,
|
|
109
|
+
borderTopRightRadius: 16,
|
|
110
|
+
maxHeight: "80%",
|
|
111
|
+
},
|
|
112
|
+
handle: {
|
|
113
|
+
width: 40,
|
|
114
|
+
height: 4,
|
|
115
|
+
borderRadius: 2,
|
|
116
|
+
alignSelf: "center",
|
|
117
|
+
marginTop: 8,
|
|
118
|
+
marginBottom: 8,
|
|
119
|
+
},
|
|
120
|
+
header: {
|
|
121
|
+
flexDirection: "row",
|
|
122
|
+
justifyContent: "space-between",
|
|
123
|
+
alignItems: "center",
|
|
124
|
+
paddingHorizontal: 20,
|
|
125
|
+
paddingBottom: 16,
|
|
126
|
+
},
|
|
127
|
+
title: {
|
|
128
|
+
fontWeight: "600",
|
|
129
|
+
},
|
|
130
|
+
doneButton: {
|
|
131
|
+
paddingHorizontal: 0,
|
|
132
|
+
},
|
|
133
|
+
doneText: {
|
|
134
|
+
fontWeight: "600",
|
|
135
|
+
},
|
|
136
|
+
content: {
|
|
137
|
+
padding: 20,
|
|
138
|
+
},
|
|
139
|
+
});
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Style Selector Component
|
|
3
|
+
* Horizontal scrollable list of style options
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { View, ScrollView, TouchableOpacity, StyleSheet } from "react-native";
|
|
8
|
+
import {
|
|
9
|
+
AtomicText,
|
|
10
|
+
useAppDesignTokens,
|
|
11
|
+
} from "@umituz/react-native-design-system";
|
|
12
|
+
import type { StyleOption } from "../../domain/types/form.types";
|
|
13
|
+
|
|
14
|
+
export interface StyleSelectorProps {
|
|
15
|
+
options: StyleOption[];
|
|
16
|
+
value: string;
|
|
17
|
+
onChange: (styleId: string) => void;
|
|
18
|
+
label: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const StyleSelector: React.FC<StyleSelectorProps> = ({
|
|
22
|
+
options,
|
|
23
|
+
value,
|
|
24
|
+
onChange,
|
|
25
|
+
label,
|
|
26
|
+
}) => {
|
|
27
|
+
const tokens = useAppDesignTokens();
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<View style={styles.container}>
|
|
31
|
+
<AtomicText
|
|
32
|
+
type="bodyMedium"
|
|
33
|
+
style={[styles.label, { color: tokens.colors.textPrimary }]}
|
|
34
|
+
>
|
|
35
|
+
{label}
|
|
36
|
+
</AtomicText>
|
|
37
|
+
<ScrollView
|
|
38
|
+
horizontal
|
|
39
|
+
showsHorizontalScrollIndicator={false}
|
|
40
|
+
contentContainerStyle={styles.scrollContent}
|
|
41
|
+
>
|
|
42
|
+
{options.map((style) => {
|
|
43
|
+
const isSelected = value === style.id;
|
|
44
|
+
return (
|
|
45
|
+
<TouchableOpacity
|
|
46
|
+
key={style.id}
|
|
47
|
+
style={[
|
|
48
|
+
styles.card,
|
|
49
|
+
{
|
|
50
|
+
backgroundColor: isSelected
|
|
51
|
+
? tokens.colors.primary
|
|
52
|
+
: tokens.colors.surface,
|
|
53
|
+
borderColor: isSelected
|
|
54
|
+
? tokens.colors.primary
|
|
55
|
+
: tokens.colors.borderLight,
|
|
56
|
+
},
|
|
57
|
+
]}
|
|
58
|
+
onPress={() => onChange(style.id)}
|
|
59
|
+
activeOpacity={0.7}
|
|
60
|
+
>
|
|
61
|
+
<AtomicText
|
|
62
|
+
type="bodyMedium"
|
|
63
|
+
style={{
|
|
64
|
+
color: isSelected ? "#FFFFFF" : tokens.colors.textPrimary,
|
|
65
|
+
fontWeight: isSelected ? "600" : "400",
|
|
66
|
+
}}
|
|
67
|
+
>
|
|
68
|
+
{style.name}
|
|
69
|
+
</AtomicText>
|
|
70
|
+
{style.description && (
|
|
71
|
+
<AtomicText
|
|
72
|
+
type="labelSmall"
|
|
73
|
+
style={{
|
|
74
|
+
color: isSelected
|
|
75
|
+
? "rgba(255,255,255,0.8)"
|
|
76
|
+
: tokens.colors.textSecondary,
|
|
77
|
+
marginTop: 4,
|
|
78
|
+
}}
|
|
79
|
+
numberOfLines={1}
|
|
80
|
+
>
|
|
81
|
+
{style.description}
|
|
82
|
+
</AtomicText>
|
|
83
|
+
)}
|
|
84
|
+
</TouchableOpacity>
|
|
85
|
+
);
|
|
86
|
+
})}
|
|
87
|
+
</ScrollView>
|
|
88
|
+
</View>
|
|
89
|
+
);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const styles = StyleSheet.create({
|
|
93
|
+
container: {
|
|
94
|
+
marginBottom: 24,
|
|
95
|
+
},
|
|
96
|
+
label: {
|
|
97
|
+
fontWeight: "600",
|
|
98
|
+
marginBottom: 12,
|
|
99
|
+
},
|
|
100
|
+
scrollContent: {
|
|
101
|
+
paddingRight: 16,
|
|
102
|
+
},
|
|
103
|
+
card: {
|
|
104
|
+
padding: 12,
|
|
105
|
+
borderRadius: 12,
|
|
106
|
+
borderWidth: 2,
|
|
107
|
+
marginRight: 12,
|
|
108
|
+
minWidth: 100,
|
|
109
|
+
},
|
|
110
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text-to-Image Generate Button Component
|
|
3
|
+
* Button to trigger image generation with cost display
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { View, StyleSheet } from "react-native";
|
|
8
|
+
import {
|
|
9
|
+
AtomicText,
|
|
10
|
+
AtomicButton,
|
|
11
|
+
AtomicIcon,
|
|
12
|
+
} from "@umituz/react-native-design-system";
|
|
13
|
+
|
|
14
|
+
export interface TextToImageGenerateButtonProps {
|
|
15
|
+
onPress: () => void;
|
|
16
|
+
onSettingsPress?: () => void;
|
|
17
|
+
disabled: boolean;
|
|
18
|
+
label: string;
|
|
19
|
+
costLabel?: string;
|
|
20
|
+
showSettings?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const TextToImageGenerateButton: React.FC<TextToImageGenerateButtonProps> = ({
|
|
24
|
+
onPress,
|
|
25
|
+
onSettingsPress,
|
|
26
|
+
disabled,
|
|
27
|
+
label,
|
|
28
|
+
costLabel,
|
|
29
|
+
showSettings = true,
|
|
30
|
+
}) => {
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<View style={styles.container}>
|
|
34
|
+
<View style={styles.buttonRow}>
|
|
35
|
+
<AtomicButton
|
|
36
|
+
onPress={onPress}
|
|
37
|
+
disabled={disabled}
|
|
38
|
+
variant="primary"
|
|
39
|
+
size="md"
|
|
40
|
+
style={styles.generateButton}
|
|
41
|
+
>
|
|
42
|
+
<AtomicText type="bodyLarge" style={styles.buttonText}>
|
|
43
|
+
{label}
|
|
44
|
+
{costLabel && ` (${costLabel})`}
|
|
45
|
+
</AtomicText>
|
|
46
|
+
</AtomicButton>
|
|
47
|
+
|
|
48
|
+
{showSettings && onSettingsPress && (
|
|
49
|
+
<AtomicButton
|
|
50
|
+
onPress={onSettingsPress}
|
|
51
|
+
variant="secondary"
|
|
52
|
+
size="md"
|
|
53
|
+
style={styles.settingsButton}
|
|
54
|
+
>
|
|
55
|
+
<AtomicIcon name="settings-outline" size="md" color="primary" />
|
|
56
|
+
</AtomicButton>
|
|
57
|
+
)}
|
|
58
|
+
</View>
|
|
59
|
+
</View>
|
|
60
|
+
);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const styles = StyleSheet.create({
|
|
64
|
+
container: {
|
|
65
|
+
marginBottom: 24,
|
|
66
|
+
},
|
|
67
|
+
buttonRow: {
|
|
68
|
+
flexDirection: "row",
|
|
69
|
+
gap: 12,
|
|
70
|
+
},
|
|
71
|
+
generateButton: {
|
|
72
|
+
flex: 1,
|
|
73
|
+
paddingVertical: 16,
|
|
74
|
+
},
|
|
75
|
+
buttonText: {
|
|
76
|
+
color: "#FFFFFF",
|
|
77
|
+
fontWeight: "600",
|
|
78
|
+
},
|
|
79
|
+
settingsButton: {
|
|
80
|
+
width: 56,
|
|
81
|
+
justifyContent: "center",
|
|
82
|
+
alignItems: "center",
|
|
83
|
+
},
|
|
84
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text-to-Image Presentation Components
|
|
3
|
+
* All component exports for text-to-image feature
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Input Components
|
|
7
|
+
export { PromptInput } from "./PromptInput";
|
|
8
|
+
export type { PromptInputProps } from "./PromptInput";
|
|
9
|
+
|
|
10
|
+
export { ExamplePrompts } from "./ExamplePrompts";
|
|
11
|
+
export type { ExamplePromptsProps } from "./ExamplePrompts";
|
|
12
|
+
|
|
13
|
+
// Selector Components
|
|
14
|
+
export { NumImagesSelector } from "./NumImagesSelector";
|
|
15
|
+
export type { NumImagesSelectorProps } from "./NumImagesSelector";
|
|
16
|
+
|
|
17
|
+
export { StyleSelector } from "./StyleSelector";
|
|
18
|
+
export type { StyleSelectorProps } from "./StyleSelector";
|
|
19
|
+
|
|
20
|
+
export { AspectRatioSelector } from "./AspectRatioSelector";
|
|
21
|
+
export type {
|
|
22
|
+
AspectRatioSelectorProps,
|
|
23
|
+
AspectRatioOption,
|
|
24
|
+
} from "./AspectRatioSelector";
|
|
25
|
+
|
|
26
|
+
export { ImageSizeSelector } from "./ImageSizeSelector";
|
|
27
|
+
export type { ImageSizeSelectorProps, ImageSizeOption } from "./ImageSizeSelector";
|
|
28
|
+
|
|
29
|
+
export { OutputFormatSelector } from "./OutputFormatSelector";
|
|
30
|
+
export type {
|
|
31
|
+
OutputFormatSelectorProps,
|
|
32
|
+
OutputFormatOption,
|
|
33
|
+
} from "./OutputFormatSelector";
|
|
34
|
+
|
|
35
|
+
// Action Components
|
|
36
|
+
export { TextToImageGenerateButton } from "./TextToImageGenerateButton";
|
|
37
|
+
export type { TextToImageGenerateButtonProps } from "./TextToImageGenerateButton";
|
|
38
|
+
|
|
39
|
+
// Sheet Components
|
|
40
|
+
export { SettingsSheet } from "./SettingsSheet";
|
|
41
|
+
export type { SettingsSheetProps } from "./SettingsSheet";
|
|
@@ -1,3 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text-to-Image Presentation Hooks
|
|
3
|
+
* All hook exports for text-to-image feature
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Form State Hook
|
|
7
|
+
export { useFormState } from "./useFormState";
|
|
8
|
+
export type { UseFormStateOptions, UseFormStateReturn } from "./useFormState";
|
|
9
|
+
|
|
10
|
+
// Generation Hook
|
|
11
|
+
export { useGeneration } from "./useGeneration";
|
|
12
|
+
export type {
|
|
13
|
+
GenerationState,
|
|
14
|
+
UseGenerationOptions,
|
|
15
|
+
UseGenerationReturn,
|
|
16
|
+
} from "./useGeneration";
|
|
17
|
+
|
|
18
|
+
// Combined Form Hook
|
|
19
|
+
export { useTextToImageForm } from "./useTextToImageForm";
|
|
20
|
+
export type {
|
|
21
|
+
UseTextToImageFormOptions,
|
|
22
|
+
UseTextToImageFormReturn,
|
|
23
|
+
} from "./useTextToImageForm";
|
|
24
|
+
|
|
25
|
+
// Provider-based Feature Hook (existing)
|
|
1
26
|
export { useTextToImageFeature } from "./useTextToImageFeature";
|
|
2
27
|
export type {
|
|
3
28
|
UseTextToImageFeatureProps,
|