@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,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
+ });
@@ -1,3 +1,3 @@
1
1
  /** @deprecated Use `Actions.FilterButton` from `@sincpro/mobile-ui/Actions` instead. */
2
- export type { FilterButtonProps } from "../Actions/Actions.FilterButton.js";
2
+ export type { FilterButtonGroupProps, FilterButtonProps, } from "../Actions/Actions.FilterButton.js";
3
3
  export { FilterButton, FilterButtonGroup, } from "../Actions/Actions.FilterButton.js";
@@ -1,10 +1,11 @@
1
- import type React from "react";
2
- import { type TextInputProps } from "react-native";
1
+ import type { ComponentRef, Ref } from "react";
2
+ import { TextInput, type TextInputProps } from "react-native";
3
3
  interface FormInputProps extends TextInputProps {
4
+ ref?: Ref<ComponentRef<typeof TextInput>>;
4
5
  label: string;
5
6
  error?: string;
6
7
  tooltip?: string;
7
8
  className?: string;
8
9
  }
9
- declare function FormInput({ label, error, tooltip, style, multiline, numberOfLines, onFocus, onBlur, className, ...props }: FormInputProps): React.JSX.Element;
10
+ declare function FormInput({ label, error, tooltip, style, multiline, numberOfLines, onFocus, onBlur, className, ref, ...props }: FormInputProps): import("react").JSX.Element;
10
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, ...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, { 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, selectionColor: theme.primary, textAlignVertical: multiline ? "top" : "center", underlineColorAndroid: "transparent", ...props }) }), hasError && (_jsx(Typography.Text, { 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;
@@ -0,0 +1,11 @@
1
+ import { type KeyboardToolbarProps } from "./Form.KeyboardToolbar.js";
2
+ import { type ReactNode, type Ref } from "react";
3
+ import type { KeyboardAwareScrollViewProps, KeyboardAwareScrollViewRef } from "react-native-keyboard-controller";
4
+ export interface KeyboardFormProps extends KeyboardAwareScrollViewProps {
5
+ children: ReactNode;
6
+ ref?: Ref<KeyboardAwareScrollViewRef>;
7
+ toolbar?: boolean;
8
+ toolbarProps?: Omit<KeyboardToolbarProps, "onHeightChange">;
9
+ toolbarLabels?: Pick<KeyboardToolbarProps, "previousLabel" | "nextLabel" | "doneLabel" | "showDoneLabel">;
10
+ }
11
+ export default function KeyboardForm({ children, toolbar, toolbarLabels, toolbarProps, bottomOffset, style, ...props }: KeyboardFormProps): import("react").JSX.Element;
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "nativewind/jsx-runtime";
2
+ import KeyboardToolbar from "./Form.KeyboardToolbar.js";
3
+ import { useState } from "react";
4
+ import { View } from "react-native";
5
+ import { KeyboardAwareScrollView, KeyboardToolbar as NativeToolbar, } from "react-native-keyboard-controller";
6
+ export default function KeyboardForm({ children, toolbar = true, toolbarLabels, toolbarProps, bottomOffset, style, ...props }) {
7
+ const [toolbarHeight, setToolbarHeight] = useState(48);
8
+ return (_jsxs(View, { style: { flex: 1 }, children: [_jsx(NativeToolbar.Group, { style: { flex: 1 }, children: _jsx(KeyboardAwareScrollView, { bottomOffset: bottomOffset ?? (toolbar ? toolbarHeight + 24 : 20), keyboardShouldPersistTaps: "handled", style: [{ flex: 1 }, style], ...props, children: children }) }), toolbar ? (_jsx(KeyboardToolbar, { ...toolbarLabels, ...toolbarProps, onHeightChange: setToolbarHeight })) : null] }));
9
+ }
@@ -0,0 +1,21 @@
1
+ import { type ReactNode } from "react";
2
+ import { type StyleProp, type TextStyle, type ViewStyle } from "react-native";
3
+ export type KeyboardToolbarAction = "previous" | "next" | "done";
4
+ export interface KeyboardToolbarProps {
5
+ previousLabel?: string;
6
+ nextLabel?: string;
7
+ doneLabel?: string;
8
+ showDoneLabel?: boolean;
9
+ onHeightChange?: (height: number) => void;
10
+ className?: string;
11
+ style?: StyleProp<ViewStyle>;
12
+ actionStyle?: StyleProp<ViewStyle>;
13
+ labelStyle?: StyleProp<TextStyle>;
14
+ iconSize?: number;
15
+ testID?: string;
16
+ renderActionContent?: (action: KeyboardToolbarAction, state: {
17
+ label: string;
18
+ disabled: boolean;
19
+ }) => ReactNode;
20
+ }
21
+ export default function KeyboardToolbar({ previousLabel, nextLabel, doneLabel, showDoneLabel, onHeightChange, className, style, actionStyle, labelStyle, iconSize, testID, renderActionContent, }: KeyboardToolbarProps): import("react").JSX.Element;
@@ -0,0 +1,70 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "nativewind/jsx-runtime";
2
+ import Icon from "../Display/Display.Icon.js";
3
+ import Pressable from "../primitives/Pressable.js";
4
+ import { useTheme } from "../theme/index.js";
5
+ import { Typography } from "../Typography/index.js";
6
+ import { useEffect, useRef, useState } from "react";
7
+ import { StyleSheet, useWindowDimensions, View, } from "react-native";
8
+ import { FocusedInputEvents, KeyboardController, KeyboardStickyView, useKeyboardState, } from "react-native-keyboard-controller";
9
+ export default function KeyboardToolbar({ previousLabel = "Campo anterior", nextLabel = "Campo siguiente", doneLabel = "Ocultar teclado", showDoneLabel = false, onHeightChange, className, style, actionStyle, labelStyle, iconSize = 20, testID = "keyboard-toolbar", renderActionContent, }) {
10
+ const theme = useTheme();
11
+ const frame = useRef(null);
12
+ const { height: windowHeight } = useWindowDimensions();
13
+ const [bottomGap, setBottomGap] = useState(0);
14
+ const [height, setHeight] = useState(48);
15
+ const [inputs, setInputs] = useState({ current: 0, count: 0 });
16
+ const isVisible = useKeyboardState((state) => state.isVisible);
17
+ useEffect(() => {
18
+ const subscription = FocusedInputEvents.addListener("focusDidSet", setInputs);
19
+ return () => subscription.remove();
20
+ }, []);
21
+ const previousDisabled = inputs.current === 0;
22
+ const nextDisabled = inputs.current >= inputs.count - 1;
23
+ const content = (action, label, disabled) => {
24
+ if (renderActionContent)
25
+ return renderActionContent(action, { label, disabled });
26
+ if (action === "done" && showDoneLabel)
27
+ return (_jsx(Typography.Text, { className: "text-text-accent", style: labelStyle, variant: "bodySmall", children: label }));
28
+ return (_jsx(Icon, { color: disabled ? theme.text.tertiary : theme.text.accent, name: action === "previous"
29
+ ? "chevron-up"
30
+ : action === "next"
31
+ ? "chevron-down"
32
+ : "checkmark", size: iconSize }));
33
+ };
34
+ return (_jsx(View, { accessibilityElementsHidden: !isVisible, importantForAccessibility: isVisible ? "auto" : "no-hide-descendants", onLayout: () => {
35
+ frame.current?.measureInWindow((_x, y, _width, frameHeight) => setBottomGap(Math.max(0, windowHeight - y - frameHeight)));
36
+ }, pointerEvents: "box-none", ref: frame, style: StyleSheet.absoluteFill, children: _jsx(KeyboardStickyView, { offset: { closed: height, opened: bottomGap - 8 }, style: { position: "absolute", bottom: 0, left: 0, right: 0 }, children: _jsxs(View, { className: className, onLayout: (event) => {
37
+ const nextHeight = event.nativeEvent.layout.height;
38
+ setHeight(nextHeight);
39
+ onHeightChange?.(nextHeight);
40
+ }, style: [
41
+ {
42
+ minHeight: 48,
43
+ flexDirection: "row",
44
+ alignItems: "center",
45
+ justifyContent: "space-between",
46
+ paddingHorizontal: 8,
47
+ backgroundColor: theme.bg.card,
48
+ borderTopWidth: StyleSheet.hairlineWidth,
49
+ borderTopColor: theme.border.light,
50
+ },
51
+ style,
52
+ ], testID: testID, children: [_jsxs(View, { style: { flexDirection: "row" }, children: [_jsx(Pressable, { accessibilityLabel: previousLabel, disabled: previousDisabled, hitSlop: 0, onPress: () => KeyboardController.setFocusTo("prev"), style: [styles.arrow, actionStyle], children: content("previous", previousLabel, previousDisabled) }), _jsx(Pressable, { accessibilityLabel: nextLabel, disabled: nextDisabled, hitSlop: 0, onPress: () => KeyboardController.setFocusTo("next"), style: [styles.arrow, actionStyle], children: content("next", nextLabel, nextDisabled) })] }), _jsx(Pressable, { accessibilityLabel: doneLabel, hitSlop: 0, onPress: () => KeyboardController.dismiss(), style: [
53
+ {
54
+ minWidth: 48,
55
+ minHeight: 48,
56
+ flexShrink: 1,
57
+ justifyContent: "center",
58
+ padding: 8,
59
+ },
60
+ actionStyle,
61
+ ], children: content("done", doneLabel, false) })] }) }) }));
62
+ }
63
+ const styles = StyleSheet.create({
64
+ arrow: {
65
+ minWidth: 48,
66
+ minHeight: 48,
67
+ alignItems: "center",
68
+ justifyContent: "center",
69
+ },
70
+ });
@@ -12,6 +12,7 @@ export interface SelectProps<T extends string | number> {
12
12
  disabled?: boolean;
13
13
  className?: string;
14
14
  testID?: string;
15
+ showCloseButton?: boolean;
15
16
  }
16
- declare function Select<T extends string | number>({ options, value, onChange, label, placeholder, title, disabled, className, testID, }: 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;
17
18
  export default Select;
@@ -1,41 +1,41 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "nativewind/jsx-runtime";
2
2
  import Icon from "../Display/Display.Icon.js";
3
3
  import Pressable from "../primitives/Pressable.js";
4
- import { theme } from "../theme/index.js";
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 { useEffect, useRef, useState } from "react";
8
- import { Animated, Modal, Pressable as RNPressable, ScrollView, View } from "react-native";
7
+ import { useSheetTranslation } from "../utils/useSheetTranslation.js";
8
+ import { vars } from "nativewind";
9
+ import { useState } from "react";
10
+ import { Animated, Modal, ScrollView, View } from "react-native";
9
11
  import { useSafeAreaInsets } from "react-native-safe-area-context";
10
- function Select({ options, value, onChange, label, placeholder = "Seleccionar…", title, disabled = false, className, testID, }) {
12
+ function Select({ options, value, onChange, label, placeholder = "Seleccionar…", title, disabled = false, className, testID, showCloseButton = true, }) {
13
+ const theme = useTheme();
11
14
  const insets = useSafeAreaInsets();
12
15
  const [open, setOpen] = useState(false);
13
16
  const selected = options.find((o) => o.value === value) ?? null;
14
- // eslint-disable-next-line react-hooks/refs
15
- const translateY = useRef(new Animated.Value(800)).current;
16
- useEffect(() => {
17
- if (open) {
18
- Animated.spring(translateY, {
19
- toValue: 0,
20
- useNativeDriver: true,
21
- damping: 20,
22
- stiffness: 300,
23
- }).start();
24
- }
25
- else {
26
- translateY.setValue(800);
27
- }
28
- }, [open, translateY]);
29
- 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 ?? 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", selected ? "text-text-primary" : "text-text-tertiary"), numberOfLines: 1, 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: _jsx(RNPressable, { className: "flex-1 justify-end bg-black/50", onPress: () => setOpen(false), children: _jsx(Animated.View, { style: { transform: [{ translateY }] }, children: _jsxs(RNPressable, { className: "rounded-t-3xl", style: {
30
- backgroundColor: theme.bg.card,
31
- paddingBottom: insets.bottom + 8,
32
- maxHeight: "70%",
33
- }, children: [_jsx(View, { className: "items-center pt-3 pb-1", children: _jsx(View, { className: "w-10 h-1 rounded-full bg-border-strong" }) }), title ? (_jsx(Typography.Text, { className: "text-text-primary px-4 py-2", semibold: true, variant: "body", children: title })) : null, _jsx(ScrollView, { children: options.map((option) => {
17
+ const { translateY } = useSheetTranslation(open);
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
19
+ ? `${label}: ${selected?.label ?? placeholder}`
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: [
21
+ vars(themeToVars(theme)),
22
+ {
23
+ transform: [{ translateY }],
24
+ maxHeight: "70%",
25
+ backgroundColor: theme.bg.card,
26
+ paddingBottom: insets.bottom + 8,
27
+ },
28
+ ], children: [_jsx(View, { className: "items-center pt-3 pb-1", children: _jsx(View, { className: "w-10 h-1 rounded-full bg-border-strong" }) }), title || showCloseButton ? (_jsxs(View, { className: "flex-row items-center justify-between px-4", children: [_jsx(Typography.Text, { accessibilityRole: "header", className: "flex-1 text-text-primary py-2", semibold: true, variant: "body", children: title ?? label ?? "Seleccionar" }), showCloseButton ? (_jsx(Pressable, { accessibilityLabel: "Cerrar opciones", onPress: () => setOpen(false), style: {
29
+ minWidth: 48,
30
+ minHeight: 48,
31
+ justifyContent: "center",
32
+ alignItems: "center",
33
+ }, children: _jsx(Typography.Text, { className: "text-text-accent", variant: "bodySmall", children: "Cerrar" }) })) : null] })) : null, _jsx(ScrollView, { children: options.map((option) => {
34
34
  const isSelected = option.value === value;
35
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: () => {
36
36
  onChange(option.value);
37
37
  setOpen(false);
38
- }, 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)));
39
- }) })] }) }) }) })] }));
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)));
39
+ }) })] })] }) })] }));
40
40
  }
41
41
  export default Select;
@@ -6,11 +6,14 @@ 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";
12
13
  import IconButton from "./Form.IconButton.js";
13
14
  import Input from "./Form.Input.js";
15
+ import KeyboardForm from "./Form.KeyboardForm.js";
16
+ import KeyboardToolbar from "./Form.KeyboardToolbar.js";
14
17
  import MonetaryInput from "./Form.MonetaryInput.js";
15
18
  import PinInput from "./Form.PinInput.js";
16
19
  import QuantitySelector from "./Form.QuantitySelector.js";
@@ -23,6 +26,9 @@ export declare const Form: {
23
26
  TextInput: import("react").FC<import("./Form.TextInput.js").TextInputFProps>;
24
27
  Input: typeof Input;
25
28
  InputField: import("react").FC<import("./Form.InputField.js").InputFieldProps>;
29
+ KeyboardForm: typeof KeyboardForm;
30
+ KeyboardToolbar: typeof KeyboardToolbar;
31
+ ErrorSummary: typeof ErrorSummary;
26
32
  Button: import("react").FC<import("./Form.Button.js").ButtonProps> & {
27
33
  Icon: import("react").FC<import("./Form.Button.js").ButtonIconProps>;
28
34
  };
@@ -50,4 +56,7 @@ export declare const Form: {
50
56
  FilterButtonGroup: typeof FilterButtonGroup;
51
57
  };
52
58
  export { type DropdownOption };
59
+ export type { ErrorSummaryItem, ErrorSummaryProps, } from "./Form.ErrorSummary.js";
60
+ export type { KeyboardFormProps } from "./Form.KeyboardForm.js";
61
+ export type { KeyboardToolbarAction, KeyboardToolbarProps, } from "./Form.KeyboardToolbar.js";
53
62
  export default Form;
@@ -7,12 +7,15 @@ 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";
13
14
  import IconButton from "./Form.IconButton.js";
14
15
  import Input from "./Form.Input.js";
15
16
  import InputField from "./Form.InputField.js";
17
+ import KeyboardForm from "./Form.KeyboardForm.js";
18
+ import KeyboardToolbar from "./Form.KeyboardToolbar.js";
16
19
  import MonetaryInput from "./Form.MonetaryInput.js";
17
20
  import PasswordInput from "./Form.PasswordInput.js";
18
21
  import PinInput from "./Form.PinInput.js";
@@ -27,6 +30,9 @@ export const Form = {
27
30
  TextInput,
28
31
  Input,
29
32
  InputField,
33
+ KeyboardForm,
34
+ KeyboardToolbar,
35
+ ErrorSummary,
30
36
  Button: ButtonWithIcon,
31
37
  IconButton,
32
38
  PasswordInput,
@@ -35,16 +35,18 @@ function AppBarAction({ icon, label, onPress, badge, accessibilityLabel, tone =
35
35
  // primary/secondary header actions like "Editar", "Nuevo", "Guardar".
36
36
  if (label) {
37
37
  return (_jsxs(Pressable, { accessibilityLabel: accessibilityLabel ?? label, accessibilityRole: "button", className: "flex-row items-center justify-center gap-1.5", hitSlop: 6, onPress: onPress, style: {
38
- height: 36,
38
+ minHeight: 48,
39
+ minWidth: 48,
39
40
  paddingHorizontal: 14,
41
+ paddingVertical: 8,
40
42
  borderRadius: 999,
41
43
  backgroundColor: bg,
42
44
  ...(showBorder ? { borderWidth: 1, borderColor: theme.border.light } : null),
43
- }, testID: testID, children: [icon ? _jsx(Icon, { color: iconColor, name: icon, size: 16 }) : null, _jsx(Typography.Text, { semibold: true, style: { color: iconColor }, variant: "bodySmall", children: label })] }));
45
+ }, testID: testID, children: [icon ? _jsx(Icon, { color: iconColor, name: icon, size: 16 }) : null, _jsx(Typography.Text, { semibold: true, style: { color: iconColor, flexShrink: 1 }, variant: "bodySmall", children: label })] }));
44
46
  }
45
47
  return (_jsxs(Pressable, { accessibilityLabel: accessibilityLabel, accessibilityRole: "button", className: "items-center justify-center", hitSlop: 6, onPress: onPress, style: {
46
- width: 40,
47
- height: 40,
48
+ width: 48,
49
+ height: 48,
48
50
  borderRadius: 12,
49
51
  backgroundColor: bg,
50
52
  ...(showBorder ? { borderWidth: 1, borderColor: theme.border.light } : null),
@@ -106,13 +108,13 @@ function AppBar({ title, subtitle, onBack, leading, actions, subheader, variant
106
108
  ? { borderBottomLeftRadius: 24, borderBottomRightRadius: 24, overflow: "hidden" }
107
109
  : null),
108
110
  ...(showShadow ? theme.shadow.md : null),
109
- }, testID: testID, children: [hasBackground ? (_jsx(GradientSurface, { colors: background.colors, end: background.end, padding: "none", preset: background.surface ?? "warm", radius: "none", start: background.start, style: StyleSheet.absoluteFill })) : gradient ? (_jsx(GradientSurface, { colors: theme.gradient.accent, padding: "none", radius: "none", style: StyleSheet.absoluteFill })) : null, background?.pattern ? (_jsx(Pattern, { color: background.patternColor ?? "#FFFFFF", kind: background.pattern, opacity: background.patternOpacity ?? 0.18 })) : null, showTopBar ? (_jsxs(View, { className: "flex-row items-center px-3 gap-2 min-h-[56px]", children: [_jsxs(View, { className: "flex-row items-center gap-2", style: { minWidth: 38 }, children: [onBack ? (_jsx(AppBarAction, { accessibilityLabel: "Volver", bare: backVariant === "plain", icon: "chevron-back", onDark: dark || gradient || hasBackground, onPress: onBack })) : null, leading] }), _jsxs(View, { className: cn("flex-1", centered && "items-center"), children: [title ? (_jsx(Typography.Text, { className: colored ? "" : "text-text-primary", numberOfLines: 1, semibold: true, style: titleStyle, variant: "subtitle", children: title })) : null, subtitle ? (_jsx(Typography.Text, { className: colored ? "" : "text-text-secondary", numberOfLines: 1, style: subtitleStyle, variant: "caption", children: subtitle })) : null] }), _jsx(View, { className: "flex-row items-center justify-end gap-2", style: { minWidth: 38 }, children: actions })] })) : null, large && title ? (_jsxs(View, { className: cn("flex-row px-4 pb-3", largeHasNav ? "items-center gap-2" : ""), style: { paddingTop: topSpacing != null ? topSpacing : 8 }, children: [onBack ? (_jsx(AppBarAction, { accessibilityLabel: "Volver", bare: backVariant === "plain", icon: "chevron-back", onDark: dark || gradient || hasBackground, onPress: onBack })) : null, leading, accentBar && !largeHasNav ? (_jsx(View, { style: {
111
+ }, testID: testID, children: [hasBackground ? (_jsx(GradientSurface, { colors: background.colors, end: background.end, padding: "none", preset: background.surface ?? "warm", radius: "none", start: background.start, style: StyleSheet.absoluteFill })) : gradient ? (_jsx(GradientSurface, { colors: theme.gradient.accent, padding: "none", radius: "none", style: StyleSheet.absoluteFill })) : null, background?.pattern ? (_jsx(Pattern, { color: background.patternColor ?? "#FFFFFF", kind: background.pattern, opacity: background.patternOpacity ?? 0.18 })) : null, showTopBar ? (_jsxs(View, { className: "flex-row items-center px-3 gap-2 min-h-[56px]", children: [_jsxs(View, { className: "flex-row items-center gap-2", style: { minWidth: 38 }, children: [onBack ? (_jsx(AppBarAction, { accessibilityLabel: "Volver", bare: backVariant === "plain", icon: "chevron-back", onDark: dark || gradient || hasBackground, onPress: onBack })) : null, leading] }), _jsxs(View, { className: cn("flex-1", centered && "items-center"), children: [title ? (_jsx(Typography.Text, { accessibilityRole: "header", className: colored ? "" : "text-text-primary", semibold: true, style: titleStyle, variant: "subtitle", children: title })) : null, subtitle ? (_jsx(Typography.Text, { className: colored ? "" : "text-text-secondary", style: subtitleStyle, variant: "caption", children: subtitle })) : null] }), _jsx(View, { className: "flex-row items-center justify-end gap-2", style: { minWidth: 38 }, children: actions })] })) : null, large && title ? (_jsxs(View, { className: cn("flex-row px-4 pb-3", largeHasNav ? "items-center gap-2" : ""), style: { paddingTop: topSpacing != null ? topSpacing : 8 }, children: [onBack ? (_jsx(AppBarAction, { accessibilityLabel: "Volver", bare: backVariant === "plain", icon: "chevron-back", onDark: dark || gradient || hasBackground, onPress: onBack })) : null, leading, accentBar && !largeHasNav ? (_jsx(View, { style: {
110
112
  width: 4,
111
113
  marginRight: 10,
112
114
  marginVertical: 2,
113
115
  borderRadius: 999,
114
116
  backgroundColor: theme.accent,
115
- } })) : null, _jsxs(View, { className: "flex-1", children: [_jsx(Typography.Text, { className: colored ? "" : "text-text-primary", style: titleStyle, variant: "h2", children: title }), subtitle ? (_jsx(Typography.Text, { className: colored ? "" : "text-text-secondary", style: subtitleStyle, variant: "bodySmall", children: subtitle })) : null] }), actions ? (_jsx(View, { className: cn("flex-row items-center justify-end gap-2 pl-2", largeHasNav ? "" : "self-center"), children: actions })) : null] })) : null, subheader ? _jsx(View, { className: "px-3 pb-3 pt-0.5", children: subheader }) : null, showWave ? (_jsx(View, { style: {
117
+ } })) : null, _jsxs(View, { className: "flex-1", children: [_jsx(Typography.Text, { accessibilityRole: "header", className: colored ? "" : "text-text-primary", style: titleStyle, variant: "h2", children: title }), subtitle ? (_jsx(Typography.Text, { className: colored ? "" : "text-text-secondary", style: subtitleStyle, variant: "bodySmall", children: subtitle })) : null] }), actions ? (_jsx(View, { className: cn("flex-row items-center justify-end gap-2 pl-2", largeHasNav ? "" : "self-center"), children: actions })) : null] })) : null, subheader ? _jsx(View, { className: "px-3 pb-3 pt-0.5", children: subheader }) : null, showWave ? (_jsx(View, { style: {
116
118
  height: 24,
117
119
  backgroundColor: theme.bg.page,
118
120
  borderTopLeftRadius: 24,
@@ -52,9 +52,14 @@ function BottomNav({ items, value, onChange, showLabels = true, centerAction, sa
52
52
  // Variant A — extended pill with text (active = horizontal pill, inactive = icon only).
53
53
  if (indicator === "pill-text") {
54
54
  if (active) {
55
- return (_jsxs(Pressable, { accessibilityLabel: item.label ?? item.key, accessibilityRole: "tab", accessibilityState: { selected: true }, className: "flex-row items-center px-4 py-2", onPress: () => onChange(item.key), style: { borderRadius: 999, backgroundColor: activeTint }, children: [renderIconNode(item, true, onActive), item.label ? (_jsx(Typography.Text, { semibold: true, style: { color: onActive, fontSize: 13, marginLeft: 8 }, children: item.label })) : null] }, item.key));
55
+ return (_jsxs(Pressable, { accessibilityLabel: item.label ?? item.key, accessibilityRole: "tab", accessibilityState: { selected: true }, className: "flex-row items-center px-4 py-2", onPress: () => onChange(item.key), style: {
56
+ borderRadius: 999,
57
+ backgroundColor: activeTint,
58
+ minHeight: 48,
59
+ minWidth: 48,
60
+ }, children: [renderIconNode(item, true, onActive), item.label ? (_jsx(Typography.Text, { semibold: true, style: { color: onActive, fontSize: 13, marginLeft: 8, flexShrink: 1 }, children: item.label })) : null] }, item.key));
56
61
  }
57
- return (_jsx(Pressable, { accessibilityLabel: item.label ?? item.key, accessibilityRole: "tab", className: "items-center justify-center px-3 py-2", onPress: () => onChange(item.key), children: renderIconNode(item, false, inactiveColor) }, item.key));
62
+ return (_jsx(Pressable, { accessibilityLabel: item.label ?? item.key, accessibilityRole: "tab", className: "items-center justify-center px-3 py-2", onPress: () => onChange(item.key), style: { minHeight: 48, minWidth: 48 }, children: renderIconNode(item, false, inactiveColor) }, item.key));
58
63
  }
59
64
  // Variants none / pill / bar / glow — vertical icon + optional indicator + optional label.
60
65
  const inPill = indicator === "pill" && active;
@@ -70,7 +75,7 @@ function BottomNav({ items, value, onChange, showLabels = true, centerAction, sa
70
75
  labelColor = theme.text.inverse;
71
76
  else if (active)
72
77
  labelColor = brandText;
73
- return (_jsxs(Pressable, { accessibilityLabel: item.label ?? item.key, accessibilityRole: "tab", accessibilityState: { selected: active }, className: "flex-1 items-center justify-center py-1.5", onPress: () => onChange(item.key), children: [indicator === "bar" && active ? (_jsx(View, { style: {
78
+ return (_jsxs(Pressable, { accessibilityLabel: item.label ?? item.key, accessibilityRole: "tab", accessibilityState: { selected: active }, className: "flex-1 items-center justify-center py-1.5", onPress: () => onChange(item.key), style: { minHeight: 48, minWidth: 48 }, children: [indicator === "bar" && active ? (_jsx(View, { style: {
74
79
  position: "absolute",
75
80
  top: 0,
76
81
  width: 26,
@@ -87,7 +92,7 @@ function BottomNav({ items, value, onChange, showLabels = true, centerAction, sa
87
92
  shadowOpacity: 0.9,
88
93
  shadowRadius: 5,
89
94
  elevation: 4,
90
- } })) : null, showLabels && item.label && indicator !== "glow" ? (_jsx(Typography.Text, { numberOfLines: 1, semibold: active, style: { color: labelColor, fontSize: 11, marginTop: 3 }, children: item.label })) : null] }, item.key));
95
+ } })) : null, showLabels && item.label && indicator !== "glow" ? (_jsx(Typography.Text, { semibold: active, style: { color: labelColor, fontSize: 11, marginTop: 3, textAlign: "center" }, children: item.label })) : null] }, item.key));
91
96
  };
92
97
  const renderCenterFab = () => (_jsx(Pressable, { accessibilityLabel: centerAction?.accessibilityLabel ?? "Acción", accessibilityRole: "button", className: "items-center justify-center", onPress: centerAction?.onPress, style: {
93
98
  width: 52,
@@ -112,7 +117,7 @@ function BottomNav({ items, value, onChange, showLabels = true, centerAction, sa
112
117
  content = items.map(renderItem);
113
118
  }
114
119
  const justify = indicator === "pill-text" ? "justify-around" : "";
115
- return (_jsx(View, { className: cn("flex-row items-center", justify, className), style: containerStyle, testID: testID, children: content }));
120
+ return (_jsx(View, { accessibilityRole: "tablist", className: cn("flex-row items-center", justify, className), style: containerStyle, testID: testID, children: content }));
116
121
  }
117
122
  export default BottomNav;
118
123
  export { BottomNav };
@@ -1,9 +1,9 @@
1
+ import type { ImageSource } from "../utils/image.js";
1
2
  import React, { ReactNode } from "react";
2
- import { type ImageSourcePropType } from "react-native";
3
3
  export interface HeaderProps {
4
4
  title?: string;
5
5
  logo?: ReactNode;
6
- logoSource?: ImageSourcePropType;
6
+ logoSource?: ImageSource;
7
7
  backButton?: boolean;
8
8
  handleBackButton?: () => void;
9
9
  className?: string;
@@ -4,10 +4,12 @@ import Pressable from "../primitives/Pressable.js";
4
4
  import { useTheme } from "../theme/index.js";
5
5
  import { cn } from "../theme/tw.js";
6
6
  import { Typography } from "../Typography/index.js";
7
+ import { useReducedMotion } from "../utils/useReducedMotion.js";
7
8
  import { cloneElement, useRef, useState } from "react";
8
9
  import { Dimensions, Modal, Pressable as RNPressable, View, } from "react-native";
9
10
  function Menu({ items, trigger, menuWidth = 220, testID }) {
10
11
  const theme = useTheme();
12
+ const reducedMotion = useReducedMotion();
11
13
  const triggerRef = useRef(null);
12
14
  const [anchor, setAnchor] = useState(null);
13
15
  const open = () => {
@@ -21,7 +23,12 @@ function Menu({ items, trigger, menuWidth = 220, testID }) {
21
23
  const top = anchor ? anchor.y + anchor.height + 4 : 0;
22
24
  return (_jsxs(View, { collapsable: false, ref: triggerRef, testID: testID, children: [trigger ? (
23
25
  // eslint-disable-next-line react-hooks/refs
24
- cloneElement(trigger, { onPress: open })) : (_jsx(Pressable, { accessibilityLabel: "M\u00E1s opciones", accessibilityRole: "button", onPress: open, children: _jsx(Icon, { color: theme.icon.secondary, name: "ellipsis-vertical", size: 22 }) })), _jsx(Modal, { animationType: "fade", onRequestClose: close, presentationStyle: "overFullScreen", transparent: true, visible: anchor !== null, children: _jsx(RNPressable, { onPress: close, style: { flex: 1 }, children: _jsx(View, { className: "rounded-lg py-1", style: {
26
+ cloneElement(trigger, { onPress: open })) : (_jsx(Pressable, { accessibilityLabel: "M\u00E1s opciones", accessibilityRole: "button", onPress: open, style: {
27
+ minWidth: 48,
28
+ minHeight: 48,
29
+ alignItems: "center",
30
+ justifyContent: "center",
31
+ }, children: _jsx(Icon, { color: theme.icon.secondary, name: "ellipsis-vertical", size: 22 }) })), _jsx(Modal, { animationType: reducedMotion ? "none" : "fade", onRequestClose: close, presentationStyle: "overFullScreen", transparent: true, visible: anchor !== null, children: _jsx(RNPressable, { onPress: close, style: { flex: 1 }, children: _jsx(View, { accessibilityViewIsModal: true, className: "rounded-lg py-1", onAccessibilityEscape: close, style: {
25
32
  position: "absolute",
26
33
  top,
27
34
  right,
@@ -35,10 +42,10 @@ function Menu({ items, trigger, menuWidth = 220, testID }) {
35
42
  return _jsx(View, { className: "my-1 h-px bg-border-default" }, `sep-${index}`);
36
43
  }
37
44
  const item = entry;
38
- return (_jsxs(Pressable, { accessibilityRole: "menuitem", accessibilityState: { disabled: item.disabled }, className: "flex-row items-center gap-3 px-4 py-3", disabled: item.disabled, onPress: () => {
45
+ return (_jsxs(Pressable, { accessibilityLabel: item.label, accessibilityRole: "menuitem", accessibilityState: { disabled: item.disabled }, className: "flex-row items-center gap-3 px-4 py-3", disabled: item.disabled, onPress: () => {
39
46
  close();
40
47
  item.onPress();
41
- }, children: [item.icon ? (_jsx(Icon, { color: item.destructive ? theme.danger : theme.icon.secondary, name: item.icon, size: 18 })) : null, _jsx(Typography.Text, { className: cn("flex-1 text-sm", item.destructive ? "text-danger" : "text-text-primary", item.disabled && "text-text-disabled"), children: item.label })] }, item.label));
48
+ }, style: { minHeight: 48 }, children: [item.icon ? (_jsx(Icon, { color: item.destructive ? theme.danger : theme.icon.secondary, name: item.icon, size: 18 })) : null, _jsx(Typography.Text, { className: cn("flex-1 text-sm", item.destructive ? "text-danger" : "text-text-primary", item.disabled && "text-text-disabled"), children: item.label })] }, item.label));
42
49
  }) }) }) })] }));
43
50
  }
44
51
  export default Menu;
@@ -1,5 +1,5 @@
1
1
  import { IRowItemProps } from "../views/types/IListView.js";
2
- declare function RowItem({ item, className }: IRowItemProps<any> & {
2
+ declare function RowItem({ item, className, onPress, readonly, }: IRowItemProps<any> & {
3
3
  className?: string;
4
4
  }): import("react").JSX.Element;
5
5
  export default RowItem;