Skip to content

Commit d83cc6c

Browse files
authored
Feat/with scroll enabler (#807)
* Add asScrollEnabled * Rename asScrollEnabled to withScrollEnabler * Fix type for FlatList \ ScrollView * Move screen to components * Simplify ref (not working) * Move ref to screen * Remove ref from screen since it creates a warning * Move to scrollEnablerProps * Fix dependencies * Remove extra renders * Move to useCallback * Fix dependencies
1 parent f3d2ecc commit d83cc6c

File tree

12 files changed

+431
-0
lines changed

12 files changed

+431
-0
lines changed

demo/src/screens/MenuStructure.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,11 @@ export const navigationData = {
8989
{title: 'StateScreen', tags: 'empty state screen', screen: 'unicorn.screens.EmptyStateScreen'},
9090
{title: 'TabController', tags: 'tabbar controller native', screen: 'unicorn.components.TabControllerScreen'},
9191
{title: 'TabBar', tags: 'tab bar', screen: 'unicorn.components.TabBarScreen'},
92+
{
93+
title: 'withScrollEnabler',
94+
tags: 'scroll enabled withScrollEnabler',
95+
screen: 'unicorn.components.WithScrollEnablerScreen'
96+
},
9297
{title: 'Wizard', tags: 'wizard', screen: 'unicorn.components.WizardScreen'}
9398
]
9499
},
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import _ from 'lodash';
2+
import React, {memo, useCallback} from 'react';
3+
import {
4+
FlatList,
5+
FlatListProps,
6+
StyleSheet,
7+
LayoutChangeEvent
8+
} from 'react-native';
9+
import {
10+
Colors,
11+
Text,
12+
View,
13+
withScrollEnabler,
14+
WithScrollEnablerProps
15+
} from 'react-native-ui-lib';
16+
17+
export type AutoLockScrollViewProps = FlatListProps<number> & {
18+
numberOfItems: number;
19+
};
20+
21+
const AutoLockFlatList = (props: AutoLockScrollViewProps) => {
22+
const numberOfItems = props.numberOfItems;
23+
24+
const WithScrollEnabler = withScrollEnabler(
25+
useCallback(
26+
(props: WithScrollEnablerProps) => {
27+
const getData = useCallback(
28+
(numberOfItems: number) => {
29+
return [...Array(numberOfItems).keys()];
30+
},
31+
[numberOfItems]
32+
);
33+
34+
const keyExtractor = useCallback((item: number) => {
35+
return item.toString();
36+
}, []);
37+
38+
const renderItem = useCallback(({index}: {index: number}) => {
39+
return (
40+
<View key={index} style={styles.item}>
41+
<Text>{index + 1}</Text>
42+
</View>
43+
);
44+
}, []);
45+
46+
const onContentSizeChange = useCallback(
47+
(contentWidth: number, contentHeight: number) => {
48+
_.invoke(props, 'onContentSizeChange', contentWidth, contentHeight);
49+
_.invoke(
50+
props,
51+
'scrollEnablerProps.onContentSizeChange',
52+
contentWidth,
53+
contentHeight
54+
);
55+
},
56+
[
57+
props.onContentSizeChange,
58+
props.scrollEnablerProps.onContentSizeChange
59+
]
60+
);
61+
62+
const onLayout = useCallback(
63+
(nativeEvent: LayoutChangeEvent) => {
64+
_.invoke(props, 'onLayout', nativeEvent);
65+
_.invoke(props, 'scrollEnablerProps.onLayout', nativeEvent);
66+
},
67+
[props.onLayout, props.scrollEnablerProps.onLayout]
68+
);
69+
70+
return (
71+
<FlatList
72+
{...props}
73+
style={styles.flatList}
74+
contentContainerStyle={styles.flatListContainer}
75+
showsHorizontalScrollIndicator={false}
76+
showsVerticalScrollIndicator={false}
77+
data={getData(numberOfItems)}
78+
renderItem={renderItem}
79+
keyExtractor={keyExtractor}
80+
onContentSizeChange={onContentSizeChange}
81+
onLayout={onLayout}
82+
scrollEnabled={props.scrollEnablerProps.scrollEnabled}
83+
/>
84+
);
85+
},
86+
[numberOfItems]
87+
)
88+
);
89+
90+
return <WithScrollEnabler {...props} />;
91+
};
92+
93+
export default memo(AutoLockFlatList);
94+
95+
const styles = StyleSheet.create({
96+
flatList: {
97+
height: 240
98+
},
99+
flatListContainer: {
100+
alignItems: 'center'
101+
},
102+
item: {
103+
width: 100,
104+
height: 100,
105+
margin: 9,
106+
backgroundColor: Colors.grey40,
107+
alignItems: 'center',
108+
justifyContent: 'center'
109+
}
110+
});
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import _ from 'lodash';
2+
import React, {memo, useCallback} from 'react';
3+
import {
4+
ScrollView,
5+
ScrollViewProps,
6+
StyleSheet,
7+
LayoutChangeEvent
8+
} from 'react-native';
9+
import {
10+
Colors,
11+
Text,
12+
View,
13+
withScrollEnabler,
14+
WithScrollEnablerProps
15+
} from 'react-native-ui-lib';
16+
17+
export type AutoLockScrollViewProps = ScrollViewProps & {
18+
numberOfItems: number;
19+
};
20+
21+
const AutoLockScrollView = (props: AutoLockScrollViewProps) => {
22+
const numberOfItems = props.numberOfItems;
23+
24+
const WithScrollEnabler = withScrollEnabler(
25+
useCallback(
26+
(props: WithScrollEnablerProps) => {
27+
const renderItem = useCallback((index: number) => {
28+
return (
29+
<View key={index} style={styles.item}>
30+
<Text>{index + 1}</Text>
31+
</View>
32+
);
33+
}, []);
34+
35+
const onContentSizeChange = useCallback(
36+
(contentWidth: number, contentHeight: number) => {
37+
_.invoke(props, 'onContentSizeChange', contentWidth, contentHeight);
38+
_.invoke(
39+
props,
40+
'scrollEnablerProps.onContentSizeChange',
41+
contentWidth,
42+
contentHeight
43+
);
44+
},
45+
[
46+
props.onContentSizeChange,
47+
props.scrollEnablerProps.onContentSizeChange
48+
]
49+
);
50+
51+
const onLayout = useCallback(
52+
(nativeEvent: LayoutChangeEvent) => {
53+
_.invoke(props, 'onLayout', nativeEvent);
54+
_.invoke(props, 'scrollEnablerProps.onLayout', nativeEvent);
55+
},
56+
[props.onLayout, props.scrollEnablerProps.onLayout]
57+
);
58+
59+
return (
60+
<ScrollView
61+
{...props}
62+
style={styles.scrollView}
63+
contentContainerStyle={styles.scrollViewContainer}
64+
showsHorizontalScrollIndicator={false}
65+
showsVerticalScrollIndicator={false}
66+
onContentSizeChange={onContentSizeChange}
67+
onLayout={onLayout}
68+
scrollEnabled={props.scrollEnablerProps.scrollEnabled}
69+
>
70+
{_.times(numberOfItems, renderItem)}
71+
</ScrollView>
72+
);
73+
},
74+
[numberOfItems]
75+
)
76+
);
77+
78+
return <WithScrollEnabler {...props} />;
79+
};
80+
81+
export default memo(AutoLockScrollView);
82+
83+
const styles = StyleSheet.create({
84+
scrollView: {
85+
height: 240
86+
},
87+
scrollViewContainer: {
88+
alignItems: 'center'
89+
},
90+
item: {
91+
width: 100,
92+
height: 100,
93+
margin: 9,
94+
backgroundColor: Colors.grey40,
95+
alignItems: 'center',
96+
justifyContent: 'center'
97+
}
98+
});
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import React, {Component} from 'react';
2+
import {LayoutChangeEvent} from 'react-native';
3+
import {Text, View} from 'react-native-ui-lib';
4+
import {
5+
renderHeader,
6+
renderBooleanOption,
7+
renderSliderOption
8+
} from '../../ExampleScreenPresenter';
9+
import AutoLockScrollView from './AutoLockScrollView';
10+
import AutoLockFlatList from './AutoLockFlatList';
11+
12+
class WithScrollEnablerScreen extends Component {
13+
state = {
14+
isListView: false,
15+
isHorizontal: false,
16+
numberOfItems: 3,
17+
contentWidth: undefined,
18+
contentHeight: undefined,
19+
layoutWidth: undefined,
20+
layoutHeight: undefined
21+
};
22+
23+
onContentSizeChange = (contentWidth: number, contentHeight: number) => {
24+
const {
25+
contentWidth: currentContentWidth,
26+
contentHeight: currentContentHeight
27+
} = this.state;
28+
if (
29+
currentContentWidth !== contentWidth ||
30+
currentContentHeight !== contentHeight
31+
) {
32+
this.setState({contentWidth, contentHeight});
33+
}
34+
};
35+
36+
onLayout = ({
37+
nativeEvent: {
38+
layout: {width, height}
39+
}
40+
}: LayoutChangeEvent) => {
41+
const {layoutWidth, layoutHeight} = this.state;
42+
if (width !== layoutWidth || height !== layoutHeight) {
43+
this.setState({layoutWidth: width, layoutHeight: height});
44+
}
45+
};
46+
47+
renderList = () => {
48+
const {isListView, isHorizontal, numberOfItems} = this.state;
49+
const Container = isListView ? AutoLockScrollView : AutoLockFlatList;
50+
51+
return (
52+
// @ts-ignore
53+
<Container
54+
key={`${isHorizontal}`}
55+
horizontal={isHorizontal}
56+
numberOfItems={numberOfItems}
57+
onContentSizeChange={this.onContentSizeChange}
58+
onLayout={this.onLayout}
59+
/>
60+
);
61+
};
62+
63+
renderData = () => {
64+
const {contentWidth, contentHeight, layoutWidth, layoutHeight} = this.state;
65+
const contentText = `Content {width, height}: ${contentWidth}, ${contentHeight}`;
66+
const layoutText = `Layout {width, height}: ${layoutWidth}, ${layoutHeight}`;
67+
return (
68+
<>
69+
<Text text70>{contentText}</Text>
70+
<Text text70>{layoutText}</Text>
71+
</>
72+
);
73+
};
74+
75+
renderOptions = () => {
76+
const {isListView, isHorizontal} = this.state;
77+
const orientationText = isHorizontal ? 'Horizontal' : 'Vertical';
78+
const listTypeText = isListView ? 'ListView' : 'FlatList';
79+
return (
80+
<>
81+
<View row>
82+
<View flex marginR-10>
83+
{renderBooleanOption.call(this, orientationText, 'isHorizontal')}
84+
</View>
85+
<View flex marginL-10>
86+
{renderBooleanOption.call(this, listTypeText, 'isListView')}
87+
</View>
88+
</View>
89+
{renderSliderOption.call(
90+
this,
91+
'Number of items shown',
92+
'numberOfItems',
93+
{
94+
min: 1,
95+
max: 5,
96+
step: 1,
97+
initial: 3
98+
}
99+
)}
100+
</>
101+
);
102+
};
103+
104+
render() {
105+
return (
106+
<View margin-10>
107+
{renderHeader('withScrollEnabler', {'marginB-10': true})}
108+
{this.renderOptions()}
109+
{this.renderData()}
110+
{this.renderList()}
111+
</View>
112+
);
113+
}
114+
}
115+
116+
export default WithScrollEnablerScreen;

demo/src/screens/componentScreens/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,5 +57,6 @@ export function registerScreens(registrar) {
5757
registrar('unicorn.screens.EmptyStateScreen', () => require('./EmptyStateScreen').default);
5858
registrar('unicorn.screens.LoadingScreen', () => require('./LoadingScreen').default);
5959
registrar('unicorn.screens.ModalScreen', () => require('./ModalScreen').default);
60+
registrar('unicorn.components.WithScrollEnablerScreen', () => require('./WithScrollEnablerScreen').default);
6061
}
6162

generatedTypes/commons/new.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export { default as UIComponent } from './UIComponent';
22
export { default as asBaseComponent, BaseComponentInjectedProps } from './asBaseComponent';
33
export { default as forwardRef, ForwardRefInjectedProps } from './forwardRef';
4+
export { default as withScrollEnabler } from './withScrollEnabler';
45
export { ContainerModifiers, MarginModifiers, TypographyModifiers, ColorsModifiers, BackgroundColorModifier } from './modifiers';
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import React from 'react';
2+
import { FlatListProps, ScrollViewProps, LayoutChangeEvent } from 'react-native';
3+
export declare type ScrollEnablerProps = {
4+
onContentSizeChange: (contentWidth: number, contentHeight: number) => void;
5+
onLayout: (event: LayoutChangeEvent) => void;
6+
scrollEnabled: boolean;
7+
};
8+
declare type SupportedViews = FlatListProps<any> | ScrollViewProps;
9+
export declare type WithScrollEnablerProps = SupportedViews & {
10+
scrollEnablerProps: ScrollEnablerProps;
11+
ref?: any;
12+
};
13+
declare function withScrollEnabler<PROPS extends SupportedViews>(WrappedComponent: React.ComponentType<WithScrollEnablerProps>): React.ComponentType<PROPS>;
14+
export default withScrollEnabler;

generatedTypes/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* Please use this file for declaring all the exports, so they could be picked up by typescript's complier
55
*/
66
export * from './style';
7+
export {withScrollEnabler} from './commons/new';
78
export {default as Card, CardPropTypes, CardSectionProps} from './components/card';
89
export {default as View, ViewPropTypes} from './components/view';
910
export {default as Text} from './components/text';

src/commons/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,8 @@ module.exports = {
2121
},
2222
get forwardRef() {
2323
return require('./forwardRef').default;
24+
},
25+
get withScrollEnabler() {
26+
return require('./withScrollEnabler').default;
2427
}
2528
};

src/commons/new.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
export {default as UIComponent} from './UIComponent';
33
export {default as asBaseComponent, BaseComponentInjectedProps} from './asBaseComponent';
44
export {default as forwardRef, ForwardRefInjectedProps} from './forwardRef';
5+
export {default as withScrollEnabler} from './withScrollEnabler';
56
export {
67
ContainerModifiers,
78
MarginModifiers,

0 commit comments

Comments
 (0)