@sincpro/mobile-ui 1.1.0 → 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 (123) hide show
  1. package/README.md +156 -29
  2. package/dist/Actions/Actions.Button.d.ts +10 -12
  3. package/dist/Actions/Actions.Button.js +9 -11
  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 +1 -2
  7. package/dist/Actions/Actions.FAB.js +5 -7
  8. package/dist/Actions/Actions.IconButton.d.ts +1 -2
  9. package/dist/Actions/Actions.IconButton.js +2 -2
  10. package/dist/Actions/index.d.ts +4 -0
  11. package/dist/Actions/index.js +3 -0
  12. package/dist/Dialog/BottomSheet/BottomSheet.Root.js +22 -34
  13. package/dist/Dialog/Dialog.ActionSheet.js +2 -16
  14. package/dist/Dialog/Dialog.Confirmation.js +7 -5
  15. package/dist/Dialog/Dialog.EditValue.js +3 -1
  16. package/dist/Dialog/Dialog.Sheet.d.ts +3 -3
  17. package/dist/Dialog/Dialog.Sheet.js +18 -25
  18. package/dist/Display/Display.Accordion.js +7 -3
  19. package/dist/Display/Display.AmountSummary.d.ts +28 -0
  20. package/dist/Display/Display.AmountSummary.js +25 -0
  21. package/dist/Display/Display.Carousel.d.ts +27 -4
  22. package/dist/Display/Display.Carousel.js +112 -12
  23. package/dist/Display/Display.ExpoImage.d.ts +8 -0
  24. package/dist/Display/Display.ExpoImage.js +15 -0
  25. package/dist/Display/Display.Image.d.ts +10 -6
  26. package/dist/Display/Display.Image.js +39 -6
  27. package/dist/Display/Display.ListItem.d.ts +1 -2
  28. package/dist/Display/Display.ListItem.js +3 -3
  29. package/dist/Display/Display.Logo.d.ts +2 -2
  30. package/dist/Display/Display.Logo.js +3 -2
  31. package/dist/Display/Display.MediaCard.d.ts +2 -2
  32. package/dist/Display/Display.MediaEmbed.d.ts +2 -2
  33. package/dist/Display/Display.ProductCard.d.ts +2 -2
  34. package/dist/Display/Display.Story.d.ts +3 -3
  35. package/dist/Display/Display.Story.js +5 -4
  36. package/dist/Display/Display.StoryTray.d.ts +4 -4
  37. package/dist/Display/Display.StoryTray.js +3 -2
  38. package/dist/Display/Display.StoryViewer.d.ts +3 -3
  39. package/dist/Display/Display.StoryViewer.js +6 -3
  40. package/dist/Display/index.d.ts +9 -0
  41. package/dist/Display/index.js +4 -0
  42. package/dist/Feedback/Feedback.DomainSplashScreen.d.ts +13 -4
  43. package/dist/Feedback/Feedback.DomainSplashScreen.js +68 -53
  44. package/dist/Feedback/Feedback.EmptyState.d.ts +3 -4
  45. package/dist/Feedback/Feedback.EmptyState.js +4 -3
  46. package/dist/Feedback/Feedback.Error.d.ts +1 -2
  47. package/dist/Feedback/Feedback.Error.js +2 -2
  48. package/dist/Feedback/Feedback.Loading.d.ts +6 -2
  49. package/dist/Feedback/Feedback.Loading.js +2 -2
  50. package/dist/Feedback/Feedback.Progress.d.ts +6 -3
  51. package/dist/Feedback/Feedback.Progress.js +29 -19
  52. package/dist/Feedback/Feedback.ProgressCircle.d.ts +3 -2
  53. package/dist/Feedback/Feedback.ProgressCircle.js +10 -7
  54. package/dist/Feedback/Feedback.Skeleton.d.ts +4 -4
  55. package/dist/Feedback/Feedback.Skeleton.js +15 -3
  56. package/dist/Feedback/Feedback.Spinner.d.ts +3 -3
  57. package/dist/Feedback/Feedback.Spinner.js +40 -9
  58. package/dist/Feedback/Feedback.Splash.d.ts +2 -2
  59. package/dist/Feedback/Feedback.Splash.js +5 -3
  60. package/dist/Feedback/Toast.context.d.ts +64 -3
  61. package/dist/Feedback/Toast.context.js +112 -31
  62. package/dist/Feedback/index.d.ts +15 -1
  63. package/dist/Feedback/index.js +4 -1
  64. package/dist/Form/Form.Checkbox.d.ts +1 -2
  65. package/dist/Form/Form.Checkbox.js +2 -2
  66. package/dist/Form/Form.DatePicker.d.ts +1 -2
  67. package/dist/Form/Form.DatePicker.js +2 -2
  68. package/dist/Form/Form.Dropdown.d.ts +15 -3
  69. package/dist/Form/Form.Dropdown.js +21 -6
  70. package/dist/Form/Form.ErrorSummary.d.ts +23 -0
  71. package/dist/Form/Form.ErrorSummary.js +53 -0
  72. package/dist/Form/Form.Input.d.ts +1 -2
  73. package/dist/Form/Form.Input.js +2 -2
  74. package/dist/Form/Form.Select.d.ts +1 -2
  75. package/dist/Form/Form.Select.js +6 -19
  76. package/dist/Form/index.d.ts +3 -0
  77. package/dist/Form/index.js +2 -0
  78. package/dist/Navigation/Navigation.AppBar.d.ts +2 -6
  79. package/dist/Navigation/Navigation.AppBar.js +9 -7
  80. package/dist/Navigation/Navigation.BottomNav.d.ts +1 -3
  81. package/dist/Navigation/Navigation.BottomNav.js +7 -6
  82. package/dist/Navigation/Navigation.Header.d.ts +2 -2
  83. package/dist/Navigation/Navigation.Menu.js +10 -3
  84. package/dist/Navigation/Navigation.Search.d.ts +41 -0
  85. package/dist/Navigation/Navigation.Search.js +47 -0
  86. package/dist/Navigation/Navigation.SearchBar.d.ts +1 -2
  87. package/dist/Navigation/Navigation.SearchBar.js +16 -19
  88. package/dist/Navigation/Navigation.SegmentedControl.d.ts +11 -3
  89. package/dist/Navigation/Navigation.SegmentedControl.js +29 -16
  90. package/dist/Navigation/index.d.ts +11 -2
  91. package/dist/Navigation/index.js +2 -0
  92. package/dist/branding.d.ts +3 -2
  93. package/dist/primitives/BaseButton.d.ts +10 -12
  94. package/dist/primitives/BaseButton.js +9 -11
  95. package/dist/theme/typography.d.ts +1 -1
  96. package/dist/utils/image.d.ts +3 -0
  97. package/dist/utils/image.js +13 -0
  98. package/dist/utils/keys.d.ts +1 -0
  99. package/dist/utils/keys.js +7 -0
  100. package/dist/utils/progress.d.ts +1 -0
  101. package/dist/utils/progress.js +5 -0
  102. package/dist/utils/reorder.js +2 -5
  103. package/dist/utils/useReducedMotion.d.ts +1 -0
  104. package/dist/utils/useReducedMotion.js +43 -0
  105. package/dist/utils/useSheetTranslation.d.ts +5 -0
  106. package/dist/utils/useSheetTranslation.js +23 -0
  107. package/dist/views/AppSplashView.d.ts +3 -2
  108. package/dist/views/AppSplashView.js +3 -2
  109. package/dist/views/AuthFormView.d.ts +2 -2
  110. package/dist/views/FormViewV2.d.ts +2 -2
  111. package/dist/views/ListViewV2.context.d.ts +3 -1
  112. package/dist/views/ListViewV2.context.js +41 -21
  113. package/dist/views/ListViewV2.d.ts +15 -7
  114. package/dist/views/ListViewV2.js +44 -23
  115. package/dist/views/types/IListView.js +6 -5
  116. package/dist/widgets/CollectionOptions.js +9 -7
  117. package/dist/widgets/FilterBar.d.ts +1 -1
  118. package/dist/widgets/FilterBar.js +1 -1
  119. package/dist/widgets/FilterBuilder.js +7 -7
  120. package/dist/widgets/HomeHeader.d.ts +2 -2
  121. package/dist/widgets/PromoModal.js +3 -1
  122. package/dist/widgets/ScreenHeader.d.ts +2 -2
  123. package/package.json +4 -1
@@ -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;
@@ -8,9 +8,8 @@ interface RowCheckBoxProps {
8
8
  onCheck?: (isChecked: boolean) => void;
9
9
  disabled?: boolean;
10
10
  accessibilityLabel?: string;
11
- accessibleSizing?: boolean;
12
11
  className?: string;
13
12
  testID?: string;
14
13
  }
15
- declare function RowCheckBox({ mainText, subText, rightText, rightComponent, leftComponent, isChecked, onCheck, disabled, accessibilityLabel, accessibleSizing, className, testID, }: 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;
16
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, accessibilityLabel, accessibleSizing = false, className, testID, }) {
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, { 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: accessibleSizing ? { minHeight: 48, paddingVertical: 8 } : undefined, testID: testID, 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;
@@ -8,7 +8,6 @@ export interface DatePickerProps {
8
8
  minimumDate?: Date;
9
9
  maximumDate?: Date;
10
10
  disabled?: boolean;
11
- accessibleSizing?: boolean;
12
11
  className?: string;
13
12
  testID?: string;
14
13
  }
@@ -17,5 +16,5 @@ export interface DatePickerProps {
17
16
  * (`@react-native-community/datetimepicker`) — a case where native wins (expected UX).
18
17
  * Subpath `Form/DatePicker`; **optional** peer dep.
19
18
  */
20
- declare function DatePicker({ value, onChange, mode, label, placeholder, format, minimumDate, maximumDate, disabled, accessibleSizing, className, testID, }: DatePickerProps): import("react").JSX.Element;
19
+ declare function DatePicker({ value, onChange, mode, label, placeholder, format, minimumDate, maximumDate, disabled, className, testID, }: DatePickerProps): import("react").JSX.Element;
21
20
  export default DatePicker;
@@ -24,7 +24,7 @@ const iconByMode = {
24
24
  * (`@react-native-community/datetimepicker`) — a case where native wins (expected UX).
25
25
  * Subpath `Form/DatePicker`; **optional** peer dep.
26
26
  */
27
- function DatePicker({ value, onChange, mode = "date", label, placeholder = "Seleccionar…", format, minimumDate, maximumDate, disabled = false, accessibleSizing = false, className, testID, }) {
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
29
  const theme = useTheme();
30
30
  const fmt = format ?? ((d) => defaultFormat(d, mode));
@@ -34,6 +34,6 @@ function DatePicker({ value, onChange, mode = "date", label, placeholder = "Sele
34
34
  if (date)
35
35
  onChange(date);
36
36
  };
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-[44px]", disabled: disabled, onPress: () => setOpen(true), style: accessibleSizing ? { minHeight: 48 } : undefined, 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] }));
38
38
  }
39
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;
@@ -1,9 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "nativewind/jsx-runtime";
2
- import { Display } from "../Display/index.js";
3
- import { theme } from "../theme/index.js";
2
+ import IconButton from "../Actions/Actions.IconButton.js";
3
+ import Icon from "../Display/Display.Icon.js";
4
+ import { useTheme } from "../theme/index.js";
5
+ import { cn } from "../theme/tw.js";
4
6
  import { Typography } from "../Typography/index.js";
7
+ import { useReducedMotion } from "../utils/useReducedMotion.js";
5
8
  import { useState } from "react";
6
- import { Modal, TouchableOpacity, TouchableWithoutFeedback, View } from "react-native";
9
+ import { Modal, ScrollView, TouchableOpacity, TouchableWithoutFeedback, View, } from "react-native";
7
10
  import { tv } from "tailwind-variants";
8
11
  const dropdown = tv({
9
12
  slots: {
@@ -52,7 +55,9 @@ const dropdown = tv({
52
55
  size: "small",
53
56
  },
54
57
  });
55
- function DropdownButton({ title, options, variant = "secondary", size = "small", }) {
58
+ function DropdownButton({ title, options, variant = "secondary", size = "small", disabled = false, closeAccessibilityLabel = "Cerrar opciones", buttonProps, contentProps, optionProps, titleTextProps, optionTextProps, renderOption, testID, }) {
59
+ const theme = useTheme();
60
+ const reducedMotion = useReducedMotion();
56
61
  const [isOpen, setIsOpen] = useState(false);
57
62
  const handleOptionPress = (option) => {
58
63
  if (!option.disabled) {
@@ -77,9 +82,19 @@ function DropdownButton({ title, options, variant = "secondary", size = "small",
77
82
  xs: "bodySmall",
78
83
  small: "bodySmall",
79
84
  }[size];
80
- return (_jsxs(_Fragment, { children: [_jsxs(TouchableOpacity, { activeOpacity: 0.7, className: styles.button(), onPress: () => setIsOpen(true), children: [_jsx(Typography.Text, { className: textColorClass, variant: textVariant, children: title }), _jsx(Display.Icon, { color: iconColor, name: "chevron-down", size: 16, type: "feather" })] }), _jsx(Modal, { animationType: "fade", presentationStyle: "overFullScreen", transparent: true, visible: isOpen, children: _jsx(TouchableWithoutFeedback, { onPress: () => setIsOpen(false), children: _jsx(View, { className: styles.overlay(), children: _jsx(TouchableWithoutFeedback, { children: _jsxs(View, { className: styles.dropdownContainer(), style: {
85
+ return (_jsxs(_Fragment, { children: [_jsxs(TouchableOpacity, { ...buttonProps, accessibilityLabel: buttonProps?.accessibilityLabel ?? title, accessibilityRole: "button", accessibilityState: {
86
+ ...buttonProps?.accessibilityState,
87
+ disabled,
88
+ expanded: isOpen,
89
+ }, activeOpacity: 0.7, className: cn(styles.button(), buttonProps?.className), disabled: disabled, onPress: () => setIsOpen(true), style: [{ minHeight: 48, minWidth: 48 }, buttonProps?.style], testID: testID, children: [_jsx(Typography.Text, { className: textColorClass, variant: textVariant, children: title }), _jsx(Icon, { color: iconColor, name: "chevron-down", size: 16, type: "feather" })] }), _jsx(Modal, { animationType: reducedMotion ? "none" : "fade", onRequestClose: () => setIsOpen(false), presentationStyle: "overFullScreen", transparent: true, visible: isOpen, children: _jsxs(View, { className: styles.overlay(), children: [_jsx(TouchableWithoutFeedback, { accessible: false, onPress: () => setIsOpen(false), children: _jsx(View, { className: "absolute inset-0" }) }), _jsxs(View, { ...contentProps, accessibilityViewIsModal: true, className: cn(styles.dropdownContainer(), contentProps?.className), onAccessibilityEscape: () => setIsOpen(false), style: [
90
+ {
81
91
  backgroundColor: theme.bg.popover,
82
92
  ...theme.shadow.lg,
83
- }, children: [_jsxs(View, { className: styles.header(), children: [_jsx(Typography.Text, { className: "text-text-primary", semibold: true, variant: "bodyLarge", children: title }), _jsx(TouchableOpacity, { onPress: () => setIsOpen(false), children: _jsx(Display.Icon, { name: "x", size: 20, type: "feather" }) })] }), _jsx(View, { className: styles.optionsList(), children: options.map((option, index) => (_jsxs(View, { children: [_jsx(TouchableOpacity, { activeOpacity: 0.7, className: dropdown({ disabled: option.disabled }).option(), disabled: option.disabled, onPress: () => handleOptionPress(option), children: _jsx(Typography.Text, { className: option.disabled ? "text-text-disabled" : "text-text-primary", variant: "body", children: option.title }) }), index < options.length - 1 && _jsx(View, { className: styles.separator() })] }, option.id))) })] }) }) }) }) })] }));
93
+ },
94
+ contentProps?.style,
95
+ ], children: [_jsxs(View, { className: styles.header(), children: [_jsx(Typography.Text, { ...titleTextProps, accessibilityRole: "header", className: cn("text-text-primary", titleTextProps?.className), semibold: titleTextProps?.semibold ?? true, variant: titleTextProps?.variant ?? "bodyLarge", children: title }), _jsx(IconButton, { accessibilityLabel: closeAccessibilityLabel, icon: "close", onPress: () => setIsOpen(false) })] }), _jsx(ScrollView, { contentContainerClassName: styles.optionsList(), keyboardShouldPersistTaps: "handled", style: { flexShrink: 1 }, children: options.map((option, index) => (_jsxs(View, { children: [_jsx(TouchableOpacity, { ...optionProps, accessibilityLabel: option.title, accessibilityRole: "button", accessibilityState: {
96
+ ...optionProps?.accessibilityState,
97
+ disabled: option.disabled,
98
+ }, activeOpacity: 0.7, className: cn(dropdown({ disabled: option.disabled }).option(), optionProps?.className), disabled: option.disabled, onPress: () => handleOptionPress(option), style: [{ minHeight: 48, minWidth: 48 }, optionProps?.style], children: renderOption ? (renderOption(option)) : (_jsx(Typography.Text, { ...optionTextProps, className: cn(option.disabled ? "text-text-disabled" : "text-text-primary", optionTextProps?.className), variant: optionTextProps?.variant ?? "body", children: option.title })) }), index < options.length - 1 && _jsx(View, { className: styles.separator() })] }, option.id))) })] })] }) })] }));
84
99
  }
85
100
  export default DropdownButton;
@@ -0,0 +1,23 @@
1
+ import { Typography } from "../Typography/index.js";
2
+ import { type ComponentProps, type ReactNode } from "react";
3
+ import { type StyleProp, type ViewProps, type ViewStyle } from "react-native";
4
+ export interface ErrorSummaryItem {
5
+ readonly id: string;
6
+ readonly label: string;
7
+ readonly message: string;
8
+ }
9
+ export interface ErrorSummaryProps extends Omit<ViewProps, "children"> {
10
+ errors: readonly ErrorSummaryItem[];
11
+ onSelectError: (id: string) => void;
12
+ title?: string;
13
+ announce?: boolean;
14
+ className?: string;
15
+ itemStyle?: StyleProp<ViewStyle>;
16
+ /** Custom content must preserve the message and remain scalable, multiline and non-interactive. */
17
+ renderErrorContent?: (error: ErrorSummaryItem, index: number) => ReactNode;
18
+ /** Custom content must remain scalable and multiline; the wrapper owns header semantics. */
19
+ renderHeader?: (title: string) => ReactNode;
20
+ itemTextProps?: Omit<ComponentProps<typeof Typography.Text>, "children" | "accessibilityRole" | "allowFontScaling" | "numberOfLines">;
21
+ titleTextProps?: Omit<ComponentProps<typeof Typography.Text>, "children" | "accessibilityRole" | "allowFontScaling" | "numberOfLines">;
22
+ }
23
+ export default function ErrorSummary({ errors, onSelectError, title, announce, className, style, itemStyle, renderErrorContent, renderHeader, itemTextProps, titleTextProps, ...props }: ErrorSummaryProps): import("react").JSX.Element | null;
@@ -0,0 +1,53 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "nativewind/jsx-runtime";
2
+ import Pressable from "../primitives/Pressable.js";
3
+ import { useTheme } from "../theme/index.js";
4
+ import { Typography } from "../Typography/index.js";
5
+ import { uniqueKeys } from "../utils/keys.js";
6
+ import { useEffect, useRef } from "react";
7
+ import { AccessibilityInfo, StyleSheet, View, } from "react-native";
8
+ export default function ErrorSummary({ errors, onSelectError, title = "Revisa los siguientes errores", announce = true, className, style, itemStyle, renderErrorContent, renderHeader, itemTextProps, titleTextProps, ...props }) {
9
+ const theme = useTheme();
10
+ const nonemptyEpisode = useRef(false);
11
+ uniqueKeys(errors, (error) => error.id, "ErrorSummary");
12
+ for (const { label, message } of errors) {
13
+ if (typeof label !== "string" || !label.trim())
14
+ throw new Error("Form.ErrorSummary requires non-empty error labels.");
15
+ if (typeof message !== "string" || !message.trim())
16
+ throw new Error("Form.ErrorSummary requires non-empty error messages.");
17
+ }
18
+ useEffect(() => {
19
+ if (errors.length === 0) {
20
+ nonemptyEpisode.current = false;
21
+ return;
22
+ }
23
+ if (nonemptyEpisode.current)
24
+ return;
25
+ nonemptyEpisode.current = true;
26
+ if (announce)
27
+ AccessibilityInfo.announceForAccessibility([title, ...errors.map(({ label, message }) => `${label}: ${message}`)].join("\n"));
28
+ }, [announce, errors, title]);
29
+ if (errors.length === 0)
30
+ return null;
31
+ return (_jsxs(View, { className: className, style: [
32
+ {
33
+ backgroundColor: theme.bg.card,
34
+ borderColor: theme.danger,
35
+ borderWidth: 2,
36
+ borderRadius: 8,
37
+ padding: 16,
38
+ gap: 8,
39
+ },
40
+ style,
41
+ ], ...props, children: [renderHeader ? (_jsx(View, { accessibilityLabel: title, accessibilityRole: "header", accessible: true, children: renderHeader(title) })) : (_jsx(Typography.Text, { semibold: true, variant: "body", ...titleTextProps, accessibilityRole: "header", allowFontScaling: true, numberOfLines: 0, children: title })), errors.map((error, index) => (_jsx(Pressable, { accessibilityLabel: `${error.label}: ${error.message}`, accessibilityRole: "button", accessible: true, hitSlop: 0, onPress: () => onSelectError(error.id), style: [styles.item, itemStyle, styles.target], children: renderErrorContent ? (renderErrorContent(error, index)) : (_jsxs(_Fragment, { children: [_jsx(Typography.Text, { semibold: true, variant: "bodySmall", ...itemTextProps, allowFontScaling: true, numberOfLines: 0, children: error.label }), _jsx(Typography.Text, { className: "text-text-accent", underline: true, ...itemTextProps, allowFontScaling: true, numberOfLines: 0, children: error.message })] })) }, error.id)))] }));
42
+ }
43
+ const styles = StyleSheet.create({
44
+ item: {
45
+ justifyContent: "center",
46
+ paddingVertical: 8,
47
+ gap: 4,
48
+ },
49
+ target: {
50
+ minHeight: 48,
51
+ minWidth: 48,
52
+ },
53
+ });
@@ -6,7 +6,6 @@ interface FormInputProps extends TextInputProps {
6
6
  error?: string;
7
7
  tooltip?: string;
8
8
  className?: string;
9
- accessibleSizing?: boolean;
10
9
  }
11
- declare function FormInput({ label, error, tooltip, style, multiline, numberOfLines, onFocus, onBlur, className, accessibleSizing, ref, ...props }: FormInputProps): import("react").JSX.Element;
10
+ declare function FormInput({ label, error, tooltip, style, multiline, numberOfLines, onFocus, onBlur, className, ref, ...props }: FormInputProps): import("react").JSX.Element;
12
11
  export default FormInput;
@@ -15,7 +15,7 @@ const inputContainer = tv({
15
15
  },
16
16
  defaultVariants: { state: "default" },
17
17
  });
18
- function FormInput({ label, error, tooltip, style, multiline = false, numberOfLines = 1, onFocus, onBlur, className, accessibleSizing = false, ref, ...props }) {
18
+ function FormInput({ label, error, tooltip, style, multiline = false, numberOfLines = 1, onFocus, onBlur, className, ref, ...props }) {
19
19
  const [isFocused, setIsFocused] = useState(false);
20
20
  const hasError = Boolean(error);
21
21
  const handleFocus = (e) => {
@@ -33,6 +33,6 @@ function FormInput({ label, error, tooltip, style, multiline = false, numberOfLi
33
33
  return "focused";
34
34
  return "default";
35
35
  };
36
- return (_jsxs(View, { className: cn("mb-2", className), children: [_jsx(Typography.Text, { className: "mb-1 font-medium text-text-primary", variant: "bodySmall", children: label }), _jsx(View, { className: inputContainer({ state: getState() }), children: _jsx(TextInput, { accessibilityHint: error || tooltip, accessibilityLabel: label, className: cn("px-3 py-2.5 text-sm min-h-[44px] text-text-primary", multiline && "min-h-[64px] pt-2.5"), multiline: multiline, numberOfLines: numberOfLines, onBlur: handleBlur, onFocus: handleFocus, placeholderTextColor: theme.text.tertiary, ref: ref, selectionColor: theme.primary, style: [accessibleSizing ? { minHeight: 48 } : undefined, style], textAlignVertical: multiline ? "top" : "center", underlineColorAndroid: "transparent", ...props }) }), hasError && (_jsx(Typography.Text, { accessibilityLiveRegion: "polite", className: "mt-0.5 text-danger", variant: "bodySmall", children: error }))] }));
36
+ return (_jsxs(View, { className: cn("mb-2", className), children: [_jsx(Typography.Text, { className: "mb-1 font-medium text-text-primary", variant: "bodySmall", children: label }), _jsx(View, { className: inputContainer({ state: getState() }), children: _jsx(TextInput, { accessibilityHint: error || tooltip, accessibilityLabel: label, className: cn("px-3 py-2.5 text-sm min-h-[48px] text-text-primary", multiline && "min-h-[64px] pt-2.5"), multiline: multiline, numberOfLines: numberOfLines, onBlur: handleBlur, onFocus: handleFocus, placeholderTextColor: theme.text.tertiary, ref: ref, selectionColor: theme.primary, style: [{ minHeight: multiline ? 64 : 48 }, style], textAlignVertical: multiline ? "top" : "center", underlineColorAndroid: "transparent", ...props }) }), hasError && (_jsx(Typography.Text, { accessibilityLiveRegion: "polite", className: "mt-0.5 text-danger", variant: "bodySmall", children: error }))] }));
37
37
  }
38
38
  export default FormInput;
@@ -10,10 +10,9 @@ export interface SelectProps<T extends string | number> {
10
10
  placeholder?: string;
11
11
  title?: string;
12
12
  disabled?: boolean;
13
- accessibleSizing?: boolean;
14
13
  className?: string;
15
14
  testID?: string;
16
15
  showCloseButton?: boolean;
17
16
  }
18
- declare function Select<T extends string | number>({ options, value, onChange, label, placeholder, title, disabled, accessibleSizing, className, testID, showCloseButton, }: SelectProps<T>): import("react").JSX.Element;
17
+ declare function Select<T extends string | number>({ options, value, onChange, label, placeholder, title, disabled, className, testID, showCloseButton, }: SelectProps<T>): import("react").JSX.Element;
19
18
  export default Select;
@@ -4,33 +4,20 @@ import Pressable from "../primitives/Pressable.js";
4
4
  import { themeToVars, useTheme } from "../theme/index.js";
5
5
  import { cn } from "../theme/tw.js";
6
6
  import { Typography } from "../Typography/index.js";
7
+ import { useSheetTranslation } from "../utils/useSheetTranslation.js";
7
8
  import { vars } from "nativewind";
8
- import { useEffect, useRef, useState } from "react";
9
+ import { useState } from "react";
9
10
  import { Animated, Modal, ScrollView, View } from "react-native";
10
11
  import { useSafeAreaInsets } from "react-native-safe-area-context";
11
- function Select({ options, value, onChange, label, placeholder = "Seleccionar…", title, disabled = false, accessibleSizing = false, className, testID, showCloseButton = accessibleSizing, }) {
12
+ function Select({ options, value, onChange, label, placeholder = "Seleccionar…", title, disabled = false, className, testID, showCloseButton = true, }) {
12
13
  const theme = useTheme();
13
14
  const insets = useSafeAreaInsets();
14
15
  const [open, setOpen] = useState(false);
15
16
  const selected = options.find((o) => o.value === value) ?? null;
16
- // eslint-disable-next-line react-hooks/refs
17
- const translateY = useRef(new Animated.Value(800)).current;
18
- useEffect(() => {
19
- if (open) {
20
- Animated.spring(translateY, {
21
- toValue: 0,
22
- useNativeDriver: true,
23
- damping: 20,
24
- stiffness: 300,
25
- }).start();
26
- }
27
- else {
28
- translateY.setValue(800);
29
- }
30
- }, [open, translateY]);
17
+ const { translateY } = useSheetTranslation(open);
31
18
  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 la lista de opciones", accessibilityLabel: label
32
19
  ? `${label}: ${selected?.label ?? placeholder}`
33
- : (selected?.label ?? placeholder), accessibilityRole: "button", accessibilityState: { disabled, expanded: open }, className: cn("flex-row items-center justify-between rounded-lg border border-input bg-bg-card px-3", accessibleSizing ? "min-h-[48px]" : "min-h-[44px]"), disabled: disabled, onPress: () => setOpen(true), children: [_jsx(Typography.Text, { className: cn("flex-1 text-sm", selected ? "text-text-primary" : "text-text-tertiary"), numberOfLines: accessibleSizing ? undefined : 1, style: accessibleSizing ? { paddingVertical: 12 } : undefined, children: selected ? selected.label : placeholder }), _jsx(Icon, { color: theme.icon.secondary, name: "chevron-down", size: 18 })] }), _jsx(Modal, { animationType: "none", onRequestClose: () => setOpen(false), presentationStyle: "overFullScreen", transparent: true, visible: open, children: _jsxs(View, { className: "flex-1 justify-end", children: [_jsx(View, { accessibilityElementsHidden: true, className: "absolute inset-0 bg-black/50", importantForAccessibility: "no-hide-descendants", onResponderRelease: () => setOpen(false), onStartShouldSetResponder: () => true }), _jsxs(Animated.View, { accessibilityViewIsModal: true, className: "rounded-t-3xl", importantForAccessibility: "yes", onAccessibilityEscape: () => setOpen(false), style: [
20
+ : (selected?.label ?? placeholder), accessibilityRole: "button", accessibilityState: { disabled, expanded: open }, 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", selected ? "text-text-primary" : "text-text-tertiary"), style: { paddingVertical: 12 }, children: selected ? selected.label : placeholder }), _jsx(Icon, { color: theme.icon.secondary, name: "chevron-down", size: 18 })] }), _jsx(Modal, { animationType: "none", onRequestClose: () => setOpen(false), presentationStyle: "overFullScreen", transparent: true, visible: open, children: _jsxs(View, { className: "flex-1 justify-end", children: [_jsx(View, { accessibilityElementsHidden: true, className: "absolute inset-0 bg-black/50", importantForAccessibility: "no-hide-descendants", onResponderRelease: () => setOpen(false), onStartShouldSetResponder: () => true }), _jsxs(Animated.View, { accessibilityViewIsModal: true, className: "rounded-t-3xl", importantForAccessibility: "yes", onAccessibilityEscape: () => setOpen(false), style: [
34
21
  vars(themeToVars(theme)),
35
22
  {
36
23
  transform: [{ translateY }],
@@ -48,7 +35,7 @@ function Select({ options, value, onChange, label, placeholder = "Seleccionar…
48
35
  return (_jsxs(Pressable, { accessibilityRole: "menuitem", accessibilityState: { selected: isSelected }, className: "flex-row items-center justify-between px-4 py-3 border-b border-border-light", onPress: () => {
49
36
  onChange(option.value);
50
37
  setOpen(false);
51
- }, style: accessibleSizing ? { minHeight: 48 } : undefined, children: [_jsx(Typography.Text, { className: cn("flex-1 text-sm", isSelected ? "text-text-accent font-semibold" : "text-text-primary"), children: option.label }), isSelected ? (_jsx(Icon, { color: theme.text.accent, name: "checkmark", size: 18 })) : null] }, String(option.value)));
38
+ }, style: { minHeight: 48, minWidth: 48 }, children: [_jsx(Typography.Text, { className: cn("flex-1 text-sm", isSelected ? "text-text-accent font-semibold" : "text-text-primary"), children: option.label }), isSelected ? (_jsx(Icon, { color: theme.text.accent, name: "checkmark", size: 18 })) : null] }, String(option.value)));
52
39
  }) })] })] }) })] }));
53
40
  }
54
41
  export default Select;
@@ -6,6 +6,7 @@ import ControlledMonetaryInput from "./Form.ControlledMonetaryInput.js";
6
6
  import ControlledRadioButton from "./Form.ControlledRadioButton.js";
7
7
  import DatePicker from "./Form.DatePicker.js";
8
8
  import Dropdown from "./Form.Dropdown.js";
9
+ import ErrorSummary from "./Form.ErrorSummary.js";
9
10
  import FAB from "./Form.FAB.js";
10
11
  import { FilterButtonGroup } from "./Form.FilterButton.js";
11
12
  import FractionalQuantityInput from "./Form.FractionalQuantityInput.js";
@@ -27,6 +28,7 @@ export declare const Form: {
27
28
  InputField: import("react").FC<import("./Form.InputField.js").InputFieldProps>;
28
29
  KeyboardForm: typeof KeyboardForm;
29
30
  KeyboardToolbar: typeof KeyboardToolbar;
31
+ ErrorSummary: typeof ErrorSummary;
30
32
  Button: import("react").FC<import("./Form.Button.js").ButtonProps> & {
31
33
  Icon: import("react").FC<import("./Form.Button.js").ButtonIconProps>;
32
34
  };
@@ -54,6 +56,7 @@ export declare const Form: {
54
56
  FilterButtonGroup: typeof FilterButtonGroup;
55
57
  };
56
58
  export { type DropdownOption };
59
+ export type { ErrorSummaryItem, ErrorSummaryProps, } from "./Form.ErrorSummary.js";
57
60
  export type { KeyboardFormProps } from "./Form.KeyboardForm.js";
58
61
  export type { KeyboardToolbarAction, KeyboardToolbarProps, } from "./Form.KeyboardToolbar.js";
59
62
  export default Form;
@@ -7,6 +7,7 @@ import ControlledMonetaryInput from "./Form.ControlledMonetaryInput.js";
7
7
  import ControlledRadioButton from "./Form.ControlledRadioButton.js";
8
8
  import DatePicker from "./Form.DatePicker.js";
9
9
  import Dropdown from "./Form.Dropdown.js";
10
+ import ErrorSummary from "./Form.ErrorSummary.js";
10
11
  import FAB from "./Form.FAB.js";
11
12
  import { FilterButton, FilterButtonGroup } from "./Form.FilterButton.js";
12
13
  import FractionalQuantityInput from "./Form.FractionalQuantityInput.js";
@@ -31,6 +32,7 @@ export const Form = {
31
32
  InputField,
32
33
  KeyboardForm,
33
34
  KeyboardToolbar,
35
+ ErrorSummary,
34
36
  Button: ButtonWithIcon,
35
37
  IconButton,
36
38
  PasswordInput,