세로 구분선 (Variant)
variant prop으로 본문과 요약 셀의 세로 구분선 표시 여부를 전환하고 적용 범위를 확인합니다.
LIVE DEMO세로 구분선 (Variant) 인터랙티브 예제
인터랙티브 데모를 불러오는 중입니다…
LIVE DEMO세로 구분선 (Variant) 인터랙티브 예제
인터랙티브 데모를 불러오는 중입니다…
import { t } from './i18n';
import * as React from 'react';
import { BGrid, type BGridColumn, type BGridDataItem, type BGridProps, type BGridSummaryColumn } from 'beautiful-grid';
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<BGridProps<SalesRow>['variant']>;
const categories = [t('하드웨어', 'Hardware'), t('소프트웨어', 'Software'), t('서비스', 'Service')];
const data: BGridDataItem<SalesRow>[] = Array.from({ length: 30 }, (_, index) => ({
values: {
id: `ORD-${String(index + 1).padStart(3, '0')}`,
product: `${t('상품', 'Product')} ${index + 1}`,
category: categories[index % categories.length],
quantity: (index % 5) + 1,
amount: 28000 + index * 3500,
},
}));
const columns: BGridColumn<SalesRow>[] = [
{ key: 'id', label: t('주문번호', 'Order Number'), width: 100, align: 'center' },
{ key: 'product', label: t('상품명', 'Product Name'), width: 180 },
{ key: 'category', label: t('분류', 'Category'), width: 120, align: 'center' },
{ key: 'quantity', label: t('수량', 'Quantity'), width: 90, align: 'right' },
{
key: 'amount',
label: t('금액', 'Amount'),
width: 140,
align: 'right',
itemRender: ({ values }) => `${values.amount.toLocaleString('ko-KR')}${t('원', 'KRW')}`,
},
];
const summaryColumns: BGridSummaryColumn<SalesRow>[] = [
{ columnIndex: 0, align: 'center', itemRender: () => <strong>{t('합계', 'Total')}</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')}${t('원', 'KRW')}`,
},
];
const variantOptions: Array<{ value: GridVariant; label: string; description: string }> = [
{ value: 'default', label: 'default', description: t('본문과 요약 셀의 세로 구분선 생략', 'Omit vertical separator between body and summary cell') },
{ value: 'vertical-bordered', label: 'vertical-bordered', description: t('본문과 요약 셀의 세로 구분선 표시', 'Show vertical separator between body and summary cell') },
];
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'>{t('세로 구분선 Variant', 'Vertical Border Variant')}</strong>
<span className='text-xs text-slate-500'>{t('본문과 하단 합계 행에서 세로 구분선의 표시 여부를 비교해 보세요.', 'Compare whether the vertical border is displayed in the body and bottom summary rows.')}</span>
</div>
<Segmented
aria-label={t('세로 구분선 variant 선택', 'Select Vertical Separator 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'>
{t('현재 설정:', 'Current Setting:')} <code>variant="{variant}"</code> —{' '}
{variantOptions.find(option => option.value === variant)?.description}
</p>
<DataGridContainer ref={containerRef}>
<BGrid<SalesRow>
width={width}
height={height}
columns={columns}
data={data}
rowKey='id'
showLineNumber
frozenColumnIndex={1}
variant={variant}
summary={{ position: 'bottom', rows: [{ 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.
*
* BGrid'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 > .bgrid-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가 바꾸는 것
BGrid의 variant prop은 그리드 본문과 Summary 셀의 세로 테두리 표현을 선택합니다. 데이터, 컬럼, 정렬, 선택 같은 동작은 바꾸지 않습니다.
| 값 | 표현 | 권장 용도 |
|---|---|---|
default |
본문 셀 사이의 세로 구분선을 생략한 기본 스타일 | 행 단위 탐색이 중심인 일반 목록 |
vertical-bordered |
본문과 Summary의 각 셀 오른쪽에 세로 구분선 표시 | 열 경계를 빠르게 구분해야 하는 밀도 높은 표 |
값을 생략하면 default가 적용됩니다.
2. 라이브 예제
위의 선택 버튼으로 두 값을 전환해 본문과 하단 합계 행의 세로 구분선을 비교할 수 있습니다. 고정 컬럼을 함께 사용해도 같은 variant가 고정 영역과 스크롤 영역에 일관되게 적용됩니다.
핵심 설정은 다음과 같습니다.
const [variant, setVariant] = useState<'default' | 'vertical-bordered'>('default');
<BGrid
columns={columns}
data={data}
variant={variant}
summary={{
position: 'bottom',
rows: [{ columns: summaryColumns }],
}}
/>;
variant는 그리드 셀의 세로 구분선 표시 방식이며, scrollbar.variant의 native | classic | modern과는 서로 다른 설정입니다. 스크롤바 모양은 스크롤바 설정 가이드를 참고하세요.
3. 테마와 함께 사용하기
vertical-bordered의 선 색상은 --bgrid-border-color-light를 사용하며, 값이 없으면 --bgrid-border-color-base로 대체됩니다. 서비스 테마에 맞춰 선 색상을 바꿀 때는 그리드 wrapper에서 CSS 변수를 재정의합니다.
.report-grid {
--bgrid-border-color-light: #cbd5e1;
}
선의 대비를 조정한 뒤에는 일반 행, hover/선택 상태, Summary 행에서 컬럼 경계를 충분히 구분할 수 있는지 함께 확인하세요.