@bigbinary/neetoui-rn 3.0.3 → 3.1.1

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 (56) hide show
  1. package/README.md +29 -1
  2. package/lib/typescript/src/components/BottomSheet/BottomSheet.d.ts +8 -1
  3. package/lib/typescript/src/components/BottomSheet/BottomSheet.d.ts.map +1 -1
  4. package/lib/typescript/src/components/ChatInput/AttachmentsView.d.ts.map +1 -1
  5. package/lib/typescript/src/components/ChatInput/ChatInput.d.ts.map +1 -1
  6. package/lib/typescript/src/components/CheckBox/CheckBox.d.ts +7 -1
  7. package/lib/typescript/src/components/CheckBox/CheckBox.d.ts.map +1 -1
  8. package/lib/typescript/src/components/Input/Input.d.ts +12 -11
  9. package/lib/typescript/src/components/Input/Input.d.ts.map +1 -1
  10. package/lib/typescript/src/components/Input/InputFrame.d.ts +41 -0
  11. package/lib/typescript/src/components/Input/InputFrame.d.ts.map +1 -0
  12. package/lib/typescript/src/components/Input/index.d.ts +1 -0
  13. package/lib/typescript/src/components/Input/index.d.ts.map +1 -1
  14. package/lib/typescript/src/components/NeetoUIProvider/NeetoUIProvider.d.ts.map +1 -1
  15. package/lib/typescript/src/components/RadioButton/RadioButton.d.ts +6 -1
  16. package/lib/typescript/src/components/RadioButton/RadioButton.d.ts.map +1 -1
  17. package/lib/typescript/src/components/SearchBar/SearchBar.d.ts.map +1 -1
  18. package/lib/typescript/src/components/Skeleton/Skeleton.d.ts.map +1 -1
  19. package/lib/typescript/src/components/Typography/Typography.d.ts +5 -0
  20. package/lib/typescript/src/components/Typography/Typography.d.ts.map +1 -1
  21. package/lib/typescript/src/components/Typography/index.d.ts +1 -1
  22. package/lib/typescript/src/components/Typography/index.d.ts.map +1 -1
  23. package/lib/typescript/src/index.d.ts +2 -2
  24. package/lib/typescript/src/index.d.ts.map +1 -1
  25. package/lib/typescript/src/unistyles.d.ts +28 -1
  26. package/lib/typescript/src/unistyles.d.ts.map +1 -1
  27. package/lib/typescript/src/utils/scale.d.ts +22 -0
  28. package/lib/typescript/src/utils/scale.d.ts.map +1 -1
  29. package/package.json +8 -6
  30. package/src/components/Alert/Alert.tsx +57 -46
  31. package/src/components/BottomSheet/BottomSheet.tsx +98 -55
  32. package/src/components/BottomSheetMenu/BottomSheetMenu.tsx +55 -48
  33. package/src/components/ChatInput/AttachmentsView.tsx +32 -18
  34. package/src/components/ChatInput/ChatInput.tsx +148 -99
  35. package/src/components/ChatInput/IconButton.tsx +21 -10
  36. package/src/components/ChatInput/MentionsInput.tsx +42 -27
  37. package/src/components/CheckBox/CheckBox.tsx +54 -33
  38. package/src/components/EmptyView/EmptyView.tsx +27 -20
  39. package/src/components/Header/Header.tsx +25 -18
  40. package/src/components/Header/ScreenHeader.tsx +17 -10
  41. package/src/components/Input/Input.tsx +118 -67
  42. package/src/components/Input/InputFrame.tsx +261 -0
  43. package/src/components/Input/index.ts +1 -0
  44. package/src/components/NeetoUIProvider/NeetoUIProvider.tsx +2 -1
  45. package/src/components/OnBoarding/OnBoarding.tsx +61 -54
  46. package/src/components/RadioButton/RadioButton.tsx +72 -31
  47. package/src/components/SearchBar/SearchBar.tsx +41 -26
  48. package/src/components/Skeleton/Skeleton.tsx +18 -11
  49. package/src/components/Toast/Toast.tsx +38 -31
  50. package/src/components/Typography/Typography.tsx +31 -2
  51. package/src/components/Typography/index.ts +5 -1
  52. package/src/index.ts +7 -2
  53. package/src/unistyles-init.ts +4 -0
  54. package/src/unistyles.ts +52 -4
  55. package/src/utils/scale.ts +36 -9
  56. package/src/types/neeto-icons-rn.d.ts +0 -33
@@ -17,7 +17,7 @@ import {
17
17
  type TextStyle,
18
18
  type ViewStyle,
19
19
  } from "react-native";
20
- import { Check } from "@bigbinary/neeto-icons-rn";
20
+ import Check from "@bigbinary/neeto-icons-rn/icons/Check";
21
21
  import {
22
22
  BottomSheetBackdrop,
23
23
  BottomSheetFlatList,
@@ -28,7 +28,7 @@ import {
28
28
  import { useSafeAreaInsets } from "react-native-safe-area-context";
29
29
  import { StyleSheet, useUnistyles } from "react-native-unistyles";
30
30
 
31
- import { moderateScale } from "../../utils/scale";
31
+ import { moderateScale, scaleFor } from "../../utils/scale";
32
32
  import { Divider } from "../Divider";
33
33
  import { Loader } from "../Loader";
34
34
  import { SearchBar, type SearchBarProps } from "../SearchBar";
@@ -78,7 +78,14 @@ export type BottomSheetProps<T> = {
78
78
  noResultsLabel?: string;
79
79
  NoResultsComponent?: ComponentType;
80
80
  onBackdropPress?: () => void;
81
+ /** Absolute cap, in points. Wins over maxHeightRatio. */
81
82
  maxHeight?: number;
83
+ /**
84
+ * Cap as a fraction of screen height, so a long list stops short of
85
+ * covering the screen. Override per sheet, or pass maxHeight for an
86
+ * absolute value.
87
+ */
88
+ maxHeightRatio?: number;
82
89
  style?: StyleProp<ViewStyle>;
83
90
  titleContainerStyle?: StyleProp<ViewStyle>;
84
91
  titleTextStyle?: StyleProp<TextStyle>;
@@ -117,6 +124,9 @@ const DefaultContentRow = ({
117
124
 
118
125
  const ItemSeparator = () => <Divider style={styles.separator} />;
119
126
 
127
+ /** 60% of the screen: enough to browse, never a full-screen takeover. */
128
+ const DEFAULT_MAX_HEIGHT_RATIO = 0.6;
129
+
120
130
  export const BottomSheet = <T,>({
121
131
  isVisible,
122
132
  hide,
@@ -144,28 +154,33 @@ export const BottomSheet = <T,>({
144
154
  NoResultsComponent,
145
155
  onBackdropPress,
146
156
  maxHeight,
157
+ maxHeightRatio = DEFAULT_MAX_HEIGHT_RATIO,
147
158
  style,
148
159
  titleContainerStyle,
149
160
  titleTextStyle,
150
161
  flatListRef,
151
162
  sheetProps,
152
163
  }: BottomSheetProps<T>) => {
153
- const { theme } = useUnistyles();
164
+ const { theme, rt } = useUnistyles();
154
165
  const insets = useSafeAreaInsets();
155
166
  const sheetRef = useRef<BottomSheetModal>(null);
156
167
  const [searchText, setSearchText] = useState("");
157
168
 
158
169
  const isVisibleRef = useRef(isVisible);
159
170
  isVisibleRef.current = isVisible;
160
- const hasPresentedRef = useRef(false);
171
+ // Tracks whether the underlying modal is still presented, so the effect
172
+ // below never dismisses a sheet gorhom has already torn down.
173
+ const isPresentedRef = useRef(false);
161
174
 
162
175
  useEffect(() => {
163
176
  if (isVisible) {
164
- hasPresentedRef.current = true;
177
+ isPresentedRef.current = true;
165
178
  sheetRef.current?.present();
166
- } else if (hasPresentedRef.current) {
167
- // Never dismiss() a modal that was never presented: gorhom marks it
168
- // DISMISSING, which makes the next present() unmount immediately.
179
+ } else if (isPresentedRef.current) {
180
+ // Never dismiss() a modal that is not presented: gorhom leaves its
181
+ // status at DISMISSING, and from then on handlePortalRender drops
182
+ // every render, so present() can never bring the sheet back.
183
+ isPresentedRef.current = false;
169
184
  sheetRef.current?.dismiss();
170
185
  }
171
186
  setSearchText("");
@@ -173,8 +188,29 @@ export const BottomSheet = <T,>({
173
188
 
174
189
  // Gesture-driven closes (swipe down, backdrop tap, Android back) end here;
175
190
  // hand control back to the owner so its isVisible state stays in sync.
191
+ // rt.screen is 0x0 until unistyles has measured the window, and stays 0x0
192
+ // under its jest mock. Deriving a cap from that yields 0, and a sheet
193
+ // pinned to a 0 snap point opens invisible — so leave the cap off until a
194
+ // real height arrives rather than computing a broken one.
195
+ const cappedHeight =
196
+ maxHeight ??
197
+ (rt.screen.height ? rt.screen.height * maxHeightRatio : undefined);
198
+
199
+ // A searchable sheet is pinned to one height: dynamic sizing re-measures
200
+ // the filtered list on every keystroke, so the sheet shrinks under the
201
+ // user while they type. Everything else stays content-sized and only gets
202
+ // the cap — a four-row menu should not claim 60% of the screen.
203
+ const sizingProps =
204
+ canSearch && cappedHeight
205
+ ? { snapPoints: [cappedHeight], enableDynamicSizing: false }
206
+ : { maxDynamicContentSize: cappedHeight };
207
+
176
208
  const handleDismiss = useCallback(() => {
177
209
  setSearchText("");
210
+ // gorhom has already unmounted the sheet and reset its status by the
211
+ // time this fires, so the isVisible=false render that hide() triggers
212
+ // must not dismiss again.
213
+ isPresentedRef.current = false;
178
214
  if (!isVisibleRef.current) return;
179
215
 
180
216
  hide();
@@ -349,8 +385,8 @@ export const BottomSheet = <T,>({
349
385
  handleIndicatorStyle={styles.handleIndicator}
350
386
  keyboardBehavior="interactive"
351
387
  keyboardBlurBehavior="restore"
352
- maxDynamicContentSize={maxHeight}
353
388
  ref={sheetRef}
389
+ {...sizingProps}
354
390
  {...sheetProps}
355
391
  // After the spread, and composed rather than replaced: this is the
356
392
  // only channel through which a gesture close reaches the owner, so an
@@ -420,49 +456,56 @@ export const BottomSheet = <T,>({
420
456
  );
421
457
  };
422
458
 
423
- const styles = StyleSheet.create(theme => ({
424
- sheetBackground: {
425
- backgroundColor: theme.colors.background.primary,
426
- borderTopLeftRadius: moderateScale(20),
427
- borderTopRightRadius: moderateScale(20),
428
- },
429
- handleIndicator: {
430
- backgroundColor: theme.colors.palette.grey400,
431
- },
432
- separator: {
433
- backgroundColor: theme.colors.palette.grey200,
434
- },
435
- header: {
436
- backgroundColor: theme.colors.background.primary,
437
- gap: moderateScale(16),
438
- paddingBottom: moderateScale(12),
439
- },
440
- titleRow: {
441
- alignItems: "center",
442
- flexDirection: "row",
443
- justifyContent: "space-between",
444
- },
445
- doneLabel: {
446
- color: theme.colors.palette.purple500,
447
- },
448
- defaultRow: {
449
- alignItems: "center",
450
- flexDirection: "row",
451
- gap: moderateScale(8),
452
- justifyContent: "space-between",
453
- minHeight: moderateScale(44),
454
- },
455
- createRow: {
456
- alignItems: "center",
457
- height: moderateScale(40),
458
- justifyContent: "center",
459
- },
460
- noResults: {
461
- alignItems: "center",
462
- marginBottom: moderateScale(2),
463
- paddingVertical: moderateScale(2),
464
- },
465
- mutedLabel: {
466
- color: theme.colors.text.secondary,
467
- },
468
- }));
459
+ const styles = StyleSheet.create((theme, rt) => {
460
+ // Reading rt.screen registers the dependency that has this sheet
461
+ // recomputed on a window resize, so scaled sizes follow rotation,
462
+ // split-screen and foldables instead of freezing at first render.
463
+ const moderateScale = scaleFor(rt.screen);
464
+
465
+ return {
466
+ sheetBackground: {
467
+ backgroundColor: theme.colors.background.primary,
468
+ borderTopLeftRadius: moderateScale(20),
469
+ borderTopRightRadius: moderateScale(20),
470
+ },
471
+ handleIndicator: {
472
+ backgroundColor: theme.colors.palette.grey400,
473
+ },
474
+ separator: {
475
+ backgroundColor: theme.colors.palette.grey200,
476
+ },
477
+ header: {
478
+ backgroundColor: theme.colors.background.primary,
479
+ gap: moderateScale(16),
480
+ paddingBottom: moderateScale(12),
481
+ },
482
+ titleRow: {
483
+ alignItems: "center",
484
+ flexDirection: "row",
485
+ justifyContent: "space-between",
486
+ },
487
+ doneLabel: {
488
+ color: theme.colors.palette.purple500,
489
+ },
490
+ defaultRow: {
491
+ alignItems: "center",
492
+ flexDirection: "row",
493
+ gap: moderateScale(8),
494
+ justifyContent: "space-between",
495
+ minHeight: moderateScale(44),
496
+ },
497
+ createRow: {
498
+ alignItems: "center",
499
+ height: moderateScale(40),
500
+ justifyContent: "center",
501
+ },
502
+ noResults: {
503
+ alignItems: "center",
504
+ marginBottom: moderateScale(2),
505
+ paddingVertical: moderateScale(2),
506
+ },
507
+ mutedLabel: {
508
+ color: theme.colors.text.secondary,
509
+ },
510
+ };
511
+ });
@@ -2,7 +2,7 @@ import { type ComponentType, type ReactNode } from "react";
2
2
  import { Pressable, View } from "react-native";
3
3
  import { StyleSheet, useUnistyles } from "react-native-unistyles";
4
4
 
5
- import { moderateScale } from "../../utils/scale";
5
+ import { moderateScale, scaleFor } from "../../utils/scale";
6
6
  import { BottomSheet } from "../BottomSheet";
7
7
  import { Typography } from "../Typography";
8
8
 
@@ -101,50 +101,57 @@ export const BottomSheetMenu = ({
101
101
  );
102
102
  };
103
103
 
104
- const styles = StyleSheet.create(theme => ({
105
- sheetContent: {
106
- paddingHorizontal: 0,
107
- },
108
- headerContainer: {
109
- borderBottomColor: theme.colors.palette.grey200,
110
- borderBottomWidth: moderateScale(1.5),
111
- paddingBottom: moderateScale(12),
112
- paddingHorizontal: moderateScale(16),
113
- paddingTop: moderateScale(8),
114
- },
115
- titleContainer: {
116
- borderBottomColor: theme.colors.palette.grey200,
117
- borderBottomWidth: moderateScale(1.5),
118
- paddingBottom: moderateScale(12),
119
- paddingHorizontal: moderateScale(16),
120
- },
121
- title: {
122
- alignSelf: "center",
123
- color: theme.colors.text.muted,
124
- },
125
- row: {
126
- alignItems: "center",
127
- flexDirection: "row",
128
- gap: moderateScale(12),
129
- paddingHorizontal: moderateScale(16),
130
- paddingVertical: moderateScale(14),
131
- },
132
- disabledRow: {
133
- opacity: 0.5,
134
- },
135
- separator: {
136
- borderBottomColor: theme.colors.palette.grey200,
137
- borderBottomWidth: StyleSheet.hairlineWidth,
138
- marginHorizontal: moderateScale(16),
139
- paddingHorizontal: 0,
140
- },
141
- cancelButton: {
142
- alignItems: "center",
143
- backgroundColor: theme.colors.palette.grey100,
144
- borderRadius: theme.radii.lg,
145
- justifyContent: "center",
146
- marginHorizontal: moderateScale(16),
147
- marginTop: moderateScale(16),
148
- paddingVertical: moderateScale(14),
149
- },
150
- }));
104
+ const styles = StyleSheet.create((theme, rt) => {
105
+ // Reading rt.screen registers the dependency that has this sheet
106
+ // recomputed on a window resize, so scaled sizes follow rotation,
107
+ // split-screen and foldables instead of freezing at first render.
108
+ const moderateScale = scaleFor(rt.screen);
109
+
110
+ return {
111
+ sheetContent: {
112
+ paddingHorizontal: 0,
113
+ },
114
+ headerContainer: {
115
+ borderBottomColor: theme.colors.palette.grey200,
116
+ borderBottomWidth: moderateScale(1.5),
117
+ paddingBottom: moderateScale(12),
118
+ paddingHorizontal: moderateScale(16),
119
+ paddingTop: moderateScale(8),
120
+ },
121
+ titleContainer: {
122
+ borderBottomColor: theme.colors.palette.grey200,
123
+ borderBottomWidth: moderateScale(1.5),
124
+ paddingBottom: moderateScale(12),
125
+ paddingHorizontal: moderateScale(16),
126
+ },
127
+ title: {
128
+ alignSelf: "center",
129
+ color: theme.colors.text.muted,
130
+ },
131
+ row: {
132
+ alignItems: "center",
133
+ flexDirection: "row",
134
+ gap: moderateScale(12),
135
+ paddingHorizontal: moderateScale(16),
136
+ paddingVertical: moderateScale(14),
137
+ },
138
+ disabledRow: {
139
+ opacity: 0.5,
140
+ },
141
+ separator: {
142
+ borderBottomColor: theme.colors.palette.grey200,
143
+ borderBottomWidth: StyleSheet.hairlineWidth,
144
+ marginHorizontal: moderateScale(16),
145
+ paddingHorizontal: 0,
146
+ },
147
+ cancelButton: {
148
+ alignItems: "center",
149
+ backgroundColor: theme.colors.palette.grey100,
150
+ borderRadius: theme.radii.lg,
151
+ justifyContent: "center",
152
+ marginHorizontal: moderateScale(16),
153
+ marginTop: moderateScale(16),
154
+ paddingVertical: moderateScale(14),
155
+ },
156
+ };
157
+ });
@@ -3,7 +3,7 @@ import { Pressable, View } from "react-native";
3
3
  import Animated, { FadeInDown, FadeInUp } from "react-native-reanimated";
4
4
  import { StyleSheet } from "react-native-unistyles";
5
5
 
6
- import { moderateScale } from "../../utils/scale";
6
+ import { scaleFor } from "../../utils/scale";
7
7
  import { Typography } from "../Typography";
8
8
 
9
9
  const Badge = ({
@@ -36,7 +36,11 @@ export const AttachmentsView = ({
36
36
  if (attachmentsCount === 0) return null;
37
37
 
38
38
  return isAttachmentsVisible ? (
39
- <Animated.View entering={FadeInUp}>{Attachments}</Animated.View>
39
+ // Held at its natural height: the cards inside are fixed-size, so a
40
+ // squeeze has to come out of the text area, not out of them.
41
+ <Animated.View entering={FadeInUp} style={styles.expanded}>
42
+ {Attachments}
43
+ </Animated.View>
40
44
  ) : (
41
45
  <Animated.View entering={FadeInDown}>
42
46
  <Pressable style={styles.badgeRow} onPress={onExpand}>
@@ -50,19 +54,29 @@ export const AttachmentsView = ({
50
54
  );
51
55
  };
52
56
 
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
- }));
57
+ const styles = StyleSheet.create((theme, rt) => {
58
+ // Reading rt.screen registers the dependency that has this sheet
59
+ // recomputed on a window resize, so scaled sizes follow rotation,
60
+ // split-screen and foldables instead of freezing at first render.
61
+ const moderateScale = scaleFor(rt.screen);
62
+
63
+ return {
64
+ expanded: {
65
+ flexShrink: 0,
66
+ },
67
+ badgeRow: {
68
+ alignItems: "flex-start",
69
+ flexDirection: "row",
70
+ flexWrap: "wrap",
71
+ gap: moderateScale(4),
72
+ },
73
+ badge: {
74
+ backgroundColor: theme.colors.background.oldLace,
75
+ borderRadius: moderateScale(20),
76
+ paddingHorizontal: moderateScale(4),
77
+ },
78
+ badgeOnNote: {
79
+ backgroundColor: theme.colors.background.primary,
80
+ },
81
+ };
82
+ });