@umituz/react-native-ai-generation-content 1.17.21 → 1.17.22

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 (44) hide show
  1. package/package.json +1 -1
  2. package/src/domains/creations/domain/types/creation-filter.ts +1 -3
  3. package/src/domains/creations/domain/value-objects/CreationsConfig.ts +12 -0
  4. package/src/domains/creations/presentation/components/FilterSheets.tsx +63 -0
  5. package/src/domains/creations/presentation/components/GalleryHeader.tsx +95 -93
  6. package/src/domains/creations/presentation/components/index.ts +1 -0
  7. package/src/domains/creations/presentation/hooks/index.ts +3 -0
  8. package/src/domains/creations/presentation/hooks/useCreationsFilter.ts +35 -48
  9. package/src/domains/creations/presentation/hooks/useGalleryFilters.ts +78 -0
  10. package/src/domains/creations/presentation/hooks/useMediaFilter.ts +54 -0
  11. package/src/domains/creations/presentation/hooks/useStatusFilter.ts +54 -0
  12. package/src/domains/creations/presentation/screens/CreationsGalleryScreen.tsx +81 -156
  13. package/src/features/image-to-video/index.ts +90 -3
  14. package/src/features/image-to-video/presentation/components/AnimationStyleSelector.tsx +135 -0
  15. package/src/features/image-to-video/presentation/components/DurationSelector.tsx +110 -0
  16. package/src/features/image-to-video/presentation/components/GenerateButton.tsx +95 -0
  17. package/src/features/image-to-video/presentation/components/HeroSection.tsx +89 -0
  18. package/src/features/image-to-video/presentation/components/ImageSelectionGrid.tsx +234 -0
  19. package/src/features/image-to-video/presentation/components/MusicMoodSelector.tsx +181 -0
  20. package/src/features/image-to-video/presentation/components/index.ts +30 -0
  21. package/src/features/image-to-video/presentation/hooks/index.ts +22 -0
  22. package/src/features/image-to-video/presentation/hooks/useFormState.ts +116 -0
  23. package/src/features/image-to-video/presentation/hooks/useGeneration.ts +85 -0
  24. package/src/features/image-to-video/presentation/hooks/useImageToVideoForm.ts +93 -0
  25. package/src/features/image-to-video/presentation/index.ts +4 -0
  26. package/src/features/text-to-video/domain/types/callback.types.ts +50 -0
  27. package/src/features/text-to-video/domain/types/component.types.ts +106 -0
  28. package/src/features/text-to-video/domain/types/config.types.ts +61 -0
  29. package/src/features/text-to-video/domain/types/index.ts +48 -4
  30. package/src/features/text-to-video/domain/types/request.types.ts +36 -0
  31. package/src/features/text-to-video/domain/types/state.types.ts +53 -0
  32. package/src/features/text-to-video/index.ts +41 -3
  33. package/src/features/text-to-video/infrastructure/services/text-to-video-executor.ts +1 -1
  34. package/src/features/text-to-video/presentation/components/FrameSelector.tsx +153 -0
  35. package/src/features/text-to-video/presentation/components/GenerationTabs.tsx +73 -0
  36. package/src/features/text-to-video/presentation/components/HeroSection.tsx +67 -0
  37. package/src/features/text-to-video/presentation/components/HintCarousel.tsx +96 -0
  38. package/src/features/text-to-video/presentation/components/OptionsPanel.tsx +123 -0
  39. package/src/features/text-to-video/presentation/components/index.ts +10 -0
  40. package/src/features/text-to-video/presentation/hooks/index.ts +11 -0
  41. package/src/features/text-to-video/presentation/hooks/useTextToVideoFeature.ts +77 -20
  42. package/src/features/text-to-video/presentation/hooks/useTextToVideoForm.ts +134 -0
  43. package/src/features/text-to-video/presentation/index.ts +6 -0
  44. package/src/features/text-to-video/domain/types/text-to-video.types.ts +0 -65
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-ai-generation-content",
3
- "version": "1.17.21",
3
+ "version": "1.17.22",
4
4
  "description": "Provider-agnostic AI generation orchestration for React Native",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  import type { CreationTypeId, CreationStatus, CreationCategory } from "./creation-types";
7
+ import { IMAGE_CREATION_TYPES, VIDEO_CREATION_TYPES, VOICE_CREATION_TYPES } from "./creation-categories";
7
8
 
8
9
  /**
9
10
  * Filter options for querying creations
@@ -114,9 +115,6 @@ export function calculateCreationStats(
114
115
  }
115
116
 
116
117
  // Calculate category counts from type counts
117
- const { IMAGE_CREATION_TYPES, VIDEO_CREATION_TYPES, VOICE_CREATION_TYPES } =
118
- require("./creation-categories");
119
-
120
118
  for (const [typeId, count] of Object.entries(stats.byType)) {
121
119
  if (IMAGE_CREATION_TYPES.includes(typeId)) {
122
120
  stats.byCategory.image += count as number;
@@ -5,6 +5,7 @@
5
5
 
6
6
  import type { Creation, CreationDocument } from "../entities/Creation";
7
7
  import type { FilterCategory } from "@umituz/react-native-design-system";
8
+ import type { FilterOption } from "../types/creation-filter";
8
9
 
9
10
  export interface CreationType {
10
11
  readonly id: string;
@@ -23,6 +24,16 @@ export interface CreationsTranslations {
23
24
  readonly filterAll: string;
24
25
  readonly filterLabel: string;
25
26
  readonly filterTitle: string;
27
+ readonly statusFilterTitle?: string;
28
+ readonly mediaFilterTitle?: string;
29
+ readonly clearFilter?: string;
30
+ }
31
+
32
+ export interface CreationsFilterConfig {
33
+ readonly statusOptions?: FilterOption[];
34
+ readonly mediaOptions?: FilterOption[];
35
+ readonly showStatusFilter?: boolean;
36
+ readonly showMediaFilter?: boolean;
26
37
  }
27
38
 
28
39
  /**
@@ -35,6 +46,7 @@ export interface CreationsConfig {
35
46
  readonly collectionName: string;
36
47
  readonly types: readonly CreationType[];
37
48
  readonly filterCategories?: readonly FilterCategory[];
49
+ readonly filterConfig?: CreationsFilterConfig;
38
50
  readonly translations: CreationsTranslations;
39
51
  readonly showFilter?: boolean;
40
52
  readonly maxThumbnails?: number;
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Filter Sheets Components
3
+ * Modal-based filter sheets for status and media type filtering
4
+ */
5
+
6
+ import React from "react";
7
+ import { FilterSheet } from "@umituz/react-native-design-system";
8
+ import type { FilterOption } from "../../domain/types/creation-filter";
9
+
10
+ interface FilterSheetConfig {
11
+ readonly visible: boolean;
12
+ readonly onClose: () => void;
13
+ readonly options: FilterOption[];
14
+ readonly selectedId: string;
15
+ readonly onSelect: (id: string) => void;
16
+ readonly onClear: () => void;
17
+ readonly title: string;
18
+ readonly clearLabel?: string;
19
+ }
20
+
21
+ export const StatusFilterSheet: React.FC<FilterSheetConfig> = ({
22
+ visible,
23
+ onClose,
24
+ options,
25
+ selectedId,
26
+ onSelect,
27
+ onClear,
28
+ title,
29
+ clearLabel
30
+ }) => (
31
+ <FilterSheet
32
+ visible={visible}
33
+ onClose={onClose}
34
+ options={options}
35
+ selectedIds={[selectedId]}
36
+ onFilterPress={onSelect}
37
+ onClearFilters={onClear}
38
+ title={title}
39
+ clearLabel={clearLabel}
40
+ />
41
+ );
42
+
43
+ export const MediaFilterSheet: React.FC<FilterSheetConfig> = ({
44
+ visible,
45
+ onClose,
46
+ options,
47
+ selectedId,
48
+ onSelect,
49
+ onClear,
50
+ title,
51
+ clearLabel
52
+ }) => (
53
+ <FilterSheet
54
+ visible={visible}
55
+ onClose={onClose}
56
+ options={options}
57
+ selectedIds={[selectedId]}
58
+ onFilterPress={onSelect}
59
+ onClearFilters={onClear}
60
+ title={title}
61
+ clearLabel={clearLabel}
62
+ />
63
+ );
@@ -1,118 +1,120 @@
1
1
  declare const __DEV__: boolean;
2
2
 
3
- import React from 'react';
4
- import { View, TouchableOpacity, StyleSheet, type ViewStyle } from 'react-native';
3
+ import React from "react";
4
+ import { View, TouchableOpacity, StyleSheet, type ViewStyle } from "react-native";
5
5
  import { AtomicText, AtomicIcon, useAppDesignTokens, type DesignTokens } from "@umituz/react-native-design-system";
6
6
 
7
+ interface FilterButtonConfig {
8
+ readonly id: string;
9
+ readonly label: string;
10
+ readonly icon: string;
11
+ readonly isActive: boolean;
12
+ readonly onPress: () => void;
13
+ }
14
+
7
15
  interface GalleryHeaderProps {
8
- readonly title: string;
9
- readonly count: number;
10
- readonly countLabel: string;
11
- readonly isFiltered: boolean;
12
- readonly onFilterPress: () => void;
13
- readonly showFilter?: boolean;
14
- readonly filterLabel?: string;
15
- readonly filterIcon?: string;
16
- readonly style?: ViewStyle;
16
+ readonly title: string;
17
+ readonly count: number;
18
+ readonly countLabel: string;
19
+ readonly filterButtons?: FilterButtonConfig[];
20
+ readonly showFilter?: boolean;
21
+ readonly style?: ViewStyle;
17
22
  }
18
23
 
19
24
  export const GalleryHeader: React.FC<GalleryHeaderProps> = ({
20
- title,
21
- count,
22
- countLabel,
23
- isFiltered,
24
- onFilterPress,
25
- showFilter = true,
26
- filterLabel = 'Filter',
27
- filterIcon = 'filter-outline',
28
- style,
25
+ title,
26
+ count,
27
+ countLabel,
28
+ filterButtons = [],
29
+ showFilter = true,
30
+ style,
29
31
  }) => {
30
- const tokens = useAppDesignTokens();
31
- const styles = useStyles(tokens);
32
+ const tokens = useAppDesignTokens();
33
+ const styles = useStyles(tokens);
32
34
 
33
- return (
34
- <View style={[styles.headerArea, style]}>
35
- <View>
36
- <AtomicText style={styles.title}>{title}</AtomicText>
37
- <AtomicText style={styles.subtitle}>
38
- {count} {countLabel}
39
- </AtomicText>
40
- </View>
41
- {showFilter && (
42
- <TouchableOpacity
43
- onPress={() => {
44
- if (__DEV__) {
45
- // eslint-disable-next-line no-console
46
- console.log('[GalleryHeader] Filter button pressed');
47
- }
48
- onFilterPress();
49
- }}
50
- style={[styles.filterButton, isFiltered && styles.filterButtonActive]}
51
- activeOpacity={0.7}
52
- >
53
- <AtomicIcon
54
- name={filterIcon}
55
- size="sm"
56
- color={isFiltered ? "primary" : "secondary"}
57
- />
58
- <AtomicText style={[styles.filterText, { color: isFiltered ? tokens.colors.primary : tokens.colors.textSecondary }]}>
59
- {filterLabel}
60
- </AtomicText>
61
- {isFiltered && (
62
- <View style={styles.badge} />
63
- )}
64
- </TouchableOpacity>
65
- )}
35
+ return (
36
+ <View style={[styles.headerArea, style]}>
37
+ <View>
38
+ <AtomicText style={styles.title}>{title}</AtomicText>
39
+ <AtomicText style={styles.subtitle}>
40
+ {count} {countLabel}
41
+ </AtomicText>
42
+ </View>
43
+ {showFilter && filterButtons.length > 0 && (
44
+ <View style={styles.filterRow}>
45
+ {filterButtons.map((btn) => (
46
+ <TouchableOpacity
47
+ key={btn.id}
48
+ onPress={() => {
49
+ if (__DEV__) {
50
+ // eslint-disable-next-line no-console
51
+ console.log(`[GalleryHeader] ${btn.id} filter pressed`);
52
+ }
53
+ btn.onPress();
54
+ }}
55
+ style={[styles.filterButton, btn.isActive && styles.filterButtonActive]}
56
+ activeOpacity={0.7}
57
+ >
58
+ <AtomicIcon
59
+ name={btn.icon}
60
+ size="sm"
61
+ color={btn.isActive ? "primary" : "secondary"}
62
+ />
63
+ <AtomicText
64
+ style={[styles.filterText, { color: btn.isActive ? tokens.colors.primary : tokens.colors.textSecondary }]}
65
+ >
66
+ {btn.label}
67
+ </AtomicText>
68
+ </TouchableOpacity>
69
+ ))}
66
70
  </View>
67
- );
71
+ )}
72
+ </View>
73
+ );
68
74
  };
69
75
 
70
- const useStyles = (tokens: DesignTokens) => StyleSheet.create({
76
+ const useStyles = (tokens: DesignTokens) =>
77
+ StyleSheet.create({
71
78
  headerArea: {
72
- flexDirection: "row",
73
- alignItems: "center",
74
- justifyContent: 'space-between',
75
- paddingHorizontal: tokens.spacing.md,
76
- paddingVertical: tokens.spacing.sm,
77
- marginBottom: tokens.spacing.sm,
79
+ flexDirection: "row",
80
+ alignItems: "center",
81
+ justifyContent: "space-between",
82
+ paddingHorizontal: tokens.spacing.md,
83
+ paddingVertical: tokens.spacing.sm,
84
+ marginBottom: tokens.spacing.sm,
78
85
  },
79
86
  title: {
80
- fontSize: 20,
81
- fontWeight: "700",
82
- color: tokens.colors.textPrimary,
83
- marginBottom: 4,
87
+ fontSize: 20,
88
+ fontWeight: "700",
89
+ color: tokens.colors.textPrimary,
90
+ marginBottom: 4,
84
91
  },
85
92
  subtitle: {
86
- fontSize: 14,
87
- color: tokens.colors.textSecondary,
88
- opacity: 0.6
93
+ fontSize: 14,
94
+ color: tokens.colors.textSecondary,
95
+ opacity: 0.6,
96
+ },
97
+ filterRow: {
98
+ flexDirection: "row",
99
+ gap: tokens.spacing.xs,
89
100
  },
90
101
  filterButton: {
91
- flexDirection: 'row',
92
- alignItems: 'center',
93
- gap: tokens.spacing.xs,
94
- paddingVertical: tokens.spacing.xs,
95
- paddingHorizontal: tokens.spacing.md,
96
- borderRadius: 999,
97
- backgroundColor: tokens.colors.surfaceVariant,
98
- borderWidth: 1,
99
- borderColor: 'transparent',
102
+ flexDirection: "row",
103
+ alignItems: "center",
104
+ gap: tokens.spacing.xs,
105
+ paddingVertical: tokens.spacing.xs,
106
+ paddingHorizontal: tokens.spacing.sm,
107
+ borderRadius: 999,
108
+ backgroundColor: tokens.colors.surfaceVariant,
109
+ borderWidth: 1,
110
+ borderColor: "transparent",
100
111
  },
101
112
  filterButtonActive: {
102
- backgroundColor: tokens.colors.primary + "15",
103
- borderColor: tokens.colors.primary + "30",
113
+ backgroundColor: tokens.colors.primary + "15",
114
+ borderColor: tokens.colors.primary + "30",
104
115
  },
105
116
  filterText: {
106
- fontSize: 14,
107
- fontWeight: "500",
108
- },
109
- badge: {
110
- width: 8,
111
- height: 8,
112
- borderRadius: 4,
113
- backgroundColor: tokens.colors.primary,
114
- position: 'absolute',
115
- top: 6,
116
- right: 6,
117
+ fontSize: 13,
118
+ fontWeight: "500",
117
119
  },
118
- });
120
+ });
@@ -21,6 +21,7 @@ export {
21
21
  createStatusFilterButtons,
22
22
  type FilterButton,
23
23
  } from "./CreationsFilterBar";
24
+ export { StatusFilterSheet, MediaFilterSheet } from "./FilterSheets";
24
25
 
25
26
  // Gallery Components
26
27
  export { GalleryHeader } from "./GalleryHeader";
@@ -6,3 +6,6 @@ export { useCreations } from "./useCreations";
6
6
  export { useDeleteCreation } from "./useDeleteCreation";
7
7
  export { useCreationsFilter } from "./useCreationsFilter";
8
8
  export { useAdvancedFilter } from "./useAdvancedFilter";
9
+ export { useStatusFilter } from "./useStatusFilter";
10
+ export { useMediaFilter } from "./useMediaFilter";
11
+ export { useGalleryFilters } from "./useGalleryFilters";
@@ -1,77 +1,64 @@
1
1
  /**
2
2
  * useCreationsFilter Hook
3
- * Handles filtering of creations by type
3
+ * Combines status and media filters to filter creations
4
+ * SOLID: Combines filters, delegates individual filter logic to separate hooks
4
5
  */
5
6
 
6
- import { useState, useMemo, useCallback } from "react";
7
+ import { useMemo } from "react";
7
8
  import type { Creation } from "../../domain/entities/Creation";
9
+ import { getCategoryForType } from "../../domain/types/creation-categories";
8
10
 
9
11
  interface UseCreationsFilterProps {
10
12
  readonly creations: Creation[] | undefined;
11
- readonly defaultFilterId?: string;
13
+ readonly statusFilter?: string;
14
+ readonly mediaFilter?: string;
12
15
  }
13
16
 
14
- interface CreationWithTags extends Creation {
15
- readonly metadata?: {
16
- readonly tags?: string[];
17
- readonly [key: string]: unknown;
18
- };
17
+ interface UseCreationsFilterReturn {
18
+ readonly filtered: Creation[];
19
+ readonly isFiltered: boolean;
20
+ readonly activeFiltersCount: number;
19
21
  }
20
22
 
21
23
  export function useCreationsFilter({
22
24
  creations,
23
- defaultFilterId = "all"
24
- }: UseCreationsFilterProps) {
25
- const [selectedIds, setSelectedIds] = useState<string[]>([defaultFilterId]);
25
+ statusFilter = "all",
26
+ mediaFilter = "all"
27
+ }: UseCreationsFilterProps): UseCreationsFilterReturn {
26
28
 
27
29
  const filtered = useMemo(() => {
28
30
  if (!creations) return [];
29
- if (selectedIds.includes(defaultFilterId)) return creations;
30
-
31
- return creations.filter((c) => {
32
- const creation = c as CreationWithTags;
33
- return (
34
- selectedIds.includes(creation.type) ||
35
- selectedIds.some(id => creation.metadata?.tags?.includes(id))
36
- );
37
- });
38
- }, [creations, selectedIds, defaultFilterId]);
39
31
 
40
- const toggleFilter = useCallback((filterId: string, multiSelect = false) => {
41
- setSelectedIds(prev => {
42
- // If selecting 'all', clear everything else
43
- if (filterId === defaultFilterId) return [defaultFilterId];
32
+ return creations.filter((creation) => {
33
+ // Status filter
34
+ if (statusFilter !== "all" && creation.status !== statusFilter) {
35
+ return false;
36
+ }
44
37
 
45
- let newIds: string[];
46
- if (!multiSelect) {
47
- // Single select
48
- if (prev.includes(filterId) && prev.length === 1) return prev;
49
- newIds = [filterId];
50
- } else {
51
- // Multi select
52
- if (prev.includes(filterId)) {
53
- newIds = prev.filter(id => id !== filterId);
54
- } else {
55
- // Remove 'all' if present
56
- newIds = [...prev.filter(id => id !== defaultFilterId), filterId];
38
+ // Media filter
39
+ if (mediaFilter !== "all") {
40
+ const category = getCategoryForType(creation.type);
41
+ if (category !== mediaFilter) {
42
+ return false;
57
43
  }
58
44
  }
59
45
 
60
- // If nothing selected, revert to 'all'
61
- if (newIds.length === 0) return [defaultFilterId];
62
- return newIds;
46
+ return true;
63
47
  });
64
- }, [defaultFilterId]);
48
+ }, [creations, statusFilter, mediaFilter]);
49
+
50
+ const activeFiltersCount = useMemo(() => {
51
+ let count = 0;
52
+ if (statusFilter !== "all") count++;
53
+ if (mediaFilter !== "all") count++;
54
+ return count;
55
+ }, [statusFilter, mediaFilter]);
65
56
 
66
- const clearFilters = useCallback(() => {
67
- setSelectedIds([defaultFilterId]);
68
- }, [defaultFilterId]);
57
+ const isFiltered = statusFilter !== "all" || mediaFilter !== "all";
69
58
 
70
59
  return {
71
60
  filtered,
72
- selectedIds,
73
- toggleFilter,
74
- clearFilters,
75
- isFiltered: !selectedIds.includes(defaultFilterId),
61
+ isFiltered,
62
+ activeFiltersCount
76
63
  };
77
64
  }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * useGalleryFilters Hook
3
+ * Manages all filter state and modals for gallery screen
4
+ * SOLID: Coordinates filter hooks without business logic
5
+ */
6
+
7
+ import { useState, useCallback } from "react";
8
+ import type { Creation } from "../../domain/entities/Creation";
9
+ import type { FilterOption } from "../../domain/types/creation-filter";
10
+ import { useStatusFilter } from "./useStatusFilter";
11
+ import { useMediaFilter } from "./useMediaFilter";
12
+ import { useCreationsFilter } from "./useCreationsFilter";
13
+
14
+ interface UseGalleryFiltersProps {
15
+ readonly creations: Creation[] | undefined;
16
+ readonly statusOptions: FilterOption[];
17
+ readonly mediaOptions: FilterOption[];
18
+ readonly t: (key: string) => string;
19
+ }
20
+
21
+ interface UseGalleryFiltersReturn {
22
+ readonly filtered: Creation[];
23
+ readonly isFiltered: boolean;
24
+ readonly activeFiltersCount: number;
25
+ readonly statusFilterVisible: boolean;
26
+ readonly mediaFilterVisible: boolean;
27
+ readonly statusFilter: ReturnType<typeof useStatusFilter>;
28
+ readonly mediaFilter: ReturnType<typeof useMediaFilter>;
29
+ readonly openStatusFilter: () => void;
30
+ readonly closeStatusFilter: () => void;
31
+ readonly openMediaFilter: () => void;
32
+ readonly closeMediaFilter: () => void;
33
+ readonly clearAllFilters: () => void;
34
+ }
35
+
36
+ export function useGalleryFilters({
37
+ creations,
38
+ statusOptions,
39
+ mediaOptions,
40
+ t
41
+ }: UseGalleryFiltersProps): UseGalleryFiltersReturn {
42
+ const [statusFilterVisible, setStatusFilterVisible] = useState(false);
43
+ const [mediaFilterVisible, setMediaFilterVisible] = useState(false);
44
+
45
+ const statusFilter = useStatusFilter({ options: statusOptions, t });
46
+ const mediaFilter = useMediaFilter({ options: mediaOptions, t });
47
+
48
+ const { filtered, isFiltered, activeFiltersCount } = useCreationsFilter({
49
+ creations,
50
+ statusFilter: statusFilter.selectedId,
51
+ mediaFilter: mediaFilter.selectedId
52
+ });
53
+
54
+ const openStatusFilter = useCallback(() => setStatusFilterVisible(true), []);
55
+ const closeStatusFilter = useCallback(() => setStatusFilterVisible(false), []);
56
+ const openMediaFilter = useCallback(() => setMediaFilterVisible(true), []);
57
+ const closeMediaFilter = useCallback(() => setMediaFilterVisible(false), []);
58
+
59
+ const clearAllFilters = useCallback(() => {
60
+ statusFilter.clearFilter();
61
+ mediaFilter.clearFilter();
62
+ }, [statusFilter, mediaFilter]);
63
+
64
+ return {
65
+ filtered,
66
+ isFiltered,
67
+ activeFiltersCount,
68
+ statusFilterVisible,
69
+ mediaFilterVisible,
70
+ statusFilter,
71
+ mediaFilter,
72
+ openStatusFilter,
73
+ closeStatusFilter,
74
+ openMediaFilter,
75
+ closeMediaFilter,
76
+ clearAllFilters
77
+ };
78
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * useMediaFilter Hook
3
+ * Handles media type filtering (image, video, voice)
4
+ * SOLID: Single Responsibility - Only handles media filter state
5
+ */
6
+
7
+ import { useState, useCallback, useMemo } from "react";
8
+ import type { FilterOption } from "../../domain/types/creation-filter";
9
+
10
+ interface UseMediaFilterProps {
11
+ readonly options: FilterOption[];
12
+ readonly t: (key: string) => string;
13
+ readonly defaultId?: string;
14
+ }
15
+
16
+ interface UseMediaFilterReturn {
17
+ readonly selectedId: string;
18
+ readonly filterOptions: FilterOption[];
19
+ readonly hasActiveFilter: boolean;
20
+ readonly selectFilter: (id: string) => void;
21
+ readonly clearFilter: () => void;
22
+ }
23
+
24
+ export function useMediaFilter({
25
+ options,
26
+ t,
27
+ defaultId = "all"
28
+ }: UseMediaFilterProps): UseMediaFilterReturn {
29
+ const [selectedId, setSelectedId] = useState(defaultId);
30
+
31
+ const filterOptions = useMemo(() =>
32
+ options.map(opt => ({
33
+ ...opt,
34
+ label: opt.labelKey ? t(opt.labelKey) : opt.label
35
+ })),
36
+ [options, t]
37
+ );
38
+
39
+ const selectFilter = useCallback((id: string) => {
40
+ setSelectedId(id);
41
+ }, []);
42
+
43
+ const clearFilter = useCallback(() => {
44
+ setSelectedId(defaultId);
45
+ }, [defaultId]);
46
+
47
+ return {
48
+ selectedId,
49
+ filterOptions,
50
+ hasActiveFilter: selectedId !== defaultId,
51
+ selectFilter,
52
+ clearFilter
53
+ };
54
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * useStatusFilter Hook
3
+ * Handles status filtering (completed, pending, processing, failed)
4
+ * SOLID: Single Responsibility - Only handles status filter state
5
+ */
6
+
7
+ import { useState, useCallback, useMemo } from "react";
8
+ import type { FilterOption } from "../../domain/types/creation-filter";
9
+
10
+ interface UseStatusFilterProps {
11
+ readonly options: FilterOption[];
12
+ readonly t: (key: string) => string;
13
+ readonly defaultId?: string;
14
+ }
15
+
16
+ interface UseStatusFilterReturn {
17
+ readonly selectedId: string;
18
+ readonly filterOptions: FilterOption[];
19
+ readonly hasActiveFilter: boolean;
20
+ readonly selectFilter: (id: string) => void;
21
+ readonly clearFilter: () => void;
22
+ }
23
+
24
+ export function useStatusFilter({
25
+ options,
26
+ t,
27
+ defaultId = "all"
28
+ }: UseStatusFilterProps): UseStatusFilterReturn {
29
+ const [selectedId, setSelectedId] = useState(defaultId);
30
+
31
+ const filterOptions = useMemo(() =>
32
+ options.map(opt => ({
33
+ ...opt,
34
+ label: opt.labelKey ? t(opt.labelKey) : opt.label
35
+ })),
36
+ [options, t]
37
+ );
38
+
39
+ const selectFilter = useCallback((id: string) => {
40
+ setSelectedId(id);
41
+ }, []);
42
+
43
+ const clearFilter = useCallback(() => {
44
+ setSelectedId(defaultId);
45
+ }, [defaultId]);
46
+
47
+ return {
48
+ selectedId,
49
+ filterOptions,
50
+ hasActiveFilter: selectedId !== defaultId,
51
+ selectFilter,
52
+ clearFilter
53
+ };
54
+ }