@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,5 +1,6 @@
|
|
|
1
1
|
import { type VariantProps } from "../theme/tw.js";
|
|
2
2
|
import type React from "react";
|
|
3
|
+
import type { AccessibilityProps } from "react-native";
|
|
3
4
|
declare const button: import("tailwind-variants").TVReturnType<{
|
|
4
5
|
variant: {
|
|
5
6
|
primary: "bg-accent";
|
|
@@ -10,18 +11,18 @@ declare const button: import("tailwind-variants").TVReturnType<{
|
|
|
10
11
|
outlineDanger: "bg-transparent border border-danger";
|
|
11
12
|
outlineBlack: "bg-transparent border border-border-strong";
|
|
12
13
|
flat: "bg-text-primary/10";
|
|
13
|
-
link: "bg-transparent
|
|
14
|
+
link: "bg-transparent";
|
|
14
15
|
transparent: "bg-transparent border border-border-light";
|
|
15
16
|
};
|
|
16
17
|
size: {
|
|
17
|
-
small: "py-1.5 px-3 min-h-[
|
|
18
|
-
medium: "py-2.5 px-4 min-h-[
|
|
18
|
+
small: "py-1.5 px-3 min-h-[48px]";
|
|
19
|
+
medium: "py-2.5 px-4 min-h-[48px]";
|
|
19
20
|
large: "py-3 px-5 min-h-[48px]";
|
|
20
21
|
};
|
|
21
22
|
disabled: {
|
|
22
23
|
true: "opacity-70";
|
|
23
24
|
};
|
|
24
|
-
}, undefined, "rounded-md items-center justify-center flex-row min-h-[
|
|
25
|
+
}, undefined, "rounded-md items-center justify-center flex-row min-h-[48px]", {
|
|
25
26
|
variant: {
|
|
26
27
|
primary: "bg-accent";
|
|
27
28
|
accent: "bg-transparent";
|
|
@@ -31,12 +32,12 @@ declare const button: import("tailwind-variants").TVReturnType<{
|
|
|
31
32
|
outlineDanger: "bg-transparent border border-danger";
|
|
32
33
|
outlineBlack: "bg-transparent border border-border-strong";
|
|
33
34
|
flat: "bg-text-primary/10";
|
|
34
|
-
link: "bg-transparent
|
|
35
|
+
link: "bg-transparent";
|
|
35
36
|
transparent: "bg-transparent border border-border-light";
|
|
36
37
|
};
|
|
37
38
|
size: {
|
|
38
|
-
small: "py-1.5 px-3 min-h-[
|
|
39
|
-
medium: "py-2.5 px-4 min-h-[
|
|
39
|
+
small: "py-1.5 px-3 min-h-[48px]";
|
|
40
|
+
medium: "py-2.5 px-4 min-h-[48px]";
|
|
40
41
|
large: "py-3 px-5 min-h-[48px]";
|
|
41
42
|
};
|
|
42
43
|
disabled: {
|
|
@@ -52,12 +53,12 @@ declare const button: import("tailwind-variants").TVReturnType<{
|
|
|
52
53
|
outlineDanger: "bg-transparent border border-danger";
|
|
53
54
|
outlineBlack: "bg-transparent border border-border-strong";
|
|
54
55
|
flat: "bg-text-primary/10";
|
|
55
|
-
link: "bg-transparent
|
|
56
|
+
link: "bg-transparent";
|
|
56
57
|
transparent: "bg-transparent border border-border-light";
|
|
57
58
|
};
|
|
58
59
|
size: {
|
|
59
|
-
small: "py-1.5 px-3 min-h-[
|
|
60
|
-
medium: "py-2.5 px-4 min-h-[
|
|
60
|
+
small: "py-1.5 px-3 min-h-[48px]";
|
|
61
|
+
medium: "py-2.5 px-4 min-h-[48px]";
|
|
61
62
|
large: "py-3 px-5 min-h-[48px]";
|
|
62
63
|
};
|
|
63
64
|
disabled: {
|
|
@@ -128,7 +129,7 @@ declare const buttonIcon: import("tailwind-variants").TVReturnType<{
|
|
|
128
129
|
true: "opacity-70";
|
|
129
130
|
};
|
|
130
131
|
}, undefined>>;
|
|
131
|
-
export interface ButtonProps extends VariantProps<typeof button
|
|
132
|
+
export interface ButtonProps extends VariantProps<typeof button>, AccessibilityProps {
|
|
132
133
|
title: string;
|
|
133
134
|
onPress?: () => void;
|
|
134
135
|
confirm?: string;
|
|
@@ -136,14 +137,16 @@ export interface ButtonProps extends VariantProps<typeof button> {
|
|
|
136
137
|
disabled?: boolean;
|
|
137
138
|
className?: string;
|
|
138
139
|
icon?: React.ReactNode;
|
|
140
|
+
testID?: string;
|
|
139
141
|
}
|
|
140
|
-
export interface ButtonIconProps extends VariantProps<typeof buttonIcon
|
|
142
|
+
export interface ButtonIconProps extends VariantProps<typeof buttonIcon>, AccessibilityProps {
|
|
141
143
|
icon: string;
|
|
142
144
|
onPress?: () => void;
|
|
143
145
|
confirm?: string;
|
|
144
146
|
loading?: boolean;
|
|
145
147
|
disabled?: boolean;
|
|
146
148
|
className?: string;
|
|
149
|
+
testID?: string;
|
|
147
150
|
}
|
|
148
151
|
declare const ButtonWithIcon: React.FC<ButtonProps> & {
|
|
149
152
|
Icon: React.FC<ButtonIconProps>;
|
|
@@ -6,10 +6,9 @@ import { tv } from "../theme/tw.js";
|
|
|
6
6
|
import { Typography } from "../Typography/index.js";
|
|
7
7
|
import { useCallback } from "react";
|
|
8
8
|
import { ActivityIndicator, TouchableOpacity, View } from "react-native";
|
|
9
|
-
import { runOnJS } from "react-native-reanimated";
|
|
10
9
|
// ─── Style variants ───────────────────────────────────────────────────────────
|
|
11
10
|
const button = tv({
|
|
12
|
-
base: "rounded-md items-center justify-center flex-row min-h-[
|
|
11
|
+
base: "rounded-md items-center justify-center flex-row min-h-[48px]",
|
|
13
12
|
variants: {
|
|
14
13
|
variant: {
|
|
15
14
|
primary: "bg-accent",
|
|
@@ -20,12 +19,12 @@ const button = tv({
|
|
|
20
19
|
outlineDanger: "bg-transparent border border-danger",
|
|
21
20
|
outlineBlack: "bg-transparent border border-border-strong",
|
|
22
21
|
flat: "bg-text-primary/10",
|
|
23
|
-
link: "bg-transparent
|
|
22
|
+
link: "bg-transparent",
|
|
24
23
|
transparent: "bg-transparent border border-border-light",
|
|
25
24
|
},
|
|
26
25
|
size: {
|
|
27
|
-
small: "py-1.5 px-3 min-h-[
|
|
28
|
-
medium: "py-2.5 px-4 min-h-[
|
|
26
|
+
small: "py-1.5 px-3 min-h-[48px]",
|
|
27
|
+
medium: "py-2.5 px-4 min-h-[48px]",
|
|
29
28
|
large: "py-3 px-5 min-h-[48px]",
|
|
30
29
|
},
|
|
31
30
|
disabled: {
|
|
@@ -152,7 +151,7 @@ const textVariantMap = {
|
|
|
152
151
|
medium: "button",
|
|
153
152
|
large: "buttonLarge",
|
|
154
153
|
};
|
|
155
|
-
const Button = ({ title, onPress, confirm, variant = "primary", size = "medium", loading = false, disabled = false, className, icon, }) => {
|
|
154
|
+
const Button = ({ title, onPress, confirm, variant = "primary", size = "medium", loading = false, disabled = false, className, icon, testID, accessibilityLabel, accessibilityState, ...accessibilityProps }) => {
|
|
156
155
|
const { show, hide } = useConfirmationContext();
|
|
157
156
|
const textColorClass = textColor({ variant: variant, disabled });
|
|
158
157
|
const loadingColorValue = getLoadingColor(variant);
|
|
@@ -166,22 +165,30 @@ const Button = ({ title, onPress, confirm, variant = "primary", size = "medium",
|
|
|
166
165
|
cancelText: "Cancelar",
|
|
167
166
|
onConfirm: () => {
|
|
168
167
|
hide();
|
|
169
|
-
|
|
168
|
+
onPress();
|
|
170
169
|
},
|
|
171
170
|
onCancel: () => hide(),
|
|
172
171
|
});
|
|
173
172
|
}
|
|
174
173
|
else if (onPress) {
|
|
175
|
-
|
|
174
|
+
onPress();
|
|
176
175
|
}
|
|
177
176
|
}, [onPress, confirm, show, hide]);
|
|
178
177
|
if (variant === "link") {
|
|
179
|
-
return (_jsx(TouchableOpacity, { activeOpacity: 0.7, className: className, disabled: disabled || loading, onPress: handlePress,
|
|
178
|
+
return (_jsx(TouchableOpacity, { activeOpacity: 0.7, className: className, disabled: disabled || loading, onPress: handlePress, ...accessibilityProps, accessibilityLabel: accessibilityLabel ?? title, accessibilityRole: "button", accessibilityState: {
|
|
179
|
+
...accessibilityState,
|
|
180
|
+
disabled: disabled || loading,
|
|
181
|
+
busy: loading,
|
|
182
|
+
}, style: { minHeight: 48, minWidth: 48, justifyContent: "center" }, testID: testID, children: loading ? (_jsx(ActivityIndicator, { color: loadingColorValue, size: "small" })) : (_jsx(Typography.Text, { allowFontScaling: true, className: textColorClass, style: { flexShrink: 1 }, variant: textVariantValue, children: title })) }));
|
|
180
183
|
}
|
|
181
184
|
const emphasize = variant === "primary" || variant === "cta";
|
|
182
|
-
return (_jsx(TouchableOpacity, { activeOpacity: 0.8, className: button({ variant, size, disabled, className }), disabled: disabled || loading, onPress: handlePress,
|
|
185
|
+
return (_jsx(TouchableOpacity, { activeOpacity: 0.8, className: button({ variant, size, disabled, className }), disabled: disabled || loading, onPress: handlePress, ...accessibilityProps, accessibilityLabel: accessibilityLabel ?? title, accessibilityRole: "button", accessibilityState: {
|
|
186
|
+
...accessibilityState,
|
|
187
|
+
disabled: disabled || loading,
|
|
188
|
+
busy: loading,
|
|
189
|
+
}, style: { minHeight: 48, minWidth: 48 }, testID: testID, children: loading ? (_jsx(ActivityIndicator, { color: loadingColorValue, size: "small" })) : (_jsxs(_Fragment, { children: [icon ? _jsx(View, { className: "mr-2", children: icon }) : null, _jsx(Typography.Text, { allowFontScaling: true, className: textColorClass, semibold: emphasize, style: { flexShrink: 1 }, variant: textVariantValue, children: title })] })) }));
|
|
183
190
|
};
|
|
184
|
-
const ButtonIcon = ({ icon, onPress, confirm, variant = "primary", size = "medium", loading = false, disabled = false, className, }) => {
|
|
191
|
+
const ButtonIcon = ({ icon, onPress, confirm, variant = "primary", size = "medium", loading = false, disabled = false, className, testID, accessibilityState, ...accessibilityProps }) => {
|
|
185
192
|
const { show, hide } = useConfirmationContext();
|
|
186
193
|
const iconColorValue = getIconColor(variant, disabled);
|
|
187
194
|
const iconSizeValue = iconSizeMap[size] ?? 20;
|
|
@@ -195,16 +202,20 @@ const ButtonIcon = ({ icon, onPress, confirm, variant = "primary", size = "mediu
|
|
|
195
202
|
cancelText: "Cancelar",
|
|
196
203
|
onConfirm: () => {
|
|
197
204
|
hide();
|
|
198
|
-
|
|
205
|
+
onPress();
|
|
199
206
|
},
|
|
200
207
|
onCancel: () => hide(),
|
|
201
208
|
});
|
|
202
209
|
}
|
|
203
210
|
else if (onPress) {
|
|
204
|
-
|
|
211
|
+
onPress();
|
|
205
212
|
}
|
|
206
213
|
}, [onPress, confirm, show, hide]);
|
|
207
|
-
return (_jsx(TouchableOpacity, { activeOpacity: 0.8, className: buttonIcon({ variant, size, disabled, className }), disabled: disabled || loading, onPress: handlePress,
|
|
214
|
+
return (_jsx(TouchableOpacity, { activeOpacity: 0.8, className: buttonIcon({ variant, size, disabled, className }), disabled: disabled || loading, onPress: handlePress, ...accessibilityProps, accessibilityRole: "button", accessibilityState: {
|
|
215
|
+
...accessibilityState,
|
|
216
|
+
disabled: disabled || loading,
|
|
217
|
+
busy: loading,
|
|
218
|
+
}, style: { minHeight: 48, minWidth: 48 }, testID: testID, children: loading ? (_jsx(ActivityIndicator, { color: loadingColorValue, size: "small" })) : (_jsx(Icon, { color: iconColorValue, name: icon, size: iconSizeValue })) }));
|
|
208
219
|
};
|
|
209
220
|
const ButtonWithIcon = Object.assign(Button, { Icon: ButtonIcon });
|
|
210
221
|
export default ButtonWithIcon;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
export interface DockProps {
|
|
3
|
+
primaryAction: ReactNode;
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
summary?: ReactNode;
|
|
6
|
+
title?: string;
|
|
7
|
+
moreLabel?: string;
|
|
8
|
+
collapseLabel?: string;
|
|
9
|
+
backdropLabel?: string;
|
|
10
|
+
expanded?: boolean;
|
|
11
|
+
defaultExpanded?: boolean;
|
|
12
|
+
onExpandedChange?: (expanded: boolean) => void;
|
|
13
|
+
dismissOnBackdrop?: boolean;
|
|
14
|
+
maxExpandedHeightRatio?: number;
|
|
15
|
+
placement?: "fixed" | "footer";
|
|
16
|
+
className?: string;
|
|
17
|
+
testID?: string;
|
|
18
|
+
}
|
|
19
|
+
declare function Dock({ primaryAction, children, summary, title, moreLabel, collapseLabel, backdropLabel, expanded: controlledExpanded, defaultExpanded, onExpandedChange, dismissOnBackdrop, maxExpandedHeightRatio, placement, className, testID, }: DockProps): import("react").JSX.Element;
|
|
20
|
+
export default Dock;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "nativewind/jsx-runtime";
|
|
2
|
+
/* eslint-disable react-hooks/refs */
|
|
3
|
+
import Icon from "../Display/Display.Icon.js";
|
|
4
|
+
import Pressable from "../primitives/Pressable.js";
|
|
5
|
+
import { themeToVars, useTheme } from "../theme/index.js";
|
|
6
|
+
import { cn } from "../theme/tw.js";
|
|
7
|
+
import { Typography } from "../Typography/index.js";
|
|
8
|
+
import { useReducedMotion } from "../utils/useReducedMotion.js";
|
|
9
|
+
import { vars } from "nativewind";
|
|
10
|
+
import { useEffect, useRef, useState } from "react";
|
|
11
|
+
import { Animated, KeyboardAvoidingView, Modal, PanResponder, Platform, ScrollView, useWindowDimensions, View, } from "react-native";
|
|
12
|
+
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
13
|
+
function Dock({ primaryAction, children, summary, title = "Más opciones", moreLabel = "Más opciones", collapseLabel = "Ocultar opciones", backdropLabel = "Cerrar panel", expanded: controlledExpanded, defaultExpanded = false, onExpandedChange, dismissOnBackdrop = true, maxExpandedHeightRatio = 0.6, placement = "fixed", className, testID, }) {
|
|
14
|
+
const [internalExpanded, setInternalExpanded] = useState(defaultExpanded);
|
|
15
|
+
const expanded = controlledExpanded ?? internalExpanded;
|
|
16
|
+
const theme = useTheme();
|
|
17
|
+
const reducedMotion = useReducedMotion();
|
|
18
|
+
const insets = useSafeAreaInsets();
|
|
19
|
+
const { height } = useWindowDimensions();
|
|
20
|
+
const [barHeight, setBarHeight] = useState(0);
|
|
21
|
+
const translateY = useRef(new Animated.Value(0)).current;
|
|
22
|
+
const requestExpanded = (next) => {
|
|
23
|
+
if (next === expanded)
|
|
24
|
+
return;
|
|
25
|
+
if (controlledExpanded === undefined)
|
|
26
|
+
setInternalExpanded(next);
|
|
27
|
+
onExpandedChange?.(next);
|
|
28
|
+
};
|
|
29
|
+
const gestureRef = useRef({ expanded, requestExpanded, reducedMotion });
|
|
30
|
+
gestureRef.current = { expanded, requestExpanded, reducedMotion };
|
|
31
|
+
const resolvedRatio = Number.isFinite(maxExpandedHeightRatio)
|
|
32
|
+
? Math.min(0.9, Math.max(0.1, maxExpandedHeightRatio))
|
|
33
|
+
: 0.6;
|
|
34
|
+
const maxPanelHeight = Math.max(0, Math.min(height * resolvedRatio, height - insets.top - insets.bottom - barHeight));
|
|
35
|
+
const resetDrag = () => {
|
|
36
|
+
translateY.stopAnimation();
|
|
37
|
+
if (gestureRef.current.reducedMotion) {
|
|
38
|
+
translateY.setValue(0);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
Animated.spring(translateY, {
|
|
42
|
+
toValue: 0,
|
|
43
|
+
useNativeDriver: true,
|
|
44
|
+
damping: 24,
|
|
45
|
+
stiffness: 260,
|
|
46
|
+
}).start();
|
|
47
|
+
};
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
translateY.stopAnimation();
|
|
50
|
+
translateY.setValue(0);
|
|
51
|
+
return () => translateY.stopAnimation();
|
|
52
|
+
}, [expanded, reducedMotion, translateY]);
|
|
53
|
+
const [pan] = useState(() => PanResponder.create({
|
|
54
|
+
onStartShouldSetPanResponder: () => true,
|
|
55
|
+
onPanResponderGrant: () => translateY.stopAnimation(),
|
|
56
|
+
onPanResponderTerminationRequest: () => false,
|
|
57
|
+
onPanResponderMove: (_, gesture) => {
|
|
58
|
+
if (gestureRef.current.expanded)
|
|
59
|
+
translateY.setValue(Math.max(0, gesture.dy));
|
|
60
|
+
},
|
|
61
|
+
onPanResponderRelease: (_, gesture) => {
|
|
62
|
+
const isVertical = Math.abs(gesture.dy) > Math.abs(gesture.dx);
|
|
63
|
+
const shouldChange = gestureRef.current.expanded
|
|
64
|
+
? gesture.dy > 40 || gesture.vy > 0.5
|
|
65
|
+
: gesture.dy < -40 || gesture.vy < -0.5;
|
|
66
|
+
if (isVertical && shouldChange) {
|
|
67
|
+
gestureRef.current.requestExpanded(!gestureRef.current.expanded);
|
|
68
|
+
}
|
|
69
|
+
resetDrag();
|
|
70
|
+
},
|
|
71
|
+
onPanResponderTerminate: resetDrag,
|
|
72
|
+
}));
|
|
73
|
+
const handle = (_jsx(View, { className: "min-h-12 items-center justify-center", ...pan.panHandlers, testID: testID ? `${testID}-handle` : undefined, children: _jsx(View, { className: "h-1 w-10 rounded-full bg-border-strong" }) }));
|
|
74
|
+
const bar = (_jsxs(View, { className: cn("gap-3 border-t border-border-light bg-bg-card px-4 pb-2", className), onLayout: (event) => setBarHeight(event.nativeEvent.layout.height), testID: testID ? `${testID}-bar` : undefined, children: [!expanded ? handle : null, summary, _jsxs(View, { className: "flex-row items-center gap-3", children: [_jsxs(Pressable, { accessibilityLabel: expanded ? collapseLabel : moreLabel, accessibilityRole: "button", accessibilityState: { expanded }, className: "min-h-12 flex-1 flex-row items-center justify-center gap-1 rounded-lg bg-bg-muted px-3", onPress: () => requestExpanded(!expanded), testID: testID ? `${testID}-more` : undefined, children: [_jsx(Typography.Text, { className: "text-text-primary", variant: "buttonSmall", children: expanded ? collapseLabel : moreLabel }), _jsx(Icon, { color: theme.icon.secondary, name: expanded ? "chevron-down" : "chevron-up", size: 16 })] }), _jsx(View, { className: "flex-1", children: primaryAction })] })] }));
|
|
75
|
+
return (_jsxs(_Fragment, { children: [_jsx(View, { className: cn(placement === "fixed" && "absolute bottom-0 left-0 right-0 z-20"), style: expanded ? { height: barHeight } : undefined, testID: testID, children: !expanded ? bar : null }), _jsx(Modal, { animationType: "none", onRequestClose: () => requestExpanded(false), presentationStyle: "overFullScreen", testID: testID ? `${testID}-modal` : undefined, transparent: true, visible: expanded, children: _jsx(KeyboardAvoidingView, { behavior: Platform.OS === "ios" ? "padding" : "height", style: { flex: 1 }, testID: testID ? `${testID}-keyboard` : undefined, children: _jsxs(View, { accessibilityViewIsModal: true, style: { flex: 1, justifyContent: "flex-end" }, children: [_jsx(Pressable, { accessibilityLabel: backdropLabel, accessibilityRole: "button", className: "absolute inset-0 bg-black/50", disabled: !dismissOnBackdrop, onPress: dismissOnBackdrop ? () => requestExpanded(false) : undefined, testID: testID ? `${testID}-backdrop` : undefined }), _jsxs(View, { onAccessibilityEscape: () => requestExpanded(false), style: [
|
|
76
|
+
vars(themeToVars(theme)),
|
|
77
|
+
{
|
|
78
|
+
paddingBottom: insets.bottom,
|
|
79
|
+
backgroundColor: theme.bg.card,
|
|
80
|
+
maxHeight: height - insets.top,
|
|
81
|
+
flexShrink: 1,
|
|
82
|
+
},
|
|
83
|
+
], children: [_jsx(View, { style: { overflow: "hidden", flexShrink: 1 }, children: _jsxs(Animated.View, { style: {
|
|
84
|
+
maxHeight: maxPanelHeight,
|
|
85
|
+
flexShrink: 1,
|
|
86
|
+
transform: [{ translateY }],
|
|
87
|
+
}, testID: testID ? `${testID}-expanded-content` : undefined, children: [handle, _jsxs(ScrollView, { contentContainerStyle: { paddingHorizontal: 16, paddingBottom: 16 }, keyboardShouldPersistTaps: "handled", nestedScrollEnabled: true, style: { flexShrink: 1 }, testID: testID ? `${testID}-scroll` : undefined, children: [title ? (_jsx(Typography.Text, { accessibilityRole: "header", className: "pb-2 text-text-primary", semibold: true, variant: "subtitle", children: title })) : null, children] })] }) }), bar] })] }) }) })] }));
|
|
88
|
+
}
|
|
89
|
+
export default Dock;
|
|
@@ -5,6 +5,7 @@ export interface FABProps {
|
|
|
5
5
|
position?: "bottom-right" | "bottom-left";
|
|
6
6
|
className?: string;
|
|
7
7
|
testID?: string;
|
|
8
|
+
accessibilityLabel?: string;
|
|
8
9
|
}
|
|
9
|
-
declare function FAB({ icon, onPress, label, position, className, testID, }: FABProps): import("react").JSX.Element;
|
|
10
|
+
declare function FAB({ icon, onPress, label, position, className, testID, accessibilityLabel, }: FABProps): import("react").JSX.Element;
|
|
10
11
|
export default FAB;
|
|
@@ -5,13 +5,15 @@ import { theme } from "../theme/index.js";
|
|
|
5
5
|
import { cn } from "../theme/tw.js";
|
|
6
6
|
import { Typography } from "../Typography/index.js";
|
|
7
7
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
8
|
-
function FAB({ icon, onPress, label, position = "bottom-right", className, testID, }) {
|
|
8
|
+
function FAB({ icon, onPress, label, position = "bottom-right", className, testID, accessibilityLabel, }) {
|
|
9
9
|
const insets = useSafeAreaInsets();
|
|
10
|
-
return (_jsxs(Pressable, { accessibilityLabel: label ?? "Acción", accessibilityRole: "button", className: cn("absolute flex-row items-center justify-center rounded-full bg-primary", label ? "h-14 px-5 gap-2" : "w-14 h-14", className), onPress: onPress, style: {
|
|
10
|
+
return (_jsxs(Pressable, { accessibilityLabel: accessibilityLabel ?? label ?? "Acción", accessibilityRole: "button", className: cn("absolute flex-row items-center justify-center rounded-full bg-primary", label ? "min-h-14 px-5 py-3 gap-2" : "w-14 h-14", className), onPress: onPress, style: {
|
|
11
|
+
minHeight: 56,
|
|
12
|
+
minWidth: 56,
|
|
11
13
|
bottom: insets.bottom + 16,
|
|
12
14
|
right: position === "bottom-right" ? 16 : undefined,
|
|
13
15
|
left: position === "bottom-left" ? 16 : undefined,
|
|
14
16
|
...theme.shadow.lg,
|
|
15
|
-
}, testID: testID, children: [_jsx(Icon, { color: theme.icon.inverse, name: icon, size: 24 }), label ? (_jsx(Typography.Text, { className: "text-text-on-primary font-semibold", children: label })) : null] }));
|
|
17
|
+
}, testID: testID, children: [_jsx(Icon, { color: theme.icon.inverse, name: icon, size: 24 }), label ? (_jsx(Typography.Text, { className: "text-text-on-primary font-semibold", style: { flexShrink: 1 }, children: label })) : null] }));
|
|
16
18
|
}
|
|
17
19
|
export default FAB;
|
|
@@ -3,8 +3,16 @@ export interface FilterButtonProps {
|
|
|
3
3
|
title: string;
|
|
4
4
|
active: boolean;
|
|
5
5
|
onPress: () => void;
|
|
6
|
+
variant?: "default" | "mobile";
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
className?: string;
|
|
9
|
+
testID?: string;
|
|
6
10
|
}
|
|
7
11
|
export declare const FilterButton: FC<FilterButtonProps>;
|
|
8
|
-
export
|
|
12
|
+
export interface FilterButtonGroupProps {
|
|
9
13
|
children: ReactNode;
|
|
10
|
-
|
|
14
|
+
layout?: "row" | "scroll" | "wrap";
|
|
15
|
+
className?: string;
|
|
16
|
+
testID?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function FilterButtonGroup({ children, layout, className, testID, }: FilterButtonGroupProps): import("react").JSX.Element;
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import { jsx as _jsx } from "nativewind/jsx-runtime";
|
|
2
|
-
import
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "nativewind/jsx-runtime";
|
|
2
|
+
import Icon from "../Display/Display.Icon.js";
|
|
3
|
+
import Pressable from "../primitives/Pressable.js";
|
|
4
|
+
import { useTheme } from "../theme/index.js";
|
|
5
|
+
import { cn, tv } from "../theme/tw.js";
|
|
3
6
|
import { Typography } from "../Typography/index.js";
|
|
4
|
-
import { TouchableOpacity, View } from "react-native";
|
|
7
|
+
import { ScrollView, TouchableOpacity, View } from "react-native";
|
|
5
8
|
const filterButton = tv({
|
|
6
9
|
base: "rounded-lg items-center justify-center py-2 px-3 min-h-[36px]",
|
|
7
10
|
variants: {
|
|
@@ -22,7 +25,19 @@ const filterText = tv({
|
|
|
22
25
|
},
|
|
23
26
|
defaultVariants: { active: false },
|
|
24
27
|
});
|
|
25
|
-
export const FilterButton = ({ title, active, onPress
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
export const FilterButton = ({ title, active, onPress, variant = "default", disabled = false, className, testID, }) => {
|
|
29
|
+
const theme = useTheme();
|
|
30
|
+
if (variant === "mobile") {
|
|
31
|
+
return (_jsxs(Pressable, { accessibilityLabel: title, accessibilityRole: "button", accessibilityState: { selected: active, disabled }, activeOpacity: 1 - theme.state.press, className: cn("flex-row items-center self-start rounded-full px-3 py-2 gap-2 max-w-full", active ? "bg-container-primary" : "bg-bg-muted", className), disabled: disabled, onPress: onPress, style: {
|
|
32
|
+
minHeight: 48,
|
|
33
|
+
minWidth: 48,
|
|
34
|
+
}, testID: testID, children: [active ? (_jsx(View, { accessibilityElementsHidden: true, importantForAccessibility: "no-hide-descendants", children: _jsx(Icon, { color: theme.container.onPrimary, name: "checkmark", size: 18 }) })) : null, _jsx(Typography.Text, { className: cn("flex-shrink", active ? "text-container-on-primary" : "text-text-primary"), variant: "bodyLarge", children: title })] }));
|
|
35
|
+
}
|
|
36
|
+
return (_jsx(TouchableOpacity, { accessibilityLabel: title, accessibilityRole: "button", accessibilityState: { selected: active, disabled }, activeOpacity: 0.8, className: filterButton({ active, className }), disabled: disabled, onPress: onPress, testID: testID, children: _jsx(Typography.Text, { className: filterText({ active }), variant: "buttonSmall", children: title }) }));
|
|
37
|
+
};
|
|
38
|
+
export function FilterButtonGroup({ children, layout = "row", className, testID, }) {
|
|
39
|
+
if (layout === "scroll") {
|
|
40
|
+
return (_jsx(ScrollView, { className: cn("py-1", className), contentContainerStyle: { gap: 8 }, horizontal: true, keyboardShouldPersistTaps: "handled", showsHorizontalScrollIndicator: false, testID: testID, children: children }));
|
|
41
|
+
}
|
|
42
|
+
return (_jsx(View, { className: cn("flex-row gap-2 p-3", layout === "wrap" && "flex-wrap", className), testID: testID, children: children }));
|
|
28
43
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type VariantProps } from "../theme/tw.js";
|
|
2
2
|
import { IconType } from "../utils/icon.js";
|
|
3
|
+
import type { AccessibilityProps } from "react-native";
|
|
3
4
|
declare const iconButton: import("tailwind-variants").TVReturnType<{
|
|
4
5
|
variant: {
|
|
5
6
|
primary: "bg-primary";
|
|
@@ -55,13 +56,14 @@ declare const iconButton: import("tailwind-variants").TVReturnType<{
|
|
|
55
56
|
true: "opacity-50";
|
|
56
57
|
};
|
|
57
58
|
}, undefined>>;
|
|
58
|
-
export interface IconButtonProps extends VariantProps<typeof iconButton
|
|
59
|
+
export interface IconButtonProps extends VariantProps<typeof iconButton>, AccessibilityProps {
|
|
59
60
|
icon?: string;
|
|
60
61
|
type?: IconType;
|
|
61
62
|
onPress: () => void;
|
|
62
63
|
loading?: boolean;
|
|
63
64
|
iconColor?: string;
|
|
64
65
|
className?: string;
|
|
66
|
+
testID?: string;
|
|
65
67
|
}
|
|
66
|
-
declare function IconButton({ icon, type, onPress, variant, size, loading, disabled, rounded, iconColor, className, }: IconButtonProps): import("react").JSX.Element;
|
|
68
|
+
declare function IconButton({ icon, type, onPress, variant, size, loading, disabled, rounded, iconColor, className, testID, accessibilityState, ...accessibilityProps }: IconButtonProps): import("react").JSX.Element;
|
|
67
69
|
export default IconButton;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx } from "nativewind/jsx-runtime";
|
|
2
|
-
import
|
|
2
|
+
import Icon from "../Display/Display.Icon.js";
|
|
3
3
|
import { theme } from "../theme/index.js";
|
|
4
4
|
import { tv } from "../theme/tw.js";
|
|
5
5
|
import { ActivityIndicator, TouchableOpacity } from "react-native";
|
|
@@ -31,8 +31,12 @@ const iconButton = tv({
|
|
|
31
31
|
disabled: false,
|
|
32
32
|
},
|
|
33
33
|
});
|
|
34
|
-
function IconButton({ icon, type = "feather", onPress, variant = "ghost", size = "medium", loading = false, disabled = false, rounded = false, iconColor, className, }) {
|
|
34
|
+
function IconButton({ icon, type = "feather", onPress, variant = "ghost", size = "medium", loading = false, disabled = false, rounded = false, iconColor, className, testID, accessibilityState, ...accessibilityProps }) {
|
|
35
35
|
const resolvedColor = iconColor ?? theme.text.accent;
|
|
36
|
-
return (_jsx(TouchableOpacity, { activeOpacity: 0.7, className: iconButton({ variant, size, rounded, disabled, className }), disabled: disabled || loading, onPress: onPress,
|
|
36
|
+
return (_jsx(TouchableOpacity, { activeOpacity: 0.7, className: iconButton({ variant, size, rounded, disabled, className }), disabled: disabled || loading, onPress: onPress, ...accessibilityProps, accessibilityRole: "button", accessibilityState: {
|
|
37
|
+
...accessibilityState,
|
|
38
|
+
disabled: disabled || loading,
|
|
39
|
+
busy: loading,
|
|
40
|
+
}, style: { minHeight: 48, minWidth: 48 }, testID: testID, children: loading ? (_jsx(ActivityIndicator, { color: resolvedColor, size: "small" })) : (_jsx(Icon, { color: resolvedColor, name: icon, size: 24, type: type })) }));
|
|
37
41
|
}
|
|
38
42
|
export default IconButton;
|
package/dist/Actions/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import Dock from "./Actions.Dock.js";
|
|
1
2
|
import FAB from "./Actions.FAB.js";
|
|
2
3
|
import { FilterButtonGroup } from "./Actions.FilterButton.js";
|
|
3
4
|
import IconButton from "./Actions.IconButton.js";
|
|
@@ -6,17 +7,20 @@ export declare const Actions: {
|
|
|
6
7
|
Button: import("react").FC<import("./Actions.Button.js").ButtonProps> & {
|
|
7
8
|
Icon: import("react").FC<import("./Actions.Button.js").ButtonIconProps>;
|
|
8
9
|
};
|
|
10
|
+
/** Compact primary-action dock with additional options in a dismissible sheet. */
|
|
11
|
+
Dock: typeof Dock;
|
|
9
12
|
/** Small icon-only tap target — ghost, primary, secondary, tertiary variants. */
|
|
10
13
|
IconButton: typeof IconButton;
|
|
11
14
|
/** Floating Action Button — fixed bottom-right/left, label-optional. */
|
|
12
15
|
FAB: typeof FAB;
|
|
13
16
|
/** Chip-style active/inactive toggle — use inside `Actions.FilterButtonGroup`. */
|
|
14
17
|
FilterButton: import("react").FC<import("./Actions.FilterButton.js").FilterButtonProps>;
|
|
15
|
-
/**
|
|
18
|
+
/** Filter chip layout; opt into horizontal scrolling or wrapping with `layout`. */
|
|
16
19
|
FilterButtonGroup: typeof FilterButtonGroup;
|
|
17
20
|
};
|
|
18
21
|
export type { ButtonIconProps, ButtonProps } from "./Actions.Button.js";
|
|
22
|
+
export type { DockProps } from "./Actions.Dock.js";
|
|
19
23
|
export type { FABProps } from "./Actions.FAB.js";
|
|
20
|
-
export type { FilterButtonProps } from "./Actions.FilterButton.js";
|
|
24
|
+
export type { FilterButtonGroupProps, FilterButtonProps, } from "./Actions.FilterButton.js";
|
|
21
25
|
export type { IconButtonProps } from "./Actions.IconButton.js";
|
|
22
26
|
export default Actions;
|
package/dist/Actions/index.js
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import ButtonWithIcon from "./Actions.Button.js";
|
|
2
|
+
import Dock from "./Actions.Dock.js";
|
|
2
3
|
import FAB from "./Actions.FAB.js";
|
|
3
4
|
import { FilterButton, FilterButtonGroup, } from "./Actions.FilterButton.js";
|
|
4
5
|
import IconButton from "./Actions.IconButton.js";
|
|
5
6
|
export const Actions = {
|
|
6
7
|
/** Primary action button. The filled one is gold; navy is text. Supports confirm dialog. */
|
|
7
8
|
Button: ButtonWithIcon,
|
|
9
|
+
/** Compact primary-action dock with additional options in a dismissible sheet. */
|
|
10
|
+
Dock,
|
|
8
11
|
/** Small icon-only tap target — ghost, primary, secondary, tertiary variants. */
|
|
9
12
|
IconButton,
|
|
10
13
|
/** Floating Action Button — fixed bottom-right/left, label-optional. */
|
|
11
14
|
FAB,
|
|
12
15
|
/** Chip-style active/inactive toggle — use inside `Actions.FilterButtonGroup`. */
|
|
13
16
|
FilterButton,
|
|
14
|
-
/**
|
|
17
|
+
/** Filter chip layout; opt into horizontal scrolling or wrapping with `layout`. */
|
|
15
18
|
FilterButtonGroup,
|
|
16
19
|
};
|
|
17
20
|
export default Actions;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "nativewind/jsx-runtime";
|
|
2
2
|
/* eslint-disable react-hooks/refs */
|
|
3
3
|
import { theme } from "../../theme/index.js";
|
|
4
|
-
import {
|
|
4
|
+
import { useSheetTranslation } from "../../utils/useSheetTranslation.js";
|
|
5
|
+
import { createContext, useContext, useRef } from "react";
|
|
5
6
|
import { Animated, Dimensions, Modal, PanResponder, Pressable, ScrollView, Text, View, } from "react-native";
|
|
6
7
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
7
8
|
const BottomSheetContext = createContext(null);
|
|
@@ -98,27 +99,17 @@ const SIZE_MAP = {
|
|
|
98
99
|
function Root({ visible, onClose, size = "auto", dismissible = true, children }) {
|
|
99
100
|
const insets = useSafeAreaInsets();
|
|
100
101
|
const screenHeight = Dimensions.get("window").height;
|
|
101
|
-
const translateY =
|
|
102
|
+
const { translateY, reducedMotion } = useSheetTranslation(visible, screenHeight);
|
|
103
|
+
const gesture = useRef({ dismissible, onClose, reducedMotion });
|
|
104
|
+
gesture.current = { dismissible, onClose, reducedMotion };
|
|
102
105
|
const maxHeight = size === "auto" ? screenHeight * 0.9 : screenHeight * SIZE_MAP[size];
|
|
103
106
|
const minHeight = size === "auto" ? 0 : screenHeight * SIZE_MAP[size];
|
|
104
|
-
useEffect(() => {
|
|
105
|
-
if (visible) {
|
|
106
|
-
Animated.spring(translateY, {
|
|
107
|
-
toValue: 0,
|
|
108
|
-
useNativeDriver: true,
|
|
109
|
-
damping: 20,
|
|
110
|
-
stiffness: 300,
|
|
111
|
-
}).start();
|
|
112
|
-
}
|
|
113
|
-
else {
|
|
114
|
-
translateY.setValue(screenHeight);
|
|
115
|
-
}
|
|
116
|
-
}, [visible, screenHeight, translateY]);
|
|
117
107
|
const panResponder = useRef(PanResponder.create({
|
|
118
|
-
onStartShouldSetPanResponder: () => dismissible,
|
|
108
|
+
onStartShouldSetPanResponder: () => gesture.current.dismissible,
|
|
119
109
|
onMoveShouldSetPanResponder: (_, gestureState) => {
|
|
120
|
-
return dismissible && Math.abs(gestureState.dy) > 10;
|
|
110
|
+
return gesture.current.dismissible && Math.abs(gestureState.dy) > 10;
|
|
121
111
|
},
|
|
112
|
+
onPanResponderGrant: () => translateY.stopAnimation(),
|
|
122
113
|
onPanResponderMove: (_, gestureState) => {
|
|
123
114
|
if (gestureState.dy > 0) {
|
|
124
115
|
translateY.setValue(gestureState.dy);
|
|
@@ -126,36 +117,33 @@ function Root({ visible, onClose, size = "auto", dismissible = true, children })
|
|
|
126
117
|
},
|
|
127
118
|
onPanResponderRelease: (_, gestureState) => {
|
|
128
119
|
if (gestureState.dy > 100 || gestureState.vy > 0.5) {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
duration: 200,
|
|
132
|
-
useNativeDriver: true,
|
|
133
|
-
}).start(() => onClose());
|
|
120
|
+
translateY.stopAnimation();
|
|
121
|
+
gesture.current.onClose();
|
|
134
122
|
}
|
|
135
123
|
else {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
124
|
+
if (gesture.current.reducedMotion)
|
|
125
|
+
translateY.setValue(0);
|
|
126
|
+
else
|
|
127
|
+
Animated.spring(translateY, {
|
|
128
|
+
toValue: 0,
|
|
129
|
+
useNativeDriver: true,
|
|
130
|
+
damping: 20,
|
|
131
|
+
stiffness: 300,
|
|
132
|
+
}).start();
|
|
142
133
|
}
|
|
143
134
|
},
|
|
144
135
|
})).current;
|
|
145
136
|
function handleBackdropPress() {
|
|
146
137
|
if (dismissible) {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
duration: 200,
|
|
150
|
-
useNativeDriver: true,
|
|
151
|
-
}).start(() => onClose());
|
|
138
|
+
translateY.stopAnimation();
|
|
139
|
+
onClose();
|
|
152
140
|
}
|
|
153
141
|
}
|
|
154
142
|
const contextValue = {
|
|
155
143
|
close: onClose,
|
|
156
144
|
panHandlers: panResponder.panHandlers,
|
|
157
145
|
};
|
|
158
|
-
return (_jsx(Modal, { animationType: "fade", onRequestClose: onClose, presentationStyle: "overFullScreen", transparent: true, visible: visible, children: _jsxs(View, { className: "flex-1 bg-black/50", children: [_jsx(Pressable, { className: "flex-1", onPress: handleBackdropPress }), _jsx(Animated.View, { className: "rounded-t-3xl overflow-hidden", style: {
|
|
146
|
+
return (_jsx(Modal, { animationType: reducedMotion ? "none" : "fade", onRequestClose: onClose, presentationStyle: "overFullScreen", transparent: true, visible: visible, children: _jsxs(View, { className: "flex-1 bg-black/50", children: [_jsx(Pressable, { className: "flex-1", onPress: handleBackdropPress }), _jsx(Animated.View, { className: "rounded-t-3xl overflow-hidden", style: {
|
|
159
147
|
backgroundColor: theme.bg.popover,
|
|
160
148
|
maxHeight,
|
|
161
149
|
minHeight: minHeight || undefined,
|
|
@@ -12,6 +12,7 @@ export interface ActionSheetProps {
|
|
|
12
12
|
title?: string;
|
|
13
13
|
message?: string;
|
|
14
14
|
cancelLabel?: string;
|
|
15
|
+
testID?: string;
|
|
15
16
|
}
|
|
16
|
-
declare function ActionSheet({ visible, onClose, actions, title, message, cancelLabel, }: ActionSheetProps): import("react").JSX.Element;
|
|
17
|
+
declare function ActionSheet({ visible, onClose, actions, title, message, cancelLabel, testID, }: ActionSheetProps): import("react").JSX.Element;
|
|
17
18
|
export default ActionSheet;
|