@sincpro/mobile-ui 1.0.1 → 1.1.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 (102) hide show
  1. package/README.md +572 -229
  2. package/dist/Actions/Actions.Button.d.ts +7 -2
  3. package/dist/Actions/Actions.Button.js +23 -10
  4. package/dist/Actions/Actions.FAB.d.ts +3 -1
  5. package/dist/Actions/Actions.FAB.js +7 -3
  6. package/dist/Actions/Actions.FilterButton.d.ts +10 -2
  7. package/dist/Actions/Actions.FilterButton.js +21 -6
  8. package/dist/Actions/Actions.IconButton.d.ts +5 -2
  9. package/dist/Actions/Actions.IconButton.js +7 -3
  10. package/dist/Actions/index.d.ts +2 -2
  11. package/dist/Actions/index.js +1 -1
  12. package/dist/Dialog/Dialog.ActionSheet.d.ts +2 -1
  13. package/dist/Dialog/Dialog.ActionSheet.js +15 -5
  14. package/dist/Dialog/Dialog.Confirmation.js +3 -3
  15. package/dist/Dialog/Dialog.NativeSheet.d.ts +10 -0
  16. package/dist/Dialog/Dialog.NativeSheet.js +21 -0
  17. package/dist/Dialog/Dialog.Sheet.d.ts +5 -1
  18. package/dist/Dialog/Dialog.Sheet.js +14 -8
  19. package/dist/Display/Display.CopyableText.d.ts +9 -2
  20. package/dist/Display/Display.CopyableText.js +75 -7
  21. package/dist/Display/Display.Icon.js +2 -1
  22. package/dist/Display/Display.KeyValue.d.ts +11 -4
  23. package/dist/Display/Display.KeyValue.js +6 -4
  24. package/dist/Display/Display.ListGroup.d.ts +23 -0
  25. package/dist/Display/Display.ListGroup.js +17 -0
  26. package/dist/Display/Display.ListItem.d.ts +16 -2
  27. package/dist/Display/Display.ListItem.js +25 -8
  28. package/dist/Display/Display.SectionHeader.d.ts +11 -1
  29. package/dist/Display/Display.SectionHeader.js +11 -2
  30. package/dist/Display/Display.SwipeableRow.d.ts +3 -1
  31. package/dist/Display/Display.SwipeableRow.js +3 -3
  32. package/dist/Display/index.d.ts +4 -0
  33. package/dist/Display/index.js +2 -0
  34. package/dist/Feedback/Feedback.Banner.js +1 -1
  35. package/dist/Feedback/Feedback.EmptyState.d.ts +2 -1
  36. package/dist/Feedback/Feedback.EmptyState.js +2 -2
  37. package/dist/Feedback/Feedback.Error.d.ts +3 -1
  38. package/dist/Feedback/Feedback.Error.js +4 -3
  39. package/dist/Form/Form.Checkbox.d.ts +4 -1
  40. package/dist/Form/Form.Checkbox.js +2 -2
  41. package/dist/Form/Form.DatePicker.d.ts +2 -1
  42. package/dist/Form/Form.DatePicker.js +4 -3
  43. package/dist/Form/Form.FilterButton.d.ts +1 -1
  44. package/dist/Form/Form.Input.d.ts +5 -3
  45. package/dist/Form/Form.Input.js +2 -2
  46. package/dist/Form/Form.KeyboardForm.d.ts +11 -0
  47. package/dist/Form/Form.KeyboardForm.js +9 -0
  48. package/dist/Form/Form.KeyboardToolbar.d.ts +21 -0
  49. package/dist/Form/Form.KeyboardToolbar.js +70 -0
  50. package/dist/Form/Form.Select.d.ts +3 -1
  51. package/dist/Form/Form.Select.js +23 -10
  52. package/dist/Form/index.d.ts +6 -0
  53. package/dist/Form/index.js +4 -0
  54. package/dist/Navigation/Navigation.AppBar.d.ts +6 -2
  55. package/dist/Navigation/Navigation.AppBar.js +7 -7
  56. package/dist/Navigation/Navigation.BottomNav.d.ts +3 -1
  57. package/dist/Navigation/Navigation.BottomNav.js +9 -5
  58. package/dist/Navigation/Navigation.RowItem.d.ts +1 -1
  59. package/dist/Navigation/Navigation.RowItem.js +2 -2
  60. package/dist/Navigation/Navigation.SearchBar.d.ts +6 -1
  61. package/dist/Navigation/Navigation.SearchBar.js +29 -8
  62. package/dist/Navigation/Navigation.SearchSuggestions.d.ts +14 -0
  63. package/dist/Navigation/Navigation.SearchSuggestions.js +7 -0
  64. package/dist/Navigation/Navigation.SegmentedControl.d.ts +3 -1
  65. package/dist/Navigation/Navigation.SegmentedControl.js +2 -1
  66. package/dist/Navigation/index.d.ts +6 -2
  67. package/dist/Navigation/index.js +2 -0
  68. package/dist/Typography/Typography.Heading.js +1 -1
  69. package/dist/Typography/Typography.SelectableText.d.ts +10 -0
  70. package/dist/Typography/Typography.SelectableText.js +16 -0
  71. package/dist/Typography/Typography.Text.js +4 -20
  72. package/dist/Typography/index.d.ts +3 -0
  73. package/dist/Typography/index.js +3 -0
  74. package/dist/layouts/AuthContainer.d.ts +4 -1
  75. package/dist/layouts/AuthContainer.js +3 -3
  76. package/dist/primitives/BaseButton.d.ts +7 -2
  77. package/dist/primitives/BaseButton.js +19 -5
  78. package/dist/theme/typography.d.ts +16 -1
  79. package/dist/theme/typography.js +19 -0
  80. package/dist/utils/criteria.d.ts +56 -0
  81. package/dist/utils/criteria.js +99 -0
  82. package/dist/utils/reorder.d.ts +4 -0
  83. package/dist/utils/reorder.js +32 -0
  84. package/dist/views/AuthFormView.d.ts +4 -1
  85. package/dist/views/AuthFormView.js +2 -2
  86. package/dist/views/FormViewV2.d.ts +7 -2
  87. package/dist/views/FormViewV2.js +35 -16
  88. package/dist/views/ListViewV2.d.ts +9 -6
  89. package/dist/views/ListViewV2.js +34 -21
  90. package/dist/widgets/CollectionOptions.d.ts +15 -0
  91. package/dist/widgets/CollectionOptions.js +127 -0
  92. package/dist/widgets/FilterBar.d.ts +21 -0
  93. package/dist/widgets/FilterBar.js +14 -0
  94. package/dist/widgets/FilterBuilder.d.ts +10 -0
  95. package/dist/widgets/FilterBuilder.js +115 -0
  96. package/dist/widgets/QueryChoice.d.ts +10 -0
  97. package/dist/widgets/QueryChoice.js +13 -0
  98. package/dist/widgets/ReorderableList.d.ts +51 -0
  99. package/dist/widgets/ReorderableList.js +138 -0
  100. package/dist/widgets/index.d.ts +4 -0
  101. package/dist/widgets/index.js +3 -0
  102. package/package.json +15 -1
@@ -1,5 +1,6 @@
1
1
  import { type VariantProps } from "../theme/tw.js";
2
2
  import type React from "react";
3
+ import type { AccessibilityProps } from "react-native";
3
4
  declare const button: import("tailwind-variants").TVReturnType<{
4
5
  variant: {
5
6
  primary: "bg-accent";
@@ -128,7 +129,7 @@ declare const buttonIcon: import("tailwind-variants").TVReturnType<{
128
129
  true: "opacity-70";
129
130
  };
130
131
  }, undefined>>;
131
- export interface ButtonProps extends VariantProps<typeof button> {
132
+ export interface ButtonProps extends VariantProps<typeof button>, AccessibilityProps {
132
133
  title: string;
133
134
  onPress?: () => void;
134
135
  confirm?: string;
@@ -136,14 +137,18 @@ export interface ButtonProps extends VariantProps<typeof button> {
136
137
  disabled?: boolean;
137
138
  className?: string;
138
139
  icon?: React.ReactNode;
140
+ testID?: string;
141
+ accessibleSizing?: boolean;
139
142
  }
140
- export interface ButtonIconProps extends VariantProps<typeof buttonIcon> {
143
+ export interface ButtonIconProps extends VariantProps<typeof buttonIcon>, AccessibilityProps {
141
144
  icon: string;
142
145
  onPress?: () => void;
143
146
  confirm?: string;
144
147
  loading?: boolean;
145
148
  disabled?: boolean;
146
149
  className?: string;
150
+ testID?: string;
151
+ accessibleSizing?: boolean;
147
152
  }
148
153
  declare const ButtonWithIcon: React.FC<ButtonProps> & {
149
154
  Icon: React.FC<ButtonIconProps>;
@@ -6,7 +6,6 @@ import { tv } from "../theme/tw.js";
6
6
  import { Typography } from "../Typography/index.js";
7
7
  import { useCallback } from "react";
8
8
  import { ActivityIndicator, TouchableOpacity, View } from "react-native";
9
- import { runOnJS } from "react-native-reanimated";
10
9
  // ─── Style variants ───────────────────────────────────────────────────────────
11
10
  const button = tv({
12
11
  base: "rounded-md items-center justify-center flex-row min-h-[44px]",
@@ -152,7 +151,7 @@ const textVariantMap = {
152
151
  medium: "button",
153
152
  large: "buttonLarge",
154
153
  };
155
- const Button = ({ title, onPress, confirm, variant = "primary", size = "medium", loading = false, disabled = false, className, icon, }) => {
154
+ const Button = ({ title, onPress, confirm, variant = "primary", size = "medium", loading = false, disabled = false, className, icon, testID, accessibleSizing = false, accessibilityLabel, accessibilityState, ...accessibilityProps }) => {
156
155
  const { show, hide } = useConfirmationContext();
157
156
  const textColorClass = textColor({ variant: variant, disabled });
158
157
  const loadingColorValue = getLoadingColor(variant);
@@ -166,22 +165,32 @@ const Button = ({ title, onPress, confirm, variant = "primary", size = "medium",
166
165
  cancelText: "Cancelar",
167
166
  onConfirm: () => {
168
167
  hide();
169
- runOnJS(onPress)();
168
+ onPress();
170
169
  },
171
170
  onCancel: () => hide(),
172
171
  });
173
172
  }
174
173
  else if (onPress) {
175
- runOnJS(onPress)();
174
+ onPress();
176
175
  }
177
176
  }, [onPress, confirm, show, hide]);
178
177
  if (variant === "link") {
179
- return (_jsx(TouchableOpacity, { activeOpacity: 0.7, className: className, disabled: disabled || loading, onPress: handlePress, children: loading ? (_jsx(ActivityIndicator, { color: loadingColorValue, size: "small" })) : (_jsx(Typography.Text, { className: textColorClass, variant: textVariantValue, children: title })) }));
178
+ return (_jsx(TouchableOpacity, { activeOpacity: 0.7, className: className, disabled: disabled || loading, onPress: handlePress, ...accessibilityProps, accessibilityLabel: accessibilityLabel ?? title, accessibilityRole: "button", accessibilityState: {
179
+ ...accessibilityState,
180
+ disabled: disabled || loading,
181
+ busy: loading,
182
+ }, style: accessibleSizing
183
+ ? { minHeight: 48, minWidth: 48, justifyContent: "center" }
184
+ : undefined, testID: testID, children: loading ? (_jsx(ActivityIndicator, { color: loadingColorValue, size: "small" })) : (_jsx(Typography.Text, { className: textColorClass, variant: textVariantValue, children: title })) }));
180
185
  }
181
186
  const emphasize = variant === "primary" || variant === "cta";
182
- return (_jsx(TouchableOpacity, { activeOpacity: 0.8, className: button({ variant, size, disabled, className }), disabled: disabled || loading, onPress: handlePress, children: loading ? (_jsx(ActivityIndicator, { color: loadingColorValue, size: "small" })) : (_jsxs(_Fragment, { children: [icon ? _jsx(View, { className: "mr-2", children: icon }) : null, _jsx(Typography.Text, { allowFontScaling: false, className: textColorClass, ellipsizeMode: "tail", numberOfLines: 1, semibold: emphasize, variant: textVariantValue, children: title })] })) }));
187
+ return (_jsx(TouchableOpacity, { activeOpacity: 0.8, className: button({ variant, size, disabled, className }), disabled: disabled || loading, onPress: handlePress, ...accessibilityProps, accessibilityLabel: accessibilityLabel ?? title, accessibilityRole: "button", accessibilityState: {
188
+ ...accessibilityState,
189
+ disabled: disabled || loading,
190
+ busy: loading,
191
+ }, style: accessibleSizing ? { minHeight: 48, minWidth: 48 } : undefined, testID: testID, children: loading ? (_jsx(ActivityIndicator, { color: loadingColorValue, size: "small" })) : (_jsxs(_Fragment, { children: [icon ? _jsx(View, { className: "mr-2", children: icon }) : null, _jsx(Typography.Text, { allowFontScaling: accessibleSizing, className: textColorClass, ellipsizeMode: "tail", numberOfLines: accessibleSizing ? undefined : 1, semibold: emphasize, variant: textVariantValue, children: title })] })) }));
183
192
  };
184
- const ButtonIcon = ({ icon, onPress, confirm, variant = "primary", size = "medium", loading = false, disabled = false, className, }) => {
193
+ const ButtonIcon = ({ icon, onPress, confirm, variant = "primary", size = "medium", loading = false, disabled = false, className, testID, accessibleSizing = false, accessibilityState, ...accessibilityProps }) => {
185
194
  const { show, hide } = useConfirmationContext();
186
195
  const iconColorValue = getIconColor(variant, disabled);
187
196
  const iconSizeValue = iconSizeMap[size] ?? 20;
@@ -195,16 +204,20 @@ const ButtonIcon = ({ icon, onPress, confirm, variant = "primary", size = "mediu
195
204
  cancelText: "Cancelar",
196
205
  onConfirm: () => {
197
206
  hide();
198
- runOnJS(onPress)();
207
+ onPress();
199
208
  },
200
209
  onCancel: () => hide(),
201
210
  });
202
211
  }
203
212
  else if (onPress) {
204
- runOnJS(onPress)();
213
+ onPress();
205
214
  }
206
215
  }, [onPress, confirm, show, hide]);
207
- return (_jsx(TouchableOpacity, { activeOpacity: 0.8, className: buttonIcon({ variant, size, disabled, className }), disabled: disabled || loading, onPress: handlePress, children: loading ? (_jsx(ActivityIndicator, { color: loadingColorValue, size: "small" })) : (_jsx(Icon, { color: iconColorValue, name: icon, size: iconSizeValue })) }));
216
+ return (_jsx(TouchableOpacity, { activeOpacity: 0.8, className: buttonIcon({ variant, size, disabled, className }), disabled: disabled || loading, onPress: handlePress, ...accessibilityProps, accessibilityRole: "button", accessibilityState: {
217
+ ...accessibilityState,
218
+ disabled: disabled || loading,
219
+ busy: loading,
220
+ }, style: accessibleSizing ? { minHeight: 48, minWidth: 48 } : undefined, testID: testID, children: loading ? (_jsx(ActivityIndicator, { color: loadingColorValue, size: "small" })) : (_jsx(Icon, { color: iconColorValue, name: icon, size: iconSizeValue })) }));
208
221
  };
209
222
  const ButtonWithIcon = Object.assign(Button, { Icon: ButtonIcon });
210
223
  export default ButtonWithIcon;
@@ -5,6 +5,8 @@ export interface FABProps {
5
5
  position?: "bottom-right" | "bottom-left";
6
6
  className?: string;
7
7
  testID?: string;
8
+ accessibilityLabel?: string;
9
+ accessibleSizing?: boolean;
8
10
  }
9
- declare function FAB({ icon, onPress, label, position, className, testID, }: FABProps): import("react").JSX.Element;
11
+ declare function FAB({ icon, onPress, label, position, className, testID, accessibilityLabel, accessibleSizing, }: FABProps): import("react").JSX.Element;
10
12
  export default FAB;
@@ -5,13 +5,17 @@ import { theme } from "../theme/index.js";
5
5
  import { cn } from "../theme/tw.js";
6
6
  import { Typography } from "../Typography/index.js";
7
7
  import { useSafeAreaInsets } from "react-native-safe-area-context";
8
- function FAB({ icon, onPress, label, position = "bottom-right", className, testID, }) {
8
+ function FAB({ icon, onPress, label, position = "bottom-right", className, testID, accessibilityLabel, accessibleSizing = false, }) {
9
9
  const insets = useSafeAreaInsets();
10
- return (_jsxs(Pressable, { accessibilityLabel: label ?? "Acción", accessibilityRole: "button", className: cn("absolute flex-row items-center justify-center rounded-full bg-primary", label ? "h-14 px-5 gap-2" : "w-14 h-14", className), onPress: onPress, style: {
10
+ return (_jsxs(Pressable, { accessibilityLabel: accessibilityLabel ?? label ?? "Acción", accessibilityRole: "button", className: cn("absolute flex-row items-center justify-center rounded-full bg-primary", label
11
+ ? accessibleSizing
12
+ ? "min-h-14 px-5 py-3 gap-2"
13
+ : "h-14 px-5 gap-2"
14
+ : "w-14 h-14", className), onPress: onPress, style: {
11
15
  bottom: insets.bottom + 16,
12
16
  right: position === "bottom-right" ? 16 : undefined,
13
17
  left: position === "bottom-left" ? 16 : undefined,
14
18
  ...theme.shadow.lg,
15
- }, testID: testID, children: [_jsx(Icon, { color: theme.icon.inverse, name: icon, size: 24 }), label ? (_jsx(Typography.Text, { className: "text-text-on-primary font-semibold", children: label })) : null] }));
19
+ }, testID: testID, children: [_jsx(Icon, { color: theme.icon.inverse, name: icon, size: 24 }), label ? (_jsx(Typography.Text, { className: "text-text-on-primary font-semibold", style: accessibleSizing ? { flexShrink: 1 } : undefined, children: label })) : null] }));
16
20
  }
17
21
  export default FAB;
@@ -3,8 +3,16 @@ export interface FilterButtonProps {
3
3
  title: string;
4
4
  active: boolean;
5
5
  onPress: () => void;
6
+ variant?: "default" | "mobile";
7
+ disabled?: boolean;
8
+ className?: string;
9
+ testID?: string;
6
10
  }
7
11
  export declare const FilterButton: FC<FilterButtonProps>;
8
- export declare function FilterButtonGroup({ children }: {
12
+ export interface FilterButtonGroupProps {
9
13
  children: ReactNode;
10
- }): import("react").JSX.Element;
14
+ layout?: "row" | "scroll" | "wrap";
15
+ className?: string;
16
+ testID?: string;
17
+ }
18
+ export declare function FilterButtonGroup({ children, layout, className, testID, }: FilterButtonGroupProps): import("react").JSX.Element;
@@ -1,7 +1,10 @@
1
- import { jsx as _jsx } from "nativewind/jsx-runtime";
2
- import { tv } from "../theme/tw.js";
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 { cn, tv } from "../theme/tw.js";
3
6
  import { Typography } from "../Typography/index.js";
4
- import { TouchableOpacity, View } from "react-native";
7
+ import { ScrollView, TouchableOpacity, View } from "react-native";
5
8
  const filterButton = tv({
6
9
  base: "rounded-lg items-center justify-center py-2 px-3 min-h-[36px]",
7
10
  variants: {
@@ -22,7 +25,19 @@ const filterText = tv({
22
25
  },
23
26
  defaultVariants: { active: false },
24
27
  });
25
- export const FilterButton = ({ title, active, onPress }) => (_jsx(TouchableOpacity, { activeOpacity: 0.8, className: filterButton({ active }), onPress: onPress, children: _jsx(Typography.Text, { className: filterText({ active }), variant: "buttonSmall", children: title }) }));
26
- export function FilterButtonGroup({ children }) {
27
- return _jsx(View, { className: "flex-row gap-2 p-3", children: children });
28
+ export const FilterButton = ({ title, active, onPress, variant = "default", disabled = false, className, testID, }) => {
29
+ const theme = useTheme();
30
+ if (variant === "mobile") {
31
+ return (_jsxs(Pressable, { accessibilityLabel: title, accessibilityRole: "button", accessibilityState: { selected: active, disabled }, activeOpacity: 1 - theme.state.press, className: cn("flex-row items-center self-start rounded-full px-3 py-2 gap-2 max-w-full", active ? "bg-container-primary" : "bg-bg-muted", className), disabled: disabled, onPress: onPress, style: {
32
+ minHeight: 48,
33
+ minWidth: 48,
34
+ }, testID: testID, children: [active ? (_jsx(View, { accessibilityElementsHidden: true, importantForAccessibility: "no-hide-descendants", children: _jsx(Icon, { color: theme.container.onPrimary, name: "checkmark", size: 18 }) })) : null, _jsx(Typography.Text, { className: cn("flex-shrink", active ? "text-container-on-primary" : "text-text-primary"), variant: "bodyLarge", children: title })] }));
35
+ }
36
+ return (_jsx(TouchableOpacity, { accessibilityLabel: title, accessibilityRole: "button", accessibilityState: { selected: active, disabled }, activeOpacity: 0.8, className: filterButton({ active, className }), disabled: disabled, onPress: onPress, testID: testID, children: _jsx(Typography.Text, { className: filterText({ active }), variant: "buttonSmall", children: title }) }));
37
+ };
38
+ export function FilterButtonGroup({ children, layout = "row", className, testID, }) {
39
+ if (layout === "scroll") {
40
+ return (_jsx(ScrollView, { className: cn("py-1", className), contentContainerStyle: { gap: 8 }, horizontal: true, keyboardShouldPersistTaps: "handled", showsHorizontalScrollIndicator: false, testID: testID, children: children }));
41
+ }
42
+ return (_jsx(View, { className: cn("flex-row gap-2 p-3", layout === "wrap" && "flex-wrap", className), testID: testID, children: children }));
28
43
  }
@@ -1,5 +1,6 @@
1
1
  import { type VariantProps } from "../theme/tw.js";
2
2
  import { IconType } from "../utils/icon.js";
3
+ import type { AccessibilityProps } from "react-native";
3
4
  declare const iconButton: import("tailwind-variants").TVReturnType<{
4
5
  variant: {
5
6
  primary: "bg-primary";
@@ -55,13 +56,15 @@ declare const iconButton: import("tailwind-variants").TVReturnType<{
55
56
  true: "opacity-50";
56
57
  };
57
58
  }, undefined>>;
58
- export interface IconButtonProps extends VariantProps<typeof iconButton> {
59
+ export interface IconButtonProps extends VariantProps<typeof iconButton>, AccessibilityProps {
59
60
  icon?: string;
60
61
  type?: IconType;
61
62
  onPress: () => void;
62
63
  loading?: boolean;
63
64
  iconColor?: string;
64
65
  className?: string;
66
+ testID?: string;
67
+ accessibleSizing?: boolean;
65
68
  }
66
- declare function IconButton({ icon, type, onPress, variant, size, loading, disabled, rounded, iconColor, className, }: IconButtonProps): import("react").JSX.Element;
69
+ declare function IconButton({ icon, type, onPress, variant, size, loading, disabled, rounded, iconColor, className, testID, accessibleSizing, accessibilityState, ...accessibilityProps }: IconButtonProps): import("react").JSX.Element;
67
70
  export default IconButton;
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx } from "nativewind/jsx-runtime";
2
- import { Display } from "../Display/index.js";
2
+ import Icon from "../Display/Display.Icon.js";
3
3
  import { theme } from "../theme/index.js";
4
4
  import { tv } from "../theme/tw.js";
5
5
  import { ActivityIndicator, TouchableOpacity } from "react-native";
@@ -31,8 +31,12 @@ const iconButton = tv({
31
31
  disabled: false,
32
32
  },
33
33
  });
34
- function IconButton({ icon, type = "feather", onPress, variant = "ghost", size = "medium", loading = false, disabled = false, rounded = false, iconColor, className, }) {
34
+ function IconButton({ icon, type = "feather", onPress, variant = "ghost", size = "medium", loading = false, disabled = false, rounded = false, iconColor, className, testID, accessibleSizing = false, accessibilityState, ...accessibilityProps }) {
35
35
  const resolvedColor = iconColor ?? theme.text.accent;
36
- return (_jsx(TouchableOpacity, { activeOpacity: 0.7, className: iconButton({ variant, size, rounded, disabled, className }), disabled: disabled || loading, onPress: onPress, children: loading ? (_jsx(ActivityIndicator, { color: resolvedColor, size: "small" })) : (_jsx(Display.Icon, { color: resolvedColor, name: icon, size: 24, type: type })) }));
36
+ return (_jsx(TouchableOpacity, { activeOpacity: 0.7, className: iconButton({ variant, size, rounded, disabled, className }), disabled: disabled || loading, onPress: onPress, ...accessibilityProps, accessibilityRole: "button", accessibilityState: {
37
+ ...accessibilityState,
38
+ disabled: disabled || loading,
39
+ busy: loading,
40
+ }, style: accessibleSizing ? { minHeight: 48, minWidth: 48 } : undefined, testID: testID, children: loading ? (_jsx(ActivityIndicator, { color: resolvedColor, size: "small" })) : (_jsx(Icon, { color: resolvedColor, name: icon, size: 24, type: type })) }));
37
41
  }
38
42
  export default IconButton;
@@ -12,11 +12,11 @@ export declare const Actions: {
12
12
  FAB: typeof FAB;
13
13
  /** Chip-style active/inactive toggle — use inside `Actions.FilterButtonGroup`. */
14
14
  FilterButton: import("react").FC<import("./Actions.FilterButton.js").FilterButtonProps>;
15
- /** Horizontal scroll container for `Actions.FilterButton` chips. */
15
+ /** Filter chip layout; opt into horizontal scrolling or wrapping with `layout`. */
16
16
  FilterButtonGroup: typeof FilterButtonGroup;
17
17
  };
18
18
  export type { ButtonIconProps, ButtonProps } from "./Actions.Button.js";
19
19
  export type { FABProps } from "./Actions.FAB.js";
20
- export type { FilterButtonProps } from "./Actions.FilterButton.js";
20
+ export type { FilterButtonGroupProps, FilterButtonProps, } from "./Actions.FilterButton.js";
21
21
  export type { IconButtonProps } from "./Actions.IconButton.js";
22
22
  export default Actions;
@@ -11,7 +11,7 @@ export const Actions = {
11
11
  FAB,
12
12
  /** Chip-style active/inactive toggle — use inside `Actions.FilterButtonGroup`. */
13
13
  FilterButton,
14
- /** Horizontal scroll container for `Actions.FilterButton` chips. */
14
+ /** Filter chip layout; opt into horizontal scrolling or wrapping with `layout`. */
15
15
  FilterButtonGroup,
16
16
  };
17
17
  export default Actions;
@@ -12,6 +12,7 @@ export interface ActionSheetProps {
12
12
  title?: string;
13
13
  message?: string;
14
14
  cancelLabel?: string;
15
+ testID?: string;
15
16
  }
16
- declare function ActionSheet({ visible, onClose, actions, title, message, cancelLabel, }: ActionSheetProps): import("react").JSX.Element;
17
+ declare function ActionSheet({ visible, onClose, actions, title, message, cancelLabel, testID, }: ActionSheetProps): import("react").JSX.Element;
17
18
  export default ActionSheet;
@@ -1,13 +1,15 @@
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 { vars } from "nativewind";
7
8
  import { useEffect, useRef } from "react";
8
- import { Animated, Modal, Pressable as RNPressable, View } from "react-native";
9
+ import { Animated, Modal, ScrollView, View } from "react-native";
9
10
  import { useSafeAreaInsets } from "react-native-safe-area-context";
10
- function ActionSheet({ visible, onClose, actions, title, message, cancelLabel = "Cancelar", }) {
11
+ function ActionSheet({ visible, onClose, actions, title, message, cancelLabel = "Cancelar", testID, }) {
12
+ const theme = useTheme();
11
13
  const insets = useSafeAreaInsets();
12
14
  // eslint-disable-next-line react-hooks/refs
13
15
  const translateY = useRef(new Animated.Value(800)).current;
@@ -24,9 +26,17 @@ function ActionSheet({ visible, onClose, actions, title, message, cancelLabel =
24
26
  translateY.setValue(800);
25
27
  }
26
28
  }, [visible, translateY]);
27
- return (_jsx(Modal, { animationType: "none", onRequestClose: onClose, presentationStyle: "overFullScreen", transparent: true, visible: visible, children: _jsx(RNPressable, { className: "flex-1 justify-end bg-black/50", onPress: onClose, children: _jsx(Animated.View, { style: { transform: [{ translateY }] }, children: _jsxs(RNPressable, { style: { paddingHorizontal: 12, paddingBottom: insets.bottom + 12 }, children: [title || message ? (_jsxs(View, { className: "rounded-xl mb-2 px-4 py-3 items-center", style: { backgroundColor: theme.bg.card }, children: [title ? (_jsx(Typography.Text, { className: "text-text-primary", semibold: true, children: title })) : null, message ? (_jsx(Typography.Text, { className: "text-text-secondary text-sm", variant: "bodySmall", children: message })) : null] })) : null, _jsx(View, { className: "rounded-xl overflow-hidden mb-2", style: { backgroundColor: theme.bg.card }, children: actions.map((action, index) => (_jsxs(Pressable, { accessibilityRole: "button", accessibilityState: { disabled: action.disabled }, className: cn("flex-row items-center justify-center gap-2 px-4 py-4", index < actions.length - 1 && "border-b border-border-light"), disabled: action.disabled, onPress: () => {
29
+ return (_jsx(Modal, { animationType: "none", onRequestClose: onClose, presentationStyle: "overFullScreen", transparent: true, visible: visible, children: _jsxs(View, { className: "flex-1 justify-end", testID: testID, children: [_jsx(View, { accessibilityElementsHidden: true, className: "absolute inset-0 bg-black/50", importantForAccessibility: "no-hide-descendants", onResponderRelease: onClose, onStartShouldSetResponder: () => true }), _jsxs(Animated.View, { accessibilityViewIsModal: true, onAccessibilityEscape: onClose, style: [
30
+ vars(themeToVars(theme)),
31
+ {
32
+ transform: [{ translateY }],
33
+ maxHeight: "90%",
34
+ paddingHorizontal: 12,
35
+ paddingBottom: insets.bottom + 12,
36
+ },
37
+ ], children: [title || message ? (_jsxs(View, { className: "rounded-xl mb-2 px-4 py-3 items-center", style: { backgroundColor: theme.bg.card }, children: [title ? (_jsx(Typography.Text, { accessibilityRole: "header", className: "text-text-primary", semibold: true, children: title })) : null, message ? (_jsx(Typography.Text, { className: "text-text-secondary text-sm", variant: "bodySmall", children: message })) : null] })) : null, _jsx(ScrollView, { bounces: false, className: "rounded-xl overflow-hidden mb-2", style: { backgroundColor: theme.bg.card }, children: actions.map((action, index) => (_jsxs(Pressable, { accessibilityLabel: action.label, accessibilityRole: "button", accessibilityState: { disabled: action.disabled }, className: cn("flex-row items-center justify-center gap-2 px-4 py-4", index < actions.length - 1 && "border-b border-border-light"), disabled: action.disabled, onPress: () => {
28
38
  onClose();
29
39
  action.onPress();
30
- }, children: [action.icon ? (_jsx(Icon, { color: action.destructive ? theme.danger : theme.icon.primary, name: action.icon, size: 20 })) : null, _jsx(Typography.Text, { className: cn(action.destructive ? "text-danger" : "text-text-primary"), semibold: true, children: action.label })] }, action.label))) }), _jsx(Pressable, { accessibilityRole: "button", className: "rounded-xl px-4 py-4 items-center", onPress: onClose, style: { backgroundColor: theme.bg.card }, children: _jsx(Typography.Text, { className: "text-text-accent", semibold: true, children: cancelLabel }) })] }) }) }) }));
40
+ }, style: { minHeight: 48 }, children: [action.icon ? (_jsx(Icon, { color: action.destructive ? theme.danger : theme.icon.primary, name: action.icon, size: 20 })) : null, _jsx(Typography.Text, { className: cn(action.destructive ? "text-danger" : "text-text-primary"), semibold: true, children: action.label })] }, action.label))) }), _jsx(Pressable, { accessibilityLabel: cancelLabel, accessibilityRole: "button", className: "rounded-xl px-4 py-4 items-center", onPress: onClose, style: { backgroundColor: theme.bg.card, minHeight: 48 }, children: _jsx(Typography.Text, { className: "text-text-accent", semibold: true, children: cancelLabel }) })] })] }) }));
31
41
  }
32
42
  export default ActionSheet;
@@ -3,11 +3,11 @@ import { BaseButton } from "../primitives/BaseButton.js";
3
3
  import { theme } from "../theme/index.js";
4
4
  import Typography from "../Typography/Typography.Text.js";
5
5
  import { Modal, TouchableWithoutFeedback, View } from "react-native";
6
- const ConfirmationDialog = ({ visible, onCancel, onConfirm, auxTitle = "cambios", subtitle = "¿Estás seguro de que quieres descartar los cambios realizados?", confirmButtonText = "Sí", cancelButtonText = "No, continuar aquí", }) => {
7
- return (_jsx(Modal, { animationType: "fade", onRequestClose: onCancel, presentationStyle: "overFullScreen", transparent: true, visible: visible, children: _jsx(TouchableWithoutFeedback, { onPress: onCancel, children: _jsx(View, { className: "flex-1 justify-center items-center p-6 bg-black/50", children: _jsx(TouchableWithoutFeedback, { children: _jsxs(View, { className: "p-3 w-full max-w-xs mx-auto", style: {
6
+ const ConfirmationDialog = ({ visible, onCancel, onConfirm, auxTitle = "Descartar cambios", subtitle = "¿Quieres descartar los cambios realizados?", confirmButtonText = "Descartar cambios", cancelButtonText = "Seguir editando", }) => {
7
+ return (_jsx(Modal, { animationType: "fade", onRequestClose: onCancel, presentationStyle: "overFullScreen", transparent: true, visible: visible, children: _jsx(TouchableWithoutFeedback, { onPress: onCancel, children: _jsx(View, { className: "flex-1 justify-center items-center p-6 bg-black/50", children: _jsx(TouchableWithoutFeedback, { children: _jsxs(View, { accessibilityViewIsModal: true, className: "p-3 w-full max-w-xs mx-auto", style: {
8
8
  borderRadius: 16,
9
9
  backgroundColor: theme.bg.popover,
10
10
  ...theme.shadow.md,
11
- }, children: [_jsx(Typography, { className: "mb-1.5 text-sm", variant: "body", children: _jsx(Typography, { semibold: true, variant: "body", children: auxTitle }) }), _jsx(Typography, { className: "mb-3 text-xs leading-tight", variant: "body", children: subtitle }), _jsxs(View, { className: "flex-row gap-2", children: [_jsx(BaseButton, { className: "flex-1", onPress: onCancel, size: "small", title: cancelButtonText, variant: "outlineBlack" }), _jsx(BaseButton, { className: "flex-1", onPress: onConfirm, size: "small", title: confirmButtonText, variant: "primary" })] })] }) }) }) }) }));
11
+ }, children: [_jsx(Typography, { accessibilityRole: "header", className: "mb-1.5 text-sm", variant: "body", children: _jsx(Typography, { semibold: true, variant: "body", children: auxTitle }) }), _jsx(Typography, { className: "mb-3 text-xs leading-tight", variant: "body", children: subtitle }), _jsxs(View, { className: "flex-row gap-2", children: [_jsx(BaseButton, { className: "flex-1", onPress: onCancel, size: "small", title: cancelButtonText, variant: "outlineBlack" }), _jsx(BaseButton, { className: "flex-1", onPress: onConfirm, size: "small", title: confirmButtonText, variant: "primary" })] })] }) }) }) }) }));
12
12
  };
13
13
  export default ConfirmationDialog;
@@ -0,0 +1,10 @@
1
+ import type { ReactNode } from "react";
2
+ export interface NativeSheetProps {
3
+ visible: boolean;
4
+ onClose: () => void;
5
+ title: string;
6
+ children: ReactNode;
7
+ testID?: string;
8
+ }
9
+ declare function NativeSheet({ visible, onClose, title, children, testID }: NativeSheetProps): import("react").JSX.Element;
10
+ export default NativeSheet;
@@ -0,0 +1,21 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "nativewind/jsx-runtime";
2
+ import BottomSheet, { BottomSheetView } from "@expo/ui/community/bottom-sheet";
3
+ import Pressable from "../primitives/Pressable.js";
4
+ import { themeToVars, useTheme } from "../theme/index.js";
5
+ import { Typography } from "../Typography/index.js";
6
+ import { vars } from "nativewind";
7
+ import { ScrollView, View } from "react-native";
8
+ const SNAP_POINTS = ["60%", "90%"];
9
+ function NativeSheet({ visible, onClose, title, children, testID }) {
10
+ const theme = useTheme();
11
+ return (_jsx(BottomSheet, { backgroundStyle: { backgroundColor: theme.bg.card }, enablePanDownToClose: true, index: visible ? 0 : -1, onClose: () => {
12
+ if (visible)
13
+ onClose();
14
+ }, snapPoints: SNAP_POINTS, children: _jsx(BottomSheetView, { style: { flex: 1 }, children: _jsxs(View, { accessibilityViewIsModal: true, onAccessibilityEscape: onClose, style: [vars(themeToVars(theme)), { flex: 1, padding: 16 }], testID: testID, children: [_jsxs(View, { className: "flex-row items-center gap-3", children: [_jsx(Typography.Heading, { className: "flex-1", variant: "h4", children: title }), _jsx(Pressable, { accessibilityLabel: "Cerrar", onPress: onClose, style: {
15
+ minHeight: 48,
16
+ minWidth: 48,
17
+ justifyContent: "center",
18
+ alignItems: "center",
19
+ }, children: _jsx(Typography.Text, { className: "text-text-accent", variant: "bodySmall", children: "Cerrar" }) })] }), _jsx(ScrollView, { contentContainerStyle: { paddingVertical: 12 }, keyboardShouldPersistTaps: "handled", children: children })] }) }) }));
20
+ }
21
+ export default NativeSheet;
@@ -15,6 +15,10 @@ export interface SheetProps {
15
15
  maxHeightRatio?: number;
16
16
  /** Allow closing by tapping the dim backdrop. Default `false` (handle/button only). */
17
17
  dismissOnBackdrop?: boolean;
18
+ /** Show a visible close button in the sheet header. */
19
+ showCloseButton?: boolean;
20
+ /** Increase interactive targets to 48 points. */
21
+ accessibleSizing?: boolean;
18
22
  testID?: string;
19
23
  }
20
24
  /**
@@ -22,5 +26,5 @@ export interface SheetProps {
22
26
  * **swipe-down to close** (handle only). By default it does NOT close on backdrop tap
23
27
  * (`dismissOnBackdrop`). No provider or native deps. Import via subpath `Dialog/Sheet`.
24
28
  */
25
- declare function Sheet({ visible, onClose, children, title, maxHeightRatio, dismissOnBackdrop, testID, }: SheetProps): import("react").JSX.Element;
29
+ declare function Sheet({ visible, onClose, children, title, maxHeightRatio, dismissOnBackdrop, showCloseButton, accessibleSizing, testID, }: SheetProps): import("react").JSX.Element;
26
30
  export default Sheet;
@@ -3,8 +3,10 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "nativewind/js
3
3
  // This file uses the canonical RN patterns: useRef(new Animated.Value()).current and PanResponder
4
4
  // created inside useState. Those hooks rules warn — they're not bugs here. Documented at the
5
5
  // eslint config level (see eslint.config.js:30-33).
6
- import { theme } from "../theme/index.js";
6
+ import Pressable from "../primitives/Pressable.js";
7
+ import { themeToVars, useTheme } from "../theme/index.js";
7
8
  import { Typography } from "../Typography/index.js";
9
+ import { vars } from "nativewind";
8
10
  import { useEffect, useRef, useState } from "react";
9
11
  import { Animated, Modal, PanResponder, ScrollView, View } from "react-native";
10
12
  import { useSafeAreaInsets } from "react-native-safe-area-context";
@@ -20,7 +22,8 @@ export function SheetProvider({ children }) {
20
22
  * **swipe-down to close** (handle only). By default it does NOT close on backdrop tap
21
23
  * (`dismissOnBackdrop`). No provider or native deps. Import via subpath `Dialog/Sheet`.
22
24
  */
23
- function Sheet({ visible, onClose, children, title, maxHeightRatio = 0.9, dismissOnBackdrop = false, testID, }) {
25
+ function Sheet({ visible, onClose, children, title, maxHeightRatio = 0.9, dismissOnBackdrop = false, showCloseButton = false, accessibleSizing = false, testID, }) {
26
+ const theme = useTheme();
24
27
  const insets = useSafeAreaInsets();
25
28
  const translateY = useRef(new Animated.Value(800)).current;
26
29
  const onCloseRef = useRef(onClose);
@@ -58,11 +61,14 @@ function Sheet({ visible, onClose, children, title, maxHeightRatio = 0.9, dismis
58
61
  }
59
62
  },
60
63
  }));
61
- return (_jsx(Modal, { animationType: "none", onRequestClose: onClose, presentationStyle: "overFullScreen", transparent: true, visible: visible, children: _jsxs(View, { className: "flex-1 justify-end", testID: testID, children: [_jsx(View, { className: "absolute inset-0 bg-black/50", onResponderRelease: dismissOnBackdrop ? onClose : undefined, onStartShouldSetResponder: () => dismissOnBackdrop }), _jsxs(Animated.View, { className: "rounded-t-3xl", style: {
62
- backgroundColor: theme.bg.card,
63
- paddingBottom: insets.bottom + 12,
64
- maxHeight: `${Math.round(maxHeightRatio * 100)}%`,
65
- transform: [{ translateY }],
66
- }, children: [_jsx(View, { className: "items-center pt-3 pb-2", ...pan.panHandlers, children: _jsx(View, { className: "w-12 h-1.5 rounded-full bg-border-strong" }) }), title ? (_jsx(Typography.Text, { className: "text-text-primary px-4 pb-1", semibold: true, variant: "subtitle", children: title })) : null, _jsx(ScrollView, { contentContainerStyle: { paddingHorizontal: 16, paddingTop: 4 }, children: children })] })] }) }));
64
+ return (_jsx(Modal, { animationType: "none", onRequestClose: onClose, presentationStyle: "overFullScreen", transparent: true, visible: visible, children: _jsxs(View, { className: "flex-1 justify-end", testID: testID, children: [_jsx(View, { className: "absolute inset-0 bg-black/50", onResponderRelease: dismissOnBackdrop ? onClose : undefined, onStartShouldSetResponder: () => dismissOnBackdrop }), _jsxs(Animated.View, { accessibilityViewIsModal: true, className: "rounded-t-3xl", importantForAccessibility: "yes", onAccessibilityEscape: onClose, style: [
65
+ vars(themeToVars(theme)),
66
+ {
67
+ backgroundColor: theme.bg.card,
68
+ paddingBottom: insets.bottom + 12,
69
+ maxHeight: `${Math.round(maxHeightRatio * 100)}%`,
70
+ transform: [{ translateY }],
71
+ },
72
+ ], children: [_jsx(View, { accessibilityElementsHidden: true, className: "items-center pt-3 pb-2", ...pan.panHandlers, children: _jsx(View, { className: "w-12 h-1.5 rounded-full bg-border-strong" }) }), title || showCloseButton ? (_jsxs(View, { className: "flex-row items-center px-4 pb-1", children: [title ? (_jsx(Typography.Text, { accessibilityRole: "header", className: "flex-1 text-text-primary", semibold: true, variant: "subtitle", children: title })) : null, showCloseButton ? (_jsx(Pressable, { accessibilityLabel: "Cerrar", accessibilityRole: "button", className: "items-center justify-center rounded-lg", hitSlop: 8, onPress: onClose, style: accessibleSizing ? { minWidth: 48, minHeight: 48 } : undefined, children: _jsx(Typography.Text, { className: "text-text-accent", variant: "bodySmall", children: "Cerrar" }) })) : null] })) : null, _jsx(ScrollView, { contentContainerStyle: { paddingHorizontal: 16, paddingTop: 4 }, children: children })] })] }) }));
67
73
  }
68
74
  export default Sheet;
@@ -1,4 +1,4 @@
1
- type CopyableTextProps = {
1
+ export type CopyableTextProps = ({
2
2
  value: string;
3
3
  getValue?: never;
4
4
  label?: string;
@@ -8,6 +8,13 @@ type CopyableTextProps = {
8
8
  getValue: () => string;
9
9
  label?: string;
10
10
  className?: string;
11
+ }) & {
12
+ accessibilityLabel?: string;
13
+ testID?: string;
14
+ disabled?: boolean;
15
+ onCopied?: (value: string) => void;
16
+ onCopyError?: (error: Error) => void;
17
+ selectable?: boolean;
11
18
  };
12
- declare function CopyableText({ value, getValue, label, className }: CopyableTextProps): import("react").JSX.Element;
19
+ declare function CopyableText({ value, getValue, label, className, accessibilityLabel, testID, disabled, onCopied, onCopyError, selectable, }: CopyableTextProps): import("react").JSX.Element;
13
20
  export default CopyableText;
@@ -1,14 +1,82 @@
1
- import { jsx as _jsx } from "nativewind/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "nativewind/jsx-runtime";
2
+ import Icon from "./Display.Icon.js";
3
+ import Pressable from "../primitives/Pressable.js";
4
+ import { useTheme } from "../theme/index.js";
2
5
  import { Typography } from "../Typography/index.js";
3
6
  import * as Clipboard from "expo-clipboard";
4
- import { TouchableOpacity } from "react-native";
5
- function CopyableText({ value, getValue, label, className }) {
7
+ import { useEffect, useRef, useState } from "react";
8
+ import { AccessibilityInfo, ActivityIndicator, View } from "react-native";
9
+ function CopyableText({ value, getValue, label, className, accessibilityLabel, testID, disabled = false, onCopied, onCopyError, selectable = false, }) {
10
+ const theme = useTheme();
11
+ const pending = useRef(false);
12
+ const [status, setStatus] = useState("idle");
13
+ const [copiedValue, setCopiedValue] = useState();
6
14
  const hasStaticValue = value !== undefined;
7
- const displayText = label || value;
15
+ const isCopied = status === "copied" && (!hasStaticValue || copiedValue === value);
16
+ const displayText = label || value || "Copiar";
17
+ const copyLabel = accessibilityLabel ?? `Copiar ${displayText}`;
18
+ useEffect(() => {
19
+ if (status !== "copied")
20
+ return;
21
+ const timer = setTimeout(() => setStatus("idle"), 3000);
22
+ return () => clearTimeout(timer);
23
+ }, [status]);
8
24
  const handleCopy = async () => {
9
- const textToCopy = hasStaticValue ? value : getValue();
10
- await Clipboard.setStringAsync(textToCopy);
25
+ if (pending.current || disabled)
26
+ return;
27
+ pending.current = true;
28
+ setStatus("copying");
29
+ let textToCopy;
30
+ try {
31
+ textToCopy = hasStaticValue ? value : getValue();
32
+ const copied = await Clipboard.setStringAsync(textToCopy);
33
+ if (!copied)
34
+ throw new Error("El portapapeles no pudo copiar el texto");
35
+ }
36
+ catch (cause) {
37
+ const error = cause instanceof Error ? cause : new Error(String(cause));
38
+ setStatus("error");
39
+ AccessibilityInfo.announceForAccessibility("No se pudo copiar. Toca para reintentar.");
40
+ pending.current = false;
41
+ console.error("CopyableText: no se pudo copiar", error);
42
+ onCopyError?.(error);
43
+ return;
44
+ }
45
+ pending.current = false;
46
+ setCopiedValue(textToCopy);
47
+ setStatus("copied");
48
+ AccessibilityInfo.announceForAccessibility("Copiado");
49
+ onCopied?.(textToCopy);
11
50
  };
12
- return (_jsx(TouchableOpacity, { onPress: handleCopy, children: _jsx(Typography.Text, { className: className || "text-text-accent", semibold: true, variant: "bodySmall", children: displayText }) }));
51
+ return (_jsxs(View, { children: [_jsxs(View, { style: {
52
+ flexDirection: "row",
53
+ alignItems: selectable ? "flex-start" : "center",
54
+ gap: 8,
55
+ }, children: [selectable ? (_jsx(Typography.SelectableText, { className: className, style: { flex: 1 }, value: displayText, variant: "body" })) : null, _jsxs(Pressable, { accessibilityHint: selectable ? "Copia el texto completo" : "Toca para copiar", accessibilityLabel: isCopied ? `${copyLabel}. Copiado` : copyLabel, accessibilityState: {
56
+ disabled: disabled || status === "copying",
57
+ busy: status === "copying",
58
+ }, accessibilityValue: {
59
+ text: isCopied
60
+ ? "Copiado"
61
+ : status === "copying"
62
+ ? "Copiando"
63
+ : status === "error"
64
+ ? "Error al copiar"
65
+ : "",
66
+ }, disabled: disabled || status === "copying", onPress: () => {
67
+ void handleCopy();
68
+ }, style: {
69
+ minHeight: 48,
70
+ minWidth: 48,
71
+ flexDirection: "row",
72
+ alignItems: "center",
73
+ justifyContent: selectable ? "center" : "flex-start",
74
+ gap: 8,
75
+ flexShrink: selectable ? 0 : 1,
76
+ }, testID: testID, children: [!selectable ? (_jsx(Typography.Text, { className: className, style: { flexShrink: 1 }, variant: "body", children: displayText })) : null, _jsx(View, { accessibilityElementsHidden: true, importantForAccessibility: "no-hide-descendants", style: { width: 24, alignItems: "center" }, children: status === "copying" ? (_jsx(ActivityIndicator, { color: theme.text.accent, size: "small" })) : (_jsx(Icon, { color: isCopied ? theme.success : theme.text.accent, name: isCopied
77
+ ? "checkmark"
78
+ : status === "error"
79
+ ? "alert-circle-outline"
80
+ : "copy-outline", size: 20 })) })] })] }), status === "error" ? (_jsx(Typography.Text, { accessibilityLiveRegion: "polite", className: "text-danger", variant: "bodySmall", children: "No se pudo copiar. Toca para reintentar." })) : null] }));
13
81
  }
14
82
  export default CopyableText;
@@ -1,8 +1,9 @@
1
1
  import { jsx as _jsx } from "nativewind/jsx-runtime";
2
2
  import { AntDesign, Feather, FontAwesome, FontAwesome5, Ionicons, MaterialCommunityIcons, MaterialIcons, SimpleLineIcons, } from "@expo/vector-icons";
3
- import { theme } from "../theme/index.js";
3
+ import { useTheme } from "../theme/index.js";
4
4
  import { View } from "react-native";
5
5
  const Icon = ({ name, size = 24, color, type = "ionicons", customIcon, className, }) => {
6
+ const theme = useTheme();
6
7
  const iconColor = color || theme.text.primary;
7
8
  const renderIcon = () => {
8
9
  if (type === "custom" && customIcon) {
@@ -1,10 +1,17 @@
1
1
  import type { ReactNode } from "react";
2
- export interface KeyValueProps {
2
+ interface KeyValueLayoutProps {
3
3
  label: string;
4
- value?: ReactNode;
5
- align?: "row" | "stacked";
4
+ align?: "row" | "stacked" | "auto";
6
5
  className?: string;
7
6
  testID?: string;
7
+ selectable?: boolean;
8
8
  }
9
- declare function KeyValue({ label, value, align, className, testID }: KeyValueProps): import("react").JSX.Element;
9
+ export type KeyValueProps = KeyValueLayoutProps & ({
10
+ copyable: true;
11
+ value: string | number;
12
+ } | {
13
+ copyable?: false;
14
+ value?: ReactNode;
15
+ });
16
+ declare function KeyValue({ label, value, align, className, testID, copyable, selectable, }: KeyValueProps): import("react").JSX.Element;
10
17
  export default KeyValue;