@sincpro/mobile-ui 1.1.0 → 2.0.1

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.
Files changed (129) hide show
  1. package/README.md +156 -29
  2. package/assets/brand-mesh.png +0 -0
  3. package/assets/fonts/FFL.txt +57 -0
  4. package/assets/fonts/Satoshi-Black.otf +0 -0
  5. package/assets/fonts/Satoshi-Bold.otf +0 -0
  6. package/assets/fonts/Satoshi-Medium.otf +0 -0
  7. package/assets/fonts/Satoshi-Regular.otf +0 -0
  8. package/dist/Actions/Actions.Button.d.ts +10 -12
  9. package/dist/Actions/Actions.Button.js +9 -11
  10. package/dist/Actions/Actions.Dock.d.ts +20 -0
  11. package/dist/Actions/Actions.Dock.js +89 -0
  12. package/dist/Actions/Actions.FAB.d.ts +1 -2
  13. package/dist/Actions/Actions.FAB.js +5 -7
  14. package/dist/Actions/Actions.IconButton.d.ts +1 -2
  15. package/dist/Actions/Actions.IconButton.js +2 -2
  16. package/dist/Actions/index.d.ts +4 -0
  17. package/dist/Actions/index.js +3 -0
  18. package/dist/Dialog/BottomSheet/BottomSheet.Root.js +22 -34
  19. package/dist/Dialog/Dialog.ActionSheet.js +2 -16
  20. package/dist/Dialog/Dialog.Confirmation.js +7 -5
  21. package/dist/Dialog/Dialog.EditValue.js +3 -1
  22. package/dist/Dialog/Dialog.Sheet.d.ts +3 -3
  23. package/dist/Dialog/Dialog.Sheet.js +18 -25
  24. package/dist/Display/Display.Accordion.js +7 -3
  25. package/dist/Display/Display.AmountSummary.d.ts +28 -0
  26. package/dist/Display/Display.AmountSummary.js +25 -0
  27. package/dist/Display/Display.Carousel.d.ts +27 -4
  28. package/dist/Display/Display.Carousel.js +112 -12
  29. package/dist/Display/Display.ExpoImage.d.ts +8 -0
  30. package/dist/Display/Display.ExpoImage.js +15 -0
  31. package/dist/Display/Display.Image.d.ts +10 -6
  32. package/dist/Display/Display.Image.js +39 -6
  33. package/dist/Display/Display.ListItem.d.ts +1 -2
  34. package/dist/Display/Display.ListItem.js +3 -3
  35. package/dist/Display/Display.Logo.d.ts +2 -2
  36. package/dist/Display/Display.Logo.js +3 -2
  37. package/dist/Display/Display.MediaCard.d.ts +2 -2
  38. package/dist/Display/Display.MediaEmbed.d.ts +2 -2
  39. package/dist/Display/Display.ProductCard.d.ts +2 -2
  40. package/dist/Display/Display.Story.d.ts +3 -3
  41. package/dist/Display/Display.Story.js +5 -4
  42. package/dist/Display/Display.StoryTray.d.ts +4 -4
  43. package/dist/Display/Display.StoryTray.js +3 -2
  44. package/dist/Display/Display.StoryViewer.d.ts +3 -3
  45. package/dist/Display/Display.StoryViewer.js +6 -3
  46. package/dist/Display/index.d.ts +9 -0
  47. package/dist/Display/index.js +4 -0
  48. package/dist/Feedback/Feedback.DomainSplashScreen.d.ts +13 -4
  49. package/dist/Feedback/Feedback.DomainSplashScreen.js +68 -53
  50. package/dist/Feedback/Feedback.EmptyState.d.ts +3 -4
  51. package/dist/Feedback/Feedback.EmptyState.js +4 -3
  52. package/dist/Feedback/Feedback.Error.d.ts +1 -2
  53. package/dist/Feedback/Feedback.Error.js +2 -2
  54. package/dist/Feedback/Feedback.Loading.d.ts +6 -2
  55. package/dist/Feedback/Feedback.Loading.js +2 -2
  56. package/dist/Feedback/Feedback.Progress.d.ts +6 -3
  57. package/dist/Feedback/Feedback.Progress.js +29 -19
  58. package/dist/Feedback/Feedback.ProgressCircle.d.ts +3 -2
  59. package/dist/Feedback/Feedback.ProgressCircle.js +10 -7
  60. package/dist/Feedback/Feedback.Skeleton.d.ts +4 -4
  61. package/dist/Feedback/Feedback.Skeleton.js +15 -3
  62. package/dist/Feedback/Feedback.Spinner.d.ts +3 -3
  63. package/dist/Feedback/Feedback.Spinner.js +40 -9
  64. package/dist/Feedback/Feedback.Splash.d.ts +2 -2
  65. package/dist/Feedback/Feedback.Splash.js +5 -3
  66. package/dist/Feedback/Toast.context.d.ts +64 -3
  67. package/dist/Feedback/Toast.context.js +112 -31
  68. package/dist/Feedback/index.d.ts +15 -1
  69. package/dist/Feedback/index.js +4 -1
  70. package/dist/Form/Form.Checkbox.d.ts +1 -2
  71. package/dist/Form/Form.Checkbox.js +2 -2
  72. package/dist/Form/Form.DatePicker.d.ts +1 -2
  73. package/dist/Form/Form.DatePicker.js +2 -2
  74. package/dist/Form/Form.Dropdown.d.ts +15 -3
  75. package/dist/Form/Form.Dropdown.js +21 -6
  76. package/dist/Form/Form.ErrorSummary.d.ts +23 -0
  77. package/dist/Form/Form.ErrorSummary.js +53 -0
  78. package/dist/Form/Form.Input.d.ts +1 -2
  79. package/dist/Form/Form.Input.js +2 -2
  80. package/dist/Form/Form.Select.d.ts +1 -2
  81. package/dist/Form/Form.Select.js +6 -19
  82. package/dist/Form/index.d.ts +3 -0
  83. package/dist/Form/index.js +2 -0
  84. package/dist/Navigation/Navigation.AppBar.d.ts +2 -6
  85. package/dist/Navigation/Navigation.AppBar.js +9 -7
  86. package/dist/Navigation/Navigation.BottomNav.d.ts +1 -3
  87. package/dist/Navigation/Navigation.BottomNav.js +7 -6
  88. package/dist/Navigation/Navigation.Header.d.ts +2 -2
  89. package/dist/Navigation/Navigation.Menu.js +10 -3
  90. package/dist/Navigation/Navigation.Search.d.ts +41 -0
  91. package/dist/Navigation/Navigation.Search.js +47 -0
  92. package/dist/Navigation/Navigation.SearchBar.d.ts +1 -2
  93. package/dist/Navigation/Navigation.SearchBar.js +16 -19
  94. package/dist/Navigation/Navigation.SegmentedControl.d.ts +11 -3
  95. package/dist/Navigation/Navigation.SegmentedControl.js +29 -16
  96. package/dist/Navigation/index.d.ts +11 -2
  97. package/dist/Navigation/index.js +2 -0
  98. package/dist/branding.d.ts +3 -2
  99. package/dist/primitives/BaseButton.d.ts +10 -12
  100. package/dist/primitives/BaseButton.js +9 -11
  101. package/dist/theme/typography.d.ts +1 -1
  102. package/dist/utils/image.d.ts +3 -0
  103. package/dist/utils/image.js +13 -0
  104. package/dist/utils/keys.d.ts +1 -0
  105. package/dist/utils/keys.js +7 -0
  106. package/dist/utils/progress.d.ts +1 -0
  107. package/dist/utils/progress.js +5 -0
  108. package/dist/utils/reorder.js +2 -5
  109. package/dist/utils/useReducedMotion.d.ts +1 -0
  110. package/dist/utils/useReducedMotion.js +43 -0
  111. package/dist/utils/useSheetTranslation.d.ts +5 -0
  112. package/dist/utils/useSheetTranslation.js +23 -0
  113. package/dist/views/AppSplashView.d.ts +3 -2
  114. package/dist/views/AppSplashView.js +3 -2
  115. package/dist/views/AuthFormView.d.ts +2 -2
  116. package/dist/views/FormViewV2.d.ts +2 -2
  117. package/dist/views/ListViewV2.context.d.ts +3 -1
  118. package/dist/views/ListViewV2.context.js +41 -21
  119. package/dist/views/ListViewV2.d.ts +15 -7
  120. package/dist/views/ListViewV2.js +44 -23
  121. package/dist/views/types/IListView.js +6 -5
  122. package/dist/widgets/CollectionOptions.js +9 -7
  123. package/dist/widgets/FilterBar.d.ts +1 -1
  124. package/dist/widgets/FilterBar.js +1 -1
  125. package/dist/widgets/FilterBuilder.js +7 -7
  126. package/dist/widgets/HomeHeader.d.ts +2 -2
  127. package/dist/widgets/PromoModal.js +3 -1
  128. package/dist/widgets/ScreenHeader.d.ts +2 -2
  129. package/package.json +5 -1
@@ -1,65 +1,80 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "nativewind/jsx-runtime";
2
2
  import GradientSurface from "../Display/Display.GradientSurface.js";
3
3
  import Pattern from "../Display/Display.Pattern.js";
4
- import { theme } from "../theme/index.js";
4
+ import Progress from "./Feedback.Progress.js";
5
+ import { useTheme } from "../theme/index.js";
5
6
  import { Typography } from "../Typography/index.js";
6
- import { useCallback, useEffect, useState } from "react";
7
- import { StyleSheet, View } from "react-native";
8
- import Animated, { Easing, useAnimatedStyle, useSharedValue, withTiming, } from "react-native-reanimated";
9
- import { scheduleOnRN } from "react-native-worklets";
10
- function DomainSplashScreen({ domainName, isLoading, children, onComplete, minDuration = 300, }) {
11
- const progressValue = useSharedValue(0);
12
- const opacityValue = useSharedValue(1);
13
- const [isComplete, setIsComplete] = useState(false);
14
- const handleComplete = useCallback(() => {
15
- if (onComplete) {
16
- onComplete();
17
- }
7
+ import { useReducedMotion } from "../utils/useReducedMotion.js";
8
+ import { useEffect, useRef, useState } from "react";
9
+ import { Animated, StyleSheet, View } from "react-native";
10
+ function DomainSplashScreen({ domainName, isLoading, children, onComplete, minDuration = 300, containerStyle, overlayStyle, renderLoading, testID, }) {
11
+ if (!Number.isFinite(minDuration) || minDuration < 0)
12
+ throw new RangeError("DomainSplashScreen minDuration must be a finite, non-negative number.");
13
+ const theme = useTheme();
14
+ const reducedMotion = useReducedMotion();
15
+ const [opacity] = useState(() => new Animated.Value(isLoading ? 1 : 0));
16
+ const [visible, setVisible] = useState(isLoading);
17
+ const startedAt = useRef(null);
18
+ const completed = useRef(false);
19
+ const onCompleteRef = useRef(onComplete);
20
+ const busy = isLoading || visible;
21
+ if (isLoading && !visible)
22
+ setVisible(true);
23
+ useEffect(() => {
24
+ onCompleteRef.current = onComplete;
18
25
  }, [onComplete]);
19
26
  useEffect(() => {
20
- if (!isLoading) {
21
- progressValue.value = withTiming(100, {
22
- duration: 300,
23
- easing: Easing.out(Easing.ease),
24
- });
25
- setTimeout(() => {
26
- opacityValue.value = withTiming(0, {
27
- duration: 400,
28
- easing: Easing.out(Easing.ease),
29
- });
30
- }, 100);
27
+ if (isLoading) {
28
+ startedAt.current = Date.now();
29
+ completed.current = false;
30
+ }
31
+ }, [isLoading]);
32
+ useEffect(() => {
33
+ if (isLoading) {
34
+ opacity.stopAnimation();
35
+ opacity.setValue(1);
31
36
  return;
32
37
  }
33
- const startTime = Date.now();
34
- const totalDuration = 2500;
35
- const interval = setInterval(() => {
36
- const elapsed = Date.now() - startTime;
37
- const newProgress = Math.min((elapsed / totalDuration) * 100, 100);
38
- progressValue.value = newProgress;
39
- if (newProgress >= 100) {
40
- clearInterval(interval);
41
- setIsComplete(true);
42
- setTimeout(() => {
43
- opacityValue.value = withTiming(0, {
44
- duration: 800,
45
- easing: Easing.out(Easing.ease),
46
- }, (finished) => {
47
- if (finished) {
48
- scheduleOnRN(handleComplete);
49
- }
50
- });
51
- }, 500);
38
+ if (startedAt.current === null || completed.current)
39
+ return;
40
+ let active = true;
41
+ let animation;
42
+ const finish = () => {
43
+ if (!active || completed.current)
44
+ return;
45
+ completed.current = true;
46
+ setVisible(false);
47
+ onCompleteRef.current?.();
48
+ };
49
+ const timeout = setTimeout(() => {
50
+ if (reducedMotion) {
51
+ opacity.setValue(0);
52
+ finish();
52
53
  }
53
- }, 50);
54
- return () => clearInterval(interval);
55
- }, [isLoading]);
56
- const progressBarStyle = useAnimatedStyle(() => ({
57
- width: `${progressValue.value}%`,
58
- }));
59
- const splashStyle = useAnimatedStyle(() => ({
60
- opacity: opacityValue.value,
61
- }));
62
- return (_jsxs(View, { style: styles.container, children: [children, _jsx(Animated.View, { pointerEvents: isLoading ? "auto" : "none", style: [StyleSheet.absoluteFill, splashStyle], children: _jsxs(GradientSurface, { preset: "warm", radius: "none", style: { position: "absolute", top: 0, left: 0, right: 0, bottom: 0 }, children: [_jsx(Pattern, { color: theme.primary, kind: "dots", opacity: 0.14, size: 26 }), _jsx(View, { className: "flex-1 justify-center items-center px-8", children: _jsxs(View, { className: "w-full items-center gap-5", children: [_jsx(Typography.Text, { bold: true, style: { color: "#FFFFFF", fontSize: 24, letterSpacing: 0.5 }, children: isComplete ? "¡Listo!" : `Cargando ${domainName}` }), !isComplete && (_jsx(View, { className: "w-full rounded-full h-1.5 overflow-hidden", style: { backgroundColor: "rgba(255,255,255,0.18)" }, children: _jsx(Animated.View, { className: "rounded-full h-1.5", style: [{ backgroundColor: theme.primary }, progressBarStyle] }) })), isComplete && (_jsxs(Typography.Text, { style: { color: "rgba(255,255,255,0.8)", fontSize: 13 }, children: ["Ya puedes comenzar a usar ", domainName, "."] }))] }) })] }) })] }));
54
+ else {
55
+ animation = Animated.timing(opacity, {
56
+ toValue: 0,
57
+ duration: 400,
58
+ useNativeDriver: true,
59
+ isInteraction: false,
60
+ });
61
+ animation.start(({ finished }) => {
62
+ if (finished)
63
+ finish();
64
+ });
65
+ }
66
+ }, Math.max(0, minDuration - (Date.now() - startedAt.current)));
67
+ return () => {
68
+ active = false;
69
+ clearTimeout(timeout);
70
+ animation?.stop();
71
+ };
72
+ }, [isLoading, minDuration, opacity, reducedMotion]);
73
+ return (_jsxs(View, { style: [styles.container, containerStyle], testID: testID, children: [_jsx(View, { accessibilityElementsHidden: busy, importantForAccessibility: busy ? "no-hide-descendants" : "auto", pointerEvents: busy ? "none" : "auto", style: styles.container, testID: testID ? `${testID}-content` : undefined, children: children }), busy ? (_jsx(Animated.View, { style: [
74
+ StyleSheet.absoluteFill,
75
+ { opacity: isLoading ? 1 : opacity },
76
+ overlayStyle,
77
+ ], testID: testID ? `${testID}-loading` : undefined, children: renderLoading ? (renderLoading({ domainName, isLoading, reducedMotion })) : (_jsxs(GradientSurface, { className: "flex-1", preset: "warm", radius: "none", style: { position: "absolute", top: 0, left: 0, right: 0, bottom: 0 }, children: [_jsx(Pattern, { color: theme.primary, kind: "dots", opacity: 0.14, size: 26 }), _jsx(View, { className: "flex-1 justify-center items-center px-8", children: _jsxs(View, { className: "w-full items-center gap-5", children: [_jsx(Typography.Text, { bold: true, style: { color: "#FFFFFF", fontSize: 24, letterSpacing: 0.5 }, children: `Cargando ${domainName}` }), _jsx(Progress, { accessibilityLabel: `Cargando ${domainName}`, indeterminate: true, indicatorStyle: { backgroundColor: theme.primary }, style: { backgroundColor: "rgba(255,255,255,0.18)" } })] }) })] })) })) : null] }));
63
78
  }
64
79
  const styles = StyleSheet.create({
65
80
  container: {
@@ -1,16 +1,15 @@
1
+ import type { ImageSource } from "../utils/image.js";
1
2
  import type { ReactNode } from "react";
2
- import { type ImageSourcePropType } from "react-native";
3
3
  export interface EmptyStateProps {
4
4
  title: string;
5
5
  description?: string;
6
6
  icon?: string;
7
- image?: ImageSourcePropType;
7
+ image?: ImageSource;
8
8
  actionLabel?: string;
9
9
  onAction?: () => void;
10
10
  children?: ReactNode;
11
11
  className?: string;
12
12
  testID?: string;
13
- accessibleSizing?: boolean;
14
13
  }
15
- declare function EmptyState({ title, description, icon, image, actionLabel, onAction, children, className, testID, accessibleSizing, }: EmptyStateProps): import("react").JSX.Element;
14
+ declare function EmptyState({ title, description, icon, image, actionLabel, onAction, children, className, testID, }: EmptyStateProps): import("react").JSX.Element;
16
15
  export default EmptyState;
@@ -1,11 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "nativewind/jsx-runtime";
2
+ import ExpoImage from "../Display/Display.ExpoImage.js";
2
3
  import Icon from "../Display/Display.Icon.js";
3
4
  import { BaseButton } from "../primitives/BaseButton.js";
4
5
  import { theme } from "../theme/index.js";
5
6
  import { cn } from "../theme/tw.js";
6
7
  import { Typography } from "../Typography/index.js";
7
- import { Image, View } from "react-native";
8
- function EmptyState({ title, description, icon = "file-tray-outline", image, actionLabel, onAction, children, className, testID, accessibleSizing = false, }) {
9
- return (_jsxs(View, { className: cn("flex-1 items-center justify-center px-8 gap-3", className), testID: testID, children: [image ? (_jsx(Image, { resizeMode: "contain", source: image, style: { width: 140, height: 140 } })) : (_jsx(View, { className: "w-20 h-20 rounded-full bg-bg-muted items-center justify-center", children: _jsx(Icon, { color: theme.icon.tertiary, name: icon, size: 40 }) })), _jsx(Typography.Text, { accessibilityRole: "header", align: "center", className: "text-text-primary", semibold: true, variant: "subtitle", children: title }), description ? (_jsx(Typography.Text, { align: "center", className: "text-text-secondary", variant: "body", children: description })) : null, actionLabel && onAction ? (_jsx(View, { className: "mt-2", children: _jsx(BaseButton, { accessibleSizing: accessibleSizing, onPress: onAction, title: actionLabel, variant: "primary" }) })) : null, children] }));
8
+ import { View } from "react-native";
9
+ function EmptyState({ title, description, icon = "file-tray-outline", image, actionLabel, onAction, children, className, testID, }) {
10
+ return (_jsxs(View, { className: cn("flex-1 items-center justify-center px-8 gap-3", className), testID: testID, children: [image ? (_jsx(ExpoImage, { contentFit: "contain", source: image, style: { width: 140, height: 140 } })) : (_jsx(View, { className: "w-20 h-20 rounded-full bg-bg-muted items-center justify-center", children: _jsx(Icon, { color: theme.icon.tertiary, name: icon, size: 40 }) })), _jsx(Typography.Text, { accessibilityRole: "header", align: "center", className: "text-text-primary", semibold: true, variant: "subtitle", children: title }), description ? (_jsx(Typography.Text, { align: "center", className: "text-text-secondary", variant: "body", children: description })) : null, actionLabel && onAction ? (_jsx(View, { className: "mt-2", children: _jsx(BaseButton, { onPress: onAction, title: actionLabel, variant: "primary" }) })) : null, children] }));
10
11
  }
11
12
  export default EmptyState;
@@ -4,8 +4,7 @@ interface ErrorProps {
4
4
  onBack?: () => void;
5
5
  onHome?: () => void;
6
6
  className?: string;
7
- accessibleSizing?: boolean;
8
7
  testID?: string;
9
8
  }
10
- declare function Error({ message, onRetry, onBack, onHome, className, accessibleSizing, testID, }: ErrorProps): import("react").JSX.Element;
9
+ declare function Error({ message, onRetry, onBack, onHome, className, testID }: ErrorProps): import("react").JSX.Element;
11
10
  export default Error;
@@ -5,7 +5,7 @@ import { theme } from "../theme/index.js";
5
5
  import { cn } from "../theme/tw.js";
6
6
  import { Typography } from "../Typography/index.js";
7
7
  import { View } from "react-native";
8
- function Error({ message, onRetry, onBack, onHome, className, accessibleSizing = false, testID, }) {
9
- return (_jsxs(View, { className: cn("flex-1 items-center justify-center px-4", className), testID: testID, children: [_jsx(Icon, { color: theme.danger, name: "exclamation-circle", size: 80, type: "fontawesome5" }), _jsx(Typography.Text, { accessibilityRole: "header", className: "pt-4 text-text-primary", semibold: true, variant: "h3", children: "\u00A1Error!" }), _jsx(Typography.Text, { accessibilityLiveRegion: "polite", className: "pt-2 text-center text-base leading-5.5 text-text-secondary", variant: "h5", children: message || "Ocurrió un error al cargar la información" }), _jsxs(View, { className: "mt-8 w-full gap-3", children: [onRetry && (_jsx(Button, { accessibleSizing: accessibleSizing, className: "w-full", onPress: onRetry, title: "Reintentar" })), onBack && (_jsx(Button, { accessibleSizing: accessibleSizing, className: "w-full", onPress: onBack, title: "Volver", variant: "secondary" })), onHome && (_jsx(Button, { accessibleSizing: accessibleSizing, className: "w-full", onPress: onHome, title: "Ir al inicio", variant: "outline" }))] })] }));
8
+ function Error({ message, onRetry, onBack, onHome, className, testID }) {
9
+ return (_jsxs(View, { className: cn("flex-1 items-center justify-center px-4", className), testID: testID, children: [_jsx(Icon, { color: theme.danger, name: "exclamation-circle", size: 80, type: "fontawesome5" }), _jsx(Typography.Text, { accessibilityRole: "header", className: "pt-4 text-text-primary", semibold: true, variant: "h3", children: "\u00A1Error!" }), _jsx(Typography.Text, { accessibilityLiveRegion: "polite", className: "pt-2 text-center text-base leading-5.5 text-text-secondary", variant: "h5", children: message || "Ocurrió un error al cargar la información" }), _jsxs(View, { className: "mt-8 w-full gap-3", children: [onRetry && _jsx(Button, { className: "w-full", onPress: onRetry, title: "Reintentar" }), onBack && (_jsx(Button, { className: "w-full", onPress: onBack, title: "Volver", variant: "secondary" })), onHome && (_jsx(Button, { className: "w-full", onPress: onHome, title: "Ir al inicio", variant: "outline" }))] })] }));
10
10
  }
11
11
  export default Error;
@@ -1,11 +1,15 @@
1
1
  import { type SpinnerVariant } from "./Feedback.Spinner.js";
2
2
  import type React from "react";
3
+ import { type StyleProp, type ViewStyle } from "react-native";
3
4
  interface LoadingStateProps {
4
5
  message?: string;
5
6
  size?: "small" | "large";
6
7
  variant?: SpinnerVariant;
7
- style?: object;
8
+ style?: StyleProp<ViewStyle>;
8
9
  className?: string;
10
+ animated?: boolean;
11
+ accessibilityLabel?: string;
12
+ testID?: string;
9
13
  }
10
- declare function LoadingState({ message, size, variant, style, className, }: LoadingStateProps): React.JSX.Element;
14
+ declare function LoadingState({ message, size, variant, style, className, animated, accessibilityLabel, testID, }: LoadingStateProps): React.JSX.Element;
11
15
  export default LoadingState;
@@ -2,7 +2,7 @@ import { jsx as _jsx } from "nativewind/jsx-runtime";
2
2
  import Spinner from "./Feedback.Spinner.js";
3
3
  import { cn } from "../theme/tw.js";
4
4
  import { View } from "react-native";
5
- function LoadingState({ message = "Cargando…", size = "small", variant = "spinner", style = {}, className, }) {
6
- return (_jsx(View, { className: cn("flex-1 justify-center items-center", className), style: style, children: _jsx(Spinner, { className: "mb-5", size: size, text: message, variant: variant }) }));
5
+ function LoadingState({ message = "Cargando…", size = "small", variant = "spinner", style = {}, className, animated, accessibilityLabel, testID, }) {
6
+ return (_jsx(View, { className: cn("flex-1 justify-center items-center", className), style: style, children: _jsx(Spinner, { accessibilityLabel: accessibilityLabel, animated: animated, className: "mb-5", size: size, testID: testID, text: message, variant: variant }) }));
7
7
  }
8
8
  export default LoadingState;
@@ -1,9 +1,12 @@
1
- export interface ProgressProps {
1
+ import { type StyleProp, type ViewProps, type ViewStyle } from "react-native";
2
+ export interface ProgressProps extends Omit<ViewProps, "children" | "accessibilityValue" | "accessibilityRole" | "accessibilityState"> {
2
3
  value?: number;
3
4
  indeterminate?: boolean;
4
5
  height?: number;
5
6
  className?: string;
6
- testID?: string;
7
+ animated?: boolean;
8
+ indicatorClassName?: string;
9
+ indicatorStyle?: StyleProp<ViewStyle>;
7
10
  }
8
- declare function Progress({ value, indeterminate, height, className, testID, }: ProgressProps): import("react").JSX.Element;
11
+ declare function Progress({ value, indeterminate, height, className, animated, indicatorClassName, indicatorStyle, style, ...props }: ProgressProps): import("react").JSX.Element;
9
12
  export default Progress;
@@ -1,37 +1,47 @@
1
1
  import { jsx as _jsx } from "nativewind/jsx-runtime";
2
2
  import { cn } from "../theme/tw.js";
3
3
  import { motion } from "../tokens/motion.js";
4
+ import { normalizeProgress } from "../utils/progress.js";
5
+ import { useReducedMotion } from "../utils/useReducedMotion.js";
4
6
  import { useEffect, useRef } from "react";
5
- import { Animated, Easing, View } from "react-native";
6
- function clamp(n) {
7
- return Math.max(0, Math.min(1, n));
8
- }
9
- function Progress({ value = 0, indeterminate = false, height = 6, className, testID, }) {
7
+ import { Animated, Easing, View, } from "react-native";
8
+ function Progress({ value = 0, indeterminate = false, height = 6, className, animated = true, indicatorClassName, indicatorStyle, style, ...props }) {
9
+ const reducedMotion = useReducedMotion();
10
+ const shouldAnimate = indeterminate && animated && !reducedMotion;
11
+ const progress = indeterminate ? 0 : normalizeProgress(value);
10
12
  // eslint-disable-next-line react-hooks/refs
11
13
  const translate = useRef(new Animated.Value(0)).current;
12
14
  useEffect(() => {
13
- if (!indeterminate)
15
+ translate.setValue(0);
16
+ if (!shouldAnimate)
14
17
  return;
15
18
  const loop = Animated.loop(Animated.timing(translate, {
16
19
  toValue: 1,
17
20
  duration: motion.duration.slower,
18
21
  easing: Easing.inOut(Easing.ease),
19
22
  useNativeDriver: true,
23
+ isInteraction: false,
20
24
  }));
21
25
  loop.start();
22
26
  return () => loop.stop();
23
- }, [indeterminate, translate]);
24
- return (_jsx(View, { accessibilityRole: "progressbar", accessibilityValue: indeterminate ? undefined : { now: Math.round(clamp(value) * 100), min: 0, max: 100 }, className: cn("w-full overflow-hidden rounded-full bg-bg-muted", className), style: { height }, testID: testID, children: indeterminate ? (_jsx(Animated.View, { className: "h-full rounded-full bg-primary", style: {
25
- width: "40%",
26
- transform: [
27
- {
28
- // eslint-disable-next-line react-hooks/refs
29
- translateX: translate.interpolate({
30
- inputRange: [0, 1],
31
- outputRange: ["-40%", "250%"],
32
- }),
33
- },
34
- ],
35
- } })) : (_jsx(View, { className: "h-full rounded-full bg-primary", style: { width: `${clamp(value) * 100}%` } })) }));
27
+ }, [shouldAnimate, translate]);
28
+ return (_jsx(View, { ...props, accessibilityRole: "progressbar", accessibilityState: { busy: indeterminate }, accessibilityValue: indeterminate ? undefined : { now: Math.round(progress * 100), min: 0, max: 100 }, accessible: true, className: cn("w-full overflow-hidden rounded-full bg-bg-muted", className), style: [{ height }, style], children: indeterminate ? (_jsx(Animated.View, { className: cn("h-full rounded-full bg-primary", indicatorClassName), style: [
29
+ {
30
+ width: "40%",
31
+ marginLeft: shouldAnimate ? 0 : "30%",
32
+ transform: shouldAnimate
33
+ ? [
34
+ {
35
+ // eslint-disable-next-line react-hooks/refs
36
+ translateX: translate.interpolate({
37
+ inputRange: [0, 1],
38
+ outputRange: ["-40%", "250%"],
39
+ }),
40
+ },
41
+ ]
42
+ : [],
43
+ },
44
+ indicatorStyle,
45
+ ] })) : (_jsx(View, { className: cn("h-full rounded-full bg-primary", indicatorClassName), style: [{ width: `${progress * 100}%` }, indicatorStyle] })) }));
36
46
  }
37
47
  export default Progress;
@@ -1,5 +1,6 @@
1
1
  import type { ReactNode } from "react";
2
- export interface ProgressCircleProps {
2
+ import { type ViewProps } from "react-native";
3
+ export interface ProgressCircleProps extends Omit<ViewProps, "accessibilityValue" | "accessibilityRole"> {
3
4
  value?: number;
4
5
  size?: number;
5
6
  strokeWidth?: number;
@@ -8,5 +9,5 @@ export interface ProgressCircleProps {
8
9
  children?: ReactNode;
9
10
  testID?: string;
10
11
  }
11
- declare function ProgressCircle({ value, size, strokeWidth, color, trackColor, children, testID, }: ProgressCircleProps): import("react").JSX.Element;
12
+ declare function ProgressCircle({ value, size, strokeWidth, color, trackColor, children, style, ...props }: ProgressCircleProps): import("react").JSX.Element;
12
13
  export default ProgressCircle;
@@ -1,15 +1,18 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "nativewind/jsx-runtime";
2
- import { theme } from "../theme/index.js";
2
+ import { useTheme } from "../theme/index.js";
3
+ import { normalizeProgress } from "../utils/progress.js";
3
4
  import { View } from "react-native";
4
5
  import Svg, { Circle } from "react-native-svg";
5
- function clamp(n) {
6
- return Math.max(0, Math.min(1, n));
7
- }
8
- function ProgressCircle({ value = 0, size = 64, strokeWidth = 6, color, trackColor, children, testID, }) {
6
+ function ProgressCircle({ value = 0, size = 64, strokeWidth = 6, color, trackColor, children, style, ...props }) {
7
+ const theme = useTheme();
8
+ const progress = normalizeProgress(value);
9
9
  const radius = (size - strokeWidth) / 2;
10
10
  const circumference = 2 * Math.PI * radius;
11
- const offset = circumference * (1 - clamp(value));
11
+ const offset = circumference * (1 - progress);
12
12
  const center = size / 2;
13
- return (_jsxs(View, { accessibilityRole: "progressbar", accessibilityValue: { now: Math.round(clamp(value) * 100), min: 0, max: 100 }, style: { width: size, height: size, alignItems: "center", justifyContent: "center" }, testID: testID, children: [_jsxs(Svg, { height: size, style: { position: "absolute" }, width: size, children: [_jsx(Circle, { cx: center, cy: center, fill: "none", r: radius, stroke: trackColor ?? theme.bg.muted, strokeWidth: strokeWidth }), _jsx(Circle, { cx: center, cy: center, fill: "none", r: radius, stroke: color ?? theme.primary, strokeDasharray: `${circumference} ${circumference}`, strokeDashoffset: offset, strokeLinecap: "round", strokeWidth: strokeWidth, transform: `rotate(-90 ${center} ${center})` })] }), children] }));
13
+ return (_jsxs(View, { ...props, accessibilityRole: "progressbar", accessibilityValue: { now: Math.round(progress * 100), min: 0, max: 100 }, accessible: true, style: [
14
+ { width: size, height: size, alignItems: "center", justifyContent: "center" },
15
+ style,
16
+ ], children: [_jsxs(Svg, { height: size, style: { position: "absolute" }, width: size, children: [_jsx(Circle, { cx: center, cy: center, fill: "none", r: radius, stroke: trackColor ?? theme.bg.muted, strokeWidth: strokeWidth }), _jsx(Circle, { cx: center, cy: center, fill: "none", r: radius, stroke: color ?? theme.primary, strokeDasharray: `${circumference} ${circumference}`, strokeDashoffset: offset, strokeLinecap: "round", strokeWidth: strokeWidth, transform: `rotate(-90 ${center} ${center})` })] }), children] }));
14
17
  }
15
18
  export default ProgressCircle;
@@ -1,11 +1,11 @@
1
- import { type DimensionValue, type ViewStyle } from "react-native";
2
- export interface SkeletonProps {
1
+ import { type DimensionValue, type ViewProps } from "react-native";
2
+ export interface SkeletonProps extends Omit<ViewProps, "children"> {
3
3
  width?: DimensionValue;
4
4
  height?: DimensionValue;
5
5
  radius?: number;
6
6
  circle?: boolean;
7
7
  className?: string;
8
- style?: ViewStyle;
8
+ animated?: boolean;
9
9
  }
10
- declare function Skeleton({ width, height, radius, circle, className, style, }: SkeletonProps): import("react").JSX.Element;
10
+ declare function Skeleton({ width, height, radius, circle, className, style, animated, ...props }: SkeletonProps): import("react").JSX.Element;
11
11
  export default Skeleton;
@@ -1,30 +1,42 @@
1
1
  import { jsx as _jsx } from "nativewind/jsx-runtime";
2
2
  import { cn } from "../theme/tw.js";
3
3
  import { motion } from "../tokens/motion.js";
4
+ import { useReducedMotion } from "../utils/useReducedMotion.js";
4
5
  import { useEffect, useRef } from "react";
5
6
  import { Animated, Easing } from "react-native";
6
- function Skeleton({ width = "100%", height = 16, radius = 8, circle = false, className, style, }) {
7
+ function Skeleton({ width = "100%", height = 16, radius = 8, circle = false, className, style, animated = true, ...props }) {
8
+ const reducedMotion = useReducedMotion();
9
+ const shouldAnimate = animated && !reducedMotion;
7
10
  // eslint-disable-next-line react-hooks/refs
8
11
  const opacity = useRef(new Animated.Value(0.5)).current;
9
12
  useEffect(() => {
13
+ opacity.setValue(0.5);
14
+ if (!shouldAnimate)
15
+ return;
10
16
  const pulse = Animated.loop(Animated.sequence([
11
17
  Animated.timing(opacity, {
12
18
  toValue: 1,
13
19
  duration: motion.duration.slow,
14
20
  easing: Easing.inOut(Easing.ease),
15
21
  useNativeDriver: true,
22
+ isInteraction: false,
16
23
  }),
17
24
  Animated.timing(opacity, {
18
25
  toValue: 0.5,
19
26
  duration: motion.duration.slow,
20
27
  easing: Easing.inOut(Easing.ease),
21
28
  useNativeDriver: true,
29
+ isInteraction: false,
22
30
  }),
23
31
  ]));
24
32
  pulse.start();
25
33
  return () => pulse.stop();
26
- }, [opacity]);
34
+ }, [opacity, shouldAnimate]);
27
35
  const resolvedRadius = circle && typeof height === "number" ? height / 2 : radius;
28
- return (_jsx(Animated.View, { accessibilityElementsHidden: true, className: cn("bg-bg-muted", className), importantForAccessibility: "no-hide-descendants", style: [{ width, height, borderRadius: resolvedRadius }, style, { opacity }] }));
36
+ return (_jsx(Animated.View, { ...props, accessibilityElementsHidden: true, className: cn("bg-bg-muted", className), importantForAccessibility: "no-hide-descendants", style: [
37
+ { width, height, borderRadius: resolvedRadius },
38
+ style,
39
+ { opacity: shouldAnimate ? opacity : 0.5 },
40
+ ] }));
29
41
  }
30
42
  export default Skeleton;
@@ -1,14 +1,14 @@
1
1
  import type React from "react";
2
- import { type ViewStyle } from "react-native";
2
+ import { type ViewProps } from "react-native";
3
3
  export type SpinnerVariant = "spinner" | "dots" | "ripple";
4
- export interface SpinnerProps {
4
+ export interface SpinnerProps extends Omit<ViewProps, "children" | "accessibilityRole" | "accessibilityState"> {
5
5
  /** Visual style of the loading indicator. Defaults to `"spinner"`. */
6
6
  variant?: SpinnerVariant;
7
7
  size?: "small" | "large";
8
8
  color?: string;
9
9
  text?: string;
10
- style?: ViewStyle;
11
10
  className?: string;
11
+ animated?: boolean;
12
12
  }
13
13
  declare const Spinner: React.FC<SpinnerProps>;
14
14
  export default Spinner;
@@ -1,11 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "nativewind/jsx-runtime";
2
- import { theme } from "../theme/index.js";
2
+ import { useTheme } from "../theme/index.js";
3
3
  import { cn } from "../theme/tw.js";
4
4
  import { Typography } from "../Typography/index.js";
5
+ import { useReducedMotion } from "../utils/useReducedMotion.js";
5
6
  import { useEffect } from "react";
6
7
  import { ActivityIndicator, View } from "react-native";
7
- import Animated, { Easing, useAnimatedStyle, useSharedValue, withDelay, withRepeat, withSequence, withTiming, } from "react-native-reanimated";
8
- function DotsLoader({ color, size }) {
8
+ import Animated, { cancelAnimation, Easing, useAnimatedStyle, useSharedValue, withDelay, withRepeat, withSequence, withTiming, } from "react-native-reanimated";
9
+ function DotsLoader({ color, size, animated }) {
9
10
  const dotSize = size === "large" ? 11 : 8;
10
11
  const bounce = size === "large" ? 13 : 9;
11
12
  const dot = {
@@ -18,24 +19,42 @@ function DotsLoader({ color, size }) {
18
19
  const y2 = useSharedValue(0);
19
20
  const y3 = useSharedValue(0);
20
21
  useEffect(() => {
22
+ y1.value = 0;
23
+ y2.value = 0;
24
+ y3.value = 0;
25
+ if (!animated)
26
+ return;
21
27
  const seq = withRepeat(withSequence(withTiming(-bounce, { duration: 280, easing: Easing.out(Easing.quad) }), withTiming(0, { duration: 280, easing: Easing.in(Easing.quad) }), withTiming(0, { duration: 200 })), -1, false);
22
28
  y1.value = withDelay(0, seq);
23
29
  y2.value = withDelay(160, seq);
24
30
  y3.value = withDelay(320, seq);
25
- }, []);
31
+ return () => {
32
+ cancelAnimation(y1);
33
+ cancelAnimation(y2);
34
+ cancelAnimation(y3);
35
+ };
36
+ }, [animated, bounce, y1, y2, y3]);
26
37
  const s1 = useAnimatedStyle(() => ({ transform: [{ translateY: y1.value }] }));
27
38
  const s2 = useAnimatedStyle(() => ({ transform: [{ translateY: y2.value }] }));
28
39
  const s3 = useAnimatedStyle(() => ({ transform: [{ translateY: y3.value }] }));
29
40
  return (_jsxs(View, { style: { flexDirection: "row", gap: 10, alignItems: "center" }, children: [_jsx(Animated.View, { style: [dot, s1] }), _jsx(Animated.View, { style: [dot, s2] }), _jsx(Animated.View, { style: [dot, s3] })] }));
30
41
  }
31
- function RippleLoader({ color, size }) {
42
+ function RippleLoader({ color, size, animated }) {
32
43
  const base = size === "large" ? 48 : 32;
33
44
  const scale = useSharedValue(0.2);
34
45
  const opacity = useSharedValue(0.85);
35
46
  useEffect(() => {
47
+ scale.value = animated ? 0.2 : 1;
48
+ opacity.value = 0.85;
49
+ if (!animated)
50
+ return;
36
51
  scale.value = withRepeat(withTiming(2.6, { duration: 1400, easing: Easing.out(Easing.quad) }), -1, false);
37
52
  opacity.value = withRepeat(withTiming(0, { duration: 1400, easing: Easing.in(Easing.quad) }), -1, false);
38
- }, []);
53
+ return () => {
54
+ cancelAnimation(scale);
55
+ cancelAnimation(opacity);
56
+ };
57
+ }, [animated, scale, opacity]);
39
58
  const animStyle = useAnimatedStyle(() => ({
40
59
  transform: [{ scale: scale.value }],
41
60
  opacity: opacity.value,
@@ -52,8 +71,20 @@ function RippleLoader({ color, size }) {
52
71
  animStyle,
53
72
  ] }) }));
54
73
  }
55
- const Spinner = ({ variant = "spinner", size = "large", color = theme.primary, text, style, className, }) => {
56
- const indicator = variant === "dots" ? (_jsx(DotsLoader, { color: color, size: size })) : variant === "ripple" ? (_jsx(RippleLoader, { color: color, size: size })) : (_jsx(ActivityIndicator, { color: color, size: size }));
57
- return (_jsxs(View, { className: cn("items-center justify-center", className), style: style, children: [indicator, text && (_jsx(Typography.Text, { className: "mt-2.5 text-base", variant: "body", children: text }))] }));
74
+ const Spinner = ({ variant = "spinner", size = "large", color, text, style, className, animated = true, accessibilityLabel = text ?? "Cargando", ...props }) => {
75
+ const theme = useTheme();
76
+ const reducedMotion = useReducedMotion();
77
+ const shouldAnimate = animated && !reducedMotion;
78
+ const resolvedColor = color ?? theme.primary;
79
+ const staticSize = size === "large" ? 36 : 20;
80
+ const indicator = variant === "dots" ? (_jsx(DotsLoader, { animated: shouldAnimate, color: resolvedColor, size: size })) : variant === "ripple" ? (_jsx(RippleLoader, { animated: shouldAnimate, color: resolvedColor, size: size })) : shouldAnimate ? (_jsx(ActivityIndicator, { color: resolvedColor, size: size })) : (_jsx(View, { style: {
81
+ width: staticSize,
82
+ height: staticSize,
83
+ borderRadius: staticSize / 2,
84
+ borderWidth: 2,
85
+ borderColor: resolvedColor,
86
+ borderTopColor: "transparent",
87
+ } }));
88
+ return (_jsxs(View, { ...props, accessibilityLabel: accessibilityLabel, accessibilityRole: "progressbar", accessibilityState: { busy: true }, accessible: true, className: cn("items-center justify-center", className), style: style, children: [_jsx(View, { accessibilityElementsHidden: true, importantForAccessibility: "no-hide-descendants", children: indicator }), text && (_jsx(Typography.Text, { className: "mt-2.5 text-base", variant: "body", children: text }))] }));
58
89
  };
59
90
  export default Spinner;
@@ -1,8 +1,8 @@
1
+ import type { ImageSource } from "../utils/image.js";
1
2
  import type { ReactNode } from "react";
2
- import { type ImageSourcePropType } from "react-native";
3
3
  interface SplashScreenProps {
4
4
  isReady: boolean;
5
- logo?: ImageSourcePropType;
5
+ logo?: ImageSource;
6
6
  minDuration?: number;
7
7
  message?: string;
8
8
  /** `default` keeps the legacy look; `hero` is the modern dark gradient + dotted texture. */
@@ -1,20 +1,22 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "nativewind/jsx-runtime";
2
+ import ExpoImage from "../Display/Display.ExpoImage.js";
2
3
  import GradientSurface from "../Display/Display.GradientSurface.js";
3
4
  import Pattern from "../Display/Display.Pattern.js";
4
5
  import Loading from "./Feedback.Loading.js";
6
+ import Spinner from "./Feedback.Spinner.js";
5
7
  import GradientContainer from "../layouts/GradientContainer.js";
6
8
  import { theme } from "../theme/index.js";
7
9
  import { Typography } from "../Typography/index.js";
8
10
  import { useEffect, useRef, useState } from "react";
9
- import { ActivityIndicator, Image, View } from "react-native";
11
+ import { View } from "react-native";
10
12
  function renderLogo(logo) {
11
13
  if (!logo) {
12
14
  return null;
13
15
  }
14
- return _jsx(Image, { className: "w-[250px] h-[200px] mb-12", resizeMode: "contain", source: logo });
16
+ return (_jsx(ExpoImage, { className: "w-[250px] h-[200px] mb-12", contentFit: "contain", source: logo }));
15
17
  }
16
18
  function HeroSplash({ logo, message, title, subtitle, }) {
17
- return (_jsxs(GradientSurface, { className: "flex-1 items-center justify-center", preset: "warm", radius: "none", children: [_jsx(Pattern, { color: theme.primary, kind: "dots", opacity: 0.16, size: 26 }), _jsxs(View, { className: "items-center", children: [logo ? (_jsx(Image, { className: "w-28 h-28 mb-7", resizeMode: "contain", source: logo, style: { shadowColor: theme.primary, shadowOpacity: 0.7, shadowRadius: 28 } })) : null, title ? (_jsx(Typography.Text, { bold: true, style: { color: "#FFFFFF", fontSize: 28, letterSpacing: 1 }, children: title })) : null, subtitle ? (_jsx(Typography.Text, { style: { color: "rgba(255,255,255,0.6)", fontSize: 13, marginTop: 6 }, children: subtitle })) : null, _jsx(View, { className: "mt-10", children: _jsx(ActivityIndicator, { accessibilityLabel: message, color: theme.primary, size: "small" }) })] })] }));
19
+ return (_jsxs(GradientSurface, { className: "flex-1 items-center justify-center", preset: "warm", radius: "none", children: [_jsx(Pattern, { color: theme.primary, kind: "dots", opacity: 0.16, size: 26 }), _jsxs(View, { className: "items-center", children: [logo ? (_jsx(ExpoImage, { className: "w-28 h-28 mb-7", contentFit: "contain", source: logo, style: { shadowColor: theme.primary, shadowOpacity: 0.7, shadowRadius: 28 } })) : null, title ? (_jsx(Typography.Text, { bold: true, style: { color: "#FFFFFF", fontSize: 28, letterSpacing: 1 }, children: title })) : null, subtitle ? (_jsx(Typography.Text, { style: { color: "rgba(255,255,255,0.6)", fontSize: 13, marginTop: 6 }, children: subtitle })) : null, _jsx(View, { className: "mt-10", children: _jsx(Spinner, { accessibilityLabel: message, color: theme.primary, size: "small" }) })] })] }));
18
20
  }
19
21
  function Splash({ isReady, logo, minDuration = 800, message = "Inicializando...", variant = "default", title, subtitle, children, }) {
20
22
  const [showContent, setShowContent] = useState(false);