@umituz/react-native-design-system 2.3.6 → 2.3.7
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/package.json +7 -3
- package/src/index.ts +15 -0
- package/src/molecules/alerts/AlertBanner.tsx +188 -0
- package/src/molecules/alerts/AlertContainer.tsx +71 -0
- package/src/molecules/alerts/AlertInline.tsx +66 -0
- package/src/molecules/alerts/AlertModal.tsx +125 -0
- package/src/molecules/alerts/AlertProvider.tsx +15 -0
- package/src/molecules/alerts/AlertService.ts +57 -0
- package/src/molecules/alerts/AlertStore.ts +22 -0
- package/src/molecules/alerts/AlertToast.tsx +215 -0
- package/src/molecules/alerts/AlertTypes.ts +58 -0
- package/src/molecules/alerts/index.ts +47 -0
- package/src/molecules/alerts/useAlert.ts +68 -0
- package/src/molecules/index.ts +3 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-design-system",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.7",
|
|
4
4
|
"description": "Universal design system for React Native apps - Consolidated package with atoms, molecules, organisms, theme, typography, responsive and safe area utilities",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -63,7 +63,9 @@
|
|
|
63
63
|
"react-native-svg": ">=15.0.0",
|
|
64
64
|
"zustand": ">=5.0.0",
|
|
65
65
|
"expo-device": ">=5.0.0",
|
|
66
|
-
"expo-application": ">=5.0.0"
|
|
66
|
+
"expo-application": ">=5.0.0",
|
|
67
|
+
"@umituz/react-native-uuid": "latest",
|
|
68
|
+
"expo-crypto": ">=13.0.0"
|
|
67
69
|
},
|
|
68
70
|
"peerDependenciesMeta": {
|
|
69
71
|
"expo-linear-gradient": {
|
|
@@ -107,7 +109,9 @@
|
|
|
107
109
|
"zustand": "^5.0.2",
|
|
108
110
|
"expo-device": "~7.0.2",
|
|
109
111
|
"expo-application": "~5.9.1",
|
|
110
|
-
"expo-localization": "~16.0.1"
|
|
112
|
+
"expo-localization": "~16.0.1",
|
|
113
|
+
"@umituz/react-native-uuid": "latest",
|
|
114
|
+
"expo-crypto": "~14.0.0"
|
|
111
115
|
},
|
|
112
116
|
"publishConfig": {
|
|
113
117
|
"access": "public"
|
package/src/index.ts
CHANGED
|
@@ -243,10 +243,25 @@ export {
|
|
|
243
243
|
Grid,
|
|
244
244
|
List,
|
|
245
245
|
Container,
|
|
246
|
+
// Alerts
|
|
247
|
+
AlertBanner,
|
|
248
|
+
AlertToast,
|
|
249
|
+
AlertInline,
|
|
250
|
+
AlertModal,
|
|
251
|
+
AlertContainer,
|
|
252
|
+
AlertProvider,
|
|
253
|
+
useAlert,
|
|
254
|
+
alertService,
|
|
255
|
+
AlertType,
|
|
256
|
+
AlertMode,
|
|
257
|
+
AlertPosition,
|
|
246
258
|
type BaseModalProps,
|
|
247
259
|
type GridProps,
|
|
248
260
|
type ListProps,
|
|
249
261
|
type ContainerProps,
|
|
262
|
+
type Alert,
|
|
263
|
+
type AlertAction,
|
|
264
|
+
type AlertOptions,
|
|
250
265
|
} from './molecules';
|
|
251
266
|
|
|
252
267
|
// =============================================================================
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AlertBanner Component
|
|
3
|
+
*
|
|
4
|
+
* Displays a banner-style alert at the top or bottom of the screen.
|
|
5
|
+
* Full-width notification bar for important messages.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React from 'react';
|
|
9
|
+
import { StyleSheet, View, Pressable } from 'react-native';
|
|
10
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
11
|
+
import { AtomicText, AtomicIcon } from '../../atoms';
|
|
12
|
+
import { useAppDesignTokens } from '../../theme';
|
|
13
|
+
import { Alert, AlertType, AlertPosition } from './AlertTypes';
|
|
14
|
+
import { useAlertStore } from './AlertStore';
|
|
15
|
+
|
|
16
|
+
interface AlertBannerProps {
|
|
17
|
+
alert: Alert;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function AlertBanner({ alert }: AlertBannerProps) {
|
|
21
|
+
const dismissAlert = useAlertStore((state) => state.dismissAlert);
|
|
22
|
+
const insets = useSafeAreaInsets();
|
|
23
|
+
const tokens = useAppDesignTokens();
|
|
24
|
+
|
|
25
|
+
const handleDismiss = () => {
|
|
26
|
+
if (alert.dismissible) {
|
|
27
|
+
dismissAlert(alert.id);
|
|
28
|
+
alert.onDismiss?.();
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const getBackgroundColor = (type: AlertType): string => {
|
|
33
|
+
switch (type) {
|
|
34
|
+
case AlertType.SUCCESS:
|
|
35
|
+
return tokens.colors.success;
|
|
36
|
+
case AlertType.ERROR:
|
|
37
|
+
return tokens.colors.error;
|
|
38
|
+
case AlertType.WARNING:
|
|
39
|
+
return tokens.colors.warning;
|
|
40
|
+
case AlertType.INFO:
|
|
41
|
+
return tokens.colors.info;
|
|
42
|
+
default:
|
|
43
|
+
return tokens.colors.backgroundSecondary;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const backgroundColor = getBackgroundColor(alert.type);
|
|
48
|
+
const textColor = tokens.colors.textInverse;
|
|
49
|
+
const isTop = alert.position === AlertPosition.TOP;
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<View
|
|
53
|
+
style={[
|
|
54
|
+
styles.container,
|
|
55
|
+
{
|
|
56
|
+
backgroundColor,
|
|
57
|
+
paddingTop: isTop ? insets.top + tokens.spacing.sm : tokens.spacing.sm,
|
|
58
|
+
paddingBottom: isTop ? tokens.spacing.sm : insets.bottom + tokens.spacing.sm,
|
|
59
|
+
paddingHorizontal: tokens.spacing.md,
|
|
60
|
+
},
|
|
61
|
+
]}
|
|
62
|
+
testID={alert.testID}
|
|
63
|
+
>
|
|
64
|
+
<View style={styles.content}>
|
|
65
|
+
<View style={styles.row}>
|
|
66
|
+
{alert.icon && (
|
|
67
|
+
<View style={[styles.iconContainer, { marginRight: tokens.spacing.sm }]}>
|
|
68
|
+
<AtomicIcon
|
|
69
|
+
name={alert.icon}
|
|
70
|
+
customSize={20}
|
|
71
|
+
customColor={textColor}
|
|
72
|
+
/>
|
|
73
|
+
</View>
|
|
74
|
+
)}
|
|
75
|
+
|
|
76
|
+
<View style={styles.textContainer}>
|
|
77
|
+
<AtomicText
|
|
78
|
+
type="bodyMedium"
|
|
79
|
+
style={[styles.title, { color: textColor }]}
|
|
80
|
+
numberOfLines={1}
|
|
81
|
+
>
|
|
82
|
+
{alert.title}
|
|
83
|
+
</AtomicText>
|
|
84
|
+
|
|
85
|
+
{alert.message && (
|
|
86
|
+
<AtomicText
|
|
87
|
+
type="bodySmall"
|
|
88
|
+
style={[
|
|
89
|
+
styles.message,
|
|
90
|
+
{ color: textColor, marginTop: tokens.spacing.xs },
|
|
91
|
+
]}
|
|
92
|
+
numberOfLines={2}
|
|
93
|
+
>
|
|
94
|
+
{alert.message}
|
|
95
|
+
</AtomicText>
|
|
96
|
+
)}
|
|
97
|
+
</View>
|
|
98
|
+
|
|
99
|
+
{alert.dismissible && (
|
|
100
|
+
<Pressable
|
|
101
|
+
onPress={handleDismiss}
|
|
102
|
+
style={[styles.closeButton, { marginLeft: tokens.spacing.sm }]}
|
|
103
|
+
hitSlop={8}
|
|
104
|
+
>
|
|
105
|
+
<AtomicIcon name="close" customSize={20} customColor={textColor} />
|
|
106
|
+
</Pressable>
|
|
107
|
+
)}
|
|
108
|
+
</View>
|
|
109
|
+
|
|
110
|
+
{alert.actions && alert.actions.length > 0 && (
|
|
111
|
+
<View style={[styles.actionsContainer, { marginTop: tokens.spacing.sm }]}>
|
|
112
|
+
{alert.actions.map((action) => (
|
|
113
|
+
<Pressable
|
|
114
|
+
key={action.id}
|
|
115
|
+
onPress={async () => {
|
|
116
|
+
await action.onPress();
|
|
117
|
+
if (action.closeOnPress ?? true) {
|
|
118
|
+
handleDismiss();
|
|
119
|
+
}
|
|
120
|
+
}}
|
|
121
|
+
style={[
|
|
122
|
+
styles.actionButton,
|
|
123
|
+
{
|
|
124
|
+
paddingVertical: tokens.spacing.xs,
|
|
125
|
+
paddingHorizontal: tokens.spacing.sm,
|
|
126
|
+
marginRight: tokens.spacing.xs,
|
|
127
|
+
},
|
|
128
|
+
]}
|
|
129
|
+
>
|
|
130
|
+
<AtomicText
|
|
131
|
+
type="bodySmall"
|
|
132
|
+
style={[
|
|
133
|
+
styles.actionText,
|
|
134
|
+
{ color: textColor },
|
|
135
|
+
]}
|
|
136
|
+
>
|
|
137
|
+
{action.label}
|
|
138
|
+
</AtomicText>
|
|
139
|
+
</Pressable>
|
|
140
|
+
))}
|
|
141
|
+
</View>
|
|
142
|
+
)}
|
|
143
|
+
</View>
|
|
144
|
+
</View>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const styles = StyleSheet.create({
|
|
149
|
+
container: {
|
|
150
|
+
width: '100%',
|
|
151
|
+
},
|
|
152
|
+
content: {
|
|
153
|
+
paddingVertical: 4,
|
|
154
|
+
},
|
|
155
|
+
row: {
|
|
156
|
+
flexDirection: 'row',
|
|
157
|
+
alignItems: 'center',
|
|
158
|
+
},
|
|
159
|
+
iconContainer: {
|
|
160
|
+
justifyContent: 'center',
|
|
161
|
+
alignItems: 'center',
|
|
162
|
+
},
|
|
163
|
+
textContainer: {
|
|
164
|
+
flex: 1,
|
|
165
|
+
},
|
|
166
|
+
title: {
|
|
167
|
+
fontWeight: '700',
|
|
168
|
+
},
|
|
169
|
+
message: {
|
|
170
|
+
opacity: 0.9,
|
|
171
|
+
},
|
|
172
|
+
closeButton: {
|
|
173
|
+
justifyContent: 'center',
|
|
174
|
+
alignItems: 'center',
|
|
175
|
+
},
|
|
176
|
+
actionsContainer: {
|
|
177
|
+
flexDirection: 'row',
|
|
178
|
+
flexWrap: 'wrap',
|
|
179
|
+
},
|
|
180
|
+
actionButton: {
|
|
181
|
+
justifyContent: 'center',
|
|
182
|
+
alignItems: 'center',
|
|
183
|
+
},
|
|
184
|
+
actionText: {
|
|
185
|
+
fontWeight: '700',
|
|
186
|
+
textDecorationLine: 'underline',
|
|
187
|
+
},
|
|
188
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AlertContainer Component
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import { View, StyleSheet } from 'react-native';
|
|
7
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
8
|
+
import { useAppDesignTokens } from '../../theme';
|
|
9
|
+
import { useAlertStore } from './AlertStore';
|
|
10
|
+
import { AlertToast } from './AlertToast';
|
|
11
|
+
import { AlertBanner } from './AlertBanner';
|
|
12
|
+
import { AlertModal } from './AlertModal';
|
|
13
|
+
import { AlertMode } from './AlertTypes';
|
|
14
|
+
|
|
15
|
+
export const AlertContainer: React.FC = () => {
|
|
16
|
+
const alerts = useAlertStore((state) => state.alerts);
|
|
17
|
+
const insets = useSafeAreaInsets();
|
|
18
|
+
const tokens = useAppDesignTokens();
|
|
19
|
+
|
|
20
|
+
const toasts = alerts.filter((a) => a.mode === AlertMode.TOAST);
|
|
21
|
+
const banners = alerts.filter((a) => a.mode === AlertMode.BANNER);
|
|
22
|
+
const modals = alerts.filter((a) => a.mode === AlertMode.MODAL);
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<View style={styles.container} pointerEvents="box-none">
|
|
26
|
+
{/* Banners at top */}
|
|
27
|
+
<View style={[styles.bannerContainer, { paddingTop: insets.top }]}>
|
|
28
|
+
{banners.map((alert) => (
|
|
29
|
+
<AlertBanner key={alert.id} alert={alert} />
|
|
30
|
+
))}
|
|
31
|
+
</View>
|
|
32
|
+
|
|
33
|
+
{/* Toasts at top or bottom (default top) */}
|
|
34
|
+
<View style={[
|
|
35
|
+
styles.toastContainer,
|
|
36
|
+
{
|
|
37
|
+
top: insets.top + (banners.length > 0 ? tokens.spacing.xl * 2 : tokens.spacing.lg),
|
|
38
|
+
paddingHorizontal: tokens.spacing.md,
|
|
39
|
+
}
|
|
40
|
+
]}>
|
|
41
|
+
{toasts.map((alert) => (
|
|
42
|
+
<View key={alert.id} style={{ marginBottom: tokens.spacing.sm, width: '100%' }}>
|
|
43
|
+
<AlertToast alert={alert} />
|
|
44
|
+
</View>
|
|
45
|
+
))}
|
|
46
|
+
</View>
|
|
47
|
+
|
|
48
|
+
{/* Modals on top of everything */}
|
|
49
|
+
{modals.map((alert) => (
|
|
50
|
+
<AlertModal key={alert.id} alert={alert} />
|
|
51
|
+
))}
|
|
52
|
+
</View>
|
|
53
|
+
);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const styles = StyleSheet.create({
|
|
57
|
+
container: {
|
|
58
|
+
...StyleSheet.absoluteFillObject,
|
|
59
|
+
zIndex: 9999,
|
|
60
|
+
},
|
|
61
|
+
bannerContainer: {
|
|
62
|
+
width: '100%',
|
|
63
|
+
},
|
|
64
|
+
toastContainer: {
|
|
65
|
+
position: 'absolute',
|
|
66
|
+
left: 0,
|
|
67
|
+
right: 0,
|
|
68
|
+
alignItems: 'center',
|
|
69
|
+
zIndex: 10000,
|
|
70
|
+
},
|
|
71
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AlertInline Component
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import { StyleSheet, View } from 'react-native';
|
|
7
|
+
import { AtomicText } from '../../atoms';
|
|
8
|
+
import { useAppDesignTokens } from '../../theme';
|
|
9
|
+
import { Alert, AlertType } from './AlertTypes';
|
|
10
|
+
|
|
11
|
+
interface AlertInlineProps {
|
|
12
|
+
alert: Alert;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const AlertInline: React.FC<AlertInlineProps> = ({ alert }) => {
|
|
16
|
+
const tokens = useAppDesignTokens();
|
|
17
|
+
|
|
18
|
+
const getBorderColor = () => {
|
|
19
|
+
switch (alert.type) {
|
|
20
|
+
case AlertType.SUCCESS: return tokens.colors.success;
|
|
21
|
+
case AlertType.ERROR: return tokens.colors.error;
|
|
22
|
+
case AlertType.WARNING: return tokens.colors.warning;
|
|
23
|
+
case AlertType.INFO: return tokens.colors.info;
|
|
24
|
+
default: return tokens.colors.border;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const getBackgroundColor = () => {
|
|
29
|
+
switch (alert.type) {
|
|
30
|
+
case AlertType.SUCCESS: return tokens.colors.success + '15';
|
|
31
|
+
case AlertType.ERROR: return tokens.colors.error + '15';
|
|
32
|
+
case AlertType.WARNING: return tokens.colors.warning + '15';
|
|
33
|
+
case AlertType.INFO: return tokens.colors.info + '15';
|
|
34
|
+
default: return tokens.colors.backgroundSecondary;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<View style={[
|
|
40
|
+
styles.container,
|
|
41
|
+
{
|
|
42
|
+
borderColor: getBorderColor(),
|
|
43
|
+
backgroundColor: getBackgroundColor(),
|
|
44
|
+
borderRadius: tokens.borders.radius.sm,
|
|
45
|
+
padding: tokens.spacing.md,
|
|
46
|
+
marginVertical: tokens.spacing.sm,
|
|
47
|
+
}
|
|
48
|
+
]}>
|
|
49
|
+
<AtomicText type="bodyMedium" style={{ color: tokens.colors.textPrimary, fontWeight: '700' }}>
|
|
50
|
+
{alert.title}
|
|
51
|
+
</AtomicText>
|
|
52
|
+
{alert.message && (
|
|
53
|
+
<AtomicText type="bodySmall" style={{ color: tokens.colors.textSecondary, marginTop: tokens.spacing.xs }}>
|
|
54
|
+
{alert.message}
|
|
55
|
+
</AtomicText>
|
|
56
|
+
)}
|
|
57
|
+
</View>
|
|
58
|
+
);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const styles = StyleSheet.create({
|
|
62
|
+
container: {
|
|
63
|
+
borderWidth: 1,
|
|
64
|
+
width: '100%',
|
|
65
|
+
},
|
|
66
|
+
});
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AlertModal Component
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import { StyleSheet, View, Modal, Pressable } from 'react-native';
|
|
7
|
+
import { AtomicText, AtomicButton } from '../../atoms';
|
|
8
|
+
import { useAppDesignTokens } from '../../theme';
|
|
9
|
+
import { Alert, AlertType } from './AlertTypes';
|
|
10
|
+
import { useAlertStore } from './AlertStore';
|
|
11
|
+
|
|
12
|
+
interface AlertModalProps {
|
|
13
|
+
alert: Alert;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const AlertModal: React.FC<AlertModalProps> = ({ alert }) => {
|
|
17
|
+
const dismissAlert = useAlertStore((state) => state.dismissAlert);
|
|
18
|
+
const tokens = useAppDesignTokens();
|
|
19
|
+
|
|
20
|
+
const handleClose = () => {
|
|
21
|
+
dismissAlert(alert.id);
|
|
22
|
+
alert.onDismiss?.();
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const getHeaderColor = () => {
|
|
26
|
+
switch (alert.type) {
|
|
27
|
+
case AlertType.SUCCESS: return tokens.colors.success;
|
|
28
|
+
case AlertType.ERROR: return tokens.colors.error;
|
|
29
|
+
case AlertType.WARNING: return tokens.colors.warning;
|
|
30
|
+
case AlertType.INFO: return tokens.colors.info;
|
|
31
|
+
default: return tokens.colors.primary;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<Modal
|
|
37
|
+
visible
|
|
38
|
+
transparent
|
|
39
|
+
animationType="fade"
|
|
40
|
+
onRequestClose={handleClose}
|
|
41
|
+
>
|
|
42
|
+
<View style={styles.overlay}>
|
|
43
|
+
<Pressable
|
|
44
|
+
style={styles.backdrop}
|
|
45
|
+
onPress={alert.dismissible ? handleClose : undefined}
|
|
46
|
+
/>
|
|
47
|
+
<View style={[
|
|
48
|
+
styles.modal,
|
|
49
|
+
{
|
|
50
|
+
backgroundColor: tokens.colors.backgroundPrimary,
|
|
51
|
+
borderRadius: tokens.borders.radius.lg,
|
|
52
|
+
borderWidth: 1,
|
|
53
|
+
borderColor: tokens.colors.border,
|
|
54
|
+
}
|
|
55
|
+
]}>
|
|
56
|
+
<View style={[styles.header, { backgroundColor: getHeaderColor() }]}>
|
|
57
|
+
<AtomicText type="titleLarge" style={{ color: tokens.colors.textInverse }}>
|
|
58
|
+
{alert.title}
|
|
59
|
+
</AtomicText>
|
|
60
|
+
</View>
|
|
61
|
+
|
|
62
|
+
<View style={[styles.content, { padding: tokens.spacing.lg }]}>
|
|
63
|
+
{alert.message && (
|
|
64
|
+
<AtomicText type="bodyMedium" style={{ color: tokens.colors.textPrimary, textAlign: 'center' }}>
|
|
65
|
+
{alert.message}
|
|
66
|
+
</AtomicText>
|
|
67
|
+
)}
|
|
68
|
+
|
|
69
|
+
<View style={[styles.actions, { marginTop: tokens.spacing.lg, gap: tokens.spacing.sm }]}>
|
|
70
|
+
{alert.actions.map((action) => (
|
|
71
|
+
<AtomicButton
|
|
72
|
+
key={action.id}
|
|
73
|
+
title={action.label}
|
|
74
|
+
variant={action.style === 'destructive' ? 'danger' : action.style === 'secondary' ? 'secondary' : 'primary'}
|
|
75
|
+
onPress={async () => {
|
|
76
|
+
await action.onPress();
|
|
77
|
+
if (action.closeOnPress ?? true) {
|
|
78
|
+
handleClose();
|
|
79
|
+
}
|
|
80
|
+
}}
|
|
81
|
+
fullWidth
|
|
82
|
+
/>
|
|
83
|
+
))}
|
|
84
|
+
{alert.actions.length === 0 && (
|
|
85
|
+
<AtomicButton
|
|
86
|
+
title="Close"
|
|
87
|
+
onPress={handleClose}
|
|
88
|
+
fullWidth
|
|
89
|
+
/>
|
|
90
|
+
)}
|
|
91
|
+
</View>
|
|
92
|
+
</View>
|
|
93
|
+
</View>
|
|
94
|
+
</View>
|
|
95
|
+
</Modal>
|
|
96
|
+
);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const styles = StyleSheet.create({
|
|
100
|
+
overlay: {
|
|
101
|
+
flex: 1,
|
|
102
|
+
justifyContent: 'center',
|
|
103
|
+
alignItems: 'center',
|
|
104
|
+
padding: 20,
|
|
105
|
+
},
|
|
106
|
+
backdrop: {
|
|
107
|
+
...StyleSheet.absoluteFillObject,
|
|
108
|
+
backgroundColor: 'rgba(0,0,0,0.6)',
|
|
109
|
+
},
|
|
110
|
+
modal: {
|
|
111
|
+
width: '100%',
|
|
112
|
+
maxWidth: 400,
|
|
113
|
+
overflow: 'hidden',
|
|
114
|
+
},
|
|
115
|
+
header: {
|
|
116
|
+
padding: 20,
|
|
117
|
+
alignItems: 'center',
|
|
118
|
+
},
|
|
119
|
+
content: {
|
|
120
|
+
alignItems: 'center',
|
|
121
|
+
},
|
|
122
|
+
actions: {
|
|
123
|
+
width: '100%',
|
|
124
|
+
},
|
|
125
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AlertProvider Component
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import { AlertContainer } from './AlertContainer';
|
|
7
|
+
|
|
8
|
+
export const AlertProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
|
9
|
+
return (
|
|
10
|
+
<>
|
|
11
|
+
{children}
|
|
12
|
+
<AlertContainer />
|
|
13
|
+
</>
|
|
14
|
+
);
|
|
15
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Alert Service
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { generateUUID } from '@umituz/react-native-uuid';
|
|
6
|
+
import { Alert, AlertType, AlertMode, AlertOptions, AlertPosition } from './AlertTypes';
|
|
7
|
+
|
|
8
|
+
export class AlertService {
|
|
9
|
+
/**
|
|
10
|
+
* Creates a base Alert object with defaults
|
|
11
|
+
*/
|
|
12
|
+
static createAlert(
|
|
13
|
+
type: AlertType,
|
|
14
|
+
defaultMode: AlertMode,
|
|
15
|
+
title: string,
|
|
16
|
+
message?: string,
|
|
17
|
+
options?: AlertOptions
|
|
18
|
+
): Alert {
|
|
19
|
+
const id = generateUUID();
|
|
20
|
+
const mode = options?.mode || defaultMode;
|
|
21
|
+
|
|
22
|
+
// Default position based on mode
|
|
23
|
+
const defaultPosition = mode === AlertMode.BANNER ? AlertPosition.TOP : AlertPosition.TOP;
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
id,
|
|
27
|
+
type,
|
|
28
|
+
mode,
|
|
29
|
+
title,
|
|
30
|
+
message,
|
|
31
|
+
position: options?.position || defaultPosition,
|
|
32
|
+
icon: options?.icon,
|
|
33
|
+
actions: options?.actions || [],
|
|
34
|
+
dismissible: options?.dismissible ?? true,
|
|
35
|
+
duration: options?.duration,
|
|
36
|
+
onDismiss: options?.onDismiss,
|
|
37
|
+
testID: options?.testID,
|
|
38
|
+
createdAt: Date.now(),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
static createSuccessAlert(title: string, message?: string, options?: AlertOptions): Alert {
|
|
43
|
+
return this.createAlert(AlertType.SUCCESS, AlertMode.TOAST, title, message, options);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
static createErrorAlert(title: string, message?: string, options?: AlertOptions): Alert {
|
|
47
|
+
return this.createAlert(AlertType.ERROR, AlertMode.TOAST, title, message, options);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
static createWarningAlert(title: string, message?: string, options?: AlertOptions): Alert {
|
|
51
|
+
return this.createAlert(AlertType.WARNING, AlertMode.TOAST, title, message, options);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
static createInfoAlert(title: string, message?: string, options?: AlertOptions): Alert {
|
|
55
|
+
return this.createAlert(AlertType.INFO, AlertMode.TOAST, title, message, options);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Alert Store
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { create } from 'zustand';
|
|
6
|
+
import { Alert } from './AlertTypes';
|
|
7
|
+
|
|
8
|
+
interface AlertState {
|
|
9
|
+
alerts: Alert[];
|
|
10
|
+
addAlert: (alert: Alert) => void;
|
|
11
|
+
dismissAlert: (id: string) => void;
|
|
12
|
+
clearAlerts: () => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const useAlertStore = create<AlertState>((set) => ({
|
|
16
|
+
alerts: [],
|
|
17
|
+
addAlert: (alert) => set((state) => ({ alerts: [...state.alerts, alert] })),
|
|
18
|
+
dismissAlert: (id) => set((state) => ({
|
|
19
|
+
alerts: state.alerts.filter((a) => a.id !== id)
|
|
20
|
+
})),
|
|
21
|
+
clearAlerts: () => set({ alerts: [] }),
|
|
22
|
+
}));
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AlertToast Component
|
|
3
|
+
*
|
|
4
|
+
* Displays a toast-style alert.
|
|
5
|
+
* Floats on top of content.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React from 'react';
|
|
9
|
+
import { StyleSheet, View, Pressable, StyleProp, ViewStyle } from 'react-native';
|
|
10
|
+
import { AtomicText, AtomicIcon } from '../../atoms';
|
|
11
|
+
import { useAppDesignTokens } from '../../theme';
|
|
12
|
+
import { Alert, AlertType } from './AlertTypes';
|
|
13
|
+
import { useAlertStore } from './AlertStore';
|
|
14
|
+
|
|
15
|
+
interface AlertToastProps {
|
|
16
|
+
alert: Alert;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function AlertToast({ alert }: AlertToastProps) {
|
|
20
|
+
const dismissAlert = useAlertStore((state) => state.dismissAlert);
|
|
21
|
+
const tokens = useAppDesignTokens();
|
|
22
|
+
|
|
23
|
+
const handleDismiss = () => {
|
|
24
|
+
if (alert.dismissible) {
|
|
25
|
+
dismissAlert(alert.id);
|
|
26
|
+
alert.onDismiss?.();
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const getBackgroundColor = (type: AlertType): string => {
|
|
31
|
+
switch (type) {
|
|
32
|
+
case AlertType.SUCCESS:
|
|
33
|
+
return tokens.colors.success;
|
|
34
|
+
case AlertType.ERROR:
|
|
35
|
+
return tokens.colors.error;
|
|
36
|
+
case AlertType.WARNING:
|
|
37
|
+
return tokens.colors.warning;
|
|
38
|
+
case AlertType.INFO:
|
|
39
|
+
return tokens.colors.info;
|
|
40
|
+
default:
|
|
41
|
+
return tokens.colors.backgroundSecondary;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const getActionButtonStyle = (style: 'primary' | 'secondary' | 'destructive' | undefined): StyleProp<ViewStyle> => {
|
|
46
|
+
switch (style) {
|
|
47
|
+
case 'primary':
|
|
48
|
+
return { backgroundColor: tokens.colors.backgroundPrimary };
|
|
49
|
+
case 'secondary':
|
|
50
|
+
return {
|
|
51
|
+
backgroundColor: 'transparent',
|
|
52
|
+
borderWidth: 1,
|
|
53
|
+
borderColor: tokens.colors.textInverse,
|
|
54
|
+
};
|
|
55
|
+
case 'destructive':
|
|
56
|
+
return { backgroundColor: tokens.colors.error };
|
|
57
|
+
default:
|
|
58
|
+
return { backgroundColor: tokens.colors.backgroundSecondary };
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const getActionTextColor = (style: 'primary' | 'secondary' | 'destructive' | undefined): string => {
|
|
63
|
+
switch (style) {
|
|
64
|
+
case 'primary':
|
|
65
|
+
return tokens.colors.textPrimary;
|
|
66
|
+
case 'secondary':
|
|
67
|
+
return tokens.colors.textInverse;
|
|
68
|
+
case 'destructive':
|
|
69
|
+
return tokens.colors.textInverse;
|
|
70
|
+
default:
|
|
71
|
+
return tokens.colors.textPrimary;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const backgroundColor = getBackgroundColor(alert.type);
|
|
76
|
+
const textColor = tokens.colors.textInverse;
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<View
|
|
80
|
+
style={[
|
|
81
|
+
styles.container,
|
|
82
|
+
{
|
|
83
|
+
backgroundColor,
|
|
84
|
+
padding: tokens.spacing.md,
|
|
85
|
+
borderRadius: tokens.borders.radius.md,
|
|
86
|
+
},
|
|
87
|
+
]}
|
|
88
|
+
testID={alert.testID}
|
|
89
|
+
>
|
|
90
|
+
<Pressable onPress={handleDismiss} style={styles.content}>
|
|
91
|
+
<View style={styles.row}>
|
|
92
|
+
{alert.icon && (
|
|
93
|
+
<View style={[styles.iconContainer, { marginRight: tokens.spacing.sm }]}>
|
|
94
|
+
<AtomicIcon
|
|
95
|
+
name={alert.icon}
|
|
96
|
+
customSize={20}
|
|
97
|
+
customColor={textColor}
|
|
98
|
+
/>
|
|
99
|
+
</View>
|
|
100
|
+
)}
|
|
101
|
+
|
|
102
|
+
<View style={styles.textContainer}>
|
|
103
|
+
<AtomicText
|
|
104
|
+
type="bodyMedium"
|
|
105
|
+
style={[styles.title, { color: textColor }]}
|
|
106
|
+
numberOfLines={2}
|
|
107
|
+
>
|
|
108
|
+
{alert.title}
|
|
109
|
+
</AtomicText>
|
|
110
|
+
|
|
111
|
+
{alert.message && (
|
|
112
|
+
<AtomicText
|
|
113
|
+
type="bodySmall"
|
|
114
|
+
style={[
|
|
115
|
+
styles.message,
|
|
116
|
+
{ color: textColor, marginTop: tokens.spacing.xs },
|
|
117
|
+
]}
|
|
118
|
+
numberOfLines={3}
|
|
119
|
+
>
|
|
120
|
+
{alert.message}
|
|
121
|
+
</AtomicText>
|
|
122
|
+
)}
|
|
123
|
+
</View>
|
|
124
|
+
|
|
125
|
+
{alert.dismissible && (
|
|
126
|
+
<Pressable
|
|
127
|
+
onPress={handleDismiss}
|
|
128
|
+
style={[styles.closeButton, { marginLeft: tokens.spacing.sm }]}
|
|
129
|
+
hitSlop={8}
|
|
130
|
+
>
|
|
131
|
+
<AtomicIcon name="close" customSize={20} customColor={textColor} />
|
|
132
|
+
</Pressable>
|
|
133
|
+
)}
|
|
134
|
+
</View>
|
|
135
|
+
|
|
136
|
+
{alert.actions && alert.actions.length > 0 && (
|
|
137
|
+
<View style={[styles.actionsContainer, { marginTop: tokens.spacing.sm }]}>
|
|
138
|
+
{alert.actions.map((action) => (
|
|
139
|
+
<Pressable
|
|
140
|
+
key={action.id}
|
|
141
|
+
onPress={async () => {
|
|
142
|
+
await action.onPress();
|
|
143
|
+
if (action.closeOnPress ?? true) {
|
|
144
|
+
handleDismiss();
|
|
145
|
+
}
|
|
146
|
+
}}
|
|
147
|
+
style={[
|
|
148
|
+
styles.actionButton,
|
|
149
|
+
{
|
|
150
|
+
paddingVertical: tokens.spacing.xs,
|
|
151
|
+
paddingHorizontal: tokens.spacing.sm,
|
|
152
|
+
marginRight: tokens.spacing.xs,
|
|
153
|
+
borderRadius: tokens.borders.radius.sm,
|
|
154
|
+
},
|
|
155
|
+
getActionButtonStyle(action.style),
|
|
156
|
+
]}
|
|
157
|
+
>
|
|
158
|
+
<AtomicText
|
|
159
|
+
type="bodySmall"
|
|
160
|
+
style={[
|
|
161
|
+
styles.actionText,
|
|
162
|
+
{ color: getActionTextColor(action.style) },
|
|
163
|
+
]}
|
|
164
|
+
>
|
|
165
|
+
{action.label}
|
|
166
|
+
</AtomicText>
|
|
167
|
+
</Pressable>
|
|
168
|
+
))}
|
|
169
|
+
</View>
|
|
170
|
+
)}
|
|
171
|
+
</Pressable>
|
|
172
|
+
</View>
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const styles = StyleSheet.create({
|
|
177
|
+
container: {
|
|
178
|
+
width: '100%',
|
|
179
|
+
},
|
|
180
|
+
content: {
|
|
181
|
+
flex: 1,
|
|
182
|
+
},
|
|
183
|
+
row: {
|
|
184
|
+
flexDirection: 'row',
|
|
185
|
+
alignItems: 'center',
|
|
186
|
+
},
|
|
187
|
+
iconContainer: {
|
|
188
|
+
justifyContent: 'center',
|
|
189
|
+
alignItems: 'center',
|
|
190
|
+
},
|
|
191
|
+
textContainer: {
|
|
192
|
+
flex: 1,
|
|
193
|
+
},
|
|
194
|
+
title: {
|
|
195
|
+
fontWeight: '700',
|
|
196
|
+
},
|
|
197
|
+
message: {
|
|
198
|
+
opacity: 0.9,
|
|
199
|
+
},
|
|
200
|
+
closeButton: {
|
|
201
|
+
justifyContent: 'center',
|
|
202
|
+
alignItems: 'center',
|
|
203
|
+
},
|
|
204
|
+
actionsContainer: {
|
|
205
|
+
flexDirection: 'row',
|
|
206
|
+
flexWrap: 'wrap',
|
|
207
|
+
},
|
|
208
|
+
actionButton: {
|
|
209
|
+
justifyContent: 'center',
|
|
210
|
+
alignItems: 'center',
|
|
211
|
+
},
|
|
212
|
+
actionText: {
|
|
213
|
+
fontWeight: '700',
|
|
214
|
+
},
|
|
215
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Alert Entity and Types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export enum AlertType {
|
|
6
|
+
SUCCESS = 'success',
|
|
7
|
+
ERROR = 'error',
|
|
8
|
+
WARNING = 'warning',
|
|
9
|
+
INFO = 'info',
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export enum AlertMode {
|
|
13
|
+
TOAST = 'toast',
|
|
14
|
+
BANNER = 'banner',
|
|
15
|
+
MODAL = 'modal',
|
|
16
|
+
INLINE = 'inline',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export enum AlertPosition {
|
|
20
|
+
TOP = 'top',
|
|
21
|
+
BOTTOM = 'bottom',
|
|
22
|
+
CENTER = 'center',
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface AlertAction {
|
|
26
|
+
id: string;
|
|
27
|
+
label: string;
|
|
28
|
+
onPress: () => Promise<void> | void;
|
|
29
|
+
style?: 'primary' | 'secondary' | 'destructive';
|
|
30
|
+
closeOnPress?: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface AlertOptions {
|
|
34
|
+
mode?: AlertMode;
|
|
35
|
+
duration?: number;
|
|
36
|
+
dismissible?: boolean;
|
|
37
|
+
onDismiss?: () => void;
|
|
38
|
+
icon?: string;
|
|
39
|
+
actions?: AlertAction[];
|
|
40
|
+
testID?: string;
|
|
41
|
+
position?: AlertPosition;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface Alert {
|
|
45
|
+
id: string;
|
|
46
|
+
type: AlertType;
|
|
47
|
+
mode: AlertMode;
|
|
48
|
+
title: string;
|
|
49
|
+
message?: string;
|
|
50
|
+
position: AlertPosition;
|
|
51
|
+
icon?: string;
|
|
52
|
+
actions: AlertAction[];
|
|
53
|
+
dismissible: boolean;
|
|
54
|
+
duration?: number;
|
|
55
|
+
createdAt: number;
|
|
56
|
+
testID?: string;
|
|
57
|
+
onDismiss?: () => void;
|
|
58
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Alerts Molecule - Public API
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export * from './AlertTypes';
|
|
6
|
+
export { useAlertStore } from './AlertStore';
|
|
7
|
+
export { AlertService } from './AlertService';
|
|
8
|
+
export { AlertBanner } from './AlertBanner';
|
|
9
|
+
export { AlertToast } from './AlertToast';
|
|
10
|
+
export { AlertInline } from './AlertInline';
|
|
11
|
+
export { AlertModal } from './AlertModal';
|
|
12
|
+
export { AlertContainer } from './AlertContainer';
|
|
13
|
+
export { AlertProvider } from './AlertProvider';
|
|
14
|
+
export { useAlert } from './useAlert';
|
|
15
|
+
|
|
16
|
+
import { AlertService } from './AlertService';
|
|
17
|
+
import { useAlertStore } from './AlertStore';
|
|
18
|
+
import { AlertOptions } from './AlertTypes';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Convenience alert service for use outside of components
|
|
22
|
+
*/
|
|
23
|
+
export const alertService = {
|
|
24
|
+
error: (title: string, message?: string, options?: AlertOptions) => {
|
|
25
|
+
const alert = AlertService.createErrorAlert(title, message, options);
|
|
26
|
+
useAlertStore.getState().addAlert(alert);
|
|
27
|
+
return alert.id;
|
|
28
|
+
},
|
|
29
|
+
success: (title: string, message?: string, options?: AlertOptions) => {
|
|
30
|
+
const alert = AlertService.createSuccessAlert(title, message, options);
|
|
31
|
+
useAlertStore.getState().addAlert(alert);
|
|
32
|
+
return alert.id;
|
|
33
|
+
},
|
|
34
|
+
warning: (title: string, message?: string, options?: AlertOptions) => {
|
|
35
|
+
const alert = AlertService.createWarningAlert(title, message, options);
|
|
36
|
+
useAlertStore.getState().addAlert(alert);
|
|
37
|
+
return alert.id;
|
|
38
|
+
},
|
|
39
|
+
info: (title: string, message?: string, options?: AlertOptions) => {
|
|
40
|
+
const alert = AlertService.createInfoAlert(title, message, options);
|
|
41
|
+
useAlertStore.getState().addAlert(alert);
|
|
42
|
+
return alert.id;
|
|
43
|
+
},
|
|
44
|
+
dismiss: (id: string) => {
|
|
45
|
+
useAlertStore.getState().dismissAlert(id);
|
|
46
|
+
},
|
|
47
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useAlert Hook
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { useCallback } from 'react';
|
|
6
|
+
import { useAlertStore } from './AlertStore';
|
|
7
|
+
import { AlertService } from './AlertService';
|
|
8
|
+
import { AlertType, AlertMode, AlertOptions } from './AlertTypes';
|
|
9
|
+
|
|
10
|
+
export interface UseAlertReturn {
|
|
11
|
+
show: (type: AlertType, mode: AlertMode, title: string, message?: string, options?: AlertOptions) => string;
|
|
12
|
+
showSuccess: (title: string, message?: string, options?: AlertOptions) => string;
|
|
13
|
+
showError: (title: string, message?: string, options?: AlertOptions) => string;
|
|
14
|
+
showWarning: (title: string, message?: string, options?: AlertOptions) => string;
|
|
15
|
+
showInfo: (title: string, message?: string, options?: AlertOptions) => string;
|
|
16
|
+
dismissAlert: (id: string) => void;
|
|
17
|
+
clearAlerts: () => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const useAlert = (): UseAlertReturn => {
|
|
21
|
+
const { addAlert, dismissAlert, clearAlerts } = useAlertStore();
|
|
22
|
+
|
|
23
|
+
const show = useCallback((
|
|
24
|
+
type: AlertType,
|
|
25
|
+
mode: AlertMode,
|
|
26
|
+
title: string,
|
|
27
|
+
message?: string,
|
|
28
|
+
options?: AlertOptions
|
|
29
|
+
) => {
|
|
30
|
+
const alert = AlertService.createAlert(type, mode, title, message, options);
|
|
31
|
+
addAlert(alert);
|
|
32
|
+
return alert.id;
|
|
33
|
+
}, [addAlert]);
|
|
34
|
+
|
|
35
|
+
const showSuccess = useCallback((title: string, message?: string, options?: AlertOptions) => {
|
|
36
|
+
const alert = AlertService.createSuccessAlert(title, message, options);
|
|
37
|
+
addAlert(alert);
|
|
38
|
+
return alert.id;
|
|
39
|
+
}, [addAlert]);
|
|
40
|
+
|
|
41
|
+
const showError = useCallback((title: string, message?: string, options?: AlertOptions) => {
|
|
42
|
+
const alert = AlertService.createErrorAlert(title, message, options);
|
|
43
|
+
addAlert(alert);
|
|
44
|
+
return alert.id;
|
|
45
|
+
}, [addAlert]);
|
|
46
|
+
|
|
47
|
+
const showWarning = useCallback((title: string, message?: string, options?: AlertOptions) => {
|
|
48
|
+
const alert = AlertService.createWarningAlert(title, message, options);
|
|
49
|
+
addAlert(alert);
|
|
50
|
+
return alert.id;
|
|
51
|
+
}, [addAlert]);
|
|
52
|
+
|
|
53
|
+
const showInfo = useCallback((title: string, message?: string, options?: AlertOptions) => {
|
|
54
|
+
const alert = AlertService.createInfoAlert(title, message, options);
|
|
55
|
+
addAlert(alert);
|
|
56
|
+
return alert.id;
|
|
57
|
+
}, [addAlert]);
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
show,
|
|
61
|
+
showSuccess,
|
|
62
|
+
showError,
|
|
63
|
+
showWarning,
|
|
64
|
+
showInfo,
|
|
65
|
+
dismissAlert,
|
|
66
|
+
clearAlerts,
|
|
67
|
+
};
|
|
68
|
+
};
|
package/src/molecules/index.ts
CHANGED