@fountain-ui/core 2.0.0-beta.10 → 2.0.0-beta.13

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 (115) hide show
  1. package/build/commonjs/Accordion/Accordion.js +3 -3
  2. package/build/commonjs/Accordion/Accordion.js.map +1 -1
  3. package/build/commonjs/ButtonBase/ButtonBase.js +56 -52
  4. package/build/commonjs/ButtonBase/ButtonBase.js.map +1 -1
  5. package/build/commonjs/CircularProgress/CircularProgress.js +19 -24
  6. package/build/commonjs/CircularProgress/CircularProgress.js.map +1 -1
  7. package/build/commonjs/ImageCore/ImageCoreNative.js +24 -26
  8. package/build/commonjs/ImageCore/ImageCoreNative.js.map +1 -1
  9. package/build/commonjs/Slide/Slide.js +12 -13
  10. package/build/commonjs/Slide/Slide.js.map +1 -1
  11. package/build/commonjs/Tabs/TabIndicator.js +2 -6
  12. package/build/commonjs/Tabs/TabIndicator.js.map +1 -1
  13. package/build/commonjs/Tabs/Tabs.js +46 -48
  14. package/build/commonjs/Tabs/Tabs.js.map +1 -1
  15. package/build/commonjs/Tabs/useTabCoordinates.js +44 -0
  16. package/build/commonjs/Tabs/useTabCoordinates.js.map +1 -0
  17. package/build/commonjs/Tabs/useTabsWidth.js +26 -0
  18. package/build/commonjs/Tabs/useTabsWidth.js.map +1 -0
  19. package/build/commonjs/Tooltip/Tooltip.js +18 -22
  20. package/build/commonjs/Tooltip/Tooltip.js.map +1 -1
  21. package/build/commonjs/animated/AnimatedPressable.js +2 -3
  22. package/build/commonjs/animated/AnimatedPressable.js.map +1 -1
  23. package/build/commonjs/hooks/useCollapsibleAppBar.js +46 -36
  24. package/build/commonjs/hooks/useCollapsibleAppBar.js.map +1 -1
  25. package/build/commonjs/hooks/useFadeInAppBar.js +35 -18
  26. package/build/commonjs/hooks/useFadeInAppBar.js.map +1 -1
  27. package/build/commonjs/hooks/useThrottle.js +3 -7
  28. package/build/commonjs/hooks/useThrottle.js.map +1 -1
  29. package/build/commonjs/internal/hooks/index.js +0 -8
  30. package/build/commonjs/internal/hooks/index.js.map +1 -1
  31. package/build/commonjs/internal/hooks/useHeight.js +2 -6
  32. package/build/commonjs/internal/hooks/useHeight.js.map +1 -1
  33. package/build/module/Accordion/Accordion.js +3 -3
  34. package/build/module/Accordion/Accordion.js.map +1 -1
  35. package/build/module/ButtonBase/ButtonBase.js +54 -48
  36. package/build/module/ButtonBase/ButtonBase.js.map +1 -1
  37. package/build/module/CircularProgress/CircularProgress.js +20 -21
  38. package/build/module/CircularProgress/CircularProgress.js.map +1 -1
  39. package/build/module/ImageCore/ImageCoreNative.js +20 -23
  40. package/build/module/ImageCore/ImageCoreNative.js.map +1 -1
  41. package/build/module/Slide/Slide.js +14 -10
  42. package/build/module/Slide/Slide.js.map +1 -1
  43. package/build/module/Tabs/TabIndicator.js +3 -7
  44. package/build/module/Tabs/TabIndicator.js.map +1 -1
  45. package/build/module/Tabs/Tabs.js +39 -39
  46. package/build/module/Tabs/Tabs.js.map +1 -1
  47. package/build/module/Tabs/useTabCoordinates.js +30 -0
  48. package/build/module/Tabs/useTabCoordinates.js.map +1 -0
  49. package/build/module/Tabs/useTabsWidth.js +18 -0
  50. package/build/module/Tabs/useTabsWidth.js.map +1 -0
  51. package/build/module/Tooltip/Tooltip.js +15 -15
  52. package/build/module/Tooltip/Tooltip.js.map +1 -1
  53. package/build/module/animated/AnimatedPressable.js +2 -3
  54. package/build/module/animated/AnimatedPressable.js.map +1 -1
  55. package/build/module/hooks/useCollapsibleAppBar.js +46 -34
  56. package/build/module/hooks/useCollapsibleAppBar.js.map +1 -1
  57. package/build/module/hooks/useFadeInAppBar.js +35 -14
  58. package/build/module/hooks/useFadeInAppBar.js.map +1 -1
  59. package/build/module/hooks/useThrottle.js +3 -3
  60. package/build/module/hooks/useThrottle.js.map +1 -1
  61. package/build/module/internal/hooks/index.js +0 -1
  62. package/build/module/internal/hooks/index.js.map +1 -1
  63. package/build/module/internal/hooks/useHeight.js +2 -2
  64. package/build/module/internal/hooks/useHeight.js.map +1 -1
  65. package/build/typescript/Tabs/useTabCoordinates.d.ts +7 -0
  66. package/build/typescript/Tabs/useTabsWidth.d.ts +2 -0
  67. package/build/typescript/animated/AnimatedPressable.d.ts +2 -2
  68. package/build/typescript/internal/hooks/index.d.ts +0 -1
  69. package/package.json +2 -2
  70. package/src/Accordion/Accordion.tsx +5 -3
  71. package/src/ButtonBase/ButtonBase.tsx +65 -43
  72. package/src/CircularProgress/CircularProgress.tsx +24 -30
  73. package/src/ImageCore/ImageCoreNative.tsx +17 -19
  74. package/src/Slide/Slide.tsx +17 -15
  75. package/src/Tabs/TabIndicator.tsx +4 -8
  76. package/src/Tabs/Tabs.tsx +37 -39
  77. package/src/Tabs/useTabCoordinates.ts +36 -0
  78. package/src/Tabs/useTabsWidth.ts +20 -0
  79. package/src/Tooltip/Tooltip.tsx +16 -16
  80. package/src/animated/AnimatedPressable.tsx +1 -2
  81. package/src/hooks/useCollapsibleAppBar.ts +41 -31
  82. package/src/hooks/useFadeInAppBar.ts +31 -15
  83. package/src/hooks/useThrottle.ts +3 -3
  84. package/src/internal/hooks/index.ts +0 -1
  85. package/src/internal/hooks/useHeight.ts +2 -2
  86. package/build/commonjs/ButtonBase/ButtonBase.ios.js +0 -101
  87. package/build/commonjs/ButtonBase/ButtonBase.ios.js.map +0 -1
  88. package/build/commonjs/ButtonBase/useDisabledReaction/index.js +0 -21
  89. package/build/commonjs/ButtonBase/useDisabledReaction/index.js.map +0 -1
  90. package/build/commonjs/ButtonBase/useDisabledReaction/index.native.js +0 -9
  91. package/build/commonjs/ButtonBase/useDisabledReaction/index.native.js.map +0 -1
  92. package/build/commonjs/ImageCore/ImageCoreNative.ios.js +0 -60
  93. package/build/commonjs/ImageCore/ImageCoreNative.ios.js.map +0 -1
  94. package/build/commonjs/internal/hooks/useWidth.js +0 -29
  95. package/build/commonjs/internal/hooks/useWidth.js.map +0 -1
  96. package/build/module/ButtonBase/ButtonBase.ios.js +0 -82
  97. package/build/module/ButtonBase/ButtonBase.ios.js.map +0 -1
  98. package/build/module/ButtonBase/useDisabledReaction/index.js +0 -12
  99. package/build/module/ButtonBase/useDisabledReaction/index.js.map +0 -1
  100. package/build/module/ButtonBase/useDisabledReaction/index.native.js +0 -2
  101. package/build/module/ButtonBase/useDisabledReaction/index.native.js.map +0 -1
  102. package/build/module/ImageCore/ImageCoreNative.ios.js +0 -45
  103. package/build/module/ImageCore/ImageCoreNative.ios.js.map +0 -1
  104. package/build/module/internal/hooks/useWidth.js +0 -15
  105. package/build/module/internal/hooks/useWidth.js.map +0 -1
  106. package/build/typescript/ButtonBase/ButtonBase.ios.d.ts +0 -2
  107. package/build/typescript/ButtonBase/useDisabledReaction/index.d.ts +0 -2
  108. package/build/typescript/ButtonBase/useDisabledReaction/index.native.d.ts +0 -2
  109. package/build/typescript/ImageCore/ImageCoreNative.ios.d.ts +0 -2
  110. package/build/typescript/internal/hooks/useWidth.d.ts +0 -2
  111. package/src/ButtonBase/ButtonBase.ios.tsx +0 -95
  112. package/src/ButtonBase/useDisabledReaction/index.native.ts +0 -4
  113. package/src/ButtonBase/useDisabledReaction/index.ts +0 -16
  114. package/src/ImageCore/ImageCoreNative.ios.tsx +0 -46
  115. package/src/internal/hooks/useWidth.ts +0 -17
@@ -1,20 +1,18 @@
1
- import React from 'react';
1
+ import React, { useEffect } from 'react';
2
2
  import { ViewProps } from 'react-native';
3
3
  import { NamedStylesStringUnion, UseStyles } from '@fountain-ui/styles';
4
- import Animated, {
5
- Easing,
6
- interpolate,
7
- useAnimatedStyle,
8
- useSharedValue,
9
- withRepeat,
10
- withTiming,
11
- } from 'react-native-reanimated';
4
+ import type { WithTimingConfig } from 'react-native-reanimated';
5
+ import Animated, { Easing, useAnimatedStyle, useSharedValue, withRepeat, withTiming } from 'react-native-reanimated';
12
6
  import { CircularProgress as CircularProgressIcon } from '../internal/icons';
13
- import { css } from '../styles';
14
7
  import { OverridableComponentProps } from '../types';
15
8
 
16
9
  type CircularProgressStyles = NamedStylesStringUnion<'root'>;
17
10
 
11
+ const ANIMATION_CONFIG: Readonly<WithTimingConfig> = { duration: 900, easing: Easing.linear };
12
+
13
+ const MIN_ROTATE_DEGREE = 0;
14
+ const MAX_ROTATE_DEGREE = 360;
15
+
18
16
  const useStyles: UseStyles<CircularProgressStyles> = function (): CircularProgressStyles {
19
17
  return {
20
18
  root: {
@@ -25,36 +23,32 @@ const useStyles: UseStyles<CircularProgressStyles> = function (): CircularProgre
25
23
  };
26
24
 
27
25
  export default function CircularProgress(props: OverridableComponentProps<ViewProps>) {
28
- const { style } = props;
26
+ const { style: styleProp } = props;
29
27
 
30
28
  const styles = useStyles();
31
29
 
32
- const rootStyle = css([
33
- styles.root,
34
- style,
35
- ]);
36
-
37
- const sharedSpin = useSharedValue(0);
38
-
39
- const spinStyle = useAnimatedStyle(() => {
40
- const interpolatedSpin = interpolate(sharedSpin.value, [0, 1], [0, 360]);
30
+ const rotate = useSharedValue(MIN_ROTATE_DEGREE);
41
31
 
42
- return {
43
- transform: [{ rotate: `${interpolatedSpin}deg` }],
44
- };
45
- });
32
+ const animatedStyle = useAnimatedStyle(() => ({
33
+ transform: [{ rotate: `${rotate.value}deg` }],
34
+ }), []);
46
35
 
47
- React.useEffect(() => {
48
- sharedSpin.value = withRepeat(
49
- withTiming(1, { duration: 900, easing: Easing.linear }),
36
+ useEffect(() => {
37
+ rotate.value = withRepeat(
38
+ withTiming(MAX_ROTATE_DEGREE, ANIMATION_CONFIG),
50
39
  -1,
51
40
  false,
52
41
  );
53
42
  }, []);
54
43
 
55
44
  return (
56
- <Animated.View style={[spinStyle, rootStyle]}>
57
- <CircularProgressIcon/>
58
- </Animated.View>
45
+ <Animated.View
46
+ children={<CircularProgressIcon/>}
47
+ style={[
48
+ animatedStyle,
49
+ styles.root,
50
+ styleProp,
51
+ ]}
52
+ />
59
53
  );
60
54
  };
@@ -1,13 +1,15 @@
1
- import React from 'react';
2
- import type { WithTimingConfig } from 'react-native-reanimated';
3
- import Animated, { useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated';
1
+ import React, { useCallback, useRef } from 'react';
2
+ import { Animated } from 'react-native';
4
3
  import FastImage from 'react-native-fast-image';
5
4
  import type ImageCoreProps from './ImageCoreProps';
6
5
 
7
6
  // @ts-ignore
8
7
  const AnimatedFastImage = Animated.createAnimatedComponent(FastImage);
9
8
 
10
- const animationConfig: WithTimingConfig = { duration: 150 };
9
+ const INITIAL_OPACITY = 0;
10
+ const LOADED_OPACITY = 1;
11
+
12
+ const ANIMATION_DURATION = 200;
11
13
 
12
14
  export default function ImageCore(props: ImageCoreProps) {
13
15
  const {
@@ -19,21 +21,17 @@ export default function ImageCore(props: ImageCoreProps) {
19
21
  width,
20
22
  } = props;
21
23
 
22
- const style = { width, height };
23
-
24
- const opacity = useSharedValue(0);
25
-
26
- const animatedStyle = useAnimatedStyle(() => ({
27
- opacity: opacity.value,
28
- }));
24
+ const opacity = useRef<Animated.Value>(new Animated.Value(INITIAL_OPACITY)).current;
29
25
 
30
- const handleLoad = () => {
31
- opacity.value = withTiming(1, animationConfig);
26
+ const handleLoad = useCallback(() => {
27
+ Animated.timing(opacity, {
28
+ toValue: LOADED_OPACITY,
29
+ duration: ANIMATION_DURATION,
30
+ useNativeDriver: true,
31
+ }).start();
32
32
 
33
- if (onLoad) {
34
- onLoad();
35
- }
36
- };
33
+ onLoad?.();
34
+ }, [onLoad]);
37
35
 
38
36
  return (
39
37
  <AnimatedFastImage
@@ -42,8 +40,8 @@ export default function ImageCore(props: ImageCoreProps) {
42
40
  resizeMode={resizeMode}
43
41
  source={{ uri: source.uri }}
44
42
  style={[
45
- animatedStyle,
46
- style,
43
+ { opacity },
44
+ { width, height },
47
45
  ]}
48
46
  />
49
47
  );
@@ -1,5 +1,5 @@
1
- import React from 'react';
2
- import { useWindowDimensions } from 'react-native';
1
+ import React, { useEffect } from 'react';
2
+ import { Dimensions } from 'react-native';
3
3
  import type { WithTimingConfig } from 'react-native-reanimated';
4
4
  import Animated, { Easing, runOnJS, useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated';
5
5
  import type SlideProps from './SlideProps';
@@ -7,6 +7,8 @@ import type SlideProps from './SlideProps';
7
7
  const defaultEnterDuration = 300;
8
8
  const defaultExitDuration = 300;
9
9
 
10
+ const getDisappearingOffsetY = (): number => Dimensions.get('window').height;
11
+
10
12
  export default function Slide(props: SlideProps) {
11
13
  const {
12
14
  animatedY: animatedYProp,
@@ -21,38 +23,38 @@ export default function Slide(props: SlideProps) {
21
23
  ...otherProps
22
24
  } = props;
23
25
 
24
- const window = useWindowDimensions();
25
-
26
- const y = useSharedValue(window.height);
26
+ const y = useSharedValue(getDisappearingOffsetY());
27
27
  const animatedY = animatedYProp || y;
28
28
 
29
29
  const animatedStyle = useAnimatedStyle(() => ({
30
30
  transform: [{ translateY: animatedY.value }],
31
- }));
31
+ }), []);
32
32
 
33
- React.useEffect(() => {
33
+ useEffect(() => {
34
34
  if (appear) {
35
- const enterConfig: WithTimingConfig = {
35
+ onEnter?.();
36
+
37
+ const toValue = 0;
38
+ const enterConfig: Readonly<WithTimingConfig> = {
36
39
  duration: enterDuration,
37
40
  easing: Easing.out(Easing.exp),
38
41
  };
39
42
 
40
- onEnter?.();
41
-
42
- animatedY.value = withTiming(0, enterConfig, isFinished => {
43
+ animatedY.value = withTiming(toValue, enterConfig, isFinished => {
43
44
  if (isFinished && onEntered) {
44
45
  runOnJS(onEntered)();
45
46
  }
46
47
  });
47
48
  } else {
48
- const exitConfig: WithTimingConfig = {
49
+ onExit?.();
50
+
51
+ const toValue = getDisappearingOffsetY();
52
+ const exitConfig: Readonly<WithTimingConfig> = {
49
53
  duration: exitDuration,
50
54
  easing: Easing.in(Easing.ease),
51
55
  };
52
56
 
53
- onExit?.();
54
-
55
- animatedY.value = withTiming(window.height, exitConfig, isFinished => {
57
+ animatedY.value = withTiming(toValue, exitConfig, isFinished => {
56
58
  if (isFinished && onExited) {
57
59
  runOnJS(onExited)();
58
60
  }
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import Animated, { useAnimatedStyle, useDerivedValue } from 'react-native-reanimated';
2
+ import Animated, { useAnimatedStyle } from 'react-native-reanimated';
3
3
  import { NamedStylesStringUnion, UseStyles } from '@fountain-ui/styles';
4
4
  import { useTheme } from '../styles';
5
5
  import type TabIndicatorProps from './TabIndicatorProps';
@@ -35,8 +35,9 @@ export default function TabIndicator(props: TabIndicatorProps) {
35
35
 
36
36
  const styles = useStyles();
37
37
 
38
- const layout = useDerivedValue(() => {
38
+ const animatedStyle = useAnimatedStyle(() => {
39
39
  const rawScrollValue = scrollValue.value;
40
+
40
41
  const index = Math.floor(rawScrollValue);
41
42
  const offset = rawScrollValue % 1;
42
43
 
@@ -53,12 +54,7 @@ export default function TabIndicator(props: TabIndicatorProps) {
53
54
  left: x1 + leftInset,
54
55
  width: Math.max(x2 - x1 - rightInset, 0),
55
56
  };
56
- });
57
-
58
- const animatedStyle = useAnimatedStyle(() => ({
59
- left: layout.value.left,
60
- width: layout.value.width,
61
- }));
57
+ }, [coordinates, scrollable]);
62
58
 
63
59
  return (
64
60
  <Animated.View
package/src/Tabs/Tabs.tsx CHANGED
@@ -1,13 +1,13 @@
1
- import React from 'react';
1
+ import React, { cloneElement, useEffect, useMemo, useRef } from 'react';
2
2
  import { GestureResponderEvent, LayoutChangeEvent, ScrollView, View } from 'react-native';
3
+ import type { WithTimingConfig } from 'react-native-reanimated';
3
4
  import { Easing, useSharedValue, withTiming } from 'react-native-reanimated';
4
5
  import { NamedStylesStringUnion, UseStyles } from '@fountain-ui/styles';
5
- import { isEveryDefined } from '@fountain-ui/utils';
6
6
  import { css, useTheme } from '../styles';
7
- import { useWidth } from '../internal/hooks';
8
7
  import type TabsProps from './TabsProps';
9
- import TabCoordinate, { defaultCoordinate } from './TabCoordinate';
10
8
  import TabIndicator from './TabIndicator';
9
+ import useTabsWidth from './useTabsWidth';
10
+ import useTabCoordinates from './useTabCoordinates';
11
11
 
12
12
  type TabsStyleKeys =
13
13
  | 'root'
@@ -34,6 +34,11 @@ const useStyles: UseStyles<TabsStyles> = function (): TabsStyles {
34
34
  };
35
35
  };
36
36
 
37
+ const ANIMATION_CONFIG: Readonly<WithTimingConfig> = {
38
+ duration: 200,
39
+ easing: Easing.out(Easing.exp),
40
+ };
41
+
37
42
  export default function Tabs(props: TabsProps) {
38
43
  const {
39
44
  children,
@@ -51,56 +56,49 @@ export default function Tabs(props: TabsProps) {
51
56
 
52
57
  const styles = useStyles();
53
58
 
54
- const [containerWidth, handleLayout] = useWidth();
59
+ const [containerWidth, handleLayout] = useTabsWidth();
55
60
 
56
- const scrollViewRef = React.useRef<ScrollView | null>(null);
61
+ const scrollViewRef = useRef<ScrollView | null>(null);
57
62
 
58
- const tabCount = React.Children.count(children);
59
- const [coordinates, setCoordinates] = React.useState<TabCoordinate[]>(() => new Array(tabCount));
63
+ const { coordinates, updateCoordinate } = useTabCoordinates(children);
60
64
 
61
65
  const internalScrollValue = useSharedValue(0);
62
- const scrollValue = scrollValueProp || internalScrollValue;
66
+ const scrollValue = scrollValueProp ?? internalScrollValue;
63
67
 
64
- const isReadyToRenderIndicator = isEveryDefined(coordinates);
68
+ const isReadyToRenderIndicator = coordinates.length > 0;
65
69
 
66
- React.useEffect(() => {
70
+ useEffect(() => {
67
71
  const animateTab = (index: number) => {
68
- internalScrollValue.value = withTiming(index, {
69
- duration: 200,
70
- easing: Easing.out(Easing.exp),
71
- });
72
+ scrollValue.value = withTiming(index, ANIMATION_CONFIG);
72
73
  };
73
74
 
74
- if (scrollValueProp === undefined) {
75
- animateTab(indexProp);
76
- }
77
- }, [indexProp, scrollValueProp, internalScrollValue]);
75
+ animateTab(indexProp);
76
+ }, [indexProp, scrollValue]);
77
+
78
+ const scrollPosition = useMemo<number>(() => {
79
+ const coordinate = coordinates[indexProp - 1];
78
80
 
79
- React.useEffect(() => {
80
- const snapTab = (index: number) => {
81
- const scrollView = scrollViewRef.current;
82
- const coordinate: TabCoordinate = coordinates[index - 1] || defaultCoordinate;
81
+ if (coordinate) {
82
+ const tabWidth = coordinate.x2 - coordinate.x1;
83
+ return Math.floor(coordinate.x1 + tabWidth / 2);
84
+ }
83
85
 
84
- if (scrollView) {
85
- const tabWidth = coordinate.x2 - coordinate.x1;
86
- const x = coordinate.x1 + tabWidth / 2;
86
+ return 0;
87
+ }, [indexProp, coordinates]);
87
88
 
88
- scrollView.scrollTo({ x, y: 0, animated: true });
89
- }
90
- };
89
+ useEffect(() => {
90
+ const scrollView = scrollViewRef.current;
91
91
 
92
- snapTab(indexProp);
93
- }, [indexProp, containerWidth, coordinates]);
92
+ if (scrollView) {
93
+ scrollView.scrollTo({ x: scrollPosition, y: 0, animated: true });
94
+ }
95
+ }, [scrollPosition, containerWidth]);
94
96
 
95
97
  const tabElements = React.Children.map(children, (child, index) => {
96
98
  const onLayout = (event: LayoutChangeEvent) => {
97
99
  const { x, width } = event.nativeEvent.layout;
98
100
 
99
- setCoordinates(prev => ([
100
- ...prev.slice(0, index),
101
- { x1: x, x2: x + width },
102
- ...prev.slice(index + 1),
103
- ]));
101
+ updateCoordinate(index, x, width);
104
102
  };
105
103
 
106
104
  const onMouseDown = (e: GestureResponderEvent) => {
@@ -121,7 +119,7 @@ export default function Tabs(props: TabsProps) {
121
119
  : (isReadyToRenderIndicator ? false : selected);
122
120
 
123
121
  //@ts-ignore
124
- return React.cloneElement(child, {
122
+ return cloneElement(child, {
125
123
  enableIndicator: enableIndicatorPlaceholder,
126
124
  onLayout,
127
125
  onPress,
@@ -169,10 +167,10 @@ export default function Tabs(props: TabsProps) {
169
167
  {indicator}
170
168
  </ScrollView>
171
169
  ) : (
172
- <>
170
+ <React.Fragment>
173
171
  {tabElements}
174
172
  {indicator}
175
- </>
173
+ </React.Fragment>
176
174
  )}
177
175
  </View>
178
176
  );
@@ -0,0 +1,36 @@
1
+ import React, { useRef, useState } from 'react';
2
+ import { isEveryDefined } from '@fountain-ui/utils';
3
+ import TabCoordinate from './TabCoordinate';
4
+
5
+ export interface UseTabCoordinates {
6
+ coordinates: TabCoordinate[];
7
+ updateCoordinate: (index: number, x: number, width: number) => void;
8
+ }
9
+
10
+ export default function useTabCoordinates(tabElements: React.ReactNode): UseTabCoordinates {
11
+ const incompleteCoordinatesRef = useRef<TabCoordinate[]>([]);
12
+
13
+ const [completeCoordinates, setCompleteCoordinates] = useState<TabCoordinate[]>([]);
14
+
15
+ const isAllCoordinatesDefined = (coordinates: TabCoordinate[]): boolean => {
16
+ const numberOfTab = React.Children.count(tabElements);
17
+ const numberOfCoordinates = coordinates.length;
18
+
19
+ const everyCoordinatesDefined = isEveryDefined(coordinates);
20
+
21
+ return numberOfTab === numberOfCoordinates && everyCoordinatesDefined;
22
+ };
23
+
24
+ const updateCoordinate = (index: number, x: number, width: number) => {
25
+ incompleteCoordinatesRef.current[index] = { x1: x, x2: x + width };
26
+
27
+ if (isAllCoordinatesDefined(incompleteCoordinatesRef.current)) {
28
+ setCompleteCoordinates(incompleteCoordinatesRef.current);
29
+ }
30
+ };
31
+
32
+ return {
33
+ coordinates: completeCoordinates,
34
+ updateCoordinate,
35
+ };
36
+ }
@@ -0,0 +1,20 @@
1
+ import { useCallback, useState } from 'react';
2
+ import { Dimensions, LayoutChangeEvent, ViewProps } from 'react-native';
3
+
4
+ const assumeInitialWidth = (): number => Dimensions.get('window').width;
5
+
6
+ const isIntegerPartEquals = (a: number, b: number) => Math.round(a) === Math.round(b);
7
+
8
+ const isIntegerPartDifferent = (a: number, b: number) => !isIntegerPartEquals(a, b);
9
+
10
+ export default function useTabsWidth(): [number, ViewProps['onLayout']] {
11
+ const [width, setWidth] = useState(assumeInitialWidth);
12
+
13
+ const onLayout = useCallback((e: LayoutChangeEvent) => {
14
+ const newWidth = e.nativeEvent.layout.width;
15
+
16
+ setWidth((prevWidth) => isIntegerPartDifferent(prevWidth, newWidth) ? newWidth : prevWidth);
17
+ }, []);
18
+
19
+ return [width, onLayout];
20
+ }
@@ -1,18 +1,18 @@
1
- import React from 'react';
1
+ import React, { useEffect, useState } from 'react';
2
2
  import { Text, View, ViewProps } from 'react-native';
3
- import { TouchableWithoutFeedback } from 'react-native-gesture-handler';
4
3
  import type { WithTimingConfig } from 'react-native-reanimated';
5
4
  import Animated, { useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated';
6
5
  import { rgb } from '@fountain-ui/utils';
6
+ import ButtonBase from '../ButtonBase';
7
7
  import { createFontStyle, css, useTheme } from '../styles';
8
8
  import { Close as CloseIcon } from '../internal/icons';
9
9
  import type TooltipProps from './TooltipProps';
10
10
  import UpArrow from './UpArrow';
11
11
 
12
- const defaultOpacity = 0.8;
12
+ const DEFAULT_OPACITY = 0.8;
13
13
  const initialLayout = { width: 0, height: 0, x: 0, y: 0 };
14
14
 
15
- const animationTimingConfig: WithTimingConfig = { duration: 150 };
15
+ const ANIMATION_CONFIG: Readonly<WithTimingConfig> = { duration: 150 };
16
16
 
17
17
  export default function Tooltip(props: TooltipProps) {
18
18
  const {
@@ -30,13 +30,13 @@ export default function Tooltip(props: TooltipProps) {
30
30
 
31
31
  const theme = useTheme();
32
32
 
33
- const [layout, setLayout] = React.useState(initialLayout);
33
+ const [layout, setLayout] = useState(initialLayout);
34
34
 
35
35
  const scale = useSharedValue(0);
36
36
 
37
- const tooltipAnimatedStyle = useAnimatedStyle(() => ({
37
+ const animatedStyle = useAnimatedStyle(() => ({
38
38
  transform: [{ scale: scale.value }],
39
- }));
39
+ }), []);
40
40
 
41
41
  const [r, g, b] = rgb(theme.palette.primary.main);
42
42
 
@@ -53,15 +53,15 @@ export default function Tooltip(props: TooltipProps) {
53
53
  overflow: visible ? undefined : 'hidden',
54
54
  };
55
55
 
56
- React.useEffect(() => {
57
- const nextValue = visible ? 1 : 0;
56
+ useEffect(() => {
57
+ const nextScaleValue = visible ? 1 : 0;
58
58
 
59
- scale.value = withTiming(nextValue, animationTimingConfig);
59
+ scale.value = withTiming(nextScaleValue, ANIMATION_CONFIG);
60
60
  }, [visible]);
61
61
 
62
62
  const touchableStyle: ViewProps['style'] = {
63
63
  alignItems: 'center',
64
- backgroundColor: `rgba(${r}, ${g}, ${b}, ${defaultOpacity})`,
64
+ backgroundColor: `rgba(${r}, ${g}, ${b}, ${DEFAULT_OPACITY})`,
65
65
  borderRadius: theme.shape.roundness,
66
66
  flexDirection: 'row',
67
67
  padding: theme.spacing(2),
@@ -78,8 +78,8 @@ export default function Tooltip(props: TooltipProps) {
78
78
  ]);
79
79
 
80
80
  const buttonElem = (
81
- <TouchableWithoutFeedback
82
- disallowInterruption={true}
81
+ <ButtonBase
82
+ pressEffect={'none'}
83
83
  onPress={onClose}
84
84
  >
85
85
  <View style={css(touchableStyle)}>
@@ -95,14 +95,14 @@ export default function Tooltip(props: TooltipProps) {
95
95
  height={20}
96
96
  />
97
97
  </View>
98
- </TouchableWithoutFeedback>
98
+ </ButtonBase>
99
99
  );
100
100
 
101
101
  const arrowElem = (
102
102
  <UpArrow
103
103
  upsideDown={placement === 'top'}
104
104
  fill={theme.palette.primary.main}
105
- opacity={defaultOpacity}
105
+ opacity={DEFAULT_OPACITY}
106
106
  />
107
107
  );
108
108
 
@@ -113,7 +113,7 @@ export default function Tooltip(props: TooltipProps) {
113
113
  <Animated.View
114
114
  onLayout={(event) => setLayout(event.nativeEvent.layout)}
115
115
  style={[
116
- tooltipAnimatedStyle,
116
+ animatedStyle,
117
117
  tooltipLayoutStyle,
118
118
  tooltipStyle,
119
119
  ]}
@@ -1,6 +1,5 @@
1
1
  import React from 'react';
2
- import Animated from 'react-native-reanimated';
2
+ import { Animated } from 'react-native';
3
3
  import Pressable from '../Pressable';
4
4
 
5
- // @ts-ignore
6
5
  export default Animated.createAnimatedComponent(Pressable);
@@ -1,5 +1,6 @@
1
- import React from 'react';
1
+ import { useRef } from 'react';
2
2
  import { Falsy, Keyboard, Platform, RegisteredStyle, ScrollViewProps, ViewProps, ViewStyle } from 'react-native';
3
+ import type { WithTimingConfig } from 'react-native-reanimated';
3
4
  import {
4
5
  runOnJS,
5
6
  useAnimatedScrollHandler,
@@ -45,7 +46,7 @@ const defaultOptions: Required<Options> = {
45
46
  keyboardDismissMode: 'none',
46
47
  };
47
48
 
48
- const ANIMATION_DURATION_MILLIS = 100;
49
+ const ANIMATION_CONFIG: Readonly<WithTimingConfig> = { duration: 100 };
49
50
 
50
51
  const SUPPORTS_DRAG_DETECTION = Platform.OS !== 'web';
51
52
 
@@ -62,7 +63,7 @@ export default function useCollapsibleAppBar(userOptions: Options = defaultOptio
62
63
  const [appBarHeight, onAppBarLayout] = useHeight();
63
64
  const [collapsibleToolbarHeight, onCollapsibleToolbarLayout] = useHeight();
64
65
 
65
- const maxTranslateY = useDerivedValue(() => -collapsibleToolbarHeight);
66
+ const maxTranslateY = useDerivedValue(() => -collapsibleToolbarHeight, [collapsibleToolbarHeight]);
66
67
 
67
68
  const translateY = useSharedValue<number>(0);
68
69
  const lastTranslateY = useSharedValue<number>(0);
@@ -71,21 +72,38 @@ export default function useCollapsibleAppBar(userOptions: Options = defaultOptio
71
72
 
72
73
  const elevationStyle = useElevationStyle(4);
73
74
  const animatedStyle = useAnimatedStyle(() => {
74
- return Platform.OS === 'web' ? ({
75
- transform: [{ translateY: translateY.value }],
76
- boxShadow: overlapped.value ? elevationStyle?.boxShadow : 0,
77
- }) : ({
78
- transform: [{ translateY: translateY.value }],
79
- elevation: overlapped.value ? elevationStyle?.elevation : 0,
80
- shadowColor: elevationStyle?.shadowColor,
81
- shadowOffset: elevationStyle?.shadowOffset,
82
- shadowRadius: elevationStyle?.shadowRadius,
83
- shadowOpacity: overlapped.value ? elevationStyle?.shadowOpacity : 0,
84
- });
85
- });
86
-
87
- const indexRef = React.useRef<number>(0);
88
- const offsetsRef = React.useRef<Array<number>>([]);
75
+ const transform = [{ translateY: translateY.value }];
76
+
77
+ if (Platform.OS === 'web') {
78
+ return {
79
+ transform,
80
+ boxShadow: overlapped.value ? elevationStyle?.boxShadow : 0,
81
+ };
82
+ }
83
+ if (Platform.OS === 'android') {
84
+ return {
85
+ transform,
86
+ elevation: overlapped.value ? elevationStyle?.elevation : 0,
87
+ };
88
+ }
89
+ if (Platform.OS === 'ios') {
90
+ return {
91
+ transform,
92
+ shadowColor: elevationStyle?.shadowColor,
93
+ shadowOffset: elevationStyle?.shadowOffset,
94
+ shadowRadius: elevationStyle?.shadowRadius,
95
+ shadowOpacity: overlapped.value ? elevationStyle?.shadowOpacity : 0,
96
+ };
97
+ }
98
+ return {};
99
+ }, [
100
+ /**
101
+ * FIXME: Consider add `elevationStyle` to dependencies.
102
+ */
103
+ ]);
104
+
105
+ const indexRef = useRef<number>(0);
106
+ const offsetsRef = useRef<Array<number>>([]);
89
107
 
90
108
  const onScrollViewChanged = (nextIndex: number) => {
91
109
  const prevIndex = indexRef.current;
@@ -105,9 +123,7 @@ export default function useCollapsibleAppBar(userOptions: Options = defaultOptio
105
123
 
106
124
  // If next ScrollView's offset is too short, expand app bar.
107
125
  if (translateY.value < 0 && savedOffsetY < appBarHeight) {
108
- translateY.value = withTiming(0, {
109
- duration: ANIMATION_DURATION_MILLIS,
110
- });
126
+ translateY.value = withTiming(0, ANIMATION_CONFIG);
111
127
  }
112
128
  };
113
129
 
@@ -136,15 +152,11 @@ export default function useCollapsibleAppBar(userOptions: Options = defaultOptio
136
152
  } else {
137
153
  if (offsetY > -maxTy) {
138
154
  if (ty === 0) {
139
- translateY.value = withTiming(Math.min(Math.max(-offsetY, maxTy), 0), {
140
- duration: ANIMATION_DURATION_MILLIS,
141
- });
155
+ translateY.value = withTiming(Math.min(Math.max(-offsetY, maxTy), 0), ANIMATION_CONFIG);
142
156
  }
143
157
  } else {
144
158
  if (ty === maxTy) {
145
- translateY.value = withTiming(0, {
146
- duration: ANIMATION_DURATION_MILLIS,
147
- });
159
+ translateY.value = withTiming(0, ANIMATION_CONFIG);
148
160
  }
149
161
  }
150
162
 
@@ -175,11 +187,9 @@ export default function useCollapsibleAppBar(userOptions: Options = defaultOptio
175
187
 
176
188
  overlapped.value = offsetY + nextTranslateY > 0;
177
189
 
178
- translateY.value = withTiming(nextTranslateY, {
179
- duration: ANIMATION_DURATION_MILLIS,
180
- });
190
+ translateY.value = withTiming(nextTranslateY, ANIMATION_CONFIG);
181
191
  },
182
- });
192
+ }, [keyboardDismissMode]);
183
193
 
184
194
  const hasCollapsible = collapsibleToolbarHeight > 0;
185
195