@umituz/react-native-ai-generation-content 1.41.3 → 1.41.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.41.3",
3
+ "version": "1.41.4",
4
4
  "description": "Provider-agnostic AI generation orchestration for React Native with result preview components",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
package/src/index.ts CHANGED
@@ -90,7 +90,7 @@ export {
90
90
  GenerateButton, ResultDisplay, AIGenerationResult, ErrorDisplay, FeatureHeader,
91
91
  AIGenScreenHeader, CreditBadge, PhotoUploadCard, SettingsSheet, StyleSelector,
92
92
  AspectRatioSelector, DurationSelector, GridSelector, StylePresetsGrid, AIGenerationForm,
93
- AIGenerationConfig,
93
+ AIGenerationConfig, ModelSelector,
94
94
  createAspectRatioOptions, createDurationOptions, createStyleOptions, createStyleOptionsFromConfig,
95
95
  ASPECT_RATIO_IDS, COMMON_DURATIONS,
96
96
  } from "./presentation/components";
@@ -121,7 +121,7 @@ export type {
121
121
  AspectRatioSelectorProps, DurationSelectorProps, GridSelectorProps, GridSelectorOption,
122
122
  StyleOption, AspectRatioOption, DurationValue, AspectRatioTranslations, DurationOption,
123
123
  StyleTranslations, AIGenerationFormProps, AIGenerationFormTranslations,
124
- AIGenerationConfigProps,
124
+ AIGenerationConfigProps, ModelOption, ModelSelectorProps,
125
125
  } from "./presentation/components";
126
126
 
127
127
  export { DEFAULT_SINGLE_PHOTO_FLOW, DEFAULT_DUAL_PHOTO_FLOW } from "./presentation/types/flow-config.types";
@@ -34,3 +34,4 @@ export * from "./headers";
34
34
  export * from "./PhotoUploadCard";
35
35
  export * from "./prompts/ExamplePrompts";
36
36
  export * from "./moderation/ModerationSummary";
37
+ export * from "./shared/ModelSelector";
@@ -0,0 +1,277 @@
1
+ /**
2
+ * ModelSelector Component
3
+ * Generic model selection dropdown for AI generation
4
+ * Package: @umituz/react-native-ai-generation-content
5
+ *
6
+ * @description
7
+ * Universal model selector component that works with any AI model type.
8
+ * Designed for 100+ applications with different model requirements.
9
+ *
10
+ * @example
11
+ * ```tsx
12
+ * <ModelSelector
13
+ * models={videoModels}
14
+ * selectedModel={selected}
15
+ * onSelectModel={(id) => setSelected(id)}
16
+ * label="Select Video Model"
17
+ * />
18
+ * ```
19
+ */
20
+
21
+ import React, { useState } from "react";
22
+ import {
23
+ View,
24
+ StyleSheet,
25
+ TouchableOpacity,
26
+ Modal,
27
+ Pressable,
28
+ ScrollView,
29
+ } from "react-native";
30
+ import {
31
+ useAppDesignTokens,
32
+ AtomicText,
33
+ AtomicIcon,
34
+ useSafeAreaInsets,
35
+ } from "@umituz/react-native-design-system";
36
+
37
+ export interface ModelOption {
38
+ readonly id: string;
39
+ readonly name: string;
40
+ readonly description?: string;
41
+ readonly cost?: number;
42
+ readonly badge?: string;
43
+ }
44
+
45
+ export interface ModelSelectorProps {
46
+ readonly models: ModelOption[];
47
+ readonly selectedModel: ModelOption | null;
48
+ readonly onSelectModel: (modelId: string) => void;
49
+ readonly label?: string;
50
+ readonly isLoading?: boolean;
51
+ readonly translations?: {
52
+ readonly selectModel?: string;
53
+ readonly credits?: string;
54
+ readonly close?: string;
55
+ };
56
+ }
57
+
58
+ export const ModelSelector: React.FC<ModelSelectorProps> = ({
59
+ models,
60
+ selectedModel,
61
+ onSelectModel,
62
+ label,
63
+ isLoading = false,
64
+ translations = {},
65
+ }) => {
66
+ const tokens = useAppDesignTokens();
67
+ const insets = useSafeAreaInsets();
68
+ const [isOpen, setIsOpen] = useState(false);
69
+
70
+ const {
71
+ selectModel = "Select Model",
72
+ credits = "credits",
73
+ close = "Close",
74
+ } = translations;
75
+
76
+ const handleSelect = (modelId: string) => {
77
+ onSelectModel(modelId);
78
+ setIsOpen(false);
79
+ };
80
+
81
+ const displayName = selectedModel?.name || label || selectModel;
82
+
83
+ return (
84
+ <>
85
+ <TouchableOpacity
86
+ style={[
87
+ styles.trigger,
88
+ {
89
+ backgroundColor: tokens.colors.surface,
90
+ borderRadius: tokens.borders.radius.md,
91
+ },
92
+ ]}
93
+ onPress={() => setIsOpen(true)}
94
+ disabled={isLoading}
95
+ >
96
+ <AtomicText type="labelMedium" color="textPrimary">
97
+ {displayName}
98
+ </AtomicText>
99
+ <AtomicIcon name="chevron-down" size="xs" color="secondary" />
100
+ </TouchableOpacity>
101
+
102
+ <Modal
103
+ visible={isOpen}
104
+ transparent
105
+ animationType="none"
106
+ onRequestClose={() => setIsOpen(false)}
107
+ >
108
+ <Pressable
109
+ style={[
110
+ styles.overlay,
111
+ { backgroundColor: tokens.colors.modalOverlay },
112
+ ]}
113
+ onPress={() => setIsOpen(false)}
114
+ >
115
+ <View
116
+ style={[
117
+ styles.dropdown,
118
+ {
119
+ backgroundColor: tokens.colors.backgroundSecondary,
120
+ marginTop: insets.top + 50,
121
+ borderRadius: tokens.borders.radius.lg,
122
+ },
123
+ ]}
124
+ >
125
+ <View
126
+ style={[
127
+ styles.header,
128
+ { borderBottomColor: tokens.colors.borderLight },
129
+ ]}
130
+ >
131
+ <AtomicText type="titleSmall" color="textPrimary">
132
+ {selectModel}
133
+ </AtomicText>
134
+ <TouchableOpacity
135
+ onPress={() => setIsOpen(false)}
136
+ accessibilityLabel={close}
137
+ accessibilityRole="button"
138
+ >
139
+ <AtomicIcon name="close" size="sm" color="secondary" />
140
+ </TouchableOpacity>
141
+ </View>
142
+
143
+ <ScrollView style={styles.list}>
144
+ {models.map((model) => {
145
+ const isSelected = selectedModel?.id === model.id;
146
+ return (
147
+ <TouchableOpacity
148
+ key={model.id}
149
+ style={[
150
+ styles.option,
151
+ {
152
+ backgroundColor: isSelected
153
+ ? `${tokens.colors.primary}15`
154
+ : "transparent",
155
+ },
156
+ ]}
157
+ onPress={() => handleSelect(model.id)}
158
+ accessibilityRole="button"
159
+ accessibilityState={{ selected: isSelected }}
160
+ >
161
+ <View style={styles.optionContent}>
162
+ <View style={styles.optionInfo}>
163
+ <AtomicText
164
+ type="bodyLarge"
165
+ style={{
166
+ color: isSelected
167
+ ? tokens.colors.primary
168
+ : tokens.colors.textPrimary,
169
+ fontWeight: isSelected ? "600" : "400",
170
+ }}
171
+ >
172
+ {model.name}
173
+ </AtomicText>
174
+ {model.description && (
175
+ <AtomicText
176
+ type="bodySmall"
177
+ color="textSecondary"
178
+ numberOfLines={2}
179
+ >
180
+ {model.description}
181
+ </AtomicText>
182
+ )}
183
+ {model.badge && (
184
+ <View
185
+ style={[
186
+ styles.badge,
187
+ {
188
+ backgroundColor: `${tokens.colors.success}20`,
189
+ },
190
+ ]}
191
+ >
192
+ <AtomicText type="labelSmall" color="success">
193
+ {model.badge}
194
+ </AtomicText>
195
+ </View>
196
+ )}
197
+ </View>
198
+ <View style={styles.optionRight}>
199
+ {model.cost !== undefined && (
200
+ <AtomicText type="labelSmall" color="textSecondary">
201
+ {model.cost} {credits}
202
+ </AtomicText>
203
+ )}
204
+ {isSelected && (
205
+ <AtomicIcon
206
+ name="checkmark"
207
+ size="sm"
208
+ color="primary"
209
+ />
210
+ )}
211
+ </View>
212
+ </View>
213
+ </TouchableOpacity>
214
+ );
215
+ })}
216
+ </ScrollView>
217
+ </View>
218
+ </Pressable>
219
+ </Modal>
220
+ </>
221
+ );
222
+ };
223
+
224
+ const styles = StyleSheet.create({
225
+ trigger: {
226
+ flexDirection: "row",
227
+ alignItems: "center",
228
+ paddingHorizontal: 12,
229
+ paddingVertical: 8,
230
+ gap: 4,
231
+ },
232
+ overlay: {
233
+ flex: 1,
234
+ },
235
+ dropdown: {
236
+ marginHorizontal: 16,
237
+ maxHeight: 400,
238
+ overflow: "hidden",
239
+ },
240
+ header: {
241
+ flexDirection: "row",
242
+ justifyContent: "space-between",
243
+ alignItems: "center",
244
+ paddingHorizontal: 16,
245
+ paddingVertical: 12,
246
+ borderBottomWidth: 1,
247
+ },
248
+ list: {
249
+ maxHeight: 350,
250
+ },
251
+ option: {
252
+ paddingHorizontal: 16,
253
+ paddingVertical: 14,
254
+ },
255
+ optionContent: {
256
+ flexDirection: "row",
257
+ justifyContent: "space-between",
258
+ alignItems: "center",
259
+ gap: 12,
260
+ },
261
+ optionInfo: {
262
+ flex: 1,
263
+ gap: 4,
264
+ },
265
+ optionRight: {
266
+ flexDirection: "row",
267
+ alignItems: "center",
268
+ gap: 8,
269
+ },
270
+ badge: {
271
+ alignSelf: "flex-start",
272
+ paddingHorizontal: 8,
273
+ paddingVertical: 2,
274
+ borderRadius: 4,
275
+ marginTop: 4,
276
+ },
277
+ });