Skip to content

Commit cfa7c92

Browse files
authored
Feat/new toast (#1696)
* add assets for new toast component * New toast implementation * New toast example screen * New toast genertate types * Add toast api file * Remove duplicate condition check * PR Review fix for onDismiss * Remove redundant View and throttleTime prop * Pass only relevant props to useToastAnimation * Use our Icon component instead of Image * rename setDismissTimer to setTimer * Add proper prop for triggering haptic feedback * minore code review refactor * Remove loader colors const * Update toast generated types * Refactor useToastPreset hook and how we use it * Use PanView only for the actual toast and not its attachment * Fix Constants import * Remove redundant const * rename ToastPresets to be plural and export them for users * Fix toast animation with an attatchment * Fix toast toggle delay due to async state change
1 parent 99cb4be commit cfa7c92

36 files changed

+915
-0
lines changed

demo/src/screens/MenuStructure.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ export const navigationData = {
157157
{title: 'Native TouchableOpacity', tags: 'touchable native', screen: 'unicorn.incubator.TouchableOpacityScreen'},
158158
{title: '(New) Dialog', tags: 'dialog modal popup alert', screen: 'unicorn.incubator.IncubatorDialogScreen'},
159159
{title: '(New) TextField', tags: 'text field input', screen: 'unicorn.components.IncubatorTextFieldScreen'},
160+
{title: '(New) Toast', tags: 'toast', screen: 'unicorn.components.IncubatorToastScreen'},
160161
{title: 'ExpandableOverlay', tags: 'text field expandable input picker', screen: 'unicorn.components.IncubatorExpandableOverlayScreen'},
161162
{title: 'WheelPicker (Incubator)', tags: 'wheel picker spinner experimental', screen: 'unicorn.incubator.WheelPickerScreen'},
162163
{title: 'Pan View', tags: 'pan swipe drag', screen: 'unicorn.incubator.PanViewScreen'},
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
import React, {Component} from 'react';
2+
import {ScrollView, StyleSheet} from 'react-native';
3+
import {Assets, Colors, View, Button, Text, Incubator} from 'react-native-ui-lib';
4+
import {renderMultipleSegmentOptions, renderBooleanOption, renderRadioGroup} from '../ExampleScreenPresenter';
5+
6+
const {Toast} = Incubator;
7+
8+
const TOAST_ACTIONS = {
9+
label: {label: 'Undo', onPress: () => console.warn('undo')},
10+
icon: {iconSource: Assets.icons.demo.plus, onPress: () => console.warn('add')}
11+
};
12+
13+
const TOAST_MESSAGES = {
14+
general: 'La formule Pass VIP illimité 5 mois est masquée',
15+
success: 'The action completed successfully.',
16+
failure: 'The action could not be completed.',
17+
offline: 'Check Your Internet Connection'
18+
};
19+
20+
export default class ToastsScreen extends Component {
21+
showToast = false; // keep this state in class instance for immediate response
22+
state = {
23+
visible: false,
24+
toastPosition: 'bottom' as Incubator.ToastProps['position'],
25+
isCustomContent: false,
26+
showLoader: false,
27+
selectedAction: '',
28+
hasAttachment: false,
29+
selectedPreset: '' as Incubator.ToastProps['preset'],
30+
isSwipeable: true
31+
};
32+
33+
toggleVisibility = () => {
34+
// Im using this for storing toast visible since setState is async and takes time to response
35+
this.showToast = !this.showToast;
36+
this.setState({
37+
visible: this.showToast
38+
});
39+
};
40+
41+
renderCustomContent = () => {
42+
return (
43+
<View flex padding-10 bg-white>
44+
<Text text60>This is a custom content</Text>
45+
<Text>
46+
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry
47+
standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to
48+
make a type specimen book.
49+
</Text>
50+
</View>
51+
);
52+
};
53+
54+
renderAboveToast = () => {
55+
return (
56+
<View flex bottom right paddingB-50 paddingR-20 pointerEvents={'box-none'}>
57+
<Button iconSource={Assets.icons.demo.dashboard} color={Colors.white} style={{height: 50, width: 50}}/>
58+
</View>
59+
);
60+
};
61+
62+
renderBelowToast = () => {
63+
return <Text>Attachment below toast</Text>;
64+
};
65+
66+
renderAttachment = () => {
67+
const {toastPosition, hasAttachment} = this.state;
68+
if (hasAttachment) {
69+
if (toastPosition === 'bottom') {
70+
return this.renderAboveToast();
71+
} else {
72+
return this.renderBelowToast();
73+
}
74+
}
75+
};
76+
77+
getAction = () => {
78+
const {selectedAction} = this.state;
79+
return TOAST_ACTIONS[selectedAction];
80+
};
81+
82+
getMessage = () => {
83+
const {selectedPreset} = this.state;
84+
85+
return TOAST_MESSAGES[selectedPreset] || TOAST_MESSAGES.general;
86+
};
87+
88+
renderToast = () => {
89+
const {visible, toastPosition, showLoader, isCustomContent, hasAttachment, selectedPreset, isSwipeable} =
90+
this.state;
91+
const action = this.getAction();
92+
93+
return (
94+
<Toast
95+
key={`${toastPosition}-${isCustomContent}-${hasAttachment}`}
96+
visible={visible}
97+
position={toastPosition}
98+
message={this.getMessage()}
99+
showLoader={showLoader}
100+
renderAttachment={this.renderAttachment}
101+
action={action}
102+
preset={selectedPreset}
103+
swipeable={isSwipeable}
104+
onDismiss={this.toggleVisibility}
105+
autoDismiss={3500}
106+
// backgroundColor={Colors.green70}
107+
// icon={Assets.icons.demo.add}
108+
// iconColor={Colors.green20}
109+
// style={{borderWidth: 1, borderColor: Colors.grey30}}
110+
// messageStyle={Typography.text80BO}
111+
>
112+
{isCustomContent ? this.renderCustomContent() : undefined}
113+
</Toast>
114+
);
115+
};
116+
117+
renderToggleButton = () => {
118+
return (
119+
<View centerH marginT-s5>
120+
<Button
121+
testID={`uilib.showToast`}
122+
marginT-10
123+
marginB-10
124+
label={'Toggle toast'}
125+
onPress={this.toggleVisibility}
126+
/>
127+
</View>
128+
);
129+
};
130+
131+
render() {
132+
return (
133+
<View flex padding-page>
134+
<Text h1 marginB-s4>
135+
Toast
136+
</Text>
137+
138+
<View flex style={styles.scrollViewContainer}>
139+
<ScrollView contentContainerStyle={styles.scrollView}>
140+
{renderMultipleSegmentOptions.call(this, 'Toast Position', 'toastPosition', [
141+
{label: 'Bottom', value: 'bottom'},
142+
{label: 'Top', value: 'top'}
143+
])}
144+
145+
{renderBooleanOption.call(this, 'Show Loader', 'showLoader')}
146+
{renderBooleanOption.call(this, 'Use custom content', 'isCustomContent')}
147+
{renderBooleanOption.call(this, 'With an attachment', 'hasAttachment')}
148+
{renderBooleanOption.call(this, 'Swipeable', 'isSwipeable')}
149+
150+
{renderRadioGroup.call(this,
151+
'Action',
152+
'selectedAction',
153+
{None: '', Label: 'label', Icon: 'icon'},
154+
{isRow: true})}
155+
156+
<Text h3 marginV-s2>
157+
Presets
158+
</Text>
159+
160+
{renderMultipleSegmentOptions.call(this, '', 'selectedPreset', [
161+
{label: 'None', value: ''},
162+
{label: 'General', value: 'general'},
163+
{label: 'Success', value: 'success'},
164+
{label: 'Failure', value: 'failure'},
165+
{label: 'Offline', value: 'offline'}
166+
])}
167+
168+
{this.renderToggleButton()}
169+
</ScrollView>
170+
</View>
171+
{this.renderToast()}
172+
</View>
173+
);
174+
}
175+
}
176+
177+
const styles = StyleSheet.create({
178+
scrollView: {
179+
paddingBottom: 80
180+
},
181+
color: {
182+
width: 30,
183+
height: 30,
184+
borderRadius: 15
185+
},
186+
selected: {
187+
borderWidth: 2,
188+
borderColor: Colors.grey10
189+
}
190+
});

demo/src/screens/incubatorScreens/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export function registerScreens(registrar) {
77
registrar('unicorn.incubator.IncubatorDialogScreen', () => require('./IncubatorDialogScreen').default);
88
registrar('unicorn.components.IncubatorExpandableOverlayScreen', () => require('./IncubatorExpandableOverlayScreen').default);
99
registrar('unicorn.components.IncubatorTextFieldScreen', () => require('./IncubatorTextFieldScreen').default);
10+
registrar('unicorn.components.IncubatorToastScreen', () => require('./IncubatorToastScreen').default);
1011
registrar('unicorn.incubator.PanViewScreen', () => require('./PanViewScreen').default);
1112
registrar('unicorn.incubator.TransitionViewScreen', () => require('./TransitionViewScreen').default);
1213
registrar('unicorn.incubator.WheelPickerScreen', () => gestureHandlerRootHOC(require('./WheelPickerScreen').default));

generatedTypes/src/incubator/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export { default as ChipsInput, ChipsInputProps, ChipsInputChangeReason, ChipsInputChipProps } from './ChipsInput';
22
export { default as ExpandableOverlay } from './expandableOverlay';
33
export { default as TextField, TextFieldProps, FieldContextType } from './TextField';
4+
export { default as Toast, ToastProps, ToastPresets } from './toast';
45
export { default as TouchableOpacity, TouchableOpacityProps } from './TouchableOpacity';
56
export { default as WheelPicker, WheelPickerProps, WheelPickerAlign, WheelPickerItemProps } from './WheelPicker';
67
export { default as PanView, PanViewProps, PanViewDirections, PanViewDismissThreshold } from './panView';
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { Animated } from 'react-native';
2+
import { ToastProps } from '../types';
3+
declare type UseToastAnimationProps = Pick<ToastProps, 'visible' | 'position' | 'onAnimationEnd' | 'enableHapticFeedback'> & {
4+
toastHeight?: number;
5+
playAccessibilityFeatures: () => void;
6+
setTimer: () => void;
7+
};
8+
declare const _default: ({ visible, position, toastHeight, onAnimationEnd, enableHapticFeedback, setTimer, playAccessibilityFeatures }: UseToastAnimationProps) => {
9+
isAnimating: boolean | undefined;
10+
toggleToast: (show?: boolean, { delay }?: {
11+
delay?: number | undefined;
12+
}) => void;
13+
opacityStyle: {
14+
opacity: Animated.AnimatedInterpolation;
15+
};
16+
translateStyle: {
17+
transform: {
18+
translateY: Animated.AnimatedInterpolation;
19+
}[];
20+
};
21+
};
22+
export default _default;
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import 'react';
2+
import { ToastProps } from '../types';
3+
declare const _default: ({ preset, icon, iconColor, message }: Pick<ToastProps, 'preset' | 'icon' | 'message' | 'iconColor'>) => {
4+
icon: any;
5+
iconColor: any;
6+
accessibilityMessage: string;
7+
};
8+
export default _default;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { ToastProps } from '../types';
2+
declare const _default: ({ autoDismiss, onDismiss }: Pick<ToastProps, 'autoDismiss' | 'onDismiss'>) => {
3+
clearTimer: () => void;
4+
setTimer: () => void;
5+
};
6+
export default _default;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import React from 'react';
2+
import { ToastProps, ToastPresets } from './types';
3+
export { ToastProps, ToastPresets };
4+
declare const _default: React.ComponentClass<ToastProps & {
5+
useCustomTheme?: boolean | undefined;
6+
}, any>;
7+
export default _default;
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/// <reference types="react" />
2+
import { ImageSourcePropType, StyleProp, TextStyle, ViewStyle } from 'react-native';
3+
import { ButtonProps } from '../../components/button';
4+
export declare enum ToastPresets {
5+
GENERAL = "general",
6+
SUCCESS = "success",
7+
FAILURE = "failure",
8+
OFFLINE = "offline"
9+
}
10+
export interface ToastProps {
11+
/**
12+
* Whether to show or hide the toast
13+
*/
14+
visible?: boolean;
15+
/**
16+
* The position of the toast. 'top' or 'bottom'.
17+
*/
18+
position?: 'top' | 'bottom';
19+
/**
20+
* Toast message
21+
*/
22+
message?: string;
23+
/**
24+
* Toast message style
25+
*/
26+
messageStyle?: StyleProp<TextStyle>;
27+
/**
28+
* should message be centered in the toast
29+
*/
30+
centerMessage?: boolean;
31+
/**
32+
* custom zIndex for toast
33+
*/
34+
zIndex?: number;
35+
/**
36+
* Custom elevation for Android
37+
*/
38+
elevation?: number;
39+
/**
40+
* a single action for the user (loader will override this)
41+
*/
42+
action?: ButtonProps;
43+
/**
44+
* should show a loader
45+
*/
46+
showLoader?: boolean;
47+
/**
48+
* callback for dismiss action
49+
*/
50+
onDismiss?: () => void;
51+
/**
52+
* whether to make the toast swipeable
53+
* require to pass onDismiss method to control visibility
54+
*/
55+
swipeable?: boolean;
56+
/**
57+
* number of milliseconds to automatically invoke the onDismiss callback
58+
*/
59+
autoDismiss?: number;
60+
/**
61+
* callback for end of component animation
62+
*/
63+
onAnimationEnd?: (visible?: boolean) => void;
64+
/**
65+
* render a custom view that will appear permanently above or below a Toast,
66+
* depends on the Toast's position, and animate with it when the Toast is made visible or dismissed
67+
*/
68+
renderAttachment?: () => JSX.Element | undefined;
69+
/**
70+
* The preset look for GENERAL, SUCCESS and FAILURE (Toast.presets.xxx)
71+
*/
72+
preset?: ToastPresets;
73+
/**
74+
* Whether to trigger an haptic feedback once the toast is shown (requires react-native-haptic-feedback dependency)
75+
*/
76+
enableHapticFeedback?: boolean;
77+
/**
78+
* Test Id for component
79+
*/
80+
testID?: string;
81+
/**
82+
* Toast style
83+
*/
84+
style?: StyleProp<ViewStyle>;
85+
/**
86+
* Toast container style
87+
*/
88+
containerStyle?: StyleProp<ViewStyle>;
89+
/**
90+
* a left icon
91+
*/
92+
icon?: ImageSourcePropType;
93+
/**
94+
* icon tint color
95+
*/
96+
iconColor?: string;
97+
/**
98+
* The background color of the toast
99+
*/
100+
backgroundColor?: string;
101+
}

src/incubator/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export {default as ChipsInput, ChipsInputProps, ChipsInputChangeReason, ChipsInp
22
export {default as ExpandableOverlay} from './expandableOverlay';
33
// @ts-ignore
44
export {default as TextField, TextFieldProps, FieldContextType} from './TextField';
5+
export {default as Toast, ToastProps, ToastPresets} from './toast';
56
export {default as TouchableOpacity, TouchableOpacityProps} from './TouchableOpacity';
67
export {default as WheelPicker, WheelPickerProps, WheelPickerAlign, WheelPickerItemProps} from './WheelPicker';
78
export {default as PanView, PanViewProps, PanViewDirections, PanViewDismissThreshold} from './panView';
256 Bytes
Loading
339 Bytes
Loading
397 Bytes
Loading
552 Bytes
Loading
676 Bytes
Loading
634 Bytes
Loading
916 Bytes
Loading
1.23 KB
Loading
1.76 KB
Loading
2.46 KB
Loading

src/incubator/toast/assets/info.png

711 Bytes
Loading
1.02 KB
Loading
1.33 KB
Loading
1.95 KB
Loading
2.61 KB
Loading
698 Bytes
Loading
1.02 KB
Loading
1.31 KB
Loading
1.95 KB
Loading
2.58 KB
Loading

0 commit comments

Comments
 (0)