@sincpro/mobile-ui 1.0.1 → 2.0.0

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 (168) hide show
  1. package/README.md +701 -231
  2. package/dist/Actions/Actions.Button.d.ts +15 -12
  3. package/dist/Actions/Actions.Button.js +25 -14
  4. package/dist/Actions/Actions.Dock.d.ts +20 -0
  5. package/dist/Actions/Actions.Dock.js +89 -0
  6. package/dist/Actions/Actions.FAB.d.ts +2 -1
  7. package/dist/Actions/Actions.FAB.js +5 -3
  8. package/dist/Actions/Actions.FilterButton.d.ts +10 -2
  9. package/dist/Actions/Actions.FilterButton.js +21 -6
  10. package/dist/Actions/Actions.IconButton.d.ts +4 -2
  11. package/dist/Actions/Actions.IconButton.js +7 -3
  12. package/dist/Actions/index.d.ts +6 -2
  13. package/dist/Actions/index.js +4 -1
  14. package/dist/Dialog/BottomSheet/BottomSheet.Root.js +22 -34
  15. package/dist/Dialog/Dialog.ActionSheet.d.ts +2 -1
  16. package/dist/Dialog/Dialog.ActionSheet.js +17 -21
  17. package/dist/Dialog/Dialog.Confirmation.js +8 -6
  18. package/dist/Dialog/Dialog.EditValue.js +3 -1
  19. package/dist/Dialog/Dialog.NativeSheet.d.ts +10 -0
  20. package/dist/Dialog/Dialog.NativeSheet.js +21 -0
  21. package/dist/Dialog/Dialog.Sheet.d.ts +5 -1
  22. package/dist/Dialog/Dialog.Sheet.js +30 -31
  23. package/dist/Display/Display.Accordion.js +7 -3
  24. package/dist/Display/Display.AmountSummary.d.ts +28 -0
  25. package/dist/Display/Display.AmountSummary.js +25 -0
  26. package/dist/Display/Display.Carousel.d.ts +27 -4
  27. package/dist/Display/Display.Carousel.js +112 -12
  28. package/dist/Display/Display.CopyableText.d.ts +9 -2
  29. package/dist/Display/Display.CopyableText.js +75 -7
  30. package/dist/Display/Display.ExpoImage.d.ts +8 -0
  31. package/dist/Display/Display.ExpoImage.js +15 -0
  32. package/dist/Display/Display.Icon.js +2 -1
  33. package/dist/Display/Display.Image.d.ts +10 -6
  34. package/dist/Display/Display.Image.js +39 -6
  35. package/dist/Display/Display.KeyValue.d.ts +11 -4
  36. package/dist/Display/Display.KeyValue.js +6 -4
  37. package/dist/Display/Display.ListGroup.d.ts +23 -0
  38. package/dist/Display/Display.ListGroup.js +17 -0
  39. package/dist/Display/Display.ListItem.d.ts +15 -2
  40. package/dist/Display/Display.ListItem.js +25 -8
  41. package/dist/Display/Display.Logo.d.ts +2 -2
  42. package/dist/Display/Display.Logo.js +3 -2
  43. package/dist/Display/Display.MediaCard.d.ts +2 -2
  44. package/dist/Display/Display.MediaEmbed.d.ts +2 -2
  45. package/dist/Display/Display.ProductCard.d.ts +2 -2
  46. package/dist/Display/Display.SectionHeader.d.ts +11 -1
  47. package/dist/Display/Display.SectionHeader.js +11 -2
  48. package/dist/Display/Display.Story.d.ts +3 -3
  49. package/dist/Display/Display.Story.js +5 -4
  50. package/dist/Display/Display.StoryTray.d.ts +4 -4
  51. package/dist/Display/Display.StoryTray.js +3 -2
  52. package/dist/Display/Display.StoryViewer.d.ts +3 -3
  53. package/dist/Display/Display.StoryViewer.js +6 -3
  54. package/dist/Display/Display.SwipeableRow.d.ts +3 -1
  55. package/dist/Display/Display.SwipeableRow.js +3 -3
  56. package/dist/Display/index.d.ts +13 -0
  57. package/dist/Display/index.js +6 -0
  58. package/dist/Feedback/Feedback.Banner.js +1 -1
  59. package/dist/Feedback/Feedback.DomainSplashScreen.d.ts +13 -4
  60. package/dist/Feedback/Feedback.DomainSplashScreen.js +68 -53
  61. package/dist/Feedback/Feedback.EmptyState.d.ts +2 -2
  62. package/dist/Feedback/Feedback.EmptyState.js +3 -2
  63. package/dist/Feedback/Feedback.Error.d.ts +2 -1
  64. package/dist/Feedback/Feedback.Error.js +4 -3
  65. package/dist/Feedback/Feedback.Loading.d.ts +6 -2
  66. package/dist/Feedback/Feedback.Loading.js +2 -2
  67. package/dist/Feedback/Feedback.Progress.d.ts +6 -3
  68. package/dist/Feedback/Feedback.Progress.js +29 -19
  69. package/dist/Feedback/Feedback.ProgressCircle.d.ts +3 -2
  70. package/dist/Feedback/Feedback.ProgressCircle.js +10 -7
  71. package/dist/Feedback/Feedback.Skeleton.d.ts +4 -4
  72. package/dist/Feedback/Feedback.Skeleton.js +15 -3
  73. package/dist/Feedback/Feedback.Spinner.d.ts +3 -3
  74. package/dist/Feedback/Feedback.Spinner.js +40 -9
  75. package/dist/Feedback/Feedback.Splash.d.ts +2 -2
  76. package/dist/Feedback/Feedback.Splash.js +5 -3
  77. package/dist/Feedback/Toast.context.d.ts +64 -3
  78. package/dist/Feedback/Toast.context.js +112 -31
  79. package/dist/Feedback/index.d.ts +15 -1
  80. package/dist/Feedback/index.js +4 -1
  81. package/dist/Form/Form.Checkbox.d.ts +3 -1
  82. package/dist/Form/Form.Checkbox.js +2 -2
  83. package/dist/Form/Form.DatePicker.js +3 -2
  84. package/dist/Form/Form.Dropdown.d.ts +15 -3
  85. package/dist/Form/Form.Dropdown.js +21 -6
  86. package/dist/Form/Form.ErrorSummary.d.ts +23 -0
  87. package/dist/Form/Form.ErrorSummary.js +53 -0
  88. package/dist/Form/Form.FilterButton.d.ts +1 -1
  89. package/dist/Form/Form.Input.d.ts +4 -3
  90. package/dist/Form/Form.Input.js +2 -2
  91. package/dist/Form/Form.KeyboardForm.d.ts +11 -0
  92. package/dist/Form/Form.KeyboardForm.js +9 -0
  93. package/dist/Form/Form.KeyboardToolbar.d.ts +21 -0
  94. package/dist/Form/Form.KeyboardToolbar.js +70 -0
  95. package/dist/Form/Form.Select.d.ts +2 -1
  96. package/dist/Form/Form.Select.js +26 -26
  97. package/dist/Form/index.d.ts +9 -0
  98. package/dist/Form/index.js +6 -0
  99. package/dist/Navigation/Navigation.AppBar.js +8 -6
  100. package/dist/Navigation/Navigation.BottomNav.js +10 -5
  101. package/dist/Navigation/Navigation.Header.d.ts +2 -2
  102. package/dist/Navigation/Navigation.Menu.js +10 -3
  103. package/dist/Navigation/Navigation.RowItem.d.ts +1 -1
  104. package/dist/Navigation/Navigation.RowItem.js +2 -2
  105. package/dist/Navigation/Navigation.Search.d.ts +41 -0
  106. package/dist/Navigation/Navigation.Search.js +47 -0
  107. package/dist/Navigation/Navigation.SearchBar.d.ts +5 -1
  108. package/dist/Navigation/Navigation.SearchBar.js +25 -7
  109. package/dist/Navigation/Navigation.SearchSuggestions.d.ts +14 -0
  110. package/dist/Navigation/Navigation.SearchSuggestions.js +7 -0
  111. package/dist/Navigation/Navigation.SegmentedControl.d.ts +11 -1
  112. package/dist/Navigation/Navigation.SegmentedControl.js +29 -15
  113. package/dist/Navigation/index.d.ts +13 -0
  114. package/dist/Navigation/index.js +4 -0
  115. package/dist/Typography/Typography.Heading.js +1 -1
  116. package/dist/Typography/Typography.SelectableText.d.ts +10 -0
  117. package/dist/Typography/Typography.SelectableText.js +16 -0
  118. package/dist/Typography/Typography.Text.js +4 -20
  119. package/dist/Typography/index.d.ts +3 -0
  120. package/dist/Typography/index.js +3 -0
  121. package/dist/branding.d.ts +3 -2
  122. package/dist/layouts/AuthContainer.d.ts +4 -1
  123. package/dist/layouts/AuthContainer.js +3 -3
  124. package/dist/primitives/BaseButton.d.ts +15 -12
  125. package/dist/primitives/BaseButton.js +21 -9
  126. package/dist/theme/typography.d.ts +16 -1
  127. package/dist/theme/typography.js +19 -0
  128. package/dist/utils/criteria.d.ts +56 -0
  129. package/dist/utils/criteria.js +99 -0
  130. package/dist/utils/image.d.ts +3 -0
  131. package/dist/utils/image.js +13 -0
  132. package/dist/utils/keys.d.ts +1 -0
  133. package/dist/utils/keys.js +7 -0
  134. package/dist/utils/progress.d.ts +1 -0
  135. package/dist/utils/progress.js +5 -0
  136. package/dist/utils/reorder.d.ts +4 -0
  137. package/dist/utils/reorder.js +29 -0
  138. package/dist/utils/useReducedMotion.d.ts +1 -0
  139. package/dist/utils/useReducedMotion.js +43 -0
  140. package/dist/utils/useSheetTranslation.d.ts +5 -0
  141. package/dist/utils/useSheetTranslation.js +23 -0
  142. package/dist/views/AppSplashView.d.ts +3 -2
  143. package/dist/views/AppSplashView.js +3 -2
  144. package/dist/views/AuthFormView.d.ts +6 -3
  145. package/dist/views/AuthFormView.js +2 -2
  146. package/dist/views/FormViewV2.d.ts +9 -4
  147. package/dist/views/FormViewV2.js +35 -16
  148. package/dist/views/ListViewV2.context.d.ts +3 -1
  149. package/dist/views/ListViewV2.context.js +41 -21
  150. package/dist/views/ListViewV2.d.ts +18 -7
  151. package/dist/views/ListViewV2.js +69 -35
  152. package/dist/views/types/IListView.js +6 -5
  153. package/dist/widgets/CollectionOptions.d.ts +15 -0
  154. package/dist/widgets/CollectionOptions.js +129 -0
  155. package/dist/widgets/FilterBar.d.ts +21 -0
  156. package/dist/widgets/FilterBar.js +14 -0
  157. package/dist/widgets/FilterBuilder.d.ts +10 -0
  158. package/dist/widgets/FilterBuilder.js +115 -0
  159. package/dist/widgets/HomeHeader.d.ts +2 -2
  160. package/dist/widgets/PromoModal.js +3 -1
  161. package/dist/widgets/QueryChoice.d.ts +10 -0
  162. package/dist/widgets/QueryChoice.js +13 -0
  163. package/dist/widgets/ReorderableList.d.ts +51 -0
  164. package/dist/widgets/ReorderableList.js +138 -0
  165. package/dist/widgets/ScreenHeader.d.ts +2 -2
  166. package/dist/widgets/index.d.ts +4 -0
  167. package/dist/widgets/index.js +3 -0
  168. package/package.json +18 -1
@@ -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);
@@ -1,22 +1,83 @@
1
+ import { type PressableProps } from "../primitives/Pressable.js";
1
2
  import { type ReactNode } from "react";
3
+ import { type StyleProp, type ViewProps, type ViewStyle } from "react-native";
2
4
  export type ToastVariant = "info" | "success" | "warning" | "danger" | "loading";
3
5
  export interface ToastAction {
4
6
  label: string;
5
7
  onPress: () => void;
6
8
  }
9
+ export interface ToastRenderProps {
10
+ id: string;
11
+ dismiss: () => void;
12
+ update: (options: Partial<ToastOptions>) => void;
13
+ }
7
14
  export interface ToastOptions {
8
15
  message: string;
9
16
  title?: string;
10
17
  variant?: ToastVariant;
11
18
  duration?: number;
12
19
  action?: ToastAction;
20
+ className?: string;
21
+ render?: (controls: ToastRenderProps) => ReactNode;
13
22
  /** Stable id: calling show()/info()/… again with the same id updates the existing toast
14
23
  * (and resets its timer) instead of stacking a duplicate. */
15
24
  id?: string;
16
- /** Render a close (✕) button. Defaults to true for persistent toasts (duration 0 / loading),
17
- * false for auto-dismissing ones. */
25
+ /** Render a close (✕) button. Defaults to true, including auto-dismissing toasts. */
18
26
  dismissible?: boolean;
19
27
  }
28
+ export type ToastCustomOptions = Omit<ToastOptions, "message" | "render"> & {
29
+ message?: string;
30
+ render: (controls: ToastRenderProps) => ReactNode;
31
+ };
32
+ export interface ToastRootProps extends Omit<ViewProps, "children"> {
33
+ children: ReactNode;
34
+ accessibilityHint?: string;
35
+ className?: string;
36
+ position?: "top" | "bottom";
37
+ style?: StyleProp<ViewStyle>;
38
+ testID?: string;
39
+ }
40
+ declare function ToastRoot({ children, accessibilityHint, className, position, style, testID, ...viewProps }: ToastRootProps): import("react").JSX.Element;
41
+ export interface ToastIconProps {
42
+ variant?: ToastVariant;
43
+ icon?: ReactNode;
44
+ color?: string;
45
+ size?: number;
46
+ }
47
+ declare function ToastIcon({ variant, icon, color, size }: ToastIconProps): import("react").JSX.Element;
48
+ export interface ToastContentProps {
49
+ children: ReactNode;
50
+ className?: string;
51
+ style?: StyleProp<ViewStyle>;
52
+ }
53
+ declare function ToastContent({ children, className, style }: ToastContentProps): import("react").JSX.Element;
54
+ export interface ToastTextProps {
55
+ children: ReactNode;
56
+ className?: string;
57
+ }
58
+ declare function ToastTitle({ children, className }: ToastTextProps): import("react").JSX.Element;
59
+ declare function ToastDescription({ children, className }: ToastTextProps): import("react").JSX.Element;
60
+ export interface ToastActionProps extends Omit<PressableProps, "children" | "onPress"> {
61
+ label: string;
62
+ onPress: () => void;
63
+ className?: string;
64
+ }
65
+ declare function ToastActionButton({ label, onPress, className, style, ...props }: ToastActionProps): import("react").JSX.Element;
66
+ export interface ToastCloseProps extends Omit<PressableProps, "children" | "onPress"> {
67
+ onPress: () => void;
68
+ accessibilityLabel?: string;
69
+ className?: string;
70
+ }
71
+ declare function ToastClose({ onPress, accessibilityLabel, className, style, ...props }: ToastCloseProps): import("react").JSX.Element;
72
+ export declare const Toast: {
73
+ Root: typeof ToastRoot;
74
+ Icon: typeof ToastIcon;
75
+ Content: typeof ToastContent;
76
+ Title: typeof ToastTitle;
77
+ Description: typeof ToastDescription;
78
+ Action: typeof ToastActionButton;
79
+ Close: typeof ToastClose;
80
+ };
20
81
  type MessageOrFn<T> = string | ((value: T) => string);
21
82
  export interface ToastPromiseMessages<T> {
22
83
  loading: string;
@@ -24,7 +85,7 @@ export interface ToastPromiseMessages<T> {
24
85
  error: MessageOrFn<unknown>;
25
86
  }
26
87
  export interface ToastContextValue {
27
- show: (options: ToastOptions) => string;
88
+ show: (options: ToastOptions | ToastCustomOptions) => string;
28
89
  update: (id: string, options: Partial<ToastOptions>) => void;
29
90
  hide: (id: string) => void;
30
91
  info: (message: string, options?: Omit<ToastOptions, "message" | "variant">) => string;
@@ -1,13 +1,63 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "nativewind/jsx-runtime";
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "nativewind/jsx-runtime";
2
2
  import Icon from "../Display/Display.Icon.js";
3
+ import Spinner from "./Feedback.Spinner.js";
3
4
  import Pressable from "../primitives/Pressable.js";
4
- import { theme } from "../theme/index.js";
5
- import { tv } from "../theme/tw.js";
5
+ import { useTheme } from "../theme/index.js";
6
+ import { cn, tv } from "../theme/tw.js";
6
7
  import { Typography } from "../Typography/index.js";
7
- import { createContext, useCallback, useContext, useMemo, useRef, useState, } from "react";
8
- import { ActivityIndicator, View } from "react-native";
8
+ import { useReducedMotion } from "../utils/useReducedMotion.js";
9
+ import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, } from "react";
10
+ import { View } from "react-native";
11
+ import { GestureDetector, usePanGesture } from "react-native-gesture-handler";
9
12
  import Animated, { FadeInDown, FadeInUp, FadeOutDown, FadeOutUp, } from "react-native-reanimated";
10
13
  import { useSafeAreaInsets } from "react-native-safe-area-context";
14
+ function ToastRoot({ children, accessibilityHint = "Usa Cerrar o desliza hacia arriba o abajo para cerrar", className, position = "top", style, testID, ...viewProps }) {
15
+ const reducedMotion = useReducedMotion();
16
+ const styles = toast();
17
+ const entering = position === "top" ? FadeInDown : FadeInUp;
18
+ const exiting = position === "top" ? FadeOutUp : FadeOutDown;
19
+ return (_jsx(Animated.View, { ...viewProps, accessibilityHint: accessibilityHint, accessibilityLiveRegion: "polite", accessibilityRole: "alert", className: styles.root({ className }), entering: reducedMotion ? undefined : entering.duration(220), exiting: reducedMotion ? undefined : exiting.duration(180), style: style, testID: testID, children: children }));
20
+ }
21
+ function ToastIcon({ variant = "info", icon, color, size = 22 }) {
22
+ const theme = useTheme();
23
+ if (icon)
24
+ return _jsx(_Fragment, { children: icon });
25
+ if (variant === "loading") {
26
+ return (_jsx(Spinner, { accessibilityLabel: "Cargando", color: color ?? theme.text.accent, size: "small" }));
27
+ }
28
+ const colors = {
29
+ info: theme.info,
30
+ success: theme.success,
31
+ warning: theme.warning,
32
+ danger: theme.danger,
33
+ };
34
+ return _jsx(Icon, { color: color ?? colors[variant], name: iconByVariant[variant], size: size });
35
+ }
36
+ function ToastContent({ children, className, style }) {
37
+ return (_jsx(View, { className: cn("flex-1", className), style: style, children: children }));
38
+ }
39
+ function ToastTitle({ children, className }) {
40
+ return (_jsx(Typography.Text, { className: cn("text-sm font-semibold text-text-primary", className), children: children }));
41
+ }
42
+ function ToastDescription({ children, className }) {
43
+ return (_jsx(Typography.Text, { className: cn("text-sm text-text-secondary", className), children: children }));
44
+ }
45
+ function ToastActionButton({ label, onPress, className, style, ...props }) {
46
+ return (_jsx(Pressable, { ...props, accessibilityLabel: label, accessibilityRole: "button", className: cn("min-h-12 justify-center px-2", className), onPress: onPress, style: [{ minHeight: 48, minWidth: 48 }, style], children: _jsx(Typography.Text, { className: "text-sm font-semibold text-text-accent", children: label }) }));
47
+ }
48
+ function ToastClose({ onPress, accessibilityLabel = "Cerrar", className, style, ...props }) {
49
+ const theme = useTheme();
50
+ return (_jsx(Pressable, { ...props, accessibilityLabel: accessibilityLabel, accessibilityRole: "button", className: cn("min-h-12 min-w-12 items-center justify-center", className), hitSlop: 8, onPress: onPress, style: [{ minHeight: 48, minWidth: 48 }, style], children: _jsx(Icon, { color: theme.text.tertiary, name: "close", size: 18 }) }));
51
+ }
52
+ export const Toast = {
53
+ Root: ToastRoot,
54
+ Icon: ToastIcon,
55
+ Content: ToastContent,
56
+ Title: ToastTitle,
57
+ Description: ToastDescription,
58
+ Action: ToastActionButton,
59
+ Close: ToastClose,
60
+ };
11
61
  const ToastContext = createContext(null);
12
62
  const DEFAULT_DURATION = 3500;
13
63
  // iOS-style card: rounded, hairline border + soft shadow, neutral surface. The variant is
@@ -20,39 +70,54 @@ const toast = tv({
20
70
  },
21
71
  });
22
72
  const iconByVariant = {
23
- info: { name: "information-circle", color: theme.info },
24
- success: { name: "checkmark-circle", color: theme.success },
25
- warning: { name: "warning", color: theme.warning },
26
- danger: { name: "close-circle", color: theme.danger },
73
+ info: "information-circle",
74
+ success: "checkmark-circle",
75
+ warning: "warning",
76
+ danger: "close-circle",
27
77
  };
28
- function ToastItem({ entry, onHide, position, }) {
78
+ function ToastItem({ entry, onHide, onUpdate, position, }) {
29
79
  const variant = entry.variant ?? "info";
30
- const styles = toast();
31
- const entering = position === "top" ? FadeInDown : FadeInUp;
32
- const exiting = position === "top" ? FadeOutUp : FadeOutDown;
33
- const resolvedDuration = entry.duration ?? (variant === "loading" ? 0 : DEFAULT_DURATION);
34
- const showClose = entry.dismissible ?? resolvedDuration === 0;
35
- return (_jsxs(Animated.View, { accessibilityLiveRegion: "polite", accessible: true, className: styles.root(), entering: entering.duration(220), exiting: exiting.duration(180), children: [variant === "loading" ? (_jsx(ActivityIndicator, { color: theme.text.accent, size: "small" })) : (_jsx(Icon, { color: iconByVariant[variant].color, name: iconByVariant[variant].name, size: 22 })), _jsxs(View, { className: "flex-1", children: [entry.title ? (_jsx(Typography.Text, { className: styles.title(), children: entry.title })) : null, _jsx(Typography.Text, { className: styles.message(), children: entry.message })] }), entry.action ? (_jsx(Pressable, { accessibilityLabel: entry.action.label, onPress: () => {
36
- entry.action?.onPress();
37
- onHide(entry.id);
38
- }, children: _jsx(Typography.Text, { className: "text-text-accent text-sm font-semibold", children: entry.action.label }) })) : null, showClose ? (_jsx(Pressable, { accessibilityLabel: "Cerrar", hitSlop: 8, onPress: () => onHide(entry.id), children: _jsx(Icon, { color: theme.text.tertiary, name: "close", size: 18 }) })) : null] }));
80
+ const showClose = entry.dismissible ?? true;
81
+ const dismiss = () => onHide(entry.id);
82
+ const pan = usePanGesture({
83
+ activeOffsetY: [-12, 12],
84
+ failOffsetX: [-24, 24],
85
+ onDeactivate: ({ canceled, translationY, velocityY }) => {
86
+ if (!canceled && (Math.abs(translationY) >= 48 || Math.abs(velocityY) >= 800)) {
87
+ dismiss();
88
+ }
89
+ },
90
+ });
91
+ return (_jsx(GestureDetector, { gesture: pan, children: _jsx(Toast.Root, { className: entry.className, onAccessibilityEscape: dismiss, position: position, children: entry.render ? (entry.render({
92
+ dismiss,
93
+ id: entry.id,
94
+ update: (options) => onUpdate(entry.id, options),
95
+ })) : (_jsxs(_Fragment, { children: [_jsx(Toast.Icon, { variant: variant }), _jsxs(Toast.Content, { children: [entry.title ? _jsx(Toast.Title, { children: entry.title }) : null, entry.message ? _jsx(Toast.Description, { children: entry.message }) : null] }), entry.action ? (_jsx(Toast.Action, { label: entry.action.label, onPress: () => {
96
+ entry.action?.onPress();
97
+ dismiss();
98
+ } })) : null, showClose ? _jsx(Toast.Close, { onPress: dismiss }) : null] })) }) }));
39
99
  }
40
100
  export function ToastProvider({ children, position = "bottom", }) {
41
101
  const insets = useSafeAreaInsets();
42
102
  const [toasts, setToasts] = useState([]);
103
+ const toastsRef = useRef([]);
43
104
  const counter = useRef(0);
44
105
  const timers = useRef({});
106
+ const commitToasts = useCallback((next) => {
107
+ toastsRef.current = next;
108
+ setToasts(next);
109
+ }, []);
45
110
  const clearTimer = useCallback((id) => {
46
111
  const timer = timers.current[id];
47
- if (timer) {
112
+ if (timer !== undefined) {
48
113
  clearTimeout(timer);
49
114
  delete timers.current[id];
50
115
  }
51
116
  }, []);
52
117
  const hide = useCallback((id) => {
53
- setToasts((prev) => prev.filter((t) => t.id !== id));
118
+ commitToasts(toastsRef.current.filter((t) => t.id !== id));
54
119
  clearTimer(id);
55
- }, [clearTimer]);
120
+ }, [clearTimer, commitToasts]);
56
121
  const scheduleDismiss = useCallback((id, variant, duration) => {
57
122
  clearTimer(id);
58
123
  const resolved = duration ?? (variant === "loading" ? 0 : DEFAULT_DURATION);
@@ -67,16 +132,32 @@ export function ToastProvider({ children, position = "bottom", }) {
67
132
  id = `toast-${counter.current}`;
68
133
  }
69
134
  const toastId = id;
70
- setToasts((prev) => prev.some((t) => t.id === toastId)
71
- ? prev.map((t) => (t.id === toastId ? { ...t, ...options, id: toastId } : t))
72
- : [...prev, { ...options, id: toastId }]);
73
- scheduleDismiss(toastId, options.variant, options.duration);
135
+ const current = toastsRef.current;
136
+ const currentEntry = current.find((entry) => entry.id === toastId);
137
+ const nextEntry = {
138
+ ...currentEntry,
139
+ ...options,
140
+ id: toastId,
141
+ message: options.message ?? currentEntry?.message ?? "",
142
+ };
143
+ commitToasts(currentEntry
144
+ ? current.map((entry) => (entry.id === toastId ? nextEntry : entry))
145
+ : [...current, nextEntry]);
146
+ scheduleDismiss(toastId, nextEntry.variant, nextEntry.duration);
74
147
  return toastId;
75
- }, [scheduleDismiss]);
148
+ }, [commitToasts, scheduleDismiss]);
76
149
  const update = useCallback((id, options) => {
77
- setToasts((prev) => prev.map((t) => (t.id === id ? { ...t, ...options } : t)));
78
- scheduleDismiss(id, options.variant, options.duration);
79
- }, [scheduleDismiss]);
150
+ const current = toastsRef.current.find((entry) => entry.id === id);
151
+ if (!current)
152
+ return;
153
+ const updated = { ...current, ...options, id };
154
+ commitToasts(toastsRef.current.map((entry) => (entry.id === id ? updated : entry)));
155
+ scheduleDismiss(id, updated.variant, updated.duration);
156
+ }, [commitToasts, scheduleDismiss]);
157
+ useEffect(() => () => {
158
+ Object.values(timers.current).forEach(clearTimeout);
159
+ timers.current = {};
160
+ }, []);
80
161
  const value = useMemo(() => {
81
162
  const promise = (p, messages) => {
82
163
  const id = show({ message: messages.loading, variant: "loading", duration: 0 });
@@ -110,7 +191,7 @@ export function ToastProvider({ children, position = "bottom", }) {
110
191
  promise,
111
192
  };
112
193
  }, [show, update, hide]);
113
- return (_jsxs(ToastContext.Provider, { value: value, children: [children, _jsx(View, { className: "absolute left-0 right-0 px-4 gap-2 z-toast", pointerEvents: "box-none", style: position === "top" ? { top: insets.top + 12 } : { bottom: insets.bottom + 12 }, children: toasts.map((entry) => (_jsx(ToastItem, { entry: entry, onHide: hide, position: position }, entry.id))) })] }));
194
+ return (_jsxs(ToastContext.Provider, { value: value, children: [children, _jsx(View, { className: "absolute left-0 right-0 px-4 gap-2 z-toast", pointerEvents: "box-none", style: position === "top" ? { top: insets.top + 12 } : { bottom: insets.bottom + 12 }, children: toasts.map((entry) => (_jsx(ToastItem, { entry: entry, onHide: hide, onUpdate: update, position: position }, entry.id))) })] }));
114
195
  }
115
196
  export function useToast() {
116
197
  const ctx = useContext(ToastContext);
@@ -9,7 +9,12 @@ import ProgressCircle from "./Feedback.ProgressCircle.js";
9
9
  import Skeleton from "./Feedback.Skeleton.js";
10
10
  import SplashScreen from "./Feedback.Splash.js";
11
11
  export { ErrorBoundary } from "./ErrorBoundary.js";
12
- export { type ToastAction, type ToastContextValue, type ToastOptions, type ToastPromiseMessages, ToastProvider, type ToastVariant, useToast, } from "./Toast.context.js";
12
+ export type { ProgressProps } from "./Feedback.Progress.js";
13
+ export type { ProgressCircleProps } from "./Feedback.ProgressCircle.js";
14
+ export type { SkeletonProps } from "./Feedback.Skeleton.js";
15
+ export type { SpinnerProps, SpinnerVariant, } from "./Feedback.Spinner.js";
16
+ export { Toast, type ToastAction, type ToastActionProps, type ToastCloseProps, type ToastContentProps, type ToastContextValue, type ToastCustomOptions, type ToastIconProps, type ToastOptions, type ToastPromiseMessages, ToastProvider, type ToastRenderProps, type ToastRootProps, type ToastTextProps, type ToastVariant, useToast, } from "./Toast.context.js";
17
+ export { useReducedMotion } from "../utils/useReducedMotion.js";
13
18
  export declare const Feedback: {
14
19
  Loading: typeof Loading;
15
20
  Spinner: import("react").FC<import("./Feedback.Spinner.js").SpinnerProps>;
@@ -22,5 +27,14 @@ export declare const Feedback: {
22
27
  EmptyState: typeof EmptyState;
23
28
  Error: typeof Error;
24
29
  SplashScreen: typeof SplashScreen;
30
+ Toast: {
31
+ Root: ({ children, accessibilityHint, className, position, style, testID, ...viewProps }: import("./Toast.context.js").ToastRootProps) => import("react").JSX.Element;
32
+ Icon: ({ variant, icon, color, size }: import("./Toast.context.js").ToastIconProps) => import("react").JSX.Element;
33
+ Content: ({ children, className, style }: import("./Toast.context.js").ToastContentProps) => import("react").JSX.Element;
34
+ Title: ({ children, className }: import("./Toast.context.js").ToastTextProps) => import("react").JSX.Element;
35
+ Description: ({ children, className }: import("./Toast.context.js").ToastTextProps) => import("react").JSX.Element;
36
+ Action: ({ label, onPress, className, style, ...props }: import("./Toast.context.js").ToastActionProps) => import("react").JSX.Element;
37
+ Close: ({ onPress, accessibilityLabel, className, style, ...props }: import("./Toast.context.js").ToastCloseProps) => import("react").JSX.Element;
38
+ };
25
39
  };
26
40
  export default Feedback;
@@ -9,8 +9,10 @@ import ProgressCircle from "./Feedback.ProgressCircle.js";
9
9
  import Skeleton from "./Feedback.Skeleton.js";
10
10
  import Spinner from "./Feedback.Spinner.js";
11
11
  import SplashScreen from "./Feedback.Splash.js";
12
+ import { Toast } from "./Toast.context.js";
12
13
  export { ErrorBoundary } from "./ErrorBoundary.js";
13
- export { ToastProvider, useToast, } from "./Toast.context.js";
14
+ export { Toast, ToastProvider, useToast, } from "./Toast.context.js";
15
+ export { useReducedMotion } from "../utils/useReducedMotion.js";
14
16
  export const Feedback = {
15
17
  Loading,
16
18
  Spinner,
@@ -23,5 +25,6 @@ export const Feedback = {
23
25
  EmptyState,
24
26
  Error,
25
27
  SplashScreen,
28
+ Toast,
26
29
  };
27
30
  export default Feedback;
@@ -7,7 +7,9 @@ interface RowCheckBoxProps {
7
7
  isChecked?: boolean;
8
8
  onCheck?: (isChecked: boolean) => void;
9
9
  disabled?: boolean;
10
+ accessibilityLabel?: string;
10
11
  className?: string;
12
+ testID?: string;
11
13
  }
12
- declare function RowCheckBox({ mainText, subText, rightText, rightComponent, leftComponent, isChecked, onCheck, disabled, className, }: RowCheckBoxProps): import("react").JSX.Element;
14
+ declare function RowCheckBox({ mainText, subText, rightText, rightComponent, leftComponent, isChecked, onCheck, disabled, accessibilityLabel, className, testID, }: RowCheckBoxProps): import("react").JSX.Element;
13
15
  export default RowCheckBox;
@@ -4,7 +4,7 @@ import { cn } from "../theme/tw.js";
4
4
  import { Typography } from "../Typography/index.js";
5
5
  import { Checkbox } from "expo-checkbox";
6
6
  import { Pressable, View } from "react-native";
7
- function RowCheckBox({ mainText, subText, rightText, rightComponent, leftComponent, isChecked = false, onCheck = () => { }, disabled = false, className, }) {
7
+ function RowCheckBox({ mainText, subText, rightText, rightComponent, leftComponent, isChecked = false, onCheck = () => { }, disabled = false, accessibilityLabel, className, testID, }) {
8
8
  const handlePress = () => {
9
9
  if (!disabled) {
10
10
  onCheck(!isChecked);
@@ -25,6 +25,6 @@ function RowCheckBox({ mainText, subText, rightText, rightComponent, leftCompone
25
25
  }
26
26
  return null;
27
27
  };
28
- return (_jsxs(Pressable, { className: cn("flex-row justify-between items-center", disabled && "opacity-50", className), disabled: disabled, onPress: handlePress, children: [_jsx(View, { children: renderLeftComponent() }), _jsxs(View, { className: "flex-row items-center", children: [renderRightComponent(), _jsx(Checkbox, { className: "ml-2", color: theme.primary, disabled: disabled, pointerEvents: "none", value: isChecked })] })] }));
28
+ return (_jsxs(Pressable, { accessibilityLabel: accessibilityLabel ?? [mainText, subText, rightText].filter(Boolean).join(", "), accessibilityRole: "checkbox", accessibilityState: { checked: isChecked, disabled }, className: cn("flex-row justify-between items-center", disabled && "opacity-50", className), disabled: disabled, onPress: handlePress, style: { minHeight: 48, minWidth: 48, paddingVertical: 8 }, testID: testID, children: [_jsx(View, { className: "flex-1 min-w-0", children: renderLeftComponent() }), _jsxs(View, { className: "flex-row items-center flex-shrink", children: [renderRightComponent(), _jsx(Checkbox, { accessibilityElementsHidden: true, accessible: false, className: "ml-2", color: theme.primary, disabled: disabled, importantForAccessibility: "no-hide-descendants", pointerEvents: "none", value: isChecked })] })] }));
29
29
  }
30
30
  export default RowCheckBox;
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "nativewind/jsx-runtime";
2
2
  import DateTimePicker from "@react-native-community/datetimepicker";
3
3
  import Icon from "../Display/Display.Icon.js";
4
4
  import Pressable from "../primitives/Pressable.js";
5
- import { theme } from "../theme/index.js";
5
+ import { useTheme } from "../theme/index.js";
6
6
  import { cn } from "../theme/tw.js";
7
7
  import { Typography } from "../Typography/index.js";
8
8
  import { useState } from "react";
@@ -26,6 +26,7 @@ const iconByMode = {
26
26
  */
27
27
  function DatePicker({ value, onChange, mode = "date", label, placeholder = "Seleccionar…", format, minimumDate, maximumDate, disabled = false, className, testID, }) {
28
28
  const [open, setOpen] = useState(false);
29
+ const theme = useTheme();
29
30
  const fmt = format ?? ((d) => defaultFormat(d, mode));
30
31
  const handleValueChange = (_event, date) => {
31
32
  if (Platform.OS === "android")
@@ -33,6 +34,6 @@ function DatePicker({ value, onChange, mode = "date", label, placeholder = "Sele
33
34
  if (date)
34
35
  onChange(date);
35
36
  };
36
- return (_jsxs(View, { className: cn("gap-1", className), testID: testID, children: [label ? (_jsx(Typography.Text, { className: "font-medium text-text-primary", variant: "bodySmall", children: label })) : null, _jsxs(Pressable, { accessibilityHint: "Abre el selector de fecha", accessibilityLabel: label ?? placeholder, accessibilityRole: "button", accessibilityState: { disabled }, className: "flex-row items-center justify-between rounded-lg border border-input bg-bg-card px-3 min-h-[44px]", disabled: disabled, onPress: () => setOpen(true), children: [_jsx(Typography.Text, { className: cn("flex-1 text-sm", value ? "text-text-primary" : "text-text-tertiary"), children: value ? fmt(value) : placeholder }), _jsx(Icon, { color: theme.icon.secondary, name: iconByMode[mode], size: 18 })] }), open ? (_jsx(DateTimePicker, { maximumDate: maximumDate, minimumDate: minimumDate, mode: mode === "datetime" ? "date" : mode, onDismiss: () => setOpen(false), onValueChange: handleValueChange, value: value ?? new Date() })) : null] }));
37
+ return (_jsxs(View, { className: cn("gap-1", className), testID: testID, children: [label ? (_jsx(Typography.Text, { className: "font-medium text-text-primary", variant: "bodySmall", children: label })) : null, _jsxs(Pressable, { accessibilityHint: "Abre el selector de fecha", accessibilityLabel: label ?? placeholder, accessibilityRole: "button", accessibilityState: { disabled }, accessibilityValue: { text: value ? fmt(value) : placeholder }, className: "flex-row items-center justify-between rounded-lg border border-input bg-bg-card px-3 min-h-[48px]", disabled: disabled, onPress: () => setOpen(true), style: { minHeight: 48, minWidth: 48 }, children: [_jsx(Typography.Text, { className: cn("flex-1 text-sm", value ? "text-text-primary" : "text-text-tertiary"), style: { paddingVertical: 12 }, children: value ? fmt(value) : placeholder }), _jsx(Icon, { color: theme.icon.secondary, name: iconByMode[mode], size: 18 })] }), open ? (_jsx(DateTimePicker, { maximumDate: maximumDate, minimumDate: minimumDate, mode: mode === "datetime" ? "date" : mode, onDismiss: () => setOpen(false), onValueChange: handleValueChange, value: value ?? new Date() })) : null] }));
37
38
  }
38
39
  export default DatePicker;
@@ -1,14 +1,26 @@
1
+ import { Typography } from "../Typography/index.js";
2
+ import { type ComponentProps, type ReactNode } from "react";
3
+ import { type TouchableOpacityProps, type ViewProps } from "react-native";
1
4
  export interface DropdownOption {
2
5
  id: string;
3
6
  title: string;
4
7
  onPress: () => void;
5
8
  disabled?: boolean;
6
9
  }
7
- interface DropdownButtonProps {
10
+ export interface DropdownButtonProps {
8
11
  title: string;
9
- options: DropdownOption[];
12
+ options: readonly DropdownOption[];
10
13
  variant?: "primary" | "secondary" | "outline";
11
14
  size?: "xs" | "small" | "medium" | "large";
15
+ disabled?: boolean;
16
+ closeAccessibilityLabel?: string;
17
+ buttonProps?: Omit<TouchableOpacityProps, "children" | "onPress" | "disabled">;
18
+ contentProps?: Omit<ViewProps, "children">;
19
+ optionProps?: Omit<TouchableOpacityProps, "children" | "onPress" | "disabled">;
20
+ titleTextProps?: ComponentProps<typeof Typography.Text>;
21
+ optionTextProps?: ComponentProps<typeof Typography.Text>;
22
+ renderOption?: (option: DropdownOption) => ReactNode;
23
+ testID?: string;
12
24
  }
13
- declare function DropdownButton({ title, options, variant, size, }: DropdownButtonProps): import("react").JSX.Element;
25
+ declare function DropdownButton({ title, options, variant, size, disabled, closeAccessibilityLabel, buttonProps, contentProps, optionProps, titleTextProps, optionTextProps, renderOption, testID, }: DropdownButtonProps): import("react").JSX.Element;
14
26
  export default DropdownButton;