@umituz/react-native-ai-creations 1.2.8 → 1.2.10

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-creations",
3
- "version": "1.2.8",
3
+ "version": "1.2.10",
4
4
  "description": "AI-generated creations gallery with filtering, sharing, and management for React Native apps",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -27,32 +27,32 @@
27
27
  "type": "git",
28
28
  "url": "https://github.com/umituz/react-native-ai-creations"
29
29
  },
30
+ "dependencies": {
31
+ "@umituz/react-native-filter": "latest",
32
+ "@umituz/react-native-image": "latest",
33
+ "@umituz/react-native-bottom-sheet": "latest",
34
+ "@umituz/react-native-alert": "latest"
35
+ },
30
36
  "peerDependencies": {
31
37
  "@tanstack/react-query": ">=5.0.0",
32
38
  "@umituz/react-native-design-system": "latest",
33
39
  "@umituz/react-native-firestore": "latest",
34
- "@umituz/react-native-image": "latest",
35
40
  "@umituz/react-native-sharing": "latest",
36
41
  "firebase": ">=11.0.0",
37
42
  "react": ">=19.1.0",
38
43
  "react-native": ">=0.81.5",
39
- "react-native-safe-area-context": ">=5.0.0",
40
- "@umituz/react-native-filter": "latest",
41
- "@umituz/react-native-bottom-sheet": "latest"
44
+ "react-native-safe-area-context": ">=5.0.0"
42
45
  },
43
46
  "devDependencies": {
44
47
  "@tanstack/react-query": "^5.62.16",
45
48
  "@types/react": "^19.0.0",
46
49
  "@umituz/react-native-design-system": "latest",
47
- "@umituz/react-native-image": "latest",
48
50
  "@umituz/react-native-sharing": "latest",
49
51
  "@umituz/react-native-firestore": "latest",
50
52
  "firebase": "^11.0.0",
51
53
  "react": "19.1.0",
52
54
  "react-native": "0.81.5",
53
55
  "react-native-safe-area-context": "^5.6.0",
54
- "@umituz/react-native-filter": "latest",
55
- "@umituz/react-native-bottom-sheet": "latest",
56
56
  "typescript": "^5.3.3"
57
57
  },
58
58
  "publishConfig": {
@@ -0,0 +1,99 @@
1
+ import React from 'react';
2
+ import { View, TouchableOpacity, StyleSheet } from 'react-native';
3
+ import { AtomicText, AtomicIcon, useAppDesignTokens } from "@umituz/react-native-design-system";
4
+
5
+ interface GalleryHeaderProps {
6
+ readonly title: string;
7
+ readonly count: number;
8
+ readonly countLabel: string;
9
+ readonly isFiltered: boolean;
10
+ readonly onFilterPress: () => void;
11
+ }
12
+
13
+ export const GalleryHeader: React.FC<GalleryHeaderProps> = ({
14
+ title,
15
+ count,
16
+ countLabel,
17
+ isFiltered,
18
+ onFilterPress
19
+ }) => {
20
+ const tokens = useAppDesignTokens();
21
+ const styles = useStyles(tokens);
22
+
23
+ return (
24
+ <View style={styles.headerArea}>
25
+ <View>
26
+ <AtomicText style={styles.title}>{title}</AtomicText>
27
+ <AtomicText style={styles.subtitle}>
28
+ {count} {countLabel}
29
+ </AtomicText>
30
+ </View>
31
+ <TouchableOpacity
32
+ onPress={onFilterPress}
33
+ style={[styles.filterButton, isFiltered && styles.filterButtonActive]}
34
+ >
35
+ <AtomicIcon
36
+ name="ListFilter"
37
+ size="sm"
38
+ color={isFiltered ? "primary" : "secondary"}
39
+ />
40
+ <AtomicText style={[styles.filterText, { color: isFiltered ? tokens.colors.primary : tokens.colors.textSecondary }]}>
41
+ Filter
42
+ </AtomicText>
43
+ {isFiltered && (
44
+ <View style={styles.badge} />
45
+ )}
46
+ </TouchableOpacity>
47
+ </View>
48
+ );
49
+ };
50
+
51
+ const useStyles = (tokens: any) => StyleSheet.create({
52
+ headerArea: {
53
+ flexDirection: "row",
54
+ alignItems: "center",
55
+ justifyContent: 'space-between',
56
+ paddingHorizontal: tokens.spacing.md,
57
+ paddingVertical: tokens.spacing.sm,
58
+ marginBottom: tokens.spacing.sm,
59
+ },
60
+ title: {
61
+ fontSize: 20,
62
+ fontWeight: "700",
63
+ color: tokens.colors.textPrimary,
64
+ marginBottom: 4,
65
+ },
66
+ subtitle: {
67
+ fontSize: 14,
68
+ color: tokens.colors.textSecondary,
69
+ opacity: 0.6
70
+ },
71
+ filterButton: {
72
+ flexDirection: 'row',
73
+ alignItems: 'center',
74
+ gap: tokens.spacing.xs,
75
+ paddingVertical: tokens.spacing.xs,
76
+ paddingHorizontal: tokens.spacing.md,
77
+ borderRadius: 999,
78
+ backgroundColor: tokens.colors.surfaceVariant,
79
+ borderWidth: 1,
80
+ borderColor: 'transparent',
81
+ },
82
+ filterButtonActive: {
83
+ backgroundColor: tokens.colors.primary + "15",
84
+ borderColor: tokens.colors.primary + "30",
85
+ },
86
+ filterText: {
87
+ fontSize: 14,
88
+ fontWeight: "500",
89
+ },
90
+ badge: {
91
+ width: 8,
92
+ height: 8,
93
+ borderRadius: 4,
94
+ backgroundColor: tokens.colors.primary,
95
+ position: 'absolute',
96
+ top: 6,
97
+ right: 6,
98
+ },
99
+ });
@@ -8,10 +8,6 @@ import type { Creation } from "../../domain/entities/Creation";
8
8
 
9
9
  const ALL_FILTER = "all";
10
10
 
11
- interface UseCreationsFilterProps {
12
- readonly creations: Creation[] | undefined;
13
- }
14
-
15
11
  import { FilterUtils } from "@umituz/react-native-filter";
16
12
 
17
13
  interface UseCreationsFilterProps {
@@ -20,6 +20,9 @@ import { EmptyState } from "../components/EmptyState";
20
20
  import { FilterBottomSheet, type FilterCategory } from "@umituz/react-native-filter";
21
21
  import { BottomSheetModalRef } from "@umituz/react-native-bottom-sheet";
22
22
  import { AtomicIcon, AtomicText } from "@umituz/react-native-design-system";
23
+ import { GalleryHeader } from "../components/GalleryHeader";
24
+
25
+ import { useAlert } from "@umituz/react-native-alert";
23
26
 
24
27
  interface CreationsGalleryScreenProps {
25
28
  readonly userId: string | null;
@@ -41,6 +44,7 @@ export function CreationsGalleryScreen({
41
44
  const tokens = useAppDesignTokens();
42
45
  const insets = useSafeAreaInsets();
43
46
  const { share } = useSharing();
47
+ const alert = useAlert();
44
48
  const [viewerVisible, setViewerVisible] = useState(false);
45
49
  const [viewerIndex, setViewerIndex] = useState(0);
46
50
  const filterSheetRef = React.useRef<BottomSheetModalRef>(null);
@@ -60,13 +64,13 @@ export function CreationsGalleryScreen({
60
64
  // Add dynamic types category if types exist
61
65
  if (config.types.length > 0) {
62
66
  categories.push({
63
- id: 'type', // This ID matches logic in useFilter? No, filter logic is flat ID based. 'type' is category ID.
67
+ id: 'type',
64
68
  title: t('creations.category') || 'Category',
65
69
  multiSelect: false,
66
70
  options: config.types.map(t => ({
67
71
  id: t.id,
68
- label: t.labelKey, // In a real app we'd translate this, but for now passing key or mocked
69
- icon: 'Image', // Default icon since types have emojis which might not work with AtomicIcon
72
+ label: t.labelKey,
73
+ icon: 'Image',
70
74
  }))
71
75
  });
72
76
  }
@@ -105,20 +109,28 @@ export function CreationsGalleryScreen({
105
109
 
106
110
  const handleDelete = useCallback(
107
111
  (creation: Creation) => {
108
- Alert.alert(
109
- t(config.translations.deleteTitle),
110
- t(config.translations.deleteMessage),
111
- [
112
- { text: t("common.cancel"), style: "cancel" },
112
+ alert.show({
113
+ title: t(config.translations.deleteTitle),
114
+ message: t(config.translations.deleteMessage),
115
+ type: 'warning' as any,
116
+ actions: [
117
+ {
118
+ id: 'cancel',
119
+ label: t("common.cancel"),
120
+ style: 'secondary',
121
+ onPress: () => { }
122
+ },
113
123
  {
114
- text: t("common.delete"),
115
- style: "destructive",
124
+ id: 'delete',
125
+ label: t("common.delete"),
126
+ style: 'destructive',
127
+ variant: 'danger',
116
128
  onPress: () => deleteMutation.mutate(creation.id),
117
129
  },
118
- ],
119
- );
130
+ ]
131
+ });
120
132
  },
121
- [t, config.translations, deleteMutation],
133
+ [t, config.translations, deleteMutation, alert],
122
134
  );
123
135
 
124
136
  const handleImageChange = useCallback(
@@ -138,38 +150,6 @@ export function CreationsGalleryScreen({
138
150
  flex: 1,
139
151
  backgroundColor: tokens.colors.backgroundPrimary,
140
152
  },
141
- headerArea: {
142
- flexDirection: "row",
143
- alignItems: "center",
144
- justifyContent: 'space-between',
145
- paddingHorizontal: tokens.spacing.md,
146
- paddingVertical: tokens.spacing.sm,
147
- marginBottom: tokens.spacing.sm,
148
- },
149
- filterButton: {
150
- flexDirection: 'row',
151
- alignItems: 'center',
152
- gap: tokens.spacing.xs,
153
- paddingVertical: tokens.spacing.xs,
154
- paddingHorizontal: tokens.spacing.md,
155
- borderRadius: (tokens as any).borders?.radius?.full || 999,
156
- backgroundColor: tokens.colors.surfaceVariant,
157
- borderWidth: 1,
158
- borderColor: 'transparent',
159
- },
160
- filterButtonActive: {
161
- backgroundColor: tokens.colors.primary + "15",
162
- borderColor: tokens.colors.primary + "30",
163
- },
164
- badge: {
165
- width: 8,
166
- height: 8,
167
- borderRadius: 4,
168
- backgroundColor: tokens.colors.primary,
169
- position: 'absolute',
170
- top: 6,
171
- right: 6,
172
- },
173
153
  list: {
174
154
  paddingHorizontal: tokens.spacing.md,
175
155
  paddingBottom: insets.bottom + tokens.spacing.xl,
@@ -210,22 +190,14 @@ export function CreationsGalleryScreen({
210
190
 
211
191
  return (
212
192
  <View style={styles.container}>
213
- <View style={styles.headerArea}>
214
- <View>
215
- <AtomicText type="headlineSmall">{t(config.translations.title) || 'My Creations'}</AtomicText>
216
- <AtomicText type="bodySmall" style={{ opacity: 0.6 }}>{filtered.length} {t(config.translations.photoCount) || 'photos'}</AtomicText>
217
- </View>
218
- <TouchableOpacity
219
- onPress={() => filterSheetRef.current?.present()}
220
- style={[styles.filterButton, isFiltered && styles.filterButtonActive]}
221
- >
222
- <AtomicIcon name="ListFilter" size="sm" color={isFiltered ? "primary" : "secondary"} />
223
- <AtomicText type="labelMedium" color={isFiltered ? "primary" : "secondary"}>Filter</AtomicText>
224
- {isFiltered && (
225
- <View style={styles.badge} />
226
- )}
227
- </TouchableOpacity>
228
- </View>
193
+ <GalleryHeader
194
+ title={t(config.translations.title) || 'My Creations'}
195
+ count={filtered.length}
196
+ countLabel={t(config.translations.photoCount) || 'photos'}
197
+ isFiltered={isFiltered}
198
+ onFilterPress={() => filterSheetRef.current?.present()}
199
+ />
200
+
229
201
  <FlatList
230
202
  data={filtered}
231
203
  renderItem={renderItem}