@chem-po/react-native 0.0.35 → 0.0.37

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.
Files changed (32) hide show
  1. package/lib/commonjs/components/button/DeleteButton.js +84 -43
  2. package/lib/commonjs/components/button/DeleteButton.js.map +1 -1
  3. package/lib/commonjs/components/button/LoadingButton.js +31 -19
  4. package/lib/commonjs/components/button/LoadingButton.js.map +1 -1
  5. package/lib/commonjs/components/theme/colorMode/DarkModeToggle.js +2 -2
  6. package/lib/commonjs/contexts/root.js +12 -6
  7. package/lib/commonjs/contexts/root.js.map +1 -1
  8. package/lib/commonjs/hooks/useThemeState.js +23 -4
  9. package/lib/commonjs/hooks/useThemeState.js.map +1 -1
  10. package/lib/module/components/button/DeleteButton.js +87 -46
  11. package/lib/module/components/button/DeleteButton.js.map +1 -1
  12. package/lib/module/components/button/LoadingButton.js +32 -20
  13. package/lib/module/components/button/LoadingButton.js.map +1 -1
  14. package/lib/module/components/theme/colorMode/DarkModeToggle.js +2 -2
  15. package/lib/module/contexts/root.js +12 -6
  16. package/lib/module/contexts/root.js.map +1 -1
  17. package/lib/module/hooks/useThemeState.js +23 -5
  18. package/lib/module/hooks/useThemeState.js.map +1 -1
  19. package/lib/typescript/components/button/DeleteButton.d.ts +8 -1
  20. package/lib/typescript/components/button/DeleteButton.d.ts.map +1 -1
  21. package/lib/typescript/components/button/LoadingButton.d.ts +2 -0
  22. package/lib/typescript/components/button/LoadingButton.d.ts.map +1 -1
  23. package/lib/typescript/contexts/root.d.ts +5 -2
  24. package/lib/typescript/contexts/root.d.ts.map +1 -1
  25. package/lib/typescript/hooks/useThemeState.d.ts +2 -1
  26. package/lib/typescript/hooks/useThemeState.d.ts.map +1 -1
  27. package/package.json +4 -3
  28. package/src/components/button/DeleteButton.tsx +111 -40
  29. package/src/components/button/LoadingButton.tsx +41 -19
  30. package/src/components/theme/colorMode/DarkModeToggle.tsx +2 -2
  31. package/src/contexts/root.tsx +30 -14
  32. package/src/hooks/useThemeState.ts +31 -6
@@ -1 +1 @@
1
- {"version":3,"file":"LoadingButton.d.ts","sourceRoot":"","sources":["../../../../src/components/button/LoadingButton.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAC7C,OAAO,KAAK,EAAE,EAAE,SAAS,EAAqD,MAAM,OAAO,CAAA;AAC3F,OAAO,EAGL,SAAS,EAGT,SAAS,EAET,SAAS,EACV,MAAM,cAAc,CAAA;AAGrB,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,CAAC,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,IAAI,CAAC,CAAA;IAC5C,QAAQ,EAAE,SAAS,CAAA;IACnB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAA;IAC5B,YAAY,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAA;IACnC,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAA;IAChC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE;QACL,IAAI,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAA;QACnD,IAAI,CAAC,EAAE,MAAM,CAAA;KACd,CAAA;IACD,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAmGtD,CAAA"}
1
+ {"version":3,"file":"LoadingButton.d.ts","sourceRoot":"","sources":["../../../../src/components/button/LoadingButton.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAC7C,OAAO,KAAK,EAAE,EAAE,SAAS,EAAqD,MAAM,OAAO,CAAA;AAC3F,OAAO,EAGL,SAAS,EAGT,SAAS,EAET,SAAS,EACV,MAAM,cAAc,CAAA;AAGrB,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,CAAC,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,IAAI,CAAC,CAAA;IAC5C,QAAQ,EAAE,SAAS,CAAA;IACnB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAA;IAC5B,YAAY,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAA;IACnC,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAA;IAChC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,SAAS,GAAG,OAAO,CAAA;IAC7B,IAAI,CAAC,EAAE;QACL,IAAI,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAA;QACnD,IAAI,CAAC,EAAE,MAAM,CAAA;KACd,CAAA;IACD,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,SAAS,CAAC,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,QAAQ,CAAC,CAAA;CAClD;AAED,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAsHtD,CAAA"}
@@ -1,12 +1,15 @@
1
1
  import { BackendAdapterInterface, ColorMode, Theme } from '@chem-po/core';
2
2
  import { ChempoProps } from '@chem-po/react';
3
- import React, { PropsWithChildren } from 'react';
3
+ import React, { FC, PropsWithChildren } from 'react';
4
4
  import { MD3Type, MD3TypescaleKey } from 'react-native-paper/lib/typescript/types';
5
5
  export type FontConfig = Partial<Record<MD3TypescaleKey, Partial<MD3Type>>>;
6
+ export type MiddlewareProvider = FC<PropsWithChildren>;
6
7
  export interface ChempoNativeProviderProps<BackendAdapter extends BackendAdapterInterface> extends PropsWithChildren<Pick<ChempoProps<BackendAdapter>, 'backendAdapter' | 'assets'>> {
7
8
  theme?: Theme;
8
9
  fonts?: FontConfig;
9
10
  initialColorMode?: ColorMode;
11
+ colorModeProp?: string;
12
+ middlewareProvider?: MiddlewareProvider;
10
13
  }
11
- export declare const ChempoNativeProvider: <BackendAdapter extends BackendAdapterInterface>({ theme: themeProp, initialColorMode, children, fonts, ...props }: ChempoNativeProviderProps<BackendAdapter>) => React.JSX.Element;
14
+ export declare const ChempoNativeProvider: <BackendAdapter extends BackendAdapterInterface>({ theme: themeProp, initialColorMode, children, fonts, colorModeProp, middlewareProvider: Middleware, ...props }: ChempoNativeProviderProps<BackendAdapter>) => React.JSX.Element;
12
15
  //# sourceMappingURL=root.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"root.d.ts","sourceRoot":"","sources":["../../../src/contexts/root.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,eAAe,CAAA;AACzE,OAAO,EAAE,WAAW,EAA4B,MAAM,gBAAgB,CAAA;AACtE,OAAO,KAAK,EAAE,EAAE,iBAAiB,EAAW,MAAM,OAAO,CAAA;AAIzD,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAA;AAOlF,MAAM,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,eAAe,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAA;AAC3E,MAAM,WAAW,yBAAyB,CAAC,cAAc,SAAS,uBAAuB,CACvF,SAAQ,iBAAiB,CAAC,IAAI,CAAC,WAAW,CAAC,cAAc,CAAC,EAAE,gBAAgB,GAAG,QAAQ,CAAC,CAAC;IACzF,KAAK,CAAC,EAAE,KAAK,CAAA;IACb,KAAK,CAAC,EAAE,UAAU,CAAA;IAClB,gBAAgB,CAAC,EAAE,SAAS,CAAA;CAC7B;AA4FD,eAAO,MAAM,oBAAoB,GAAI,cAAc,SAAS,uBAAuB,EAAE,mEAMlF,yBAAyB,CAAC,cAAc,CAAC,sBAiB3C,CAAA"}
1
+ {"version":3,"file":"root.d.ts","sourceRoot":"","sources":["../../../src/contexts/root.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,eAAe,CAAA;AACzE,OAAO,EAAE,WAAW,EAA4B,MAAM,gBAAgB,CAAA;AACtE,OAAO,KAAK,EAAE,EAAE,EAAE,EAAE,iBAAiB,EAAW,MAAM,OAAO,CAAA;AAK7D,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAA;AAQlF,MAAM,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,eAAe,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAA;AAC3E,MAAM,MAAM,kBAAkB,GAAG,EAAE,CAAC,iBAAiB,CAAC,CAAA;AACtD,MAAM,WAAW,yBAAyB,CAAC,cAAc,SAAS,uBAAuB,CACvF,SAAQ,iBAAiB,CAAC,IAAI,CAAC,WAAW,CAAC,cAAc,CAAC,EAAE,gBAAgB,GAAG,QAAQ,CAAC,CAAC;IACzF,KAAK,CAAC,EAAE,KAAK,CAAA;IACb,KAAK,CAAC,EAAE,UAAU,CAAA;IAClB,gBAAgB,CAAC,EAAE,SAAS,CAAA;IAC5B,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,kBAAkB,CAAC,EAAE,kBAAkB,CAAA;CACxC;AA4FD,eAAO,MAAM,oBAAoB,GAAI,cAAc,SAAS,uBAAuB,EAAE,kHAQlF,yBAAyB,CAAC,cAAc,CAAC,sBA0B3C,CAAA"}
@@ -1,8 +1,9 @@
1
1
  import { ColorMode, Theme } from '@chem-po/core';
2
2
  import { ThemeProps } from '@chem-po/react';
3
3
  export interface UseThemeProps {
4
- initialColorMode?: ColorMode;
4
+ colorModeProp?: string;
5
5
  theme?: Theme;
6
+ initialColorMode?: ColorMode;
6
7
  }
7
8
  export declare const useThemeState: (props?: UseThemeProps) => ThemeProps;
8
9
  //# sourceMappingURL=useThemeState.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useThemeState.d.ts","sourceRoot":"","sources":["../../../src/hooks/useThemeState.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,eAAe,CAAA;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAG3C,MAAM,WAAW,aAAa;IAC5B,gBAAgB,CAAC,EAAE,SAAS,CAAA;IAC5B,KAAK,CAAC,EAAE,KAAK,CAAA;CACd;AAED,eAAO,MAAM,aAAa,GAAI,QAAQ,aAAa,eAQlD,CAAA"}
1
+ {"version":3,"file":"useThemeState.d.ts","sourceRoot":"","sources":["../../../src/hooks/useThemeState.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,eAAe,CAAA;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAI3C,MAAM,WAAW,aAAa;IAC5B,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,KAAK,CAAC,EAAE,KAAK,CAAA;IAEb,gBAAgB,CAAC,EAAE,SAAS,CAAA;CAC7B;AAED,eAAO,MAAM,aAAa,GAAI,QAAQ,aAAa,eA8BlD,CAAA"}
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@chem-po/react-native",
3
3
  "author": "Elan Canfield",
4
4
  "license": "MIT",
5
- "version": "0.0.35",
5
+ "version": "0.0.37",
6
6
  "main": "lib/commonjs/index.js",
7
7
  "types": "lib/typescript/index.d.ts",
8
8
  "source": "src/index.ts",
@@ -46,14 +46,15 @@
46
46
  "react-native": "0.79.5",
47
47
  "react-native-element-dropdown": "^2.12.4",
48
48
  "react-native-gesture-handler": "~2.24.0",
49
+ "react-native-safe-area-context": "5.4.0",
49
50
  "react-native-reanimated": "~3.17.4",
50
51
  "react-native-notifier": "^2.0.0",
51
52
  "react-native-paper": "^5.14.3",
52
53
  "react-native-paper-dates": "^0.22.42",
53
54
  "react-native-svg": "15.11.2",
54
55
  "zustand": "^4.3.3",
55
- "@chem-po/react": "0.0.35",
56
- "@chem-po/core": "0.0.35"
56
+ "@chem-po/core": "0.0.37",
57
+ "@chem-po/react": "0.0.37"
57
58
  },
58
59
  "devDependencies": {
59
60
  "@babel/core": "^7.26.0",
@@ -1,8 +1,9 @@
1
- import { useToast } from '@chem-po/react'
1
+ import { useBackgroundColor, useColorModeValue, useToast } from '@chem-po/react'
2
2
  import { Ionicons } from '@expo/vector-icons'
3
- import React, { ReactNode, useCallback, useEffect, useRef, useState } from 'react'
4
- import { ActivityIndicator, Animated, Modal, StyleSheet, Text, View, ViewStyle } from 'react-native'
3
+ import React, { ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react'
4
+ import { ActivityIndicator, Animated, StyleSheet, Text, View, ViewStyle } from 'react-native'
5
5
  import { Pressable } from 'react-native-gesture-handler'
6
+ import { Modal, Portal } from 'react-native-paper'
6
7
  import { Txt } from '../text/Txt'
7
8
 
8
9
  const defaultAlertBodyText = "Are you sure? You can't undo this action afterwards."
@@ -15,42 +16,48 @@ export const DeleteConfirmAlert = ({
15
16
  actionName = 'Delete',
16
17
  actionLoading,
17
18
  itemName,
19
+ color,
20
+ cancelText = 'CANCEL',
18
21
  }: {
19
22
  confirmActive: boolean
20
23
  onCancel: () => void
21
24
  onConfirm: () => void
22
25
  body?: string | ReactNode
23
26
  actionLoading?: boolean
27
+ color: string
24
28
  actionName?: string
29
+ cancelText?: string
25
30
  itemName: string
26
31
  }) => {
32
+ const backgroundColor = useBackgroundColor(100)
27
33
  return (
28
- <Modal
29
- transparent={true}
30
- visible={confirmActive}
31
- onRequestClose={onCancel}
32
- animationType="fade">
33
- <View style={styles.overlay}>
34
- <View style={styles.alertContainer}>
34
+ <Portal>
35
+ <Modal style={styles.modal} visible={confirmActive} onDismiss={onCancel}>
36
+ <View style={[styles.alertContainer, { backgroundColor }]}>
35
37
  <Txt style={styles.alertTitle}>
36
38
  {actionName} {itemName}?
37
39
  </Txt>
38
40
  {typeof body === 'string' ? <Txt style={styles.alertBody}>{body}</Txt> : body}
39
41
  <View style={styles.buttonContainer}>
40
42
  <Pressable style={styles.cancelButton} onPress={onCancel}>
41
- <Txt style={styles.cancelText}>Cancel</Txt>
43
+ <Txt style={styles.cancelText}>{cancelText}</Txt>
42
44
  </Pressable>
43
- <Pressable style={styles.confirmButton} onPress={onConfirm} disabled={actionLoading}>
45
+ <Pressable
46
+ style={[styles.confirmButton, { backgroundColor: color }]}
47
+ onPress={onConfirm}
48
+ disabled={actionLoading}>
44
49
  {actionLoading ? (
45
50
  <ActivityIndicator color="#fff" />
46
51
  ) : (
47
- <Txt style={[styles.buttonText, styles.confirmButtonText]}>{actionName}</Txt>
52
+ <Txt style={[styles.buttonText, styles.confirmButtonText]}>
53
+ {actionName.toUpperCase()}
54
+ </Txt>
48
55
  )}
49
56
  </Pressable>
50
57
  </View>
51
58
  </View>
52
- </View>
53
- </Modal>
59
+ </Modal>
60
+ </Portal>
54
61
  )
55
62
  }
56
63
 
@@ -62,7 +69,23 @@ export const DeleteButton: React.FC<{
62
69
  text?: string
63
70
  alertBody?: string | ReactNode
64
71
  style?: ViewStyle
65
- }> = ({ onDelete, itemName, noConfirm, alertBody, text, actionName = 'Delete', style }) => {
72
+ color?: string
73
+ variant?: 'outline' | 'solid'
74
+ iconSize?: number
75
+ iconProps?: React.ComponentProps<typeof Ionicons>
76
+ }> = ({
77
+ onDelete,
78
+ itemName,
79
+ noConfirm,
80
+ alertBody,
81
+ text,
82
+ actionName = 'Delete',
83
+ style,
84
+ color: colorProp,
85
+ variant = 'outline',
86
+ iconSize = 18,
87
+ iconProps,
88
+ }) => {
66
89
  const { showError } = useToast()
67
90
  const [deleteLoading, setDeleteLoading] = useState(false)
68
91
  const [confirmActive, setConfirmActive] = useState(false)
@@ -70,6 +93,10 @@ export const DeleteButton: React.FC<{
70
93
  const isMounted = useRef(true)
71
94
  const contentOpacity = useRef(new Animated.Value(1)).current
72
95
  const loaderOpacity = useRef(new Animated.Value(0)).current
96
+ const defaultColor = useColorModeValue('#dd2222', '#ff7777')
97
+ const color = colorProp ?? defaultColor
98
+
99
+ const textColor = useMemo(() => (variant === 'solid' ? 'white' : color), [variant, color])
73
100
 
74
101
  useEffect(() => {
75
102
  isMounted.current = true
@@ -122,7 +149,15 @@ export const DeleteButton: React.FC<{
122
149
  <>
123
150
  {text ? (
124
151
  <Pressable
125
- style={[styles.button, style]}
152
+ style={[
153
+ styles.button,
154
+ {
155
+ borderColor: color,
156
+ backgroundColor: variant === 'solid' ? color : 'transparent',
157
+ borderWidth: variant === 'outline' ? 1 : 0,
158
+ },
159
+ style,
160
+ ]}
126
161
  onPress={() => {
127
162
  if (noConfirm) handleDelete()
128
163
  else setConfirmActive(true)
@@ -130,30 +165,55 @@ export const DeleteButton: React.FC<{
130
165
  disabled={deleteLoading}>
131
166
  <View style={styles.buttonContent}>
132
167
  <Animated.View style={[styles.contentContainer, { opacity: contentOpacity }]}>
133
- <Ionicons name="trash" size={20} color="red" />
134
- <Text style={styles.buttonText}>{text}</Text>
168
+ <Ionicons
169
+ name="trash"
170
+ size={iconSize}
171
+ color={textColor}
172
+ {...iconProps}
173
+ style={[variant === 'solid' ? styles.shadow : {}, iconProps?.style]}
174
+ />
175
+ <Text
176
+ style={[
177
+ styles.buttonText,
178
+ { color: textColor },
179
+ variant === 'solid' ? styles.shadow : undefined,
180
+ ]}>
181
+ {text}
182
+ </Text>
135
183
  </Animated.View>
136
-
137
184
  <Animated.View style={[styles.loaderContainer, { opacity: loaderOpacity }]}>
138
- <ActivityIndicator color="#ff0000" size="small" />
185
+ <ActivityIndicator color={textColor} size="small" />
139
186
  </Animated.View>
140
187
  </View>
141
188
  </Pressable>
142
189
  ) : (
143
190
  <Pressable
144
- style={[styles.iconButton, style]}
191
+ style={[
192
+ styles.iconButton,
193
+ {
194
+ borderColor: color,
195
+ borderWidth: variant === 'outline' ? 1 : 0,
196
+ backgroundColor: variant === 'solid' ? color : 'transparent',
197
+ },
198
+ style,
199
+ ]}
145
200
  onPress={() => {
146
201
  if (noConfirm) handleDelete()
147
202
  else setConfirmActive(true)
148
203
  }}
149
204
  disabled={deleteLoading}>
150
- <View style={styles.buttonContent}>
205
+ <View style={styles.iconButtonContent}>
151
206
  <Animated.View style={[styles.contentContainer, { opacity: contentOpacity }]}>
152
- <Ionicons name="trash" size={24} color="#ee0000" />
207
+ <Ionicons
208
+ name="trash"
209
+ size={iconSize}
210
+ color={textColor}
211
+ {...iconProps}
212
+ style={[variant === 'solid' ? styles.shadow : {}, iconProps?.style]}
213
+ />
153
214
  </Animated.View>
154
-
155
215
  <Animated.View style={[styles.loaderContainer, { opacity: loaderOpacity }]}>
156
- <ActivityIndicator color="#ff0000" size="small" />
216
+ <ActivityIndicator color={textColor} size="small" />
157
217
  </Animated.View>
158
218
  </View>
159
219
  </Pressable>
@@ -169,6 +229,7 @@ export const DeleteButton: React.FC<{
169
229
  actionName={actionName}
170
230
  body={alertBody}
171
231
  itemName={itemName}
232
+ color={color}
172
233
  />
173
234
  )}
174
235
  </>
@@ -176,18 +237,16 @@ export const DeleteButton: React.FC<{
176
237
  }
177
238
 
178
239
  const styles = StyleSheet.create({
179
- overlay: {
180
- flex: 1,
181
- justifyContent: 'center',
182
- alignItems: 'center',
183
- backgroundColor: 'rgba(0, 0, 0, 0.5)',
184
- },
185
240
  alertContainer: {
186
- width: '80%',
187
- maxWidth: 400,
188
241
  backgroundColor: 'white',
189
242
  borderRadius: 10,
190
243
  padding: 20,
244
+ maxWidth: 300,
245
+ alignItems: 'center',
246
+ },
247
+ modal: {
248
+ flex: 1,
249
+ justifyContent: 'center',
191
250
  alignItems: 'center',
192
251
  },
193
252
  alertTitle: {
@@ -198,6 +257,7 @@ const styles = StyleSheet.create({
198
257
  alertBody: {
199
258
  fontSize: 16,
200
259
  marginBottom: 20,
260
+ textAlign: 'center',
201
261
  },
202
262
  buttonContainer: {
203
263
  flexDirection: 'row',
@@ -226,7 +286,6 @@ const styles = StyleSheet.create({
226
286
  alignItems: 'center',
227
287
  },
228
288
  buttonText: {
229
- color: 'red',
230
289
  fontWeight: 'bold',
231
290
  },
232
291
  confirmButtonText: {
@@ -238,14 +297,11 @@ const styles = StyleSheet.create({
238
297
  button: {
239
298
  flexDirection: 'row',
240
299
  alignItems: 'center',
241
- padding: 10,
242
- backgroundColor: 'transparent',
300
+ borderRadius: 4,
243
301
  },
244
302
  buttonContent: {
245
303
  flexDirection: 'row',
246
304
  gap: 3,
247
- borderWidth: 1,
248
- borderColor: 'red',
249
305
  paddingHorizontal: 13,
250
306
  paddingVertical: 5,
251
307
  borderRadius: 4,
@@ -265,9 +321,24 @@ const styles = StyleSheet.create({
265
321
  alignItems: 'center',
266
322
  justifyContent: 'center',
267
323
  },
324
+ shadow: {
325
+ textShadowColor: 'rgba(0, 0, 0, 0.3)',
326
+ textShadowOffset: { width: 1, height: 1 },
327
+ textShadowRadius: 4,
328
+ },
268
329
  iconButton: {
269
- padding: 10,
270
330
  backgroundColor: 'transparent',
331
+ borderRadius: 4,
332
+ width: 32,
333
+ height: 32,
334
+ alignItems: 'center',
335
+ justifyContent: 'center',
336
+ },
337
+ iconButtonContent: {
338
+ flexDirection: 'row',
339
+ alignItems: 'center',
340
+ justifyContent: 'center',
341
+ gap: 3,
271
342
  },
272
343
  loading: {
273
344
  position: 'absolute',
@@ -1,4 +1,4 @@
1
- import { useBorderColor, useTextColor, useToast } from '@chem-po/react'
1
+ import { useTextColor, useToast } from '@chem-po/react'
2
2
  import { Ionicons } from '@expo/vector-icons'
3
3
  import React, { ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react'
4
4
  import {
@@ -20,11 +20,13 @@ export interface LoadingButtonProps {
20
20
  contentStyle?: StyleProp<ViewStyle>
21
21
  textStyle?: StyleProp<TextStyle>
22
22
  color?: string
23
+ variant?: 'outline' | 'solid'
23
24
  icon?: {
24
25
  name: React.ComponentProps<typeof Ionicons>['name']
25
26
  size?: number
26
27
  }
27
28
  disabled?: boolean
29
+ iconProps?: React.ComponentProps<typeof Ionicons>
28
30
  }
29
31
 
30
32
  export const LoadingButton: React.FC<LoadingButtonProps> = ({
@@ -34,8 +36,10 @@ export const LoadingButton: React.FC<LoadingButtonProps> = ({
34
36
  contentStyle,
35
37
  textStyle,
36
38
  color: colorProp,
39
+ variant = 'outline',
37
40
  icon,
38
41
  disabled = false,
42
+ iconProps,
39
43
  }) => {
40
44
  const [loading, setLoading] = useState(false)
41
45
  const isMounted = useRef(true)
@@ -89,12 +93,21 @@ export const LoadingButton: React.FC<LoadingButtonProps> = ({
89
93
 
90
94
  const fallbackColor = useTextColor()
91
95
  const color = useMemo(() => colorProp ?? fallbackColor, [colorProp, fallbackColor])
92
- const borderColor = useBorderColor()
96
+ const textColor = useMemo(() => (variant === 'solid' ? 'white' : color), [variant, color])
97
+ const backgroundColor = useMemo(
98
+ () => (variant === 'solid' ? color : 'transparent'),
99
+ [variant, color],
100
+ )
93
101
  return (
94
102
  <Pressable
95
103
  style={[
96
104
  styles.button,
97
- { borderColor: color, backgroundColor: disabled ? '#f0f0f0' : 'transparent' },
105
+ {
106
+ opacity: disabled ? 0.6 : 1,
107
+ borderColor: color,
108
+ borderWidth: variant === 'outline' ? 1 : 0,
109
+ backgroundColor,
110
+ },
98
111
  style,
99
112
  ]}
100
113
  onPress={() => {
@@ -102,17 +115,25 @@ export const LoadingButton: React.FC<LoadingButtonProps> = ({
102
115
  }}
103
116
  disabled={loading || disabled}>
104
117
  <View
105
- style={[
106
- styles.buttonContent,
107
- { borderColor: disabled ? '#ccc' : borderColor },
108
- contentStyle,
109
- ]}>
118
+ style={[styles.buttonContent, { borderColor: disabled ? '#ccc' : color }, contentStyle]}>
110
119
  <Animated.View style={[styles.contentContainer, { opacity: contentOpacity }]}>
111
120
  {icon && (
112
- <Ionicons name={icon.name} size={icon.size ?? 20} color={disabled ? '#ccc' : color} />
121
+ <Ionicons
122
+ name={icon.name}
123
+ size={icon.size ?? 20}
124
+ color={textColor}
125
+ {...iconProps}
126
+ style={[variant === 'solid' ? styles.shadow : {}, iconProps?.style]}
127
+ />
113
128
  )}
114
129
  {typeof children === 'string' ? (
115
- <Text style={[styles.buttonText, { color: disabled ? '#ccc' : color }, textStyle]}>
130
+ <Text
131
+ style={[
132
+ styles.buttonText,
133
+ { color: textColor },
134
+ variant === 'solid' ? styles.shadow : {},
135
+ textStyle,
136
+ ]}>
116
137
  {children}
117
138
  </Text>
118
139
  ) : (
@@ -121,7 +142,7 @@ export const LoadingButton: React.FC<LoadingButtonProps> = ({
121
142
  </Animated.View>
122
143
 
123
144
  <Animated.View style={[styles.loaderContainer, { opacity: loaderOpacity }]}>
124
- <ActivityIndicator color={color} size="small" />
145
+ <ActivityIndicator color={textColor} size="small" />
125
146
  </Animated.View>
126
147
  </View>
127
148
  </Pressable>
@@ -133,24 +154,20 @@ const styles = StyleSheet.create({
133
154
  flexDirection: 'row',
134
155
  alignItems: 'center',
135
156
  backgroundColor: 'transparent',
157
+ borderRadius: 4,
158
+ paddingHorizontal: 12,
159
+ paddingVertical: 6,
136
160
  },
137
161
  buttonContent: {
138
- flexDirection: 'row',
139
- gap: 8,
140
- borderWidth: 1,
141
- paddingHorizontal: 16,
142
- paddingVertical: 8,
143
- borderRadius: 4,
144
162
  alignItems: 'center',
145
163
  justifyContent: 'center',
146
- minHeight: 40,
147
164
  position: 'relative',
148
165
  },
149
166
  contentContainer: {
150
167
  flexDirection: 'row',
151
168
  alignItems: 'center',
152
169
  justifyContent: 'center',
153
- gap: 8,
170
+ gap: 4,
154
171
  },
155
172
  loaderContainer: {
156
173
  ...StyleSheet.absoluteFillObject,
@@ -160,4 +177,9 @@ const styles = StyleSheet.create({
160
177
  buttonText: {
161
178
  fontWeight: 'bold',
162
179
  },
180
+ shadow: {
181
+ textShadowColor: 'rgba(0, 0, 0, 0.3)',
182
+ textShadowOffset: { width: 1, height: 1 },
183
+ textShadowRadius: 4,
184
+ },
163
185
  })
@@ -38,8 +38,8 @@ const styles = StyleSheet.create({
38
38
  right: 12,
39
39
  },
40
40
  button: {
41
- width: 25,
42
- height: 25,
41
+ width: 32,
42
+ height: 32,
43
43
  borderRadius: 16,
44
44
  justifyContent: 'center',
45
45
  alignItems: 'center',
@@ -1,22 +1,27 @@
1
1
  import { BackendAdapterInterface, ColorMode, Theme } from '@chem-po/core'
2
2
  import { ChempoProps, ChempoProvider, useTheme } from '@chem-po/react'
3
- import React, { PropsWithChildren, useMemo } from 'react'
3
+ import React, { FC, PropsWithChildren, useMemo } from 'react'
4
+ import { GestureHandlerRootView } from 'react-native-gesture-handler'
4
5
  import { NotifierWrapper } from 'react-native-notifier'
5
6
  import { configureFonts, PaperProvider, Props as PaperProviderProps } from 'react-native-paper'
6
7
  import { en, registerTranslation } from 'react-native-paper-dates'
7
8
  import { MD3Type, MD3TypescaleKey } from 'react-native-paper/lib/typescript/types'
9
+ import { SafeAreaProvider } from 'react-native-safe-area-context'
8
10
  import { nativeToast } from '../constants/toast'
9
- import { useThemeState } from '../hooks/useThemeState'
11
+ import { UseThemeProps, useThemeState } from '../hooks/useThemeState'
10
12
  import { initializeScreen } from '../store/useScreen'
11
13
 
12
14
  registerTranslation('en', en)
13
15
 
14
16
  export type FontConfig = Partial<Record<MD3TypescaleKey, Partial<MD3Type>>>
17
+ export type MiddlewareProvider = FC<PropsWithChildren>
15
18
  export interface ChempoNativeProviderProps<BackendAdapter extends BackendAdapterInterface>
16
19
  extends PropsWithChildren<Pick<ChempoProps<BackendAdapter>, 'backendAdapter' | 'assets'>> {
17
20
  theme?: Theme
18
21
  fonts?: FontConfig
19
22
  initialColorMode?: ColorMode
23
+ colorModeProp?: string
24
+ middlewareProvider?: MiddlewareProvider
20
25
  }
21
26
  const createPaperTheme = (
22
27
  theme: Theme,
@@ -114,22 +119,33 @@ export const ChempoNativeProvider = <BackendAdapter extends BackendAdapterInterf
114
119
  initialColorMode,
115
120
  children,
116
121
  fonts,
122
+ colorModeProp,
123
+ middlewareProvider: Middleware,
117
124
  ...props
118
125
  }: ChempoNativeProviderProps<BackendAdapter>) => {
119
- const useThemeProps = useMemo(
120
- () => ({ theme: themeProp, initialColorMode }),
121
- [themeProp, initialColorMode],
126
+ const useThemeProps = useMemo<UseThemeProps>(
127
+ () => ({ theme: themeProp, initialColorMode, colorModeProp }),
128
+ [themeProp, initialColorMode, colorModeProp],
122
129
  )
123
130
  const theme = useThemeState(useThemeProps)
131
+
132
+ const body = (
133
+ <ChempoPaperProvider fonts={fonts}>
134
+ <NotifierWrapper>{children}</NotifierWrapper>
135
+ </ChempoPaperProvider>
136
+ )
137
+
124
138
  return (
125
- <NotifierWrapper>
126
- <ChempoProvider
127
- toast={nativeToast}
128
- theme={theme}
129
- initializeScreen={initializeScreen}
130
- {...props}>
131
- <ChempoPaperProvider fonts={fonts}>{children}</ChempoPaperProvider>
132
- </ChempoProvider>
133
- </NotifierWrapper>
139
+ <ChempoProvider
140
+ toast={nativeToast}
141
+ theme={theme}
142
+ initializeScreen={initializeScreen}
143
+ {...props}>
144
+ <SafeAreaProvider>
145
+ <GestureHandlerRootView>
146
+ {Middleware ? <Middleware>{body}</Middleware> : body}
147
+ </GestureHandlerRootView>
148
+ </SafeAreaProvider>
149
+ </ChempoProvider>
134
150
  )
135
151
  }
@@ -1,18 +1,43 @@
1
1
  import { ColorMode, Theme } from '@chem-po/core'
2
2
  import { ThemeProps } from '@chem-po/react'
3
- import { useMemo, useState } from 'react'
3
+ import AsyncStorage from '@react-native-async-storage/async-storage'
4
+ import { useCallback, useEffect, useMemo, useState } from 'react'
4
5
 
5
6
  export interface UseThemeProps {
6
- initialColorMode?: ColorMode
7
+ colorModeProp?: string
7
8
  theme?: Theme
9
+ // deprecated - using AsyncStorage instead
10
+ initialColorMode?: ColorMode
8
11
  }
9
12
 
10
13
  export const useThemeState = (props?: UseThemeProps) => {
11
- const { initialColorMode, theme } = props ?? {}
12
- const [colorMode, setColorMode] = useState<ColorMode>(initialColorMode ?? 'light')
14
+ const { initialColorMode, theme, colorModeProp = 'chempoColorMode' } = props ?? {}
15
+ const [initialized, setInitialized] = useState(false)
16
+
17
+ const [colorMode, setColorModeState] = useState<ColorMode>(initialColorMode ?? 'light')
18
+
19
+ useEffect(() => {
20
+ if (initialized) return
21
+
22
+ const initialize = async () => {
23
+ const storedColorMode = await AsyncStorage.getItem(colorModeProp)
24
+ if (storedColorMode) setColorModeState(storedColorMode as ColorMode)
25
+ else AsyncStorage.setItem(colorModeProp, initialColorMode ?? 'light')
26
+ setInitialized(true)
27
+ }
28
+ initialize()
29
+ }, [initialized, colorModeProp, initialColorMode])
30
+
31
+ const setColorMode = useCallback(
32
+ (colorMode: ColorMode) => {
33
+ setColorModeState(colorMode)
34
+ AsyncStorage.setItem(colorModeProp, colorMode)
35
+ },
36
+ [colorModeProp],
37
+ )
13
38
 
14
39
  return useMemo<ThemeProps>(
15
- () => ({ colorMode, setColorMode, theme }),
16
- [colorMode, setColorMode, theme],
40
+ () => ({ colorMode, setColorMode, theme, colorModeProp, initialized }),
41
+ [colorMode, setColorMode, theme, colorModeProp, initialized],
17
42
  )
18
43
  }