@umituz/react-native-ai-generation-content 1.72.11 → 1.72.13

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.
Files changed (29) hide show
  1. package/package.json +1 -1
  2. package/src/domains/creations/presentation/components/CreationCard.utils.ts +2 -3
  3. package/src/domains/creations/presentation/components/CreationsFilterBar.tsx +41 -5
  4. package/src/domains/creations/presentation/hooks/filterHelpers.ts +5 -17
  5. package/src/domains/image-to-video/presentation/hooks/useFormState.ts +30 -58
  6. package/src/domains/image-to-video/presentation/hooks/useGeneration.ts +41 -71
  7. package/src/domains/text-to-image/presentation/hooks/useFormState.ts +34 -81
  8. package/src/exports/presentation.ts +5 -5
  9. package/src/index.ts +3 -0
  10. package/src/presentation/components/GenerationProgressContent.tsx +5 -2
  11. package/src/presentation/components/PendingJobCard.tsx +9 -2
  12. package/src/presentation/components/README.md +6 -5
  13. package/src/presentation/components/index.ts +0 -4
  14. package/src/shared/components/common/ProgressBar.tsx +99 -0
  15. package/src/shared/components/common/index.ts +5 -0
  16. package/src/shared/components/index.ts +5 -0
  17. package/src/shared/hooks/factories/createFormStateHook.ts +119 -0
  18. package/src/shared/hooks/factories/createGenerationHook.ts +253 -0
  19. package/src/shared/hooks/factories/index.ts +21 -0
  20. package/src/shared/hooks/index.ts +5 -0
  21. package/src/shared/index.ts +14 -0
  22. package/src/shared/utils/date/index.ts +11 -0
  23. package/src/shared/utils/date/normalization.ts +60 -0
  24. package/src/shared/utils/filters/createFilterButtons.ts +60 -0
  25. package/src/shared/utils/filters/index.ts +11 -0
  26. package/src/shared/utils/index.ts +6 -0
  27. package/src/domains/creations/presentation/components/filter-bar-utils.ts +0 -96
  28. package/src/presentation/components/GenerationProgressBar.tsx +0 -78
  29. package/src/presentation/components/PendingJobProgressBar.tsx +0 -56
@@ -1,7 +1,5 @@
1
1
  export { GenerationProgressContent } from "./GenerationProgressContent";
2
- export { GenerationProgressBar } from "./GenerationProgressBar";
3
2
  export { PendingJobCard } from "./PendingJobCard";
4
- export { PendingJobProgressBar } from "./PendingJobProgressBar";
5
3
  export { PendingJobCardActions } from "./PendingJobCardActions";
6
4
  export { PromptInput } from "./PromptInput";
7
5
  export { AIGenerationHero } from "./AIGenerationHero";
@@ -12,14 +10,12 @@ export * from "./AIGenerationForm.types";
12
10
  export * from "./AIGenerationConfig";
13
11
 
14
12
  export type { GenerationProgressContentProps } from "./GenerationProgressContent";
15
- export type { GenerationProgressBarProps } from "./GenerationProgressBar";
16
13
 
17
14
  export type {
18
15
  PendingJobCardProps,
19
16
  StatusLabels,
20
17
  } from "./PendingJobCard";
21
18
 
22
- export type { PendingJobProgressBarProps } from "./PendingJobProgressBar";
23
19
  export type { PendingJobCardActionsProps } from "./PendingJobCardActions";
24
20
  export type { PromptInputProps } from "./PromptInput";
25
21
  export type { AIGenerationHeroProps } from "./AIGenerationHero";
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Unified ProgressBar Component
3
+ * Flexible progress bar with optional percentage display
4
+ */
5
+
6
+ import React from "react";
7
+ import { View, StyleSheet } from "react-native";
8
+ import { AtomicText, useAppDesignTokens } from "@umituz/react-native-design-system";
9
+ import { clampProgress, roundProgress } from "../../../infrastructure/utils/progress.utils";
10
+
11
+ export interface ProgressBarProps {
12
+ /** Progress value (0-100) */
13
+ progress: number;
14
+ /** Show percentage text below bar */
15
+ showPercentage?: boolean;
16
+ /** Custom text color */
17
+ textColor?: string;
18
+ /** Custom progress fill color */
19
+ progressColor?: string;
20
+ /** Custom background color */
21
+ backgroundColor?: string;
22
+ /** Bar height (default: 8 for standard, 4 for compact) */
23
+ height?: number;
24
+ /** Spacing below bar (default: 16 with percentage, 0 without) */
25
+ marginBottom?: number;
26
+ /** Border radius (default: height / 2) */
27
+ borderRadius?: number;
28
+ }
29
+
30
+ export const ProgressBar: React.FC<ProgressBarProps> = ({
31
+ progress,
32
+ showPercentage = false,
33
+ textColor,
34
+ progressColor,
35
+ backgroundColor,
36
+ height = 8,
37
+ marginBottom,
38
+ borderRadius,
39
+ }) => {
40
+ const tokens = useAppDesignTokens();
41
+ const clampedProgress = clampProgress(progress);
42
+ const actualBorderRadius = borderRadius ?? height / 2;
43
+ const actualMarginBottom = marginBottom ?? (showPercentage ? 16 : 0);
44
+
45
+ return (
46
+ <View style={[styles.container, { marginBottom: actualMarginBottom }]}>
47
+ <View
48
+ style={[
49
+ styles.background,
50
+ {
51
+ backgroundColor: backgroundColor || tokens.colors.borderLight,
52
+ height,
53
+ borderRadius: actualBorderRadius,
54
+ },
55
+ ]}
56
+ >
57
+ <View
58
+ style={[
59
+ styles.fill,
60
+ {
61
+ backgroundColor: progressColor || tokens.colors.primary,
62
+ width: `${clampedProgress}%`,
63
+ borderRadius: actualBorderRadius,
64
+ },
65
+ ]}
66
+ />
67
+ </View>
68
+ {showPercentage && (
69
+ <AtomicText
70
+ style={[
71
+ styles.text,
72
+ { color: textColor || tokens.colors.textPrimary },
73
+ ]}
74
+ >
75
+ {roundProgress(clampedProgress)}%
76
+ </AtomicText>
77
+ )}
78
+ </View>
79
+ );
80
+ };
81
+
82
+ const styles = StyleSheet.create({
83
+ container: {
84
+ width: "100%",
85
+ alignItems: "center",
86
+ },
87
+ background: {
88
+ width: "100%",
89
+ overflow: "hidden",
90
+ },
91
+ fill: {
92
+ height: "100%",
93
+ },
94
+ text: {
95
+ fontSize: 14,
96
+ fontWeight: "600",
97
+ marginTop: 8,
98
+ },
99
+ });
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Common Shared Components
3
+ */
4
+
5
+ export { ProgressBar, type ProgressBarProps } from "./ProgressBar";
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Shared Components
3
+ */
4
+
5
+ export * from "./common";
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Generic Form State Hook Factory
3
+ * Creates type-safe form state management hooks
4
+ */
5
+
6
+ import { useState, useCallback, useMemo } from "react";
7
+
8
+ /**
9
+ * Form state configuration
10
+ */
11
+ export interface FormStateConfig<TState, TDefaults> {
12
+ /** Initial state factory */
13
+ createInitialState: (defaults: TDefaults) => TState;
14
+ /** Optional state validator */
15
+ validate?: (state: TState) => void;
16
+ }
17
+
18
+ /**
19
+ * Form actions type - maps state keys to setters
20
+ */
21
+ export type FormActions<TState> = {
22
+ [K in keyof TState as `set${Capitalize<string & K>}`]: (value: TState[K]) => void;
23
+ } & {
24
+ reset: () => void;
25
+ };
26
+
27
+ /**
28
+ * Form state hook return type
29
+ */
30
+ export interface FormStateHookReturn<TState, TActions> {
31
+ state: TState;
32
+ actions: TActions;
33
+ }
34
+
35
+ /**
36
+ * Options for form state hook
37
+ */
38
+ export interface FormStateHookOptions<TDefaults> {
39
+ defaults: TDefaults;
40
+ }
41
+
42
+ /**
43
+ * Creates a type-safe form state hook
44
+ * @param config - Form state configuration
45
+ * @returns Form state hook
46
+ *
47
+ * @example
48
+ * ```ts
49
+ * const useMyForm = createFormStateHook({
50
+ * createInitialState: (defaults) => ({
51
+ * name: "",
52
+ * age: defaults.age,
53
+ * }),
54
+ * });
55
+ *
56
+ * // Usage
57
+ * const { state, actions } = useMyForm({ defaults: { age: 18 } });
58
+ * actions.setName("John");
59
+ * actions.setAge(25);
60
+ * actions.reset();
61
+ * ```
62
+ */
63
+ export function createFormStateHook<
64
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
65
+ TState extends Record<string, any>,
66
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
67
+ TDefaults extends Record<string, any>,
68
+ TActions extends FormActions<TState> = FormActions<TState>
69
+ >(
70
+ config: FormStateConfig<TState, TDefaults>
71
+ ) {
72
+ return function useFormState(
73
+ options: FormStateHookOptions<TDefaults>
74
+ ): FormStateHookReturn<TState, TActions> {
75
+ const { defaults } = options;
76
+
77
+ // Validate defaults if validator provided
78
+ const validatedDefaults = useMemo(() => {
79
+ if (config.validate) {
80
+ const initialState = config.createInitialState(defaults);
81
+ config.validate(initialState);
82
+ }
83
+ return defaults;
84
+ }, [defaults]);
85
+
86
+ // Create initial state
87
+ const [state, setState] = useState<TState>(() =>
88
+ config.createInitialState(validatedDefaults)
89
+ );
90
+
91
+ // Create reset function
92
+ const reset = useCallback(() => {
93
+ setState(config.createInitialState(validatedDefaults));
94
+ }, [validatedDefaults]);
95
+
96
+ // Create actions dynamically
97
+ const actions = useMemo(() => {
98
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
99
+ const actionObj: any = { reset };
100
+
101
+ // Generate setter for each state key
102
+ Object.keys(state).forEach((key) => {
103
+ const setterName = `set${key.charAt(0).toUpperCase()}${key.slice(1)}`;
104
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
105
+ actionObj[setterName] = (value: any) => {
106
+ setState((prev) => ({ ...prev, [key]: value }));
107
+ };
108
+ });
109
+
110
+ return actionObj as TActions;
111
+ }, [state, reset]);
112
+
113
+ return { state, actions };
114
+ };
115
+ }
116
+
117
+ // Note: createFormStateHookWithIndividualState removed due to type complexity
118
+ // and lack of usage. Use createFormStateHook instead which provides the same
119
+ // functionality with better type safety.
@@ -0,0 +1,253 @@
1
+ /**
2
+ * Generic Generation Hook Factory
3
+ * Creates type-safe generation hooks with error handling, progress tracking, and abort support
4
+ */
5
+
6
+ import { useState, useCallback, useRef, useEffect } from "react";
7
+
8
+ /**
9
+ * Generation state
10
+ */
11
+ export interface GenerationState {
12
+ isGenerating: boolean;
13
+ progress: number;
14
+ error: string | null;
15
+ }
16
+
17
+ /**
18
+ * Generation callbacks
19
+ */
20
+ export interface GenerationCallbacks<TResult> {
21
+ /** Called when generation succeeds */
22
+ onSuccess?: (result: TResult) => void;
23
+ /** Called when generation fails */
24
+ onError?: (error: string) => void;
25
+ /** Called on progress update */
26
+ onProgress?: (progress: number) => void;
27
+ }
28
+
29
+ /**
30
+ * Generation hook configuration
31
+ */
32
+ export interface GenerationHookConfig<TRequest, TResult> {
33
+ /** Execute the generation request */
34
+ execute: (request: TRequest, signal?: AbortSignal) => Promise<TResult>;
35
+ /** Optional validation before execution */
36
+ validate?: (request: TRequest) => string | null;
37
+ /** Optional transform for errors */
38
+ transformError?: (error: unknown) => string;
39
+ }
40
+
41
+ /**
42
+ * Generation hook return type
43
+ */
44
+ export interface GenerationHookReturn<TRequest, TResult> {
45
+ generationState: GenerationState;
46
+ handleGenerate: (request: TRequest) => Promise<TResult | null>;
47
+ setProgress: (progress: number) => void;
48
+ setError: (error: string | null) => void;
49
+ abort: () => void;
50
+ }
51
+
52
+ const INITIAL_STATE: GenerationState = {
53
+ isGenerating: false,
54
+ progress: 0,
55
+ error: null,
56
+ };
57
+
58
+ /**
59
+ * Creates a type-safe generation hook
60
+ * @param config - Generation hook configuration
61
+ * @param callbacks - Generation callbacks
62
+ * @returns Generation hook
63
+ *
64
+ * @example
65
+ * ```ts
66
+ * const useMyGeneration = createGenerationHook({
67
+ * execute: async (request) => {
68
+ * return await api.generate(request);
69
+ * },
70
+ * validate: (request) => {
71
+ * if (!request.prompt) return "Prompt is required";
72
+ * return null;
73
+ * },
74
+ * });
75
+ *
76
+ * // Usage
77
+ * const { generationState, handleGenerate } = useMyGeneration({
78
+ * onSuccess: (result) => console.log("Success!", result),
79
+ * onError: (error) => console.error("Error:", error),
80
+ * });
81
+ * ```
82
+ */
83
+ export function createGenerationHook<TRequest, TResult>(
84
+ config: GenerationHookConfig<TRequest, TResult>
85
+ ) {
86
+ return function useGeneration(
87
+ callbacks: GenerationCallbacks<TResult> = {}
88
+ ): GenerationHookReturn<TRequest, TResult> {
89
+ const [generationState, setGenerationState] = useState<GenerationState>(INITIAL_STATE);
90
+
91
+ const abortControllerRef = useRef<AbortController | null>(null);
92
+ const isMountedRef = useRef(true);
93
+
94
+ // Stabilize callbacks
95
+ const onSuccessRef = useRef(callbacks.onSuccess);
96
+ const onErrorRef = useRef(callbacks.onError);
97
+ const onProgressRef = useRef(callbacks.onProgress);
98
+
99
+ useEffect(() => {
100
+ onSuccessRef.current = callbacks.onSuccess;
101
+ onErrorRef.current = callbacks.onError;
102
+ onProgressRef.current = callbacks.onProgress;
103
+ }, [callbacks.onSuccess, callbacks.onError, callbacks.onProgress]);
104
+
105
+ // Cleanup on unmount
106
+ useEffect(() => {
107
+ isMountedRef.current = true;
108
+ return () => {
109
+ isMountedRef.current = false;
110
+ abortControllerRef.current?.abort();
111
+ };
112
+ }, []);
113
+
114
+ const setProgress = useCallback((progress: number) => {
115
+ if (!isMountedRef.current) return;
116
+ setGenerationState((prev) => ({ ...prev, progress }));
117
+ onProgressRef.current?.(progress);
118
+ }, []);
119
+
120
+ const setError = useCallback((error: string | null) => {
121
+ if (!isMountedRef.current) return;
122
+ setGenerationState((prev) => ({ ...prev, error, isGenerating: false }));
123
+ if (error) {
124
+ onErrorRef.current?.(error);
125
+ }
126
+ }, []);
127
+
128
+ const abort = useCallback(() => {
129
+ abortControllerRef.current?.abort();
130
+ if (isMountedRef.current) {
131
+ setGenerationState((prev) => ({
132
+ ...prev,
133
+ isGenerating: false,
134
+ error: "Generation aborted",
135
+ }));
136
+ }
137
+ }, []);
138
+
139
+ const handleGenerate = useCallback(
140
+ async (request: TRequest): Promise<TResult | null> => {
141
+ // Validate request
142
+ if (config.validate) {
143
+ const validationError = config.validate(request);
144
+ if (validationError) {
145
+ setError(validationError);
146
+ return null;
147
+ }
148
+ }
149
+
150
+ // Create new AbortController
151
+ abortControllerRef.current = new AbortController();
152
+
153
+ setGenerationState({
154
+ isGenerating: true,
155
+ progress: 0,
156
+ error: null,
157
+ });
158
+
159
+ try {
160
+ const result = await config.execute(
161
+ request,
162
+ abortControllerRef.current.signal
163
+ );
164
+
165
+ if (!isMountedRef.current || abortControllerRef.current.signal.aborted) {
166
+ return null;
167
+ }
168
+
169
+ setGenerationState((prev) => ({
170
+ ...prev,
171
+ isGenerating: false,
172
+ progress: 100
173
+ }));
174
+
175
+ onSuccessRef.current?.(result);
176
+ return result;
177
+ } catch (error) {
178
+ if (!isMountedRef.current || abortControllerRef.current.signal.aborted) {
179
+ return null;
180
+ }
181
+
182
+ const errorMessage = config.transformError
183
+ ? config.transformError(error)
184
+ : error instanceof Error
185
+ ? error.message
186
+ : String(error);
187
+
188
+ setError(errorMessage);
189
+ return null;
190
+ } finally {
191
+ abortControllerRef.current = null;
192
+ }
193
+ },
194
+ [config, setError]
195
+ );
196
+
197
+ return {
198
+ generationState,
199
+ handleGenerate,
200
+ setProgress,
201
+ setError,
202
+ abort,
203
+ };
204
+ };
205
+ }
206
+
207
+ /**
208
+ * Creates a generation hook with progress updates
209
+ * Useful when the generation API supports progress callbacks
210
+ */
211
+ export function createGenerationHookWithProgress<TRequest, TResult>(
212
+ config: GenerationHookConfig<TRequest, TResult> & {
213
+ /** Get progress stream or polling function */
214
+ subscribeToProgress?: (
215
+ request: TRequest,
216
+ onProgress: (progress: number) => void
217
+ ) => () => void;
218
+ }
219
+ ) {
220
+ const baseHook = createGenerationHook(config);
221
+
222
+ return function useGenerationWithProgress(
223
+ callbacks: GenerationCallbacks<TResult> = {}
224
+ ): GenerationHookReturn<TRequest, TResult> {
225
+ const hookResult = baseHook(callbacks);
226
+ const unsubscribeRef = useRef<(() => void) | null>(null);
227
+
228
+ const handleGenerateWithProgress = useCallback(
229
+ async (request: TRequest): Promise<TResult | null> => {
230
+ // Subscribe to progress if available
231
+ if (config.subscribeToProgress) {
232
+ unsubscribeRef.current = config.subscribeToProgress(
233
+ request,
234
+ hookResult.setProgress
235
+ );
236
+ }
237
+
238
+ try {
239
+ return await hookResult.handleGenerate(request);
240
+ } finally {
241
+ unsubscribeRef.current?.();
242
+ unsubscribeRef.current = null;
243
+ }
244
+ },
245
+ [hookResult, config.subscribeToProgress]
246
+ );
247
+
248
+ return {
249
+ ...hookResult,
250
+ handleGenerate: handleGenerateWithProgress,
251
+ };
252
+ };
253
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Hook Factories
3
+ * Generic hook creation utilities
4
+ */
5
+
6
+ export {
7
+ createFormStateHook,
8
+ type FormStateConfig,
9
+ type FormActions,
10
+ type FormStateHookReturn,
11
+ type FormStateHookOptions,
12
+ } from "./createFormStateHook";
13
+
14
+ export {
15
+ createGenerationHook,
16
+ createGenerationHookWithProgress,
17
+ type GenerationState as BaseGenerationState,
18
+ type GenerationCallbacks,
19
+ type GenerationHookConfig,
20
+ type GenerationHookReturn,
21
+ } from "./createGenerationHook";
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Shared Hooks
3
+ */
4
+
5
+ export * from "./factories";
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Shared Utilities, Components, and Hooks
3
+ * Centralized exports for all shared code
4
+ */
5
+
6
+ // Components
7
+ export * from "./components";
8
+
9
+ // Hooks
10
+ export * from "./hooks";
11
+
12
+ // Utils
13
+ export * from "./utils/date";
14
+ export * from "./utils/filters";
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Date Utilities
3
+ * Centralized date handling utilities
4
+ */
5
+
6
+ export {
7
+ normalizeDateToTimestamp,
8
+ normalizeToDate,
9
+ isValidDate,
10
+ compareDates,
11
+ } from "./normalization";
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Date Normalization Utilities
3
+ * Provides consistent date handling across the application
4
+ */
5
+
6
+ /**
7
+ * Normalizes a Date or timestamp to a timestamp number
8
+ * @param date - Date object or timestamp number
9
+ * @returns Timestamp in milliseconds
10
+ */
11
+ export function normalizeDateToTimestamp(date: Date | number | undefined): number {
12
+ if (date === undefined) return 0;
13
+ return date instanceof Date ? date.getTime() : date;
14
+ }
15
+
16
+ /**
17
+ * Normalizes a Date or timestamp to a Date object
18
+ * @param date - Date object or timestamp number
19
+ * @returns Date object
20
+ */
21
+ export function normalizeToDate(date: Date | number): Date {
22
+ return date instanceof Date ? date : new Date(date);
23
+ }
24
+
25
+ /**
26
+ * Checks if a value is a valid date
27
+ * @param value - Value to check
28
+ * @returns True if value is a valid Date or number timestamp
29
+ */
30
+ export function isValidDate(value: unknown): value is Date | number {
31
+ if (value instanceof Date) {
32
+ return !isNaN(value.getTime());
33
+ }
34
+ if (typeof value === "number") {
35
+ return !isNaN(value) && isFinite(value);
36
+ }
37
+ return false;
38
+ }
39
+
40
+ /**
41
+ * Compares two dates for sorting
42
+ * @param a - First date
43
+ * @param b - Second date
44
+ * @param order - Sort order ('asc' or 'desc')
45
+ * @returns Comparison result for Array.sort()
46
+ */
47
+ export function compareDates(
48
+ a: Date | number | undefined,
49
+ b: Date | number | undefined,
50
+ order: "asc" | "desc" = "asc"
51
+ ): number {
52
+ const aTime = normalizeDateToTimestamp(a);
53
+ const bTime = normalizeDateToTimestamp(b);
54
+
55
+ if (aTime === 0 && bTime === 0) return 0;
56
+ if (aTime === 0) return 1;
57
+ if (bTime === 0) return -1;
58
+
59
+ return order === "desc" ? bTime - aTime : aTime - bTime;
60
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Generic Filter Button Factory
3
+ * Creates filter button configurations from data
4
+ */
5
+
6
+ export interface FilterButtonConfig<T = string> {
7
+ id: T;
8
+ label: string;
9
+ icon: string;
10
+ active: boolean;
11
+ onPress: () => void;
12
+ }
13
+
14
+ export interface FilterItemInput<T = string> {
15
+ id: T;
16
+ label: string;
17
+ icon: string;
18
+ }
19
+
20
+ /**
21
+ * Creates filter buttons from configuration
22
+ * @param items - Array of filter items with id, label, and icon
23
+ * @param activeFilter - Currently active filter id
24
+ * @param onSelect - Callback when a filter is selected
25
+ * @returns Array of filter button configurations
26
+ */
27
+ export function createFilterButtons<T extends string = string>(
28
+ items: readonly FilterItemInput<T>[],
29
+ activeFilter: T,
30
+ onSelect: (filter: T) => void
31
+ ): FilterButtonConfig<T>[] {
32
+ return items.map((item) => ({
33
+ id: item.id,
34
+ label: item.label,
35
+ icon: item.icon,
36
+ active: activeFilter === item.id,
37
+ onPress: () => onSelect(item.id),
38
+ }));
39
+ }
40
+
41
+ /**
42
+ * Creates filter buttons from a record/map
43
+ * @param filterMap - Record of filter id to label and icon
44
+ * @param activeFilter - Currently active filter id
45
+ * @param onSelect - Callback when a filter is selected
46
+ * @returns Array of filter button configurations
47
+ */
48
+ export function createFilterButtonsFromMap<T extends string = string>(
49
+ filterMap: Record<T, { label: string; icon: string }>,
50
+ activeFilter: T,
51
+ onSelect: (filter: T) => void
52
+ ): FilterButtonConfig<T>[] {
53
+ return Object.entries(filterMap).map(([id, config]) => ({
54
+ id: id as T,
55
+ label: (config as { label: string; icon: string }).label,
56
+ icon: (config as { label: string; icon: string }).icon,
57
+ active: activeFilter === id,
58
+ onPress: () => onSelect(id as T),
59
+ }));
60
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Filter Utilities
3
+ * Centralized filter utilities
4
+ */
5
+
6
+ export {
7
+ createFilterButtons,
8
+ createFilterButtonsFromMap,
9
+ type FilterButtonConfig,
10
+ type FilterItemInput,
11
+ } from "./createFilterButtons";