orn-ui 0.1.1 → 0.2.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 +20 -2
- package/lib/commonjs/atoms/PressableScale.js +9 -10
- package/lib/commonjs/atoms/PressableScale.js.map +1 -1
- package/lib/commonjs/atoms/Skeleton.js +96 -0
- package/lib/commonjs/atoms/Skeleton.js.map +1 -0
- package/lib/commonjs/atoms/Transition.js +171 -0
- package/lib/commonjs/atoms/Transition.js.map +1 -0
- package/lib/commonjs/atoms/index.js +20 -0
- package/lib/commonjs/atoms/index.js.map +1 -1
- package/lib/commonjs/molecules/OptionCard.js +55 -22
- package/lib/commonjs/molecules/OptionCard.js.map +1 -1
- package/lib/commonjs/molecules/{ThemeToggle.js → SegmentedControl.js} +36 -36
- package/lib/commonjs/molecules/SegmentedControl.js.map +1 -0
- package/lib/commonjs/molecules/Stepper.js +124 -47
- package/lib/commonjs/molecules/Stepper.js.map +1 -1
- package/lib/commonjs/molecules/Steps.js +36 -6
- package/lib/commonjs/molecules/Steps.js.map +1 -1
- package/lib/commonjs/molecules/index.js +9 -3
- package/lib/commonjs/molecules/index.js.map +1 -1
- package/lib/commonjs/organisms/AlertProvider.js +26 -0
- package/lib/commonjs/organisms/AlertProvider.js.map +1 -1
- package/lib/commonjs/organisms/BottomSheet.js +118 -39
- package/lib/commonjs/organisms/BottomSheet.js.map +1 -1
- package/lib/commonjs/organisms/DateField.js +46 -15
- package/lib/commonjs/organisms/DateField.js.map +1 -1
- package/lib/commonjs/organisms/DatePicker.js +129 -50
- package/lib/commonjs/organisms/DatePicker.js.map +1 -1
- package/lib/commonjs/organisms/List.js +66 -44
- package/lib/commonjs/organisms/List.js.map +1 -1
- package/lib/commonjs/organisms/Modal.js +42 -87
- package/lib/commonjs/organisms/Modal.js.map +1 -1
- package/lib/commonjs/organisms/NavigationBar.js +210 -0
- package/lib/commonjs/organisms/NavigationBar.js.map +1 -0
- package/lib/commonjs/organisms/Screen.js +9 -2
- package/lib/commonjs/organisms/Screen.js.map +1 -1
- package/lib/commonjs/organisms/SearchList.js +95 -27
- package/lib/commonjs/organisms/SearchList.js.map +1 -1
- package/lib/commonjs/organisms/Select.js +222 -50
- package/lib/commonjs/organisms/Select.js.map +1 -1
- package/lib/commonjs/organisms/ThemeToggle.js +52 -0
- package/lib/commonjs/organisms/ThemeToggle.js.map +1 -0
- package/lib/commonjs/organisms/Toast.js +8 -22
- package/lib/commonjs/organisms/Toast.js.map +1 -1
- package/lib/commonjs/organisms/ToastProvider.js +24 -0
- package/lib/commonjs/organisms/ToastProvider.js.map +1 -1
- package/lib/commonjs/organisms/Wizard.js +23 -2
- package/lib/commonjs/organisms/Wizard.js.map +1 -1
- package/lib/commonjs/organisms/imperativeBridge.js +28 -0
- package/lib/commonjs/organisms/imperativeBridge.js.map +1 -0
- package/lib/commonjs/organisms/index.js +50 -0
- package/lib/commonjs/organisms/index.js.map +1 -1
- package/lib/commonjs/theme/colors.js +207 -0
- package/lib/commonjs/theme/colors.js.map +1 -0
- package/lib/commonjs/theme/createTheme.js +25 -2
- package/lib/commonjs/theme/createTheme.js.map +1 -1
- package/lib/commonjs/theme/index.js +25 -0
- package/lib/commonjs/theme/index.js.map +1 -1
- package/lib/commonjs/theme/labels.js +5 -1
- package/lib/commonjs/theme/labels.js.map +1 -1
- package/lib/commonjs/theme/palettes.js +5 -2
- package/lib/commonjs/theme/palettes.js.map +1 -1
- package/lib/module/atoms/PressableScale.js +9 -10
- package/lib/module/atoms/PressableScale.js.map +1 -1
- package/lib/module/atoms/Skeleton.js +91 -0
- package/lib/module/atoms/Skeleton.js.map +1 -0
- package/lib/module/atoms/Transition.js +165 -0
- package/lib/module/atoms/Transition.js.map +1 -0
- package/lib/module/atoms/index.js +2 -0
- package/lib/module/atoms/index.js.map +1 -1
- package/lib/module/molecules/OptionCard.js +56 -23
- package/lib/module/molecules/OptionCard.js.map +1 -1
- package/lib/module/molecules/{ThemeToggle.js → SegmentedControl.js} +36 -36
- package/lib/module/molecules/SegmentedControl.js.map +1 -0
- package/lib/module/molecules/Stepper.js +125 -49
- package/lib/module/molecules/Stepper.js.map +1 -1
- package/lib/module/molecules/Steps.js +37 -7
- package/lib/module/molecules/Steps.js.map +1 -1
- package/lib/module/molecules/index.js +2 -2
- package/lib/module/molecules/index.js.map +1 -1
- package/lib/module/organisms/AlertProvider.js +24 -1
- package/lib/module/organisms/AlertProvider.js.map +1 -1
- package/lib/module/organisms/BottomSheet.js +120 -41
- package/lib/module/organisms/BottomSheet.js.map +1 -1
- package/lib/module/organisms/DateField.js +49 -18
- package/lib/module/organisms/DateField.js.map +1 -1
- package/lib/module/organisms/DatePicker.js +131 -52
- package/lib/module/organisms/DatePicker.js.map +1 -1
- package/lib/module/organisms/List.js +68 -46
- package/lib/module/organisms/List.js.map +1 -1
- package/lib/module/organisms/Modal.js +44 -89
- package/lib/module/organisms/Modal.js.map +1 -1
- package/lib/module/organisms/NavigationBar.js +205 -0
- package/lib/module/organisms/NavigationBar.js.map +1 -0
- package/lib/module/organisms/Screen.js +9 -2
- package/lib/module/organisms/Screen.js.map +1 -1
- package/lib/module/organisms/SearchList.js +96 -28
- package/lib/module/organisms/SearchList.js.map +1 -1
- package/lib/module/organisms/Select.js +224 -52
- package/lib/module/organisms/Select.js.map +1 -1
- package/lib/module/organisms/ThemeToggle.js +47 -0
- package/lib/module/organisms/ThemeToggle.js.map +1 -0
- package/lib/module/organisms/Toast.js +10 -24
- package/lib/module/organisms/Toast.js.map +1 -1
- package/lib/module/organisms/ToastProvider.js +21 -0
- package/lib/module/organisms/ToastProvider.js.map +1 -1
- package/lib/module/organisms/Wizard.js +24 -3
- package/lib/module/organisms/Wizard.js.map +1 -1
- package/lib/module/organisms/imperativeBridge.js +24 -0
- package/lib/module/organisms/imperativeBridge.js.map +1 -0
- package/lib/module/organisms/index.js +4 -2
- package/lib/module/organisms/index.js.map +1 -1
- package/lib/module/theme/colors.js +194 -0
- package/lib/module/theme/colors.js.map +1 -0
- package/lib/module/theme/createTheme.js +25 -2
- package/lib/module/theme/createTheme.js.map +1 -1
- package/lib/module/theme/index.js +1 -0
- package/lib/module/theme/index.js.map +1 -1
- package/lib/module/theme/labels.js +5 -1
- package/lib/module/theme/labels.js.map +1 -1
- package/lib/module/theme/palettes.js +5 -2
- package/lib/module/theme/palettes.js.map +1 -1
- package/lib/typescript/atoms/PressableScale.d.ts +3 -2
- package/lib/typescript/atoms/PressableScale.d.ts.map +1 -1
- package/lib/typescript/atoms/Skeleton.d.ts +29 -0
- package/lib/typescript/atoms/Skeleton.d.ts.map +1 -0
- package/lib/typescript/atoms/Transition.d.ts +51 -0
- package/lib/typescript/atoms/Transition.d.ts.map +1 -0
- package/lib/typescript/atoms/index.d.ts +4 -0
- package/lib/typescript/atoms/index.d.ts.map +1 -1
- package/lib/typescript/molecules/OptionCard.d.ts +11 -6
- package/lib/typescript/molecules/OptionCard.d.ts.map +1 -1
- package/lib/typescript/molecules/SegmentedControl.d.ts +24 -0
- package/lib/typescript/molecules/SegmentedControl.d.ts.map +1 -0
- package/lib/typescript/molecules/Stepper.d.ts +35 -3
- package/lib/typescript/molecules/Stepper.d.ts.map +1 -1
- package/lib/typescript/molecules/Steps.d.ts.map +1 -1
- package/lib/typescript/molecules/index.d.ts +4 -4
- package/lib/typescript/molecules/index.d.ts.map +1 -1
- package/lib/typescript/organisms/AlertProvider.d.ts +9 -0
- package/lib/typescript/organisms/AlertProvider.d.ts.map +1 -1
- package/lib/typescript/organisms/BottomSheet.d.ts +26 -4
- package/lib/typescript/organisms/BottomSheet.d.ts.map +1 -1
- package/lib/typescript/organisms/DateField.d.ts +23 -5
- package/lib/typescript/organisms/DateField.d.ts.map +1 -1
- package/lib/typescript/organisms/DatePicker.d.ts +5 -1
- package/lib/typescript/organisms/DatePicker.d.ts.map +1 -1
- package/lib/typescript/organisms/List.d.ts +9 -2
- package/lib/typescript/organisms/List.d.ts.map +1 -1
- package/lib/typescript/organisms/Modal.d.ts +3 -5
- package/lib/typescript/organisms/Modal.d.ts.map +1 -1
- package/lib/typescript/organisms/NavigationBar.d.ts +32 -0
- package/lib/typescript/organisms/NavigationBar.d.ts.map +1 -0
- package/lib/typescript/organisms/Screen.d.ts +2 -1
- package/lib/typescript/organisms/Screen.d.ts.map +1 -1
- package/lib/typescript/organisms/SearchList.d.ts +21 -5
- package/lib/typescript/organisms/SearchList.d.ts.map +1 -1
- package/lib/typescript/organisms/Select.d.ts +19 -4
- package/lib/typescript/organisms/Select.d.ts.map +1 -1
- package/lib/typescript/{molecules → organisms}/ThemeToggle.d.ts +4 -4
- package/lib/typescript/organisms/ThemeToggle.d.ts.map +1 -0
- package/lib/typescript/organisms/Toast.d.ts.map +1 -1
- package/lib/typescript/organisms/ToastProvider.d.ts +8 -0
- package/lib/typescript/organisms/ToastProvider.d.ts.map +1 -1
- package/lib/typescript/organisms/Wizard.d.ts +7 -1
- package/lib/typescript/organisms/Wizard.d.ts.map +1 -1
- package/lib/typescript/organisms/imperativeBridge.d.ts +12 -0
- package/lib/typescript/organisms/imperativeBridge.d.ts.map +1 -0
- package/lib/typescript/organisms/index.d.ts +7 -3
- package/lib/typescript/organisms/index.d.ts.map +1 -1
- package/lib/typescript/theme/colors.d.ts +44 -0
- package/lib/typescript/theme/colors.d.ts.map +1 -0
- package/lib/typescript/theme/createTheme.d.ts +3 -0
- package/lib/typescript/theme/createTheme.d.ts.map +1 -1
- package/lib/typescript/theme/index.d.ts +3 -1
- package/lib/typescript/theme/index.d.ts.map +1 -1
- package/lib/typescript/theme/labels.d.ts +6 -0
- package/lib/typescript/theme/labels.d.ts.map +1 -1
- package/lib/typescript/theme/palettes.d.ts.map +1 -1
- package/lib/typescript/theme/types.d.ts +9 -0
- package/lib/typescript/theme/types.d.ts.map +1 -1
- package/package.json +67 -6
- package/registry/alert-provider.json +9 -2
- package/registry/bottom-sheet.json +6 -2
- package/registry/core.json +9 -5
- package/registry/date-field.json +2 -1
- package/registry/date-picker.json +1 -1
- package/registry/index.json +99 -30
- package/registry/list.json +2 -1
- package/registry/manifest.json +44 -6
- package/registry/modal.json +3 -2
- package/registry/navigation-bar.json +18 -0
- package/registry/option-card.json +3 -2
- package/registry/pressable-scale.json +1 -1
- package/registry/screen.json +1 -1
- package/registry/search-list.json +2 -1
- package/registry/segmented-control.json +18 -0
- package/registry/select.json +1 -1
- package/registry/skeleton.json +18 -0
- package/registry/stepper.json +3 -2
- package/registry/steps.json +1 -1
- package/registry/theme-toggle.json +5 -4
- package/registry/toast-provider.json +11 -3
- package/registry/toast.json +3 -2
- package/registry/transition.json +19 -0
- package/registry/wizard.json +3 -2
- package/src/atoms/PressableScale.tsx +16 -6
- package/src/atoms/Skeleton.tsx +114 -0
- package/src/atoms/Transition.tsx +189 -0
- package/src/atoms/index.ts +4 -0
- package/src/molecules/OptionCard.tsx +62 -25
- package/src/molecules/SegmentedControl.tsx +84 -0
- package/src/molecules/Stepper.tsx +147 -32
- package/src/molecules/Steps.tsx +27 -8
- package/src/molecules/index.ts +4 -4
- package/src/organisms/AlertProvider.tsx +26 -1
- package/src/organisms/BottomSheet.tsx +214 -62
- package/src/organisms/DateField.tsx +79 -19
- package/src/organisms/DatePicker.tsx +198 -80
- package/src/organisms/List.tsx +73 -41
- package/src/organisms/Modal.tsx +49 -61
- package/src/organisms/NavigationBar.tsx +226 -0
- package/src/organisms/Screen.tsx +11 -3
- package/src/organisms/SearchList.tsx +131 -35
- package/src/organisms/Select.tsx +223 -45
- package/src/organisms/ThemeToggle.tsx +30 -0
- package/src/organisms/Toast.tsx +8 -21
- package/src/organisms/ToastProvider.tsx +24 -0
- package/src/organisms/Wizard.tsx +37 -3
- package/src/organisms/imperativeBridge.ts +28 -0
- package/src/organisms/index.ts +7 -3
- package/src/theme/colors.ts +175 -0
- package/src/theme/createTheme.ts +38 -4
- package/src/theme/index.ts +3 -1
- package/src/theme/labels.ts +13 -0
- package/src/theme/palettes.ts +5 -2
- package/src/theme/types.ts +10 -0
- package/lib/commonjs/molecules/ThemeToggle.js.map +0 -1
- package/lib/module/molecules/ThemeToggle.js.map +0 -1
- package/lib/typescript/molecules/ThemeToggle.d.ts.map +0 -1
- package/src/molecules/ThemeToggle.tsx +0 -67
|
@@ -1,16 +1,52 @@
|
|
|
1
1
|
import React, { memo, useEffect, useRef, useState } from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
Animated,
|
|
4
|
+
Dimensions,
|
|
5
|
+
Keyboard,
|
|
6
|
+
KeyboardAvoidingView,
|
|
7
|
+
Modal,
|
|
8
|
+
PanResponder,
|
|
9
|
+
Platform,
|
|
10
|
+
Pressable,
|
|
11
|
+
ScrollView,
|
|
12
|
+
View,
|
|
13
|
+
type ViewStyle,
|
|
14
|
+
} from 'react-native';
|
|
3
15
|
import { createStyles } from '../theme/createStyles';
|
|
4
|
-
import { useInsets } from '../theme/UIProvider';
|
|
16
|
+
import { useColors, useInsets } from '../theme/UIProvider';
|
|
17
|
+
import { Title } from '../atoms/Text';
|
|
18
|
+
import { IconButton } from '../atoms/IconButton';
|
|
19
|
+
import { Transition } from '../atoms/Transition';
|
|
5
20
|
|
|
6
21
|
const { height: SCREEN_HEIGHT } = Dimensions.get('window');
|
|
7
22
|
const DISMISS_THRESHOLD = 100;
|
|
8
23
|
|
|
24
|
+
export type FooterPlacement = 'fixed' | 'scroll' | 'hide-with-keyboard';
|
|
25
|
+
|
|
9
26
|
export interface BottomSheetProps {
|
|
10
27
|
visible: boolean;
|
|
11
28
|
onClose: () => void;
|
|
12
29
|
children: React.ReactNode;
|
|
30
|
+
/** Con título aparece el header, con su botón de cerrar. */
|
|
31
|
+
title?: string;
|
|
32
|
+
/** Acciones de la hoja. Dónde vive lo decide `footerPlacement`. */
|
|
33
|
+
footer?: React.ReactNode;
|
|
34
|
+
/**
|
|
35
|
+
* 'fixed' lo ancla al pie de la hoja; 'scroll' lo deja al final del
|
|
36
|
+
* contenido; 'hide-with-keyboard' lo esconde mientras se escribe, que en un
|
|
37
|
+
* formulario largo le devuelve esa franja al campo enfocado. @default 'fixed'
|
|
38
|
+
*/
|
|
39
|
+
footerPlacement?: FooterPlacement;
|
|
40
|
+
/** El contenido scrollea cuando no entra. @default true */
|
|
41
|
+
scrollable?: boolean;
|
|
42
|
+
/** Arrastrar la hoja hacia abajo la cierra. @default true */
|
|
43
|
+
draggable?: boolean;
|
|
44
|
+
/** Fracción de la pantalla (0-1) o píxeles. @default 0.9 */
|
|
45
|
+
maxHeight?: number;
|
|
13
46
|
closeAccessibilityLabel?: string;
|
|
47
|
+
containerStyle?: ViewStyle;
|
|
48
|
+
contentStyle?: ViewStyle;
|
|
49
|
+
testID?: string;
|
|
14
50
|
}
|
|
15
51
|
|
|
16
52
|
const useStyles = createStyles((theme) => ({
|
|
@@ -20,76 +56,192 @@ const useStyles = createStyles((theme) => ({
|
|
|
20
56
|
backgroundColor: theme.colors.surface,
|
|
21
57
|
borderTopLeftRadius: theme.tokens.radius.xxl,
|
|
22
58
|
borderTopRightRadius: theme.tokens.radius.xxl,
|
|
23
|
-
paddingHorizontal: theme.tokens.spacing.xl,
|
|
24
59
|
...theme.tokens.shadow.lg,
|
|
25
60
|
},
|
|
26
61
|
handleArea: { paddingVertical: theme.tokens.spacing.md, alignItems: 'center' },
|
|
27
62
|
handle: { width: 40, height: 5, borderRadius: 3, backgroundColor: theme.colors.border },
|
|
63
|
+
header: {
|
|
64
|
+
flexDirection: 'row',
|
|
65
|
+
alignItems: 'center',
|
|
66
|
+
gap: theme.tokens.spacing.md,
|
|
67
|
+
paddingHorizontal: theme.tokens.spacing.xl,
|
|
68
|
+
paddingBottom: theme.tokens.spacing.md,
|
|
69
|
+
},
|
|
70
|
+
headerSpaced: { paddingTop: theme.tokens.spacing.xl },
|
|
71
|
+
title: { flex: 1, fontSize: theme.tokens.fontSize.xl, fontWeight: theme.tokens.fontWeight.bold },
|
|
72
|
+
content: { paddingHorizontal: theme.tokens.spacing.xl },
|
|
73
|
+
scroll: { flexShrink: 1 },
|
|
74
|
+
footer: {
|
|
75
|
+
paddingHorizontal: theme.tokens.spacing.xl,
|
|
76
|
+
paddingTop: theme.tokens.spacing.md,
|
|
77
|
+
borderTopWidth: 1,
|
|
78
|
+
borderTopColor: theme.colors.border,
|
|
79
|
+
},
|
|
80
|
+
footerInline: { paddingTop: theme.tokens.spacing.xl },
|
|
28
81
|
}));
|
|
29
82
|
|
|
30
83
|
const absoluteFill: ViewStyle = { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 };
|
|
31
84
|
|
|
32
85
|
/**
|
|
33
86
|
* BottomSheet
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
87
|
+
* Hoja que entra desde abajo, con arrastre para cerrar (PanResponder, no un
|
|
88
|
+
* gesture-handler externo: la librería es cero dependencias). Header, footer y
|
|
89
|
+
* scroll son opcionales, así que cubre tanto la hoja desnuda como el formulario
|
|
90
|
+
* completo.
|
|
37
91
|
*/
|
|
38
|
-
export const BottomSheet = memo(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
>
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
92
|
+
export const BottomSheet = memo(
|
|
93
|
+
({
|
|
94
|
+
visible,
|
|
95
|
+
onClose,
|
|
96
|
+
children,
|
|
97
|
+
title,
|
|
98
|
+
footer,
|
|
99
|
+
footerPlacement = 'fixed',
|
|
100
|
+
scrollable = true,
|
|
101
|
+
draggable = true,
|
|
102
|
+
maxHeight = 0.9,
|
|
103
|
+
closeAccessibilityLabel = 'Close',
|
|
104
|
+
containerStyle,
|
|
105
|
+
contentStyle,
|
|
106
|
+
testID,
|
|
107
|
+
}: BottomSheetProps) => {
|
|
108
|
+
const styles = useStyles();
|
|
109
|
+
const colors = useColors();
|
|
110
|
+
const insets = useInsets();
|
|
111
|
+
const [mounted, setMounted] = useState(visible);
|
|
112
|
+
const [keyboardOpen, setKeyboardOpen] = useState(false);
|
|
113
|
+
const progress = useRef(new Animated.Value(0)).current;
|
|
114
|
+
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
if (visible) {
|
|
117
|
+
setMounted(true);
|
|
118
|
+
Animated.timing(progress, { toValue: 1, duration: 250, useNativeDriver: true }).start();
|
|
119
|
+
} else {
|
|
120
|
+
Animated.timing(progress, { toValue: 0, duration: 200, useNativeDriver: true }).start(({ finished }) => {
|
|
121
|
+
if (finished) setMounted(false);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}, [visible, progress]);
|
|
125
|
+
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
if (footerPlacement !== 'hide-with-keyboard') return;
|
|
128
|
+
const [show, hide] =
|
|
129
|
+
Platform.OS === 'ios' ? ['keyboardWillShow', 'keyboardWillHide'] : ['keyboardDidShow', 'keyboardDidHide'];
|
|
130
|
+
const subs = [
|
|
131
|
+
Keyboard.addListener(show as 'keyboardDidShow', () => setKeyboardOpen(true)),
|
|
132
|
+
Keyboard.addListener(hide as 'keyboardDidHide', () => setKeyboardOpen(false)),
|
|
133
|
+
];
|
|
134
|
+
return () => subs.forEach((sub) => sub.remove());
|
|
135
|
+
}, [footerPlacement]);
|
|
136
|
+
|
|
137
|
+
const panResponder = useRef(
|
|
138
|
+
PanResponder.create({
|
|
139
|
+
onMoveShouldSetPanResponder: (_, gesture) => gesture.dy > 5 && Math.abs(gesture.dy) > Math.abs(gesture.dx),
|
|
140
|
+
onPanResponderMove: (_, gesture) => {
|
|
141
|
+
if (gesture.dy > 0) progress.setValue(1 - Math.min(gesture.dy, SCREEN_HEIGHT) / SCREEN_HEIGHT);
|
|
142
|
+
},
|
|
143
|
+
onPanResponderRelease: (_, gesture) => {
|
|
144
|
+
if (gesture.dy > DISMISS_THRESHOLD) {
|
|
145
|
+
onClose();
|
|
146
|
+
} else {
|
|
147
|
+
Animated.timing(progress, { toValue: 1, duration: 150, useNativeDriver: true }).start();
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
})
|
|
151
|
+
).current;
|
|
152
|
+
|
|
153
|
+
if (!mounted) return null;
|
|
154
|
+
|
|
155
|
+
// Porcentaje y no píxeles: se mide contra el contenedor, que con el teclado
|
|
156
|
+
// abierto es más bajo, así que la hoja entra siempre entera.
|
|
157
|
+
const resolvedMaxHeight: number | `${number}%` = maxHeight <= 1 ? `${maxHeight * 100}%` : maxHeight;
|
|
158
|
+
const hasHeader = !!title;
|
|
159
|
+
|
|
160
|
+
const scrolledFooter = !!footer && footerPlacement === 'scroll' && (
|
|
161
|
+
<View style={styles.footerInline}>{footer}</View>
|
|
162
|
+
);
|
|
163
|
+
const pinnedFooter =
|
|
164
|
+
!!footer && footerPlacement !== 'scroll' && !(footerPlacement === 'hide-with-keyboard' && keyboardOpen) ? (
|
|
165
|
+
<View style={styles.footer}>{footer}</View>
|
|
166
|
+
) : null;
|
|
167
|
+
|
|
168
|
+
const body = scrollable ? (
|
|
169
|
+
<ScrollView
|
|
170
|
+
style={styles.scroll}
|
|
171
|
+
contentContainerStyle={[styles.content, contentStyle]}
|
|
172
|
+
keyboardShouldPersistTaps="handled"
|
|
173
|
+
showsVerticalScrollIndicator={false}
|
|
174
|
+
>
|
|
175
|
+
{children}
|
|
176
|
+
{scrolledFooter}
|
|
177
|
+
</ScrollView>
|
|
178
|
+
) : (
|
|
179
|
+
<View style={[styles.content, contentStyle]}>
|
|
180
|
+
{children}
|
|
181
|
+
{scrolledFooter}
|
|
91
182
|
</View>
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
return (
|
|
186
|
+
<Modal visible transparent animationType="none" statusBarTranslucent onRequestClose={onClose}>
|
|
187
|
+
{/* La hoja está anclada abajo, así que el teclado la tapa entera: hay
|
|
188
|
+
que empujarla, no ajustar insets adentro del scroll. Una sola
|
|
189
|
+
compensación, la del contenedor. */}
|
|
190
|
+
<KeyboardAvoidingView
|
|
191
|
+
style={styles.container}
|
|
192
|
+
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
|
193
|
+
>
|
|
194
|
+
<Transition value={progress} preset="fade" style={[absoluteFill, styles.backdrop]}>
|
|
195
|
+
{/* Con header, el botón de cerrar ya expone la acción: repetir el
|
|
196
|
+
label acá deja dos elementos con el mismo nombre. */}
|
|
197
|
+
<Pressable
|
|
198
|
+
style={absoluteFill}
|
|
199
|
+
onPress={onClose}
|
|
200
|
+
testID="bottom-sheet-backdrop"
|
|
201
|
+
accessibilityRole={hasHeader ? undefined : 'button'}
|
|
202
|
+
accessibilityLabel={hasHeader ? undefined : closeAccessibilityLabel}
|
|
203
|
+
/>
|
|
204
|
+
</Transition>
|
|
205
|
+
|
|
206
|
+
<Transition
|
|
207
|
+
value={progress}
|
|
208
|
+
preset="slide-up"
|
|
209
|
+
distance={SCREEN_HEIGHT}
|
|
210
|
+
testID={testID}
|
|
211
|
+
style={[
|
|
212
|
+
styles.sheet,
|
|
213
|
+
{ maxHeight: resolvedMaxHeight, paddingBottom: Math.max(insets.bottom, 20) },
|
|
214
|
+
containerStyle,
|
|
215
|
+
]}
|
|
216
|
+
>
|
|
217
|
+
{draggable && (
|
|
218
|
+
<View style={styles.handleArea} testID="bottom-sheet-handle" {...panResponder.panHandlers}>
|
|
219
|
+
<View style={styles.handle} />
|
|
220
|
+
</View>
|
|
221
|
+
)}
|
|
222
|
+
|
|
223
|
+
{hasHeader && (
|
|
224
|
+
<View style={[styles.header, !draggable && styles.headerSpaced]}>
|
|
225
|
+
<Title style={styles.title} numberOfLines={1}>
|
|
226
|
+
{title}
|
|
227
|
+
</Title>
|
|
228
|
+
<IconButton
|
|
229
|
+
iconName="close"
|
|
230
|
+
size={24}
|
|
231
|
+
color={colors.text}
|
|
232
|
+
onPress={onClose}
|
|
233
|
+
accessibilityLabel={closeAccessibilityLabel}
|
|
234
|
+
/>
|
|
235
|
+
</View>
|
|
236
|
+
)}
|
|
237
|
+
|
|
238
|
+
{body}
|
|
239
|
+
|
|
240
|
+
{pinnedFooter}
|
|
241
|
+
</Transition>
|
|
242
|
+
</KeyboardAvoidingView>
|
|
243
|
+
</Modal>
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
);
|
|
95
247
|
BottomSheet.displayName = 'BottomSheet';
|
|
@@ -1,26 +1,44 @@
|
|
|
1
|
-
import React, { memo, useState } from 'react';
|
|
1
|
+
import React, { memo, useCallback, useState } from 'react';
|
|
2
2
|
import { Text, TouchableOpacity, View, type StyleProp, type ViewStyle } from 'react-native';
|
|
3
3
|
import { createStyles } from '../theme/createStyles';
|
|
4
|
-
import { useAllowFontScaling, useColors } from '../theme/UIProvider';
|
|
4
|
+
import { useAllowFontScaling, useColors, useLabels } from '../theme/UIProvider';
|
|
5
5
|
import { Modal } from './Modal';
|
|
6
|
-
import { DatePicker,
|
|
6
|
+
import { DatePicker, type DatePickerProps, type DateRange } from './DatePicker';
|
|
7
7
|
import { Button } from '../atoms/Button';
|
|
8
8
|
|
|
9
9
|
export interface DateFieldProps
|
|
10
10
|
extends Pick<DatePickerProps, 'minDate' | 'maxDate' | 'monthNames' | 'weekdayNames' | 'firstDayOfWeek'> {
|
|
11
11
|
label?: string;
|
|
12
12
|
required?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* 'single' elige una fecha; 'range' elige un intervalo con dos toques.
|
|
15
|
+
* @default 'single'
|
|
16
|
+
*/
|
|
17
|
+
mode?: 'single' | 'range';
|
|
18
|
+
/** Fecha elegida en mode="single". */
|
|
13
19
|
value?: Date;
|
|
14
|
-
|
|
20
|
+
/** Se llama en mode="single". */
|
|
21
|
+
onChange?: (date: Date) => void;
|
|
22
|
+
/** Rango elegido en mode="range". */
|
|
23
|
+
range?: DateRange;
|
|
24
|
+
/** Se llama en mode="range", tanto al fijar el inicio como al cerrar el rango. */
|
|
25
|
+
onRangeChange?: (range: DateRange) => void;
|
|
15
26
|
placeholder?: string;
|
|
16
27
|
error?: string;
|
|
17
28
|
disabled?: boolean;
|
|
18
|
-
/** Formatea la fecha en el campo. @default "
|
|
29
|
+
/** Formatea la fecha en el campo. @default "Month D, YYYY" */
|
|
19
30
|
format?: (date: Date) => string;
|
|
20
|
-
/** Título del modal del calendario. @default
|
|
31
|
+
/** Título del modal del calendario. @default el `selectDate` de `useLabels()` */
|
|
21
32
|
modalTitle?: string;
|
|
33
|
+
/**
|
|
34
|
+
* Si se pasa, el modal muestra un botón que borra la selección y cierra.
|
|
35
|
+
* Sin esto no hay botón: el "Clear" anterior sólo cerraba, sin limpiar nada.
|
|
36
|
+
*/
|
|
37
|
+
onClear?: () => void;
|
|
38
|
+
/** Texto de ese botón. @default el `clear` de `useLabels()` */
|
|
22
39
|
clearLabel?: string;
|
|
23
40
|
containerStyle?: StyleProp<ViewStyle>;
|
|
41
|
+
testID?: string;
|
|
24
42
|
}
|
|
25
43
|
|
|
26
44
|
function defaultFormat(date: Date, monthNames: string[]): string {
|
|
@@ -58,27 +76,67 @@ export const DateField = memo(
|
|
|
58
76
|
({
|
|
59
77
|
label,
|
|
60
78
|
required = false,
|
|
79
|
+
mode = 'single',
|
|
61
80
|
value,
|
|
62
81
|
onChange,
|
|
82
|
+
range,
|
|
83
|
+
onRangeChange,
|
|
63
84
|
placeholder = 'Select a date',
|
|
64
85
|
error,
|
|
65
86
|
disabled = false,
|
|
66
87
|
format,
|
|
67
|
-
modalTitle
|
|
68
|
-
|
|
88
|
+
modalTitle,
|
|
89
|
+
onClear,
|
|
90
|
+
clearLabel,
|
|
69
91
|
containerStyle,
|
|
70
|
-
monthNames
|
|
92
|
+
monthNames,
|
|
93
|
+
testID,
|
|
71
94
|
...pickerProps
|
|
72
95
|
}: DateFieldProps) => {
|
|
73
96
|
const colors = useColors();
|
|
74
97
|
const styles = useStyles();
|
|
98
|
+
const labels = useLabels();
|
|
75
99
|
const allowFontScaling = useAllowFontScaling();
|
|
76
100
|
const [open, setOpen] = useState(false);
|
|
77
101
|
|
|
78
|
-
const
|
|
102
|
+
const months = monthNames ?? labels.months;
|
|
103
|
+
const isRange = mode === 'range';
|
|
104
|
+
|
|
105
|
+
const formatDate = (date: Date) => (format ? format(date) : defaultFormat(date, months));
|
|
106
|
+
|
|
107
|
+
// El calendario se cierra solo cuando ya no falta nada por elegir: en
|
|
108
|
+
// rango, recién cuando el segundo toque cierra el intervalo.
|
|
109
|
+
const handleChange = useCallback(
|
|
110
|
+
(date: Date) => {
|
|
111
|
+
onChange?.(date);
|
|
112
|
+
setOpen(false);
|
|
113
|
+
},
|
|
114
|
+
[onChange]
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
const handleRangeChange = useCallback(
|
|
118
|
+
(next: DateRange) => {
|
|
119
|
+
onRangeChange?.(next);
|
|
120
|
+
if (next.end) setOpen(false);
|
|
121
|
+
},
|
|
122
|
+
[onRangeChange]
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
const handleClear = useCallback(() => {
|
|
126
|
+
onClear?.();
|
|
127
|
+
setOpen(false);
|
|
128
|
+
}, [onClear]);
|
|
129
|
+
|
|
130
|
+
const hasValue = isRange ? !!range?.start : !!value;
|
|
131
|
+
let display = placeholder;
|
|
132
|
+
if (isRange && range?.start) {
|
|
133
|
+
display = range.end ? `${formatDate(range.start)} — ${formatDate(range.end)}` : formatDate(range.start);
|
|
134
|
+
} else if (!isRange && value) {
|
|
135
|
+
display = formatDate(value);
|
|
136
|
+
}
|
|
79
137
|
|
|
80
138
|
return (
|
|
81
|
-
<View style={[styles.container, containerStyle]}>
|
|
139
|
+
<View style={[styles.container, containerStyle]} testID={testID}>
|
|
82
140
|
{!!label && (
|
|
83
141
|
<Text allowFontScaling={allowFontScaling} style={styles.label}>
|
|
84
142
|
{label}
|
|
@@ -95,7 +153,7 @@ export const DateField = memo(
|
|
|
95
153
|
accessibilityLabel={label ?? placeholder}
|
|
96
154
|
accessibilityState={{ disabled, expanded: open }}
|
|
97
155
|
>
|
|
98
|
-
<Text allowFontScaling={allowFontScaling} style={[styles.fieldText, !
|
|
156
|
+
<Text allowFontScaling={allowFontScaling} style={[styles.fieldText, !hasValue && styles.placeholder]}>
|
|
99
157
|
{display}
|
|
100
158
|
</Text>
|
|
101
159
|
<Text allowFontScaling={allowFontScaling} style={{ color: colors.textLight }}>
|
|
@@ -112,19 +170,21 @@ export const DateField = memo(
|
|
|
112
170
|
<Modal
|
|
113
171
|
visible={open}
|
|
114
172
|
onClose={() => setOpen(false)}
|
|
115
|
-
title={modalTitle}
|
|
173
|
+
title={modalTitle ?? labels.selectDate}
|
|
116
174
|
variant="overlay"
|
|
117
175
|
scrollable={false}
|
|
118
|
-
footer={
|
|
176
|
+
footer={
|
|
177
|
+
onClear ? <Button title={clearLabel ?? labels.clear} variant="ghost" onPress={handleClear} /> : undefined
|
|
178
|
+
}
|
|
119
179
|
>
|
|
120
180
|
<DatePicker
|
|
181
|
+
mode={mode}
|
|
121
182
|
value={value}
|
|
122
|
-
|
|
183
|
+
range={range}
|
|
184
|
+
monthNames={months}
|
|
123
185
|
{...pickerProps}
|
|
124
|
-
onChange={
|
|
125
|
-
|
|
126
|
-
setOpen(false);
|
|
127
|
-
}}
|
|
186
|
+
onChange={handleChange}
|
|
187
|
+
onRangeChange={handleRangeChange}
|
|
128
188
|
/>
|
|
129
189
|
</Modal>
|
|
130
190
|
</View>
|