@umituz/react-native-ai-creations 1.2.1 → 1.2.3
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 +13 -7
- package/src/domain/value-objects/CreationsConfig.ts +3 -0
- package/src/presentation/components/CreationThumbnail.tsx +6 -6
- package/src/presentation/components/FilterChips.tsx +3 -1
- package/src/presentation/hooks/useCreationsFilter.ts +28 -21
- package/src/presentation/screens/CreationsGalleryScreen.tsx +60 -11
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-creations",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.3",
|
|
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",
|
|
@@ -34,18 +34,24 @@
|
|
|
34
34
|
"@umituz/react-native-image": "latest",
|
|
35
35
|
"@umituz/react-native-sharing": "latest",
|
|
36
36
|
"firebase": ">=11.0.0",
|
|
37
|
-
"react": ">=
|
|
38
|
-
"react-native": ">=0.
|
|
39
|
-
"react-native-safe-area-context": ">=
|
|
37
|
+
"react": ">=19.1.0",
|
|
38
|
+
"react-native": ">=0.81.5",
|
|
39
|
+
"react-native-safe-area-context": ">=5.0.0",
|
|
40
|
+
"@umituz/react-native-filter": "^1.4.2",
|
|
41
|
+
"@umituz/react-native-bottom-sheet": "latest",
|
|
42
|
+
"@umituz/react-native-design-system-atoms": "latest"
|
|
40
43
|
},
|
|
41
44
|
"devDependencies": {
|
|
42
45
|
"@tanstack/react-query": "^5.62.16",
|
|
43
46
|
"@types/react": "^19.0.0",
|
|
44
47
|
"@umituz/react-native-image": "^1.1.1",
|
|
45
48
|
"firebase": "^11.0.0",
|
|
46
|
-
"react": "
|
|
47
|
-
"react-native": "
|
|
48
|
-
"react-native-safe-area-context": "^
|
|
49
|
+
"react": "19.1.0",
|
|
50
|
+
"react-native": "0.81.5",
|
|
51
|
+
"react-native-safe-area-context": "^5.6.0",
|
|
52
|
+
"@umituz/react-native-filter": "^1.4.2",
|
|
53
|
+
"@umituz/react-native-bottom-sheet": "latest",
|
|
54
|
+
"@umituz/react-native-design-system-atoms": "latest",
|
|
49
55
|
"typescript": "^5.3.3"
|
|
50
56
|
},
|
|
51
57
|
"publishConfig": {
|
|
@@ -39,9 +39,12 @@ export type PathBuilder = (userId: string) => string[];
|
|
|
39
39
|
*/
|
|
40
40
|
export type DocumentMapper = (id: string, data: CreationDocument) => Creation;
|
|
41
41
|
|
|
42
|
+
import type { FilterCategory } from "@umituz/react-native-filter";
|
|
43
|
+
|
|
42
44
|
export interface CreationsConfig {
|
|
43
45
|
readonly collectionName: string;
|
|
44
46
|
readonly types: readonly CreationType[];
|
|
47
|
+
readonly filterCategories?: readonly FilterCategory[];
|
|
45
48
|
readonly translations: CreationsTranslations;
|
|
46
49
|
readonly maxThumbnails?: number;
|
|
47
50
|
readonly gridColumns?: number;
|
|
@@ -7,7 +7,7 @@ import React, { useMemo, useState } from "react";
|
|
|
7
7
|
import { Image, TouchableOpacity, StyleSheet, View } from "react-native";
|
|
8
8
|
import {
|
|
9
9
|
useAppDesignTokens,
|
|
10
|
-
AtomicIcon
|
|
10
|
+
AtomicIcon,
|
|
11
11
|
} from "@umituz/react-native-design-system";
|
|
12
12
|
|
|
13
13
|
interface CreationThumbnailProps {
|
|
@@ -35,13 +35,13 @@ export function CreationThumbnail({
|
|
|
35
35
|
},
|
|
36
36
|
overlay: {
|
|
37
37
|
...StyleSheet.absoluteFillObject,
|
|
38
|
-
backgroundColor:
|
|
38
|
+
backgroundColor: "rgba(0, 0, 0, 0.5)",
|
|
39
39
|
borderRadius: tokens.spacing.sm,
|
|
40
|
-
justifyContent:
|
|
41
|
-
alignItems:
|
|
40
|
+
justifyContent: "center",
|
|
41
|
+
alignItems: "center",
|
|
42
42
|
},
|
|
43
43
|
}),
|
|
44
|
-
[tokens, size]
|
|
44
|
+
[tokens, size]
|
|
45
45
|
);
|
|
46
46
|
|
|
47
47
|
return (
|
|
@@ -55,7 +55,7 @@ export function CreationThumbnail({
|
|
|
55
55
|
<Image source={{ uri }} style={styles.thumbnail} />
|
|
56
56
|
{isPressed && onPress && (
|
|
57
57
|
<View style={styles.overlay}>
|
|
58
|
-
<AtomicIcon name="eye" size="lg" color="
|
|
58
|
+
<AtomicIcon name="eye" size="lg" color="textInverse" />
|
|
59
59
|
</View>
|
|
60
60
|
)}
|
|
61
61
|
</TouchableOpacity>
|
|
@@ -14,6 +14,7 @@ interface FilterChipsProps {
|
|
|
14
14
|
readonly selectedType: string;
|
|
15
15
|
readonly allLabel: string;
|
|
16
16
|
readonly onSelect: (type: string) => void;
|
|
17
|
+
readonly style?: any;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
export function FilterChips({
|
|
@@ -22,6 +23,7 @@ export function FilterChips({
|
|
|
22
23
|
selectedType,
|
|
23
24
|
allLabel,
|
|
24
25
|
onSelect,
|
|
26
|
+
style,
|
|
25
27
|
}: FilterChipsProps) {
|
|
26
28
|
const tokens = useAppDesignTokens();
|
|
27
29
|
|
|
@@ -59,7 +61,7 @@ export function FilterChips({
|
|
|
59
61
|
const visibleTypes = types.filter((t) => availableTypes.includes(t.id));
|
|
60
62
|
|
|
61
63
|
return (
|
|
62
|
-
<View style={styles.container}>
|
|
64
|
+
<View style={[styles.container, style]}>
|
|
63
65
|
<ScrollView
|
|
64
66
|
horizontal
|
|
65
67
|
showsHorizontalScrollIndicator={false}
|
|
@@ -12,35 +12,42 @@ interface UseCreationsFilterProps {
|
|
|
12
12
|
readonly creations: Creation[] | undefined;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
const [selectedType, setSelectedType] = useState<string>(ALL_FILTER);
|
|
15
|
+
import { FilterUtils } from "@umituz/react-native-filter";
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
interface UseCreationsFilterProps {
|
|
18
|
+
readonly creations: Creation[] | undefined;
|
|
19
|
+
readonly defaultFilterId?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function useCreationsFilter({
|
|
23
|
+
creations,
|
|
24
|
+
defaultFilterId = "all"
|
|
25
|
+
}: UseCreationsFilterProps) {
|
|
26
|
+
const [selectedIds, setSelectedIds] = useState<string[]>([defaultFilterId]);
|
|
23
27
|
|
|
24
28
|
const filtered = useMemo(() => {
|
|
25
29
|
if (!creations) return [];
|
|
26
|
-
if (
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
if (selectedIds.includes(defaultFilterId)) return creations;
|
|
31
|
+
|
|
32
|
+
return creations.filter((c) =>
|
|
33
|
+
selectedIds.includes(c.type) ||
|
|
34
|
+
selectedIds.some(id => (c as any).metadata?.tags?.includes(id))
|
|
35
|
+
);
|
|
36
|
+
}, [creations, selectedIds, defaultFilterId]);
|
|
29
37
|
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
}, []);
|
|
38
|
+
const toggleFilter = useCallback((filterId: string, multiSelect: boolean = false) => {
|
|
39
|
+
setSelectedIds(prev => FilterUtils.toggleFilter(prev, filterId, multiSelect, defaultFilterId));
|
|
40
|
+
}, [defaultFilterId]);
|
|
33
41
|
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
}, []);
|
|
42
|
+
const clearFilters = useCallback(() => {
|
|
43
|
+
setSelectedIds([defaultFilterId]);
|
|
44
|
+
}, [defaultFilterId]);
|
|
37
45
|
|
|
38
46
|
return {
|
|
39
47
|
filtered,
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
isFiltered: selectedType !== ALL_FILTER,
|
|
48
|
+
selectedIds,
|
|
49
|
+
toggleFilter,
|
|
50
|
+
clearFilters,
|
|
51
|
+
isFiltered: !selectedIds.includes(defaultFilterId),
|
|
45
52
|
};
|
|
46
53
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import React, { useMemo, useCallback, useState } from "react";
|
|
7
|
-
import { View, FlatList, StyleSheet, RefreshControl, Alert } from "react-native";
|
|
7
|
+
import { View, FlatList, StyleSheet, RefreshControl, Alert, TouchableOpacity } from "react-native";
|
|
8
8
|
import { useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
9
9
|
import { useSharing } from "@umituz/react-native-sharing";
|
|
10
10
|
import { ImageGallery } from "@umituz/react-native-image";
|
|
@@ -18,6 +18,9 @@ import { useCreationsFilter } from "../hooks/useCreationsFilter";
|
|
|
18
18
|
import { CreationCard } from "../components/CreationCard";
|
|
19
19
|
import { FilterChips } from "../components/FilterChips";
|
|
20
20
|
import { EmptyState } from "../components/EmptyState";
|
|
21
|
+
import { FilterBottomSheet, type FilterCategory } from "@umituz/react-native-filter";
|
|
22
|
+
import { BottomSheetModalRef } from "@umituz/react-native-bottom-sheet";
|
|
23
|
+
import { AtomicIcon, AtomicText } from "@umituz/react-native-design-system-atoms";
|
|
21
24
|
|
|
22
25
|
interface CreationsGalleryScreenProps {
|
|
23
26
|
readonly userId: string | null;
|
|
@@ -41,6 +44,7 @@ export function CreationsGalleryScreen({
|
|
|
41
44
|
const { share } = useSharing();
|
|
42
45
|
const [viewerVisible, setViewerVisible] = useState(false);
|
|
43
46
|
const [viewerIndex, setViewerIndex] = useState(0);
|
|
47
|
+
const filterSheetRef = React.useRef<BottomSheetModalRef>(null);
|
|
44
48
|
|
|
45
49
|
const { data: creations, isLoading, refetch } = useCreations({
|
|
46
50
|
userId,
|
|
@@ -48,9 +52,15 @@ export function CreationsGalleryScreen({
|
|
|
48
52
|
});
|
|
49
53
|
|
|
50
54
|
const deleteMutation = useDeleteCreation({ userId, repository });
|
|
51
|
-
const { filtered,
|
|
55
|
+
const { filtered, selectedIds, toggleFilter, clearFilters, isFiltered } =
|
|
52
56
|
useCreationsFilter({ creations });
|
|
53
57
|
|
|
58
|
+
const availableTypes = useMemo(() => {
|
|
59
|
+
if (!creations) return [];
|
|
60
|
+
const types = new Set(creations.map((c) => c.type));
|
|
61
|
+
return Array.from(types);
|
|
62
|
+
}, [creations]);
|
|
63
|
+
|
|
54
64
|
const handleView = useCallback(
|
|
55
65
|
(creation: Creation) => {
|
|
56
66
|
const index = filtered.findIndex((c) => c.id === creation.id);
|
|
@@ -111,6 +121,20 @@ export function CreationsGalleryScreen({
|
|
|
111
121
|
flex: 1,
|
|
112
122
|
backgroundColor: tokens.colors.backgroundPrimary,
|
|
113
123
|
},
|
|
124
|
+
headerArea: {
|
|
125
|
+
flexDirection: "row",
|
|
126
|
+
alignItems: "center",
|
|
127
|
+
paddingRight: tokens.spacing.md,
|
|
128
|
+
},
|
|
129
|
+
filterButton: {
|
|
130
|
+
padding: tokens.spacing.sm,
|
|
131
|
+
borderRadius: (tokens as any).borders?.radius?.full || 999,
|
|
132
|
+
backgroundColor: tokens.colors.surfaceVariant,
|
|
133
|
+
marginLeft: tokens.spacing.sm,
|
|
134
|
+
},
|
|
135
|
+
filterButtonActive: {
|
|
136
|
+
backgroundColor: tokens.colors.primary + "15",
|
|
137
|
+
},
|
|
114
138
|
list: {
|
|
115
139
|
padding: tokens.spacing.md,
|
|
116
140
|
paddingBottom: insets.bottom + tokens.spacing.xl,
|
|
@@ -151,15 +175,26 @@ export function CreationsGalleryScreen({
|
|
|
151
175
|
|
|
152
176
|
return (
|
|
153
177
|
<View style={styles.container}>
|
|
154
|
-
{
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
178
|
+
<View style={styles.headerArea}>
|
|
179
|
+
{config.types.length > 0 && availableTypes.length > 1 && (
|
|
180
|
+
<FilterChips
|
|
181
|
+
style={{ flex: 1 }}
|
|
182
|
+
types={config.types}
|
|
183
|
+
availableTypes={availableTypes}
|
|
184
|
+
selectedType={selectedIds[0] || "all"}
|
|
185
|
+
allLabel={t(config.translations.filterAll)}
|
|
186
|
+
onSelect={(type) => toggleFilter(type, false)}
|
|
187
|
+
/>
|
|
188
|
+
)}
|
|
189
|
+
{(config.filterCategories?.length || 0) > 0 && (
|
|
190
|
+
<TouchableOpacity
|
|
191
|
+
onPress={() => filterSheetRef.current?.present()}
|
|
192
|
+
style={[styles.filterButton, isFiltered && styles.filterButtonActive]}
|
|
193
|
+
>
|
|
194
|
+
<AtomicIcon name="ListFilter" size="md" color={isFiltered ? "primary" : "secondary"} />
|
|
195
|
+
</TouchableOpacity>
|
|
196
|
+
)}
|
|
197
|
+
</View>
|
|
163
198
|
<FlatList
|
|
164
199
|
data={filtered}
|
|
165
200
|
renderItem={renderItem}
|
|
@@ -182,6 +217,20 @@ export function CreationsGalleryScreen({
|
|
|
182
217
|
enableEditing={enableEditing}
|
|
183
218
|
onImageChange={handleImageChange}
|
|
184
219
|
/>
|
|
220
|
+
|
|
221
|
+
{config.filterCategories && (
|
|
222
|
+
<FilterBottomSheet
|
|
223
|
+
ref={filterSheetRef}
|
|
224
|
+
categories={config.filterCategories as FilterCategory[]}
|
|
225
|
+
selectedIds={selectedIds}
|
|
226
|
+
onFilterPress={(id, catId) => {
|
|
227
|
+
const category = config.filterCategories?.find(c => c.id === catId);
|
|
228
|
+
toggleFilter(id, category?.multiSelect);
|
|
229
|
+
}}
|
|
230
|
+
onClearFilters={clearFilters}
|
|
231
|
+
title={t("common.filter")}
|
|
232
|
+
/>
|
|
233
|
+
)}
|
|
185
234
|
</View>
|
|
186
235
|
);
|
|
187
236
|
}
|