@umituz/react-native-design-system 2.9.13 → 2.9.15

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@umituz/react-native-design-system",
3
- "version": "2.9.13",
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 onboarding utilities",
3
+ "version": "2.9.15",
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
+ };