@webority-technologies/mobile 0.0.15 → 0.0.21
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/lib/commonjs/components/Accordion/Accordion.js +60 -19
- package/lib/commonjs/components/AppBar/AppBar.js +29 -20
- package/lib/commonjs/components/Avatar/Avatar.js +38 -8
- package/lib/commonjs/components/Badge/Badge.js +66 -4
- package/lib/commonjs/components/Banner/Banner.js +146 -66
- package/lib/commonjs/components/BottomNavigation/BottomNavigation.js +37 -15
- package/lib/commonjs/components/BottomSheet/BottomSheet.js +78 -53
- package/lib/commonjs/components/Button/Button.js +12 -5
- package/lib/commonjs/components/Card/Card.js +106 -16
- package/lib/commonjs/components/Carousel/Carousel.js +66 -12
- package/lib/commonjs/components/Checkbox/Checkbox.js +11 -7
- package/lib/commonjs/components/Chip/Chip.js +44 -12
- package/lib/commonjs/components/DatePicker/DatePicker.js +185 -76
- package/lib/commonjs/components/DateRangePicker/DateRangePicker.js +133 -59
- package/lib/commonjs/components/Dialog/Dialog.js +16 -10
- package/lib/commonjs/components/Drawer/Drawer.js +13 -10
- package/lib/commonjs/components/FieldBase/FieldBase.js +494 -0
- package/lib/commonjs/components/FieldBase/index.js +32 -0
- package/lib/commonjs/components/FloatingActionButton/FloatingActionButton.js +69 -44
- package/lib/commonjs/components/ForceUpdateDialog/ForceUpdateDialog.js +8 -2
- package/lib/commonjs/components/FormField/FormField.js +3 -2
- package/lib/commonjs/components/ImageGallery/ImageGallery.js +132 -44
- package/lib/commonjs/components/Input/Input.js +144 -181
- package/lib/commonjs/components/ListItem/ListItem.js +90 -11
- package/lib/commonjs/components/Modal/Modal.js +55 -27
- package/lib/commonjs/components/NumberInput/NumberInput.js +60 -106
- package/lib/commonjs/components/OTPInput/OTPInput.js +65 -58
- package/lib/commonjs/components/PickerTrigger/PickerTrigger.js +185 -0
- package/lib/commonjs/components/{AppIcon → PickerTrigger}/index.js +4 -4
- package/lib/commonjs/components/ProgressBar/ProgressBar.js +19 -11
- package/lib/commonjs/components/Radio/Radio.js +11 -6
- package/lib/commonjs/components/Rating/Rating.js +85 -19
- package/lib/commonjs/components/SearchBar/SearchBar.js +90 -107
- package/lib/commonjs/components/SegmentedControl/SegmentedControl.js +22 -11
- package/lib/commonjs/components/Select/Select.js +62 -91
- package/lib/commonjs/components/Skeleton/Skeleton.js +131 -174
- package/lib/commonjs/components/Skeleton/SkeletonClock.js +117 -0
- package/lib/commonjs/components/Skeleton/SkeletonContent.js +164 -81
- package/lib/commonjs/components/Skeleton/SkeletonProvider.js +72 -10
- package/lib/commonjs/components/Skeleton/index.js +17 -16
- package/lib/commonjs/components/Slider/Slider.js +44 -25
- package/lib/commonjs/components/Stepper/Stepper.js +199 -29
- package/lib/commonjs/components/Swipeable/Swipeable.js +36 -19
- package/lib/commonjs/components/Switch/Switch.js +9 -2
- package/lib/commonjs/components/Tabs/Tabs.js +84 -21
- package/lib/commonjs/components/TimePicker/TimePicker.js +123 -45
- package/lib/commonjs/components/Toast/Toast.js +27 -16
- package/lib/commonjs/components/Tooltip/Tooltip.js +56 -32
- package/lib/commonjs/components/index.js +37 -37
- package/lib/commonjs/theme/tokens.js +55 -7
- package/lib/module/components/Accordion/Accordion.js +61 -20
- package/lib/module/components/AppBar/AppBar.js +29 -20
- package/lib/module/components/Avatar/Avatar.js +39 -9
- package/lib/module/components/Badge/Badge.js +67 -5
- package/lib/module/components/Banner/Banner.js +147 -67
- package/lib/module/components/BottomNavigation/BottomNavigation.js +37 -15
- package/lib/module/components/BottomSheet/BottomSheet.js +80 -55
- package/lib/module/components/Button/Button.js +12 -5
- package/lib/module/components/Card/Card.js +107 -17
- package/lib/module/components/Carousel/Carousel.js +67 -13
- package/lib/module/components/Checkbox/Checkbox.js +11 -7
- package/lib/module/components/Chip/Chip.js +45 -13
- package/lib/module/components/DatePicker/DatePicker.js +185 -76
- package/lib/module/components/DateRangePicker/DateRangePicker.js +134 -60
- package/lib/module/components/Dialog/Dialog.js +16 -10
- package/lib/module/components/Drawer/Drawer.js +13 -10
- package/lib/module/components/FieldBase/FieldBase.js +485 -0
- package/lib/module/components/FieldBase/index.js +4 -0
- package/lib/module/components/FloatingActionButton/FloatingActionButton.js +69 -44
- package/lib/module/components/ForceUpdateDialog/ForceUpdateDialog.js +8 -2
- package/lib/module/components/FormField/FormField.js +3 -2
- package/lib/module/components/ImageGallery/ImageGallery.js +128 -40
- package/lib/module/components/Input/Input.js +144 -179
- package/lib/module/components/ListItem/ListItem.js +91 -12
- package/lib/module/components/Modal/Modal.js +55 -27
- package/lib/module/components/NumberInput/NumberInput.js +60 -106
- package/lib/module/components/OTPInput/OTPInput.js +65 -58
- package/lib/module/components/PickerTrigger/PickerTrigger.js +181 -0
- package/lib/module/components/PickerTrigger/index.js +4 -0
- package/lib/module/components/ProgressBar/ProgressBar.js +19 -11
- package/lib/module/components/Radio/Radio.js +11 -6
- package/lib/module/components/Rating/Rating.js +86 -20
- package/lib/module/components/SearchBar/SearchBar.js +90 -107
- package/lib/module/components/SegmentedControl/SegmentedControl.js +22 -11
- package/lib/module/components/Select/Select.js +62 -91
- package/lib/module/components/Skeleton/Skeleton.js +135 -175
- package/lib/module/components/Skeleton/SkeletonClock.js +110 -0
- package/lib/module/components/Skeleton/SkeletonContent.js +167 -84
- package/lib/module/components/Skeleton/SkeletonProvider.js +71 -10
- package/lib/module/components/Skeleton/index.js +3 -2
- package/lib/module/components/Slider/Slider.js +44 -25
- package/lib/module/components/Stepper/Stepper.js +201 -31
- package/lib/module/components/Swipeable/Swipeable.js +36 -19
- package/lib/module/components/Switch/Switch.js +9 -2
- package/lib/module/components/Tabs/Tabs.js +84 -21
- package/lib/module/components/TimePicker/TimePicker.js +123 -45
- package/lib/module/components/Toast/Toast.js +27 -16
- package/lib/module/components/Tooltip/Tooltip.js +56 -32
- package/lib/module/components/index.js +2 -2
- package/lib/module/theme/tokens.js +55 -7
- package/lib/typescript/commonjs/components/Accordion/Accordion.d.ts +10 -5
- package/lib/typescript/commonjs/components/AppBar/AppBar.d.ts +8 -0
- package/lib/typescript/commonjs/components/Avatar/Avatar.d.ts +12 -6
- package/lib/typescript/commonjs/components/Badge/Badge.d.ts +7 -6
- package/lib/typescript/commonjs/components/Banner/Banner.d.ts +17 -6
- package/lib/typescript/commonjs/components/BottomSheet/BottomSheet.d.ts +7 -0
- package/lib/typescript/commonjs/components/Card/Card.d.ts +17 -6
- package/lib/typescript/commonjs/components/Carousel/Carousel.d.ts +7 -6
- package/lib/typescript/commonjs/components/Checkbox/Checkbox.d.ts +9 -1
- package/lib/typescript/commonjs/components/Chip/Chip.d.ts +13 -6
- package/lib/typescript/commonjs/components/DatePicker/DatePicker.d.ts +38 -3
- package/lib/typescript/commonjs/components/DateRangePicker/DateRangePicker.d.ts +36 -3
- package/lib/typescript/commonjs/components/Dialog/Dialog.d.ts +13 -1
- package/lib/typescript/commonjs/components/FieldBase/FieldBase.d.ts +172 -0
- package/lib/typescript/commonjs/components/FieldBase/index.d.ts +3 -0
- package/lib/typescript/commonjs/components/FloatingActionButton/FloatingActionButton.d.ts +8 -6
- package/lib/typescript/commonjs/components/FloatingActionButton/index.d.ts +1 -1
- package/lib/typescript/commonjs/components/ForceUpdateDialog/ForceUpdateDialog.d.ts +7 -0
- package/lib/typescript/commonjs/components/FormField/FormField.d.ts +7 -0
- package/lib/typescript/commonjs/components/ImageGallery/ImageGallery.d.ts +6 -4
- package/lib/typescript/commonjs/components/Input/Input.d.ts +7 -1
- package/lib/typescript/commonjs/components/ListItem/ListItem.d.ts +13 -6
- package/lib/typescript/commonjs/components/NumberInput/NumberInput.d.ts +3 -0
- package/lib/typescript/commonjs/components/PickerTrigger/PickerTrigger.d.ts +57 -0
- package/lib/typescript/commonjs/components/PickerTrigger/index.d.ts +3 -0
- package/lib/typescript/commonjs/components/ProgressBar/ProgressBar.d.ts +2 -0
- package/lib/typescript/commonjs/components/Radio/Radio.d.ts +3 -0
- package/lib/typescript/commonjs/components/Rating/Rating.d.ts +9 -6
- package/lib/typescript/commonjs/components/SegmentedControl/SegmentedControl.d.ts +3 -0
- package/lib/typescript/commonjs/components/Skeleton/Skeleton.d.ts +49 -20
- package/lib/typescript/commonjs/components/Skeleton/SkeletonClock.d.ts +60 -0
- package/lib/typescript/commonjs/components/Skeleton/SkeletonContent.d.ts +80 -19
- package/lib/typescript/commonjs/components/Skeleton/SkeletonProvider.d.ts +39 -5
- package/lib/typescript/commonjs/components/Skeleton/index.d.ts +6 -4
- package/lib/typescript/commonjs/components/Slider/Slider.d.ts +12 -1
- package/lib/typescript/commonjs/components/Stepper/Stepper.d.ts +18 -6
- package/lib/typescript/commonjs/components/Swipeable/Swipeable.d.ts +2 -0
- package/lib/typescript/commonjs/components/Switch/Switch.d.ts +1 -0
- package/lib/typescript/commonjs/components/Tabs/Tabs.d.ts +26 -2
- package/lib/typescript/commonjs/components/TimePicker/TimePicker.d.ts +36 -3
- package/lib/typescript/commonjs/components/Toast/Toast.d.ts +8 -0
- package/lib/typescript/commonjs/components/Tooltip/Tooltip.d.ts +7 -1
- package/lib/typescript/commonjs/components/index.d.ts +5 -5
- package/lib/typescript/commonjs/index.d.ts +1 -1
- package/lib/typescript/commonjs/theme/index.d.ts +1 -1
- package/lib/typescript/commonjs/theme/types.d.ts +578 -12
- package/lib/typescript/module/components/Accordion/Accordion.d.ts +10 -5
- package/lib/typescript/module/components/AppBar/AppBar.d.ts +8 -0
- package/lib/typescript/module/components/Avatar/Avatar.d.ts +12 -6
- package/lib/typescript/module/components/Badge/Badge.d.ts +7 -6
- package/lib/typescript/module/components/Banner/Banner.d.ts +17 -6
- package/lib/typescript/module/components/BottomSheet/BottomSheet.d.ts +7 -0
- package/lib/typescript/module/components/Card/Card.d.ts +17 -6
- package/lib/typescript/module/components/Carousel/Carousel.d.ts +7 -6
- package/lib/typescript/module/components/Checkbox/Checkbox.d.ts +9 -1
- package/lib/typescript/module/components/Chip/Chip.d.ts +13 -6
- package/lib/typescript/module/components/DatePicker/DatePicker.d.ts +38 -3
- package/lib/typescript/module/components/DateRangePicker/DateRangePicker.d.ts +36 -3
- package/lib/typescript/module/components/Dialog/Dialog.d.ts +13 -1
- package/lib/typescript/module/components/FieldBase/FieldBase.d.ts +172 -0
- package/lib/typescript/module/components/FieldBase/index.d.ts +3 -0
- package/lib/typescript/module/components/FloatingActionButton/FloatingActionButton.d.ts +8 -6
- package/lib/typescript/module/components/FloatingActionButton/index.d.ts +1 -1
- package/lib/typescript/module/components/ForceUpdateDialog/ForceUpdateDialog.d.ts +7 -0
- package/lib/typescript/module/components/FormField/FormField.d.ts +7 -0
- package/lib/typescript/module/components/ImageGallery/ImageGallery.d.ts +6 -4
- package/lib/typescript/module/components/Input/Input.d.ts +7 -1
- package/lib/typescript/module/components/ListItem/ListItem.d.ts +13 -6
- package/lib/typescript/module/components/NumberInput/NumberInput.d.ts +3 -0
- package/lib/typescript/module/components/PickerTrigger/PickerTrigger.d.ts +57 -0
- package/lib/typescript/module/components/PickerTrigger/index.d.ts +3 -0
- package/lib/typescript/module/components/ProgressBar/ProgressBar.d.ts +2 -0
- package/lib/typescript/module/components/Radio/Radio.d.ts +3 -0
- package/lib/typescript/module/components/Rating/Rating.d.ts +9 -6
- package/lib/typescript/module/components/SegmentedControl/SegmentedControl.d.ts +3 -0
- package/lib/typescript/module/components/Skeleton/Skeleton.d.ts +49 -20
- package/lib/typescript/module/components/Skeleton/SkeletonClock.d.ts +60 -0
- package/lib/typescript/module/components/Skeleton/SkeletonContent.d.ts +80 -19
- package/lib/typescript/module/components/Skeleton/SkeletonProvider.d.ts +39 -5
- package/lib/typescript/module/components/Skeleton/index.d.ts +6 -4
- package/lib/typescript/module/components/Slider/Slider.d.ts +12 -1
- package/lib/typescript/module/components/Stepper/Stepper.d.ts +18 -6
- package/lib/typescript/module/components/Swipeable/Swipeable.d.ts +2 -0
- package/lib/typescript/module/components/Switch/Switch.d.ts +1 -0
- package/lib/typescript/module/components/Tabs/Tabs.d.ts +26 -2
- package/lib/typescript/module/components/TimePicker/TimePicker.d.ts +36 -3
- package/lib/typescript/module/components/Toast/Toast.d.ts +8 -0
- package/lib/typescript/module/components/Tooltip/Tooltip.d.ts +7 -1
- package/lib/typescript/module/components/index.d.ts +5 -5
- package/lib/typescript/module/index.d.ts +1 -1
- package/lib/typescript/module/theme/index.d.ts +1 -1
- package/lib/typescript/module/theme/types.d.ts +578 -12
- package/package.json +2 -6
- package/lib/commonjs/components/AppIcon/AppIcon.js +0 -120
- package/lib/commonjs/types/vector-icons.d.js +0 -2
- package/lib/module/components/AppIcon/AppIcon.js +0 -111
- package/lib/module/components/AppIcon/index.js +0 -4
- package/lib/module/types/vector-icons.d.js +0 -2
- package/lib/typescript/commonjs/components/AppIcon/AppIcon.d.ts +0 -20
- package/lib/typescript/commonjs/components/AppIcon/index.d.ts +0 -3
- package/lib/typescript/module/components/AppIcon/AppIcon.d.ts +0 -20
- package/lib/typescript/module/components/AppIcon/index.d.ts +0 -3
|
@@ -6,7 +6,10 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
|
6
6
|
import { useTheme, createAnimatedValue, setNativeValue } from "../../theme/index.js";
|
|
7
7
|
import { usePressAnimation } from "../../hooks/usePressAnimation.js";
|
|
8
8
|
import { triggerHaptic } from "../../utils/hapticUtils.js";
|
|
9
|
-
|
|
9
|
+
|
|
10
|
+
// Local shape mirror — see types.ts FloatingActionButtonTokens for the canonical
|
|
11
|
+
// definition. Declared here so the component can read tokens before types.ts is
|
|
12
|
+
// regenerated; will collapse to a direct import once that lands.
|
|
10
13
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
11
14
|
const sizeMap = {
|
|
12
15
|
sm: {
|
|
@@ -47,9 +50,6 @@ const toneForeground = (theme, tone) => {
|
|
|
47
50
|
if (tone === 'neutral') return theme.colors.text.primary;
|
|
48
51
|
return theme.colors.text.inverse;
|
|
49
52
|
};
|
|
50
|
-
const isIconConfig = value => {
|
|
51
|
-
return typeof value === 'object' && value !== null && ! /*#__PURE__*/React.isValidElement(value) && typeof value.name === 'string';
|
|
52
|
-
};
|
|
53
53
|
const FloatingActionButton = /*#__PURE__*/forwardRef((props, ref) => {
|
|
54
54
|
const {
|
|
55
55
|
icon,
|
|
@@ -65,6 +65,8 @@ const FloatingActionButton = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
65
65
|
accessibilityLabel,
|
|
66
66
|
accessibilityHint,
|
|
67
67
|
style,
|
|
68
|
+
containerStyle,
|
|
69
|
+
labelStyle,
|
|
68
70
|
testID
|
|
69
71
|
} = props;
|
|
70
72
|
const theme = useTheme();
|
|
@@ -78,6 +80,10 @@ const FloatingActionButton = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
78
80
|
} = usePressAnimation({
|
|
79
81
|
enabled: !disabled
|
|
80
82
|
});
|
|
83
|
+
const fabTokens = theme.components.floatingActionButton;
|
|
84
|
+
const edgeOffset = fabTokens?.edgeOffset ?? 24;
|
|
85
|
+
const defaultBottomOffset = fabTokens?.bottomOffset ?? 24;
|
|
86
|
+
const pressHaptic = fabTokens?.pressHaptic ?? false;
|
|
81
87
|
const hideAnim = useRef(createAnimatedValue(0)).current;
|
|
82
88
|
useEffect(() => {
|
|
83
89
|
if (!hideOnScroll) {
|
|
@@ -93,7 +99,7 @@ const FloatingActionButton = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
93
99
|
}, [hideOnScroll, isScrolling, hideAnim, theme.motion.duration.normal, theme.motion.easing.standard]);
|
|
94
100
|
const hideTranslateY = hideAnim.interpolate({
|
|
95
101
|
inputRange: [0, 1],
|
|
96
|
-
outputRange: [0, sizeStyles.diameter + (bottomOffset ??
|
|
102
|
+
outputRange: [0, sizeStyles.diameter + (bottomOffset ?? defaultBottomOffset) + insets.bottom + edgeOffset]
|
|
97
103
|
});
|
|
98
104
|
const hideOpacity = hideAnim.interpolate({
|
|
99
105
|
inputRange: [0, 1],
|
|
@@ -101,27 +107,22 @@ const FloatingActionButton = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
101
107
|
});
|
|
102
108
|
const handlePress = _event => {
|
|
103
109
|
if (disabled) return;
|
|
104
|
-
triggerHaptic('impactLight');
|
|
110
|
+
if (pressHaptic) triggerHaptic('impactLight');
|
|
105
111
|
onPress();
|
|
106
112
|
};
|
|
107
|
-
const renderedIcon =
|
|
108
|
-
name: icon.name,
|
|
109
|
-
family: icon.family ?? 'feather',
|
|
110
|
-
size: sizeStyles.iconSize,
|
|
111
|
-
color: toneForeground(theme, tone)
|
|
112
|
-
}) : icon;
|
|
113
|
+
const renderedIcon = icon;
|
|
113
114
|
const isExtended = typeof label === 'string' && label.length > 0;
|
|
114
115
|
const backgroundColor = disabled ? theme.colors.surface.disabled : toneBackground(theme, tone);
|
|
115
116
|
const foregroundColor = disabled ? theme.colors.text.disabled : toneForeground(theme, tone);
|
|
116
117
|
const positionStyle = useMemo(() => {
|
|
117
118
|
if (!position) return null;
|
|
118
|
-
const bottom = (bottomOffset ??
|
|
119
|
+
const bottom = (bottomOffset ?? defaultBottomOffset) + insets.bottom;
|
|
119
120
|
switch (position) {
|
|
120
121
|
case 'bottomLeft':
|
|
121
122
|
return {
|
|
122
123
|
position: 'absolute',
|
|
123
124
|
bottom,
|
|
124
|
-
left:
|
|
125
|
+
left: edgeOffset
|
|
125
126
|
};
|
|
126
127
|
case 'bottomCenter':
|
|
127
128
|
return {
|
|
@@ -134,10 +135,10 @@ const FloatingActionButton = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
134
135
|
return {
|
|
135
136
|
position: 'absolute',
|
|
136
137
|
bottom,
|
|
137
|
-
right:
|
|
138
|
+
right: edgeOffset
|
|
138
139
|
};
|
|
139
140
|
}
|
|
140
|
-
}, [position, bottomOffset, insets.bottom]);
|
|
141
|
+
}, [position, bottomOffset, insets.bottom, defaultBottomOffset, edgeOffset]);
|
|
141
142
|
return /*#__PURE__*/_jsx(Animated.View, {
|
|
142
143
|
style: [positionStyle, {
|
|
143
144
|
transform: [{
|
|
@@ -146,7 +147,7 @@ const FloatingActionButton = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
146
147
|
scale
|
|
147
148
|
}],
|
|
148
149
|
opacity: hideOpacity
|
|
149
|
-
}, style],
|
|
150
|
+
}, containerStyle, style],
|
|
150
151
|
pointerEvents: hideOnScroll && isScrolling ? 'none' : 'auto',
|
|
151
152
|
children: /*#__PURE__*/_jsxs(Pressable, {
|
|
152
153
|
ref: ref,
|
|
@@ -183,7 +184,7 @@ const FloatingActionButton = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
183
184
|
fontSize: sizeStyles.fontSize,
|
|
184
185
|
fontWeight: theme.typography.fontWeight.semibold,
|
|
185
186
|
marginLeft: theme.spacing.sm
|
|
186
|
-
}],
|
|
187
|
+
}, labelStyle],
|
|
187
188
|
numberOfLines: 1,
|
|
188
189
|
children: label
|
|
189
190
|
}) : null]
|
|
@@ -209,8 +210,6 @@ const buildStyles = _theme => StyleSheet.create({
|
|
|
209
210
|
// so they remain smooth during JS thread work.
|
|
210
211
|
// =====================================================================
|
|
211
212
|
|
|
212
|
-
const SECONDARY_GAP = 16;
|
|
213
|
-
const STAGGER_MS = 50;
|
|
214
213
|
const FloatingActionButtonGroup = props => {
|
|
215
214
|
const {
|
|
216
215
|
primaryIcon,
|
|
@@ -223,28 +222,54 @@ const FloatingActionButtonGroup = props => {
|
|
|
223
222
|
bottomOffset,
|
|
224
223
|
size = 'md',
|
|
225
224
|
accessibilityLabel = 'Quick actions',
|
|
225
|
+
containerStyle,
|
|
226
|
+
secondaryActionStyle,
|
|
227
|
+
labelPillStyle,
|
|
228
|
+
labelStyle,
|
|
226
229
|
testID
|
|
227
230
|
} = props;
|
|
228
231
|
const theme = useTheme();
|
|
229
232
|
const insets = useSafeAreaInsets();
|
|
230
233
|
const sizeStyles = sizeMap[size];
|
|
231
234
|
const styles = useMemo(() => buildGroupStyles(theme), [theme]);
|
|
235
|
+
const fabTokens = theme.components.floatingActionButton;
|
|
236
|
+
const edgeOffset = fabTokens?.edgeOffset ?? 24;
|
|
237
|
+
const defaultBottomOffset = fabTokens?.bottomOffset ?? 24;
|
|
238
|
+
const secondaryGap = fabTokens?.secondaryGap ?? 16;
|
|
239
|
+
const staggerMs = fabTokens?.staggerMs ?? 50;
|
|
240
|
+
const pressHaptic = fabTokens?.pressHaptic ?? false;
|
|
232
241
|
const isControlled = typeof controlledOpen === 'boolean';
|
|
233
242
|
const [internalOpen, setInternalOpen] = useState(defaultOpen);
|
|
234
243
|
const isOpen = isControlled ? controlledOpen : internalOpen;
|
|
235
244
|
|
|
236
245
|
// Animation drivers
|
|
237
246
|
const progress = useRef(createAnimatedValue(isOpen ? 1 : 0)).current;
|
|
247
|
+
// Stable refs for per-item Animated.Values. Reconciled inside useEffect — never
|
|
248
|
+
// mutated during render — so re-renders don't recreate Animated.Value instances
|
|
249
|
+
// (previous implementation leaked one Animated.Value per re-render when the
|
|
250
|
+
// actions array length changed).
|
|
238
251
|
const itemAnims = useRef([]);
|
|
239
|
-
|
|
240
|
-
//
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
252
|
+
// Bump this when the underlying refs array is re-shaped so downstream effects
|
|
253
|
+
// and mapping reads see the new length.
|
|
254
|
+
const [itemAnimsVersion, setItemAnimsVersion] = useState(0);
|
|
255
|
+
useEffect(() => {
|
|
256
|
+
const current = itemAnims.current;
|
|
257
|
+
if (current.length === actions.length) return;
|
|
258
|
+
if (current.length < actions.length) {
|
|
259
|
+
const next = current.slice();
|
|
260
|
+
for (let i = current.length; i < actions.length; i += 1) {
|
|
261
|
+
next.push(new Animated.Value(isOpen ? 1 : 0));
|
|
262
|
+
}
|
|
263
|
+
itemAnims.current = next;
|
|
264
|
+
} else {
|
|
265
|
+
// Drop stale refs when actions array shrinks.
|
|
266
|
+
itemAnims.current = current.slice(0, actions.length);
|
|
245
267
|
}
|
|
246
|
-
|
|
247
|
-
|
|
268
|
+
setItemAnimsVersion(v => v + 1);
|
|
269
|
+
// isOpen intentionally not in deps — only used to seed brand-new values; the
|
|
270
|
+
// open/close effect below will animate them to the correct target anyway.
|
|
271
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
272
|
+
}, [actions.length]);
|
|
248
273
|
useEffect(() => {
|
|
249
274
|
Animated.timing(progress, {
|
|
250
275
|
toValue: isOpen ? 1 : 0,
|
|
@@ -260,34 +285,34 @@ const FloatingActionButtonGroup = props => {
|
|
|
260
285
|
}));
|
|
261
286
|
// Reverse stagger order on close so items closest to primary collapse last
|
|
262
287
|
const ordered = isOpen ? animations : [...animations].reverse();
|
|
263
|
-
Animated.stagger(
|
|
264
|
-
}, [isOpen, progress, theme.motion.duration.normal, theme.motion.easing.standard, actions.length]);
|
|
288
|
+
Animated.stagger(staggerMs, ordered).start();
|
|
289
|
+
}, [isOpen, progress, theme.motion.duration.normal, theme.motion.easing.standard, actions.length, itemAnimsVersion, staggerMs]);
|
|
265
290
|
const setOpen = useCallback(next => {
|
|
266
|
-
triggerHaptic('impactLight');
|
|
291
|
+
if (pressHaptic) triggerHaptic('impactLight');
|
|
267
292
|
if (!isControlled) setInternalOpen(next);
|
|
268
293
|
onOpenChange?.(next);
|
|
269
|
-
}, [isControlled, onOpenChange]);
|
|
294
|
+
}, [isControlled, onOpenChange, pressHaptic]);
|
|
270
295
|
const handlePrimaryPress = useCallback(() => {
|
|
271
296
|
setOpen(!isOpen);
|
|
272
297
|
}, [isOpen, setOpen]);
|
|
273
298
|
const handleActionPress = useCallback(action => {
|
|
274
|
-
triggerHaptic('selection');
|
|
299
|
+
if (pressHaptic) triggerHaptic('selection');
|
|
275
300
|
action.onPress();
|
|
276
301
|
// Close after action runs
|
|
277
302
|
if (!isControlled) setInternalOpen(false);
|
|
278
303
|
onOpenChange?.(false);
|
|
279
|
-
}, [isControlled, onOpenChange]);
|
|
304
|
+
}, [isControlled, onOpenChange, pressHaptic]);
|
|
280
305
|
const handleBackdropPress = useCallback(() => {
|
|
281
306
|
if (isOpen) setOpen(false);
|
|
282
307
|
}, [isOpen, setOpen]);
|
|
283
308
|
const positionStyle = useMemo(() => {
|
|
284
|
-
const bottom = (bottomOffset ??
|
|
309
|
+
const bottom = (bottomOffset ?? defaultBottomOffset) + insets.bottom;
|
|
285
310
|
switch (position) {
|
|
286
311
|
case 'bottomLeft':
|
|
287
312
|
return {
|
|
288
313
|
position: 'absolute',
|
|
289
314
|
bottom,
|
|
290
|
-
left:
|
|
315
|
+
left: edgeOffset,
|
|
291
316
|
alignItems: 'flex-start'
|
|
292
317
|
};
|
|
293
318
|
case 'bottomCenter':
|
|
@@ -302,14 +327,14 @@ const FloatingActionButtonGroup = props => {
|
|
|
302
327
|
return {
|
|
303
328
|
position: 'absolute',
|
|
304
329
|
bottom,
|
|
305
|
-
right:
|
|
330
|
+
right: edgeOffset,
|
|
306
331
|
alignItems: 'flex-end'
|
|
307
332
|
};
|
|
308
333
|
}
|
|
309
|
-
}, [position, bottomOffset, insets.bottom]);
|
|
334
|
+
}, [position, bottomOffset, insets.bottom, defaultBottomOffset, edgeOffset]);
|
|
310
335
|
|
|
311
336
|
// Vertical slot offset per index: secondary at index i sits (diameter + gap) * (i + 1) above primary
|
|
312
|
-
const slotOffset = index => -((sizeStyles.diameter +
|
|
337
|
+
const slotOffset = index => -((sizeStyles.diameter + secondaryGap) * (index + 1));
|
|
313
338
|
const backdropOpacity = progress.interpolate({
|
|
314
339
|
inputRange: [0, 1],
|
|
315
340
|
outputRange: [0, 0.4]
|
|
@@ -333,7 +358,7 @@ const FloatingActionButtonGroup = props => {
|
|
|
333
358
|
testID: testID ? `${testID}-backdrop` : undefined
|
|
334
359
|
})
|
|
335
360
|
}) : null, /*#__PURE__*/_jsxs(View, {
|
|
336
|
-
style: positionStyle,
|
|
361
|
+
style: [positionStyle, containerStyle],
|
|
337
362
|
accessibilityRole: 'menu',
|
|
338
363
|
testID: testID,
|
|
339
364
|
children: [actions.map((action, index) => {
|
|
@@ -369,13 +394,13 @@ const FloatingActionButtonGroup = props => {
|
|
|
369
394
|
paddingVertical: theme.spacing.xs,
|
|
370
395
|
borderRadius: theme.radius.sm,
|
|
371
396
|
marginRight: theme.spacing.sm
|
|
372
|
-
}],
|
|
397
|
+
}, labelPillStyle],
|
|
373
398
|
children: /*#__PURE__*/_jsx(Text, {
|
|
374
|
-
style: {
|
|
399
|
+
style: [{
|
|
375
400
|
color: theme.colors.text.primary,
|
|
376
401
|
fontSize: theme.typography.fontSize.sm,
|
|
377
402
|
fontWeight: theme.typography.fontWeight.medium
|
|
378
|
-
},
|
|
403
|
+
}, labelStyle],
|
|
379
404
|
numberOfLines: 1,
|
|
380
405
|
children: action.label
|
|
381
406
|
})
|
|
@@ -392,7 +417,7 @@ const FloatingActionButtonGroup = props => {
|
|
|
392
417
|
height: secondaryDiameter,
|
|
393
418
|
borderRadius: secondaryDiameter / 2,
|
|
394
419
|
backgroundColor: itemBackground
|
|
395
|
-
}],
|
|
420
|
+
}, secondaryActionStyle],
|
|
396
421
|
testID: testID ? `${testID}-action-${action.key}` : undefined,
|
|
397
422
|
children: /*#__PURE__*/React.isValidElement(action.icon) ? action.icon : /*#__PURE__*/_jsx(Text, {
|
|
398
423
|
style: {
|
|
@@ -27,7 +27,10 @@ export const ForceUpdateDialog = ({
|
|
|
27
27
|
title = 'Update available',
|
|
28
28
|
message = 'A new version of the app is available. Please update to continue using the latest features and security improvements.',
|
|
29
29
|
updateLabel = 'Update now',
|
|
30
|
-
laterLabel = 'Later'
|
|
30
|
+
laterLabel = 'Later',
|
|
31
|
+
containerStyle,
|
|
32
|
+
titleStyle,
|
|
33
|
+
messageStyle
|
|
31
34
|
}) => {
|
|
32
35
|
const [updating, setUpdating] = useState(false);
|
|
33
36
|
const handleUpdate = useCallback(() => {
|
|
@@ -68,7 +71,10 @@ export const ForceUpdateDialog = ({
|
|
|
68
71
|
message: message,
|
|
69
72
|
variant: "info",
|
|
70
73
|
actions: actions,
|
|
71
|
-
dismissOnAction: false
|
|
74
|
+
dismissOnAction: false,
|
|
75
|
+
containerStyle: containerStyle,
|
|
76
|
+
titleStyle: titleStyle,
|
|
77
|
+
messageStyle: messageStyle
|
|
72
78
|
});
|
|
73
79
|
};
|
|
74
80
|
export default ForceUpdateDialog;
|
|
@@ -16,6 +16,7 @@ const FormField = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
16
16
|
helperStyle,
|
|
17
17
|
errorStyle,
|
|
18
18
|
containerStyle,
|
|
19
|
+
inputContainerStyle: inputContainerStyleProp,
|
|
19
20
|
accessibilityLabel,
|
|
20
21
|
testID
|
|
21
22
|
} = props;
|
|
@@ -56,12 +57,12 @@ const FormField = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
56
57
|
testID: testID,
|
|
57
58
|
children: [layout === 'inline' ? /*#__PURE__*/_jsxs(_Fragment, {
|
|
58
59
|
children: [labelNode, /*#__PURE__*/_jsx(View, {
|
|
59
|
-
style: inputContainerStyle,
|
|
60
|
+
style: [inputContainerStyle, inputContainerStyleProp],
|
|
60
61
|
children: children
|
|
61
62
|
})]
|
|
62
63
|
}) : /*#__PURE__*/_jsxs(_Fragment, {
|
|
63
64
|
children: [labelNode, /*#__PURE__*/_jsx(View, {
|
|
64
|
-
style: inputContainerStyle,
|
|
65
|
+
style: [inputContainerStyle, inputContainerStyleProp],
|
|
65
66
|
children: children
|
|
66
67
|
})]
|
|
67
68
|
}), showHelper ? /*#__PURE__*/_jsx(Text, {
|
|
@@ -24,14 +24,15 @@ import { Dimensions, Image, Modal as RNModal, Pressable, StyleSheet, Text, View
|
|
|
24
24
|
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
|
|
25
25
|
import Animated, { runOnJS, useAnimatedStyle, useSharedValue, withSpring, withTiming } from 'react-native-reanimated';
|
|
26
26
|
import { Carousel } from "../Carousel/index.js";
|
|
27
|
-
import {
|
|
28
|
-
import { SkeletonContent } from "../Skeleton/index.js";
|
|
27
|
+
import { Skeleton } from "../Skeleton/index.js";
|
|
29
28
|
import { useTheme } from "../../theme/index.js";
|
|
30
29
|
import { triggerHaptic } from "../../utils/index.js";
|
|
30
|
+
|
|
31
|
+
// Local shape mirror — see types.ts ImageGalleryTokens for the canonical definition.
|
|
31
32
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
const
|
|
33
|
+
const MAX_SCALE_FALLBACK = 4;
|
|
34
|
+
const MIN_SCALE_FALLBACK = 1;
|
|
35
|
+
const DOUBLE_TAP_SCALE_FALLBACK = 2;
|
|
35
36
|
const SPRING_CONFIG = {
|
|
36
37
|
damping: 18,
|
|
37
38
|
stiffness: 220,
|
|
@@ -52,6 +53,8 @@ const ImageGallery = ({
|
|
|
52
53
|
}) => {
|
|
53
54
|
const theme = useTheme();
|
|
54
55
|
const styles = useMemo(() => buildStyles(theme), [theme]);
|
|
56
|
+
const galleryTokens = theme.components.imageGallery;
|
|
57
|
+
const pressHaptic = galleryTokens?.pressHaptic ?? false;
|
|
55
58
|
const [currentIndex, setCurrentIndex] = useState(clamp(initialIndex, 0, Math.max(0, images.length - 1)));
|
|
56
59
|
const [lightboxOpen, setLightboxOpen] = useState(false);
|
|
57
60
|
const handleIndexChange = useCallback(idx => {
|
|
@@ -60,13 +63,13 @@ const ImageGallery = ({
|
|
|
60
63
|
}, [onIndexChange]);
|
|
61
64
|
const openLightbox = useCallback(() => {
|
|
62
65
|
if (!enableLightbox) return;
|
|
63
|
-
triggerHaptic('selection');
|
|
66
|
+
if (pressHaptic) triggerHaptic('selection');
|
|
64
67
|
setLightboxOpen(true);
|
|
65
|
-
}, [enableLightbox]);
|
|
68
|
+
}, [enableLightbox, pressHaptic]);
|
|
66
69
|
const closeLightbox = useCallback(() => {
|
|
67
|
-
triggerHaptic('selection');
|
|
70
|
+
if (pressHaptic) triggerHaptic('selection');
|
|
68
71
|
setLightboxOpen(false);
|
|
69
|
-
}, []);
|
|
72
|
+
}, [pressHaptic]);
|
|
70
73
|
const renderImage = useCallback((image, idx) => {
|
|
71
74
|
const a11y = image.alt ?? `Image ${idx + 1} of ${images.length}`;
|
|
72
75
|
return /*#__PURE__*/_jsx(Pressable, {
|
|
@@ -119,20 +122,86 @@ const ImageGallery = ({
|
|
|
119
122
|
enablePinchZoom: enablePinchZoom,
|
|
120
123
|
showCounter: showCounter,
|
|
121
124
|
onClose: closeLightbox,
|
|
122
|
-
onIndexChange: handleIndexChange
|
|
125
|
+
onIndexChange: handleIndexChange,
|
|
126
|
+
pressHaptic: pressHaptic
|
|
123
127
|
}) : null]
|
|
124
128
|
});
|
|
125
129
|
if (loading) {
|
|
126
|
-
return /*#__PURE__*/_jsx(
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
+
return /*#__PURE__*/_jsx(ImageGallerySkeleton, {
|
|
131
|
+
images: images,
|
|
132
|
+
showThumbnails: showThumbnails,
|
|
133
|
+
showCounter: showCounter,
|
|
134
|
+
containerStyle: containerStyle,
|
|
135
|
+
testID: testID
|
|
130
136
|
});
|
|
131
137
|
}
|
|
132
138
|
return rendered;
|
|
133
139
|
};
|
|
134
140
|
ImageGallery.displayName = 'ImageGallery';
|
|
135
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Placeholder shape for `<ImageGallery>`. Hero image block + optional
|
|
144
|
+
* counter pill + thumbnail strip. Heights are tuned to match the live
|
|
145
|
+
* component's defaults so the gallery doesn't reflow when ready.
|
|
146
|
+
*/
|
|
147
|
+
const ImageGallerySkeleton = ({
|
|
148
|
+
images,
|
|
149
|
+
showThumbnails = true,
|
|
150
|
+
showCounter = true,
|
|
151
|
+
containerStyle,
|
|
152
|
+
testID
|
|
153
|
+
}) => {
|
|
154
|
+
const thumbCount = Math.min(images?.length || 4, 6);
|
|
155
|
+
return /*#__PURE__*/_jsxs(View, {
|
|
156
|
+
style: [{
|
|
157
|
+
width: '100%'
|
|
158
|
+
}, containerStyle],
|
|
159
|
+
testID: testID,
|
|
160
|
+
accessibilityRole: "progressbar",
|
|
161
|
+
accessibilityState: {
|
|
162
|
+
busy: true
|
|
163
|
+
},
|
|
164
|
+
children: [/*#__PURE__*/_jsxs(View, {
|
|
165
|
+
style: {
|
|
166
|
+
position: 'relative'
|
|
167
|
+
},
|
|
168
|
+
children: [/*#__PURE__*/_jsx(Skeleton, {
|
|
169
|
+
width: "100%",
|
|
170
|
+
height: 280,
|
|
171
|
+
radius: "lg"
|
|
172
|
+
}), showCounter ? /*#__PURE__*/_jsx(View, {
|
|
173
|
+
style: {
|
|
174
|
+
position: 'absolute',
|
|
175
|
+
top: 12,
|
|
176
|
+
right: 12
|
|
177
|
+
},
|
|
178
|
+
children: /*#__PURE__*/_jsx(Skeleton, {
|
|
179
|
+
width: 44,
|
|
180
|
+
height: 22,
|
|
181
|
+
radius: "full"
|
|
182
|
+
})
|
|
183
|
+
}) : null]
|
|
184
|
+
}), showThumbnails ? /*#__PURE__*/_jsx(View, {
|
|
185
|
+
style: {
|
|
186
|
+
flexDirection: 'row',
|
|
187
|
+
marginTop: 12
|
|
188
|
+
},
|
|
189
|
+
children: Array.from({
|
|
190
|
+
length: thumbCount
|
|
191
|
+
}).map((_, i) => /*#__PURE__*/_jsx(Skeleton, {
|
|
192
|
+
width: 64,
|
|
193
|
+
height: 64,
|
|
194
|
+
radius: "md",
|
|
195
|
+
style: {
|
|
196
|
+
marginRight: 8
|
|
197
|
+
}
|
|
198
|
+
}, `sk-thumb-${i}`))
|
|
199
|
+
}) : null]
|
|
200
|
+
});
|
|
201
|
+
};
|
|
202
|
+
ImageGallerySkeleton.displayName = 'ImageGallerySkeleton';
|
|
203
|
+
ImageGallery.Skeleton = ImageGallerySkeleton;
|
|
204
|
+
|
|
136
205
|
// ───────── Lightbox ─────────
|
|
137
206
|
|
|
138
207
|
const Lightbox = ({
|
|
@@ -142,26 +211,35 @@ const Lightbox = ({
|
|
|
142
211
|
enablePinchZoom,
|
|
143
212
|
showCounter,
|
|
144
213
|
onClose,
|
|
145
|
-
onIndexChange
|
|
214
|
+
onIndexChange,
|
|
215
|
+
pressHaptic
|
|
146
216
|
}) => {
|
|
147
217
|
const theme = useTheme();
|
|
148
218
|
const styles = useMemo(() => buildLightboxStyles(theme), [theme]);
|
|
219
|
+
const galleryTokens = theme.components.imageGallery;
|
|
220
|
+
const maxScale = galleryTokens?.maxScale ?? MAX_SCALE_FALLBACK;
|
|
221
|
+
const minScale = galleryTokens?.minScale ?? MIN_SCALE_FALLBACK;
|
|
222
|
+
const doubleTapScale = galleryTokens?.doubleTapScale ?? DOUBLE_TAP_SCALE_FALLBACK;
|
|
149
223
|
const carouselRef = useRef(null);
|
|
150
224
|
const [activeIndex, setActiveIndex] = useState(initialIndex);
|
|
151
225
|
const handleSwipe = useCallback(idx => {
|
|
152
226
|
if (idx !== activeIndex) {
|
|
153
|
-
triggerHaptic('selection');
|
|
227
|
+
if (pressHaptic) triggerHaptic('selection');
|
|
154
228
|
setActiveIndex(idx);
|
|
155
229
|
onIndexChange(idx);
|
|
156
230
|
}
|
|
157
|
-
}, [activeIndex, onIndexChange]);
|
|
231
|
+
}, [activeIndex, onIndexChange, pressHaptic]);
|
|
158
232
|
const renderItem = useCallback((image, idx) => /*#__PURE__*/_jsx(ZoomableImage, {
|
|
159
233
|
image: image,
|
|
160
234
|
index: idx,
|
|
161
235
|
total: images.length,
|
|
162
236
|
active: idx === activeIndex,
|
|
163
|
-
enabled: enablePinchZoom
|
|
164
|
-
|
|
237
|
+
enabled: enablePinchZoom,
|
|
238
|
+
maxScale: maxScale,
|
|
239
|
+
minScale: minScale,
|
|
240
|
+
doubleTapScale: doubleTapScale,
|
|
241
|
+
pressHaptic: pressHaptic
|
|
242
|
+
}), [activeIndex, enablePinchZoom, images.length, maxScale, minScale, doubleTapScale, pressHaptic]);
|
|
165
243
|
const caption = images[activeIndex]?.caption;
|
|
166
244
|
return /*#__PURE__*/_jsx(RNModal, {
|
|
167
245
|
visible: visible,
|
|
@@ -197,11 +275,15 @@ const Lightbox = ({
|
|
|
197
275
|
accessibilityLabel: "Close gallery",
|
|
198
276
|
style: styles.closeButton,
|
|
199
277
|
hitSlop: 12,
|
|
200
|
-
children:
|
|
201
|
-
|
|
202
|
-
family: "feather",
|
|
203
|
-
size: "lg",
|
|
278
|
+
children: theme.icons?.close ? theme.icons.close({
|
|
279
|
+
size: 24,
|
|
204
280
|
color: theme.colors.text.inverse
|
|
281
|
+
}) : /*#__PURE__*/_jsx(Text, {
|
|
282
|
+
style: {
|
|
283
|
+
fontSize: 24,
|
|
284
|
+
color: theme.colors.text.inverse
|
|
285
|
+
},
|
|
286
|
+
children: "\xD7"
|
|
205
287
|
})
|
|
206
288
|
}), caption ? /*#__PURE__*/_jsx(Animated.View, {
|
|
207
289
|
style: styles.captionWrap,
|
|
@@ -224,7 +306,11 @@ const ZoomableImage = ({
|
|
|
224
306
|
index,
|
|
225
307
|
total,
|
|
226
308
|
active,
|
|
227
|
-
enabled
|
|
309
|
+
enabled,
|
|
310
|
+
maxScale,
|
|
311
|
+
minScale,
|
|
312
|
+
doubleTapScale,
|
|
313
|
+
pressHaptic
|
|
228
314
|
}) => {
|
|
229
315
|
const screen = Dimensions.get('window');
|
|
230
316
|
const scale = useSharedValue(1);
|
|
@@ -259,7 +345,9 @@ const ZoomableImage = ({
|
|
|
259
345
|
savedTranslateY.value = 0;
|
|
260
346
|
}
|
|
261
347
|
}, [active, scale, translateX, translateY, savedScale, savedTranslateX, savedTranslateY]);
|
|
262
|
-
const triggerImpact = useCallback(() =>
|
|
348
|
+
const triggerImpact = useCallback(() => {
|
|
349
|
+
if (pressHaptic) triggerHaptic('impactLight');
|
|
350
|
+
}, [pressHaptic]);
|
|
263
351
|
const pinch = useMemo(() => Gesture.Pinch().enabled(enabled).onStart(() => {
|
|
264
352
|
'worklet';
|
|
265
353
|
|
|
@@ -268,33 +356,33 @@ const ZoomableImage = ({
|
|
|
268
356
|
'worklet';
|
|
269
357
|
|
|
270
358
|
const next = savedScale.value * e.scale;
|
|
271
|
-
// Clamp
|
|
272
|
-
if (next <
|
|
273
|
-
scale.value =
|
|
274
|
-
} else if (next >
|
|
275
|
-
scale.value =
|
|
359
|
+
// Clamp minScale..maxScale on the UI thread so pinch feels rubbery at limits.
|
|
360
|
+
if (next < minScale) {
|
|
361
|
+
scale.value = minScale + (next - minScale) * 0.3;
|
|
362
|
+
} else if (next > maxScale) {
|
|
363
|
+
scale.value = maxScale + (next - maxScale) * 0.2;
|
|
276
364
|
} else {
|
|
277
365
|
scale.value = next;
|
|
278
366
|
}
|
|
279
367
|
}).onEnd(() => {
|
|
280
368
|
'worklet';
|
|
281
369
|
|
|
282
|
-
if (scale.value <
|
|
283
|
-
scale.value = withSpring(
|
|
370
|
+
if (scale.value < minScale) {
|
|
371
|
+
scale.value = withSpring(minScale, SPRING_CONFIG);
|
|
284
372
|
translateX.value = withSpring(0, SPRING_CONFIG);
|
|
285
373
|
translateY.value = withSpring(0, SPRING_CONFIG);
|
|
286
|
-
savedScale.value =
|
|
374
|
+
savedScale.value = minScale;
|
|
287
375
|
savedTranslateX.value = 0;
|
|
288
376
|
savedTranslateY.value = 0;
|
|
289
377
|
return;
|
|
290
378
|
}
|
|
291
|
-
if (scale.value >
|
|
292
|
-
scale.value = withSpring(
|
|
293
|
-
savedScale.value =
|
|
379
|
+
if (scale.value > maxScale) {
|
|
380
|
+
scale.value = withSpring(maxScale, SPRING_CONFIG);
|
|
381
|
+
savedScale.value = maxScale;
|
|
294
382
|
return;
|
|
295
383
|
}
|
|
296
384
|
savedScale.value = scale.value;
|
|
297
|
-
}), [enabled, scale, savedScale, translateX, translateY, savedTranslateX, savedTranslateY]);
|
|
385
|
+
}), [enabled, scale, savedScale, translateX, translateY, savedTranslateX, savedTranslateY, minScale, maxScale]);
|
|
298
386
|
|
|
299
387
|
// Pan only engages when zoomed in — otherwise the parent Carousel owns horizontal swipe.
|
|
300
388
|
const pan = useMemo(() => Gesture.Pan().minPointers(1).maxPointers(1).onStart(() => {
|
|
@@ -326,10 +414,10 @@ const ZoomableImage = ({
|
|
|
326
414
|
savedTranslateX.value = 0;
|
|
327
415
|
savedTranslateY.value = 0;
|
|
328
416
|
} else {
|
|
329
|
-
scale.value = withSpring(
|
|
330
|
-
savedScale.value =
|
|
417
|
+
scale.value = withSpring(doubleTapScale, SPRING_CONFIG);
|
|
418
|
+
savedScale.value = doubleTapScale;
|
|
331
419
|
}
|
|
332
|
-
}), [scale, savedScale, translateX, translateY, savedTranslateX, savedTranslateY, triggerImpact]);
|
|
420
|
+
}), [scale, savedScale, translateX, translateY, savedTranslateX, savedTranslateY, triggerImpact, doubleTapScale]);
|
|
333
421
|
|
|
334
422
|
// Race the gestures so they coordinate cleanly: a tap won't be misread as a tiny pan,
|
|
335
423
|
// and pan won't fire when the user actually starts a pinch.
|