@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.
- package/package.json +2 -2
- package/src/exports/infinite-scroll.ts +7 -0
- package/src/exports/uuid.ts +7 -0
- package/src/index.ts +10 -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
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-design-system",
|
|
3
|
-
"version": "2.6.
|
|
4
|
-
"description": "Universal design system for React Native apps - Consolidated package with atoms, molecules, organisms, theme, typography, responsive, safe area and
|
|
3
|
+
"version": "2.6.111",
|
|
4
|
+
"description": "Universal design system for React Native apps - Consolidated package with atoms, molecules, organisms, theme, typography, responsive, safe area, exception, infinite scroll and UUID utilities",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
7
7
|
"exports": {
|
package/src/index.ts
CHANGED
|
@@ -62,6 +62,16 @@ export * from './exports/safe-area';
|
|
|
62
62
|
// =============================================================================
|
|
63
63
|
export * from './exports/exception';
|
|
64
64
|
|
|
65
|
+
// =============================================================================
|
|
66
|
+
// INFINITE SCROLL EXPORTS
|
|
67
|
+
// =============================================================================
|
|
68
|
+
export * from './exports/infinite-scroll';
|
|
69
|
+
|
|
70
|
+
// =============================================================================
|
|
71
|
+
// UUID EXPORTS
|
|
72
|
+
// =============================================================================
|
|
73
|
+
export * from './exports/uuid';
|
|
74
|
+
|
|
65
75
|
// =============================================================================
|
|
66
76
|
// VARIANT UTILITIES
|
|
67
77
|
// =============================================================================
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Infinite Scroll List Props Interface
|
|
3
|
+
*
|
|
4
|
+
* Domain interface for component props
|
|
5
|
+
* Follows SOLID, DRY, KISS principles
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type React from "react";
|
|
9
|
+
import type { InfiniteScrollConfig } from "../types/infinite-scroll-config";
|
|
10
|
+
|
|
11
|
+
export interface InfiniteScrollListProps<T> {
|
|
12
|
+
/**
|
|
13
|
+
* Configuration for infinite scroll
|
|
14
|
+
*/
|
|
15
|
+
config: InfiniteScrollConfig<T>;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Render function for each item
|
|
19
|
+
*/
|
|
20
|
+
renderItem: (item: T, index: number) => React.ReactElement;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Optional: Custom loading component
|
|
24
|
+
*/
|
|
25
|
+
loadingComponent?: React.ReactElement;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Optional: Custom loading more component
|
|
29
|
+
*/
|
|
30
|
+
loadingMoreComponent?: React.ReactElement;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Optional: Custom empty component
|
|
34
|
+
*/
|
|
35
|
+
emptyComponent?: React.ReactElement;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Optional: Custom error component
|
|
39
|
+
*/
|
|
40
|
+
errorComponent?: (error: string, retry: () => void) => React.ReactElement;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Optional: List header component
|
|
44
|
+
*/
|
|
45
|
+
ListHeaderComponent?: React.ReactElement;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Optional: List footer component
|
|
49
|
+
*/
|
|
50
|
+
ListFooterComponent?: React.ReactElement;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Optional: Additional FlatList props
|
|
54
|
+
*/
|
|
55
|
+
flatListProps?: Omit<
|
|
56
|
+
React.ComponentProps<typeof import("react-native").FlatList<T>>,
|
|
57
|
+
| "data"
|
|
58
|
+
| "renderItem"
|
|
59
|
+
| "keyExtractor"
|
|
60
|
+
| "onEndReached"
|
|
61
|
+
| "onEndReachedThreshold"
|
|
62
|
+
| "onRefresh"
|
|
63
|
+
| "refreshing"
|
|
64
|
+
| "ListHeaderComponent"
|
|
65
|
+
| "ListFooterComponent"
|
|
66
|
+
>;
|
|
67
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Infinite Scroll Configuration Types
|
|
3
|
+
*
|
|
4
|
+
* Domain types for infinite scroll configuration
|
|
5
|
+
* Follows SOLID, DRY, KISS principles
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Paginated result for cursor-based pagination
|
|
10
|
+
*/
|
|
11
|
+
export interface PaginatedResult<T> {
|
|
12
|
+
items: T[];
|
|
13
|
+
nextCursor: string | null;
|
|
14
|
+
hasMore: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Base configuration shared by all pagination modes
|
|
19
|
+
*/
|
|
20
|
+
interface BaseConfig<T> {
|
|
21
|
+
/**
|
|
22
|
+
* Total number of items available (optional, for progress tracking)
|
|
23
|
+
*/
|
|
24
|
+
totalItems?: number;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Number of items to load per page
|
|
28
|
+
* Default: 20
|
|
29
|
+
*/
|
|
30
|
+
pageSize?: number;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Number of items from the end to trigger loading more
|
|
34
|
+
* Default: 5 (loads more when 5 items from bottom)
|
|
35
|
+
*/
|
|
36
|
+
threshold?: number;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Enable automatic loading when threshold is reached
|
|
40
|
+
* Default: true
|
|
41
|
+
*/
|
|
42
|
+
autoLoad?: boolean;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Optional: Function to get unique key for each item
|
|
46
|
+
* If not provided, uses array index
|
|
47
|
+
* @param item - Item to get key for
|
|
48
|
+
* @param index - Item index
|
|
49
|
+
* @returns Unique key string
|
|
50
|
+
*/
|
|
51
|
+
getItemKey?: (item: T, index: number) => string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Page-based pagination configuration (default, backward compatible)
|
|
56
|
+
*/
|
|
57
|
+
export interface PageBasedConfig<T> extends BaseConfig<T> {
|
|
58
|
+
/**
|
|
59
|
+
* Initial page number (0-indexed)
|
|
60
|
+
* Default: 0
|
|
61
|
+
*/
|
|
62
|
+
initialPage?: number;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Function to fetch data for a specific page
|
|
66
|
+
* @param page - Page number (0-indexed)
|
|
67
|
+
* @param pageSize - Number of items per page
|
|
68
|
+
* @returns Promise resolving to array of items
|
|
69
|
+
*/
|
|
70
|
+
fetchData: (page: number, pageSize: number) => Promise<T[]>;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Optional: Function to check if there are more items
|
|
74
|
+
* If not provided, checks if last page has fewer items than pageSize
|
|
75
|
+
* @param lastPage - Last fetched page of items
|
|
76
|
+
* @param allPages - All fetched pages
|
|
77
|
+
* @returns true if there are more items to load
|
|
78
|
+
*/
|
|
79
|
+
hasMore?: (lastPage: T[], allPages: T[][]) => boolean;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Cursor-based pagination configuration (new, for Firestore)
|
|
84
|
+
*/
|
|
85
|
+
export interface CursorBasedConfig<T> extends BaseConfig<T> {
|
|
86
|
+
/**
|
|
87
|
+
* Discriminator for cursor-based mode
|
|
88
|
+
*/
|
|
89
|
+
paginationMode: "cursor";
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Function to fetch data using cursor
|
|
93
|
+
* @param cursor - Cursor for next page (undefined for first page)
|
|
94
|
+
* @param pageSize - Number of items per page
|
|
95
|
+
* @returns Promise resolving to paginated result with cursor
|
|
96
|
+
*/
|
|
97
|
+
fetchCursor: (
|
|
98
|
+
cursor: string | undefined,
|
|
99
|
+
pageSize: number,
|
|
100
|
+
) => Promise<PaginatedResult<T>>;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Infinite scroll configuration (discriminated union)
|
|
105
|
+
*/
|
|
106
|
+
export type InfiniteScrollConfig<T> =
|
|
107
|
+
| PageBasedConfig<T>
|
|
108
|
+
| CursorBasedConfig<T>;
|
|
@@ -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
|
+
});
|