@umituz/react-native-ai-creations 1.3.3 → 1.3.5

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.3.3",
3
+ "version": "1.3.5",
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",
@@ -68,4 +68,4 @@
68
68
  "README.md",
69
69
  "LICENSE"
70
70
  ]
71
- }
71
+ }
@@ -33,11 +33,6 @@ const useStyles = (tokens: any) => StyleSheet.create({
33
33
  borderRadius: 24,
34
34
  overflow: 'hidden',
35
35
  backgroundColor: tokens.colors.surface,
36
- shadowColor: "#000",
37
- shadowOffset: { width: 0, height: 8 },
38
- shadowOpacity: 0.2,
39
- shadowRadius: 16,
40
- elevation: 8,
41
36
  },
42
37
  image: {
43
38
  width: '100%',
@@ -0,0 +1,157 @@
1
+ import React, { forwardRef, useCallback, useMemo } from 'react';
2
+ import { View, StyleSheet, TouchableOpacity, ScrollView } from 'react-native';
3
+ import { BottomSheetModal, BottomSheetModalRef } from '@umituz/react-native-bottom-sheet';
4
+ import { useAppDesignTokens, AtomicText, AtomicIcon } from '@umituz/react-native-design-system';
5
+
6
+ export interface FilterOption {
7
+ id: string;
8
+ label: string;
9
+ icon?: string;
10
+ }
11
+
12
+ export interface FilterCategory {
13
+ id: string;
14
+ title: string;
15
+ multiSelect?: boolean;
16
+ options: FilterOption[];
17
+ }
18
+
19
+ interface FilterBottomSheetProps {
20
+ categories: FilterCategory[];
21
+ selectedIds: string[];
22
+ onFilterPress: (id: string, categoryId: string) => void;
23
+ onClearFilters: () => void;
24
+ title: string;
25
+ }
26
+
27
+ export const FilterBottomSheet = forwardRef<BottomSheetModalRef, FilterBottomSheetProps>((props, ref) => {
28
+ const { categories, selectedIds, onFilterPress, onClearFilters, title } = props;
29
+ const tokens = useAppDesignTokens();
30
+ const styles = useStyles(tokens);
31
+
32
+ const snapPoints = useMemo(() => ['50%', '75%'], []);
33
+
34
+ const renderOption = useCallback((option: FilterOption, category: FilterCategory) => {
35
+ const isSelected = selectedIds.includes(option.id);
36
+
37
+ return (
38
+ <TouchableOpacity
39
+ key={option.id}
40
+ style={[styles.option, isSelected && styles.optionSelected]}
41
+ onPress={() => onFilterPress(option.id, category.id)}
42
+ >
43
+ <View style={styles.optionContent}>
44
+ {option.icon && (
45
+ <View style={styles.optionIcon}>
46
+ <AtomicIcon
47
+ name={option.icon as any}
48
+ size="sm"
49
+ color={isSelected ? "primary" : "text"}
50
+ />
51
+ </View>
52
+ )}
53
+ <AtomicText
54
+ style={[styles.optionLabel, isSelected && styles.optionLabelSelected]}
55
+ >
56
+ {option.label}
57
+ </AtomicText>
58
+ </View>
59
+ {isSelected && (
60
+ <AtomicIcon name="check" size="sm" color="primary" />
61
+ )}
62
+ </TouchableOpacity>
63
+ );
64
+ }, [onFilterPress, selectedIds, styles]);
65
+
66
+ return (
67
+ <BottomSheetModal
68
+ ref={ref}
69
+ snapPoints={snapPoints}
70
+ >
71
+ <View style={styles.header}>
72
+ <AtomicText style={styles.headerTitle}>{title}</AtomicText>
73
+ <TouchableOpacity onPress={onClearFilters}>
74
+ <AtomicText style={styles.clearButton}>Clear</AtomicText>
75
+ </TouchableOpacity>
76
+ </View>
77
+
78
+ <ScrollView contentContainerStyle={styles.content}>
79
+ {categories.map(category => (
80
+ <View key={category.id} style={styles.categoryContainer}>
81
+ <AtomicText style={styles.categoryTitle}>{category.title}</AtomicText>
82
+ <View style={styles.optionsContainer}>
83
+ {category.options.map(option => renderOption(option, category))}
84
+ </View>
85
+ </View>
86
+ ))}
87
+ </ScrollView>
88
+ </BottomSheetModal>
89
+ );
90
+ });
91
+
92
+ const useStyles = (tokens: any) => StyleSheet.create({
93
+ content: {
94
+ padding: tokens.spacing.md,
95
+ paddingBottom: tokens.spacing.xl,
96
+ },
97
+ header: {
98
+ flexDirection: 'row',
99
+ justifyContent: 'space-between',
100
+ alignItems: 'center',
101
+ paddingHorizontal: tokens.spacing.md,
102
+ paddingBottom: tokens.spacing.sm,
103
+ borderBottomWidth: 1,
104
+ borderBottomColor: tokens.colors.outline,
105
+ },
106
+ headerTitle: {
107
+ fontSize: 20,
108
+ fontWeight: '700',
109
+ color: tokens.colors.textPrimary,
110
+ },
111
+ clearButton: {
112
+ color: tokens.colors.primary,
113
+ fontSize: 14,
114
+ fontWeight: '600',
115
+ },
116
+ categoryContainer: {
117
+ marginTop: tokens.spacing.md,
118
+ },
119
+ categoryTitle: {
120
+ marginBottom: tokens.spacing.xs,
121
+ color: tokens.colors.textSecondary,
122
+ fontSize: 16,
123
+ fontWeight: '600',
124
+ },
125
+ optionsContainer: {
126
+ backgroundColor: tokens.colors.background,
127
+ borderRadius: tokens.borderRadius.md,
128
+ overflow: 'hidden',
129
+ },
130
+ option: {
131
+ flexDirection: 'row',
132
+ alignItems: 'center',
133
+ justifyContent: 'space-between',
134
+ padding: tokens.spacing.md,
135
+ backgroundColor: tokens.colors.background,
136
+ borderBottomWidth: 1,
137
+ borderBottomColor: tokens.colors.surface,
138
+ },
139
+ optionSelected: {
140
+ backgroundColor: tokens.colors.surface,
141
+ },
142
+ optionContent: {
143
+ flexDirection: 'row',
144
+ alignItems: 'center',
145
+ },
146
+ optionIcon: {
147
+ marginRight: tokens.spacing.sm,
148
+ },
149
+ optionLabel: {
150
+ color: tokens.colors.text,
151
+ fontSize: 14,
152
+ },
153
+ optionLabelSelected: {
154
+ color: tokens.colors.primary,
155
+ fontWeight: 'bold',
156
+ },
157
+ });
@@ -9,6 +9,7 @@ export { CreationsHomeCard } from "./CreationsHomeCard";
9
9
  export { CreationCard } from "./CreationCard";
10
10
  export { CreationThumbnail } from "./CreationThumbnail";
11
11
  export { CreationsGrid } from "./CreationsGrid";
12
+ export { FilterBottomSheet, type FilterCategory, type FilterOption } from "./FilterBottomSheet";
12
13
 
13
14
  // Detail Components
14
15
  export { DetailHeader } from "./CreationDetail/DetailHeader";
@@ -6,10 +6,6 @@
6
6
  import { useState, useMemo, useCallback } from "react";
7
7
  import type { Creation } from "../../domain/entities/Creation";
8
8
 
9
- const ALL_FILTER = "all";
10
-
11
- import { FilterUtils } from "@umituz/react-native-bottom-sheet";
12
-
13
9
  interface UseCreationsFilterProps {
14
10
  readonly creations: Creation[] | undefined;
15
11
  readonly defaultFilterId?: string;
@@ -32,7 +28,32 @@ export function useCreationsFilter({
32
28
  }, [creations, selectedIds, defaultFilterId]);
33
29
 
34
30
  const toggleFilter = useCallback((filterId: string, multiSelect: boolean = false) => {
35
- setSelectedIds(prev => FilterUtils.toggleFilter(prev, filterId, multiSelect, defaultFilterId));
31
+ setSelectedIds(prev => {
32
+ // If selecting 'all', clear everything else
33
+ if (filterId === defaultFilterId) return [defaultFilterId];
34
+
35
+ let newIds: string[];
36
+ if (!multiSelect) {
37
+ // Single select
38
+ // If we tap the already selected item in single mode, should we Deselect it?
39
+ // Typically in radio-button style filters, no. But let's assume valid toggling behavior suitable for the UI.
40
+ // If single select, simply switch to the new one.
41
+ if (prev.includes(filterId) && prev.length === 1) return prev;
42
+ newIds = [filterId];
43
+ } else {
44
+ // Multi select
45
+ if (prev.includes(filterId)) {
46
+ newIds = prev.filter(id => id !== filterId);
47
+ } else {
48
+ // Remove 'all' if present
49
+ newIds = [...prev.filter(id => id !== defaultFilterId), filterId];
50
+ }
51
+ }
52
+
53
+ // If nothing selected, revert to 'all'
54
+ if (newIds.length === 0) return [defaultFilterId];
55
+ return newIds;
56
+ });
36
57
  }, [defaultFilterId]);
37
58
 
38
59
  const clearFilters = useCallback(() => {
@@ -9,9 +9,8 @@ import { useCreations } from "../hooks/useCreations";
9
9
  import { useDeleteCreation } from "../hooks/useDeleteCreation";
10
10
  import { useCreationsFilter } from "../hooks/useCreationsFilter";
11
11
  import { useAlert } from "@umituz/react-native-alert";
12
- import { FilterBottomSheet, type FilterCategory } from "@umituz/react-native-bottom-sheet";
13
12
  import { BottomSheetModalRef } from "@umituz/react-native-bottom-sheet";
14
- import { GalleryHeader, EmptyState, CreationsGrid } from "../components";
13
+ import { GalleryHeader, EmptyState, CreationsGrid, FilterBottomSheet, type FilterCategory } from "../components";
15
14
  import type { Creation } from "../../domain/entities/Creation";
16
15
  import type { CreationsConfig } from "../../domain/value-objects/CreationsConfig";
17
16
  import type { ICreationsRepository } from "../../domain/repositories/ICreationsRepository";
@@ -175,8 +174,7 @@ export function CreationsGalleryScreen({
175
174
  );
176
175
  }
177
176
 
178
- );
179
- }
177
+
180
178
 
181
179
  const useStyles = (tokens: any) => StyleSheet.create({
182
180
  container: { flex: 1, backgroundColor: tokens.colors.background },