Skip to content

Commit 6134f3b

Browse files
committed
feat: integrate alert functionality in App component and remove deprecated alert API and types
1 parent eeafe4a commit 6134f3b

File tree

13 files changed

+468
-5
lines changed

13 files changed

+468
-5
lines changed

example/src/App.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,21 @@
1-
import { Text, View, StyleSheet } from 'react-native';
1+
import { Text, View, StyleSheet, Pressable } from 'react-native';
2+
3+
import { alert, AlertContainer } from 'react-native-alert-queue';
24

35
export default function App() {
46
return (
57
<View style={styles.container}>
6-
<Text>Example will be here</Text>
8+
<Pressable
9+
onPress={() =>
10+
alert.show({
11+
title: 'Hello',
12+
message: 'I am an alert',
13+
})
14+
}
15+
>
16+
<Text>Show Alert</Text>
17+
</Pressable>
18+
<AlertContainer />
719
</View>
820
);
921
}

src/components/Alert/controller.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useCallback } from 'react';
22

33
import type { ViewProps } from './types';
4-
import { alert } from '../../alert.api';
4+
import { alert } from '../../containers/AlertContainer/alert.api';
55

66
export const useController = <R = unknown>({
77
onAwaitableDismiss,

src/components/Alert/index.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,9 @@ export const Alert: FC<ViewProps> = (props) => {
117117
const renderCloseButtonCb = useCallback(() => {
118118
return isDismissible ? (
119119
<Pressable onPress={onDismissButtonPress}>
120-
<CloseIcon width={24} height={24} fill="gray" />
120+
<View style={styles.closeButton}>
121+
<CloseIcon width={24} height={24} fill="gray" />
122+
</View>
121123
</Pressable>
122124
) : null;
123125
}, [isDismissible, onDismissButtonPress]);

src/components/Alert/styles.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export const styles = StyleSheet.create({
2828
marginHorizontal: 20,
2929
},
3030
titleContainer: {
31+
flexDirection: 'row',
3132
paddingTop: 0,
3233
paddingBottom: 15,
3334
paddingHorizontal: 20,

src/alert.api.ts renamed to src/containers/AlertContainer/alert.api.ts

File renamed without changes.
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { useCallback, useRef, useState } from 'react';
2+
import type { KeyboardEvent, NativeEventSubscription } from 'react-native';
3+
import {
4+
BackHandler,
5+
Keyboard,
6+
LayoutAnimation,
7+
Platform,
8+
TextInput,
9+
} from 'react-native';
10+
11+
export const usePlatformController = () => {
12+
const [keyboardHeight, setKeyboardHeight] = useState(0);
13+
14+
const onKeyboardDidShow = useCallback((e: KeyboardEvent) => {
15+
LayoutAnimation.easeInEaseOut();
16+
17+
setKeyboardHeight(e.endCoordinates.height);
18+
}, []);
19+
20+
const onKeyboardDidHide = useCallback(() => {
21+
LayoutAnimation.easeInEaseOut();
22+
23+
setKeyboardHeight(0);
24+
}, []);
25+
26+
const focusedTextInput =
27+
useRef<ReturnType<(typeof TextInput)['State']['currentlyFocusedInput']>>(
28+
null
29+
);
30+
31+
const androidBackButtonPressSub = useRef<NativeEventSubscription>(null);
32+
33+
const keyboardDidShowSub =
34+
useRef<ReturnType<(typeof Keyboard)['addListener']>>(null);
35+
36+
const keyboardDidHideSub =
37+
useRef<ReturnType<(typeof Keyboard)['addListener']>>(null);
38+
39+
const onShow = useCallback(() => {
40+
focusedTextInput.current = TextInput.State.currentlyFocusedInput();
41+
TextInput.State.blurTextInput(focusedTextInput.current);
42+
43+
if (Platform.OS === 'android') {
44+
androidBackButtonPressSub.current = BackHandler.addEventListener(
45+
'hardwareBackPress',
46+
() => {
47+
return true;
48+
}
49+
);
50+
}
51+
52+
if (Platform.OS === 'ios') {
53+
keyboardDidShowSub.current = Keyboard.addListener(
54+
'keyboardDidShow',
55+
onKeyboardDidShow
56+
);
57+
58+
keyboardDidHideSub.current = Keyboard.addListener(
59+
'keyboardDidHide',
60+
onKeyboardDidHide
61+
);
62+
}
63+
}, [onKeyboardDidHide, onKeyboardDidShow]);
64+
65+
const onBeforeUpdate = useCallback(() => {
66+
LayoutAnimation.easeInEaseOut();
67+
}, []);
68+
69+
const onHide = useCallback(() => {
70+
if (focusedTextInput.current) {
71+
TextInput.State.focusTextInput(focusedTextInput.current);
72+
}
73+
74+
if (Platform.OS === 'android') {
75+
androidBackButtonPressSub.current?.remove();
76+
}
77+
78+
if (Platform.OS === 'ios') {
79+
keyboardDidShowSub.current?.remove();
80+
keyboardDidHideSub.current?.remove();
81+
}
82+
83+
setKeyboardHeight(0);
84+
}, []);
85+
86+
return { bottomOffset: keyboardHeight, onBeforeUpdate, onHide, onShow };
87+
};
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
import { useCallback, useEffect, useRef, useState } from 'react';
2+
3+
import type { AlertProps, CurrentAlert, Props } from './types';
4+
5+
// import SuccessSvg from '../../../../assets/icons/primary/Tick Square.svg';
6+
import { EventEmitter } from '../../utils/EventEmitter';
7+
8+
import { usePlatformController } from './controller.platform';
9+
import { processAlertProps } from './utils';
10+
11+
export const useController = ({ animationDuration }: Props) => {
12+
const [isShown, setIsShown] = useState(false);
13+
const [isHiding, setIsHiding] = useState(false);
14+
const [currentAlert, setCurrentAlert] = useState<CurrentAlert>();
15+
const [hideEmitter] = useState(() => new EventEmitter());
16+
17+
const isMounted = useRef(false);
18+
const queue = useRef<CurrentAlert[]>([]);
19+
const hideTimeout = useRef(0);
20+
const isShownRef = useRef(false);
21+
22+
const { bottomOffset, onBeforeUpdate, onHide, onShow } =
23+
usePlatformController();
24+
25+
useEffect(() => {
26+
isMounted.current = true;
27+
28+
return () => {
29+
isMounted.current = false;
30+
};
31+
}, []);
32+
33+
const show = useCallback(
34+
(alert: AlertProps) => {
35+
return new Promise<unknown>((resolve) => {
36+
if (!isMounted.current) {
37+
resolve(undefined);
38+
39+
return;
40+
}
41+
42+
alert = processAlertProps(alert);
43+
44+
if (isShownRef.current) {
45+
queue.current.push({ ...alert, resolve });
46+
47+
return;
48+
}
49+
50+
setIsShown(true);
51+
52+
isShownRef.current = true;
53+
54+
onShow();
55+
56+
setCurrentAlert({ ...alert, resolve });
57+
});
58+
},
59+
[onShow]
60+
);
61+
62+
const update = useCallback(
63+
(alert: AlertProps = {}) => {
64+
if (!isShownRef.current) {
65+
return;
66+
}
67+
68+
alert = processAlertProps(alert);
69+
70+
onBeforeUpdate();
71+
72+
setCurrentAlert((prev) => ({ ...alert, resolve: prev!.resolve }));
73+
},
74+
[onBeforeUpdate]
75+
);
76+
77+
useEffect(() => {
78+
const handleHide = () => {
79+
const nextAlert = queue.current.shift();
80+
81+
if (nextAlert) {
82+
// prevent automatic batching in React 18
83+
setTimeout(() => {
84+
setIsShown(true);
85+
isShownRef.current = true;
86+
87+
onShow();
88+
89+
setCurrentAlert(nextAlert);
90+
}, 100);
91+
}
92+
};
93+
94+
hideEmitter.on(handleHide);
95+
96+
return () => hideEmitter.off(handleHide);
97+
}, [onShow, hideEmitter]);
98+
99+
const hide = useCallback(() => {
100+
if (!isShownRef.current || isHiding) {
101+
return;
102+
}
103+
104+
setIsHiding(true);
105+
106+
hideTimeout.current = +setTimeout(() => {
107+
onHide();
108+
109+
setCurrentAlert(undefined);
110+
setIsShown(false);
111+
isShownRef.current = false;
112+
setIsHiding(false);
113+
114+
hideEmitter.emit();
115+
}, animationDuration);
116+
}, [animationDuration, isHiding, onHide, hideEmitter]);
117+
118+
useEffect(() => {
119+
return () => {
120+
clearTimeout(hideTimeout.current);
121+
};
122+
}, []);
123+
124+
const clearQueue = useCallback(
125+
(hideDisplayedAlert?: boolean) => {
126+
queue.current = [];
127+
128+
if (hideDisplayedAlert) {
129+
hide();
130+
}
131+
},
132+
[hide]
133+
);
134+
135+
const getAlert = useCallback(() => currentAlert, [currentAlert]);
136+
137+
const confirm = useCallback(
138+
(alert?: AlertProps) => {
139+
const title = alert?.title || 'Are you sure?';
140+
141+
// const buttons: Required<AlertProps<boolean>>['buttons'] =
142+
// alert?.buttons?.length === 2
143+
// ? [
144+
// {
145+
// label: alert.buttons[0]!,
146+
// onAwaitableClick: (resolve) => resolve(true),
147+
// scheme: alert.confirmButtonScheme || 'primary',
148+
// },
149+
// {
150+
// label: alert.buttons[1]!,
151+
// onAwaitableClick: (resolve) => resolve(false),
152+
// },
153+
// ]
154+
// : [
155+
// {
156+
// label: 'Yes',
157+
// onAwaitableClick: (resolve) => resolve(true),
158+
// scheme: alert?.confirmButtonScheme || 'primary',
159+
// },
160+
// {
161+
// label: 'No',
162+
// onAwaitableClick: (resolve) => resolve(false),
163+
// },
164+
// ];
165+
166+
return show({
167+
...alert,
168+
title,
169+
});
170+
},
171+
172+
[show]
173+
);
174+
175+
const showError = useCallback(
176+
(error: Error) => {
177+
show({
178+
message: error.message,
179+
title: 'Oops! Something went wrong!',
180+
});
181+
},
182+
[show]
183+
);
184+
185+
const success = useCallback(
186+
(alert?: AlertProps) => {
187+
show({
188+
// icon: SuccessSvg,
189+
iconColor: 'icon.primary',
190+
iconSize: 72,
191+
title: 'Success!',
192+
...alert,
193+
});
194+
},
195+
[show]
196+
);
197+
198+
return {
199+
bottomOffset,
200+
clearQueue,
201+
confirm,
202+
currentAlert,
203+
getAlert,
204+
hide,
205+
isHiding,
206+
isShown,
207+
show,
208+
showError,
209+
success,
210+
update,
211+
};
212+
};

0 commit comments

Comments
 (0)