@sincpro/mobile-ui 1.1.0 → 2.0.1
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 +156 -29
- package/assets/brand-mesh.png +0 -0
- package/assets/fonts/FFL.txt +57 -0
- package/assets/fonts/Satoshi-Black.otf +0 -0
- package/assets/fonts/Satoshi-Bold.otf +0 -0
- package/assets/fonts/Satoshi-Medium.otf +0 -0
- package/assets/fonts/Satoshi-Regular.otf +0 -0
- package/dist/Actions/Actions.Button.d.ts +10 -12
- package/dist/Actions/Actions.Button.js +9 -11
- package/dist/Actions/Actions.Dock.d.ts +20 -0
- package/dist/Actions/Actions.Dock.js +89 -0
- package/dist/Actions/Actions.FAB.d.ts +1 -2
- package/dist/Actions/Actions.FAB.js +5 -7
- package/dist/Actions/Actions.IconButton.d.ts +1 -2
- package/dist/Actions/Actions.IconButton.js +2 -2
- package/dist/Actions/index.d.ts +4 -0
- package/dist/Actions/index.js +3 -0
- package/dist/Dialog/BottomSheet/BottomSheet.Root.js +22 -34
- package/dist/Dialog/Dialog.ActionSheet.js +2 -16
- package/dist/Dialog/Dialog.Confirmation.js +7 -5
- package/dist/Dialog/Dialog.EditValue.js +3 -1
- package/dist/Dialog/Dialog.Sheet.d.ts +3 -3
- package/dist/Dialog/Dialog.Sheet.js +18 -25
- 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.ExpoImage.d.ts +8 -0
- package/dist/Display/Display.ExpoImage.js +15 -0
- package/dist/Display/Display.Image.d.ts +10 -6
- package/dist/Display/Display.Image.js +39 -6
- package/dist/Display/Display.ListItem.d.ts +1 -2
- package/dist/Display/Display.ListItem.js +3 -3
- 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.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/index.d.ts +9 -0
- package/dist/Display/index.js +4 -0
- package/dist/Feedback/Feedback.DomainSplashScreen.d.ts +13 -4
- package/dist/Feedback/Feedback.DomainSplashScreen.js +68 -53
- package/dist/Feedback/Feedback.EmptyState.d.ts +3 -4
- package/dist/Feedback/Feedback.EmptyState.js +4 -3
- package/dist/Feedback/Feedback.Error.d.ts +1 -2
- package/dist/Feedback/Feedback.Error.js +2 -2
- 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 +1 -2
- package/dist/Form/Form.Checkbox.js +2 -2
- package/dist/Form/Form.DatePicker.d.ts +1 -2
- package/dist/Form/Form.DatePicker.js +2 -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.Input.d.ts +1 -2
- package/dist/Form/Form.Input.js +2 -2
- package/dist/Form/Form.Select.d.ts +1 -2
- package/dist/Form/Form.Select.js +6 -19
- package/dist/Form/index.d.ts +3 -0
- package/dist/Form/index.js +2 -0
- package/dist/Navigation/Navigation.AppBar.d.ts +2 -6
- package/dist/Navigation/Navigation.AppBar.js +9 -7
- package/dist/Navigation/Navigation.BottomNav.d.ts +1 -3
- package/dist/Navigation/Navigation.BottomNav.js +7 -6
- package/dist/Navigation/Navigation.Header.d.ts +2 -2
- package/dist/Navigation/Navigation.Menu.js +10 -3
- package/dist/Navigation/Navigation.Search.d.ts +41 -0
- package/dist/Navigation/Navigation.Search.js +47 -0
- package/dist/Navigation/Navigation.SearchBar.d.ts +1 -2
- package/dist/Navigation/Navigation.SearchBar.js +16 -19
- package/dist/Navigation/Navigation.SegmentedControl.d.ts +11 -3
- package/dist/Navigation/Navigation.SegmentedControl.js +29 -16
- package/dist/Navigation/index.d.ts +11 -2
- package/dist/Navigation/index.js +2 -0
- package/dist/branding.d.ts +3 -2
- package/dist/primitives/BaseButton.d.ts +10 -12
- package/dist/primitives/BaseButton.js +9 -11
- package/dist/theme/typography.d.ts +1 -1
- 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.js +2 -5
- 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 +2 -2
- package/dist/views/FormViewV2.d.ts +2 -2
- package/dist/views/ListViewV2.context.d.ts +3 -1
- package/dist/views/ListViewV2.context.js +41 -21
- package/dist/views/ListViewV2.d.ts +15 -7
- package/dist/views/ListViewV2.js +44 -23
- package/dist/views/types/IListView.js +6 -5
- package/dist/widgets/CollectionOptions.js +9 -7
- package/dist/widgets/FilterBar.d.ts +1 -1
- package/dist/widgets/FilterBar.js +1 -1
- package/dist/widgets/FilterBuilder.js +7 -7
- package/dist/widgets/HomeHeader.d.ts +2 -2
- package/dist/widgets/PromoModal.js +3 -1
- package/dist/widgets/ScreenHeader.d.ts +2 -2
- package/package.json +5 -1
|
@@ -1,22 +1,83 @@
|
|
|
1
|
+
import { type PressableProps } from "../primitives/Pressable.js";
|
|
1
2
|
import { type ReactNode } from "react";
|
|
3
|
+
import { type StyleProp, type ViewProps, type ViewStyle } from "react-native";
|
|
2
4
|
export type ToastVariant = "info" | "success" | "warning" | "danger" | "loading";
|
|
3
5
|
export interface ToastAction {
|
|
4
6
|
label: string;
|
|
5
7
|
onPress: () => void;
|
|
6
8
|
}
|
|
9
|
+
export interface ToastRenderProps {
|
|
10
|
+
id: string;
|
|
11
|
+
dismiss: () => void;
|
|
12
|
+
update: (options: Partial<ToastOptions>) => void;
|
|
13
|
+
}
|
|
7
14
|
export interface ToastOptions {
|
|
8
15
|
message: string;
|
|
9
16
|
title?: string;
|
|
10
17
|
variant?: ToastVariant;
|
|
11
18
|
duration?: number;
|
|
12
19
|
action?: ToastAction;
|
|
20
|
+
className?: string;
|
|
21
|
+
render?: (controls: ToastRenderProps) => ReactNode;
|
|
13
22
|
/** Stable id: calling show()/info()/… again with the same id updates the existing toast
|
|
14
23
|
* (and resets its timer) instead of stacking a duplicate. */
|
|
15
24
|
id?: string;
|
|
16
|
-
/** Render a close (✕) button. Defaults to true
|
|
17
|
-
* false for auto-dismissing ones. */
|
|
25
|
+
/** Render a close (✕) button. Defaults to true, including auto-dismissing toasts. */
|
|
18
26
|
dismissible?: boolean;
|
|
19
27
|
}
|
|
28
|
+
export type ToastCustomOptions = Omit<ToastOptions, "message" | "render"> & {
|
|
29
|
+
message?: string;
|
|
30
|
+
render: (controls: ToastRenderProps) => ReactNode;
|
|
31
|
+
};
|
|
32
|
+
export interface ToastRootProps extends Omit<ViewProps, "children"> {
|
|
33
|
+
children: ReactNode;
|
|
34
|
+
accessibilityHint?: string;
|
|
35
|
+
className?: string;
|
|
36
|
+
position?: "top" | "bottom";
|
|
37
|
+
style?: StyleProp<ViewStyle>;
|
|
38
|
+
testID?: string;
|
|
39
|
+
}
|
|
40
|
+
declare function ToastRoot({ children, accessibilityHint, className, position, style, testID, ...viewProps }: ToastRootProps): import("react").JSX.Element;
|
|
41
|
+
export interface ToastIconProps {
|
|
42
|
+
variant?: ToastVariant;
|
|
43
|
+
icon?: ReactNode;
|
|
44
|
+
color?: string;
|
|
45
|
+
size?: number;
|
|
46
|
+
}
|
|
47
|
+
declare function ToastIcon({ variant, icon, color, size }: ToastIconProps): import("react").JSX.Element;
|
|
48
|
+
export interface ToastContentProps {
|
|
49
|
+
children: ReactNode;
|
|
50
|
+
className?: string;
|
|
51
|
+
style?: StyleProp<ViewStyle>;
|
|
52
|
+
}
|
|
53
|
+
declare function ToastContent({ children, className, style }: ToastContentProps): import("react").JSX.Element;
|
|
54
|
+
export interface ToastTextProps {
|
|
55
|
+
children: ReactNode;
|
|
56
|
+
className?: string;
|
|
57
|
+
}
|
|
58
|
+
declare function ToastTitle({ children, className }: ToastTextProps): import("react").JSX.Element;
|
|
59
|
+
declare function ToastDescription({ children, className }: ToastTextProps): import("react").JSX.Element;
|
|
60
|
+
export interface ToastActionProps extends Omit<PressableProps, "children" | "onPress"> {
|
|
61
|
+
label: string;
|
|
62
|
+
onPress: () => void;
|
|
63
|
+
className?: string;
|
|
64
|
+
}
|
|
65
|
+
declare function ToastActionButton({ label, onPress, className, style, ...props }: ToastActionProps): import("react").JSX.Element;
|
|
66
|
+
export interface ToastCloseProps extends Omit<PressableProps, "children" | "onPress"> {
|
|
67
|
+
onPress: () => void;
|
|
68
|
+
accessibilityLabel?: string;
|
|
69
|
+
className?: string;
|
|
70
|
+
}
|
|
71
|
+
declare function ToastClose({ onPress, accessibilityLabel, className, style, ...props }: ToastCloseProps): import("react").JSX.Element;
|
|
72
|
+
export declare const Toast: {
|
|
73
|
+
Root: typeof ToastRoot;
|
|
74
|
+
Icon: typeof ToastIcon;
|
|
75
|
+
Content: typeof ToastContent;
|
|
76
|
+
Title: typeof ToastTitle;
|
|
77
|
+
Description: typeof ToastDescription;
|
|
78
|
+
Action: typeof ToastActionButton;
|
|
79
|
+
Close: typeof ToastClose;
|
|
80
|
+
};
|
|
20
81
|
type MessageOrFn<T> = string | ((value: T) => string);
|
|
21
82
|
export interface ToastPromiseMessages<T> {
|
|
22
83
|
loading: string;
|
|
@@ -24,7 +85,7 @@ export interface ToastPromiseMessages<T> {
|
|
|
24
85
|
error: MessageOrFn<unknown>;
|
|
25
86
|
}
|
|
26
87
|
export interface ToastContextValue {
|
|
27
|
-
show: (options: ToastOptions) => string;
|
|
88
|
+
show: (options: ToastOptions | ToastCustomOptions) => string;
|
|
28
89
|
update: (id: string, options: Partial<ToastOptions>) => void;
|
|
29
90
|
hide: (id: string) => void;
|
|
30
91
|
info: (message: string, options?: Omit<ToastOptions, "message" | "variant">) => string;
|
|
@@ -1,13 +1,63 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "nativewind/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "nativewind/jsx-runtime";
|
|
2
2
|
import Icon from "../Display/Display.Icon.js";
|
|
3
|
+
import Spinner from "./Feedback.Spinner.js";
|
|
3
4
|
import Pressable from "../primitives/Pressable.js";
|
|
4
|
-
import {
|
|
5
|
-
import { tv } from "../theme/tw.js";
|
|
5
|
+
import { useTheme } from "../theme/index.js";
|
|
6
|
+
import { cn, tv } from "../theme/tw.js";
|
|
6
7
|
import { Typography } from "../Typography/index.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
8
|
+
import { useReducedMotion } from "../utils/useReducedMotion.js";
|
|
9
|
+
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, } from "react";
|
|
10
|
+
import { View } from "react-native";
|
|
11
|
+
import { GestureDetector, usePanGesture } from "react-native-gesture-handler";
|
|
9
12
|
import Animated, { FadeInDown, FadeInUp, FadeOutDown, FadeOutUp, } from "react-native-reanimated";
|
|
10
13
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
14
|
+
function ToastRoot({ children, accessibilityHint = "Usa Cerrar o desliza hacia arriba o abajo para cerrar", className, position = "top", style, testID, ...viewProps }) {
|
|
15
|
+
const reducedMotion = useReducedMotion();
|
|
16
|
+
const styles = toast();
|
|
17
|
+
const entering = position === "top" ? FadeInDown : FadeInUp;
|
|
18
|
+
const exiting = position === "top" ? FadeOutUp : FadeOutDown;
|
|
19
|
+
return (_jsx(Animated.View, { ...viewProps, accessibilityHint: accessibilityHint, accessibilityLiveRegion: "polite", accessibilityRole: "alert", className: styles.root({ className }), entering: reducedMotion ? undefined : entering.duration(220), exiting: reducedMotion ? undefined : exiting.duration(180), style: style, testID: testID, children: children }));
|
|
20
|
+
}
|
|
21
|
+
function ToastIcon({ variant = "info", icon, color, size = 22 }) {
|
|
22
|
+
const theme = useTheme();
|
|
23
|
+
if (icon)
|
|
24
|
+
return _jsx(_Fragment, { children: icon });
|
|
25
|
+
if (variant === "loading") {
|
|
26
|
+
return (_jsx(Spinner, { accessibilityLabel: "Cargando", color: color ?? theme.text.accent, size: "small" }));
|
|
27
|
+
}
|
|
28
|
+
const colors = {
|
|
29
|
+
info: theme.info,
|
|
30
|
+
success: theme.success,
|
|
31
|
+
warning: theme.warning,
|
|
32
|
+
danger: theme.danger,
|
|
33
|
+
};
|
|
34
|
+
return _jsx(Icon, { color: color ?? colors[variant], name: iconByVariant[variant], size: size });
|
|
35
|
+
}
|
|
36
|
+
function ToastContent({ children, className, style }) {
|
|
37
|
+
return (_jsx(View, { className: cn("flex-1", className), style: style, children: children }));
|
|
38
|
+
}
|
|
39
|
+
function ToastTitle({ children, className }) {
|
|
40
|
+
return (_jsx(Typography.Text, { className: cn("text-sm font-semibold text-text-primary", className), children: children }));
|
|
41
|
+
}
|
|
42
|
+
function ToastDescription({ children, className }) {
|
|
43
|
+
return (_jsx(Typography.Text, { className: cn("text-sm text-text-secondary", className), children: children }));
|
|
44
|
+
}
|
|
45
|
+
function ToastActionButton({ label, onPress, className, style, ...props }) {
|
|
46
|
+
return (_jsx(Pressable, { ...props, accessibilityLabel: label, accessibilityRole: "button", className: cn("min-h-12 justify-center px-2", className), onPress: onPress, style: [{ minHeight: 48, minWidth: 48 }, style], children: _jsx(Typography.Text, { className: "text-sm font-semibold text-text-accent", children: label }) }));
|
|
47
|
+
}
|
|
48
|
+
function ToastClose({ onPress, accessibilityLabel = "Cerrar", className, style, ...props }) {
|
|
49
|
+
const theme = useTheme();
|
|
50
|
+
return (_jsx(Pressable, { ...props, accessibilityLabel: accessibilityLabel, accessibilityRole: "button", className: cn("min-h-12 min-w-12 items-center justify-center", className), hitSlop: 8, onPress: onPress, style: [{ minHeight: 48, minWidth: 48 }, style], children: _jsx(Icon, { color: theme.text.tertiary, name: "close", size: 18 }) }));
|
|
51
|
+
}
|
|
52
|
+
export const Toast = {
|
|
53
|
+
Root: ToastRoot,
|
|
54
|
+
Icon: ToastIcon,
|
|
55
|
+
Content: ToastContent,
|
|
56
|
+
Title: ToastTitle,
|
|
57
|
+
Description: ToastDescription,
|
|
58
|
+
Action: ToastActionButton,
|
|
59
|
+
Close: ToastClose,
|
|
60
|
+
};
|
|
11
61
|
const ToastContext = createContext(null);
|
|
12
62
|
const DEFAULT_DURATION = 3500;
|
|
13
63
|
// iOS-style card: rounded, hairline border + soft shadow, neutral surface. The variant is
|
|
@@ -20,39 +70,54 @@ const toast = tv({
|
|
|
20
70
|
},
|
|
21
71
|
});
|
|
22
72
|
const iconByVariant = {
|
|
23
|
-
info:
|
|
24
|
-
success:
|
|
25
|
-
warning:
|
|
26
|
-
danger:
|
|
73
|
+
info: "information-circle",
|
|
74
|
+
success: "checkmark-circle",
|
|
75
|
+
warning: "warning",
|
|
76
|
+
danger: "close-circle",
|
|
27
77
|
};
|
|
28
|
-
function ToastItem({ entry, onHide, position, }) {
|
|
78
|
+
function ToastItem({ entry, onHide, onUpdate, position, }) {
|
|
29
79
|
const variant = entry.variant ?? "info";
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
80
|
+
const showClose = entry.dismissible ?? true;
|
|
81
|
+
const dismiss = () => onHide(entry.id);
|
|
82
|
+
const pan = usePanGesture({
|
|
83
|
+
activeOffsetY: [-12, 12],
|
|
84
|
+
failOffsetX: [-24, 24],
|
|
85
|
+
onDeactivate: ({ canceled, translationY, velocityY }) => {
|
|
86
|
+
if (!canceled && (Math.abs(translationY) >= 48 || Math.abs(velocityY) >= 800)) {
|
|
87
|
+
dismiss();
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
return (_jsx(GestureDetector, { gesture: pan, children: _jsx(Toast.Root, { className: entry.className, onAccessibilityEscape: dismiss, position: position, children: entry.render ? (entry.render({
|
|
92
|
+
dismiss,
|
|
93
|
+
id: entry.id,
|
|
94
|
+
update: (options) => onUpdate(entry.id, options),
|
|
95
|
+
})) : (_jsxs(_Fragment, { children: [_jsx(Toast.Icon, { variant: variant }), _jsxs(Toast.Content, { children: [entry.title ? _jsx(Toast.Title, { children: entry.title }) : null, entry.message ? _jsx(Toast.Description, { children: entry.message }) : null] }), entry.action ? (_jsx(Toast.Action, { label: entry.action.label, onPress: () => {
|
|
96
|
+
entry.action?.onPress();
|
|
97
|
+
dismiss();
|
|
98
|
+
} })) : null, showClose ? _jsx(Toast.Close, { onPress: dismiss }) : null] })) }) }));
|
|
39
99
|
}
|
|
40
100
|
export function ToastProvider({ children, position = "bottom", }) {
|
|
41
101
|
const insets = useSafeAreaInsets();
|
|
42
102
|
const [toasts, setToasts] = useState([]);
|
|
103
|
+
const toastsRef = useRef([]);
|
|
43
104
|
const counter = useRef(0);
|
|
44
105
|
const timers = useRef({});
|
|
106
|
+
const commitToasts = useCallback((next) => {
|
|
107
|
+
toastsRef.current = next;
|
|
108
|
+
setToasts(next);
|
|
109
|
+
}, []);
|
|
45
110
|
const clearTimer = useCallback((id) => {
|
|
46
111
|
const timer = timers.current[id];
|
|
47
|
-
if (timer) {
|
|
112
|
+
if (timer !== undefined) {
|
|
48
113
|
clearTimeout(timer);
|
|
49
114
|
delete timers.current[id];
|
|
50
115
|
}
|
|
51
116
|
}, []);
|
|
52
117
|
const hide = useCallback((id) => {
|
|
53
|
-
|
|
118
|
+
commitToasts(toastsRef.current.filter((t) => t.id !== id));
|
|
54
119
|
clearTimer(id);
|
|
55
|
-
}, [clearTimer]);
|
|
120
|
+
}, [clearTimer, commitToasts]);
|
|
56
121
|
const scheduleDismiss = useCallback((id, variant, duration) => {
|
|
57
122
|
clearTimer(id);
|
|
58
123
|
const resolved = duration ?? (variant === "loading" ? 0 : DEFAULT_DURATION);
|
|
@@ -67,16 +132,32 @@ export function ToastProvider({ children, position = "bottom", }) {
|
|
|
67
132
|
id = `toast-${counter.current}`;
|
|
68
133
|
}
|
|
69
134
|
const toastId = id;
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
135
|
+
const current = toastsRef.current;
|
|
136
|
+
const currentEntry = current.find((entry) => entry.id === toastId);
|
|
137
|
+
const nextEntry = {
|
|
138
|
+
...currentEntry,
|
|
139
|
+
...options,
|
|
140
|
+
id: toastId,
|
|
141
|
+
message: options.message ?? currentEntry?.message ?? "",
|
|
142
|
+
};
|
|
143
|
+
commitToasts(currentEntry
|
|
144
|
+
? current.map((entry) => (entry.id === toastId ? nextEntry : entry))
|
|
145
|
+
: [...current, nextEntry]);
|
|
146
|
+
scheduleDismiss(toastId, nextEntry.variant, nextEntry.duration);
|
|
74
147
|
return toastId;
|
|
75
|
-
}, [scheduleDismiss]);
|
|
148
|
+
}, [commitToasts, scheduleDismiss]);
|
|
76
149
|
const update = useCallback((id, options) => {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
150
|
+
const current = toastsRef.current.find((entry) => entry.id === id);
|
|
151
|
+
if (!current)
|
|
152
|
+
return;
|
|
153
|
+
const updated = { ...current, ...options, id };
|
|
154
|
+
commitToasts(toastsRef.current.map((entry) => (entry.id === id ? updated : entry)));
|
|
155
|
+
scheduleDismiss(id, updated.variant, updated.duration);
|
|
156
|
+
}, [commitToasts, scheduleDismiss]);
|
|
157
|
+
useEffect(() => () => {
|
|
158
|
+
Object.values(timers.current).forEach(clearTimeout);
|
|
159
|
+
timers.current = {};
|
|
160
|
+
}, []);
|
|
80
161
|
const value = useMemo(() => {
|
|
81
162
|
const promise = (p, messages) => {
|
|
82
163
|
const id = show({ message: messages.loading, variant: "loading", duration: 0 });
|
|
@@ -110,7 +191,7 @@ export function ToastProvider({ children, position = "bottom", }) {
|
|
|
110
191
|
promise,
|
|
111
192
|
};
|
|
112
193
|
}, [show, update, hide]);
|
|
113
|
-
return (_jsxs(ToastContext.Provider, { value: value, children: [children, _jsx(View, { className: "absolute left-0 right-0 px-4 gap-2 z-toast", pointerEvents: "box-none", style: position === "top" ? { top: insets.top + 12 } : { bottom: insets.bottom + 12 }, children: toasts.map((entry) => (_jsx(ToastItem, { entry: entry, onHide: hide, position: position }, entry.id))) })] }));
|
|
194
|
+
return (_jsxs(ToastContext.Provider, { value: value, children: [children, _jsx(View, { className: "absolute left-0 right-0 px-4 gap-2 z-toast", pointerEvents: "box-none", style: position === "top" ? { top: insets.top + 12 } : { bottom: insets.bottom + 12 }, children: toasts.map((entry) => (_jsx(ToastItem, { entry: entry, onHide: hide, onUpdate: update, position: position }, entry.id))) })] }));
|
|
114
195
|
}
|
|
115
196
|
export function useToast() {
|
|
116
197
|
const ctx = useContext(ToastContext);
|
package/dist/Feedback/index.d.ts
CHANGED
|
@@ -9,7 +9,12 @@ import ProgressCircle from "./Feedback.ProgressCircle.js";
|
|
|
9
9
|
import Skeleton from "./Feedback.Skeleton.js";
|
|
10
10
|
import SplashScreen from "./Feedback.Splash.js";
|
|
11
11
|
export { ErrorBoundary } from "./ErrorBoundary.js";
|
|
12
|
-
export
|
|
12
|
+
export type { ProgressProps } from "./Feedback.Progress.js";
|
|
13
|
+
export type { ProgressCircleProps } from "./Feedback.ProgressCircle.js";
|
|
14
|
+
export type { SkeletonProps } from "./Feedback.Skeleton.js";
|
|
15
|
+
export type { SpinnerProps, SpinnerVariant, } from "./Feedback.Spinner.js";
|
|
16
|
+
export { Toast, type ToastAction, type ToastActionProps, type ToastCloseProps, type ToastContentProps, type ToastContextValue, type ToastCustomOptions, type ToastIconProps, type ToastOptions, type ToastPromiseMessages, ToastProvider, type ToastRenderProps, type ToastRootProps, type ToastTextProps, type ToastVariant, useToast, } from "./Toast.context.js";
|
|
17
|
+
export { useReducedMotion } from "../utils/useReducedMotion.js";
|
|
13
18
|
export declare const Feedback: {
|
|
14
19
|
Loading: typeof Loading;
|
|
15
20
|
Spinner: import("react").FC<import("./Feedback.Spinner.js").SpinnerProps>;
|
|
@@ -22,5 +27,14 @@ export declare const Feedback: {
|
|
|
22
27
|
EmptyState: typeof EmptyState;
|
|
23
28
|
Error: typeof Error;
|
|
24
29
|
SplashScreen: typeof SplashScreen;
|
|
30
|
+
Toast: {
|
|
31
|
+
Root: ({ children, accessibilityHint, className, position, style, testID, ...viewProps }: import("./Toast.context.js").ToastRootProps) => import("react").JSX.Element;
|
|
32
|
+
Icon: ({ variant, icon, color, size }: import("./Toast.context.js").ToastIconProps) => import("react").JSX.Element;
|
|
33
|
+
Content: ({ children, className, style }: import("./Toast.context.js").ToastContentProps) => import("react").JSX.Element;
|
|
34
|
+
Title: ({ children, className }: import("./Toast.context.js").ToastTextProps) => import("react").JSX.Element;
|
|
35
|
+
Description: ({ children, className }: import("./Toast.context.js").ToastTextProps) => import("react").JSX.Element;
|
|
36
|
+
Action: ({ label, onPress, className, style, ...props }: import("./Toast.context.js").ToastActionProps) => import("react").JSX.Element;
|
|
37
|
+
Close: ({ onPress, accessibilityLabel, className, style, ...props }: import("./Toast.context.js").ToastCloseProps) => import("react").JSX.Element;
|
|
38
|
+
};
|
|
25
39
|
};
|
|
26
40
|
export default Feedback;
|
package/dist/Feedback/index.js
CHANGED
|
@@ -9,8 +9,10 @@ import ProgressCircle from "./Feedback.ProgressCircle.js";
|
|
|
9
9
|
import Skeleton from "./Feedback.Skeleton.js";
|
|
10
10
|
import Spinner from "./Feedback.Spinner.js";
|
|
11
11
|
import SplashScreen from "./Feedback.Splash.js";
|
|
12
|
+
import { Toast } from "./Toast.context.js";
|
|
12
13
|
export { ErrorBoundary } from "./ErrorBoundary.js";
|
|
13
|
-
export { ToastProvider, useToast, } from "./Toast.context.js";
|
|
14
|
+
export { Toast, ToastProvider, useToast, } from "./Toast.context.js";
|
|
15
|
+
export { useReducedMotion } from "../utils/useReducedMotion.js";
|
|
14
16
|
export const Feedback = {
|
|
15
17
|
Loading,
|
|
16
18
|
Spinner,
|
|
@@ -23,5 +25,6 @@ export const Feedback = {
|
|
|
23
25
|
EmptyState,
|
|
24
26
|
Error,
|
|
25
27
|
SplashScreen,
|
|
28
|
+
Toast,
|
|
26
29
|
};
|
|
27
30
|
export default Feedback;
|
|
@@ -8,9 +8,8 @@ interface RowCheckBoxProps {
|
|
|
8
8
|
onCheck?: (isChecked: boolean) => void;
|
|
9
9
|
disabled?: boolean;
|
|
10
10
|
accessibilityLabel?: string;
|
|
11
|
-
accessibleSizing?: boolean;
|
|
12
11
|
className?: string;
|
|
13
12
|
testID?: string;
|
|
14
13
|
}
|
|
15
|
-
declare function RowCheckBox({ mainText, subText, rightText, rightComponent, leftComponent, isChecked, onCheck, disabled, accessibilityLabel,
|
|
14
|
+
declare function RowCheckBox({ mainText, subText, rightText, rightComponent, leftComponent, isChecked, onCheck, disabled, accessibilityLabel, className, testID, }: RowCheckBoxProps): import("react").JSX.Element;
|
|
16
15
|
export default RowCheckBox;
|
|
@@ -4,7 +4,7 @@ import { cn } from "../theme/tw.js";
|
|
|
4
4
|
import { Typography } from "../Typography/index.js";
|
|
5
5
|
import { Checkbox } from "expo-checkbox";
|
|
6
6
|
import { Pressable, View } from "react-native";
|
|
7
|
-
function RowCheckBox({ mainText, subText, rightText, rightComponent, leftComponent, isChecked = false, onCheck = () => { }, disabled = false, accessibilityLabel,
|
|
7
|
+
function RowCheckBox({ mainText, subText, rightText, rightComponent, leftComponent, isChecked = false, onCheck = () => { }, disabled = false, accessibilityLabel, className, testID, }) {
|
|
8
8
|
const handlePress = () => {
|
|
9
9
|
if (!disabled) {
|
|
10
10
|
onCheck(!isChecked);
|
|
@@ -25,6 +25,6 @@ function RowCheckBox({ mainText, subText, rightText, rightComponent, leftCompone
|
|
|
25
25
|
}
|
|
26
26
|
return null;
|
|
27
27
|
};
|
|
28
|
-
return (_jsxs(Pressable, { accessibilityLabel: accessibilityLabel ?? [mainText, subText, rightText].filter(Boolean).join(", "), accessibilityRole: "checkbox", accessibilityState: { checked: isChecked, disabled }, className: cn("flex-row justify-between items-center", disabled && "opacity-50", className), disabled: disabled, onPress: handlePress, style:
|
|
28
|
+
return (_jsxs(Pressable, { accessibilityLabel: accessibilityLabel ?? [mainText, subText, rightText].filter(Boolean).join(", "), accessibilityRole: "checkbox", accessibilityState: { checked: isChecked, disabled }, className: cn("flex-row justify-between items-center", disabled && "opacity-50", className), disabled: disabled, onPress: handlePress, style: { minHeight: 48, minWidth: 48, paddingVertical: 8 }, testID: testID, children: [_jsx(View, { className: "flex-1 min-w-0", children: renderLeftComponent() }), _jsxs(View, { className: "flex-row items-center flex-shrink", children: [renderRightComponent(), _jsx(Checkbox, { accessibilityElementsHidden: true, accessible: false, className: "ml-2", color: theme.primary, disabled: disabled, importantForAccessibility: "no-hide-descendants", pointerEvents: "none", value: isChecked })] })] }));
|
|
29
29
|
}
|
|
30
30
|
export default RowCheckBox;
|
|
@@ -8,7 +8,6 @@ export interface DatePickerProps {
|
|
|
8
8
|
minimumDate?: Date;
|
|
9
9
|
maximumDate?: Date;
|
|
10
10
|
disabled?: boolean;
|
|
11
|
-
accessibleSizing?: boolean;
|
|
12
11
|
className?: string;
|
|
13
12
|
testID?: string;
|
|
14
13
|
}
|
|
@@ -17,5 +16,5 @@ export interface DatePickerProps {
|
|
|
17
16
|
* (`@react-native-community/datetimepicker`) — a case where native wins (expected UX).
|
|
18
17
|
* Subpath `Form/DatePicker`; **optional** peer dep.
|
|
19
18
|
*/
|
|
20
|
-
declare function DatePicker({ value, onChange, mode, label, placeholder, format, minimumDate, maximumDate, disabled,
|
|
19
|
+
declare function DatePicker({ value, onChange, mode, label, placeholder, format, minimumDate, maximumDate, disabled, className, testID, }: DatePickerProps): import("react").JSX.Element;
|
|
21
20
|
export default DatePicker;
|
|
@@ -24,7 +24,7 @@ const iconByMode = {
|
|
|
24
24
|
* (`@react-native-community/datetimepicker`) — a case where native wins (expected UX).
|
|
25
25
|
* Subpath `Form/DatePicker`; **optional** peer dep.
|
|
26
26
|
*/
|
|
27
|
-
function DatePicker({ value, onChange, mode = "date", label, placeholder = "Seleccionar…", format, minimumDate, maximumDate, disabled = false,
|
|
27
|
+
function DatePicker({ value, onChange, mode = "date", label, placeholder = "Seleccionar…", format, minimumDate, maximumDate, disabled = false, className, testID, }) {
|
|
28
28
|
const [open, setOpen] = useState(false);
|
|
29
29
|
const theme = useTheme();
|
|
30
30
|
const fmt = format ?? ((d) => defaultFormat(d, mode));
|
|
@@ -34,6 +34,6 @@ function DatePicker({ value, onChange, mode = "date", label, placeholder = "Sele
|
|
|
34
34
|
if (date)
|
|
35
35
|
onChange(date);
|
|
36
36
|
};
|
|
37
|
-
return (_jsxs(View, { className: cn("gap-1", className), testID: testID, children: [label ? (_jsx(Typography.Text, { className: "font-medium text-text-primary", variant: "bodySmall", children: label })) : null, _jsxs(Pressable, { accessibilityHint: "Abre el selector de fecha", accessibilityLabel: label ?? placeholder, accessibilityRole: "button", accessibilityState: { disabled }, accessibilityValue: { text: value ? fmt(value) : placeholder }, className: "flex-row items-center justify-between rounded-lg border border-input bg-bg-card px-3 min-h-[
|
|
37
|
+
return (_jsxs(View, { className: cn("gap-1", className), testID: testID, children: [label ? (_jsx(Typography.Text, { className: "font-medium text-text-primary", variant: "bodySmall", children: label })) : null, _jsxs(Pressable, { accessibilityHint: "Abre el selector de fecha", accessibilityLabel: label ?? placeholder, accessibilityRole: "button", accessibilityState: { disabled }, accessibilityValue: { text: value ? fmt(value) : placeholder }, className: "flex-row items-center justify-between rounded-lg border border-input bg-bg-card px-3 min-h-[48px]", disabled: disabled, onPress: () => setOpen(true), style: { minHeight: 48, minWidth: 48 }, children: [_jsx(Typography.Text, { className: cn("flex-1 text-sm", value ? "text-text-primary" : "text-text-tertiary"), style: { paddingVertical: 12 }, children: value ? fmt(value) : placeholder }), _jsx(Icon, { color: theme.icon.secondary, name: iconByMode[mode], size: 18 })] }), open ? (_jsx(DateTimePicker, { maximumDate: maximumDate, minimumDate: minimumDate, mode: mode === "datetime" ? "date" : mode, onDismiss: () => setOpen(false), onValueChange: handleValueChange, value: value ?? new Date() })) : null] }));
|
|
38
38
|
}
|
|
39
39
|
export default DatePicker;
|
|
@@ -1,14 +1,26 @@
|
|
|
1
|
+
import { Typography } from "../Typography/index.js";
|
|
2
|
+
import { type ComponentProps, type ReactNode } from "react";
|
|
3
|
+
import { type TouchableOpacityProps, type ViewProps } from "react-native";
|
|
1
4
|
export interface DropdownOption {
|
|
2
5
|
id: string;
|
|
3
6
|
title: string;
|
|
4
7
|
onPress: () => void;
|
|
5
8
|
disabled?: boolean;
|
|
6
9
|
}
|
|
7
|
-
interface DropdownButtonProps {
|
|
10
|
+
export interface DropdownButtonProps {
|
|
8
11
|
title: string;
|
|
9
|
-
options: DropdownOption[];
|
|
12
|
+
options: readonly DropdownOption[];
|
|
10
13
|
variant?: "primary" | "secondary" | "outline";
|
|
11
14
|
size?: "xs" | "small" | "medium" | "large";
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
closeAccessibilityLabel?: string;
|
|
17
|
+
buttonProps?: Omit<TouchableOpacityProps, "children" | "onPress" | "disabled">;
|
|
18
|
+
contentProps?: Omit<ViewProps, "children">;
|
|
19
|
+
optionProps?: Omit<TouchableOpacityProps, "children" | "onPress" | "disabled">;
|
|
20
|
+
titleTextProps?: ComponentProps<typeof Typography.Text>;
|
|
21
|
+
optionTextProps?: ComponentProps<typeof Typography.Text>;
|
|
22
|
+
renderOption?: (option: DropdownOption) => ReactNode;
|
|
23
|
+
testID?: string;
|
|
12
24
|
}
|
|
13
|
-
declare function DropdownButton({ title, options, variant, size, }: DropdownButtonProps): import("react").JSX.Element;
|
|
25
|
+
declare function DropdownButton({ title, options, variant, size, disabled, closeAccessibilityLabel, buttonProps, contentProps, optionProps, titleTextProps, optionTextProps, renderOption, testID, }: DropdownButtonProps): import("react").JSX.Element;
|
|
14
26
|
export default DropdownButton;
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "nativewind/jsx-runtime";
|
|
2
|
-
import
|
|
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
|
+
});
|
|
@@ -6,7 +6,6 @@ interface FormInputProps extends TextInputProps {
|
|
|
6
6
|
error?: string;
|
|
7
7
|
tooltip?: string;
|
|
8
8
|
className?: string;
|
|
9
|
-
accessibleSizing?: boolean;
|
|
10
9
|
}
|
|
11
|
-
declare function FormInput({ label, error, tooltip, style, multiline, numberOfLines, onFocus, onBlur, className,
|
|
10
|
+
declare function FormInput({ label, error, tooltip, style, multiline, numberOfLines, onFocus, onBlur, className, ref, ...props }: FormInputProps): import("react").JSX.Element;
|
|
12
11
|
export default FormInput;
|
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,
|
|
18
|
+
function FormInput({ label, error, tooltip, style, multiline = false, numberOfLines = 1, onFocus, onBlur, className, ref, ...props }) {
|
|
19
19
|
const [isFocused, setIsFocused] = useState(false);
|
|
20
20
|
const hasError = Boolean(error);
|
|
21
21
|
const handleFocus = (e) => {
|
|
@@ -33,6 +33,6 @@ function FormInput({ label, error, tooltip, style, multiline = false, numberOfLi
|
|
|
33
33
|
return "focused";
|
|
34
34
|
return "default";
|
|
35
35
|
};
|
|
36
|
-
return (_jsxs(View, { className: cn("mb-2", className), children: [_jsx(Typography.Text, { className: "mb-1 font-medium text-text-primary", variant: "bodySmall", children: label }), _jsx(View, { className: inputContainer({ state: getState() }), children: _jsx(TextInput, { accessibilityHint: error || tooltip, accessibilityLabel: label, className: cn("px-3 py-2.5 text-sm min-h-[
|
|
36
|
+
return (_jsxs(View, { className: cn("mb-2", className), children: [_jsx(Typography.Text, { className: "mb-1 font-medium text-text-primary", variant: "bodySmall", children: label }), _jsx(View, { className: inputContainer({ state: getState() }), children: _jsx(TextInput, { accessibilityHint: error || tooltip, accessibilityLabel: label, className: cn("px-3 py-2.5 text-sm min-h-[48px] text-text-primary", multiline && "min-h-[64px] pt-2.5"), multiline: multiline, numberOfLines: numberOfLines, onBlur: handleBlur, onFocus: handleFocus, placeholderTextColor: theme.text.tertiary, ref: ref, selectionColor: theme.primary, style: [{ minHeight: multiline ? 64 : 48 }, style], textAlignVertical: multiline ? "top" : "center", underlineColorAndroid: "transparent", ...props }) }), hasError && (_jsx(Typography.Text, { accessibilityLiveRegion: "polite", className: "mt-0.5 text-danger", variant: "bodySmall", children: error }))] }));
|
|
37
37
|
}
|
|
38
38
|
export default FormInput;
|
|
@@ -10,10 +10,9 @@ export interface SelectProps<T extends string | number> {
|
|
|
10
10
|
placeholder?: string;
|
|
11
11
|
title?: string;
|
|
12
12
|
disabled?: boolean;
|
|
13
|
-
accessibleSizing?: boolean;
|
|
14
13
|
className?: string;
|
|
15
14
|
testID?: string;
|
|
16
15
|
showCloseButton?: boolean;
|
|
17
16
|
}
|
|
18
|
-
declare function Select<T extends string | number>({ options, value, onChange, label, placeholder, title, disabled,
|
|
17
|
+
declare function Select<T extends string | number>({ options, value, onChange, label, placeholder, title, disabled, className, testID, showCloseButton, }: SelectProps<T>): import("react").JSX.Element;
|
|
19
18
|
export default Select;
|