Skip to content

Commit

Permalink
feat(scrollview): add support for horizontal scrollview
Browse files Browse the repository at this point in the history
  • Loading branch information
Artur Bien committed Dec 31, 2020
1 parent f14d164 commit aa9eabd
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 30 deletions.
45 changes: 45 additions & 0 deletions example/src/examples/ScrollViewExample.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React from 'react';
import { StyleSheet, View } from 'react-native';
import { Text, Panel, Cutout, ScrollView } from 'react95-native';

const lorem = `Lorem Ipsum is simply dummy text of the printing and typesetting
industry. Lorem Ipsum has been the industry standard dummy text
ever since the 1500s, when an unknown printer took a galley of
type and scrambled it to make a type specimen book. It has
survived not only five centuries, but also the leap into
electronic typesetting, remaining essentially unchanged. It was
popularised in the 1960s with the release of Letraset sheets
containing Lorem Ipsum passages, and more recently with desktop
publishing software like Aldus PageMaker including versions of
Lorem Ipsum.`;

const NumberInputExample = () => {
return (
<Panel style={styles.container}>
<Cutout style={{ height: 200 }}>
<ScrollView alwaysShowScrollbars>
<Text>{lorem}</Text>
</ScrollView>
</Cutout>
<Cutout style={{ marginTop: 20 }}>
<ScrollView alwaysShowScrollbars horizontal>
<View style={{ width: 1000 }}>
<Text>{lorem}</Text>
</View>
</ScrollView>
</Cutout>
</Panel>
);
};

const styles = StyleSheet.create({
container: {
flex: 1,
padding: 16,
},
fieldset: {
padding: 20,
},
});

export default NumberInputExample;
6 changes: 6 additions & 0 deletions example/src/examples/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import PanelExample from './PanelExample';
import ProgressExample from './ProgressExample';
import RadioExample from './RadioExample';
import ScrollPanelExample from './ScrollPanelExample';
import ScrollViewExample from './ScrollViewExample';
import SelectBoxExample from './SelectBoxExample';
import SelectExample from './SelectExample';
import SnackbarExample from './SnackbarExample';
Expand Down Expand Up @@ -68,6 +69,11 @@ export default [
title: 'Fieldset',
},
{ name: 'PanelExample', component: PanelExample, title: 'Panel' },
{
name: 'ScrollViewExample',
component: ScrollViewExample,
title: 'ScrollView',
},
{
name: 'ScrollPanelExample',
component: ScrollPanelExample,
Expand Down
75 changes: 45 additions & 30 deletions src/ScrollView/ScrollView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { Panel, Button } from '..';
type ScrollViewProps = React.ComponentProps<typeof View> & {
alwaysShowScrollbars?: boolean;
children: React.ReactNode;
horizontal?: boolean;
scrollViewProps?: React.ComponentProps<typeof RNScrollView>;
style?: StyleProp<ViewStyle>;
};
Expand All @@ -44,6 +45,7 @@ const Icon = (
const ScrollView = ({
alwaysShowScrollbars = false,
children,
horizontal = false,
scrollViewProps = {},
style,
...rest
Expand All @@ -53,29 +55,31 @@ const ScrollView = ({
const scrollViewRef = useRef<RNScrollView>(null);
const [contentOffset, setContentOffset] = useState({ x: 0, y: 0 });
const [contentSize, setContentSize] = useState(0);
const [scrollViewHeight, setScrollViewHeight] = useState(0);
const [scrollViewSize, setScrollViewSize] = useState(0);

const scrollElementHeightPercent = 100 * (scrollViewHeight / contentSize);
const visiblePercentage = 100 * (scrollViewSize / contentSize);

const scrollPerc =
(contentOffset.y / (contentSize - scrollViewHeight)) *
(100 - scrollElementHeightPercent);
const scrollAxis = horizontal ? 'x' : 'y';
const scrollDimension = horizontal ? 'width' : 'height';

const scrolledPercentage = (contentOffset[scrollAxis] / contentSize) * 100;
const thumbPosition = Math.max(
0,
Math.min(
100 - scrollElementHeightPercent,
parseFloat((scrollPerc || 0).toFixed(3)),
100 - visiblePercentage,
parseFloat(scrolledPercentage.toFixed(3)),
),
);

const moveScroll = (direction: -1 | 1) => {
if (scrollViewRef.current) {
scrollViewRef.current.scrollTo({ y: contentOffset.y + 24 * direction });
scrollViewRef.current.scrollTo({
[scrollAxis]: contentOffset[scrollAxis] + 24 * direction,
});
}
};

const contentFullyVisible = contentSize <= scrollViewHeight;
const contentFullyVisible = contentSize <= scrollViewSize;

const handleScroll = (e: NativeSyntheticEvent<NativeScrollEvent>) => {
scrollViewProps.onScroll?.(e);
Expand All @@ -84,32 +88,49 @@ const ScrollView = ({

const handleContentSizeChange = (width: number, height: number) => {
scrollViewProps.onContentSizeChange?.(width, height);
setContentSize(height);
setContentSize(horizontal ? width : height);
};

const handleLayout = (e: LayoutChangeEvent) => {
scrollViewProps.onLayout?.(e);
setScrollViewHeight(e.nativeEvent.layout.height);
setScrollViewSize(e.nativeEvent.layout[scrollDimension]);
};

return (
<View style={[styles.wrapper, style]} {...rest}>
<View
style={[
styles.wrapper,
{
flexDirection: horizontal ? 'column' : 'row',
},
style,
]}
{...rest}
>
<View style={[styles.content]}>
<RNScrollView
{...scrollViewProps}
showsVerticalScrollIndicator={false}
showsHorizontalScrollIndicator={false}
scrollEventThrottle={10}
ref={scrollViewRef}
onScroll={handleScroll}
onContentSizeChange={handleContentSizeChange}
onLayout={handleLayout}
horizontal={horizontal}
>
{children}
</RNScrollView>
</View>
{(!contentFullyVisible || alwaysShowScrollbars) && (
<View
style={[styles.scrollbarTrack, { backgroundColor: theme.material }]}
style={[
{
flexDirection: horizontal ? 'row' : 'column',
[scrollDimension]: '100%',
backgroundColor: theme.material,
},
]}
>
<ImageBackground
style={[styles.background]}
Expand All @@ -132,22 +153,25 @@ const ScrollView = ({
style={{
justifyContent: 'center',
alignItems: 'center',
transform: [{ rotate: '180deg' }],
transform: [{ rotate: horizontal ? '90deg' : '180deg' }],
}}
>
{Icon}
</View>
</Button>
<View style={[styles.scrollbar]}>
<View style={[styles.scrollbarTrack]}>
{!contentFullyVisible && (
// SCROLLBAR THUMB
<Panel
variant='outside'
style={[
styles.scrollbarBar,
{
position: 'absolute',
top: `${thumbPosition}%`,
height: `${scrollElementHeightPercent}%`,
[horizontal ? 'left' : 'top']: `${thumbPosition}%`,
height: horizontal
? scrollbarSize
: `${visiblePercentage}%`,
width: horizontal ? `${visiblePercentage}%` : scrollbarSize,
},
]}
/>
Expand All @@ -163,7 +187,7 @@ const ScrollView = ({
style={{
justifyContent: 'center',
alignItems: 'center',
// flex: 1,
transform: [{ rotate: horizontal ? '-90deg' : '0deg' }],
}}
>
{Icon}
Expand All @@ -178,24 +202,18 @@ const ScrollView = ({
const styles = StyleSheet.create({
wrapper: {
display: 'flex',
flexDirection: 'row',
height: 'auto',
position: 'relative',
},
content: {
flexGrow: 1,
flex: 1,
},
scrollbarTrack: {
height: '100%',
flexShrink: 1,
},
scrollbarButton: {
height: scrollbarSize,
width: scrollbarSize,
padding: 0,
},
scrollbar: {
width: scrollbarSize,
scrollbarTrack: {
overflow: 'hidden',
flex: 1,
},
Expand All @@ -206,9 +224,6 @@ const styles = StyleSheet.create({
bottom: 0,
left: 0,
},
scrollbarBar: {
width: '100%',
},
});

export default ScrollView;

0 comments on commit aa9eabd

Please sign in to comment.