@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.
- package/README.md +572 -229
- package/dist/Actions/Actions.Button.d.ts +7 -2
- package/dist/Actions/Actions.Button.js +23 -10
- package/dist/Actions/Actions.FAB.d.ts +3 -1
- package/dist/Actions/Actions.FAB.js +7 -3
- package/dist/Actions/Actions.FilterButton.d.ts +10 -2
- package/dist/Actions/Actions.FilterButton.js +21 -6
- package/dist/Actions/Actions.IconButton.d.ts +5 -2
- package/dist/Actions/Actions.IconButton.js +7 -3
- package/dist/Actions/index.d.ts +2 -2
- package/dist/Actions/index.js +1 -1
- package/dist/Dialog/Dialog.ActionSheet.d.ts +2 -1
- package/dist/Dialog/Dialog.ActionSheet.js +15 -5
- package/dist/Dialog/Dialog.Confirmation.js +3 -3
- package/dist/Dialog/Dialog.NativeSheet.d.ts +10 -0
- package/dist/Dialog/Dialog.NativeSheet.js +21 -0
- package/dist/Dialog/Dialog.Sheet.d.ts +5 -1
- package/dist/Dialog/Dialog.Sheet.js +14 -8
- package/dist/Display/Display.CopyableText.d.ts +9 -2
- package/dist/Display/Display.CopyableText.js +75 -7
- package/dist/Display/Display.Icon.js +2 -1
- package/dist/Display/Display.KeyValue.d.ts +11 -4
- package/dist/Display/Display.KeyValue.js +6 -4
- package/dist/Display/Display.ListGroup.d.ts +23 -0
- package/dist/Display/Display.ListGroup.js +17 -0
- package/dist/Display/Display.ListItem.d.ts +16 -2
- package/dist/Display/Display.ListItem.js +25 -8
- package/dist/Display/Display.SectionHeader.d.ts +11 -1
- package/dist/Display/Display.SectionHeader.js +11 -2
- package/dist/Display/Display.SwipeableRow.d.ts +3 -1
- package/dist/Display/Display.SwipeableRow.js +3 -3
- package/dist/Display/index.d.ts +4 -0
- package/dist/Display/index.js +2 -0
- package/dist/Feedback/Feedback.Banner.js +1 -1
- package/dist/Feedback/Feedback.EmptyState.d.ts +2 -1
- package/dist/Feedback/Feedback.EmptyState.js +2 -2
- package/dist/Feedback/Feedback.Error.d.ts +3 -1
- package/dist/Feedback/Feedback.Error.js +4 -3
- package/dist/Form/Form.Checkbox.d.ts +4 -1
- package/dist/Form/Form.Checkbox.js +2 -2
- package/dist/Form/Form.DatePicker.d.ts +2 -1
- package/dist/Form/Form.DatePicker.js +4 -3
- package/dist/Form/Form.FilterButton.d.ts +1 -1
- package/dist/Form/Form.Input.d.ts +5 -3
- package/dist/Form/Form.Input.js +2 -2
- package/dist/Form/Form.KeyboardForm.d.ts +11 -0
- package/dist/Form/Form.KeyboardForm.js +9 -0
- package/dist/Form/Form.KeyboardToolbar.d.ts +21 -0
- package/dist/Form/Form.KeyboardToolbar.js +70 -0
- package/dist/Form/Form.Select.d.ts +3 -1
- package/dist/Form/Form.Select.js +23 -10
- package/dist/Form/index.d.ts +6 -0
- package/dist/Form/index.js +4 -0
- package/dist/Navigation/Navigation.AppBar.d.ts +6 -2
- package/dist/Navigation/Navigation.AppBar.js +7 -7
- package/dist/Navigation/Navigation.BottomNav.d.ts +3 -1
- package/dist/Navigation/Navigation.BottomNav.js +9 -5
- package/dist/Navigation/Navigation.RowItem.d.ts +1 -1
- package/dist/Navigation/Navigation.RowItem.js +2 -2
- package/dist/Navigation/Navigation.SearchBar.d.ts +6 -1
- package/dist/Navigation/Navigation.SearchBar.js +29 -8
- package/dist/Navigation/Navigation.SearchSuggestions.d.ts +14 -0
- package/dist/Navigation/Navigation.SearchSuggestions.js +7 -0
- package/dist/Navigation/Navigation.SegmentedControl.d.ts +3 -1
- package/dist/Navigation/Navigation.SegmentedControl.js +2 -1
- package/dist/Navigation/index.d.ts +6 -2
- package/dist/Navigation/index.js +2 -0
- package/dist/Typography/Typography.Heading.js +1 -1
- package/dist/Typography/Typography.SelectableText.d.ts +10 -0
- package/dist/Typography/Typography.SelectableText.js +16 -0
- package/dist/Typography/Typography.Text.js +4 -20
- package/dist/Typography/index.d.ts +3 -0
- package/dist/Typography/index.js +3 -0
- package/dist/layouts/AuthContainer.d.ts +4 -1
- package/dist/layouts/AuthContainer.js +3 -3
- package/dist/primitives/BaseButton.d.ts +7 -2
- package/dist/primitives/BaseButton.js +19 -5
- package/dist/theme/typography.d.ts +16 -1
- package/dist/theme/typography.js +19 -0
- package/dist/utils/criteria.d.ts +56 -0
- package/dist/utils/criteria.js +99 -0
- package/dist/utils/reorder.d.ts +4 -0
- package/dist/utils/reorder.js +32 -0
- package/dist/views/AuthFormView.d.ts +4 -1
- package/dist/views/AuthFormView.js +2 -2
- package/dist/views/FormViewV2.d.ts +7 -2
- package/dist/views/FormViewV2.js +35 -16
- package/dist/views/ListViewV2.d.ts +9 -6
- package/dist/views/ListViewV2.js +34 -21
- package/dist/widgets/CollectionOptions.d.ts +15 -0
- package/dist/widgets/CollectionOptions.js +127 -0
- package/dist/widgets/FilterBar.d.ts +21 -0
- package/dist/widgets/FilterBar.js +14 -0
- package/dist/widgets/FilterBuilder.d.ts +10 -0
- package/dist/widgets/FilterBuilder.js +115 -0
- package/dist/widgets/QueryChoice.d.ts +10 -0
- package/dist/widgets/QueryChoice.js +13 -0
- package/dist/widgets/ReorderableList.d.ts +51 -0
- package/dist/widgets/ReorderableList.js +138 -0
- package/dist/widgets/index.d.ts +4 -0
- package/dist/widgets/index.js +3 -0
- 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
|
-
|
|
168
|
+
onPress();
|
|
170
169
|
},
|
|
171
170
|
onCancel: () => hide(),
|
|
172
171
|
});
|
|
173
172
|
}
|
|
174
173
|
else if (onPress) {
|
|
175
|
-
|
|
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,
|
|
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,
|
|
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
|
-
|
|
207
|
+
onPress();
|
|
199
208
|
},
|
|
200
209
|
onCancel: () => hide(),
|
|
201
210
|
});
|
|
202
211
|
}
|
|
203
212
|
else if (onPress) {
|
|
204
|
-
|
|
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,
|
|
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
|
|
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
|
|
12
|
+
export interface FilterButtonGroupProps {
|
|
9
13
|
children: ReactNode;
|
|
10
|
-
|
|
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
|
|
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
|
|
26
|
-
|
|
27
|
-
|
|
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
|
|
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,
|
|
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;
|
package/dist/Actions/index.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
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;
|
package/dist/Actions/index.js
CHANGED
|
@@ -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
|
-
/**
|
|
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 {
|
|
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,
|
|
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:
|
|
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 = "¿
|
|
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
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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 {
|
|
5
|
-
|
|
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
|
|
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
|
-
|
|
10
|
-
|
|
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 (
|
|
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 {
|
|
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
|
-
|
|
2
|
+
interface KeyValueLayoutProps {
|
|
3
3
|
label: string;
|
|
4
|
-
|
|
5
|
-
align?: "row" | "stacked";
|
|
4
|
+
align?: "row" | "stacked" | "auto";
|
|
6
5
|
className?: string;
|
|
7
6
|
testID?: string;
|
|
7
|
+
selectable?: boolean;
|
|
8
8
|
}
|
|
9
|
-
|
|
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;
|