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

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 (45) hide show
  1. package/build/commonjs/Tabs/TabIndicator.js +1 -5
  2. package/build/commonjs/Tabs/TabIndicator.js.map +1 -1
  3. package/build/commonjs/Tabs/Tabs.js +46 -48
  4. package/build/commonjs/Tabs/Tabs.js.map +1 -1
  5. package/build/commonjs/Tabs/useTabCoordinates.js +44 -0
  6. package/build/commonjs/Tabs/useTabCoordinates.js.map +1 -0
  7. package/build/commonjs/Tabs/useTabsWidth.js +26 -0
  8. package/build/commonjs/Tabs/useTabsWidth.js.map +1 -0
  9. package/build/commonjs/hooks/useCollapsibleAppBar.js +31 -15
  10. package/build/commonjs/hooks/useCollapsibleAppBar.js.map +1 -1
  11. package/build/commonjs/hooks/useFadeInAppBar.js +26 -8
  12. package/build/commonjs/hooks/useFadeInAppBar.js.map +1 -1
  13. package/build/commonjs/internal/hooks/index.js +0 -8
  14. package/build/commonjs/internal/hooks/index.js.map +1 -1
  15. package/build/module/Tabs/TabIndicator.js +2 -6
  16. package/build/module/Tabs/TabIndicator.js.map +1 -1
  17. package/build/module/Tabs/Tabs.js +39 -39
  18. package/build/module/Tabs/Tabs.js.map +1 -1
  19. package/build/module/Tabs/useTabCoordinates.js +30 -0
  20. package/build/module/Tabs/useTabCoordinates.js.map +1 -0
  21. package/build/module/Tabs/useTabsWidth.js +18 -0
  22. package/build/module/Tabs/useTabsWidth.js.map +1 -0
  23. package/build/module/hooks/useCollapsibleAppBar.js +31 -15
  24. package/build/module/hooks/useCollapsibleAppBar.js.map +1 -1
  25. package/build/module/hooks/useFadeInAppBar.js +26 -8
  26. package/build/module/hooks/useFadeInAppBar.js.map +1 -1
  27. package/build/module/internal/hooks/index.js +0 -1
  28. package/build/module/internal/hooks/index.js.map +1 -1
  29. package/build/typescript/Tabs/useTabCoordinates.d.ts +7 -0
  30. package/build/typescript/Tabs/useTabsWidth.d.ts +2 -0
  31. package/build/typescript/internal/hooks/index.d.ts +0 -1
  32. package/package.json +2 -2
  33. package/src/Tabs/TabIndicator.tsx +3 -7
  34. package/src/Tabs/Tabs.tsx +37 -39
  35. package/src/Tabs/useTabCoordinates.ts +36 -0
  36. package/src/Tabs/useTabsWidth.ts +20 -0
  37. package/src/hooks/useCollapsibleAppBar.ts +22 -11
  38. package/src/hooks/useFadeInAppBar.ts +23 -8
  39. package/src/internal/hooks/index.ts +0 -1
  40. package/build/commonjs/internal/hooks/useWidth.js +0 -29
  41. package/build/commonjs/internal/hooks/useWidth.js.map +0 -1
  42. package/build/module/internal/hooks/useWidth.js +0 -15
  43. package/build/module/internal/hooks/useWidth.js.map +0 -1
  44. package/build/typescript/internal/hooks/useWidth.d.ts +0 -2
  45. package/src/internal/hooks/useWidth.ts +0 -17
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 && scrollPosition > 0) {
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
+ }
@@ -71,17 +71,28 @@ export default function useCollapsibleAppBar(userOptions: Options = defaultOptio
71
71
 
72
72
  const elevationStyle = useElevationStyle(4);
73
73
  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
- });
74
+ if (Platform.OS === 'web') {
75
+ return {
76
+ transform: [{ translateY: translateY.value }],
77
+ boxShadow: overlapped.value ? elevationStyle?.boxShadow : 0,
78
+ };
79
+ }
80
+ if (Platform.OS === 'android') {
81
+ return {
82
+ transform: [{ translateY: translateY.value }],
83
+ elevation: overlapped.value ? elevationStyle?.elevation : 0,
84
+ };
85
+ }
86
+ if (Platform.OS === 'ios') {
87
+ return {
88
+ transform: [{ translateY: translateY.value }],
89
+ shadowColor: elevationStyle?.shadowColor,
90
+ shadowOffset: elevationStyle?.shadowOffset,
91
+ shadowRadius: elevationStyle?.shadowRadius,
92
+ shadowOpacity: overlapped.value ? elevationStyle?.shadowOpacity : 0,
93
+ };
94
+ }
95
+ return {};
85
96
  });
86
97
 
87
98
  const indexRef = React.useRef<number>(0);
@@ -77,14 +77,29 @@ export default function useFadeInAppBar(userOptions: Options = defaultOptions):
77
77
  const backgroundColor = theme.palette.background.default;
78
78
  const [r, g, b] = React.useMemo(() => rgb(backgroundColor), [backgroundColor]);
79
79
 
80
- const animatedAppBarStyle = useAnimatedStyle(() => ({
81
- backgroundColor: `rgba(${r}, ${g}, ${b}, ${normalized.value})`,
82
- elevation: normalized.value >= 1 ? 6 : 0,
83
- shadowColor: '#000',
84
- shadowOffset,
85
- shadowRadius: 4.65,
86
- shadowOpacity: normalized.value >= 1 ? 0.25 : 0,
87
- }));
80
+ const animatedAppBarStyle = useAnimatedStyle(() => {
81
+ if (Platform.OS === 'web') {
82
+ return {
83
+ backgroundColor: `rgba(${r}, ${g}, ${b}, ${normalized.value})`,
84
+ };
85
+ }
86
+ if (Platform.OS === 'android') {
87
+ return {
88
+ backgroundColor: `rgba(${r}, ${g}, ${b}, ${normalized.value})`,
89
+ elevation: normalized.value >= 1 ? 6 : 0,
90
+ };
91
+ }
92
+ if (Platform.OS === 'ios') {
93
+ return {
94
+ backgroundColor: `rgba(${r}, ${g}, ${b}, ${normalized.value})`,
95
+ shadowColor: '#000',
96
+ shadowOffset,
97
+ shadowRadius: 4.65,
98
+ shadowOpacity: normalized.value >= 1 ? 0.25 : 0,
99
+ };
100
+ }
101
+ return {};
102
+ });
88
103
 
89
104
  const animatedTitleStyle = useAnimatedStyle(() => ({
90
105
  opacity: normalized.value,
@@ -1,2 +1 @@
1
1
  export { default as useHeight } from './useHeight';
2
- export { default as useWidth } from './useWidth';
@@ -1,29 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.default = useWidth;
7
-
8
- var _react = _interopRequireDefault(require("react"));
9
-
10
- var _reactNative = require("react-native");
11
-
12
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
13
-
14
- function useWidth() {
15
- const window = (0, _reactNative.useWindowDimensions)();
16
-
17
- const [width, setWidth] = _react.default.useState(window.width);
18
-
19
- const onLayout = _react.default.useCallback(e => {
20
- const newWidth = e.nativeEvent.layout.width;
21
-
22
- if (newWidth > 0 && Math.round(newWidth) !== Math.round(width)) {
23
- setWidth(newWidth);
24
- }
25
- }, []);
26
-
27
- return [width, onLayout];
28
- }
29
- //# sourceMappingURL=useWidth.js.map
@@ -1 +0,0 @@
1
- {"version":3,"names":["useWidth","window","useWindowDimensions","width","setWidth","React","useState","onLayout","useCallback","e","newWidth","nativeEvent","layout","Math","round"],"sources":["useWidth.ts"],"sourcesContent":["import React from 'react';\nimport { LayoutChangeEvent, useWindowDimensions, ViewProps } from 'react-native';\n\nexport default function useWidth(): [number, ViewProps['onLayout']] {\n const window = useWindowDimensions();\n const [width, setWidth] = React.useState(window.width);\n\n const onLayout = React.useCallback((e: LayoutChangeEvent) => {\n const newWidth = e.nativeEvent.layout.width;\n\n if (newWidth > 0 && Math.round(newWidth) !== Math.round(width)) {\n setWidth(newWidth);\n }\n }, []);\n\n return [width, onLayout];\n}"],"mappings":";;;;;;;AAAA;;AACA;;;;AAEe,SAASA,QAAT,GAAqD;EAChE,MAAMC,MAAM,GAAG,IAAAC,gCAAA,GAAf;;EACA,MAAM,CAACC,KAAD,EAAQC,QAAR,IAAoBC,cAAA,CAAMC,QAAN,CAAeL,MAAM,CAACE,KAAtB,CAA1B;;EAEA,MAAMI,QAAQ,GAAGF,cAAA,CAAMG,WAAN,CAAmBC,CAAD,IAA0B;IACzD,MAAMC,QAAQ,GAAGD,CAAC,CAACE,WAAF,CAAcC,MAAd,CAAqBT,KAAtC;;IAEA,IAAIO,QAAQ,GAAG,CAAX,IAAgBG,IAAI,CAACC,KAAL,CAAWJ,QAAX,MAAyBG,IAAI,CAACC,KAAL,CAAWX,KAAX,CAA7C,EAAgE;MAC5DC,QAAQ,CAACM,QAAD,CAAR;IACH;EACJ,CANgB,EAMd,EANc,CAAjB;;EAQA,OAAO,CAACP,KAAD,EAAQI,QAAR,CAAP;AACH"}
@@ -1,15 +0,0 @@
1
- import React from 'react';
2
- import { useWindowDimensions } from 'react-native';
3
- export default function useWidth() {
4
- const window = useWindowDimensions();
5
- const [width, setWidth] = React.useState(window.width);
6
- const onLayout = React.useCallback(e => {
7
- const newWidth = e.nativeEvent.layout.width;
8
-
9
- if (newWidth > 0 && Math.round(newWidth) !== Math.round(width)) {
10
- setWidth(newWidth);
11
- }
12
- }, []);
13
- return [width, onLayout];
14
- }
15
- //# sourceMappingURL=useWidth.js.map
@@ -1 +0,0 @@
1
- {"version":3,"names":["React","useWindowDimensions","useWidth","window","width","setWidth","useState","onLayout","useCallback","e","newWidth","nativeEvent","layout","Math","round"],"sources":["useWidth.ts"],"sourcesContent":["import React from 'react';\nimport { LayoutChangeEvent, useWindowDimensions, ViewProps } from 'react-native';\n\nexport default function useWidth(): [number, ViewProps['onLayout']] {\n const window = useWindowDimensions();\n const [width, setWidth] = React.useState(window.width);\n\n const onLayout = React.useCallback((e: LayoutChangeEvent) => {\n const newWidth = e.nativeEvent.layout.width;\n\n if (newWidth > 0 && Math.round(newWidth) !== Math.round(width)) {\n setWidth(newWidth);\n }\n }, []);\n\n return [width, onLayout];\n}"],"mappings":"AAAA,OAAOA,KAAP,MAAkB,OAAlB;AACA,SAA4BC,mBAA5B,QAAkE,cAAlE;AAEA,eAAe,SAASC,QAAT,GAAqD;EAChE,MAAMC,MAAM,GAAGF,mBAAmB,EAAlC;EACA,MAAM,CAACG,KAAD,EAAQC,QAAR,IAAoBL,KAAK,CAACM,QAAN,CAAeH,MAAM,CAACC,KAAtB,CAA1B;EAEA,MAAMG,QAAQ,GAAGP,KAAK,CAACQ,WAAN,CAAmBC,CAAD,IAA0B;IACzD,MAAMC,QAAQ,GAAGD,CAAC,CAACE,WAAF,CAAcC,MAAd,CAAqBR,KAAtC;;IAEA,IAAIM,QAAQ,GAAG,CAAX,IAAgBG,IAAI,CAACC,KAAL,CAAWJ,QAAX,MAAyBG,IAAI,CAACC,KAAL,CAAWV,KAAX,CAA7C,EAAgE;MAC5DC,QAAQ,CAACK,QAAD,CAAR;IACH;EACJ,CANgB,EAMd,EANc,CAAjB;EAQA,OAAO,CAACN,KAAD,EAAQG,QAAR,CAAP;AACH"}
@@ -1,2 +0,0 @@
1
- import { ViewProps } from 'react-native';
2
- export default function useWidth(): [number, ViewProps['onLayout']];
@@ -1,17 +0,0 @@
1
- import React from 'react';
2
- import { LayoutChangeEvent, useWindowDimensions, ViewProps } from 'react-native';
3
-
4
- export default function useWidth(): [number, ViewProps['onLayout']] {
5
- const window = useWindowDimensions();
6
- const [width, setWidth] = React.useState(window.width);
7
-
8
- const onLayout = React.useCallback((e: LayoutChangeEvent) => {
9
- const newWidth = e.nativeEvent.layout.width;
10
-
11
- if (newWidth > 0 && Math.round(newWidth) !== Math.round(width)) {
12
- setWidth(newWidth);
13
- }
14
- }, []);
15
-
16
- return [width, onLayout];
17
- }