Skip to content

Commit a340b77

Browse files
implement alert position bottom (#302)
* implement alert position bottom In doing so, opportunity to correct onMoveShouldSetPanResponder to be based on panResponderEnabled hence eliminates need for panResponderMoveDistance prop. This was based on: "Called for every touch move on the View when it is not the responder: does this view want to "claim" touch responsiveness?" for this case yes when enabled. https://reactnative.dev/docs/gesture-responder-system#responder-lifecycle * update readme support * update demo gif
1 parent 0cb8bf9 commit a340b77

File tree

4 files changed

+89
-41
lines changed

4 files changed

+89
-41
lines changed

DropdownAlert.tsx

+65-34
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
ViewProps,
2323
TouchableOpacityProps,
2424
ImageProps,
25+
useWindowDimensions,
2526
} from 'react-native';
2627
import Queue from './Queue';
2728

@@ -82,6 +83,11 @@ export const DropDownAlertTestID = {
8283
CancelImage: 'cancelImage',
8384
};
8485

86+
export enum DropdownAlertPosition {
87+
Top = 'top',
88+
Bottom = 'bottom',
89+
}
90+
8591
// References
8692
// Image source: https://reactnative.dev/docs/image#source
8793
// Image style: https://reactnative.dev/docs/image#style
@@ -123,9 +129,6 @@ export type DropdownAlertProps = {
123129
elevation?: number;
124130
// It is used in the Animated.View style so alert is above other UI components
125131
zIndex?: number;
126-
// Distance on the Y-axis for alert to move by pan gesture
127-
// panResponderEnabled must be true as well
128-
panResponderMoveDistance?: number;
129132
// Distance on the Y-axis for the alert to be dismissed by pan gesture
130133
// panResponderEnabled must be true as well
131134
panResponderDismissDistance?: number;
@@ -174,6 +177,7 @@ export type DropdownAlertProps = {
174177
dismiss?: (func: () => void) => void;
175178
springAnimationConfig?: Animated.SpringAnimationConfig;
176179
children?: ReactNode;
180+
alertPosition?: 'top' | 'bottom';
177181
};
178182

179183
const DropdownAlert: React.FunctionComponent<DropdownAlertProps> = ({
@@ -237,7 +241,6 @@ const DropdownAlert: React.FunctionComponent<DropdownAlertProps> = ({
237241
updateStatusBar = true,
238242
elevation = 1,
239243
zIndex = 1,
240-
panResponderMoveDistance = 0,
241244
renderImage = undefined,
242245
renderCancel = undefined,
243246
renderTitle = undefined,
@@ -261,11 +264,13 @@ const DropdownAlert: React.FunctionComponent<DropdownAlertProps> = ({
261264
},
262265
panResponderDismissDistance = -10,
263266
children = undefined,
267+
alertPosition = DropdownAlertPosition.Top,
264268
}) => {
269+
const windowDimensions = useWindowDimensions();
265270
const isIOS = Platform.OS === 'ios';
266271
const isAndroid = Platform.OS === 'android';
267272
const isBelowIOS11 = isIOS && Number(Platform.Version) < 11;
268-
const [top, setTop] = useState(0);
273+
const [dimValue, setDimValue] = useState(0);
269274
const [height, setHeight] = useState(99);
270275
const defaultAlertData: DropdownAlertData = {
271276
type: '',
@@ -288,24 +293,40 @@ const DropdownAlert: React.FunctionComponent<DropdownAlertProps> = ({
288293
_event: GestureResponderEvent,
289294
gestureState: PanResponderGestureState,
290295
) {
291-
if (
292-
panResponderEnabled &&
293-
gestureState.dy <= panResponderDismissDistance
294-
) {
295-
_dismiss(DropdownAlertDismissAction.Pan);
296+
if (panResponderEnabled) {
297+
switch (alertPosition) {
298+
case DropdownAlertPosition.Bottom:
299+
if (gestureState.dy >= Math.abs(panResponderDismissDistance)) {
300+
_dismiss(DropdownAlertDismissAction.Pan);
301+
}
302+
break;
303+
304+
default:
305+
if (gestureState.dy <= panResponderDismissDistance) {
306+
_dismiss(DropdownAlertDismissAction.Pan);
307+
}
308+
break;
309+
}
296310
}
297311
}
298312
return PanResponder.create({
299313
onStartShouldSetPanResponder: () => panResponderEnabled,
300-
onMoveShouldSetPanResponder: (_event, gestureState) => {
301-
if (panResponderEnabled) {
302-
return gestureState.dy <= panResponderMoveDistance;
303-
}
304-
return panResponderEnabled;
305-
},
314+
onMoveShouldSetPanResponder: () => panResponderEnabled,
306315
onPanResponderMove: (_event, gestureState) => {
307-
if (panResponderEnabled && gestureState.dy < 0) {
308-
setTop(gestureState.dy);
316+
if (panResponderEnabled) {
317+
switch (alertPosition) {
318+
case DropdownAlertPosition.Bottom:
319+
if (gestureState.dy > 0) {
320+
setDimValue(0 - gestureState.dy);
321+
}
322+
break;
323+
324+
default:
325+
if (gestureState.dy < 0) {
326+
setDimValue(gestureState.dy);
327+
}
328+
break;
329+
}
309330
}
310331
},
311332
onPanResponderRelease: (event, gestureState) =>
@@ -318,7 +339,7 @@ const DropdownAlert: React.FunctionComponent<DropdownAlertProps> = ({
318339
const panResponder = useMemo(_getPanResponder, [
319340
panResponderEnabled,
320341
panResponderDismissDistance,
321-
panResponderMoveDistance,
342+
alertPosition,
322343
]);
323344

324345
function _alertWithData(data?: DropdownAlertData) {
@@ -378,7 +399,7 @@ const DropdownAlert: React.FunctionComponent<DropdownAlertProps> = ({
378399
onDismissPress(alertDataRef.current);
379400
break;
380401
}
381-
setTop(0);
402+
setDimValue(0);
382403
queue.current.dequeue();
383404
if (!queue.current.isEmpty) {
384405
_alert(queue.current.first);
@@ -389,7 +410,7 @@ const DropdownAlert: React.FunctionComponent<DropdownAlertProps> = ({
389410
dismiss(_dismiss);
390411

391412
function _updateStatusBar(active = false, type = '') {
392-
if (updateStatusBar) {
413+
if (updateStatusBar && alertPosition === DropdownAlertPosition.Top) {
393414
if (isAndroid) {
394415
if (active) {
395416
let backgroundColor = activeStatusBarBackgroundColor;
@@ -545,23 +566,33 @@ const DropdownAlert: React.FunctionComponent<DropdownAlertProps> = ({
545566
}
546567

547568
function _getViewAnimatedStyle() {
569+
let viewStyle: ViewStyle = {
570+
// https://github.com/microsoft/TypeScript/issues/11465
571+
position: 'absolute' as 'absolute',
572+
top: dimValue,
573+
left: 0,
574+
right: 0,
575+
elevation,
576+
zIndex,
577+
};
578+
let animatedInterpolateConfig = {
579+
inputRange: [0, 1],
580+
outputRange: [0 - height, 0],
581+
};
582+
if (alertPosition === DropdownAlertPosition.Bottom) {
583+
viewStyle.top = undefined;
584+
viewStyle.bottom = dimValue;
585+
animatedInterpolateConfig.outputRange[0] =
586+
windowDimensions.height - height;
587+
}
548588
return [
549-
{
550-
// https://github.com/microsoft/TypeScript/issues/11465
551-
position: 'absolute' as 'absolute',
552-
top,
553-
left: 0,
554-
right: 0,
555-
elevation,
556-
zIndex,
557-
},
589+
viewStyle,
558590
{
559591
transform: [
560592
{
561-
translateY: animatedValue.current.interpolate({
562-
inputRange: [0, 1],
563-
outputRange: [0 - height, 0],
564-
}),
593+
translateY: animatedValue.current.interpolate(
594+
animatedInterpolateConfig,
595+
),
565596
},
566597
],
567598
},

README.md

+10-6
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@ An alert to notify users about an error or something else. It can be dismissed b
2929
import the library
3030

3131
```javascript
32-
import DropdownAlert, {DropdownAlertData, DropdownAlertType} from 'react-native-dropdownalert';
32+
import DropdownAlert, {
33+
DropdownAlertData,
34+
DropdownAlertType,
35+
} from 'react-native-dropdownalert';
3336
```
3437

3538
create an alert promise function variable
@@ -56,10 +59,11 @@ const alertData = await alert({
5659

5760
## Support
5861

59-
| react-native version | package version | reason |
60-
| -------------------- | :-------------: | ------------------- |
61-
| 0.50.0 | >=3.2.0 | use `SafeAreaView` |
62-
| 0.44.0 | >=2.12.0 | use `ViewPropTypes` |
62+
| react minium version | react-native minium version | package version | reason |
63+
| :------------------: | :-------------------------: | :-------------: | ------------------- |
64+
| v16.8.0 | v0.61.0 | v5.0.0 | use react hooks |
65+
| v16.0.0 | v0.50.0 | v3.2.0 | use `SafeAreaView` |
66+
| v16.0.0-alpha.6 | v0.44.0 | v2.12.0 | use `ViewPropTypes` |
6367

6468
## Using `children` prop
6569

@@ -80,6 +84,6 @@ Either way `DropdownAlert` will render these instead of the pre-defined child co
8084
## Caveats
8185

8286
- Modals can overlap `DropdownAlert`` if it is not inside the modal's document tree.
83-
- It is important you place the `DropdownAlert` above the `StackNavigator`.
87+
- It is important you place the `DropdownAlert` below the `StackNavigator`.
8488

8589
> Inspired by: [RKDropdownAlert](https://github.com/cwRichardKim/RKDropdownAlert)

example/App.tsx

+14-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import DropdownAlert, {
1414
DropdownAlertType,
1515
DropdownAlertColor,
1616
DropdownAlertProps,
17-
} from 'react-native-dropdownalert';
17+
} from './src/DropdownAlert';
1818
import NotificationIOS from './NotificationIOS';
1919
import NotificationAndroid from './NotificationAndroid';
2020

@@ -132,6 +132,19 @@ function App(): JSX.Element {
132132
},
133133
color: 'teal',
134134
},
135+
{
136+
name: 'Bottom',
137+
alertData: {
138+
type: DropdownAlertType.Info,
139+
title: 'Info',
140+
message: 'This demonstrates an info alert with bottom alert position.',
141+
},
142+
alertProps: {
143+
alertPosition: 'bottom',
144+
infoColor: 'green',
145+
},
146+
color: 'green',
147+
},
135148
];
136149

137150
function _renderItem(listItemIndex: ListItemIndex) {

screenshots/demo.gif

77.3 KB
Loading

0 commit comments

Comments
 (0)