rnnovaui 0.9.4 → 0.9.6

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rnnovaui",
3
- "version": "0.9.4",
3
+ "version": "0.9.6",
4
4
  "description": "",
5
5
  "main": "src/index.js",
6
6
  "react-native": "src/index.js",
@@ -10,7 +10,6 @@ export const UIAccordionContent = memo(function UIAccordionContent({
10
10
  style,
11
11
  innerStyle,
12
12
  testID,
13
- onLayout,
14
13
  }) {
15
14
  const accordion = useUIAccordion();
16
15
 
@@ -18,129 +17,52 @@ export const UIAccordionContent = memo(function UIAccordionContent({
18
17
 
19
18
  const [contentHeight, setContentHeight] = useState(0);
20
19
 
21
- const [mounted, setMounted] = useState(item.open);
22
-
23
20
  const heightAnimation = useRef(new Animated.Value(item.open ? 1 : 0)).current;
24
21
 
25
22
  const opacityAnimation = useRef(
26
23
  new Animated.Value(item.open ? 1 : 0),
27
24
  ).current;
28
25
 
29
- const previousOpen = useRef(item.open);
30
-
31
26
  /*
32
- * -----------------------------------------------
33
- * CONTENT MEASUREMENT
34
- * -----------------------------------------------
35
- *
36
- * We only update the cached height when the
37
- * actual content height changes.
27
+ * ------------------------------------------------
28
+ * MEASURE REAL CONTENT HEIGHT
29
+ * ------------------------------------------------
38
30
  */
39
31
 
40
- const handleContentLayout = (event) => {
32
+ const handleLayout = (event) => {
41
33
  const height = Math.ceil(event.nativeEvent.layout.height);
42
34
 
43
35
  if (height > 0 && height !== contentHeight) {
44
36
  setContentHeight(height);
45
37
  }
46
-
47
- onLayout?.(event);
48
38
  };
49
39
 
50
40
  /*
51
- * -----------------------------------------------
52
- * OPEN / CLOSE ANIMATION
53
- * -----------------------------------------------
41
+ * ------------------------------------------------
42
+ * ANIMATION
43
+ * ------------------------------------------------
54
44
  */
55
45
 
56
46
  useEffect(() => {
57
- const wasOpen = previousOpen.current;
58
-
59
- previousOpen.current = item.open;
60
-
61
47
  /*
62
- * Nothing changed.
48
+ * If content has not been measured yet,
49
+ * don't try to animate to an unknown height.
63
50
  */
64
- if (wasOpen === item.open) {
51
+ if (contentHeight <= 0) {
65
52
  return;
66
53
  }
67
54
 
68
- /*
69
- * ---------------------------------------------
70
- * OPEN
71
- * ---------------------------------------------
72
- */
73
-
74
- if (item.open) {
75
- setMounted(true);
76
-
77
- /*
78
- * If content has not been measured yet,
79
- * wait for onLayout.
80
- */
81
- if (contentHeight <= 0) {
82
- heightAnimation.setValue(0);
83
-
84
- opacityAnimation.setValue(0);
85
-
86
- return;
87
- }
88
-
89
- if (!accordion.animated) {
90
- heightAnimation.setValue(1);
91
-
92
- opacityAnimation.setValue(1);
93
-
94
- return;
95
- }
96
-
97
- /*
98
- * Start exactly from closed.
99
- */
100
- heightAnimation.setValue(0);
101
-
102
- opacityAnimation.setValue(0);
103
-
104
- Animated.parallel([
105
- Animated.timing(heightAnimation, {
106
- toValue: 1,
107
-
108
- duration: accordion.animationDuration,
109
-
110
- useNativeDriver: false,
111
- }),
112
-
113
- Animated.timing(opacityAnimation, {
114
- toValue: 1,
115
-
116
- duration: Math.min(accordion.animationDuration, 160),
117
-
118
- useNativeDriver: false,
119
- }),
120
- ]).start();
121
-
122
- return;
123
- }
124
-
125
- /*
126
- * ---------------------------------------------
127
- * CLOSE
128
- * ---------------------------------------------
129
- */
130
-
131
55
  if (!accordion.animated) {
132
- heightAnimation.setValue(0);
133
-
134
- opacityAnimation.setValue(0);
56
+ heightAnimation.setValue(item.open ? 1 : 0);
135
57
 
136
- setMounted(false);
58
+ opacityAnimation.setValue(item.open ? 1 : 0);
137
59
 
138
60
  return;
139
61
  }
140
62
 
141
63
  Animated.parallel([
142
64
  Animated.timing(heightAnimation, {
143
- toValue: 0,
65
+ toValue: item.open ? 1 : 0,
144
66
 
145
67
  duration: accordion.animationDuration,
146
68
 
@@ -148,17 +70,13 @@ export const UIAccordionContent = memo(function UIAccordionContent({
148
70
  }),
149
71
 
150
72
  Animated.timing(opacityAnimation, {
151
- toValue: 0,
73
+ toValue: item.open ? 1 : 0,
152
74
 
153
- duration: Math.min(accordion.animationDuration, 150),
75
+ duration: Math.min(accordion.animationDuration, 160),
154
76
 
155
- useNativeDriver: false,
77
+ useNativeDriver: true,
156
78
  }),
157
- ]).start(({ finished }) => {
158
- if (finished) {
159
- setMounted(false);
160
- }
161
- });
79
+ ]).start();
162
80
  }, [
163
81
  item.open,
164
82
  contentHeight,
@@ -169,51 +87,26 @@ export const UIAccordionContent = memo(function UIAccordionContent({
169
87
  ]);
170
88
 
171
89
  /*
172
- * -----------------------------------------------
173
- * IMPORTANT:
174
- *
175
- * Keep the content mounted while measuring.
176
- *
177
- * We use a wrapper whose height is animated.
178
- * The inner content itself keeps its natural
179
- * height.
180
- * -----------------------------------------------
90
+ * ------------------------------------------------
91
+ * ANIMATED HEIGHT
92
+ * ------------------------------------------------
181
93
  */
182
94
 
183
- if (!mounted) {
184
- /*
185
- * Keep a non-visible measurement copy mounted
186
- * only when the component has never measured.
187
- *
188
- * Once contentHeight is known, don't keep
189
- * measurement content around.
190
- */
191
- if (contentHeight > 0) {
192
- return null;
193
- }
95
+ const animatedHeight = heightAnimation.interpolate({
96
+ inputRange: [0, 1],
194
97
 
195
- return (
196
- <View style={styles.measureOnly} pointerEvents="none">
197
- <View onLayout={handleContentLayout}>{children}</View>
198
- </View>
199
- );
200
- }
98
+ outputRange: [0, contentHeight],
99
+ });
201
100
 
202
101
  /*
203
- * -----------------------------------------------
204
- * ANIMATED HEIGHT
205
- * -----------------------------------------------
102
+ * ------------------------------------------------
103
+ * CONTENT
104
+ * ------------------------------------------------
105
+ *
106
+ * The inner content remains mounted all the time.
107
+ * Therefore backend content can have any height.
206
108
  */
207
109
 
208
- const animatedHeight =
209
- contentHeight > 0
210
- ? heightAnimation.interpolate({
211
- inputRange: [0, 1],
212
-
213
- outputRange: [0, contentHeight],
214
- })
215
- : 0;
216
-
217
110
  return (
218
111
  <Animated.View
219
112
  testID={testID}
@@ -229,7 +122,7 @@ export const UIAccordionContent = memo(function UIAccordionContent({
229
122
  style,
230
123
  ]}
231
124
  >
232
- <View style={[styles.inner, innerStyle]} onLayout={handleContentLayout}>
125
+ <View style={[styles.inner, innerStyle]} onLayout={handleLayout}>
233
126
  {children}
234
127
  </View>
235
128
  </Animated.View>
@@ -246,18 +139,6 @@ const styles = StyleSheet.create({
246
139
  inner: {
247
140
  width: "100%",
248
141
  },
249
-
250
- measureOnly: {
251
- position: "absolute",
252
-
253
- left: 0,
254
-
255
- right: 0,
256
-
257
- opacity: 0,
258
-
259
- zIndex: -1,
260
- },
261
142
  });
262
143
 
263
144
  UIAccordionContent.displayName = "UIAccordion.Content";
@@ -69,12 +69,12 @@ export const UITabsAnimationPresets = {
69
69
  },
70
70
 
71
71
  smooth: {
72
- animation: "indicatorSlide",
73
- duration: 240,
72
+ animation: "fade",
73
+ duration: 220,
74
74
  },
75
75
 
76
76
  snappy: {
77
- animation: "indicatorSlide",
77
+ animation: "fade",
78
78
  duration: 160,
79
79
  },
80
80
 
@@ -100,27 +100,29 @@ const TabsContext = createContext(null);
100
100
  * ======================================================= */
101
101
 
102
102
  function getSizeConfig(size) {
103
- if (size === "sm") {
104
- return {
105
- height: 42,
106
- horizontalPadding: 10,
107
- fontSize: 12,
108
- };
109
- }
103
+ switch (size) {
104
+ case "sm":
105
+ return {
106
+ height: 42,
107
+ horizontalPadding: 10,
108
+ fontSize: 12,
109
+ };
110
110
 
111
- if (size === "lg") {
112
- return {
113
- height: 58,
114
- horizontalPadding: 16,
115
- fontSize: 16,
116
- };
117
- }
111
+ case "lg":
112
+ return {
113
+ height: 58,
114
+ horizontalPadding: 16,
115
+ fontSize: 16,
116
+ };
118
117
 
119
- return {
120
- height: 48,
121
- horizontalPadding: 12,
122
- fontSize: 14,
123
- };
118
+ case "md":
119
+ default:
120
+ return {
121
+ height: 48,
122
+ horizontalPadding: 12,
123
+ fontSize: 14,
124
+ };
125
+ }
124
126
  }
125
127
 
126
128
  function getEasing(type) {
@@ -196,9 +198,14 @@ const UITabsRoot = forwardRef(function UITabs(
196
198
 
197
199
  indicatorAnimation,
198
200
 
201
+ /*
202
+ * IMPORTANT:
203
+ * Content defaults to fade.
204
+ * It does NOT slide by default.
205
+ */
199
206
  contentAnimation = "fade",
200
207
 
201
- directionAware = true,
208
+ directionAware = false,
202
209
 
203
210
  spring = {
204
211
  damping: 18,
@@ -302,6 +309,7 @@ const UITabsRoot = forwardRef(function UITabs(
302
309
  if (
303
310
  old &&
304
311
  old.x === layout.x &&
312
+ old.y === layout.y &&
305
313
  old.width === layout.width &&
306
314
  old.height === layout.height
307
315
  ) {
@@ -574,7 +582,7 @@ const UITabsList = memo(function UITabsList({
574
582
  showsHorizontalScrollIndicator={showsHorizontalScrollIndicator}
575
583
  keyboardShouldPersistTaps={keyboardShouldPersistTaps}
576
584
  bounces
577
- contentContainerStyle={styles.scrollContent}
585
+ contentContainerStyle={[styles.scrollContent]}
578
586
  >
579
587
  {content}
580
588
  </ScrollView>
@@ -657,7 +665,9 @@ const UITabsTrigger = memo(function UITabsTrigger({
657
665
  if (animation === "opacity") {
658
666
  Animated.timing(opacity, {
659
667
  toValue: 0.65,
668
+
660
669
  duration: 80,
670
+
661
671
  useNativeDriver: true,
662
672
  }).start();
663
673
 
@@ -675,7 +685,9 @@ const UITabsTrigger = memo(function UITabsTrigger({
675
685
 
676
686
  Animated.timing(opacity, {
677
687
  toValue: 0.8,
688
+
678
689
  duration: 80,
690
+
679
691
  useNativeDriver: true,
680
692
  }),
681
693
  ]).start();
@@ -693,7 +705,9 @@ const UITabsTrigger = memo(function UITabsTrigger({
693
705
 
694
706
  Animated.timing(opacity, {
695
707
  toValue: 1,
708
+
696
709
  duration: 100,
710
+
697
711
  useNativeDriver: true,
698
712
  }),
699
713
  ]).start();
@@ -887,9 +901,9 @@ const UITabsIndicator = memo(function UITabsIndicator({
887
901
 
888
902
  const targetWidth = activeLayout.width;
889
903
 
890
- const animation = tabs.indicatorAnimation;
904
+ const indicatorAnimation = tabs.indicatorAnimation;
891
905
 
892
- if (!tabs.animated || animation === "none") {
906
+ if (!tabs.animated || indicatorAnimation === "none") {
893
907
  animatedX.setValue(targetX);
894
908
 
895
909
  animatedWidth.setValue(targetWidth);
@@ -901,37 +915,51 @@ const UITabsIndicator = memo(function UITabsIndicator({
901
915
  return;
902
916
  }
903
917
 
904
- if (animation === "indicatorFade" || animation === "indicatorFadeScale") {
918
+ if (indicatorAnimation === "indicatorFade") {
905
919
  animatedOpacity.setValue(0);
906
920
  }
907
921
 
908
- if (animation === "indicatorScale" || animation === "indicatorFadeScale") {
922
+ if (
923
+ indicatorAnimation === "indicatorScale" ||
924
+ indicatorAnimation === "indicatorFadeScale"
925
+ ) {
909
926
  animatedScale.setValue(0.8);
910
927
  }
911
928
 
912
- if (animation === "indicatorSpring" || animation === "spring") {
929
+ if (
930
+ indicatorAnimation === "indicatorSpring" ||
931
+ indicatorAnimation === "spring"
932
+ ) {
913
933
  Animated.parallel([
914
934
  Animated.spring(animatedX, {
915
935
  toValue: targetX,
936
+
916
937
  useNativeDriver: false,
938
+
917
939
  ...tabs.spring,
918
940
  }),
919
941
 
920
942
  Animated.spring(animatedWidth, {
921
943
  toValue: targetWidth,
944
+
922
945
  useNativeDriver: false,
946
+
923
947
  ...tabs.spring,
924
948
  }),
925
949
 
926
950
  Animated.spring(animatedScale, {
927
951
  toValue: 1,
952
+
928
953
  useNativeDriver: false,
954
+
929
955
  ...tabs.spring,
930
956
  }),
931
957
 
932
958
  Animated.timing(animatedOpacity, {
933
959
  toValue: 1,
960
+
934
961
  duration: tabs.animationDuration,
962
+
935
963
  useNativeDriver: false,
936
964
  }),
937
965
  ]).start();
@@ -942,27 +970,37 @@ const UITabsIndicator = memo(function UITabsIndicator({
942
970
  Animated.parallel([
943
971
  Animated.timing(animatedX, {
944
972
  toValue: targetX,
973
+
945
974
  duration: tabs.animationDuration,
975
+
946
976
  easing: getEasing(tabs.animationEasing),
977
+
947
978
  useNativeDriver: false,
948
979
  }),
949
980
 
950
981
  Animated.timing(animatedWidth, {
951
982
  toValue: targetWidth,
983
+
952
984
  duration: tabs.animationDuration,
985
+
953
986
  easing: getEasing(tabs.animationEasing),
987
+
954
988
  useNativeDriver: false,
955
989
  }),
956
990
 
957
991
  Animated.timing(animatedOpacity, {
958
992
  toValue: 1,
993
+
959
994
  duration: tabs.animationDuration,
995
+
960
996
  useNativeDriver: false,
961
997
  }),
962
998
 
963
999
  Animated.timing(animatedScale, {
964
1000
  toValue: 1,
1001
+
965
1002
  duration: tabs.animationDuration,
1003
+
966
1004
  useNativeDriver: false,
967
1005
  }),
968
1006
  ]).start();
@@ -1039,6 +1077,18 @@ const UITabsContent = memo(function UITabsContent({
1039
1077
  style,
1040
1078
  contentContainerStyle,
1041
1079
 
1080
+ /*
1081
+ * Default is fade.
1082
+ *
1083
+ * Supported:
1084
+ * none
1085
+ * fade
1086
+ * scale
1087
+ * fadeScale
1088
+ * slide
1089
+ * slideFade
1090
+ * slideScale
1091
+ */
1042
1092
  animation,
1043
1093
 
1044
1094
  animationDuration,
@@ -1065,9 +1115,7 @@ const UITabsContent = memo(function UITabsContent({
1065
1115
 
1066
1116
  const previousActive = useRef(active);
1067
1117
 
1068
- const direction = tabs.getDirection(value);
1069
-
1070
- const resolvedAnimation = animation || tabs.contentAnimation;
1118
+ const resolvedAnimation = animation ?? tabs.contentAnimation ?? "fade";
1071
1119
 
1072
1120
  useEffect(() => {
1073
1121
  if (active) {
@@ -1119,6 +1167,8 @@ const UITabsContent = memo(function UITabsContent({
1119
1167
 
1120
1168
  duration: animationDuration ?? tabs.animationDuration,
1121
1169
 
1170
+ easing: getEasing(tabs.animationEasing),
1171
+
1122
1172
  useNativeDriver: true,
1123
1173
  }).start(() => {
1124
1174
  if (unmountOnExit) {
@@ -1142,10 +1192,22 @@ const UITabsContent = memo(function UITabsContent({
1142
1192
  return null;
1143
1193
  }
1144
1194
 
1195
+ /*
1196
+ * IMPORTANT:
1197
+ *
1198
+ * Default fade has:
1199
+ * - opacity animation
1200
+ * - NO translateX
1201
+ * - NO translateY
1202
+ *
1203
+ * Therefore it cannot behave
1204
+ * like a carousel.
1205
+ */
1206
+
1145
1207
  const isFade =
1146
1208
  resolvedAnimation === "fade" ||
1147
- resolvedAnimation === "slideFade" ||
1148
- resolvedAnimation === "fadeScale";
1209
+ resolvedAnimation === "fadeScale" ||
1210
+ resolvedAnimation === "slideFade";
1149
1211
 
1150
1212
  const isScale =
1151
1213
  resolvedAnimation === "scale" ||
@@ -1157,40 +1219,48 @@ const UITabsContent = memo(function UITabsContent({
1157
1219
  resolvedAnimation === "slideFade" ||
1158
1220
  resolvedAnimation === "slideScale";
1159
1221
 
1160
- const translateX = isSlide
1222
+ const translateX = isSlide && tabs.directionAware ? 16 : 0;
1223
+
1224
+ const animatedTranslateX = isSlide
1161
1225
  ? progress.interpolate({
1162
1226
  inputRange: [0, 1],
1163
- outputRange: [direction * 16, 0],
1227
+ outputRange: [translateX, 0],
1164
1228
  })
1165
1229
  : 0;
1166
1230
 
1167
- const scale = isScale
1231
+ const animatedScale = isScale
1168
1232
  ? progress.interpolate({
1169
1233
  inputRange: [0, 1],
1170
- outputRange: [0.96, 1],
1234
+ outputRange: [0.98, 1],
1171
1235
  })
1172
1236
  : 1;
1173
1237
 
1238
+ const animatedOpacity = isFade ? progress : 1;
1239
+
1174
1240
  return (
1175
1241
  <Animated.View
1176
1242
  testID={testID}
1177
1243
  accessible={accessible}
1178
1244
  accessibilityLabel={accessibilityLabel}
1179
1245
  style={[
1246
+ styles.content,
1247
+
1180
1248
  {
1181
- opacity: isFade ? progress : 1,
1249
+ opacity: animatedOpacity,
1182
1250
 
1183
1251
  transform: [
1184
1252
  {
1185
- translateX,
1253
+ translateX: animatedTranslateX,
1186
1254
  },
1255
+
1187
1256
  {
1188
- scale,
1257
+ scale: animatedScale,
1189
1258
  },
1190
1259
  ],
1191
1260
  },
1192
1261
 
1193
1262
  style,
1263
+
1194
1264
  contentContainerStyle,
1195
1265
  ]}
1196
1266
  {...props}
@@ -1222,19 +1292,21 @@ export function useUITabs() {
1222
1292
  * COMPOUND COMPONENT API
1223
1293
  * ======================================================= */
1224
1294
 
1225
- UITabsRoot.List = UITabsList;
1295
+ const UITabs = memo(UITabsRoot);
1226
1296
 
1227
- UITabsRoot.Trigger = UITabsTrigger;
1297
+ UITabs.List = UITabsList;
1228
1298
 
1229
- UITabsRoot.Content = UITabsContent;
1299
+ UITabs.Trigger = UITabsTrigger;
1230
1300
 
1231
- UITabsRoot.Indicator = UITabsIndicator;
1301
+ UITabs.Content = UITabsContent;
1302
+
1303
+ UITabs.Indicator = UITabsIndicator;
1232
1304
 
1233
1305
  /* =========================================================
1234
1306
  * DISPLAY NAMES
1235
1307
  * ======================================================= */
1236
1308
 
1237
- UITabsRoot.displayName = "UITabs";
1309
+ UITabs.displayName = "UITabs";
1238
1310
 
1239
1311
  UITabsList.displayName = "UITabs.List";
1240
1312
 
@@ -1248,7 +1320,7 @@ UITabsIndicator.displayName = "UITabs.Indicator";
1248
1320
  * EXPORT
1249
1321
  * ======================================================= */
1250
1322
 
1251
- export const UITabs = memo(UITabsRoot);
1323
+ export { UITabs };
1252
1324
 
1253
1325
  /* =========================================================
1254
1326
  * STYLES
@@ -1261,6 +1333,7 @@ const styles = StyleSheet.create({
1261
1333
 
1262
1334
  list: {
1263
1335
  width: "100%",
1336
+
1264
1337
  overflow: "hidden",
1265
1338
  },
1266
1339
 
@@ -1335,4 +1408,8 @@ const styles = StyleSheet.create({
1335
1408
 
1336
1409
  zIndex: 20,
1337
1410
  },
1411
+
1412
+ content: {
1413
+ width: "100%",
1414
+ },
1338
1415
  });