ScrollArea
Basic usage
To use the ScrollArea component, wrap your content with ScrollArea. It will create a couple of div wraps and handle the necessary calculations. You can set the height or width directly on the ScrollArea or somewhere higher in the hierarchy. max-height and max-width are also supported.
tsx
import { Box, ScrollArea } from '@semcore/ui/base-components';
import React from 'react';
let randomIndex = 1;
const stableRandom = () => {
if (randomIndex > 20) randomIndex = 1;
return Math.abs(Math.sin(Math.exp(Math.PI * randomIndex * Math.cos(100 - randomIndex++))));
};
function getRandomColor() {
const letters = '0123456789ABCDEF';
let color = '#';
for (let i = 0; i < 6; i++) {
color += letters[Math.floor(stableRandom() * 16)];
}
return color;
}
class Demo extends React.PureComponent {
render() {
return (
<ScrollArea h={300}>
{[...new Array(100)].map((_, index) => (
<Box
key={index}
inline
m={2}
w={120}
h={120}
style={{ backgroundColor: getRandomColor() }}
/>
))}
</ScrollArea>
);
}
}
export default () => <Demo />;
Synchronized scroll
tsx
import { Box, Flex, ScrollArea } from '@semcore/ui/base-components';
import React from 'react';
let randomIndex = 1;
const stableRandom = () => {
if (randomIndex > 20) randomIndex = 1;
return Math.abs(Math.sin(Math.exp(Math.PI * randomIndex * Math.cos(100 - randomIndex++))));
};
function getRandomColor() {
const letters = '0123456789ABCDEF';
let color = '#';
for (let i = 0; i < 6; i++) {
color += letters[Math.floor(stableRandom() * 16)];
}
return color;
}
class Demo extends React.PureComponent {
controlled: HTMLDivElement | null = null;
handleMainScroll = (e: React.MouseEvent<HTMLDivElement>) => {
if (this.controlled) {
this.controlled.scrollTop = e.currentTarget.scrollTop;
}
};
componentDidMount() {
if (this.controlled) {
this.controlled.scrollTop = 0;
}
}
render() {
return (
<Flex>
<Box style={{ position: 'relative' }}>
<h3 id='main-title'>Main ScrollArea</h3>
<ScrollArea w={300} h={300}>
<ScrollArea.Container
role='group'
aria-labelledby='main-title'
onScroll={this.handleMainScroll}
>
{[...new Array(100)].map((_, index) => (
<Box
key={index}
inline
m={2}
w={120}
h={120}
style={{ backgroundColor: getRandomColor() }}
/>
))}
</ScrollArea.Container>
<ScrollArea.Bar />
</ScrollArea>
</Box>
<Box>
<h3 id='control-title'>Controlled ScrollArea</h3>
<ScrollArea w={300} h={300}>
<ScrollArea.Container
role='group'
aria-labelledby='control-title'
ref={(node: HTMLDivElement | null) => {
this.controlled = node;
}}
>
{[...new Array(100)].map((_, index) => (
<Box
key={index}
inline
m={2}
w={120}
h={120}
style={{ backgroundColor: getRandomColor() }}
/>
))}
</ScrollArea.Container>
<ScrollArea.Bar />
</ScrollArea>
</Box>
</Flex>
);
}
}
export default () => <Demo />;
Synchronized reverse scroll
tsx
import { Box, Flex, ScrollArea } from '@semcore/ui/base-components';
import React from 'react';
let randomIndex = 1;
const stableRandom = () => {
if (randomIndex > 20) randomIndex = 1;
return Math.abs(Math.sin(Math.exp(Math.PI * randomIndex * Math.cos(100 - randomIndex++))));
};
function getRandomColor() {
const letters = '0123456789ABCDEF';
let color = '#';
for (let i = 0; i < 6; i++) {
color += letters[Math.floor(stableRandom() * 16)];
}
return color;
}
class Demo extends React.PureComponent {
mirror: HTMLDivElement | null = null;
handleScrollMain = (e: React.MouseEvent<HTMLDivElement>) => {
if (this.mirror) {
this.mirror.scrollTop =
this.mirror.scrollHeight - this.mirror.clientHeight - e.currentTarget.scrollTop;
}
};
componentDidMount() {
if (this.mirror) {
this.mirror.scrollTop = this.mirror.scrollHeight - this.mirror.clientHeight;
}
}
render() {
return (
<Flex>
<Box style={{ position: 'relative' }}>
<h3 id='main-reverse-title'>Main ScrollArea</h3>
<ScrollArea w={300} h={300}>
<ScrollArea.Container
role='group'
aria-labelledby='main-reverse-title'
onScroll={this.handleScrollMain}
>
{[...new Array(100)].map((_, index) => (
<Box
key={index}
inline
m={2}
w={120}
h={120}
style={{ backgroundColor: getRandomColor() }}
/>
))}
</ScrollArea.Container>
<ScrollArea.Bar />
</ScrollArea>
</Box>
<Box>
<h3 id='control-reverse-title'>Reversed ScrollArea</h3>
<ScrollArea w={300} h={300}>
<ScrollArea.Container
role='group'
aria-labelledby='control-reverse-title'
ref={(node: HTMLDivElement | null) => {
this.mirror = node;
}}
>
<Flex flexWrap reverse>
{[...new Array(100)].map((_, index) => (
<Box
key={index}
inline
m={2}
w={120}
h={120}
style={{ backgroundColor: getRandomColor() }}
/>
))}
</Flex>
</ScrollArea.Container>
<ScrollArea.Bar />
</ScrollArea>
</Box>
</Flex>
);
}
}
export default () => <Demo />;
Dynamic virtual list
The dynamic virtual list is powered by React-virtualized.
tsx
import { Box, Flex, ScrollArea } from '@semcore/ui/base-components';
import Button from '@semcore/ui/button';
import { Text } from '@semcore/ui/typography';
import React from 'react';
import { List } from 'react-virtualized';
const list = [...new Array(6)];
const renderRow = ({
key,
index,
style,
}: { key: string; index: number; style: React.CSSProperties }) => {
return (
<Box
key={key}
inline
m={2}
w={120}
h={120}
style={{ border: '1px solid black', ...style }}
role='row'
>
<Text bold size={200} m='auto' role='gridcell'>
{index + 1}
</Text>
</Box>
);
};
const Demo = () => {
const [data, setData] = React.useState(list);
const innerRef: React.MutableRefObject<HTMLDivElement | null> = React.useRef(null);
const ref = (node: HTMLDivElement | null) => {
if (node && innerRef.current) {
innerRef.current = node.querySelector('.ReactVirtualized__Grid__innerScrollContainer');
}
};
return (
<Flex direction='column' inline>
<Flex alignItems='center' mb={2} gap={2}>
<Button
onClick={() => {
setData(data.concat(undefined));
}}
>
Add item
</Button>
<Button onClick={() => setData(data.slice(0, -1))}>Remove item</Button>
<Text role='status' aria-live='polite'>
Count:
{' '}
{data.length}
</Text>
</Flex>
<Box h={500}>
{data.length
// eslint-disable-next-line @stylistic/multiline-ternary
? (
<ScrollArea inner={innerRef}>
<ScrollArea.Container
ref={ref}
// @ts-ignore
tag={List}
height={500}
rowCount={data.length}
width={500}
rowHeight={120}
rowRenderer={renderRow}
/>
<ScrollArea.Bar orientation='vertical' />
</ScrollArea>
) : null}
</Box>
</Flex>
);
};
export default Demo;