컬럼 숨김과 복구 (Column Visibility)
헤더 툴박스에서 컬럼을 숨기고, 다른 컬럼 메뉴에서 다시 표시하거나 전체 복구하는 방법을 설명합니다.
LIVE DEMO컬럼 숨김과 복구 (Column Visibility) 인터랙티브 예제
인터랙티브 데모를 불러오는 중입니다…
LIVE DEMO컬럼 숨김과 복구 (Column Visibility) 인터랙티브 예제
인터랙티브 데모를 불러오는 중입니다…
import * as React from 'react';
import { useMemo, useState } from 'react';
import { Button, Tag } from 'antd';
import { Eye, RotateCcw } from 'lucide-react';
import { BGrid } from 'beautiful-grid';
import type { BGridColumn, BGridColumnGroupNode, BGridDataQuery } from 'beautiful-grid';
import DataGridContainer from '../components/DataGridContainer';
import { useContainerSize } from '../hooks/useContainerSize';
import { t, exampleMsg } from './i18n';
interface OrderRow {
orderNo: string;
customer: string;
region: string;
status: string;
owner: string;
dueDate: string;
amount: number;
}
const customers = ['Northwind', 'Contoso', 'Fabrikam', 'Adventure Works', 'Tailspin'];
const regions = [t('서울', 'Seoul'), t('부산', 'Busan'), t('대전', 'Daejeon'), t('광주', 'Gwangju')];
const statuses = [t('접수', 'Received'), t('처리 중', 'Processing'), t('출고', 'Shipped')];
const owners = [t('김서준', 'Alex Kim'), t('이하은', 'Jamie Lee'), t('박지후', 'Chris Park')];
const rows = Array.from({ length: 24 }, (_, index) => ({
values: {
orderNo: `ORD-${String(index + 1).padStart(4, '0')}`,
customer: customers[index % customers.length],
region: regions[index % regions.length],
status: statuses[index % statuses.length],
owner: owners[index % owners.length],
dueDate: `2026-09-${String((index % 20) + 8).padStart(2, '0')}`,
amount: 128000 + index * 17500,
},
}));
const groups: BGridColumnGroupNode[] = [
{
id: 'order',
label: t('주문 정보', 'Order'),
children: ['orderNo', 'customer'],
},
{
id: 'delivery',
label: t('처리 정보', 'Fulfillment'),
children: ['region', 'status', 'owner', 'dueDate'],
},
];
export default function ColumnVisibilityExample() {
const containerRef = React.useRef<HTMLDivElement>(null);
const { width, height } = useContainerSize(containerRef);
const [hiddenColumnIds, setHiddenColumnIds] = useState<string[]>(['owner']);
const [query, setQuery] = useState<BGridDataQuery>({
sortParams: [],
filterParams: [],
});
const columns = useMemo<BGridColumn<OrderRow>[]>(
() => [
{
id: 'orderNo',
key: 'orderNo',
label: t('주문 번호', 'Order No.'),
width: 120,
hideable: false,
toolbox: true,
filter: { type: 'text' },
},
{
id: 'customer',
key: 'customer',
label: t('고객사', 'Customer'),
width: 150,
toolbox: true,
filter: { type: 'text' },
},
{
id: 'region',
key: 'region',
label: t('권역', 'Region'),
width: 100,
align: 'center',
toolbox: true,
filter: { type: 'values' },
},
{
id: 'status',
key: 'status',
label: t('상태', 'Status'),
width: 105,
align: 'center',
toolbox: true,
filter: { type: 'values' },
},
{
id: 'owner',
key: 'owner',
label: t('담당자', 'Owner'),
width: 110,
align: 'center',
toolbox: true,
filter: { type: 'values' },
},
{
id: 'dueDate',
key: 'dueDate',
label: t('출고 예정일', 'Due date'),
width: 120,
align: 'center',
toolbox: true,
filter: { type: 'text' },
},
{
id: 'amount',
key: 'amount',
label: t('주문 금액', 'Amount'),
width: 130,
align: 'right',
toolbox: true,
filter: { type: 'number' },
itemRender: ({ value }) => `${Number(value).toLocaleString()}${t('원', ' KRW')}`,
},
],
[],
);
return (
<>
<div className='flex flex-wrap items-center justify-between gap-2 p-3 bg-slate-50 border border-slate-200 rounded-lg text-sm'>
<div className='flex flex-wrap items-center gap-2'>
<Tag
data-testid='hidden-column-status'
className='inline-flex items-center gap-1'
color={hiddenColumnIds.length ? 'blue' : 'default'}
>
<Eye size={13} aria-hidden='true' />
<span>{t('숨긴 컬럼', 'Hidden')} {hiddenColumnIds.length}</span>
</Tag>
<Tag>{t('정렬', 'Sort')} {query.sortParams.length}</Tag>
<Tag>{t('필터', 'Filter')} {query.filterParams.length}</Tag>
<span className='text-slate-500'>
{hiddenColumnIds.length
? hiddenColumnIds.map(id => columns.find(column => column.id === id)?.label ?? id).join(', ')
: t('모든 컬럼 표시 중', 'All columns visible')}
</span>
</div>
<Button
size='small'
icon={<RotateCcw size={14} />}
disabled={hiddenColumnIds.length === 0}
onClick={() => setHiddenColumnIds([])}
>
{t('모두 표시', 'Show all')}
</Button>
</div>
<DataGridContainer ref={containerRef}>
<BGrid
width={width}
height={height}
data={rows}
columns={columns}
msg={exampleMsg}
columnGroups={groups}
headerHeight={54}
frozenColumnIndex={2}
columnVisibility={{
hiddenColumnIds,
onChange: setHiddenColumnIds,
}}
dataControl={{
mode: 'client',
query,
onChange: setQuery,
multiSort: true,
}}
rowKey='orderNo'
variant='vertical-bordered'
/>
</DataGridContainer>
</>
);
}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,
};
}기본 사용법
컬럼 숨김과 복구 기능은 BeautifulGrid 1.0.6부터 지원합니다.
columnVisibility를 켜면 각 컬럼 툴박스에 이 컬럼 숨기기가 추가됩니다. 숨긴 컬럼은 남아 있는 컬럼의 메뉴에서 다시 표시할 수 있습니다. 마지막 표시 컬럼은 숨길 수 없습니다. 위 라이브 데모처럼 컬럼의 toolbox와 filter를 함께 설정하면 같은 메뉴에서 정렬, 필터, 숨김을 모두 사용할 수 있습니다.
<BGrid
width={800}
height={420}
columns={columns}
data={data}
columnVisibility
/>
숨김 상태 저장하기
사용자 설정을 저장하려면 컬럼마다 고유한 id를 지정하고 controlled 상태를 사용합니다.
const [hiddenColumnIds, setHiddenColumnIds] = useState<string[]>(['owner']);
<BGrid
width={800}
height={420}
columns={columns}
data={data}
columnVisibility={{
hiddenColumnIds,
onChange: setHiddenColumnIds,
}}
/>
특정 컬럼을 사용자가 숨기지 못하게 하려면 hideable: false를 설정합니다. API로 전달한 숨김 상태는 정렬·필터 조건을 제거하지 않으며, 컬럼을 다시 표시하면 이전 조건이 그대로 나타납니다.
const columns = [
{ id: 'orderNo', key: 'orderNo', label: '주문 번호', width: 120, hideable: false },
{ id: 'customer', key: 'customer', label: '고객사', width: 160 },
{ id: 'owner', key: 'owner', label: '담당자', width: 120 },
];
컬럼이 숨겨진 동안에는 드래그 컬럼 재배치가 비활성화됩니다. 모든 컬럼을 다시 표시하면 columnSortable 설정이 자동으로 복원됩니다.