Skip to content

Commit 1ce4ff3

Browse files
M-i-k-e-lethanshar
andauthored
Feat/change tab controller center selected logic (#1103)
* Move TabBar to functional component * Fix Fader in RTL * change tab controller center selected logic (and move it to a helper hook) * Fix import (cycle) + generate types * Change the DYNAMIC logic (did not look good when scrolling pages) * Revert merging wrong commit * FocusItemsHelper --> useFocusItemsHelper * Add (and use) useScrollEnabler and useScrollReached hooks * Move hooks to the hooks folder * sideSpacing --> outerSpacing * Fix typo Co-authored-by: Ethan Sharabi <[email protected]> * renderSelectedIndicator --> selectedIndicator * Move useFocusItemsHelper * Rename useFocusItemsHelper --> useScrollIToItem * Fix typo * Create useScrollTo hook * Refactor Props and ResultProps --> better names Co-authored-by: Ethan Sharabi <[email protected]>
1 parent 465fa76 commit 1ce4ff3

File tree

21 files changed

+721
-319
lines changed

21 files changed

+721
-319
lines changed

demo/src/screens/componentScreens/FaderScreen.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import _ from 'lodash';
22
import React, {Component} from 'react';
33
import {StyleSheet, ScrollView} from 'react-native';
44
import {Colors, Text, View, Fader, withScrollReached, WithScrollReachedProps} from 'react-native-ui-lib';
5+
// @ts-ignore
56
import {renderHeader} from '../ExampleScreenPresenter';
67

78
const numberOfItems = 3;
@@ -10,6 +11,7 @@ const itemWidth = 100;
1011
const itemHeight = 100;
1112
const tintColor = undefined;
1213

14+
// @ts-ignore
1315
const horizontal = faderPosition === Fader.position.START || faderPosition === Fader.position.END;
1416

1517
class FaderScreen extends Component<WithScrollReachedProps> {

demo/src/screens/componentScreens/TabControllerScreen/index.tsx

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,18 +35,15 @@ class TabControllerScreen extends Component<{}, State> {
3535
this.state.items = this.generateTabItems();
3636
}
3737

38-
generateTabItems = (fewItems = this.state.fewItems, centerSelected = this.state.centerSelected): TabControllerItemProps[] => {
38+
generateTabItems = (fewItems = this.state.fewItems): TabControllerItemProps[] => {
3939
let items: TabControllerItemProps[] = _.chain(TABS)
4040
.take(fewItems ? 3 : TABS.length)
4141
.map<TabControllerItemProps>(tab => ({label: tab, key: tab}))
4242
.value();
4343

4444
const addItem: TabControllerItemProps = {icon: Assets.icons.demo.add, key: 'add', ignore: true, width: 60, onPress: this.onAddItem};
4545

46-
if (!centerSelected) {
47-
items = [...items, addItem];
48-
}
49-
return items;
46+
return [...items, addItem];
5047
};
5148

5249
componentDidMount() {
@@ -83,7 +80,7 @@ class TabControllerScreen extends Component<{}, State> {
8380
toggleCenterSelected = () => {
8481
const {fewItems, centerSelected} = this.state;
8582
this.setState({
86-
items: this.generateTabItems(fewItems, !centerSelected),
83+
items: this.generateTabItems(fewItems),
8784
centerSelected: !centerSelected,
8885
key: Date.now()
8986
});

generatedTypes/components/fader/index.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/// <reference types="react" />
2+
import { ImageProps } from '../image';
23
export declare enum FaderPosition {
34
/**
45
* @deprecated please use START instead
@@ -13,7 +14,7 @@ export declare enum FaderPosition {
1314
TOP = "TOP",
1415
BOTTOM = "BOTTOM"
1516
}
16-
export declare type FaderProps = {
17+
export declare type FaderProps = Pick<ImageProps, 'supportRTL'> & {
1718
/**
1819
* Whether the fader is visible (default is true)
1920
*/
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import React from 'react';
22
import { ViewProps, ScrollViewProps } from 'react-native';
3+
import { ForwardRefInjectedProps } from '../../commons/forwardRef';
34
export declare type FadedScrollViewProps = ViewProps & ScrollViewProps & {
45
children?: React.ReactNode | React.ReactNode[];
56
};
6-
declare const _default: React.ComponentClass<FadedScrollViewProps, any> | React.FunctionComponent<FadedScrollViewProps>;
7+
declare type Props = FadedScrollViewProps & ForwardRefInjectedProps;
8+
declare const _default: React.ComponentType<Props>;
79
export default _default;

generatedTypes/components/tabController/TabBarItem.d.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { PureComponent } from 'react';
2-
import { TextStyle, LayoutRectangle, LayoutChangeEvent, StyleProp, ViewStyle } from 'react-native';
2+
import { /* processColor, */ TextStyle, LayoutChangeEvent, StyleProp, ViewStyle } from 'react-native';
33
import _ from 'lodash';
44
import Reanimated from 'react-native-reanimated';
55
import { State } from 'react-native-gesture-handler';
@@ -86,7 +86,7 @@ interface Props extends TabControllerItemProps {
8686
targetPage: any;
8787
state: State;
8888
currentPage: Reanimated.Adaptable<number>;
89-
onLayout: (layout: Partial<LayoutRectangle>, index: number) => void;
89+
onLayout?: (event: LayoutChangeEvent, index: number) => void;
9090
}
9191
/**
9292
* @description: TabController's TabBarItem
@@ -102,8 +102,7 @@ export default class TabBarItem extends PureComponent<Props> {
102102
private itemWidth?;
103103
private itemRef;
104104
constructor(props: Props);
105-
onStateChange: (...args: any[]) => void;
106-
onLayout: ({ nativeEvent: { layout: { width } } }: LayoutChangeEvent) => void;
105+
onLayout: (event: LayoutChangeEvent) => void;
107106
onPress: () => void;
108107
getItemStyle(): any[];
109108
getLabelStyle(): (TextStyle | _.Dictionary<Reanimated.Node<number> | Reanimated.Node<string | number | boolean> | Reanimated.Node<"normal" | "bold" | "100" | "200" | "300" | "400" | "500" | "600" | "700" | "800" | "900"> | undefined> | undefined)[];

generatedTypes/hooks/index.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
11
export { default as useToggleValue } from './useToggleValue';
22
export { default as useDidUpdate } from './useDidUpdate';
3+
export { default as useScrollEnabler } from './useScrollEnabler';
4+
export { default as useScrollReached } from './useScrollReached';
5+
export { default as useScrollToItem } from './useScrollToItem';
6+
export { default as useScrollTo } from './useScrollTo';
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { LayoutChangeEvent } from 'react-native';
2+
export declare type ScrollEnablerProps = {
3+
/**
4+
* Whether the scroll is horizontal (default is false).
5+
*/
6+
horizontal?: boolean;
7+
};
8+
export declare type ScrollEnablerResultProps = {
9+
/**
10+
* onContentSizeChange callback (should be set to your onContentSizeChange).
11+
*/
12+
onContentSizeChange: (contentWidth: number, contentHeight: number) => void;
13+
/**
14+
* onLayout callback (should be set to your onLayout).
15+
*/
16+
onLayout: (event: LayoutChangeEvent) => void;
17+
/**
18+
* Whether the scroll should be enabled (should be set to your scrollEnabled).
19+
*/
20+
scrollEnabled: boolean;
21+
};
22+
declare const useScrollEnabler: (props?: ScrollEnablerProps) => ScrollEnablerResultProps;
23+
export default useScrollEnabler;
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { NativeSyntheticEvent, NativeScrollEvent } from 'react-native';
2+
export declare type ScrollEnablerProps = {
3+
/**
4+
* Whether the scroll is horizontal (default is false).
5+
*/
6+
horizontal?: boolean;
7+
/**
8+
* Allows to be notified prior to actually reaching the start \ end of the scroll (by the threshold).
9+
* Should be a positive value.
10+
*/
11+
threshold?: number;
12+
};
13+
export declare type ScrollEnablerResultProps = {
14+
/**
15+
* onScroll callback (should be set to your onScroll).
16+
*/
17+
onScroll: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
18+
/**
19+
* Is the scroll at the start (or equal\smaller than the threshold if one was given)
20+
*/
21+
isScrollAtStart?: boolean;
22+
/**
23+
* Is the scroll at the end (or equal\greater than the threshold if one was given)
24+
*/
25+
isScrollAtEnd?: boolean;
26+
};
27+
declare const useScrollReached: (props?: ScrollEnablerProps) => ScrollEnablerResultProps;
28+
export default useScrollReached;
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { RefObject } from 'react';
2+
import { ScrollView, FlatList } from 'react-native';
3+
export declare type ScrollToSupportedViews = ScrollView | FlatList;
4+
export declare type ScrollToProps<T extends ScrollToSupportedViews> = {
5+
/**
6+
* A reference to the ScrollView (or FlatList) which the items are in
7+
*/
8+
scrollViewRef?: RefObject<T>;
9+
/**
10+
* Is the scroll view horizontal (default is true)
11+
*/
12+
horizontal?: boolean;
13+
};
14+
export declare type ScrollToResultProps<T extends ScrollToSupportedViews> = {
15+
/**
16+
* A reference to the ScrollView (or FlatList) which the items are in (from the props or a created one)
17+
*/
18+
scrollViewRef: RefObject<T>;
19+
/**
20+
* scrollTo callback.
21+
* scrollToOffset - the x or y to scroll to.
22+
* animated - should the scroll be animated (default is true)
23+
*/
24+
scrollTo: (scrollToOffset: number, animated?: boolean) => void;
25+
};
26+
declare const useScrollTo: <T extends ScrollToSupportedViews>(props: ScrollToProps<T>) => ScrollToResultProps<T>;
27+
export default useScrollTo;
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { LayoutChangeEvent } from 'react-native';
2+
import { ScrollToProps, ScrollToSupportedViews, ScrollToResultProps } from '../useScrollTo';
3+
export declare enum OffsetType {
4+
CENTER = "CENTER",
5+
DYNAMIC = "DYNAMIC",
6+
LEFT = "LEFT",
7+
RIGHT = "RIGHT"
8+
}
9+
export declare type ScrollToItemProps<T extends ScrollToSupportedViews> = Pick<ScrollToProps<T>, 'scrollViewRef'> & {
10+
/**
11+
* The number of items
12+
*/
13+
itemsCount: number;
14+
/**
15+
* The selected item's index
16+
*/
17+
selectedIndex?: number;
18+
/**
19+
* Where would the item be located (default to CENTER)
20+
*/
21+
offsetType?: OffsetType;
22+
/**
23+
* Add a margin to the offset (default to true)
24+
* This gives a better UX
25+
* Not relevant to OffsetType.CENTER
26+
*/
27+
addOffsetMargin?: boolean;
28+
/**
29+
* How much space (padding \ margin) is there on the left\right of the items
30+
*/
31+
outerSpacing?: number;
32+
/**
33+
* How much space (padding \ margin) is there between each item
34+
*/
35+
innerSpacing?: number;
36+
};
37+
export declare type ScrollToItemResultProps<T extends ScrollToSupportedViews> = Pick<ScrollToResultProps<T>, 'scrollViewRef'> & {
38+
/**
39+
* This should be called by each ot the items' onLayout
40+
*/
41+
onItemLayout: (event: LayoutChangeEvent, index: number) => void;
42+
/**
43+
* The items' width
44+
*/
45+
itemsWidths: number[];
46+
/**
47+
* Use in order to focus the item with the specified index
48+
*/
49+
focusIndex: (index: number, animated?: boolean) => void;
50+
};
51+
declare const useScrollToItem: <T extends ScrollToSupportedViews>(props: ScrollToItemProps<T>) => ScrollToItemResultProps<T>;
52+
export default useScrollToItem;

src/commons/withScrollEnabler.tsx

Lines changed: 24 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import React, {useState, useCallback, useRef} from 'react';
2-
// eslint-disable-next-line no-unused-vars
32
import {FlatListProps, ScrollViewProps, LayoutChangeEvent} from 'react-native';
4-
// eslint-disable-next-line no-unused-vars
53
import forwardRef, {ForwardRefInjectedProps} from './forwardRef';
64
//@ts-ignore
75
import hoistStatics from 'hoist-non-react-statics';
@@ -21,9 +19,7 @@ export type WithScrollEnablerProps = {
2119

2220
type PropTypes = ForwardRefInjectedProps & SupportedViewsProps;
2321

24-
function withScrollEnabler<PROPS, STATICS = {}>(
25-
WrappedComponent: React.ComponentType<PROPS & WithScrollEnablerProps>
26-
): React.ComponentType<PROPS> & STATICS {
22+
function withScrollEnabler<PROPS, STATICS = {}>(WrappedComponent: React.ComponentType<PROPS & WithScrollEnablerProps>): React.ComponentType<PROPS> & STATICS {
2723
const ScrollEnabler: React.FunctionComponent<PROPS & PropTypes> = (props: PROPS & PropTypes) => {
2824
const [scrollEnabled, setScrollEnabled] = useState(true);
2925
const contentSize = useRef(0);
@@ -36,36 +32,32 @@ function withScrollEnabler<PROPS, STATICS = {}>(
3632
}
3733
}, [scrollEnabled]);
3834

39-
const onContentSizeChange = useCallback(
40-
(contentWidth: number, contentHeight: number) => {
41-
const size = props.horizontal ? contentWidth : contentHeight;
42-
if (size !== contentSize.current) {
43-
contentSize.current = size;
44-
if (layoutSize.current > 0) {
45-
checkScroll();
46-
}
35+
const onContentSizeChange = useCallback((contentWidth: number, contentHeight: number) => {
36+
const size = props.horizontal ? contentWidth : contentHeight;
37+
if (size !== contentSize.current) {
38+
contentSize.current = size;
39+
if (layoutSize.current > 0) {
40+
checkScroll();
4741
}
48-
},
49-
[props.horizontal, checkScroll]
50-
);
42+
}
43+
},
44+
[props.horizontal, checkScroll]);
5145

52-
const onLayout = useCallback(
53-
(event: LayoutChangeEvent) => {
54-
const {
55-
nativeEvent: {
56-
layout: {width, height}
57-
}
58-
} = event;
59-
const size = props.horizontal ? width : height;
60-
if (size !== layoutSize.current) {
61-
layoutSize.current = size;
62-
if (contentSize.current > 0) {
63-
checkScroll();
64-
}
46+
const onLayout = useCallback((event: LayoutChangeEvent) => {
47+
const {
48+
nativeEvent: {
49+
layout: {width, height}
6550
}
66-
},
67-
[props.horizontal, checkScroll]
68-
);
51+
} = event;
52+
const size = props.horizontal ? width : height;
53+
if (size !== layoutSize.current) {
54+
layoutSize.current = size;
55+
if (contentSize.current > 0) {
56+
checkScroll();
57+
}
58+
}
59+
},
60+
[props.horizontal, checkScroll]);
6961

7062
return (
7163
<WrappedComponent

0 commit comments

Comments
 (0)