@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.
- package/build/commonjs/Accordion/Accordion.js +3 -3
- package/build/commonjs/Accordion/Accordion.js.map +1 -1
- package/build/commonjs/ButtonBase/ButtonBase.js +56 -52
- package/build/commonjs/ButtonBase/ButtonBase.js.map +1 -1
- package/build/commonjs/CircularProgress/CircularProgress.js +19 -24
- package/build/commonjs/CircularProgress/CircularProgress.js.map +1 -1
- package/build/commonjs/ImageCore/ImageCoreNative.js +24 -26
- package/build/commonjs/ImageCore/ImageCoreNative.js.map +1 -1
- package/build/commonjs/Slide/Slide.js +12 -13
- package/build/commonjs/Slide/Slide.js.map +1 -1
- package/build/commonjs/Tabs/TabIndicator.js +2 -6
- package/build/commonjs/Tabs/TabIndicator.js.map +1 -1
- package/build/commonjs/Tabs/Tabs.js +46 -48
- package/build/commonjs/Tabs/Tabs.js.map +1 -1
- package/build/commonjs/Tabs/useTabCoordinates.js +44 -0
- package/build/commonjs/Tabs/useTabCoordinates.js.map +1 -0
- package/build/commonjs/Tabs/useTabsWidth.js +26 -0
- package/build/commonjs/Tabs/useTabsWidth.js.map +1 -0
- package/build/commonjs/Tooltip/Tooltip.js +18 -22
- package/build/commonjs/Tooltip/Tooltip.js.map +1 -1
- package/build/commonjs/animated/AnimatedPressable.js +2 -3
- package/build/commonjs/animated/AnimatedPressable.js.map +1 -1
- package/build/commonjs/hooks/useCollapsibleAppBar.js +46 -36
- package/build/commonjs/hooks/useCollapsibleAppBar.js.map +1 -1
- package/build/commonjs/hooks/useFadeInAppBar.js +35 -18
- package/build/commonjs/hooks/useFadeInAppBar.js.map +1 -1
- package/build/commonjs/hooks/useThrottle.js +3 -7
- package/build/commonjs/hooks/useThrottle.js.map +1 -1
- package/build/commonjs/internal/hooks/index.js +0 -8
- package/build/commonjs/internal/hooks/index.js.map +1 -1
- package/build/commonjs/internal/hooks/useHeight.js +2 -6
- package/build/commonjs/internal/hooks/useHeight.js.map +1 -1
- package/build/module/Accordion/Accordion.js +3 -3
- package/build/module/Accordion/Accordion.js.map +1 -1
- package/build/module/ButtonBase/ButtonBase.js +54 -48
- package/build/module/ButtonBase/ButtonBase.js.map +1 -1
- package/build/module/CircularProgress/CircularProgress.js +20 -21
- package/build/module/CircularProgress/CircularProgress.js.map +1 -1
- package/build/module/ImageCore/ImageCoreNative.js +20 -23
- package/build/module/ImageCore/ImageCoreNative.js.map +1 -1
- package/build/module/Slide/Slide.js +14 -10
- package/build/module/Slide/Slide.js.map +1 -1
- package/build/module/Tabs/TabIndicator.js +3 -7
- package/build/module/Tabs/TabIndicator.js.map +1 -1
- package/build/module/Tabs/Tabs.js +39 -39
- package/build/module/Tabs/Tabs.js.map +1 -1
- package/build/module/Tabs/useTabCoordinates.js +30 -0
- package/build/module/Tabs/useTabCoordinates.js.map +1 -0
- package/build/module/Tabs/useTabsWidth.js +18 -0
- package/build/module/Tabs/useTabsWidth.js.map +1 -0
- package/build/module/Tooltip/Tooltip.js +15 -15
- package/build/module/Tooltip/Tooltip.js.map +1 -1
- package/build/module/animated/AnimatedPressable.js +2 -3
- package/build/module/animated/AnimatedPressable.js.map +1 -1
- package/build/module/hooks/useCollapsibleAppBar.js +46 -34
- package/build/module/hooks/useCollapsibleAppBar.js.map +1 -1
- package/build/module/hooks/useFadeInAppBar.js +35 -14
- package/build/module/hooks/useFadeInAppBar.js.map +1 -1
- package/build/module/hooks/useThrottle.js +3 -3
- package/build/module/hooks/useThrottle.js.map +1 -1
- package/build/module/internal/hooks/index.js +0 -1
- package/build/module/internal/hooks/index.js.map +1 -1
- package/build/module/internal/hooks/useHeight.js +2 -2
- package/build/module/internal/hooks/useHeight.js.map +1 -1
- package/build/typescript/Tabs/useTabCoordinates.d.ts +7 -0
- package/build/typescript/Tabs/useTabsWidth.d.ts +2 -0
- package/build/typescript/animated/AnimatedPressable.d.ts +2 -2
- package/build/typescript/internal/hooks/index.d.ts +0 -1
- package/package.json +2 -2
- package/src/Accordion/Accordion.tsx +5 -3
- package/src/ButtonBase/ButtonBase.tsx +65 -43
- package/src/CircularProgress/CircularProgress.tsx +24 -30
- package/src/ImageCore/ImageCoreNative.tsx +17 -19
- package/src/Slide/Slide.tsx +17 -15
- package/src/Tabs/TabIndicator.tsx +4 -8
- package/src/Tabs/Tabs.tsx +37 -39
- package/src/Tabs/useTabCoordinates.ts +36 -0
- package/src/Tabs/useTabsWidth.ts +20 -0
- package/src/Tooltip/Tooltip.tsx +16 -16
- package/src/animated/AnimatedPressable.tsx +1 -2
- package/src/hooks/useCollapsibleAppBar.ts +41 -31
- package/src/hooks/useFadeInAppBar.ts +31 -15
- package/src/hooks/useThrottle.ts +3 -3
- package/src/internal/hooks/index.ts +0 -1
- package/src/internal/hooks/useHeight.ts +2 -2
- package/build/commonjs/ButtonBase/ButtonBase.ios.js +0 -101
- package/build/commonjs/ButtonBase/ButtonBase.ios.js.map +0 -1
- package/build/commonjs/ButtonBase/useDisabledReaction/index.js +0 -21
- package/build/commonjs/ButtonBase/useDisabledReaction/index.js.map +0 -1
- package/build/commonjs/ButtonBase/useDisabledReaction/index.native.js +0 -9
- package/build/commonjs/ButtonBase/useDisabledReaction/index.native.js.map +0 -1
- package/build/commonjs/ImageCore/ImageCoreNative.ios.js +0 -60
- package/build/commonjs/ImageCore/ImageCoreNative.ios.js.map +0 -1
- package/build/commonjs/internal/hooks/useWidth.js +0 -29
- package/build/commonjs/internal/hooks/useWidth.js.map +0 -1
- package/build/module/ButtonBase/ButtonBase.ios.js +0 -82
- package/build/module/ButtonBase/ButtonBase.ios.js.map +0 -1
- package/build/module/ButtonBase/useDisabledReaction/index.js +0 -12
- package/build/module/ButtonBase/useDisabledReaction/index.js.map +0 -1
- package/build/module/ButtonBase/useDisabledReaction/index.native.js +0 -2
- package/build/module/ButtonBase/useDisabledReaction/index.native.js.map +0 -1
- package/build/module/ImageCore/ImageCoreNative.ios.js +0 -45
- package/build/module/ImageCore/ImageCoreNative.ios.js.map +0 -1
- package/build/module/internal/hooks/useWidth.js +0 -15
- package/build/module/internal/hooks/useWidth.js.map +0 -1
- package/build/typescript/ButtonBase/ButtonBase.ios.d.ts +0 -2
- package/build/typescript/ButtonBase/useDisabledReaction/index.d.ts +0 -2
- package/build/typescript/ButtonBase/useDisabledReaction/index.native.d.ts +0 -2
- package/build/typescript/ImageCore/ImageCoreNative.ios.d.ts +0 -2
- package/build/typescript/internal/hooks/useWidth.d.ts +0 -2
- package/src/ButtonBase/ButtonBase.ios.tsx +0 -95
- package/src/ButtonBase/useDisabledReaction/index.native.ts +0 -4
- package/src/ButtonBase/useDisabledReaction/index.ts +0 -16
- package/src/ImageCore/ImageCoreNative.ios.tsx +0 -46
- 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
|
|
5
|
-
|
|
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
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
});
|
|
32
|
+
const animatedStyle = useAnimatedStyle(() => ({
|
|
33
|
+
transform: [{ rotate: `${rotate.value}deg` }],
|
|
34
|
+
}), []);
|
|
46
35
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
withTiming(
|
|
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
|
|
57
|
-
<CircularProgressIcon/>
|
|
58
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
26
|
+
const handleLoad = useCallback(() => {
|
|
27
|
+
Animated.timing(opacity, {
|
|
28
|
+
toValue: LOADED_OPACITY,
|
|
29
|
+
duration: ANIMATION_DURATION,
|
|
30
|
+
useNativeDriver: true,
|
|
31
|
+
}).start();
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
46
|
-
|
|
43
|
+
{ opacity },
|
|
44
|
+
{ width, height },
|
|
47
45
|
]}
|
|
48
46
|
/>
|
|
49
47
|
);
|
package/src/Slide/Slide.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import {
|
|
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
|
|
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
|
-
|
|
33
|
+
useEffect(() => {
|
|
34
34
|
if (appear) {
|
|
35
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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] =
|
|
59
|
+
const [containerWidth, handleLayout] = useTabsWidth();
|
|
55
60
|
|
|
56
|
-
const scrollViewRef =
|
|
61
|
+
const scrollViewRef = useRef<ScrollView | null>(null);
|
|
57
62
|
|
|
58
|
-
const
|
|
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
|
|
66
|
+
const scrollValue = scrollValueProp ?? internalScrollValue;
|
|
63
67
|
|
|
64
|
-
const isReadyToRenderIndicator =
|
|
68
|
+
const isReadyToRenderIndicator = coordinates.length > 0;
|
|
65
69
|
|
|
66
|
-
|
|
70
|
+
useEffect(() => {
|
|
67
71
|
const animateTab = (index: number) => {
|
|
68
|
-
|
|
69
|
-
duration: 200,
|
|
70
|
-
easing: Easing.out(Easing.exp),
|
|
71
|
-
});
|
|
72
|
+
scrollValue.value = withTiming(index, ANIMATION_CONFIG);
|
|
72
73
|
};
|
|
73
74
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
animateTab(indexProp);
|
|
76
|
+
}, [indexProp, scrollValue]);
|
|
77
|
+
|
|
78
|
+
const scrollPosition = useMemo<number>(() => {
|
|
79
|
+
const coordinate = coordinates[indexProp - 1];
|
|
78
80
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
if (coordinate) {
|
|
82
|
+
const tabWidth = coordinate.x2 - coordinate.x1;
|
|
83
|
+
return Math.floor(coordinate.x1 + tabWidth / 2);
|
|
84
|
+
}
|
|
83
85
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const x = coordinate.x1 + tabWidth / 2;
|
|
86
|
+
return 0;
|
|
87
|
+
}, [indexProp, coordinates]);
|
|
87
88
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
};
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
const scrollView = scrollViewRef.current;
|
|
91
91
|
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
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
|
|
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
|
+
}
|
package/src/Tooltip/Tooltip.tsx
CHANGED
|
@@ -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
|
|
12
|
+
const DEFAULT_OPACITY = 0.8;
|
|
13
13
|
const initialLayout = { width: 0, height: 0, x: 0, y: 0 };
|
|
14
14
|
|
|
15
|
-
const
|
|
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] =
|
|
33
|
+
const [layout, setLayout] = useState(initialLayout);
|
|
34
34
|
|
|
35
35
|
const scale = useSharedValue(0);
|
|
36
36
|
|
|
37
|
-
const
|
|
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
|
-
|
|
57
|
-
const
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
const nextScaleValue = visible ? 1 : 0;
|
|
58
58
|
|
|
59
|
-
scale.value = withTiming(
|
|
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}, ${
|
|
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
|
-
<
|
|
82
|
-
|
|
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
|
-
</
|
|
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={
|
|
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
|
-
|
|
116
|
+
animatedStyle,
|
|
117
117
|
tooltipLayoutStyle,
|
|
118
118
|
tooltipStyle,
|
|
119
119
|
]}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import
|
|
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
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
|