@sincpro/mobile-ui 1.0.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +701 -231
- package/dist/Actions/Actions.Button.d.ts +15 -12
- package/dist/Actions/Actions.Button.js +25 -14
- package/dist/Actions/Actions.Dock.d.ts +20 -0
- package/dist/Actions/Actions.Dock.js +89 -0
- package/dist/Actions/Actions.FAB.d.ts +2 -1
- package/dist/Actions/Actions.FAB.js +5 -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 +4 -2
- package/dist/Actions/Actions.IconButton.js +7 -3
- package/dist/Actions/index.d.ts +6 -2
- package/dist/Actions/index.js +4 -1
- package/dist/Dialog/BottomSheet/BottomSheet.Root.js +22 -34
- package/dist/Dialog/Dialog.ActionSheet.d.ts +2 -1
- package/dist/Dialog/Dialog.ActionSheet.js +17 -21
- package/dist/Dialog/Dialog.Confirmation.js +8 -6
- package/dist/Dialog/Dialog.EditValue.js +3 -1
- 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 +30 -31
- package/dist/Display/Display.Accordion.js +7 -3
- package/dist/Display/Display.AmountSummary.d.ts +28 -0
- package/dist/Display/Display.AmountSummary.js +25 -0
- package/dist/Display/Display.Carousel.d.ts +27 -4
- package/dist/Display/Display.Carousel.js +112 -12
- package/dist/Display/Display.CopyableText.d.ts +9 -2
- package/dist/Display/Display.CopyableText.js +75 -7
- package/dist/Display/Display.ExpoImage.d.ts +8 -0
- package/dist/Display/Display.ExpoImage.js +15 -0
- package/dist/Display/Display.Icon.js +2 -1
- package/dist/Display/Display.Image.d.ts +10 -6
- package/dist/Display/Display.Image.js +39 -6
- 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 +15 -2
- package/dist/Display/Display.ListItem.js +25 -8
- package/dist/Display/Display.Logo.d.ts +2 -2
- package/dist/Display/Display.Logo.js +3 -2
- package/dist/Display/Display.MediaCard.d.ts +2 -2
- package/dist/Display/Display.MediaEmbed.d.ts +2 -2
- package/dist/Display/Display.ProductCard.d.ts +2 -2
- package/dist/Display/Display.SectionHeader.d.ts +11 -1
- package/dist/Display/Display.SectionHeader.js +11 -2
- package/dist/Display/Display.Story.d.ts +3 -3
- package/dist/Display/Display.Story.js +5 -4
- package/dist/Display/Display.StoryTray.d.ts +4 -4
- package/dist/Display/Display.StoryTray.js +3 -2
- package/dist/Display/Display.StoryViewer.d.ts +3 -3
- package/dist/Display/Display.StoryViewer.js +6 -3
- package/dist/Display/Display.SwipeableRow.d.ts +3 -1
- package/dist/Display/Display.SwipeableRow.js +3 -3
- package/dist/Display/index.d.ts +13 -0
- package/dist/Display/index.js +6 -0
- package/dist/Feedback/Feedback.Banner.js +1 -1
- package/dist/Feedback/Feedback.DomainSplashScreen.d.ts +13 -4
- package/dist/Feedback/Feedback.DomainSplashScreen.js +68 -53
- package/dist/Feedback/Feedback.EmptyState.d.ts +2 -2
- package/dist/Feedback/Feedback.EmptyState.js +3 -2
- package/dist/Feedback/Feedback.Error.d.ts +2 -1
- package/dist/Feedback/Feedback.Error.js +4 -3
- package/dist/Feedback/Feedback.Loading.d.ts +6 -2
- package/dist/Feedback/Feedback.Loading.js +2 -2
- package/dist/Feedback/Feedback.Progress.d.ts +6 -3
- package/dist/Feedback/Feedback.Progress.js +29 -19
- package/dist/Feedback/Feedback.ProgressCircle.d.ts +3 -2
- package/dist/Feedback/Feedback.ProgressCircle.js +10 -7
- package/dist/Feedback/Feedback.Skeleton.d.ts +4 -4
- package/dist/Feedback/Feedback.Skeleton.js +15 -3
- package/dist/Feedback/Feedback.Spinner.d.ts +3 -3
- package/dist/Feedback/Feedback.Spinner.js +40 -9
- package/dist/Feedback/Feedback.Splash.d.ts +2 -2
- package/dist/Feedback/Feedback.Splash.js +5 -3
- package/dist/Feedback/Toast.context.d.ts +64 -3
- package/dist/Feedback/Toast.context.js +112 -31
- package/dist/Feedback/index.d.ts +15 -1
- package/dist/Feedback/index.js +4 -1
- package/dist/Form/Form.Checkbox.d.ts +3 -1
- package/dist/Form/Form.Checkbox.js +2 -2
- package/dist/Form/Form.DatePicker.js +3 -2
- package/dist/Form/Form.Dropdown.d.ts +15 -3
- package/dist/Form/Form.Dropdown.js +21 -6
- package/dist/Form/Form.ErrorSummary.d.ts +23 -0
- package/dist/Form/Form.ErrorSummary.js +53 -0
- package/dist/Form/Form.FilterButton.d.ts +1 -1
- package/dist/Form/Form.Input.d.ts +4 -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 +2 -1
- package/dist/Form/Form.Select.js +26 -26
- package/dist/Form/index.d.ts +9 -0
- package/dist/Form/index.js +6 -0
- package/dist/Navigation/Navigation.AppBar.js +8 -6
- package/dist/Navigation/Navigation.BottomNav.js +10 -5
- package/dist/Navigation/Navigation.Header.d.ts +2 -2
- package/dist/Navigation/Navigation.Menu.js +10 -3
- package/dist/Navigation/Navigation.RowItem.d.ts +1 -1
- package/dist/Navigation/Navigation.RowItem.js +2 -2
- package/dist/Navigation/Navigation.Search.d.ts +41 -0
- package/dist/Navigation/Navigation.Search.js +47 -0
- package/dist/Navigation/Navigation.SearchBar.d.ts +5 -1
- package/dist/Navigation/Navigation.SearchBar.js +25 -7
- package/dist/Navigation/Navigation.SearchSuggestions.d.ts +14 -0
- package/dist/Navigation/Navigation.SearchSuggestions.js +7 -0
- package/dist/Navigation/Navigation.SegmentedControl.d.ts +11 -1
- package/dist/Navigation/Navigation.SegmentedControl.js +29 -15
- package/dist/Navigation/index.d.ts +13 -0
- package/dist/Navigation/index.js +4 -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/branding.d.ts +3 -2
- package/dist/layouts/AuthContainer.d.ts +4 -1
- package/dist/layouts/AuthContainer.js +3 -3
- package/dist/primitives/BaseButton.d.ts +15 -12
- package/dist/primitives/BaseButton.js +21 -9
- 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/image.d.ts +3 -0
- package/dist/utils/image.js +13 -0
- package/dist/utils/keys.d.ts +1 -0
- package/dist/utils/keys.js +7 -0
- package/dist/utils/progress.d.ts +1 -0
- package/dist/utils/progress.js +5 -0
- package/dist/utils/reorder.d.ts +4 -0
- package/dist/utils/reorder.js +29 -0
- package/dist/utils/useReducedMotion.d.ts +1 -0
- package/dist/utils/useReducedMotion.js +43 -0
- package/dist/utils/useSheetTranslation.d.ts +5 -0
- package/dist/utils/useSheetTranslation.js +23 -0
- package/dist/views/AppSplashView.d.ts +3 -2
- package/dist/views/AppSplashView.js +3 -2
- package/dist/views/AuthFormView.d.ts +6 -3
- package/dist/views/AuthFormView.js +2 -2
- package/dist/views/FormViewV2.d.ts +9 -4
- package/dist/views/FormViewV2.js +35 -16
- package/dist/views/ListViewV2.context.d.ts +3 -1
- package/dist/views/ListViewV2.context.js +41 -21
- package/dist/views/ListViewV2.d.ts +18 -7
- package/dist/views/ListViewV2.js +69 -35
- package/dist/views/types/IListView.js +6 -5
- package/dist/widgets/CollectionOptions.d.ts +15 -0
- package/dist/widgets/CollectionOptions.js +129 -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/HomeHeader.d.ts +2 -2
- package/dist/widgets/PromoModal.js +3 -1
- 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/ScreenHeader.d.ts +2 -2
- package/dist/widgets/index.d.ts +4 -0
- package/dist/widgets/index.js +3 -0
- package/package.json +18 -1
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "nativewind/jsx-runtime";
|
|
2
|
-
import
|
|
3
|
-
import
|
|
2
|
+
import IconButton from "../Actions/Actions.IconButton.js";
|
|
3
|
+
import Icon from "../Display/Display.Icon.js";
|
|
4
|
+
import { useTheme } from "../theme/index.js";
|
|
5
|
+
import { cn } from "../theme/tw.js";
|
|
4
6
|
import { Typography } from "../Typography/index.js";
|
|
7
|
+
import { useReducedMotion } from "../utils/useReducedMotion.js";
|
|
5
8
|
import { useState } from "react";
|
|
6
|
-
import { Modal, TouchableOpacity, TouchableWithoutFeedback, View } from "react-native";
|
|
9
|
+
import { Modal, ScrollView, TouchableOpacity, TouchableWithoutFeedback, View, } from "react-native";
|
|
7
10
|
import { tv } from "tailwind-variants";
|
|
8
11
|
const dropdown = tv({
|
|
9
12
|
slots: {
|
|
@@ -52,7 +55,9 @@ const dropdown = tv({
|
|
|
52
55
|
size: "small",
|
|
53
56
|
},
|
|
54
57
|
});
|
|
55
|
-
function DropdownButton({ title, options, variant = "secondary", size = "small", }) {
|
|
58
|
+
function DropdownButton({ title, options, variant = "secondary", size = "small", disabled = false, closeAccessibilityLabel = "Cerrar opciones", buttonProps, contentProps, optionProps, titleTextProps, optionTextProps, renderOption, testID, }) {
|
|
59
|
+
const theme = useTheme();
|
|
60
|
+
const reducedMotion = useReducedMotion();
|
|
56
61
|
const [isOpen, setIsOpen] = useState(false);
|
|
57
62
|
const handleOptionPress = (option) => {
|
|
58
63
|
if (!option.disabled) {
|
|
@@ -77,9 +82,19 @@ function DropdownButton({ title, options, variant = "secondary", size = "small",
|
|
|
77
82
|
xs: "bodySmall",
|
|
78
83
|
small: "bodySmall",
|
|
79
84
|
}[size];
|
|
80
|
-
return (_jsxs(_Fragment, { children: [_jsxs(TouchableOpacity, {
|
|
85
|
+
return (_jsxs(_Fragment, { children: [_jsxs(TouchableOpacity, { ...buttonProps, accessibilityLabel: buttonProps?.accessibilityLabel ?? title, accessibilityRole: "button", accessibilityState: {
|
|
86
|
+
...buttonProps?.accessibilityState,
|
|
87
|
+
disabled,
|
|
88
|
+
expanded: isOpen,
|
|
89
|
+
}, activeOpacity: 0.7, className: cn(styles.button(), buttonProps?.className), disabled: disabled, onPress: () => setIsOpen(true), style: [{ minHeight: 48, minWidth: 48 }, buttonProps?.style], testID: testID, children: [_jsx(Typography.Text, { className: textColorClass, variant: textVariant, children: title }), _jsx(Icon, { color: iconColor, name: "chevron-down", size: 16, type: "feather" })] }), _jsx(Modal, { animationType: reducedMotion ? "none" : "fade", onRequestClose: () => setIsOpen(false), presentationStyle: "overFullScreen", transparent: true, visible: isOpen, children: _jsxs(View, { className: styles.overlay(), children: [_jsx(TouchableWithoutFeedback, { accessible: false, onPress: () => setIsOpen(false), children: _jsx(View, { className: "absolute inset-0" }) }), _jsxs(View, { ...contentProps, accessibilityViewIsModal: true, className: cn(styles.dropdownContainer(), contentProps?.className), onAccessibilityEscape: () => setIsOpen(false), style: [
|
|
90
|
+
{
|
|
81
91
|
backgroundColor: theme.bg.popover,
|
|
82
92
|
...theme.shadow.lg,
|
|
83
|
-
},
|
|
93
|
+
},
|
|
94
|
+
contentProps?.style,
|
|
95
|
+
], children: [_jsxs(View, { className: styles.header(), children: [_jsx(Typography.Text, { ...titleTextProps, accessibilityRole: "header", className: cn("text-text-primary", titleTextProps?.className), semibold: titleTextProps?.semibold ?? true, variant: titleTextProps?.variant ?? "bodyLarge", children: title }), _jsx(IconButton, { accessibilityLabel: closeAccessibilityLabel, icon: "close", onPress: () => setIsOpen(false) })] }), _jsx(ScrollView, { contentContainerClassName: styles.optionsList(), keyboardShouldPersistTaps: "handled", style: { flexShrink: 1 }, children: options.map((option, index) => (_jsxs(View, { children: [_jsx(TouchableOpacity, { ...optionProps, accessibilityLabel: option.title, accessibilityRole: "button", accessibilityState: {
|
|
96
|
+
...optionProps?.accessibilityState,
|
|
97
|
+
disabled: option.disabled,
|
|
98
|
+
}, activeOpacity: 0.7, className: cn(dropdown({ disabled: option.disabled }).option(), optionProps?.className), disabled: option.disabled, onPress: () => handleOptionPress(option), style: [{ minHeight: 48, minWidth: 48 }, optionProps?.style], children: renderOption ? (renderOption(option)) : (_jsx(Typography.Text, { ...optionTextProps, className: cn(option.disabled ? "text-text-disabled" : "text-text-primary", optionTextProps?.className), variant: optionTextProps?.variant ?? "body", children: option.title })) }), index < options.length - 1 && _jsx(View, { className: styles.separator() })] }, option.id))) })] })] }) })] }));
|
|
84
99
|
}
|
|
85
100
|
export default DropdownButton;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Typography } from "../Typography/index.js";
|
|
2
|
+
import { type ComponentProps, type ReactNode } from "react";
|
|
3
|
+
import { type StyleProp, type ViewProps, type ViewStyle } from "react-native";
|
|
4
|
+
export interface ErrorSummaryItem {
|
|
5
|
+
readonly id: string;
|
|
6
|
+
readonly label: string;
|
|
7
|
+
readonly message: string;
|
|
8
|
+
}
|
|
9
|
+
export interface ErrorSummaryProps extends Omit<ViewProps, "children"> {
|
|
10
|
+
errors: readonly ErrorSummaryItem[];
|
|
11
|
+
onSelectError: (id: string) => void;
|
|
12
|
+
title?: string;
|
|
13
|
+
announce?: boolean;
|
|
14
|
+
className?: string;
|
|
15
|
+
itemStyle?: StyleProp<ViewStyle>;
|
|
16
|
+
/** Custom content must preserve the message and remain scalable, multiline and non-interactive. */
|
|
17
|
+
renderErrorContent?: (error: ErrorSummaryItem, index: number) => ReactNode;
|
|
18
|
+
/** Custom content must remain scalable and multiline; the wrapper owns header semantics. */
|
|
19
|
+
renderHeader?: (title: string) => ReactNode;
|
|
20
|
+
itemTextProps?: Omit<ComponentProps<typeof Typography.Text>, "children" | "accessibilityRole" | "allowFontScaling" | "numberOfLines">;
|
|
21
|
+
titleTextProps?: Omit<ComponentProps<typeof Typography.Text>, "children" | "accessibilityRole" | "allowFontScaling" | "numberOfLines">;
|
|
22
|
+
}
|
|
23
|
+
export default function ErrorSummary({ errors, onSelectError, title, announce, className, style, itemStyle, renderErrorContent, renderHeader, itemTextProps, titleTextProps, ...props }: ErrorSummaryProps): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "nativewind/jsx-runtime";
|
|
2
|
+
import Pressable from "../primitives/Pressable.js";
|
|
3
|
+
import { useTheme } from "../theme/index.js";
|
|
4
|
+
import { Typography } from "../Typography/index.js";
|
|
5
|
+
import { uniqueKeys } from "../utils/keys.js";
|
|
6
|
+
import { useEffect, useRef } from "react";
|
|
7
|
+
import { AccessibilityInfo, StyleSheet, View, } from "react-native";
|
|
8
|
+
export default function ErrorSummary({ errors, onSelectError, title = "Revisa los siguientes errores", announce = true, className, style, itemStyle, renderErrorContent, renderHeader, itemTextProps, titleTextProps, ...props }) {
|
|
9
|
+
const theme = useTheme();
|
|
10
|
+
const nonemptyEpisode = useRef(false);
|
|
11
|
+
uniqueKeys(errors, (error) => error.id, "ErrorSummary");
|
|
12
|
+
for (const { label, message } of errors) {
|
|
13
|
+
if (typeof label !== "string" || !label.trim())
|
|
14
|
+
throw new Error("Form.ErrorSummary requires non-empty error labels.");
|
|
15
|
+
if (typeof message !== "string" || !message.trim())
|
|
16
|
+
throw new Error("Form.ErrorSummary requires non-empty error messages.");
|
|
17
|
+
}
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
if (errors.length === 0) {
|
|
20
|
+
nonemptyEpisode.current = false;
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (nonemptyEpisode.current)
|
|
24
|
+
return;
|
|
25
|
+
nonemptyEpisode.current = true;
|
|
26
|
+
if (announce)
|
|
27
|
+
AccessibilityInfo.announceForAccessibility([title, ...errors.map(({ label, message }) => `${label}: ${message}`)].join("\n"));
|
|
28
|
+
}, [announce, errors, title]);
|
|
29
|
+
if (errors.length === 0)
|
|
30
|
+
return null;
|
|
31
|
+
return (_jsxs(View, { className: className, style: [
|
|
32
|
+
{
|
|
33
|
+
backgroundColor: theme.bg.card,
|
|
34
|
+
borderColor: theme.danger,
|
|
35
|
+
borderWidth: 2,
|
|
36
|
+
borderRadius: 8,
|
|
37
|
+
padding: 16,
|
|
38
|
+
gap: 8,
|
|
39
|
+
},
|
|
40
|
+
style,
|
|
41
|
+
], ...props, children: [renderHeader ? (_jsx(View, { accessibilityLabel: title, accessibilityRole: "header", accessible: true, children: renderHeader(title) })) : (_jsx(Typography.Text, { semibold: true, variant: "body", ...titleTextProps, accessibilityRole: "header", allowFontScaling: true, numberOfLines: 0, children: title })), errors.map((error, index) => (_jsx(Pressable, { accessibilityLabel: `${error.label}: ${error.message}`, accessibilityRole: "button", accessible: true, hitSlop: 0, onPress: () => onSelectError(error.id), style: [styles.item, itemStyle, styles.target], children: renderErrorContent ? (renderErrorContent(error, index)) : (_jsxs(_Fragment, { children: [_jsx(Typography.Text, { semibold: true, variant: "bodySmall", ...itemTextProps, allowFontScaling: true, numberOfLines: 0, children: error.label }), _jsx(Typography.Text, { className: "text-text-accent", underline: true, ...itemTextProps, allowFontScaling: true, numberOfLines: 0, children: error.message })] })) }, error.id)))] }));
|
|
42
|
+
}
|
|
43
|
+
const styles = StyleSheet.create({
|
|
44
|
+
item: {
|
|
45
|
+
justifyContent: "center",
|
|
46
|
+
paddingVertical: 8,
|
|
47
|
+
gap: 4,
|
|
48
|
+
},
|
|
49
|
+
target: {
|
|
50
|
+
minHeight: 48,
|
|
51
|
+
minWidth: 48,
|
|
52
|
+
},
|
|
53
|
+
});
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
/** @deprecated Use `Actions.FilterButton` from `@sincpro/mobile-ui/Actions` instead. */
|
|
2
|
-
export type { FilterButtonProps } from "../Actions/Actions.FilterButton.js";
|
|
2
|
+
export type { FilterButtonGroupProps, FilterButtonProps, } from "../Actions/Actions.FilterButton.js";
|
|
3
3
|
export { FilterButton, FilterButtonGroup, } from "../Actions/Actions.FilterButton.js";
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import { type TextInputProps } from "react-native";
|
|
1
|
+
import type { ComponentRef, Ref } from "react";
|
|
2
|
+
import { TextInput, type TextInputProps } from "react-native";
|
|
3
3
|
interface FormInputProps extends TextInputProps {
|
|
4
|
+
ref?: Ref<ComponentRef<typeof TextInput>>;
|
|
4
5
|
label: string;
|
|
5
6
|
error?: string;
|
|
6
7
|
tooltip?: string;
|
|
7
8
|
className?: string;
|
|
8
9
|
}
|
|
9
|
-
declare function FormInput({ label, error, tooltip, style, multiline, numberOfLines, onFocus, onBlur, className, ...props }: FormInputProps):
|
|
10
|
+
declare function FormInput({ label, error, tooltip, style, multiline, numberOfLines, onFocus, onBlur, className, ref, ...props }: FormInputProps): import("react").JSX.Element;
|
|
10
11
|
export default FormInput;
|
package/dist/Form/Form.Input.js
CHANGED
|
@@ -15,7 +15,7 @@ const inputContainer = tv({
|
|
|
15
15
|
},
|
|
16
16
|
defaultVariants: { state: "default" },
|
|
17
17
|
});
|
|
18
|
-
function FormInput({ label, error, tooltip, style, multiline = false, numberOfLines = 1, onFocus, onBlur, className, ...props }) {
|
|
18
|
+
function FormInput({ label, error, tooltip, style, multiline = false, numberOfLines = 1, onFocus, onBlur, className, ref, ...props }) {
|
|
19
19
|
const [isFocused, setIsFocused] = useState(false);
|
|
20
20
|
const hasError = Boolean(error);
|
|
21
21
|
const handleFocus = (e) => {
|
|
@@ -33,6 +33,6 @@ function FormInput({ label, error, tooltip, style, multiline = false, numberOfLi
|
|
|
33
33
|
return "focused";
|
|
34
34
|
return "default";
|
|
35
35
|
};
|
|
36
|
-
return (_jsxs(View, { className: cn("mb-2", className), children: [_jsx(Typography.Text, { className: "mb-1 font-medium text-text-primary", variant: "bodySmall", children: label }), _jsx(View, { className: inputContainer({ state: getState() }), children: _jsx(TextInput, { className: cn("px-3 py-2.5 text-sm min-h-[
|
|
36
|
+
return (_jsxs(View, { className: cn("mb-2", className), children: [_jsx(Typography.Text, { className: "mb-1 font-medium text-text-primary", variant: "bodySmall", children: label }), _jsx(View, { className: inputContainer({ state: getState() }), children: _jsx(TextInput, { accessibilityHint: error || tooltip, accessibilityLabel: label, className: cn("px-3 py-2.5 text-sm min-h-[48px] text-text-primary", multiline && "min-h-[64px] pt-2.5"), multiline: multiline, numberOfLines: numberOfLines, onBlur: handleBlur, onFocus: handleFocus, placeholderTextColor: theme.text.tertiary, ref: ref, selectionColor: theme.primary, style: [{ minHeight: multiline ? 64 : 48 }, style], textAlignVertical: multiline ? "top" : "center", underlineColorAndroid: "transparent", ...props }) }), hasError && (_jsx(Typography.Text, { accessibilityLiveRegion: "polite", className: "mt-0.5 text-danger", variant: "bodySmall", children: error }))] }));
|
|
37
37
|
}
|
|
38
38
|
export default FormInput;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type KeyboardToolbarProps } from "./Form.KeyboardToolbar.js";
|
|
2
|
+
import { type ReactNode, type Ref } from "react";
|
|
3
|
+
import type { KeyboardAwareScrollViewProps, KeyboardAwareScrollViewRef } from "react-native-keyboard-controller";
|
|
4
|
+
export interface KeyboardFormProps extends KeyboardAwareScrollViewProps {
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
ref?: Ref<KeyboardAwareScrollViewRef>;
|
|
7
|
+
toolbar?: boolean;
|
|
8
|
+
toolbarProps?: Omit<KeyboardToolbarProps, "onHeightChange">;
|
|
9
|
+
toolbarLabels?: Pick<KeyboardToolbarProps, "previousLabel" | "nextLabel" | "doneLabel" | "showDoneLabel">;
|
|
10
|
+
}
|
|
11
|
+
export default function KeyboardForm({ children, toolbar, toolbarLabels, toolbarProps, bottomOffset, style, ...props }: KeyboardFormProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "nativewind/jsx-runtime";
|
|
2
|
+
import KeyboardToolbar from "./Form.KeyboardToolbar.js";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { View } from "react-native";
|
|
5
|
+
import { KeyboardAwareScrollView, KeyboardToolbar as NativeToolbar, } from "react-native-keyboard-controller";
|
|
6
|
+
export default function KeyboardForm({ children, toolbar = true, toolbarLabels, toolbarProps, bottomOffset, style, ...props }) {
|
|
7
|
+
const [toolbarHeight, setToolbarHeight] = useState(48);
|
|
8
|
+
return (_jsxs(View, { style: { flex: 1 }, children: [_jsx(NativeToolbar.Group, { style: { flex: 1 }, children: _jsx(KeyboardAwareScrollView, { bottomOffset: bottomOffset ?? (toolbar ? toolbarHeight + 24 : 20), keyboardShouldPersistTaps: "handled", style: [{ flex: 1 }, style], ...props, children: children }) }), toolbar ? (_jsx(KeyboardToolbar, { ...toolbarLabels, ...toolbarProps, onHeightChange: setToolbarHeight })) : null] }));
|
|
9
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import { type StyleProp, type TextStyle, type ViewStyle } from "react-native";
|
|
3
|
+
export type KeyboardToolbarAction = "previous" | "next" | "done";
|
|
4
|
+
export interface KeyboardToolbarProps {
|
|
5
|
+
previousLabel?: string;
|
|
6
|
+
nextLabel?: string;
|
|
7
|
+
doneLabel?: string;
|
|
8
|
+
showDoneLabel?: boolean;
|
|
9
|
+
onHeightChange?: (height: number) => void;
|
|
10
|
+
className?: string;
|
|
11
|
+
style?: StyleProp<ViewStyle>;
|
|
12
|
+
actionStyle?: StyleProp<ViewStyle>;
|
|
13
|
+
labelStyle?: StyleProp<TextStyle>;
|
|
14
|
+
iconSize?: number;
|
|
15
|
+
testID?: string;
|
|
16
|
+
renderActionContent?: (action: KeyboardToolbarAction, state: {
|
|
17
|
+
label: string;
|
|
18
|
+
disabled: boolean;
|
|
19
|
+
}) => ReactNode;
|
|
20
|
+
}
|
|
21
|
+
export default function KeyboardToolbar({ previousLabel, nextLabel, doneLabel, showDoneLabel, onHeightChange, className, style, actionStyle, labelStyle, iconSize, testID, renderActionContent, }: KeyboardToolbarProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "nativewind/jsx-runtime";
|
|
2
|
+
import Icon from "../Display/Display.Icon.js";
|
|
3
|
+
import Pressable from "../primitives/Pressable.js";
|
|
4
|
+
import { useTheme } from "../theme/index.js";
|
|
5
|
+
import { Typography } from "../Typography/index.js";
|
|
6
|
+
import { useEffect, useRef, useState } from "react";
|
|
7
|
+
import { StyleSheet, useWindowDimensions, View, } from "react-native";
|
|
8
|
+
import { FocusedInputEvents, KeyboardController, KeyboardStickyView, useKeyboardState, } from "react-native-keyboard-controller";
|
|
9
|
+
export default function KeyboardToolbar({ previousLabel = "Campo anterior", nextLabel = "Campo siguiente", doneLabel = "Ocultar teclado", showDoneLabel = false, onHeightChange, className, style, actionStyle, labelStyle, iconSize = 20, testID = "keyboard-toolbar", renderActionContent, }) {
|
|
10
|
+
const theme = useTheme();
|
|
11
|
+
const frame = useRef(null);
|
|
12
|
+
const { height: windowHeight } = useWindowDimensions();
|
|
13
|
+
const [bottomGap, setBottomGap] = useState(0);
|
|
14
|
+
const [height, setHeight] = useState(48);
|
|
15
|
+
const [inputs, setInputs] = useState({ current: 0, count: 0 });
|
|
16
|
+
const isVisible = useKeyboardState((state) => state.isVisible);
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
const subscription = FocusedInputEvents.addListener("focusDidSet", setInputs);
|
|
19
|
+
return () => subscription.remove();
|
|
20
|
+
}, []);
|
|
21
|
+
const previousDisabled = inputs.current === 0;
|
|
22
|
+
const nextDisabled = inputs.current >= inputs.count - 1;
|
|
23
|
+
const content = (action, label, disabled) => {
|
|
24
|
+
if (renderActionContent)
|
|
25
|
+
return renderActionContent(action, { label, disabled });
|
|
26
|
+
if (action === "done" && showDoneLabel)
|
|
27
|
+
return (_jsx(Typography.Text, { className: "text-text-accent", style: labelStyle, variant: "bodySmall", children: label }));
|
|
28
|
+
return (_jsx(Icon, { color: disabled ? theme.text.tertiary : theme.text.accent, name: action === "previous"
|
|
29
|
+
? "chevron-up"
|
|
30
|
+
: action === "next"
|
|
31
|
+
? "chevron-down"
|
|
32
|
+
: "checkmark", size: iconSize }));
|
|
33
|
+
};
|
|
34
|
+
return (_jsx(View, { accessibilityElementsHidden: !isVisible, importantForAccessibility: isVisible ? "auto" : "no-hide-descendants", onLayout: () => {
|
|
35
|
+
frame.current?.measureInWindow((_x, y, _width, frameHeight) => setBottomGap(Math.max(0, windowHeight - y - frameHeight)));
|
|
36
|
+
}, pointerEvents: "box-none", ref: frame, style: StyleSheet.absoluteFill, children: _jsx(KeyboardStickyView, { offset: { closed: height, opened: bottomGap - 8 }, style: { position: "absolute", bottom: 0, left: 0, right: 0 }, children: _jsxs(View, { className: className, onLayout: (event) => {
|
|
37
|
+
const nextHeight = event.nativeEvent.layout.height;
|
|
38
|
+
setHeight(nextHeight);
|
|
39
|
+
onHeightChange?.(nextHeight);
|
|
40
|
+
}, style: [
|
|
41
|
+
{
|
|
42
|
+
minHeight: 48,
|
|
43
|
+
flexDirection: "row",
|
|
44
|
+
alignItems: "center",
|
|
45
|
+
justifyContent: "space-between",
|
|
46
|
+
paddingHorizontal: 8,
|
|
47
|
+
backgroundColor: theme.bg.card,
|
|
48
|
+
borderTopWidth: StyleSheet.hairlineWidth,
|
|
49
|
+
borderTopColor: theme.border.light,
|
|
50
|
+
},
|
|
51
|
+
style,
|
|
52
|
+
], testID: testID, children: [_jsxs(View, { style: { flexDirection: "row" }, children: [_jsx(Pressable, { accessibilityLabel: previousLabel, disabled: previousDisabled, hitSlop: 0, onPress: () => KeyboardController.setFocusTo("prev"), style: [styles.arrow, actionStyle], children: content("previous", previousLabel, previousDisabled) }), _jsx(Pressable, { accessibilityLabel: nextLabel, disabled: nextDisabled, hitSlop: 0, onPress: () => KeyboardController.setFocusTo("next"), style: [styles.arrow, actionStyle], children: content("next", nextLabel, nextDisabled) })] }), _jsx(Pressable, { accessibilityLabel: doneLabel, hitSlop: 0, onPress: () => KeyboardController.dismiss(), style: [
|
|
53
|
+
{
|
|
54
|
+
minWidth: 48,
|
|
55
|
+
minHeight: 48,
|
|
56
|
+
flexShrink: 1,
|
|
57
|
+
justifyContent: "center",
|
|
58
|
+
padding: 8,
|
|
59
|
+
},
|
|
60
|
+
actionStyle,
|
|
61
|
+
], children: content("done", doneLabel, false) })] }) }) }));
|
|
62
|
+
}
|
|
63
|
+
const styles = StyleSheet.create({
|
|
64
|
+
arrow: {
|
|
65
|
+
minWidth: 48,
|
|
66
|
+
minHeight: 48,
|
|
67
|
+
alignItems: "center",
|
|
68
|
+
justifyContent: "center",
|
|
69
|
+
},
|
|
70
|
+
});
|
|
@@ -12,6 +12,7 @@ export interface SelectProps<T extends string | number> {
|
|
|
12
12
|
disabled?: boolean;
|
|
13
13
|
className?: string;
|
|
14
14
|
testID?: string;
|
|
15
|
+
showCloseButton?: boolean;
|
|
15
16
|
}
|
|
16
|
-
declare function Select<T extends string | number>({ options, value, onChange, label, placeholder, title, disabled, className, testID, }: SelectProps<T>): import("react").JSX.Element;
|
|
17
|
+
declare function Select<T extends string | number>({ options, value, onChange, label, placeholder, title, disabled, className, testID, showCloseButton, }: SelectProps<T>): import("react").JSX.Element;
|
|
17
18
|
export default Select;
|
package/dist/Form/Form.Select.js
CHANGED
|
@@ -1,41 +1,41 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "nativewind/jsx-runtime";
|
|
2
2
|
import Icon from "../Display/Display.Icon.js";
|
|
3
3
|
import Pressable from "../primitives/Pressable.js";
|
|
4
|
-
import {
|
|
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 {
|
|
8
|
-
import {
|
|
7
|
+
import { useSheetTranslation } from "../utils/useSheetTranslation.js";
|
|
8
|
+
import { vars } from "nativewind";
|
|
9
|
+
import { useState } from "react";
|
|
10
|
+
import { Animated, Modal, ScrollView, View } from "react-native";
|
|
9
11
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
10
|
-
function Select({ options, value, onChange, label, placeholder = "Seleccionar…", title, disabled = false, className, testID, }) {
|
|
12
|
+
function Select({ options, value, onChange, label, placeholder = "Seleccionar…", title, disabled = false, className, testID, showCloseButton = true, }) {
|
|
13
|
+
const theme = useTheme();
|
|
11
14
|
const insets = useSafeAreaInsets();
|
|
12
15
|
const [open, setOpen] = useState(false);
|
|
13
16
|
const selected = options.find((o) => o.value === value) ?? null;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
paddingBottom: insets.bottom + 8,
|
|
32
|
-
maxHeight: "70%",
|
|
33
|
-
}, children: [_jsx(View, { className: "items-center pt-3 pb-1", children: _jsx(View, { className: "w-10 h-1 rounded-full bg-border-strong" }) }), title ? (_jsx(Typography.Text, { className: "text-text-primary px-4 py-2", semibold: true, variant: "body", children: title })) : null, _jsx(ScrollView, { children: options.map((option) => {
|
|
17
|
+
const { translateY } = useSheetTranslation(open);
|
|
18
|
+
return (_jsxs(View, { className: cn("gap-1", className), testID: testID, children: [label ? (_jsx(Typography.Text, { className: "font-medium text-text-primary", variant: "bodySmall", children: label })) : null, _jsxs(Pressable, { accessibilityHint: "Abre la lista de opciones", accessibilityLabel: label
|
|
19
|
+
? `${label}: ${selected?.label ?? placeholder}`
|
|
20
|
+
: (selected?.label ?? placeholder), accessibilityRole: "button", accessibilityState: { disabled, expanded: open }, className: "flex-row items-center justify-between rounded-lg border border-input bg-bg-card px-3 min-h-[48px]", disabled: disabled, onPress: () => setOpen(true), style: { minHeight: 48, minWidth: 48 }, children: [_jsx(Typography.Text, { className: cn("flex-1 text-sm", selected ? "text-text-primary" : "text-text-tertiary"), style: { paddingVertical: 12 }, children: selected ? selected.label : placeholder }), _jsx(Icon, { color: theme.icon.secondary, name: "chevron-down", size: 18 })] }), _jsx(Modal, { animationType: "none", onRequestClose: () => setOpen(false), presentationStyle: "overFullScreen", transparent: true, visible: open, children: _jsxs(View, { className: "flex-1 justify-end", children: [_jsx(View, { accessibilityElementsHidden: true, className: "absolute inset-0 bg-black/50", importantForAccessibility: "no-hide-descendants", onResponderRelease: () => setOpen(false), onStartShouldSetResponder: () => true }), _jsxs(Animated.View, { accessibilityViewIsModal: true, className: "rounded-t-3xl", importantForAccessibility: "yes", onAccessibilityEscape: () => setOpen(false), style: [
|
|
21
|
+
vars(themeToVars(theme)),
|
|
22
|
+
{
|
|
23
|
+
transform: [{ translateY }],
|
|
24
|
+
maxHeight: "70%",
|
|
25
|
+
backgroundColor: theme.bg.card,
|
|
26
|
+
paddingBottom: insets.bottom + 8,
|
|
27
|
+
},
|
|
28
|
+
], children: [_jsx(View, { className: "items-center pt-3 pb-1", children: _jsx(View, { className: "w-10 h-1 rounded-full bg-border-strong" }) }), title || showCloseButton ? (_jsxs(View, { className: "flex-row items-center justify-between px-4", children: [_jsx(Typography.Text, { accessibilityRole: "header", className: "flex-1 text-text-primary py-2", semibold: true, variant: "body", children: title ?? label ?? "Seleccionar" }), showCloseButton ? (_jsx(Pressable, { accessibilityLabel: "Cerrar opciones", onPress: () => setOpen(false), style: {
|
|
29
|
+
minWidth: 48,
|
|
30
|
+
minHeight: 48,
|
|
31
|
+
justifyContent: "center",
|
|
32
|
+
alignItems: "center",
|
|
33
|
+
}, children: _jsx(Typography.Text, { className: "text-text-accent", variant: "bodySmall", children: "Cerrar" }) })) : null] })) : null, _jsx(ScrollView, { children: options.map((option) => {
|
|
34
34
|
const isSelected = option.value === value;
|
|
35
35
|
return (_jsxs(Pressable, { accessibilityRole: "menuitem", accessibilityState: { selected: isSelected }, className: "flex-row items-center justify-between px-4 py-3 border-b border-border-light", onPress: () => {
|
|
36
36
|
onChange(option.value);
|
|
37
37
|
setOpen(false);
|
|
38
|
-
}, children: [_jsx(Typography.Text, { className: cn("flex-1 text-sm", isSelected ? "text-text-accent font-semibold" : "text-text-primary"), children: option.label }), isSelected ? (_jsx(Icon, { color: theme.text.accent, name: "checkmark", size: 18 })) : null] }, String(option.value)));
|
|
39
|
-
}) })] }) }) })
|
|
38
|
+
}, style: { minHeight: 48, minWidth: 48 }, children: [_jsx(Typography.Text, { className: cn("flex-1 text-sm", isSelected ? "text-text-accent font-semibold" : "text-text-primary"), children: option.label }), isSelected ? (_jsx(Icon, { color: theme.text.accent, name: "checkmark", size: 18 })) : null] }, String(option.value)));
|
|
39
|
+
}) })] })] }) })] }));
|
|
40
40
|
}
|
|
41
41
|
export default Select;
|
package/dist/Form/index.d.ts
CHANGED
|
@@ -6,11 +6,14 @@ import ControlledMonetaryInput from "./Form.ControlledMonetaryInput.js";
|
|
|
6
6
|
import ControlledRadioButton from "./Form.ControlledRadioButton.js";
|
|
7
7
|
import DatePicker from "./Form.DatePicker.js";
|
|
8
8
|
import Dropdown from "./Form.Dropdown.js";
|
|
9
|
+
import ErrorSummary from "./Form.ErrorSummary.js";
|
|
9
10
|
import FAB from "./Form.FAB.js";
|
|
10
11
|
import { FilterButtonGroup } from "./Form.FilterButton.js";
|
|
11
12
|
import FractionalQuantityInput from "./Form.FractionalQuantityInput.js";
|
|
12
13
|
import IconButton from "./Form.IconButton.js";
|
|
13
14
|
import Input from "./Form.Input.js";
|
|
15
|
+
import KeyboardForm from "./Form.KeyboardForm.js";
|
|
16
|
+
import KeyboardToolbar from "./Form.KeyboardToolbar.js";
|
|
14
17
|
import MonetaryInput from "./Form.MonetaryInput.js";
|
|
15
18
|
import PinInput from "./Form.PinInput.js";
|
|
16
19
|
import QuantitySelector from "./Form.QuantitySelector.js";
|
|
@@ -23,6 +26,9 @@ export declare const Form: {
|
|
|
23
26
|
TextInput: import("react").FC<import("./Form.TextInput.js").TextInputFProps>;
|
|
24
27
|
Input: typeof Input;
|
|
25
28
|
InputField: import("react").FC<import("./Form.InputField.js").InputFieldProps>;
|
|
29
|
+
KeyboardForm: typeof KeyboardForm;
|
|
30
|
+
KeyboardToolbar: typeof KeyboardToolbar;
|
|
31
|
+
ErrorSummary: typeof ErrorSummary;
|
|
26
32
|
Button: import("react").FC<import("./Form.Button.js").ButtonProps> & {
|
|
27
33
|
Icon: import("react").FC<import("./Form.Button.js").ButtonIconProps>;
|
|
28
34
|
};
|
|
@@ -50,4 +56,7 @@ export declare const Form: {
|
|
|
50
56
|
FilterButtonGroup: typeof FilterButtonGroup;
|
|
51
57
|
};
|
|
52
58
|
export { type DropdownOption };
|
|
59
|
+
export type { ErrorSummaryItem, ErrorSummaryProps, } from "./Form.ErrorSummary.js";
|
|
60
|
+
export type { KeyboardFormProps } from "./Form.KeyboardForm.js";
|
|
61
|
+
export type { KeyboardToolbarAction, KeyboardToolbarProps, } from "./Form.KeyboardToolbar.js";
|
|
53
62
|
export default Form;
|
package/dist/Form/index.js
CHANGED
|
@@ -7,12 +7,15 @@ import ControlledMonetaryInput from "./Form.ControlledMonetaryInput.js";
|
|
|
7
7
|
import ControlledRadioButton from "./Form.ControlledRadioButton.js";
|
|
8
8
|
import DatePicker from "./Form.DatePicker.js";
|
|
9
9
|
import Dropdown from "./Form.Dropdown.js";
|
|
10
|
+
import ErrorSummary from "./Form.ErrorSummary.js";
|
|
10
11
|
import FAB from "./Form.FAB.js";
|
|
11
12
|
import { FilterButton, FilterButtonGroup } from "./Form.FilterButton.js";
|
|
12
13
|
import FractionalQuantityInput from "./Form.FractionalQuantityInput.js";
|
|
13
14
|
import IconButton from "./Form.IconButton.js";
|
|
14
15
|
import Input from "./Form.Input.js";
|
|
15
16
|
import InputField from "./Form.InputField.js";
|
|
17
|
+
import KeyboardForm from "./Form.KeyboardForm.js";
|
|
18
|
+
import KeyboardToolbar from "./Form.KeyboardToolbar.js";
|
|
16
19
|
import MonetaryInput from "./Form.MonetaryInput.js";
|
|
17
20
|
import PasswordInput from "./Form.PasswordInput.js";
|
|
18
21
|
import PinInput from "./Form.PinInput.js";
|
|
@@ -27,6 +30,9 @@ export const Form = {
|
|
|
27
30
|
TextInput,
|
|
28
31
|
Input,
|
|
29
32
|
InputField,
|
|
33
|
+
KeyboardForm,
|
|
34
|
+
KeyboardToolbar,
|
|
35
|
+
ErrorSummary,
|
|
30
36
|
Button: ButtonWithIcon,
|
|
31
37
|
IconButton,
|
|
32
38
|
PasswordInput,
|
|
@@ -35,16 +35,18 @@ function AppBarAction({ icon, label, onPress, badge, accessibilityLabel, tone =
|
|
|
35
35
|
// primary/secondary header actions like "Editar", "Nuevo", "Guardar".
|
|
36
36
|
if (label) {
|
|
37
37
|
return (_jsxs(Pressable, { accessibilityLabel: accessibilityLabel ?? label, accessibilityRole: "button", className: "flex-row items-center justify-center gap-1.5", hitSlop: 6, onPress: onPress, style: {
|
|
38
|
-
|
|
38
|
+
minHeight: 48,
|
|
39
|
+
minWidth: 48,
|
|
39
40
|
paddingHorizontal: 14,
|
|
41
|
+
paddingVertical: 8,
|
|
40
42
|
borderRadius: 999,
|
|
41
43
|
backgroundColor: bg,
|
|
42
44
|
...(showBorder ? { borderWidth: 1, borderColor: theme.border.light } : null),
|
|
43
|
-
}, testID: testID, children: [icon ? _jsx(Icon, { color: iconColor, name: icon, size: 16 }) : null, _jsx(Typography.Text, { semibold: true, style: { color: iconColor }, variant: "bodySmall", children: label })] }));
|
|
45
|
+
}, testID: testID, children: [icon ? _jsx(Icon, { color: iconColor, name: icon, size: 16 }) : null, _jsx(Typography.Text, { semibold: true, style: { color: iconColor, flexShrink: 1 }, variant: "bodySmall", children: label })] }));
|
|
44
46
|
}
|
|
45
47
|
return (_jsxs(Pressable, { accessibilityLabel: accessibilityLabel, accessibilityRole: "button", className: "items-center justify-center", hitSlop: 6, onPress: onPress, style: {
|
|
46
|
-
width:
|
|
47
|
-
height:
|
|
48
|
+
width: 48,
|
|
49
|
+
height: 48,
|
|
48
50
|
borderRadius: 12,
|
|
49
51
|
backgroundColor: bg,
|
|
50
52
|
...(showBorder ? { borderWidth: 1, borderColor: theme.border.light } : null),
|
|
@@ -106,13 +108,13 @@ function AppBar({ title, subtitle, onBack, leading, actions, subheader, variant
|
|
|
106
108
|
? { borderBottomLeftRadius: 24, borderBottomRightRadius: 24, overflow: "hidden" }
|
|
107
109
|
: null),
|
|
108
110
|
...(showShadow ? theme.shadow.md : null),
|
|
109
|
-
}, testID: testID, children: [hasBackground ? (_jsx(GradientSurface, { colors: background.colors, end: background.end, padding: "none", preset: background.surface ?? "warm", radius: "none", start: background.start, style: StyleSheet.absoluteFill })) : gradient ? (_jsx(GradientSurface, { colors: theme.gradient.accent, padding: "none", radius: "none", style: StyleSheet.absoluteFill })) : null, background?.pattern ? (_jsx(Pattern, { color: background.patternColor ?? "#FFFFFF", kind: background.pattern, opacity: background.patternOpacity ?? 0.18 })) : null, showTopBar ? (_jsxs(View, { className: "flex-row items-center px-3 gap-2 min-h-[56px]", children: [_jsxs(View, { className: "flex-row items-center gap-2", style: { minWidth: 38 }, children: [onBack ? (_jsx(AppBarAction, { accessibilityLabel: "Volver", bare: backVariant === "plain", icon: "chevron-back", onDark: dark || gradient || hasBackground, onPress: onBack })) : null, leading] }), _jsxs(View, { className: cn("flex-1", centered && "items-center"), children: [title ? (_jsx(Typography.Text, { className: colored ? "" : "text-text-primary",
|
|
111
|
+
}, testID: testID, children: [hasBackground ? (_jsx(GradientSurface, { colors: background.colors, end: background.end, padding: "none", preset: background.surface ?? "warm", radius: "none", start: background.start, style: StyleSheet.absoluteFill })) : gradient ? (_jsx(GradientSurface, { colors: theme.gradient.accent, padding: "none", radius: "none", style: StyleSheet.absoluteFill })) : null, background?.pattern ? (_jsx(Pattern, { color: background.patternColor ?? "#FFFFFF", kind: background.pattern, opacity: background.patternOpacity ?? 0.18 })) : null, showTopBar ? (_jsxs(View, { className: "flex-row items-center px-3 gap-2 min-h-[56px]", children: [_jsxs(View, { className: "flex-row items-center gap-2", style: { minWidth: 38 }, children: [onBack ? (_jsx(AppBarAction, { accessibilityLabel: "Volver", bare: backVariant === "plain", icon: "chevron-back", onDark: dark || gradient || hasBackground, onPress: onBack })) : null, leading] }), _jsxs(View, { className: cn("flex-1", centered && "items-center"), children: [title ? (_jsx(Typography.Text, { accessibilityRole: "header", className: colored ? "" : "text-text-primary", semibold: true, style: titleStyle, variant: "subtitle", children: title })) : null, subtitle ? (_jsx(Typography.Text, { className: colored ? "" : "text-text-secondary", style: subtitleStyle, variant: "caption", children: subtitle })) : null] }), _jsx(View, { className: "flex-row items-center justify-end gap-2", style: { minWidth: 38 }, children: actions })] })) : null, large && title ? (_jsxs(View, { className: cn("flex-row px-4 pb-3", largeHasNav ? "items-center gap-2" : ""), style: { paddingTop: topSpacing != null ? topSpacing : 8 }, children: [onBack ? (_jsx(AppBarAction, { accessibilityLabel: "Volver", bare: backVariant === "plain", icon: "chevron-back", onDark: dark || gradient || hasBackground, onPress: onBack })) : null, leading, accentBar && !largeHasNav ? (_jsx(View, { style: {
|
|
110
112
|
width: 4,
|
|
111
113
|
marginRight: 10,
|
|
112
114
|
marginVertical: 2,
|
|
113
115
|
borderRadius: 999,
|
|
114
116
|
backgroundColor: theme.accent,
|
|
115
|
-
} })) : null, _jsxs(View, { className: "flex-1", children: [_jsx(Typography.Text, { className: colored ? "" : "text-text-primary", style: titleStyle, variant: "h2", children: title }), subtitle ? (_jsx(Typography.Text, { className: colored ? "" : "text-text-secondary", style: subtitleStyle, variant: "bodySmall", children: subtitle })) : null] }), actions ? (_jsx(View, { className: cn("flex-row items-center justify-end gap-2 pl-2", largeHasNav ? "" : "self-center"), children: actions })) : null] })) : null, subheader ? _jsx(View, { className: "px-3 pb-3 pt-0.5", children: subheader }) : null, showWave ? (_jsx(View, { style: {
|
|
117
|
+
} })) : null, _jsxs(View, { className: "flex-1", children: [_jsx(Typography.Text, { accessibilityRole: "header", className: colored ? "" : "text-text-primary", style: titleStyle, variant: "h2", children: title }), subtitle ? (_jsx(Typography.Text, { className: colored ? "" : "text-text-secondary", style: subtitleStyle, variant: "bodySmall", children: subtitle })) : null] }), actions ? (_jsx(View, { className: cn("flex-row items-center justify-end gap-2 pl-2", largeHasNav ? "" : "self-center"), children: actions })) : null] })) : null, subheader ? _jsx(View, { className: "px-3 pb-3 pt-0.5", children: subheader }) : null, showWave ? (_jsx(View, { style: {
|
|
116
118
|
height: 24,
|
|
117
119
|
backgroundColor: theme.bg.page,
|
|
118
120
|
borderTopLeftRadius: 24,
|
|
@@ -52,9 +52,14 @@ function BottomNav({ items, value, onChange, showLabels = true, centerAction, sa
|
|
|
52
52
|
// Variant A — extended pill with text (active = horizontal pill, inactive = icon only).
|
|
53
53
|
if (indicator === "pill-text") {
|
|
54
54
|
if (active) {
|
|
55
|
-
return (_jsxs(Pressable, { accessibilityLabel: item.label ?? item.key, accessibilityRole: "tab", accessibilityState: { selected: true }, className: "flex-row items-center px-4 py-2", onPress: () => onChange(item.key), style: {
|
|
55
|
+
return (_jsxs(Pressable, { accessibilityLabel: item.label ?? item.key, accessibilityRole: "tab", accessibilityState: { selected: true }, className: "flex-row items-center px-4 py-2", onPress: () => onChange(item.key), style: {
|
|
56
|
+
borderRadius: 999,
|
|
57
|
+
backgroundColor: activeTint,
|
|
58
|
+
minHeight: 48,
|
|
59
|
+
minWidth: 48,
|
|
60
|
+
}, children: [renderIconNode(item, true, onActive), item.label ? (_jsx(Typography.Text, { semibold: true, style: { color: onActive, fontSize: 13, marginLeft: 8, flexShrink: 1 }, children: item.label })) : null] }, item.key));
|
|
56
61
|
}
|
|
57
|
-
return (_jsx(Pressable, { accessibilityLabel: item.label ?? item.key, accessibilityRole: "tab", className: "items-center justify-center px-3 py-2", onPress: () => onChange(item.key), children: renderIconNode(item, false, inactiveColor) }, item.key));
|
|
62
|
+
return (_jsx(Pressable, { accessibilityLabel: item.label ?? item.key, accessibilityRole: "tab", className: "items-center justify-center px-3 py-2", onPress: () => onChange(item.key), style: { minHeight: 48, minWidth: 48 }, children: renderIconNode(item, false, inactiveColor) }, item.key));
|
|
58
63
|
}
|
|
59
64
|
// Variants none / pill / bar / glow — vertical icon + optional indicator + optional label.
|
|
60
65
|
const inPill = indicator === "pill" && active;
|
|
@@ -70,7 +75,7 @@ function BottomNav({ items, value, onChange, showLabels = true, centerAction, sa
|
|
|
70
75
|
labelColor = theme.text.inverse;
|
|
71
76
|
else if (active)
|
|
72
77
|
labelColor = brandText;
|
|
73
|
-
return (_jsxs(Pressable, { accessibilityLabel: item.label ?? item.key, accessibilityRole: "tab", accessibilityState: { selected: active }, className: "flex-1 items-center justify-center py-1.5", onPress: () => onChange(item.key), children: [indicator === "bar" && active ? (_jsx(View, { style: {
|
|
78
|
+
return (_jsxs(Pressable, { accessibilityLabel: item.label ?? item.key, accessibilityRole: "tab", accessibilityState: { selected: active }, className: "flex-1 items-center justify-center py-1.5", onPress: () => onChange(item.key), style: { minHeight: 48, minWidth: 48 }, children: [indicator === "bar" && active ? (_jsx(View, { style: {
|
|
74
79
|
position: "absolute",
|
|
75
80
|
top: 0,
|
|
76
81
|
width: 26,
|
|
@@ -87,7 +92,7 @@ function BottomNav({ items, value, onChange, showLabels = true, centerAction, sa
|
|
|
87
92
|
shadowOpacity: 0.9,
|
|
88
93
|
shadowRadius: 5,
|
|
89
94
|
elevation: 4,
|
|
90
|
-
} })) : null, showLabels && item.label && indicator !== "glow" ? (_jsx(Typography.Text, {
|
|
95
|
+
} })) : null, showLabels && item.label && indicator !== "glow" ? (_jsx(Typography.Text, { semibold: active, style: { color: labelColor, fontSize: 11, marginTop: 3, textAlign: "center" }, children: item.label })) : null] }, item.key));
|
|
91
96
|
};
|
|
92
97
|
const renderCenterFab = () => (_jsx(Pressable, { accessibilityLabel: centerAction?.accessibilityLabel ?? "Acción", accessibilityRole: "button", className: "items-center justify-center", onPress: centerAction?.onPress, style: {
|
|
93
98
|
width: 52,
|
|
@@ -112,7 +117,7 @@ function BottomNav({ items, value, onChange, showLabels = true, centerAction, sa
|
|
|
112
117
|
content = items.map(renderItem);
|
|
113
118
|
}
|
|
114
119
|
const justify = indicator === "pill-text" ? "justify-around" : "";
|
|
115
|
-
return (_jsx(View, { className: cn("flex-row items-center", justify, className), style: containerStyle, testID: testID, children: content }));
|
|
120
|
+
return (_jsx(View, { accessibilityRole: "tablist", className: cn("flex-row items-center", justify, className), style: containerStyle, testID: testID, children: content }));
|
|
116
121
|
}
|
|
117
122
|
export default BottomNav;
|
|
118
123
|
export { BottomNav };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import type { ImageSource } from "../utils/image.js";
|
|
1
2
|
import React, { ReactNode } from "react";
|
|
2
|
-
import { type ImageSourcePropType } from "react-native";
|
|
3
3
|
export interface HeaderProps {
|
|
4
4
|
title?: string;
|
|
5
5
|
logo?: ReactNode;
|
|
6
|
-
logoSource?:
|
|
6
|
+
logoSource?: ImageSource;
|
|
7
7
|
backButton?: boolean;
|
|
8
8
|
handleBackButton?: () => void;
|
|
9
9
|
className?: string;
|
|
@@ -4,10 +4,12 @@ import Pressable from "../primitives/Pressable.js";
|
|
|
4
4
|
import { useTheme } from "../theme/index.js";
|
|
5
5
|
import { cn } from "../theme/tw.js";
|
|
6
6
|
import { Typography } from "../Typography/index.js";
|
|
7
|
+
import { useReducedMotion } from "../utils/useReducedMotion.js";
|
|
7
8
|
import { cloneElement, useRef, useState } from "react";
|
|
8
9
|
import { Dimensions, Modal, Pressable as RNPressable, View, } from "react-native";
|
|
9
10
|
function Menu({ items, trigger, menuWidth = 220, testID }) {
|
|
10
11
|
const theme = useTheme();
|
|
12
|
+
const reducedMotion = useReducedMotion();
|
|
11
13
|
const triggerRef = useRef(null);
|
|
12
14
|
const [anchor, setAnchor] = useState(null);
|
|
13
15
|
const open = () => {
|
|
@@ -21,7 +23,12 @@ function Menu({ items, trigger, menuWidth = 220, testID }) {
|
|
|
21
23
|
const top = anchor ? anchor.y + anchor.height + 4 : 0;
|
|
22
24
|
return (_jsxs(View, { collapsable: false, ref: triggerRef, testID: testID, children: [trigger ? (
|
|
23
25
|
// eslint-disable-next-line react-hooks/refs
|
|
24
|
-
cloneElement(trigger, { onPress: open })) : (_jsx(Pressable, { accessibilityLabel: "M\u00E1s opciones", accessibilityRole: "button", onPress: open,
|
|
26
|
+
cloneElement(trigger, { onPress: open })) : (_jsx(Pressable, { accessibilityLabel: "M\u00E1s opciones", accessibilityRole: "button", onPress: open, style: {
|
|
27
|
+
minWidth: 48,
|
|
28
|
+
minHeight: 48,
|
|
29
|
+
alignItems: "center",
|
|
30
|
+
justifyContent: "center",
|
|
31
|
+
}, children: _jsx(Icon, { color: theme.icon.secondary, name: "ellipsis-vertical", size: 22 }) })), _jsx(Modal, { animationType: reducedMotion ? "none" : "fade", onRequestClose: close, presentationStyle: "overFullScreen", transparent: true, visible: anchor !== null, children: _jsx(RNPressable, { onPress: close, style: { flex: 1 }, children: _jsx(View, { accessibilityViewIsModal: true, className: "rounded-lg py-1", onAccessibilityEscape: close, style: {
|
|
25
32
|
position: "absolute",
|
|
26
33
|
top,
|
|
27
34
|
right,
|
|
@@ -35,10 +42,10 @@ function Menu({ items, trigger, menuWidth = 220, testID }) {
|
|
|
35
42
|
return _jsx(View, { className: "my-1 h-px bg-border-default" }, `sep-${index}`);
|
|
36
43
|
}
|
|
37
44
|
const item = entry;
|
|
38
|
-
return (_jsxs(Pressable, { accessibilityRole: "menuitem", accessibilityState: { disabled: item.disabled }, className: "flex-row items-center gap-3 px-4 py-3", disabled: item.disabled, onPress: () => {
|
|
45
|
+
return (_jsxs(Pressable, { accessibilityLabel: item.label, accessibilityRole: "menuitem", accessibilityState: { disabled: item.disabled }, className: "flex-row items-center gap-3 px-4 py-3", disabled: item.disabled, onPress: () => {
|
|
39
46
|
close();
|
|
40
47
|
item.onPress();
|
|
41
|
-
}, children: [item.icon ? (_jsx(Icon, { color: item.destructive ? theme.danger : theme.icon.secondary, name: item.icon, size: 18 })) : null, _jsx(Typography.Text, { className: cn("flex-1 text-sm", item.destructive ? "text-danger" : "text-text-primary", item.disabled && "text-text-disabled"), children: item.label })] }, item.label));
|
|
48
|
+
}, style: { minHeight: 48 }, children: [item.icon ? (_jsx(Icon, { color: item.destructive ? theme.danger : theme.icon.secondary, name: item.icon, size: 18 })) : null, _jsx(Typography.Text, { className: cn("flex-1 text-sm", item.destructive ? "text-danger" : "text-text-primary", item.disabled && "text-text-disabled"), children: item.label })] }, item.label));
|
|
42
49
|
}) }) }) })] }));
|
|
43
50
|
}
|
|
44
51
|
export default Menu;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { IRowItemProps } from "../views/types/IListView.js";
|
|
2
|
-
declare function RowItem({ item, className }: IRowItemProps<any> & {
|
|
2
|
+
declare function RowItem({ item, className, onPress, readonly, }: IRowItemProps<any> & {
|
|
3
3
|
className?: string;
|
|
4
4
|
}): import("react").JSX.Element;
|
|
5
5
|
export default RowItem;
|