@umituz/react-native-design-system 2.6.110 → 2.6.111

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.
@@ -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
+ });
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Component Types
3
+ *
4
+ * Shared types for presentation components
5
+ */
6
+
7
+ import type { TextStyle, ViewStyle, AccessibilityProps } from "react-native";
8
+
9
+ export interface EmptyProps extends AccessibilityProps {
10
+ /**
11
+ * Optional text to display
12
+ * Default: "No items found"
13
+ */
14
+ text?: string;
15
+
16
+ /**
17
+ * Optional accessibility label
18
+ * Default: "Empty list"
19
+ */
20
+ accessibilityLabel?: string;
21
+
22
+ /**
23
+ * Optional custom style for container
24
+ */
25
+ containerStyle?: ViewStyle;
26
+
27
+ /**
28
+ * Optional custom style for text
29
+ */
30
+ textStyle?: TextStyle;
31
+ }
32
+
33
+ export interface ErrorProps extends AccessibilityProps {
34
+ /**
35
+ * Error message to display
36
+ */
37
+ error: string;
38
+
39
+ /**
40
+ * Retry callback
41
+ */
42
+ onRetry: () => void;
43
+
44
+ /**
45
+ * Optional text for retry button
46
+ * Default: "Tap to retry"
47
+ */
48
+ retryText?: string;
49
+
50
+ /**
51
+ * Optional accessibility label for error
52
+ * Default: "Error: {error}"
53
+ */
54
+ accessibilityLabel?: string;
55
+
56
+ /**
57
+ * Optional accessibility hint for retry button
58
+ * Default: "Double tap to retry"
59
+ */
60
+ retryAccessibilityHint?: string;
61
+
62
+ /**
63
+ * Optional custom style for container
64
+ */
65
+ containerStyle?: ViewStyle;
66
+
67
+ /**
68
+ * Optional custom style for error text
69
+ */
70
+ errorTextStyle?: TextStyle;
71
+
72
+ /**
73
+ * Optional custom style for retry text
74
+ */
75
+ retryTextStyle?: TextStyle;
76
+ }
77
+
78
+ export interface LoadingProps extends AccessibilityProps {
79
+ /**
80
+ * Optional custom style for container
81
+ */
82
+ containerStyle?: ViewStyle;
83
+
84
+ /**
85
+ * Optional size for indicator
86
+ * Default: "large"
87
+ */
88
+ size?: "small" | "large" | number;
89
+
90
+ /**
91
+ * Optional color for indicator
92
+ */
93
+ color?: string;
94
+
95
+ /**
96
+ * Optional accessibility label
97
+ * Default: "Loading"
98
+ */
99
+ accessibilityLabel?: string;
100
+ }
101
+
102
+ export interface LoadingMoreProps extends AccessibilityProps {
103
+ /**
104
+ * Optional custom style for container
105
+ */
106
+ containerStyle?: ViewStyle;
107
+
108
+ /**
109
+ * Optional size for indicator
110
+ * Default: "small"
111
+ */
112
+ size?: "small" | "large" | number;
113
+
114
+ /**
115
+ * Optional color for indicator
116
+ */
117
+ color?: string;
118
+
119
+ /**
120
+ * Optional accessibility label
121
+ * Default: "Loading more"
122
+ */
123
+ accessibilityLabel?: string;
124
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Pagination Helper
3
+ * SOLID: Single Responsibility - Handle pagination operations
4
+ */
5
+
6
+ import type { InfiniteScrollConfig, PaginatedResult } from "../../domain/types/infinite-scroll-config";
7
+ import type { InfiniteScrollState } from "../../domain/types/infinite-scroll-state";
8
+
9
+ export function isCursorMode<T>(
10
+ config: InfiniteScrollConfig<T>,
11
+ ): config is Extract<InfiniteScrollConfig<T>, { paginationMode: "cursor" }> {
12
+ return "paginationMode" in config && config.paginationMode === "cursor";
13
+ }
14
+
15
+ export async function loadData<T>(
16
+ config: InfiniteScrollConfig<T>,
17
+ pageOrCursor: number | string | undefined,
18
+ pageSize: number,
19
+ totalItems?: number,
20
+ ): Promise<InfiniteScrollState<T>> {
21
+ if (isCursorMode(config)) {
22
+ const result = await config.fetchCursor(pageOrCursor as string | undefined, pageSize);
23
+ return {
24
+ items: result.items,
25
+ pages: [result.items],
26
+ currentPage: 0,
27
+ cursor: result.nextCursor,
28
+ hasMore: result.hasMore,
29
+ isLoading: false,
30
+ isLoadingMore: false,
31
+ isRefreshing: false,
32
+ error: null,
33
+ totalItems,
34
+ };
35
+ } else {
36
+ const data = await config.fetchData(pageOrCursor as number, pageSize);
37
+ const hasMore = data.length >= pageSize;
38
+ return {
39
+ items: data,
40
+ pages: [data],
41
+ currentPage: pageOrCursor as number,
42
+ cursor: null,
43
+ hasMore,
44
+ isLoading: false,
45
+ isLoadingMore: false,
46
+ isRefreshing: false,
47
+ error: null,
48
+ totalItems,
49
+ };
50
+ }
51
+ }
52
+
53
+ export async function loadMoreData<T>(
54
+ config: InfiniteScrollConfig<T>,
55
+ state: InfiniteScrollState<T>,
56
+ pageSize: number,
57
+ ): Promise<Partial<InfiniteScrollState<T>>> {
58
+ if (isCursorMode(config)) {
59
+ if (!state.cursor) throw new Error("No cursor available");
60
+ const result = await config.fetchCursor(state.cursor, pageSize);
61
+ return {
62
+ items: [...state.items, ...result.items],
63
+ pages: [...state.pages, result.items],
64
+ cursor: result.nextCursor,
65
+ hasMore: result.hasMore,
66
+ isLoadingMore: false,
67
+ error: null,
68
+ };
69
+ } else {
70
+ const nextPage = state.currentPage + 1;
71
+ const data = await config.fetchData(nextPage, pageSize);
72
+ const newPages = [...state.pages, data];
73
+ const hasMore = data.length >= pageSize;
74
+ return {
75
+ items: newPages.flat(),
76
+ pages: newPages,
77
+ currentPage: nextPage,
78
+ hasMore,
79
+ isLoadingMore: false,
80
+ error: null,
81
+ };
82
+ }
83
+ }