Skip to content

Commit

Permalink
Merge pull request #4 from stoplightio/SL-151/scroller
Browse files Browse the repository at this point in the history
SL-151/scroller
  • Loading branch information
casserni authored Nov 29, 2018
2 parents f9d5baf + 330b9f9 commit f289e62
Show file tree
Hide file tree
Showing 17 changed files with 610 additions and 129 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,12 @@ TODO
- Icon
- Link
- List
- ListScroller
- Popup
- Portal
- Mark
- Menu
- ScrollBox
- ScrollList
- Table
- Text
- Textarea
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"@types/lodash": "4.x.x",
"@types/prismjs": "^1.9.0",
"@types/react": "16.x.x",
"@types/react-custom-scrollbars": "^4.0.5",
"@types/react-dom": "16.x.x",
"@types/react-input-autosize": "2.0.x",
"@types/react-textarea-autosize": "4.3.x",
Expand All @@ -57,8 +58,9 @@
"@types/styled-system": "3.0.x",
"lodash": "4.x.x",
"prismjs": "1.15.x",
"react-dom": "^16.7.0-alpha.2",
"react-contextmenu": "2.10.x",
"react-custom-scrollbars": "4.2.x",
"react-dom": "^16.7.0-alpha.2",
"react-input-autosize": "2.2.x",
"react-simple-code-editor": "0.7.x",
"react-textarea-autosize": "7.1.0-1",
Expand Down
79 changes: 0 additions & 79 deletions src/ListScroller.tsx

This file was deleted.

138 changes: 138 additions & 0 deletions src/ScrollBox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import * as React from 'react';
// @ts-ignore
import { positionValues, Scrollbars } from 'react-custom-scrollbars';

import { Box, IBoxProps } from './Box';
import { useScrollToHash } from './hooks/useScrollToHash';
import { getScrollTransform, getThumbDimension, horizontalTrackStyle, verticalTrackStyle } from './utils/scroll';

export interface IScrollBoxRef {
scrollTop: (top?: number) => void;
scrollLeft: (left?: number) => void;

scrollToTop: () => void;
scrollToBottom: () => void;
scrollToLeft: () => void;
scrollToRight: () => void;

getScrollLeft: () => number;
getScrollTop: () => number;
getScrollWidth: () => number;
getScrollHeight: () => number;

getClientWidth: () => number;
getClientHeight: () => number;

getThumbVerticalHeight: () => number;
getThumbHorizontalWidth: () => number;

getValues: () => any;
}

export interface IScrollBox extends IBoxProps {
innerRef?: React.RefObject<IScrollBoxRef>;

autoHeight?: boolean;
autoHideTimeout?: number;

// can scroll to an anchor/id
scrollTo?: string;

// if use are using this as a list scroller children should be an array
children?: any;

onUpdate?: (values: positionValues) => void;
}

export const ScrollBox = (props: IScrollBox) => {
// pull out scrollTo so they are not in scrollbarProps (don't want them spred onto <Scrollbars /> component)
const { scrollTo, children, onUpdate, autoHeight = true, autoHideTimeout = 500, innerRef, ...scrollbarProps } = props;

const [isScrolling, setisScrolling] = React.useState(false);

useScrollToHash(scrollTo);

const scrollbars = innerRef || React.useRef<IScrollBoxRef>(null);
const current = scrollbars.current as IScrollBoxRef;
const values = (current && current.getValues()) || {};
const { clientHeight, clientWidth, scrollHeight, scrollLeft, scrollTop, scrollWidth } = values;

const thumbHorizontal = getThumbDimension({ scroll: scrollWidth, client: clientWidth }) || 0;
const thumbVertical = getThumbDimension({ scroll: scrollHeight, client: clientHeight }) || 0;

return (
<Scrollbars
{...scrollbarProps}
ref={innerRef || scrollbars}
autoHideTimeout={autoHideTimeout}
autoHeight={autoHeight}
onUpdate={onUpdate}
onScroll={(e: any) => {
if (isScrolling) {
// @ts-ignore
clearTimeout(isScrolling);
}

setisScrolling(
// @ts-ignore
setTimeout(() => {
setisScrolling(false);
}, autoHideTimeout)
);
}}
renderView={({ style }: any) => {
// overide to offset the native scroll bars
return (
<div
style={{
...style,
marginRight: '-15px',
marginBottom: '-15px',
}}
/>
);
}}
// Custom component overrides
renderTrackHorizontal={({ style }: any) => {
return <div style={horizontalTrackStyle()} />;
}}
renderThumbHorizontal={({ style }: any) => {
return (
<Box
radius="full"
cursor="grab"
bg="scrollbar.bg"
opacity={isScrolling ? 1 : 0}
height="6px"
width={thumbHorizontal}
css={{
transition: 'opacity .1s',
transform: `translateX(${getScrollTransform(clientWidth, scrollWidth, scrollLeft, thumbHorizontal)}px)`,
}}
/>
);
}}
renderTrackVertical={() => {
return <div style={verticalTrackStyle()} />;
}}
renderThumbVertical={({ style }: any) => {
return (
<Box
radius="full"
cursor="grab"
bg="scrollbar.bg"
opacity={isScrolling ? 1 : 0}
height={thumbVertical}
width="6px"
css={{
transition: 'opacity .1s',
transform: `translateY(${getScrollTransform(clientHeight, scrollHeight, scrollTop, thumbVertical)}px)`,
}}
/>
);
}}
>
{children}
</Scrollbars>
);
};
120 changes: 120 additions & 0 deletions src/ScrollList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import * as React from 'react';
import { AutoSizer, Index, List, ListRowProps, ListRowRenderer } from 'react-virtualized';

import { Box } from './Box';
import { getScrollTransform, getThumbDimension, verticalTrackStyle } from './utils/scroll';

import { styled } from './utils';

export interface IScrollListItemProps {
key: string;
index: number;
value: any;
style: React.CSSProperties;
}

export interface IOnScroll {
clientHeight?: number;
scrollHeight?: number;
scrollTop?: number;
}

export interface IScrollListProps {
// Either a fixed row height (number) or a function that returns the height of a row given its index.
rowHeight: number | ((params: Index) => number);

// Responsible for rendering a row
rowRenderer: ({ key, index, value }: IScrollListItemProps) => JSX.Element;
noRowsRenderer?: () => JSX.Element;
onScroll?: (e: IOnScroll) => void;
list: any[];

// Controls the alignment scrolled-to-rows.
scrollToAlignment?: 'auto' | 'start' | 'end' | 'center';

// Row index to ensure visible (by forcefully scrolling if necessary)
scrollToIndex?: number;

// Forced vertical scroll offset; can be used to synchronize scrolling between components
offset?: number;
}

const ListView = (props: IScrollListProps & { className: string }) => {
const {
className,
list,
rowHeight,
scrollToIndex,
scrollToAlignment,
offset,
rowRenderer,
noRowsRenderer,
onScroll,
} = props;

const [{ clientHeight, scrollHeight, scrollTop }, setScrollEvent] = React.useState<IOnScroll>({});
const thumbSize = getThumbDimension({ scroll: scrollHeight, client: clientHeight });

const [isScrolling, setisScrolling] = React.useState(false);

const renderRow = ({ key, index, style }: ListRowProps) => rowRenderer({ key, index, value: list[index], style });

return (
<AutoSizer>
{({ height, width }) => (
<Box height={height} width={width} overflow="hidden" className={'box'}>
<List
className={className}
rowHeight={rowHeight}
rowCount={list.length}
rowRenderer={renderRow as ListRowRenderer}
noRowsRenderer={noRowsRenderer}
onScroll={(e: IOnScroll) => {
setScrollEvent(e);

if (isScrolling) {
// @ts-ignore
clearTimeout(isScrolling);
}

setisScrolling(
// @ts-ignore
setTimeout(() => {
setisScrolling(false);
}, 1000)
);

if (onScroll) {
onScroll(e);
}
}}
scrollToAlignment={scrollToAlignment}
scrollToIndex={scrollToIndex}
scrollTop={offset}
height={height + 15} // add 15 to offset the native scrollbars
width={width + 15} // add 15 to offset the native scrollbars
/>

{/** scrollbar */}
<div style={verticalTrackStyle()}>
<Box
className={'scroll1'}
height={`${thumbSize}px`}
width="6px"
cursor="grab"
radius="full"
opacity={isScrolling ? 1 : 0}
bg="scrollbar.bg"
css={{
transform: `translateY(${getScrollTransform(clientHeight, scrollHeight, scrollTop, thumbSize)}px)`,
transition: 'opacity .1s',
}}
/>
</div>
</Box>
)}
</AutoSizer>
);
};

export const ScrollList = styled<IScrollListProps>(ListView as any)``;
Loading

0 comments on commit f289e62

Please sign in to comment.