그리드 테두리 변형 (Variant)
variant prop으로 기본 테두리와 세로 구분선이 있는 표 형태를 전환하고 본문·요약 행에 적용되는 범위를 확인합니다.
import * as React from 'react';
import { AXDataGrid, type AXDGColumn, type AXDGDataItem, type AXDGProps } from '@axboot/datagrid';
import { Segmented } from 'antd';
import DataGridContainer from '../components/DataGridContainer';
import { useContainerSize } from '../hooks/useContainerSize';
interface SalesRow {
id: string;
product: string;
category: string;
quantity: number;
amount: number;
}
type GridVariant = NonNullable<AXDGProps<SalesRow>['variant']>;
const categories = ['하드웨어', '소프트웨어', '서비스'];
const data: AXDGDataItem<SalesRow>[] = Array.from({ length: 30 }, (_, index) => ({
values: {
id: `ORD-${String(index + 1).padStart(3, '0')}`,
product: `상품 ${index + 1}`,
category: categories[index % categories.length],
quantity: (index % 5) + 1,
amount: 28000 + index * 3500,
},
}));
const columns: AXDGColumn<SalesRow>[] = [
{ key: 'id', label: '주문번호', width: 100, align: 'center' },
{ key: 'product', label: '상품명', width: 180 },
{ key: 'category', label: '분류', width: 120, align: 'center' },
{ key: 'quantity', label: '수량', width: 90, align: 'right' },
{
key: 'amount',
label: '금액',
width: 140,
align: 'right',
itemRender: ({ values }) => `${values.amount.toLocaleString('ko-KR')}원`,
},
];
const summaryColumns: NonNullable<AXDGProps<SalesRow>['summary']>['columns'] = [
{ columnIndex: 0, align: 'center', itemRender: () => <strong>합계</strong> },
{
columnIndex: 3,
align: 'right',
itemRender: ({ data }) => data.reduce((sum, item) => sum + item.values.quantity, 0),
},
{
columnIndex: 4,
align: 'right',
itemRender: ({ data }) => `${data.reduce((sum, item) => sum + item.values.amount, 0).toLocaleString('ko-KR')}원`,
},
];
const variantOptions: Array<{ value: GridVariant; label: string; description: string }> = [
{ value: 'default', label: 'default', description: '행 중심의 기본 테두리' },
{ value: 'vertical-bordered', label: 'vertical-bordered', description: '본문과 요약 셀의 세로 구분선 표시' },
];
export default function VariantExample() {
const [variant, setVariant] = React.useState<GridVariant>('default');
const containerRef = React.useRef<HTMLDivElement>(null);
const { width, height } = useContainerSize(containerRef);
return (
<div className='flex min-h-0 flex-col gap-3'>
<div className='flex flex-wrap items-center justify-between gap-3 rounded-lg border border-slate-200 bg-slate-50 p-3 text-sm text-slate-700'>
<div>
<strong className='block text-slate-900'>그리드 테두리 Variant</strong>
<span className='text-xs text-slate-500'>본문과 하단 합계 행의 세로 구분선을 비교해 보세요.</span>
</div>
<Segmented
aria-label='그리드 테두리 variant 선택'
value={variant}
options={variantOptions.map(({ value, label }) => ({ value, label }))}
onChange={value => setVariant(value as GridVariant)}
/>
</div>
<p className='m-0 text-xs text-slate-500' aria-live='polite'>
현재 설정: <code>variant="{variant}"</code> —{' '}
{variantOptions.find(option => option.value === variant)?.description}
</p>
<DataGridContainer ref={containerRef}>
<AXDataGrid<SalesRow>
width={width}
height={height}
columns={columns}
data={data}
rowKey='id'
showLineNumber
frozenColumnIndex={1}
variant={variant}
summary={{ position: 'bottom', columns: summaryColumns }}
/>
</DataGridContainer>
</div>
);
}import * as React from 'react';
import './DataGridContainer.css';
interface DataGridContainerProps extends React.HTMLAttributes<HTMLDivElement> {
children?: React.ReactNode;
}
/**
* Keeps a DataGrid in a measured, fixed layout box.
*
* AXDataGrid's rendered root is absolutely positioned within this relative
* container. This makes a ResizeObserver measurement authoritative when a
* surrounding flex or grid layout shrinks as well as when it expands.
*/
const DataGridContainer = React.forwardRef<HTMLDivElement, DataGridContainerProps>(
({ className, ...rest }, ref) => (
<div ref={ref} className={`data-grid-container ${className ?? ''}`.trim()} {...rest} />
),
);
DataGridContainer.displayName = 'DataGridContainer';
export default DataGridContainer;.data-grid-container {
position: relative;
width: 100%;
height: 400px;
overflow: hidden;
font-size: 13px;
}
.data-grid-container > .axdg-root {
position: absolute;
inset: 0;
}import * as React from 'react';
export function useContainerSize(ref: React.MutableRefObject<HTMLElement | null>, additionalDeps: unknown[] = []) {
const [width, setWidth] = React.useState(0);
const [height, setHeight] = React.useState(0);
const resizeObserver = React.useRef(
new ResizeObserver(entries => {
if (entries.length !== 1) {
throw new Error('Invalid Container length');
}
const [entry] = entries;
const { width, height } = entry.contentRect;
setWidth(width);
setHeight(height);
}),
);
React.useEffect(() => {
if (!ref.current) return;
const observer = resizeObserver.current;
const element = ref.current;
setWidth(element.clientWidth);
setHeight(element.clientHeight);
observer.observe(element);
return () => {
observer.unobserve(element);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [...additionalDeps, ref]);
return {
width,
height,
};
}1. variant가 바꾸는 것
AXDataGrid의 variant prop은 그리드 본문과 Summary 셀의 세로 테두리 표현을 선택합니다. 데이터, 컬럼, 정렬, 선택 같은 동작은 바꾸지 않습니다.
| 값 | 표현 | 권장 용도 |
|---|---|---|
default |
본문 셀 사이의 세로 구분선을 생략한 기본 스타일 | 행 단위 탐색이 중심인 일반 목록 |
vertical-bordered |
본문과 Summary의 각 셀 오른쪽에 세로 구분선 표시 | 열 경계를 빠르게 구분해야 하는 밀도 높은 표 |
값을 생략하면 default가 적용됩니다.
2. 라이브 예제
위의 선택 버튼으로 두 값을 전환해 본문과 하단 합계 행의 세로 구분선을 비교할 수 있습니다. 고정 컬럼을 함께 사용해도 같은 variant가 고정 영역과 스크롤 영역에 일관되게 적용됩니다.
핵심 설정은 다음과 같습니다.
const [variant, setVariant] = useState<'default' | 'vertical-bordered'>('default');
<AXDataGrid
columns={columns}
data={data}
variant={variant}
summary={{
position: 'bottom',
columns: summaryColumns,
}}
/>;
variant는 그리드 테두리 프리셋이고, scrollbar.variant의 native | classic | modern과는 서로 다른 설정입니다. 스크롤바 모양은 스크롤바 설정 가이드를 참고하세요.
3. 테마와 함께 사용하기
vertical-bordered의 선 색상은 --axdg-border-color-light를 사용하며, 값이 없으면 --axdg-border-color-base로 대체됩니다. 서비스 테마에 맞춰 선 색상을 바꿀 때는 그리드 wrapper에서 CSS 변수를 재정의합니다.
.report-grid {
--axdg-border-color-light: #cbd5e1;
}
선의 대비를 조정한 뒤에는 일반 행, hover/선택 상태, Summary 행에서 컬럼 경계를 충분히 구분할 수 있는지 함께 확인하세요.