ScrollArea

ScrollArea

커스텀 스크롤바가 있는 영역 컴포넌트예요. 콘텐츠를 스크롤 가능한 컨테이너로 만들어요.

출처: 문서

본문

사용법 (Usage)

ScrollArea 컴포넌트는 다음 prop을 지원해요.

  • type – 스크롤바 동작 정의:
    • hover – hover 시 스크롤바 표시
    • scroll – 스크롤 시 스크롤바 표시
    • auto – overflow: auto와 유사, 콘텐츠가 넘칠 때 항상 스크롤바 표시
    • always – auto와 같지만 콘텐츠가 넘치는지와 무관하게 항상 스크롤바 표시
    • never – 스크롤바 항상 숨김
  • offsetScrollbars – 스크롤바를 오프셋하는 패딩을 추가 (옵션: true는 두 스크롤바 모두, x는 가로만, y는 세로만, present는 스크롤바가 보일 때만)
  • scrollbarSize – 스크롤바 크기, 스크롤바와 썸의 너비/높이 제어
  • scrollHideDelay – 스크롤바를 숨기는 지연(ms), type이 hover 또는 scroll일 때만 적용
  • overscrollBehavior – 뷰포트의 overscroll-behavior 제어
import { ScrollArea } from '@mantine/core';

function Demo() {
  return (
    <ScrollArea h={250}>
      {/* ... content */}
    </ScrollArea>
  );
}

가로 스크롤바 (Horizontal scrollbars)

scrollbars="x" prop으로 가로 스크롤바를 활성화할 수 있어요.

import { ScrollArea, Box } from '@mantine/core';

function Demo() {
  return (
    <ScrollArea w="100%" scrollbars="x">
      <Box w="max-content">{/* ... wide content */}</Box>
    </ScrollArea>
  );
}

가로 스크롤바 비활성화 (Disable horizontal scrollbars)

가로 스크롤바를 비활성화하려면 scrollbars="y" prop을 설정해요.

import { ScrollArea } from '@mantine/core';

function Demo() {
  return <ScrollArea scrollbars="y">{/* ... content */}</ScrollArea>;
}

세로 스크롤바 위치 (Vertical scrollbar position)

기본적으로 세로 스크롤바는 인라인 끝 가장자리를 따라가요. LTR에서는 오른쪽, RTL에서는 왼쪽에 렌더링돼요. verticalScrollbarPosition prop을 left 또는 right로 설정하면 방향과 무관하게 세로 스크롤바를 물리적 측면에 고정할 수 있어요.

이는 대부분의 데스크톱 소프트웨어(Windows, Office, Gmail 등)의 동작과 일치하게 세로 스크롤바를 오른쪽에 유지하려는 RTL 애플리케이션에 유용해요. 이 prop은 오프셋 패딩, 모서리, 가로 스크롤바 간격도 다시 정렬해서 offsetScrollbars와 scrollbars="xy"와 함께 올바르게 동작해요. prop을 생략하면 기본 방향 기반 동작이 유지돼요.

import { ScrollArea } from '@mantine/core';

function Demo() {
  return <ScrollArea verticalScrollbarPosition="right">{/* ... content */}</ScrollArea>;
}

스크롤 위치 변경 구독 (Subscribe to scroll position changes)

onScrollPositionChange 함수를 설정하면 스크롤 위치 변경을 구독할 수 있어요. 사용자가 x, y 좌표로 스크롤할 때마다 호출돼요.

import { useState } from 'react';
import { Text, ScrollArea, Code, Box } from '@mantine/core';

function Demo() {
  const [scrollPosition, onScrollPositionChange] = useState({ x: 0, y: 0 });

  return (
    <>
      <ScrollArea onScrollPositionChange={onScrollPositionChange}>
        {/* ... content */}
      </ScrollArea>
      <Code>Scroll position: {`{ x: ${scrollPosition.x}, y: ${scrollPosition.y} }`}</Code>
    </>
  );
}

스크롤 경계 콜백 (Scroll boundary callbacks)

ScrollArea 컴포넌트는 스크롤이 경계에 도달할 때 트리거되는 콜백을 지원해요.

import { useState } from 'react';
import { Badge, Box, Group, ScrollArea, Stack, Text } from '@mantine/core';

function Demo() {
  const [topReached, setTopReached] = useState(0);
  const [bottomReached, setBottomReached] = useState(0);
  const [leftReached, setLeftReached] = useState(0);
  const [rightReached, setRightReached] = useState(0);

  return (
    <>
      <Badge>Top: {topReached}</Badge>
      <Badge>Bottom: {bottomReached}</Badge>
      <Badge>Left: {leftReached}</Badge>
      <Badge>Right: {rightReached}</Badge>

      <ScrollArea
        onTopReached={() => setTopReached((c) => c + 1)}
        onBottomReached={() => setBottomReached((c) => c + 1)}
        onLeftReached={() => setLeftReached((c) => c + 1)}
        onRightReached={() => setRightReached((c) => c + 1)}
        scrollbars="xy"
      >
        <Box w="max-content">
          {Array(50)
            .fill(0)
            .map((_, i) => (
              <Text key={i}>Line {i + 1} - This is a long line that requires horizontal scrolling</Text>
            ))}
        </Box>
      </ScrollArea>
    </>
  );
}

위치로 스크롤 (Scroll to position)

프로그램 방식으로 어떤 위치로든 스크롤하려면 viewportRef prop으로 뷰포트 요소 ref를 얻고 scrollTo 메서드를 호출해요.

import { useRef } from 'react';
import { ScrollArea, Button, Stack, Group } from '@mantine/core';

function Demo() {
  const viewport = useRef<HTMLDivElement>(null);

  const scrollToBottom = () =>
    viewport.current!.scrollTo({ top: viewport.current!.scrollHeight, behavior: 'smooth' });

  const scrollToCenter = () =>
    viewport.current!.scrollTo({ top: viewport.current!.scrollHeight / 2, behavior: 'smooth' });

  const scrollToTop = () => viewport.current!.scrollTo({ top: 0, behavior: 'smooth' });

  return (
    <Stack align="center">
      <ScrollArea viewportRef={viewport} h={250}>
        {/* ... content */}
      </ScrollArea>
      <Group>
        <Button onClick={scrollToBottom} variant="default">Scroll to bottom</Button>
        <Button onClick={scrollToCenter} variant="default">Scroll to center</Button>
        <Button onClick={scrollToTop} variant="default">Scroll to top</Button>
      </Group>
    </Stack>
  );
}

시작 스크롤 위치 (Start scroll position)

startScrollPosition prop으로 컴포넌트가 마운트될 때 초기 스크롤 위치를 설정할 수 있어요. useEffect와 함께 viewportRef를 사용하는 것과 달리 이 방식은 (0, 0) 위치에서 콘텐츠가 깜빡이는 것을 피할 수 있어요.

import { ScrollArea } from '@mantine/core';

function Demo() {
  return (
    <ScrollArea h={250} startScrollPosition={{ x: 0, y: 120 }}>
      {/* ... content */}
    </ScrollArea>
  );
}

Styles API

ScrollArea는 Styles API를 지원해요. classNames prop으로 컴포넌트의 내부 요소에 스타일을 추가할 수 있어요.

요소를 뷰 안으로 스크롤 (Scroll element into view)

scrollIntoView를 사용해 요소를 뷰포트 안으로 스크롤할 수 있어요 (자세한 예시는 원문의 groceries 데모 참고).

viewportRef.current
  ?.querySelectorAll('[data-list-item]')
  ?.[nextIndex]?.scrollIntoView({ block: 'nearest' });

ScrollArea.Autosize

ScrollArea.Autosize 컴포넌트는 주어진 max-height에 도달할 때 스크롤 가능한 컨테이너를 만들 수 있게 해요. 세로 넘침이 발생할 때 감지하는 콜백도 지원해요.

  • onOverflowChange – 콘텐츠가 max-height를 초과해서 컨테이너가 스크롤 가능해지거나 아니게 될 때 트리거
import { useCounter } from '@mantine/hooks';
import { ScrollArea, Button, Group } from '@mantine/core';

const lorem =
  'Lorem ipsum, dolor sit amet consectetur adipisicing elit. Dicta perspiciatis reiciendis voluptate eaque itaque quos. Natus iure tenetur libero, reprehenderit ad, sequi, in aliquam eos necessitatibus expedita delectus veniam culpa!';

function Demo() {
  const [count, handlers] = useCounter(3, { min: 0, max: 10 });
  const content = Array(count)
    .fill(0)
    .map((_, index) => <p key={index}>{lorem}</p>);

  return (
    <>
      <ScrollArea.Autosize mah={200} maw={300} mx="auto">
        {content}
      </ScrollArea.Autosize>
      <Group justify="center" mt="md">
        <Button variant="default" onClick={() => handlers.decrement()}>Remove paragraph</Button>
        <Button variant="default" onClick={() => handlers.increment()}>Add paragraph</Button>
      </Group>
    </>
  );
}

ScrollArea.Autosize with Popover

ScrollArea.Autosize를 Popover 안에서 사용해 검색 가능한 목록을 만들 수 있어요 (원문의 groceries 데모 참고).

더 알아보기 (Learn more)