@umituz/react-native-design-system 2.6.110 → 2.6.112
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 +7 -3
- package/src/atoms/image/AtomicImage.tsx +29 -0
- package/src/atoms/index.ts +3 -0
- package/src/exports/image.ts +7 -0
- package/src/exports/infinite-scroll.ts +7 -0
- package/src/exports/uuid.ts +7 -0
- package/src/image/domain/entities/EditorTypes.ts +23 -0
- package/src/image/domain/entities/ImageConstants.ts +38 -0
- package/src/image/domain/entities/ImageFilterTypes.ts +70 -0
- package/src/image/domain/entities/ImageTemplateTypes.ts +18 -0
- package/src/image/domain/entities/ImageTypes.ts +86 -0
- package/src/image/domain/entities/ValidationResult.ts +15 -0
- package/src/image/domain/entities/editor/EditorConfigTypes.ts +35 -0
- package/src/image/domain/entities/editor/EditorElementTypes.ts +60 -0
- package/src/image/domain/entities/editor/EditorFilterTypes.ts +9 -0
- package/src/image/domain/entities/editor/EditorLayerTypes.ts +34 -0
- package/src/image/domain/entities/editor/EditorStateTypes.ts +35 -0
- package/src/image/domain/entities/editor/EditorToolTypes.ts +33 -0
- package/src/image/domain/utils/ImageUtils.ts +103 -0
- package/src/image/index.ts +123 -0
- package/src/image/infrastructure/services/ImageBatchService.ts +110 -0
- package/src/image/infrastructure/services/ImageConversionService.ts +74 -0
- package/src/image/infrastructure/services/ImageEditorService.ts +136 -0
- package/src/image/infrastructure/services/ImageEnhanceService.ts +123 -0
- package/src/image/infrastructure/services/ImageMetadataService.ts +116 -0
- package/src/image/infrastructure/services/ImageStorageService.ts +37 -0
- package/src/image/infrastructure/services/ImageTemplateService.ts +66 -0
- package/src/image/infrastructure/services/ImageTransformService.ts +89 -0
- package/src/image/infrastructure/services/ImageViewerService.ts +64 -0
- package/src/image/infrastructure/utils/BatchProcessor.ts +95 -0
- package/src/image/infrastructure/utils/FilterProcessor.ts +124 -0
- package/src/image/infrastructure/utils/ImageAnalysisUtils.ts +122 -0
- package/src/image/infrastructure/utils/ImageEditorHistoryUtils.ts +63 -0
- package/src/image/infrastructure/utils/ImageErrorHandler.ts +40 -0
- package/src/image/infrastructure/utils/ImageFilterUtils.ts +21 -0
- package/src/image/infrastructure/utils/ImageQualityPresets.ts +110 -0
- package/src/image/infrastructure/utils/ImageTransformUtils.ts +25 -0
- package/src/image/infrastructure/utils/ImageValidator.ts +59 -0
- package/src/image/infrastructure/utils/LayerManager.ts +77 -0
- package/src/image/infrastructure/utils/MetadataExtractor.ts +83 -0
- package/src/image/infrastructure/utils/filters/BasicFilters.ts +61 -0
- package/src/image/infrastructure/utils/filters/FilterHelpers.ts +21 -0
- package/src/image/infrastructure/utils/filters/SpecialFilters.ts +84 -0
- package/src/image/infrastructure/utils/validation/image-validator.ts +77 -0
- package/src/image/infrastructure/utils/validation/mime-type-validator.ts +101 -0
- package/src/image/infrastructure/utils/validation/mime-types.constants.ts +41 -0
- package/src/image/presentation/components/GalleryHeader.tsx +126 -0
- package/src/image/presentation/components/ImageGallery.tsx +138 -0
- package/src/image/presentation/components/editor/FilterPickerSheet.tsx +75 -0
- package/src/image/presentation/components/editor/StickerPickerSheet.tsx +62 -0
- package/src/image/presentation/components/editor/TextEditorSheet.tsx +98 -0
- package/src/image/presentation/components/editor/TextEditorTabs.tsx +111 -0
- package/src/image/presentation/components/image/AtomicImage.tsx +29 -0
- package/src/image/presentation/hooks/useImage.ts +39 -0
- package/src/image/presentation/hooks/useImageBatch.ts +28 -0
- package/src/image/presentation/hooks/useImageConversion.ts +29 -0
- package/src/image/presentation/hooks/useImageEnhance.ts +32 -0
- package/src/image/presentation/hooks/useImageGallery.ts +90 -0
- package/src/image/presentation/hooks/useImageMetadata.ts +28 -0
- package/src/image/presentation/hooks/useImageOperation.ts +37 -0
- package/src/image/presentation/hooks/useImageTransform.ts +42 -0
- package/src/index.ts +15 -0
- package/src/infinite-scroll/domain/interfaces/infinite-scroll-list-props.ts +67 -0
- package/src/infinite-scroll/domain/types/infinite-scroll-config.ts +108 -0
- package/src/infinite-scroll/domain/types/infinite-scroll-return.ts +40 -0
- package/src/infinite-scroll/domain/types/infinite-scroll-state.ts +58 -0
- package/src/infinite-scroll/domain/utils/pagination-utils.ts +63 -0
- package/src/infinite-scroll/domain/utils/type-guards.ts +53 -0
- package/src/infinite-scroll/index.ts +62 -0
- package/src/infinite-scroll/presentation/components/empty.tsx +44 -0
- package/src/infinite-scroll/presentation/components/error.tsx +66 -0
- package/src/infinite-scroll/presentation/components/infinite-scroll-list.tsx +120 -0
- package/src/infinite-scroll/presentation/components/loading-more.tsx +38 -0
- package/src/infinite-scroll/presentation/components/loading.tsx +40 -0
- package/src/infinite-scroll/presentation/components/types.ts +124 -0
- package/src/infinite-scroll/presentation/hooks/pagination.helper.ts +83 -0
- package/src/infinite-scroll/presentation/hooks/useInfiniteScroll.ts +327 -0
- package/src/uuid/index.ts +15 -0
- package/src/uuid/infrastructure/utils/UUIDUtils.ts +75 -0
- package/src/uuid/package-lock.json +14255 -0
- package/src/uuid/types/UUID.ts +36 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Infinite Scroll Return Types
|
|
3
|
+
*
|
|
4
|
+
* Domain types for hook return values
|
|
5
|
+
* Follows SOLID, DRY, KISS principles
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { InfiniteScrollState } from "./infinite-scroll-state";
|
|
9
|
+
|
|
10
|
+
export interface UseInfiniteScrollReturn<T> {
|
|
11
|
+
/**
|
|
12
|
+
* All loaded items (flattened)
|
|
13
|
+
*/
|
|
14
|
+
items: T[];
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Current state
|
|
18
|
+
*/
|
|
19
|
+
state: InfiniteScrollState<T>;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Load next page of items
|
|
23
|
+
*/
|
|
24
|
+
loadMore: () => Promise<void>;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Refresh all data (resets to page 0)
|
|
28
|
+
*/
|
|
29
|
+
refresh: () => Promise<void>;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Reset to initial state
|
|
33
|
+
*/
|
|
34
|
+
reset: () => void;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Check if can load more
|
|
38
|
+
*/
|
|
39
|
+
canLoadMore: boolean;
|
|
40
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Infinite Scroll State Types
|
|
3
|
+
*
|
|
4
|
+
* Domain types for infinite scroll state management
|
|
5
|
+
* Follows SOLID, DRY, KISS principles
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface InfiniteScrollState<T> {
|
|
9
|
+
/**
|
|
10
|
+
* All loaded items (flattened from pages)
|
|
11
|
+
*/
|
|
12
|
+
items: T[];
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* All pages of items (used in page-based mode)
|
|
16
|
+
*/
|
|
17
|
+
pages: T[][];
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Current page number (0-indexed, page-based mode)
|
|
21
|
+
*/
|
|
22
|
+
currentPage: number;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Current cursor (cursor-based mode)
|
|
26
|
+
*/
|
|
27
|
+
cursor: string | null;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Whether more items are available
|
|
31
|
+
*/
|
|
32
|
+
hasMore: boolean;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Whether currently loading initial data
|
|
36
|
+
*/
|
|
37
|
+
isLoading: boolean;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Whether currently loading more items
|
|
41
|
+
*/
|
|
42
|
+
isLoadingMore: boolean;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Whether currently refreshing
|
|
46
|
+
*/
|
|
47
|
+
isRefreshing: boolean;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Error message if any
|
|
51
|
+
*/
|
|
52
|
+
error: string | null;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Total number of items (if known)
|
|
56
|
+
*/
|
|
57
|
+
totalItems?: number;
|
|
58
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pagination Utilities
|
|
3
|
+
*
|
|
4
|
+
* Pure functions for pagination calculations
|
|
5
|
+
* Follows SOLID, DRY, KISS principles
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Calculate onEndReachedThreshold from threshold value
|
|
10
|
+
* Converts threshold (number of items) to percentage (0-1)
|
|
11
|
+
*
|
|
12
|
+
* @param threshold - Number of items from bottom to trigger load
|
|
13
|
+
* @param defaultThreshold - Default threshold if not provided (default: 0.1 = 10%)
|
|
14
|
+
* @returns Threshold value between 0.01 and 1.0
|
|
15
|
+
*/
|
|
16
|
+
export function calculateEndReachedThreshold(
|
|
17
|
+
threshold?: number,
|
|
18
|
+
defaultThreshold = 0.1,
|
|
19
|
+
): number {
|
|
20
|
+
if (!threshold) {
|
|
21
|
+
return defaultThreshold;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Convert threshold to percentage (0-1 range)
|
|
25
|
+
// Ensure minimum 0.01 (1%) and maximum 1.0 (100%)
|
|
26
|
+
const calculated = threshold / 100;
|
|
27
|
+
return Math.max(0.01, Math.min(1.0, calculated));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Calculate pagination slice for client-side pagination
|
|
32
|
+
*
|
|
33
|
+
* @param items - All items to paginate
|
|
34
|
+
* @param page - Page number (0-indexed)
|
|
35
|
+
* @param pageSize - Number of items per page
|
|
36
|
+
* @returns Slice of items for the requested page
|
|
37
|
+
*/
|
|
38
|
+
export function getPageSlice<T>(
|
|
39
|
+
items: T[],
|
|
40
|
+
page: number,
|
|
41
|
+
pageSize: number,
|
|
42
|
+
): T[] {
|
|
43
|
+
const start = page * pageSize;
|
|
44
|
+
const end = start + pageSize;
|
|
45
|
+
return items.slice(start, end);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Check if there are more items to load
|
|
50
|
+
*
|
|
51
|
+
* @param lastPage - Last fetched page
|
|
52
|
+
* @param allPages - All fetched pages
|
|
53
|
+
* @param pageSize - Page size
|
|
54
|
+
* @returns True if there are more items to load
|
|
55
|
+
*/
|
|
56
|
+
export function hasMoreItems<T>(
|
|
57
|
+
lastPage: T[],
|
|
58
|
+
allPages: T[][],
|
|
59
|
+
pageSize: number,
|
|
60
|
+
): boolean {
|
|
61
|
+
// If last page has fewer items than pageSize, we've reached the end
|
|
62
|
+
return lastPage.length >= pageSize;
|
|
63
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type Guard Utilities
|
|
3
|
+
*
|
|
4
|
+
* Runtime type checking for better type safety
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { InfiniteScrollConfig, PageBasedConfig, CursorBasedConfig } from "../types/infinite-scroll-config";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Check if config is page-based pagination
|
|
11
|
+
*/
|
|
12
|
+
export function isPageBasedConfig<T>(
|
|
13
|
+
config: InfiniteScrollConfig<T>,
|
|
14
|
+
): config is PageBasedConfig<T> {
|
|
15
|
+
return "fetchData" in config && typeof config.fetchData === "function";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Check if config is cursor-based pagination
|
|
20
|
+
*/
|
|
21
|
+
export function isCursorBasedConfig<T>(
|
|
22
|
+
config: InfiniteScrollConfig<T>,
|
|
23
|
+
): config is CursorBasedConfig<T> {
|
|
24
|
+
return "paginationMode" in config && config.paginationMode === "cursor";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Check if value is a valid error
|
|
29
|
+
*/
|
|
30
|
+
export function isError(value: unknown): value is Error {
|
|
31
|
+
return value instanceof Error;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Check if value is a non-null object
|
|
36
|
+
*/
|
|
37
|
+
export function isNonNullObject<T>(value: T | null | undefined): value is T {
|
|
38
|
+
return value !== null && value !== undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Check if array has items
|
|
43
|
+
*/
|
|
44
|
+
export function hasItems<T>(items: T[] | null | undefined): items is T[] {
|
|
45
|
+
return Array.isArray(items) && items.length > 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Check if string is not empty
|
|
50
|
+
*/
|
|
51
|
+
export function isNonEmptyString(value: string | null | undefined): value is string {
|
|
52
|
+
return typeof value === "string" && value.length > 0;
|
|
53
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React Native Infinite Scroll
|
|
3
|
+
*
|
|
4
|
+
* Modern infinite scroll system for React Native
|
|
5
|
+
* Follows SOLID, DRY, KISS principles
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Page-based and cursor-based pagination
|
|
9
|
+
* - Automatic retry with exponential backoff
|
|
10
|
+
* - Request cancellation with AbortController
|
|
11
|
+
* - Performance monitoring in __DEV__ mode
|
|
12
|
+
* - Full accessibility support
|
|
13
|
+
* - Type-safe utilities
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
// Domain Layer
|
|
17
|
+
export type {
|
|
18
|
+
InfiniteScrollConfig,
|
|
19
|
+
PaginatedResult,
|
|
20
|
+
PageBasedConfig,
|
|
21
|
+
CursorBasedConfig,
|
|
22
|
+
} from "./domain/types/infinite-scroll-config";
|
|
23
|
+
export type { InfiniteScrollState } from "./domain/types/infinite-scroll-state";
|
|
24
|
+
export type { UseInfiniteScrollReturn } from "./domain/types/infinite-scroll-return";
|
|
25
|
+
export type { InfiniteScrollListProps } from "./domain/interfaces/infinite-scroll-list-props";
|
|
26
|
+
|
|
27
|
+
// Domain Utils
|
|
28
|
+
export {
|
|
29
|
+
calculateEndReachedThreshold,
|
|
30
|
+
getPageSlice,
|
|
31
|
+
hasMoreItems,
|
|
32
|
+
} from "./domain/utils/pagination-utils";
|
|
33
|
+
|
|
34
|
+
// Type Guards
|
|
35
|
+
export {
|
|
36
|
+
isPageBasedConfig,
|
|
37
|
+
isCursorBasedConfig,
|
|
38
|
+
isError,
|
|
39
|
+
isNonNullObject,
|
|
40
|
+
hasItems,
|
|
41
|
+
isNonEmptyString,
|
|
42
|
+
} from "./domain/utils/type-guards";
|
|
43
|
+
|
|
44
|
+
// Presentation Layer - Hooks
|
|
45
|
+
export { useInfiniteScroll } from "./presentation/hooks/useInfiniteScroll";
|
|
46
|
+
|
|
47
|
+
// Presentation Layer - Components
|
|
48
|
+
export { InfiniteScrollList } from "./presentation/components/infinite-scroll-list";
|
|
49
|
+
|
|
50
|
+
// Component Types
|
|
51
|
+
export type {
|
|
52
|
+
EmptyProps,
|
|
53
|
+
ErrorProps,
|
|
54
|
+
LoadingProps,
|
|
55
|
+
LoadingMoreProps,
|
|
56
|
+
} from "./presentation/components/types";
|
|
57
|
+
|
|
58
|
+
// State Components
|
|
59
|
+
export { Loading } from "./presentation/components/loading";
|
|
60
|
+
export { LoadingMore } from "./presentation/components/loading-more";
|
|
61
|
+
export { Empty } from "./presentation/components/empty";
|
|
62
|
+
export { Error } from "./presentation/components/error";
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Empty Component
|
|
3
|
+
*
|
|
4
|
+
* Presentation component for empty state
|
|
5
|
+
* Fully customizable via props
|
|
6
|
+
* Accessibility support included
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import React from "react";
|
|
10
|
+
import { View, Text, StyleSheet } from "react-native";
|
|
11
|
+
import type { EmptyProps } from "./types";
|
|
12
|
+
|
|
13
|
+
export const Empty = React.memo<EmptyProps>(({
|
|
14
|
+
text = "No items found",
|
|
15
|
+
containerStyle,
|
|
16
|
+
textStyle,
|
|
17
|
+
accessibilityLabel = "Empty list",
|
|
18
|
+
...accessibilityProps
|
|
19
|
+
}) => (
|
|
20
|
+
<View
|
|
21
|
+
style={[styles.container, containerStyle]}
|
|
22
|
+
accessibilityLabel={accessibilityLabel}
|
|
23
|
+
accessibilityRole="text"
|
|
24
|
+
accessibilityValue={{ text }}
|
|
25
|
+
{...accessibilityProps}
|
|
26
|
+
>
|
|
27
|
+
<Text style={[styles.text, textStyle]}>{text}</Text>
|
|
28
|
+
</View>
|
|
29
|
+
));
|
|
30
|
+
|
|
31
|
+
Empty.displayName = "Empty";
|
|
32
|
+
|
|
33
|
+
const styles = StyleSheet.create({
|
|
34
|
+
container: {
|
|
35
|
+
flex: 1,
|
|
36
|
+
justifyContent: "center",
|
|
37
|
+
alignItems: "center",
|
|
38
|
+
padding: 20,
|
|
39
|
+
},
|
|
40
|
+
text: {
|
|
41
|
+
fontSize: 16,
|
|
42
|
+
textAlign: "center",
|
|
43
|
+
},
|
|
44
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Component
|
|
3
|
+
*
|
|
4
|
+
* Presentation component for error state
|
|
5
|
+
* Fully customizable via props
|
|
6
|
+
* Accessibility support included
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import React from "react";
|
|
10
|
+
import { View, Text, StyleSheet, TouchableOpacity } from "react-native";
|
|
11
|
+
import type { ErrorProps } from "./types";
|
|
12
|
+
|
|
13
|
+
export const Error = React.memo<ErrorProps>(({
|
|
14
|
+
error,
|
|
15
|
+
onRetry,
|
|
16
|
+
retryText = "Tap to retry",
|
|
17
|
+
containerStyle,
|
|
18
|
+
errorTextStyle,
|
|
19
|
+
retryTextStyle,
|
|
20
|
+
accessibilityLabel,
|
|
21
|
+
retryAccessibilityHint = "Double tap to retry",
|
|
22
|
+
...accessibilityProps
|
|
23
|
+
}) => (
|
|
24
|
+
<View
|
|
25
|
+
style={[styles.container, containerStyle]}
|
|
26
|
+
accessibilityLabel={accessibilityLabel || `Error: ${error}`}
|
|
27
|
+
accessibilityRole="alert"
|
|
28
|
+
{...accessibilityProps}
|
|
29
|
+
>
|
|
30
|
+
<Text
|
|
31
|
+
style={[styles.errorText, errorTextStyle]}
|
|
32
|
+
accessibilityRole="text"
|
|
33
|
+
>
|
|
34
|
+
{error}
|
|
35
|
+
</Text>
|
|
36
|
+
<TouchableOpacity
|
|
37
|
+
onPress={onRetry}
|
|
38
|
+
accessibilityRole="button"
|
|
39
|
+
accessibilityLabel={retryText}
|
|
40
|
+
accessibilityHint={retryAccessibilityHint}
|
|
41
|
+
>
|
|
42
|
+
<Text style={[styles.retryText, retryTextStyle]}>{retryText}</Text>
|
|
43
|
+
</TouchableOpacity>
|
|
44
|
+
</View>
|
|
45
|
+
));
|
|
46
|
+
|
|
47
|
+
Error.displayName = "Error";
|
|
48
|
+
|
|
49
|
+
const styles = StyleSheet.create({
|
|
50
|
+
container: {
|
|
51
|
+
flex: 1,
|
|
52
|
+
justifyContent: "center",
|
|
53
|
+
alignItems: "center",
|
|
54
|
+
padding: 20,
|
|
55
|
+
},
|
|
56
|
+
errorText: {
|
|
57
|
+
fontSize: 16,
|
|
58
|
+
textAlign: "center",
|
|
59
|
+
marginBottom: 8,
|
|
60
|
+
},
|
|
61
|
+
retryText: {
|
|
62
|
+
fontSize: 14,
|
|
63
|
+
textAlign: "center",
|
|
64
|
+
marginTop: 8,
|
|
65
|
+
},
|
|
66
|
+
});
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* InfiniteScrollList Component
|
|
3
|
+
*
|
|
4
|
+
* Presentation component for infinite scroll list
|
|
5
|
+
* Optimized with React.memo for performance
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React from "react";
|
|
9
|
+
import { FlatList } from "react-native";
|
|
10
|
+
import { useInfiniteScroll } from "../hooks/useInfiniteScroll";
|
|
11
|
+
import { calculateEndReachedThreshold } from "../../domain/utils/pagination-utils";
|
|
12
|
+
import type { InfiniteScrollListProps } from "../../domain/interfaces/infinite-scroll-list-props";
|
|
13
|
+
import { Loading } from "./loading";
|
|
14
|
+
import { LoadingMore } from "./loading-more";
|
|
15
|
+
import { Empty } from "./empty";
|
|
16
|
+
import { Error } from "./error";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Render error component
|
|
20
|
+
*/
|
|
21
|
+
function renderErrorComponent(
|
|
22
|
+
error: string,
|
|
23
|
+
retry: () => void,
|
|
24
|
+
errorComponent?: (error: string, retry: () => void) => React.ReactElement,
|
|
25
|
+
): React.ReactElement {
|
|
26
|
+
if (errorComponent) {
|
|
27
|
+
return errorComponent(error, retry);
|
|
28
|
+
}
|
|
29
|
+
return <Error error={error} onRetry={retry} />;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* InfiniteScrollList Component
|
|
34
|
+
*
|
|
35
|
+
* FlatList wrapper with automatic infinite scroll and pagination
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```tsx
|
|
39
|
+
* <InfiniteScrollList
|
|
40
|
+
* config={{
|
|
41
|
+
* pageSize: 20,
|
|
42
|
+
* threshold: 5,
|
|
43
|
+
* fetchData: async (page, pageSize) => {
|
|
44
|
+
* const response = await api.getItems({ page, limit: pageSize });
|
|
45
|
+
* return response.data;
|
|
46
|
+
* },
|
|
47
|
+
* }}
|
|
48
|
+
* renderItem={(item) => <ItemCard item={item} />}
|
|
49
|
+
* />
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
function InfiniteScrollListComponent<T>({
|
|
53
|
+
config,
|
|
54
|
+
renderItem,
|
|
55
|
+
loadingComponent,
|
|
56
|
+
loadingMoreComponent,
|
|
57
|
+
emptyComponent,
|
|
58
|
+
errorComponent,
|
|
59
|
+
ListHeaderComponent,
|
|
60
|
+
ListFooterComponent,
|
|
61
|
+
flatListProps,
|
|
62
|
+
}: InfiniteScrollListProps<T>): React.ReactElement {
|
|
63
|
+
const { items, state, loadMore, refresh, canLoadMore } = useInfiniteScroll(config);
|
|
64
|
+
|
|
65
|
+
const handleEndReached = React.useCallback(() => {
|
|
66
|
+
if (canLoadMore && config.autoLoad !== false) {
|
|
67
|
+
loadMore();
|
|
68
|
+
}
|
|
69
|
+
}, [canLoadMore, loadMore, config.autoLoad]);
|
|
70
|
+
|
|
71
|
+
const getItemKey = React.useCallback(
|
|
72
|
+
(item: T, index: number): string => {
|
|
73
|
+
if (config.getItemKey) {
|
|
74
|
+
return config.getItemKey(item, index);
|
|
75
|
+
}
|
|
76
|
+
return `item-${index}`;
|
|
77
|
+
},
|
|
78
|
+
[config],
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
// Loading state
|
|
82
|
+
if (state.isLoading) {
|
|
83
|
+
return loadingComponent || <Loading />;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Error state
|
|
87
|
+
if (state.error) {
|
|
88
|
+
return renderErrorComponent(state.error, refresh, errorComponent);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Empty state
|
|
92
|
+
if (items.length === 0 && !state.isLoading) {
|
|
93
|
+
return emptyComponent || <Empty />;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Render list
|
|
97
|
+
return (
|
|
98
|
+
<FlatList
|
|
99
|
+
data={items}
|
|
100
|
+
renderItem={({ item, index }) => renderItem(item, index)}
|
|
101
|
+
keyExtractor={(item, index) => getItemKey(item, index)}
|
|
102
|
+
onEndReached={handleEndReached}
|
|
103
|
+
onEndReachedThreshold={calculateEndReachedThreshold(config.threshold)}
|
|
104
|
+
onRefresh={refresh}
|
|
105
|
+
refreshing={state.isRefreshing}
|
|
106
|
+
ListHeaderComponent={ListHeaderComponent}
|
|
107
|
+
ListFooterComponent={
|
|
108
|
+
<>
|
|
109
|
+
{ListFooterComponent}
|
|
110
|
+
{state.isLoadingMore && (loadingMoreComponent || <LoadingMore />)}
|
|
111
|
+
</>
|
|
112
|
+
}
|
|
113
|
+
{...flatListProps}
|
|
114
|
+
/>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export const InfiniteScrollList = React.memo(InfiniteScrollListComponent) as <T>(
|
|
119
|
+
props: InfiniteScrollListProps<T>,
|
|
120
|
+
) => React.ReactElement;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loading More Component
|
|
3
|
+
*
|
|
4
|
+
* Presentation component for loading more state
|
|
5
|
+
* Fully customizable via props
|
|
6
|
+
* Accessibility support included
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import React from "react";
|
|
10
|
+
import { View, ActivityIndicator, StyleSheet } from "react-native";
|
|
11
|
+
import type { LoadingMoreProps } from "./types";
|
|
12
|
+
|
|
13
|
+
export const LoadingMore = React.memo<LoadingMoreProps>(({
|
|
14
|
+
containerStyle,
|
|
15
|
+
size = "small",
|
|
16
|
+
color,
|
|
17
|
+
accessibilityLabel = "Loading more",
|
|
18
|
+
...accessibilityProps
|
|
19
|
+
}) => (
|
|
20
|
+
<View
|
|
21
|
+
style={[styles.container, containerStyle]}
|
|
22
|
+
accessibilityLabel={accessibilityLabel}
|
|
23
|
+
accessibilityRole="progressbar"
|
|
24
|
+
accessibilityState={{ busy: true }}
|
|
25
|
+
{...accessibilityProps}
|
|
26
|
+
>
|
|
27
|
+
<ActivityIndicator size={size} color={color} />
|
|
28
|
+
</View>
|
|
29
|
+
));
|
|
30
|
+
|
|
31
|
+
LoadingMore.displayName = "LoadingMore";
|
|
32
|
+
|
|
33
|
+
const styles = StyleSheet.create({
|
|
34
|
+
container: {
|
|
35
|
+
padding: 16,
|
|
36
|
+
alignItems: "center",
|
|
37
|
+
},
|
|
38
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loading Component
|
|
3
|
+
*
|
|
4
|
+
* Presentation component for loading state
|
|
5
|
+
* Fully customizable via props
|
|
6
|
+
* Accessibility support included
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import React from "react";
|
|
10
|
+
import { View, ActivityIndicator, StyleSheet } from "react-native";
|
|
11
|
+
import type { LoadingProps } from "./types";
|
|
12
|
+
|
|
13
|
+
export const Loading = React.memo<LoadingProps>(({
|
|
14
|
+
containerStyle,
|
|
15
|
+
size = "large",
|
|
16
|
+
color,
|
|
17
|
+
accessibilityLabel = "Loading",
|
|
18
|
+
...accessibilityProps
|
|
19
|
+
}) => (
|
|
20
|
+
<View
|
|
21
|
+
style={[styles.container, containerStyle]}
|
|
22
|
+
accessibilityLabel={accessibilityLabel}
|
|
23
|
+
accessibilityRole="progressbar"
|
|
24
|
+
accessibilityState={{ busy: true }}
|
|
25
|
+
{...accessibilityProps}
|
|
26
|
+
>
|
|
27
|
+
<ActivityIndicator size={size} color={color} />
|
|
28
|
+
</View>
|
|
29
|
+
));
|
|
30
|
+
|
|
31
|
+
Loading.displayName = "Loading";
|
|
32
|
+
|
|
33
|
+
const styles = StyleSheet.create({
|
|
34
|
+
container: {
|
|
35
|
+
flex: 1,
|
|
36
|
+
justifyContent: "center",
|
|
37
|
+
alignItems: "center",
|
|
38
|
+
padding: 20,
|
|
39
|
+
},
|
|
40
|
+
});
|