@umituz/react-native-design-system 2.9.12 → 2.9.14
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 +5 -3
- package/src/exports/loading.ts +20 -0
- package/src/index.ts +5 -0
- package/src/loading/domain/types/loading.types.ts +40 -0
- package/src/loading/index.ts +18 -0
- package/src/loading/infrastructure/store/LoadingStore.ts +43 -0
- package/src/loading/presentation/components/LoadingOverlay.tsx +49 -0
- package/src/loading/presentation/hooks/useGlobalLoading.ts +51 -0
- package/src/loading/presentation/providers/LoadingProvider.tsx +65 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-design-system",
|
|
3
|
-
"version": "2.9.
|
|
4
|
-
"description": "Universal design system for React Native apps - Consolidated package with atoms, molecules, organisms, theme, typography, responsive, safe area, exception, infinite scroll, UUID, image, timezone, offline, and
|
|
3
|
+
"version": "2.9.14",
|
|
4
|
+
"description": "Universal design system for React Native apps - Consolidated package with atoms, molecules, organisms, theme, typography, responsive, safe area, exception, infinite scroll, UUID, image, timezone, offline, onboarding, and loading utilities",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
7
7
|
"exports": {
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"./filesystem": "./src/filesystem/index.ts",
|
|
28
28
|
"./media": "./src/media/index.ts",
|
|
29
29
|
"./tanstack": "./src/tanstack/index.ts",
|
|
30
|
+
"./loading": "./src/loading/index.ts",
|
|
30
31
|
"./package.json": "./package.json"
|
|
31
32
|
},
|
|
32
33
|
"scripts": {
|
|
@@ -51,7 +52,8 @@
|
|
|
51
52
|
"safe-area",
|
|
52
53
|
"image",
|
|
53
54
|
"timezone",
|
|
54
|
-
"offline"
|
|
55
|
+
"offline",
|
|
56
|
+
"loading"
|
|
55
57
|
],
|
|
56
58
|
"author": "Ümit UZ <umit@umituz.com>",
|
|
57
59
|
"license": "MIT",
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loading Exports
|
|
3
|
+
* Global loading state management
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export {
|
|
7
|
+
useLoadingStore,
|
|
8
|
+
LoadingOverlay,
|
|
9
|
+
useGlobalLoading,
|
|
10
|
+
LoadingProvider,
|
|
11
|
+
} from '../loading';
|
|
12
|
+
|
|
13
|
+
export type {
|
|
14
|
+
LoadingSource,
|
|
15
|
+
LoadingState,
|
|
16
|
+
LoadingActions,
|
|
17
|
+
LoadingStore,
|
|
18
|
+
LoadingProviderProps,
|
|
19
|
+
LoadingOverlayProps,
|
|
20
|
+
} from '../loading';
|
package/src/index.ts
CHANGED
|
@@ -125,3 +125,8 @@ export * from './exports/media';
|
|
|
125
125
|
// TANSTACK EXPORTS
|
|
126
126
|
// =============================================================================
|
|
127
127
|
export * from './exports/tanstack';
|
|
128
|
+
|
|
129
|
+
// =============================================================================
|
|
130
|
+
// LOADING EXPORTS
|
|
131
|
+
// =============================================================================
|
|
132
|
+
export * from './exports/loading';
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loading Types
|
|
3
|
+
* Type definitions for global loading system
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type LoadingSource = 'navigation' | 'fetch' | 'manual';
|
|
7
|
+
|
|
8
|
+
export interface LoadingState {
|
|
9
|
+
isLoading: boolean;
|
|
10
|
+
message?: string;
|
|
11
|
+
source?: LoadingSource;
|
|
12
|
+
count: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface LoadingActions {
|
|
16
|
+
show: (message?: string, source?: LoadingSource) => void;
|
|
17
|
+
hide: (source?: LoadingSource) => void;
|
|
18
|
+
reset: () => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface LoadingStore extends LoadingState, LoadingActions {}
|
|
22
|
+
|
|
23
|
+
export interface LoadingProviderProps {
|
|
24
|
+
children: React.ReactNode;
|
|
25
|
+
spinnerColor?: string;
|
|
26
|
+
spinnerSize?: 'sm' | 'md' | 'lg' | 'xl';
|
|
27
|
+
overlayColor?: string;
|
|
28
|
+
defaultMessage?: string;
|
|
29
|
+
detectNavigation?: boolean;
|
|
30
|
+
detectFetching?: boolean;
|
|
31
|
+
minDisplayTime?: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface LoadingOverlayProps {
|
|
35
|
+
visible: boolean;
|
|
36
|
+
message?: string;
|
|
37
|
+
spinnerColor?: string;
|
|
38
|
+
spinnerSize?: 'sm' | 'md' | 'lg' | 'xl';
|
|
39
|
+
overlayColor?: string;
|
|
40
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loading Module
|
|
3
|
+
* Global loading state management and auto-detection
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type {
|
|
7
|
+
LoadingSource,
|
|
8
|
+
LoadingState,
|
|
9
|
+
LoadingActions,
|
|
10
|
+
LoadingStore,
|
|
11
|
+
LoadingProviderProps,
|
|
12
|
+
LoadingOverlayProps,
|
|
13
|
+
} from './domain/types/loading.types';
|
|
14
|
+
|
|
15
|
+
export { useLoadingStore } from './infrastructure/store/LoadingStore';
|
|
16
|
+
export { LoadingOverlay } from './presentation/components/LoadingOverlay';
|
|
17
|
+
export { useGlobalLoading } from './presentation/hooks/useGlobalLoading';
|
|
18
|
+
export { LoadingProvider } from './presentation/providers/LoadingProvider';
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LoadingStore
|
|
3
|
+
* Global loading state management with Zustand
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { create } from 'zustand';
|
|
7
|
+
import type { LoadingStore, LoadingSource } from '../../domain/types/loading.types';
|
|
8
|
+
|
|
9
|
+
const initialState = {
|
|
10
|
+
isLoading: false,
|
|
11
|
+
message: undefined,
|
|
12
|
+
source: undefined,
|
|
13
|
+
count: 0,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const useLoadingStore = create<LoadingStore>((set) => ({
|
|
17
|
+
...initialState,
|
|
18
|
+
|
|
19
|
+
show: (message?: string, source: LoadingSource = 'manual') => {
|
|
20
|
+
set((state) => ({
|
|
21
|
+
isLoading: true,
|
|
22
|
+
message: message || state.message,
|
|
23
|
+
source,
|
|
24
|
+
count: state.count + 1,
|
|
25
|
+
}));
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
hide: () => {
|
|
29
|
+
set((state) => {
|
|
30
|
+
const newCount = Math.max(0, state.count - 1);
|
|
31
|
+
return {
|
|
32
|
+
count: newCount,
|
|
33
|
+
isLoading: newCount > 0,
|
|
34
|
+
message: newCount > 0 ? state.message : undefined,
|
|
35
|
+
source: newCount > 0 ? state.source : undefined,
|
|
36
|
+
};
|
|
37
|
+
});
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
reset: () => {
|
|
41
|
+
set(initialState);
|
|
42
|
+
},
|
|
43
|
+
}));
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LoadingOverlay Component
|
|
3
|
+
* Full-screen loading overlay with spinner
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import { View, StyleSheet, Modal } from 'react-native';
|
|
8
|
+
import { AtomicSpinner } from '../../../atoms/AtomicSpinner';
|
|
9
|
+
import type { LoadingOverlayProps } from '../../domain/types/loading.types';
|
|
10
|
+
|
|
11
|
+
const DEFAULT_OVERLAY_COLOR = 'rgba(0, 0, 0, 0.5)';
|
|
12
|
+
|
|
13
|
+
export const LoadingOverlay: React.FC<LoadingOverlayProps> = ({
|
|
14
|
+
visible,
|
|
15
|
+
message,
|
|
16
|
+
spinnerColor = 'white',
|
|
17
|
+
spinnerSize = 'lg',
|
|
18
|
+
overlayColor = DEFAULT_OVERLAY_COLOR,
|
|
19
|
+
}) => {
|
|
20
|
+
if (!visible) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<Modal
|
|
26
|
+
visible={visible}
|
|
27
|
+
transparent
|
|
28
|
+
animationType="none"
|
|
29
|
+
statusBarTranslucent
|
|
30
|
+
>
|
|
31
|
+
<View style={[styles.overlay, { backgroundColor: overlayColor }]}>
|
|
32
|
+
<AtomicSpinner
|
|
33
|
+
size={spinnerSize}
|
|
34
|
+
color={spinnerColor}
|
|
35
|
+
text={message}
|
|
36
|
+
textPosition="bottom"
|
|
37
|
+
/>
|
|
38
|
+
</View>
|
|
39
|
+
</Modal>
|
|
40
|
+
);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const styles = StyleSheet.create({
|
|
44
|
+
overlay: {
|
|
45
|
+
flex: 1,
|
|
46
|
+
justifyContent: 'center',
|
|
47
|
+
alignItems: 'center',
|
|
48
|
+
},
|
|
49
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useGlobalLoading Hook
|
|
3
|
+
* Manual control for global loading state
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useCallback } from 'react';
|
|
7
|
+
import { useLoadingStore } from '../../infrastructure/store/LoadingStore';
|
|
8
|
+
import type { LoadingSource } from '../../domain/types/loading.types';
|
|
9
|
+
|
|
10
|
+
interface UseGlobalLoadingReturn {
|
|
11
|
+
isLoading: boolean;
|
|
12
|
+
message?: string;
|
|
13
|
+
show: (message?: string) => void;
|
|
14
|
+
hide: () => void;
|
|
15
|
+
withLoading: <T>(
|
|
16
|
+
asyncFn: () => Promise<T>,
|
|
17
|
+
message?: string
|
|
18
|
+
) => Promise<T>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function useGlobalLoading(): UseGlobalLoadingReturn {
|
|
22
|
+
const { isLoading, message, show, hide } = useLoadingStore();
|
|
23
|
+
|
|
24
|
+
const showLoading = useCallback((msg?: string) => {
|
|
25
|
+
show(msg, 'manual' as LoadingSource);
|
|
26
|
+
}, [show]);
|
|
27
|
+
|
|
28
|
+
const hideLoading = useCallback(() => {
|
|
29
|
+
hide();
|
|
30
|
+
}, [hide]);
|
|
31
|
+
|
|
32
|
+
const withLoading = useCallback(
|
|
33
|
+
async <T>(asyncFn: () => Promise<T>, msg?: string): Promise<T> => {
|
|
34
|
+
showLoading(msg);
|
|
35
|
+
try {
|
|
36
|
+
return await asyncFn();
|
|
37
|
+
} finally {
|
|
38
|
+
hideLoading();
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
[showLoading, hideLoading]
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
isLoading,
|
|
46
|
+
message,
|
|
47
|
+
show: showLoading,
|
|
48
|
+
hide: hideLoading,
|
|
49
|
+
withLoading,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LoadingProvider
|
|
3
|
+
* Global loading provider with auto-detection
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { useEffect, useRef } from 'react';
|
|
7
|
+
import { useIsFetching } from '@tanstack/react-query';
|
|
8
|
+
import { useLoadingStore } from '../../infrastructure/store/LoadingStore';
|
|
9
|
+
import { LoadingOverlay } from '../components/LoadingOverlay';
|
|
10
|
+
import type { LoadingProviderProps } from '../../domain/types/loading.types';
|
|
11
|
+
|
|
12
|
+
export const LoadingProvider: React.FC<LoadingProviderProps> = ({
|
|
13
|
+
children,
|
|
14
|
+
spinnerColor,
|
|
15
|
+
spinnerSize = 'lg',
|
|
16
|
+
overlayColor,
|
|
17
|
+
defaultMessage,
|
|
18
|
+
detectFetching = true,
|
|
19
|
+
minDisplayTime = 300,
|
|
20
|
+
}) => {
|
|
21
|
+
const { isLoading, message, show, hide } = useLoadingStore();
|
|
22
|
+
const isFetching = useIsFetching();
|
|
23
|
+
const hideTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
24
|
+
const showTimeRef = useRef<number>(0);
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (!detectFetching) return;
|
|
28
|
+
|
|
29
|
+
if (isFetching > 0) {
|
|
30
|
+
if (hideTimeoutRef.current) {
|
|
31
|
+
clearTimeout(hideTimeoutRef.current);
|
|
32
|
+
hideTimeoutRef.current = null;
|
|
33
|
+
}
|
|
34
|
+
showTimeRef.current = Date.now();
|
|
35
|
+
show(defaultMessage, 'fetch');
|
|
36
|
+
} else if (isLoading) {
|
|
37
|
+
const elapsed = Date.now() - showTimeRef.current;
|
|
38
|
+
const remaining = Math.max(0, minDisplayTime - elapsed);
|
|
39
|
+
|
|
40
|
+
hideTimeoutRef.current = setTimeout(() => {
|
|
41
|
+
hide();
|
|
42
|
+
hideTimeoutRef.current = null;
|
|
43
|
+
}, remaining);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return () => {
|
|
47
|
+
if (hideTimeoutRef.current) {
|
|
48
|
+
clearTimeout(hideTimeoutRef.current);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}, [isFetching, detectFetching, show, hide, defaultMessage, minDisplayTime, isLoading]);
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<>
|
|
55
|
+
{children}
|
|
56
|
+
<LoadingOverlay
|
|
57
|
+
visible={isLoading}
|
|
58
|
+
message={message || defaultMessage}
|
|
59
|
+
spinnerColor={spinnerColor}
|
|
60
|
+
spinnerSize={spinnerSize}
|
|
61
|
+
overlayColor={overlayColor}
|
|
62
|
+
/>
|
|
63
|
+
</>
|
|
64
|
+
);
|
|
65
|
+
};
|