스크롤바 설정 (Custom Scrollbar)

네이티브 브라우저 스크롤바와 OS에 종속되지 않는 커스텀 오버레이 스크롤바의 설정 및 도킹(Dock) 옵션을 학습합니다.

#scrollbar#custom-scrollbar#native-scrollbar#scrollbar-dock#scroll-metrics
검토일: 2026-08-18
GitHub
import * as React from 'react';
import { AXDataGrid } from '@axboot/datagrid';
import type { AXDGColumn } from '@axboot/datagrid';
import { useContainerSize } from '../hooks/useContainerSize';
import DataGridContainer from '../components/DataGridContainer';
import { Segmented, Switch, Select } from 'antd';

const columns: AXDGColumn<any>[] = [
  { key: 'id', label: 'ID', width: 80, align: 'center' },
  { key: 'title', label: 'Title', width: 300 },
  { key: 'count', label: 'Count', width: 100, align: 'right' },
  { key: 'desc', label: 'Description', width: 600 },
];

const data = Array.from({ length: 150 }).map((_, i) => ({
  values: {
    id: i + 1,
    title: `Scrollbar test item ${i + 1}`,
    count: i * 10,
    desc: `Description for item ${i + 1}. This is to make the row longer to test horizontal scrolling.`,
  }
}));

export default function ScrollbarExample() {
  const [variant, setVariant] = React.useState<'native' | 'classic' | 'modern'>('classic');
  const [statusVisible, setStatusVisible] = React.useState(true);
  const [statusContentMode, setStatusContentMode] = React.useState<'default' | 'custom text' | 'custom render'>('default');

  const containerRef = React.useRef<HTMLDivElement>(null);
  const { width: containerWidth, height: containerHeight } = useContainerSize(containerRef);

  const getStatusContent = () => {
    if (statusContentMode === 'custom text') return 'Last synced: 10:30 AM';
    if (statusContentMode === 'custom render') {
      return ({ totalItems, visibleItems }: any) => (
        <span style={{ color: 'blue', fontWeight: 600 }}>
          {visibleItems} / {totalItems} Custom Render
        </span>
      );
    }
    return undefined;
  };

  return (
    <>
      <div className='flex flex-wrap items-center gap-4 p-3 bg-slate-50 border border-slate-200 rounded-lg text-sm mb-4'>
        <div className='flex items-center gap-2'>
          <span className='text-xs text-slate-500 font-medium'>Variant:</span>
          <Segmented
            value={variant}
            onChange={val => setVariant(val as any)}
            options={['native', 'classic', 'modern']}
          />
        </div>
        
        <div className='flex items-center gap-2'>
          <span className='text-xs text-slate-500 font-medium'>Status Visible:</span>
          <Switch checked={statusVisible} onChange={setStatusVisible} size="small" />
        </div>

        <div className='flex items-center gap-2'>
          <span className='text-xs text-slate-500 font-medium'>Status Content:</span>
          <Select
            value={statusContentMode}
            onChange={setStatusContentMode}
            options={[
              { label: 'Default', value: 'default' },
              { label: 'Custom Text', value: 'custom text' },
              { label: 'Custom Render', value: 'custom render' }
            ]}
            style={{ width: 140 }}
            size="small"
          />
        </div>
      </div>

      <DataGridContainer ref={containerRef}>
        <AXDataGrid
          width={containerWidth}
          height={containerHeight}
          columns={columns}
          data={data}
          scrollbar={{
            variant,
          }}
          status={{
            visible: statusVisible,
            content: getStatusContent(),
          }}
          frozenColumnIndex={1}
        />
      </DataGridContainer>
    </>
  );
}

1. 언제 사용하며 왜 필요한가요?

운영체제와 브라우저에 따라 기본 스크롤바의 모양과 점유 공간이 다를 수 있습니다. scrollbar prop으로 native, classic, modern 변형과 가로·세로 스크롤바의 표시 여부를 설정할 수 있습니다. 커스텀 가로 스크롤바는 항상 Bottom Bar에 표시되며 위치는 변경할 수 없습니다.

  • modern: 얇고 둥근 트랙과 썸, 미니멀한 이동 버튼을 사용하는 기본 스타일
  • classic: 각진 트랙과 화살표 버튼을 사용하는 Windows 스타일
  • native: 브라우저 네이티브 스크롤바에 기존 AXBOOT 테마만 적용하는 호환 스타일

2. 실무 완성형 예제: 커스텀 스크롤바 활성화

import React, { useState } from 'react';
import { AXDataGrid, type AXDGColumn, type AXDGDataItem } from '@axboot/datagrid';

interface Item {
  id: number;
  col1: string;
  col2: string;
  col3: string;
  col4: string;
  col5: string;
}

export default function CustomScrollbarGrid() {
  const [data] = useState<AXDGDataItem<Item>[]>(
    Array.from({ length: 50 }).map((_, i) => ({
      values: {
        id: i + 1,
        col1: `데이터_1_${i}`,
        col2: `데이터_2_${i}`,
        col3: `데이터_3_${i}`,
        col4: `데이터_4_${i}`,
        col5: `데이터_5_${i}`,
      },
    }))
  );

  const columns: AXDGColumn<Item>[] = [
    { key: 'id', label: 'ID', width: 60, align: 'center' },
    { key: 'col1', label: '열 1', width: 180 },
    { key: 'col2', label: '열 2', width: 180 },
    { key: 'col3', label: '열 3', width: 180 },
    { key: 'col4', label: '열 4', width: 180 },
    { key: 'col5', label: '열 5', width: 180 },
  ];

  return (
    <div>
      <AXDataGrid<Item>
        width={600} // 가로 스크롤 유도를 위해 좁게 설정
        height={260}
        columns={columns}
        data={data}
        rowKey="id"
        // 커스텀 스크롤바 설정
        scrollbar={{
          variant: 'modern', // 'native' | 'classic' | 'modern'
        }}
      />
    </div>
  );
}