WellNuo/components/ui/LoadingOverlay.tsx
Sergei 2b36f801f1 Add comprehensive loading state management system
- Add useLoadingState hook with data/loading/error state management
- Add useSimpleLoading hook for basic boolean loading state
- Add useMultipleLoadingStates hook for tracking multiple items
- Create Skeleton component with shimmer animation for placeholders
- Create specialized skeletons: SkeletonText, SkeletonAvatar, SkeletonCard,
  SkeletonListItem, SkeletonBeneficiaryCard, SkeletonSensorCard
- Create LoadingOverlay components: modal, inline, and card variants
- Create ScreenLoading wrapper with loading/error/content states
- Create RefreshableScreen with built-in pull-to-refresh
- Create EmptyState and LoadingButtonState utility components
- Add comprehensive tests for all components and hooks (61 tests passing)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-01 10:11:14 -08:00

131 lines
2.9 KiB
TypeScript

import React from 'react';
import {
View,
Text,
StyleSheet,
ActivityIndicator,
Modal,
ViewStyle,
} from 'react-native';
import { AppColors, BorderRadius, FontSizes, Spacing, Shadows } from '@/constants/theme';
interface LoadingOverlayProps {
visible: boolean;
message?: string;
transparent?: boolean;
}
/**
* Full-screen modal loading overlay
* Use for operations that block the entire UI
*/
export function LoadingOverlay({
visible,
message = 'Loading...',
transparent = true,
}: LoadingOverlayProps) {
if (!visible) return null;
return (
<Modal
visible={visible}
transparent={transparent}
animationType="fade"
statusBarTranslucent
>
<View style={styles.overlay}>
<View style={styles.container}>
<ActivityIndicator size="large" color={AppColors.primary} />
{message && <Text style={styles.message}>{message}</Text>}
</View>
</View>
</Modal>
);
}
interface InlineLoadingOverlayProps {
visible: boolean;
message?: string;
style?: ViewStyle;
}
/**
* Inline loading overlay that covers its parent container
* Use for loading states within a specific section
*/
export function InlineLoadingOverlay({
visible,
message,
style,
}: InlineLoadingOverlayProps) {
if (!visible) return null;
return (
<View style={[styles.inlineOverlay, style]}>
<ActivityIndicator size="large" color={AppColors.primary} />
{message && <Text style={styles.inlineMessage}>{message}</Text>}
</View>
);
}
interface LoadingCardOverlayProps {
visible: boolean;
}
/**
* Card-style loading overlay
* Use for overlay on cards or smaller components
*/
export function LoadingCardOverlay({ visible }: LoadingCardOverlayProps) {
if (!visible) return null;
return (
<View style={styles.cardOverlay}>
<ActivityIndicator size="small" color={AppColors.primary} />
</View>
);
}
const styles = StyleSheet.create({
overlay: {
flex: 1,
backgroundColor: AppColors.overlay,
justifyContent: 'center',
alignItems: 'center',
},
container: {
backgroundColor: AppColors.surface,
borderRadius: BorderRadius.xl,
padding: Spacing.xl,
alignItems: 'center',
minWidth: 150,
...Shadows.lg,
},
message: {
marginTop: Spacing.md,
fontSize: FontSizes.base,
color: AppColors.textSecondary,
textAlign: 'center',
},
inlineOverlay: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'rgba(255, 255, 255, 0.9)',
justifyContent: 'center',
alignItems: 'center',
borderRadius: BorderRadius.lg,
},
inlineMessage: {
marginTop: Spacing.sm,
fontSize: FontSizes.sm,
color: AppColors.textSecondary,
textAlign: 'center',
},
cardOverlay: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'rgba(255, 255, 255, 0.8)',
justifyContent: 'center',
alignItems: 'center',
borderRadius: BorderRadius.lg,
},
});