@bigbinary/neetoui-rn 2.3.4 → 3.0.0

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 (280) hide show
  1. package/README.md +118 -0
  2. package/lib/typescript/src/components/Alert/Alert.d.ts +32 -0
  3. package/lib/typescript/src/components/Alert/Alert.d.ts.map +1 -0
  4. package/lib/typescript/src/components/Alert/index.d.ts +2 -0
  5. package/lib/typescript/src/components/Alert/index.d.ts.map +1 -0
  6. package/lib/typescript/src/components/Avatar/Avatar.d.ts +21 -0
  7. package/lib/typescript/src/components/Avatar/Avatar.d.ts.map +1 -0
  8. package/lib/typescript/src/components/Avatar/index.d.ts +2 -0
  9. package/lib/typescript/src/components/Avatar/index.d.ts.map +1 -0
  10. package/lib/typescript/src/components/BottomSheet/BottomSheet.d.ts +61 -0
  11. package/lib/typescript/src/components/BottomSheet/BottomSheet.d.ts.map +1 -0
  12. package/lib/typescript/src/components/BottomSheet/index.d.ts +2 -0
  13. package/lib/typescript/src/components/BottomSheet/index.d.ts.map +1 -0
  14. package/lib/typescript/src/components/BottomSheetMenu/BottomSheetMenu.d.ts +28 -0
  15. package/lib/typescript/src/components/BottomSheetMenu/BottomSheetMenu.d.ts.map +1 -0
  16. package/lib/typescript/src/components/BottomSheetMenu/index.d.ts +2 -0
  17. package/lib/typescript/src/components/BottomSheetMenu/index.d.ts.map +1 -0
  18. package/lib/typescript/src/components/Button/Button.d.ts +79 -0
  19. package/lib/typescript/src/components/Button/Button.d.ts.map +1 -0
  20. package/lib/typescript/src/components/Button/index.d.ts +2 -0
  21. package/lib/typescript/src/components/Button/index.d.ts.map +1 -0
  22. package/lib/typescript/src/components/ChatInput/AttachmentsView.d.ts +11 -0
  23. package/lib/typescript/src/components/ChatInput/AttachmentsView.d.ts.map +1 -0
  24. package/lib/typescript/src/components/ChatInput/ChatInput.d.ts +78 -0
  25. package/lib/typescript/src/components/ChatInput/ChatInput.d.ts.map +1 -0
  26. package/lib/typescript/src/components/ChatInput/IconButton.d.ts +16 -0
  27. package/lib/typescript/src/components/ChatInput/IconButton.d.ts.map +1 -0
  28. package/lib/typescript/src/components/ChatInput/MentionsInput.d.ts +13 -0
  29. package/lib/typescript/src/components/ChatInput/MentionsInput.d.ts.map +1 -0
  30. package/lib/typescript/src/components/ChatInput/constants.d.ts +9 -0
  31. package/lib/typescript/src/components/ChatInput/constants.d.ts.map +1 -0
  32. package/lib/typescript/src/components/ChatInput/index.d.ts +4 -0
  33. package/lib/typescript/src/components/ChatInput/index.d.ts.map +1 -0
  34. package/lib/typescript/src/components/ChatInput/mentions.d.ts +26 -0
  35. package/lib/typescript/src/components/ChatInput/mentions.d.ts.map +1 -0
  36. package/lib/typescript/src/components/CheckBox/CheckBox.d.ts +13 -0
  37. package/lib/typescript/src/components/CheckBox/CheckBox.d.ts.map +1 -0
  38. package/lib/typescript/src/components/CheckBox/index.d.ts +2 -0
  39. package/lib/typescript/src/components/CheckBox/index.d.ts.map +1 -0
  40. package/lib/typescript/src/components/Container/Container.d.ts +9 -0
  41. package/lib/typescript/src/components/Container/Container.d.ts.map +1 -0
  42. package/lib/typescript/src/components/Container/index.d.ts +2 -0
  43. package/lib/typescript/src/components/Container/index.d.ts.map +1 -0
  44. package/lib/typescript/src/components/Divider/Divider.d.ts +7 -0
  45. package/lib/typescript/src/components/Divider/Divider.d.ts.map +1 -0
  46. package/lib/typescript/src/components/Divider/index.d.ts +2 -0
  47. package/lib/typescript/src/components/Divider/index.d.ts.map +1 -0
  48. package/lib/typescript/src/components/EmptyView/EmptyView.d.ts +26 -0
  49. package/lib/typescript/src/components/EmptyView/EmptyView.d.ts.map +1 -0
  50. package/lib/typescript/src/components/EmptyView/index.d.ts +2 -0
  51. package/lib/typescript/src/components/EmptyView/index.d.ts.map +1 -0
  52. package/lib/typescript/src/components/FlashList/FlashList.d.ts +14 -0
  53. package/lib/typescript/src/components/FlashList/FlashList.d.ts.map +1 -0
  54. package/lib/typescript/src/components/FlashList/index.d.ts +2 -0
  55. package/lib/typescript/src/components/FlashList/index.d.ts.map +1 -0
  56. package/lib/typescript/src/components/Header/BaseHeader.d.ts +5 -0
  57. package/lib/typescript/src/components/Header/BaseHeader.d.ts.map +1 -0
  58. package/lib/typescript/src/components/Header/Header.d.ts +21 -0
  59. package/lib/typescript/src/components/Header/Header.d.ts.map +1 -0
  60. package/lib/typescript/src/components/Header/ScreenHeader.d.ts +11 -0
  61. package/lib/typescript/src/components/Header/ScreenHeader.d.ts.map +1 -0
  62. package/lib/typescript/src/components/Header/index.d.ts +4 -0
  63. package/lib/typescript/src/components/Header/index.d.ts.map +1 -0
  64. package/lib/typescript/src/components/Input/Input.d.ts +15 -0
  65. package/lib/typescript/src/components/Input/Input.d.ts.map +1 -0
  66. package/lib/typescript/src/components/Input/index.d.ts +2 -0
  67. package/lib/typescript/src/components/Input/index.d.ts.map +1 -0
  68. package/lib/typescript/src/components/Loader/Loader.d.ts +12 -0
  69. package/lib/typescript/src/components/Loader/Loader.d.ts.map +1 -0
  70. package/lib/typescript/src/components/Loader/index.d.ts +2 -0
  71. package/lib/typescript/src/components/Loader/index.d.ts.map +1 -0
  72. package/lib/typescript/src/components/NeetoUIProvider/NeetoUIProvider.d.ts +16 -0
  73. package/lib/typescript/src/components/NeetoUIProvider/NeetoUIProvider.d.ts.map +1 -0
  74. package/lib/typescript/src/components/NeetoUIProvider/index.d.ts +2 -0
  75. package/lib/typescript/src/components/NeetoUIProvider/index.d.ts.map +1 -0
  76. package/lib/typescript/src/components/OnBoarding/OnBoarding.d.ts +22 -0
  77. package/lib/typescript/src/components/OnBoarding/OnBoarding.d.ts.map +1 -0
  78. package/lib/typescript/src/components/OnBoarding/index.d.ts +2 -0
  79. package/lib/typescript/src/components/OnBoarding/index.d.ts.map +1 -0
  80. package/lib/typescript/src/components/ParentView/ParentView.d.ts +13 -0
  81. package/lib/typescript/src/components/ParentView/ParentView.d.ts.map +1 -0
  82. package/lib/typescript/src/components/ParentView/index.d.ts +2 -0
  83. package/lib/typescript/src/components/ParentView/index.d.ts.map +1 -0
  84. package/lib/typescript/src/components/RadioButton/RadioButton.d.ts +12 -0
  85. package/lib/typescript/src/components/RadioButton/RadioButton.d.ts.map +1 -0
  86. package/lib/typescript/src/components/RadioButton/index.d.ts +2 -0
  87. package/lib/typescript/src/components/RadioButton/index.d.ts.map +1 -0
  88. package/lib/typescript/src/components/ScrollView/ScrollView.d.ts +8 -0
  89. package/lib/typescript/src/components/ScrollView/ScrollView.d.ts.map +1 -0
  90. package/lib/typescript/src/components/ScrollView/index.d.ts +2 -0
  91. package/lib/typescript/src/components/ScrollView/index.d.ts.map +1 -0
  92. package/lib/typescript/src/components/SearchBar/SearchBar.d.ts +12 -0
  93. package/lib/typescript/src/components/SearchBar/SearchBar.d.ts.map +1 -0
  94. package/lib/typescript/src/components/SearchBar/index.d.ts +2 -0
  95. package/lib/typescript/src/components/SearchBar/index.d.ts.map +1 -0
  96. package/lib/typescript/src/components/Skeleton/Skeleton.d.ts +41 -0
  97. package/lib/typescript/src/components/Skeleton/Skeleton.d.ts.map +1 -0
  98. package/lib/typescript/src/components/Skeleton/index.d.ts +2 -0
  99. package/lib/typescript/src/components/Skeleton/index.d.ts.map +1 -0
  100. package/lib/typescript/src/components/TextWithUrl/TextWithUrl.d.ts +11 -0
  101. package/lib/typescript/src/components/TextWithUrl/TextWithUrl.d.ts.map +1 -0
  102. package/lib/typescript/src/components/TextWithUrl/index.d.ts +2 -0
  103. package/lib/typescript/src/components/TextWithUrl/index.d.ts.map +1 -0
  104. package/lib/typescript/src/components/Toast/Toast.d.ts +13 -0
  105. package/lib/typescript/src/components/Toast/Toast.d.ts.map +1 -0
  106. package/lib/typescript/src/components/Toast/ToastGlyph.d.ts +4 -0
  107. package/lib/typescript/src/components/Toast/ToastGlyph.d.ts.map +1 -0
  108. package/lib/typescript/src/components/Toast/index.d.ts +2 -0
  109. package/lib/typescript/src/components/Toast/index.d.ts.map +1 -0
  110. package/lib/typescript/src/components/Touchable/Touchable.d.ts +23 -0
  111. package/lib/typescript/src/components/Touchable/Touchable.d.ts.map +1 -0
  112. package/lib/typescript/src/components/Touchable/index.d.ts +2 -0
  113. package/lib/typescript/src/components/Touchable/index.d.ts.map +1 -0
  114. package/lib/typescript/src/components/Typography/Typography.d.ts +64 -0
  115. package/lib/typescript/src/components/Typography/Typography.d.ts.map +1 -0
  116. package/lib/typescript/src/components/Typography/index.d.ts +2 -0
  117. package/lib/typescript/src/components/Typography/index.d.ts.map +1 -0
  118. package/lib/typescript/src/hooks/index.d.ts +4 -0
  119. package/lib/typescript/src/hooks/index.d.ts.map +1 -0
  120. package/lib/typescript/src/hooks/useDebounce.d.ts +2 -0
  121. package/lib/typescript/src/hooks/useDebounce.d.ts.map +1 -0
  122. package/lib/typescript/src/hooks/useNeetoTheme.d.ts +8 -0
  123. package/lib/typescript/src/hooks/useNeetoTheme.d.ts.map +1 -0
  124. package/lib/typescript/src/hooks/useRefreshByUser.d.ts +5 -0
  125. package/lib/typescript/src/hooks/useRefreshByUser.d.ts.map +1 -0
  126. package/lib/typescript/src/index.d.ts +31 -0
  127. package/lib/typescript/src/index.d.ts.map +1 -0
  128. package/lib/typescript/src/theme/palette.d.ts +28 -0
  129. package/lib/typescript/src/theme/palette.d.ts.map +1 -0
  130. package/lib/typescript/src/theme/palette.generated.d.ts +134 -0
  131. package/lib/typescript/src/theme/palette.generated.d.ts.map +1 -0
  132. package/lib/typescript/src/theme/tokens.d.ts +184 -0
  133. package/lib/typescript/src/theme/tokens.d.ts.map +1 -0
  134. package/lib/typescript/src/unistyles-init.d.ts +2 -0
  135. package/lib/typescript/src/unistyles-init.d.ts.map +1 -0
  136. package/lib/typescript/src/unistyles.d.ts +21 -0
  137. package/lib/typescript/src/unistyles.d.ts.map +1 -0
  138. package/lib/typescript/src/utils/index.d.ts +18 -0
  139. package/lib/typescript/src/utils/index.d.ts.map +1 -0
  140. package/lib/typescript/src/utils/scale.d.ts +2 -0
  141. package/lib/typescript/src/utils/scale.d.ts.map +1 -0
  142. package/package.json +125 -51
  143. package/src/components/Alert/Alert.tsx +222 -0
  144. package/src/components/Alert/index.ts +1 -0
  145. package/src/components/Avatar/Avatar.tsx +123 -0
  146. package/src/components/Avatar/index.ts +1 -0
  147. package/src/components/BottomSheet/BottomSheet.tsx +468 -0
  148. package/src/components/BottomSheet/index.ts +5 -0
  149. package/src/components/BottomSheetMenu/BottomSheetMenu.tsx +150 -0
  150. package/src/components/BottomSheetMenu/index.ts +5 -0
  151. package/src/components/Button/Button.tsx +134 -0
  152. package/src/components/Button/index.ts +1 -0
  153. package/src/components/ChatInput/AttachmentsView.tsx +68 -0
  154. package/src/components/ChatInput/ChatInput.tsx +393 -0
  155. package/src/components/ChatInput/IconButton.tsx +52 -0
  156. package/src/components/ChatInput/MentionsInput.tsx +126 -0
  157. package/src/components/ChatInput/constants.ts +18 -0
  158. package/src/components/ChatInput/index.ts +11 -0
  159. package/src/components/ChatInput/mentions.ts +91 -0
  160. package/src/components/CheckBox/CheckBox.tsx +86 -0
  161. package/src/components/CheckBox/index.ts +1 -0
  162. package/src/components/Container/Container.tsx +14 -0
  163. package/src/components/Container/index.ts +1 -0
  164. package/src/components/Divider/Divider.tsx +32 -0
  165. package/src/components/Divider/index.ts +1 -0
  166. package/src/components/EmptyView/EmptyView.tsx +80 -0
  167. package/src/components/EmptyView/index.ts +1 -0
  168. package/src/components/FlashList/FlashList.tsx +162 -0
  169. package/src/components/FlashList/index.ts +1 -0
  170. package/src/components/Header/BaseHeader.tsx +19 -0
  171. package/src/components/Header/Header.tsx +114 -0
  172. package/src/components/Header/ScreenHeader.tsx +34 -0
  173. package/src/components/Header/index.ts +3 -0
  174. package/src/components/Input/Input.tsx +122 -0
  175. package/src/components/Input/index.ts +1 -0
  176. package/src/components/Loader/Loader.tsx +93 -0
  177. package/src/components/Loader/index.ts +1 -0
  178. package/src/components/NeetoUIProvider/NeetoUIProvider.tsx +43 -0
  179. package/src/components/NeetoUIProvider/index.ts +1 -0
  180. package/src/components/OnBoarding/OnBoarding.tsx +177 -0
  181. package/src/components/OnBoarding/index.ts +5 -0
  182. package/src/components/ParentView/ParentView.tsx +98 -0
  183. package/src/components/ParentView/index.ts +1 -0
  184. package/src/components/RadioButton/RadioButton.tsx +80 -0
  185. package/src/components/RadioButton/index.ts +1 -0
  186. package/src/components/ScrollView/ScrollView.tsx +30 -0
  187. package/src/components/ScrollView/index.ts +1 -0
  188. package/src/components/SearchBar/SearchBar.tsx +122 -0
  189. package/src/components/SearchBar/index.ts +1 -0
  190. package/src/components/Skeleton/Skeleton.tsx +121 -0
  191. package/src/components/Skeleton/index.ts +1 -0
  192. package/src/components/TextWithUrl/TextWithUrl.tsx +54 -0
  193. package/src/components/TextWithUrl/index.ts +1 -0
  194. package/src/components/Toast/Toast.tsx +157 -0
  195. package/src/components/Toast/ToastGlyph.tsx +53 -0
  196. package/src/components/Toast/index.ts +1 -0
  197. package/src/components/Touchable/Touchable.tsx +54 -0
  198. package/src/components/Touchable/index.ts +1 -0
  199. package/src/components/Typography/Typography.tsx +76 -0
  200. package/src/components/Typography/index.ts +1 -0
  201. package/src/hooks/index.ts +3 -0
  202. package/src/hooks/useDebounce.ts +17 -0
  203. package/src/hooks/useNeetoTheme.ts +10 -0
  204. package/src/hooks/useRefreshByUser.ts +17 -0
  205. package/src/index.ts +68 -0
  206. package/src/theme/palette.generated.ts +141 -0
  207. package/src/theme/palette.ts +34 -0
  208. package/src/theme/tokens.ts +205 -0
  209. package/src/types/neeto-icons-rn.d.ts +33 -0
  210. package/src/unistyles-init.ts +16 -0
  211. package/src/unistyles.ts +34 -0
  212. package/src/utils/index.ts +41 -0
  213. package/src/utils/scale.ts +16 -0
  214. package/components/Accordion.js +0 -1
  215. package/components/ActionIcon.js +0 -1
  216. package/components/Alert.js +0 -1
  217. package/components/Avatar.js +0 -1
  218. package/components/Badge.js +0 -1
  219. package/components/BottomSheet.js +0 -1
  220. package/components/BottomTabBar.js +0 -1
  221. package/components/Button.js +0 -1
  222. package/components/ButtonGroup.js +0 -1
  223. package/components/Calendar.js +0 -1
  224. package/components/Card.js +0 -1
  225. package/components/Carousel.js +0 -1
  226. package/components/ChatInput/AttachmentsView.js +0 -1
  227. package/components/ChatInput/Badge.js +0 -1
  228. package/components/ChatInput/ChatInput.js +0 -1
  229. package/components/ChatInput/EmailFields.js +0 -1
  230. package/components/ChatInput/IconButton.js +0 -1
  231. package/components/ChatInput/MentionsInputWrapper.js +0 -1
  232. package/components/CheckBox.js +0 -1
  233. package/components/Chip.js +0 -1
  234. package/components/Container.js +0 -1
  235. package/components/Divider.js +0 -1
  236. package/components/FAB.js +0 -1
  237. package/components/FlashList.js +0 -1
  238. package/components/FlatList.js +0 -1
  239. package/components/Input.js +0 -1
  240. package/components/InputEmailChip.js +0 -1
  241. package/components/LineLoader.js +0 -1
  242. package/components/ListItem.js +0 -1
  243. package/components/Loader.js +0 -1
  244. package/components/MultiSelect.js +0 -1
  245. package/components/NotificationIcon.js +0 -1
  246. package/components/NotificationPreferenceList.js +0 -1
  247. package/components/OnBoarding.js +0 -1
  248. package/components/OrganizationItem.js +0 -1
  249. package/components/OtpInputs.js +0 -1
  250. package/components/ParentView.js +0 -1
  251. package/components/Popover.js +0 -1
  252. package/components/RadioButton.js +0 -1
  253. package/components/RichTextEditor.js +0 -1
  254. package/components/Ripple.js +0 -1
  255. package/components/ScrollView.js +0 -1
  256. package/components/SearchBar.js +0 -1
  257. package/components/SegmentedTopBar/Indicator.js +0 -1
  258. package/components/SegmentedTopBar/Tab.js +0 -1
  259. package/components/SegmentedTopBar/index.js +0 -1
  260. package/components/SocialButton.js +0 -1
  261. package/components/Toast.js +0 -1
  262. package/components/ToggleSwitch.js +0 -1
  263. package/components/TopBar.js +0 -1
  264. package/components/Touchable.js +0 -1
  265. package/components/TouchableWithoutFeedback.js +0 -1
  266. package/components/Typography.js +0 -1
  267. package/components/index.js +0 -1
  268. package/config/index.js +0 -1
  269. package/config/toasterConfig.js +0 -1
  270. package/contexts/index.js +0 -1
  271. package/hooks/index.js +0 -1
  272. package/hooks/useDebounce.js +0 -1
  273. package/hooks/useKeyboard.js +0 -1
  274. package/hooks/useRefreshByUser.js +0 -1
  275. package/index.d.ts +0 -630
  276. package/index.js +0 -1
  277. package/react-native.config.js +0 -10
  278. package/theme/index.js +0 -1
  279. package/utils/index.js +0 -1
  280. package/utils/scale.js +0 -1
@@ -0,0 +1,134 @@
1
+ import {
2
+ ActivityIndicator,
3
+ Pressable,
4
+ type PressableProps,
5
+ type StyleProp,
6
+ type TextStyle,
7
+ type ViewStyle,
8
+ } from "react-native";
9
+ import { StyleSheet, type UnistylesVariants } from "react-native-unistyles";
10
+
11
+ import { Typography } from "../Typography";
12
+
13
+ type ButtonVariants = UnistylesVariants<typeof styles>;
14
+
15
+ export type ButtonProps = Omit<
16
+ PressableProps,
17
+ "children" | "disabled" | "style"
18
+ > &
19
+ ButtonVariants & {
20
+ label: string;
21
+ isLoading?: boolean;
22
+ disabled?: boolean;
23
+ style?: StyleProp<ViewStyle>;
24
+ labelStyle?: StyleProp<TextStyle>;
25
+ };
26
+
27
+ export const Button = ({
28
+ label,
29
+ variant = "solid",
30
+ size = "md",
31
+ isLoading = false,
32
+ disabled = false,
33
+ style,
34
+ labelStyle,
35
+ ...rest
36
+ }: ButtonProps) => {
37
+ const isDisabled = disabled || isLoading;
38
+
39
+ styles.useVariants({ variant, size, disabled: isDisabled });
40
+
41
+ return (
42
+ <Pressable
43
+ {...rest}
44
+ accessibilityRole="button"
45
+ accessibilityState={{ busy: isLoading, disabled: isDisabled }}
46
+ disabled={isDisabled}
47
+ style={[styles.container, style]}
48
+ >
49
+ {isLoading ? (
50
+ <ActivityIndicator testID="button-loader" />
51
+ ) : (
52
+ <Typography style={[styles.label, labelStyle]}>{label}</Typography>
53
+ )}
54
+ </Pressable>
55
+ );
56
+ };
57
+
58
+ const styles = StyleSheet.create(theme => ({
59
+ container: {
60
+ alignItems: "center",
61
+ borderColor: "transparent",
62
+ borderRadius: theme.radii.lg,
63
+ borderWidth: 1,
64
+ flexDirection: "row",
65
+ justifyContent: "center",
66
+ variants: {
67
+ variant: {
68
+ solid: {
69
+ backgroundColor: theme.colors.primary,
70
+ borderColor: theme.colors.primary,
71
+ },
72
+ inverse: {
73
+ backgroundColor: theme.colors.background.primary,
74
+ borderColor: theme.colors.primary,
75
+ },
76
+ text: {
77
+ backgroundColor: "transparent",
78
+ },
79
+ // Borderless grey surface with plain text — the dismissive action
80
+ // in a sheet (cal's Cancel row, Alert's secondary buttons).
81
+ // borderColor stays the base "transparent".
82
+ secondary: {
83
+ backgroundColor: theme.colors.palette.grey100,
84
+ },
85
+ danger: {
86
+ backgroundColor: theme.colors.danger,
87
+ borderColor: theme.colors.danger,
88
+ },
89
+ },
90
+ size: {
91
+ sm: {
92
+ minHeight: 32,
93
+ paddingHorizontal: theme.spacing.md,
94
+ paddingVertical: theme.spacing.xs,
95
+ },
96
+ md: {
97
+ minHeight: 40,
98
+ paddingHorizontal: theme.spacing.lg,
99
+ paddingVertical: theme.spacing.sm,
100
+ },
101
+ lg: {
102
+ minHeight: 48,
103
+ paddingHorizontal: theme.spacing.xl,
104
+ paddingVertical: theme.spacing.md,
105
+ },
106
+ },
107
+ disabled: {
108
+ true: { opacity: 0.5 },
109
+ false: {},
110
+ },
111
+ },
112
+ },
113
+ label: {
114
+ fontFamily: theme.fonts.medium,
115
+ variants: {
116
+ variant: {
117
+ solid: { color: theme.colors.text.inverse },
118
+ inverse: { color: theme.colors.primary },
119
+ text: { color: theme.colors.primary },
120
+ secondary: { color: theme.colors.text.primary },
121
+ danger: { color: theme.colors.text.inverse },
122
+ },
123
+ size: {
124
+ sm: { fontSize: theme.fontSizes.xs },
125
+ md: { fontSize: theme.fontSizes.s },
126
+ lg: { fontSize: theme.fontSizes.l },
127
+ },
128
+ disabled: {
129
+ true: {},
130
+ false: {},
131
+ },
132
+ },
133
+ },
134
+ }));
@@ -0,0 +1 @@
1
+ export { Button, type ButtonProps } from "./Button";
@@ -0,0 +1,68 @@
1
+ import { type ReactNode } from "react";
2
+ import { Pressable, View } from "react-native";
3
+ import Animated, { FadeInDown, FadeInUp } from "react-native-reanimated";
4
+ import { StyleSheet } from "react-native-unistyles";
5
+
6
+ import { moderateScale } from "../../utils/scale";
7
+ import { Typography } from "../Typography";
8
+
9
+ const Badge = ({
10
+ text,
11
+ isNoteOptionSelected,
12
+ }: {
13
+ text: string;
14
+ isNoteOptionSelected?: boolean;
15
+ }) => (
16
+ <View style={[styles.badge, isNoteOptionSelected && styles.badgeOnNote]}>
17
+ <Typography size="3xs">{text}</Typography>
18
+ </View>
19
+ );
20
+
21
+ type AttachmentsViewProps = {
22
+ isAttachmentsVisible: boolean;
23
+ onExpand: () => void;
24
+ Attachments?: ReactNode;
25
+ attachmentsCount?: number;
26
+ isNoteOptionSelected: boolean;
27
+ };
28
+
29
+ export const AttachmentsView = ({
30
+ isAttachmentsVisible,
31
+ onExpand,
32
+ Attachments,
33
+ attachmentsCount = 0,
34
+ isNoteOptionSelected,
35
+ }: AttachmentsViewProps) => {
36
+ if (attachmentsCount === 0) return null;
37
+
38
+ return isAttachmentsVisible ? (
39
+ <Animated.View entering={FadeInUp}>{Attachments}</Animated.View>
40
+ ) : (
41
+ <Animated.View entering={FadeInDown}>
42
+ <Pressable style={styles.badgeRow} onPress={onExpand}>
43
+ <Badge isNoteOptionSelected={isNoteOptionSelected} text="Attachments" />
44
+ <Badge
45
+ isNoteOptionSelected={isNoteOptionSelected}
46
+ text={`+ ${attachmentsCount}`}
47
+ />
48
+ </Pressable>
49
+ </Animated.View>
50
+ );
51
+ };
52
+
53
+ const styles = StyleSheet.create(theme => ({
54
+ badgeRow: {
55
+ alignItems: "flex-start",
56
+ flexDirection: "row",
57
+ flexWrap: "wrap",
58
+ gap: moderateScale(4),
59
+ },
60
+ badge: {
61
+ backgroundColor: theme.colors.background.oldLace,
62
+ borderRadius: moderateScale(20),
63
+ paddingHorizontal: moderateScale(4),
64
+ },
65
+ badgeOnNote: {
66
+ backgroundColor: theme.colors.background.primary,
67
+ },
68
+ }));
@@ -0,0 +1,393 @@
1
+ import {
2
+ forwardRef,
3
+ useEffect,
4
+ useImperativeHandle,
5
+ useRef,
6
+ useState,
7
+ type ReactNode,
8
+ } from "react";
9
+ import { type TextInput, View, type TextInputProps } from "react-native";
10
+ import {
11
+ Attachment,
12
+ BookOpen,
13
+ CannedResponses,
14
+ ChatBubble,
15
+ Down,
16
+ Expand,
17
+ Forward,
18
+ Minimize,
19
+ Notes,
20
+ SendPlane,
21
+ } from "@bigbinary/neeto-icons-rn";
22
+ import { StyleSheet, useUnistyles } from "react-native-unistyles";
23
+
24
+ import { moderateScale } from "../../utils/scale";
25
+ import { BottomSheet } from "../BottomSheet";
26
+ import { Skeleton } from "../Skeleton";
27
+ import { Touchable } from "../Touchable";
28
+ import { Typography } from "../Typography";
29
+ import { AttachmentsView } from "./AttachmentsView";
30
+ import { IconButton } from "./IconButton";
31
+ import { MentionsInput } from "./MentionsInput";
32
+ import {
33
+ ACTION_LABELS,
34
+ OPTION_TYPES,
35
+ PLACEHOLDERS,
36
+ type ChatInputOption,
37
+ } from "./constants";
38
+ import {
39
+ convertMentionsToHTMLAndPlainText,
40
+ type MentionSuggestion,
41
+ } from "./mentions";
42
+
43
+ export type ChatInputPayload = {
44
+ toEmails: string[];
45
+ ccEmails: string[];
46
+ bccEmails: string[];
47
+ html: string;
48
+ plainText: string;
49
+ status?: string;
50
+ };
51
+
52
+ export type ChatInputRef = {
53
+ clearEmailFields: () => void;
54
+ };
55
+
56
+ export type ChatInputProps = Omit<
57
+ TextInputProps,
58
+ "value" | "onChange" | "onChangeText" | "onSelectionChange"
59
+ > & {
60
+ value?: string;
61
+ onChangeText?: (value: string) => void;
62
+ initialSelectedOption?: ChatInputOption;
63
+ onOptionChange?: (option: ChatInputOption) => void;
64
+ onReply?: (payload: ChatInputPayload) => void;
65
+ onAddNote?: (payload: ChatInputPayload) => void;
66
+ /** Forward icon renders only when this is passed (v2 behavior). */
67
+ onForward?: (payload: ChatInputPayload) => void;
68
+ onAttachment?: () => void;
69
+ /** Canned-responses icon renders only when this is passed. */
70
+ onCannedResponse?: () => void;
71
+ /** Articles icon renders only when this is passed. */
72
+ onArticles?: () => void;
73
+ showArticlesFor?: ChatInputOption[];
74
+ showCannedResponsesFor?: ChatInputOption[];
75
+ showSuggestionsFor?: ChatInputOption[];
76
+ /** Extra "Send and set as closed" menu on the reply action (desk). */
77
+ showReplyMenuOptions?: boolean;
78
+ toEmails?: string[];
79
+ attachmentsCount?: number;
80
+ Attachments?: ReactNode;
81
+ suggestions?: MentionSuggestion[];
82
+ disabled?: boolean;
83
+ isLoading?: boolean;
84
+ };
85
+
86
+ /**
87
+ * Composer used by chat and desk. Ported against the two real call sites
88
+ * rather than the whole v2 surface: v2's EmailFields UI was gated behind
89
+ * shouldShowEmailFields, which no app passes — the email-entry UI is dead
90
+ * in production and is not ported. toEmails still flows into every action
91
+ * payload and clearEmailFields still resets it (desk's ref contract).
92
+ *
93
+ * v2's Popover reply menu (react-native-popover-view, dropped dep) is now
94
+ * a BottomSheet.
95
+ */
96
+ export const ChatInput = forwardRef<ChatInputRef, ChatInputProps>(
97
+ (
98
+ {
99
+ value = "",
100
+ onChangeText = () => {},
101
+ initialSelectedOption = OPTION_TYPES.REPLY,
102
+ onOptionChange = () => {},
103
+ onReply = () => {},
104
+ onAddNote = () => {},
105
+ onForward,
106
+ onAttachment = () => {},
107
+ onCannedResponse,
108
+ onArticles,
109
+ showArticlesFor = [OPTION_TYPES.REPLY, OPTION_TYPES.NOTE],
110
+ showCannedResponsesFor = [OPTION_TYPES.REPLY],
111
+ showSuggestionsFor = [OPTION_TYPES.NOTE],
112
+ showReplyMenuOptions = false,
113
+ toEmails: initialToEmails,
114
+ attachmentsCount = 0,
115
+ Attachments,
116
+ suggestions = [],
117
+ disabled = false,
118
+ isLoading = false,
119
+ ...inputProps
120
+ },
121
+ ref
122
+ ) => {
123
+ const { theme } = useUnistyles();
124
+ const inputRef = useRef<TextInput>(null);
125
+
126
+ const [selectedOption, setSelectedOption] = useState<ChatInputOption>(
127
+ initialSelectedOption
128
+ );
129
+ const [isAttachmentsVisible, setIsAttachmentsVisible] = useState(false);
130
+ const [isReplyMenuVisible, setIsReplyMenuVisible] = useState(false);
131
+
132
+ const [toEmails, setToEmails] = useState(initialToEmails ?? []);
133
+ const [toEmailsForForward, setToEmailsForForward] = useState<string[]>([]);
134
+ const [ccEmails, setCcEmails] = useState<string[]>([]);
135
+ const [bccEmails, setBccEmails] = useState<string[]>([]);
136
+
137
+ const isReplySelected = selectedOption === OPTION_TYPES.REPLY;
138
+ const isNoteSelected = selectedOption === OPTION_TYPES.NOTE;
139
+ const isForwardSelected = selectedOption === OPTION_TYPES.FORWARD;
140
+
141
+ useImperativeHandle(
142
+ ref,
143
+ () => ({
144
+ clearEmailFields: () => {
145
+ setToEmails(initialToEmails ?? []);
146
+ setToEmailsForForward([]);
147
+ setCcEmails([]);
148
+ setBccEmails([]);
149
+ inputRef.current?.blur();
150
+ },
151
+ }),
152
+ [initialToEmails]
153
+ );
154
+
155
+ useEffect(() => {
156
+ onOptionChange(selectedOption);
157
+ // v2 quirk kept: fires on mount too, and intentionally does not chase
158
+ // a new onOptionChange identity.
159
+ // eslint-disable-next-line react-hooks/exhaustive-deps
160
+ }, [selectedOption]);
161
+
162
+ useEffect(() => {
163
+ setSelectedOption(initialSelectedOption);
164
+ }, [initialSelectedOption]);
165
+
166
+ useEffect(() => {
167
+ setToEmails(initialToEmails ?? []);
168
+ }, [initialToEmails]);
169
+
170
+ const selectOption = (option: ChatInputOption) => {
171
+ setIsAttachmentsVisible(false);
172
+ inputRef.current?.focus();
173
+ setSelectedOption(option);
174
+ };
175
+
176
+ const buildPayload = (extra?: Partial<ChatInputPayload>) => ({
177
+ toEmails: isForwardSelected ? toEmailsForForward : toEmails,
178
+ ccEmails,
179
+ bccEmails,
180
+ ...convertMentionsToHTMLAndPlainText({ suggestions, value }),
181
+ ...extra,
182
+ });
183
+
184
+ const actionHandlers: Record<
185
+ ChatInputOption,
186
+ (payload: ChatInputPayload) => void
187
+ > = {
188
+ REPLY: onReply,
189
+ NOTE: onAddNote,
190
+ FORWARD: onForward ?? (() => {}),
191
+ };
192
+
193
+ const handleAction = (extra?: Partial<ChatInputPayload>) => {
194
+ actionHandlers[selectedOption](buildPayload(extra));
195
+ setIsAttachmentsVisible(false);
196
+ };
197
+
198
+ const handleAttachmentPress = () => {
199
+ // v2: when collapsed attachments already exist, first tap only
200
+ // expands them instead of re-opening the upload flow.
201
+ if (isAttachmentsVisible || attachmentsCount === 0) onAttachment();
202
+ setIsAttachmentsVisible(true);
203
+ };
204
+
205
+ const shouldShowExpandToggle =
206
+ attachmentsCount > 0 || (toEmails.length > 0 && !isNoteSelected);
207
+
208
+ const isActionDisabled =
209
+ disabled || (isForwardSelected && toEmailsForForward.length === 0);
210
+
211
+ return (
212
+ <View>
213
+ {isLoading && (
214
+ <Skeleton animated height={moderateScale(2)} variant="line" />
215
+ )}
216
+ <View style={[styles.container, isNoteSelected && styles.noteSurface]}>
217
+ <View style={styles.inputRow}>
218
+ <MentionsInput
219
+ inputRef={inputRef}
220
+ placeholder={PLACEHOLDERS[selectedOption]}
221
+ suggestions={suggestions}
222
+ value={value}
223
+ shouldShowSuggestions={showSuggestionsFor.includes(
224
+ selectedOption
225
+ )}
226
+ onChange={onChangeText}
227
+ {...inputProps}
228
+ />
229
+ {shouldShowExpandToggle && (
230
+ <IconButton
231
+ accessibilityLabel={
232
+ isAttachmentsVisible ? "Collapse extras" : "Expand extras"
233
+ }
234
+ Icon={isAttachmentsVisible ? Minimize : Expand}
235
+ style={styles.expandButton}
236
+ onPress={() => setIsAttachmentsVisible(current => !current)}
237
+ />
238
+ )}
239
+ </View>
240
+ <AttachmentsView
241
+ Attachments={Attachments}
242
+ attachmentsCount={attachmentsCount}
243
+ isAttachmentsVisible={isAttachmentsVisible}
244
+ isNoteOptionSelected={isNoteSelected}
245
+ onExpand={() => setIsAttachmentsVisible(true)}
246
+ />
247
+ <View style={styles.toolbar}>
248
+ <View style={styles.toolbarGroup}>
249
+ <IconButton
250
+ accessibilityLabel="Reply"
251
+ Icon={ChatBubble}
252
+ isActive={isReplySelected}
253
+ onPress={() => selectOption(OPTION_TYPES.REPLY)}
254
+ />
255
+ <IconButton
256
+ accessibilityLabel="Add note"
257
+ Icon={Notes}
258
+ isActive={isNoteSelected}
259
+ onPress={() => selectOption(OPTION_TYPES.NOTE)}
260
+ />
261
+ {onForward && (
262
+ <IconButton
263
+ accessibilityLabel="Forward"
264
+ Icon={Forward}
265
+ isActive={isForwardSelected}
266
+ onPress={() => selectOption(OPTION_TYPES.FORWARD)}
267
+ />
268
+ )}
269
+ <View style={styles.toolbarDivider} />
270
+ {onCannedResponse &&
271
+ showCannedResponsesFor.includes(selectedOption) && (
272
+ <IconButton
273
+ accessibilityLabel="Canned responses"
274
+ Icon={CannedResponses}
275
+ isActive={false}
276
+ onPress={onCannedResponse}
277
+ />
278
+ )}
279
+ {onArticles && showArticlesFor.includes(selectedOption) && (
280
+ <IconButton
281
+ accessibilityLabel="Articles"
282
+ Icon={BookOpen}
283
+ isActive={false}
284
+ onPress={onArticles}
285
+ />
286
+ )}
287
+ <IconButton
288
+ accessibilityLabel="Add attachment"
289
+ disabled={isLoading}
290
+ Icon={Attachment}
291
+ onPress={handleAttachmentPress}
292
+ />
293
+ </View>
294
+ <View style={styles.toolbarGroup}>
295
+ <Touchable
296
+ accessibilityLabel="Send"
297
+ accessibilityRole="button"
298
+ disabled={isActionDisabled}
299
+ style={[
300
+ styles.sendButton,
301
+ isActionDisabled && styles.sendButtonDisabled,
302
+ ]}
303
+ onPress={() => handleAction()}
304
+ >
305
+ {!!ACTION_LABELS[selectedOption] && (
306
+ <Typography size="m" style={styles.sendLabel}>
307
+ {ACTION_LABELS[selectedOption]}
308
+ </Typography>
309
+ )}
310
+ {isReplySelected && (
311
+ <SendPlane
312
+ color={theme.colors.primary}
313
+ size={moderateScale(22)}
314
+ />
315
+ )}
316
+ </Touchable>
317
+ {showReplyMenuOptions && isReplySelected && (
318
+ <>
319
+ <Touchable
320
+ accessibilityLabel="More reply options"
321
+ accessibilityRole="button"
322
+ disabled={disabled}
323
+ style={styles.sendButton}
324
+ onPress={() => setIsReplyMenuVisible(true)}
325
+ >
326
+ <Down
327
+ color={theme.colors.text.primary}
328
+ size={moderateScale(22)}
329
+ />
330
+ </Touchable>
331
+ <BottomSheet
332
+ data={["Send and set as closed"]}
333
+ hide={() => setIsReplyMenuVisible(false)}
334
+ isVisible={isReplyMenuVisible}
335
+ onItemPress={() => handleAction({ status: "closed" })}
336
+ />
337
+ </>
338
+ )}
339
+ </View>
340
+ </View>
341
+ </View>
342
+ </View>
343
+ );
344
+ }
345
+ );
346
+
347
+ ChatInput.displayName = "ChatInput";
348
+
349
+ const styles = StyleSheet.create(theme => ({
350
+ container: {
351
+ paddingBottom: moderateScale(5),
352
+ paddingHorizontal: moderateScale(16),
353
+ },
354
+ noteSurface: {
355
+ backgroundColor: theme.colors.background.oldLace,
356
+ },
357
+ inputRow: {
358
+ flexDirection: "row",
359
+ justifyContent: "space-between",
360
+ },
361
+ expandButton: {
362
+ alignSelf: "center",
363
+ },
364
+ toolbar: {
365
+ alignItems: "center",
366
+ flexDirection: "row",
367
+ justifyContent: "space-between",
368
+ marginTop: moderateScale(10),
369
+ },
370
+ toolbarGroup: {
371
+ alignItems: "center",
372
+ flexDirection: "row",
373
+ },
374
+ toolbarDivider: {
375
+ alignSelf: "stretch",
376
+ backgroundColor: theme.colors.palette.grey400,
377
+ marginHorizontal: moderateScale(5),
378
+ width: moderateScale(2),
379
+ },
380
+ sendButton: {
381
+ alignItems: "center",
382
+ flexDirection: "row",
383
+ gap: moderateScale(4),
384
+ minHeight: moderateScale(30),
385
+ paddingHorizontal: moderateScale(4),
386
+ },
387
+ sendButtonDisabled: {
388
+ opacity: 0.5,
389
+ },
390
+ sendLabel: {
391
+ color: theme.colors.primary,
392
+ },
393
+ }));
@@ -0,0 +1,52 @@
1
+ import { type ComponentType } from "react";
2
+ import { type StyleProp, type ViewStyle } from "react-native";
3
+ import { StyleSheet, useUnistyles } from "react-native-unistyles";
4
+
5
+ import { moderateScale } from "../../utils/scale";
6
+ import { Touchable } from "../Touchable";
7
+
8
+ type IconButtonProps = {
9
+ Icon: ComponentType<{ size?: number; color?: string }>;
10
+ onPress: () => void;
11
+ isActive?: boolean;
12
+ disabled?: boolean;
13
+ accessibilityLabel?: string;
14
+ style?: StyleProp<ViewStyle>;
15
+ };
16
+
17
+ export const IconButton = ({
18
+ Icon,
19
+ onPress,
20
+ isActive = true,
21
+ disabled = false,
22
+ accessibilityLabel,
23
+ style,
24
+ }: IconButtonProps) => {
25
+ const { theme } = useUnistyles();
26
+
27
+ return (
28
+ <Touchable
29
+ accessibilityLabel={accessibilityLabel}
30
+ accessibilityRole="button"
31
+ disabled={disabled}
32
+ style={[styles.container, !isActive && styles.inactive, style]}
33
+ onPress={onPress}
34
+ >
35
+ <Icon
36
+ color={disabled ? theme.colors.text.muted : theme.colors.text.primary}
37
+ size={moderateScale(24)}
38
+ />
39
+ </Touchable>
40
+ );
41
+ };
42
+
43
+ const styles = StyleSheet.create({
44
+ container: {
45
+ alignItems: "center",
46
+ justifyContent: "center",
47
+ paddingHorizontal: moderateScale(8),
48
+ },
49
+ inactive: {
50
+ opacity: 0.5,
51
+ },
52
+ });