컨테이너 리사이즈 대응 (DataGridContainer)
ResizeObserver로 그리드 컨테이너의 실제 크기를 측정해, 레이아웃이 커지거나 작아져도 정확한 크기로 렌더링합니다.
import * as React from 'react';
import { AXDataGrid } from '@axboot/datagrid';
import type { AXDGColumn, AXDGDataItem } from '@axboot/datagrid';
import DataGridContainer from '../components/DataGridContainer';
import { useContainerSize } from '../hooks/useContainerSize';
interface Order {
id: string;
customer: string;
status: '준비 중' | '배송 중' | '완료';
amount: number;
}
const columns: AXDGColumn<Order>[] = [
{ key: 'id', label: '주문 번호', width: 120 },
{ key: 'customer', label: '고객', width: 180 },
{ key: 'status', label: '상태', width: 110, align: 'center' },
{ key: 'amount', label: '금액', width: 140, align: 'right', itemRender: ({ value }) => `${value.toLocaleString()}원` },
];
const data: AXDGDataItem<Order>[] = Array.from({ length: 80 }, (_, index) => ({
values: {
id: `ORDER-${String(index + 1).padStart(4, '0')}`,
customer: `고객 ${index + 1}`,
status: ['준비 중', '배송 중', '완료'][index % 3] as Order['status'],
amount: 18000 + index * 750,
},
}));
export default function ContainerResizeExample() {
const [isSidebarOpen, setIsSidebarOpen] = React.useState(true);
const containerRef = React.useRef<HTMLDivElement>(null);
const { width, height } = useContainerSize(containerRef);
return (
<div>
<div style={{ alignItems: 'center', display: 'flex', gap: 12, marginBottom: 12 }}>
<button
type="button"
onClick={() => setIsSidebarOpen(open => !open)}
style={{ background: '#2563eb', border: 0, borderRadius: 6, color: '#fff', cursor: 'pointer', padding: '8px 12px' }}
>
{isSidebarOpen ? '사이드 패널 닫기' : '사이드 패널 열기'}
</button>
<span style={{ color: '#64748b', fontSize: 13 }}>컨테이너: {Math.round(width)} × {Math.round(height)}px</span>
</div>
<div
style={{
display: 'grid',
gap: 12,
gridTemplateColumns: isSidebarOpen ? 'minmax(150px, 0.35fr) minmax(0, 1fr)' : 'minmax(0, 1fr)',
height: 440,
}}
>
{isSidebarOpen && (
<aside style={{ background: '#f1f5f9', borderRadius: 8, color: '#475569', padding: 16 }}>
<strong style={{ display: 'block', marginBottom: 8 }}>필터 패널</strong>
화면 폭이 줄어도 그리드는 이 영역의 정확한 크기를 다시 측정합니다.
</aside>
)}
<DataGridContainer ref={containerRef} style={{ height: '100%', minWidth: 0 }}>
<AXDataGrid<Order> width={width} height={height} columns={columns} data={data} />
</DataGridContainer>
</div>
</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. 그리드 크기는 화면이 아니라 컨테이너에서 측정합니다
업무 화면은 사이드 패널, 탭, 분할 화면처럼 실행 중에 폭과 높이가 바뀌는 경우가 많습니다. 그리드에 최초의 window 크기나 고정 값을 전달하면 화면이 넓어질 때는 문제가 드러나지 않아도, 좁아질 때 이전 크기가 남아 가로 스크롤과 레이아웃이 어긋날 수 있습니다.
useContainerSize는 ResizeObserver로 그리드를 담는 요소의 실제 콘텐츠 영역을 관찰합니다. 측정된 width, height를 AXDataGrid에 전달하면 부모 레이아웃의 확대·축소 모두에 맞춰 다시 렌더링됩니다.
2. DataGridContainer가 필요한 이유
DataGridContainer는 position: relative인 측정 기준점입니다. 그 안의 DataGrid 루트는 position: absolute; inset: 0으로 배치합니다. 따라서 컨테이너는 일반 레이아웃 흐름 안에서 정확한 크기를 갖고, 그리드는 그 측정 영역을 빈틈없이 채웁니다.
특히 flex나 grid 레이아웃에서는 그리드를 담는 칸에 min-width: 0을 주어야 작은 폭으로 줄어들 수 있습니다. 컨테이너에는 명시적인 높이도 필요합니다. DataGrid는 그 높이를 기준으로 보이는 행 수와 가상 스크롤 범위를 계산합니다.
3. 적용 순서
- 그리드 영역을
DataGridContainer로 감싸고 높이를 정합니다. useContainerSize에 컨테이너 ref를 전달합니다.- 반환된
width,height를AXDataGrid에 전달합니다. - 부모가 flex/grid라면 그리드 칸에
minWidth: 0또는 CSSmin-width: 0을 적용합니다.
위 실행 예제에서 사이드 패널을 열고 닫아 보세요. 컨테이너 폭이 커질 때뿐 아니라 작아질 때도 그리드가 즉시 정확한 폭으로 맞춰집니다.
4. 피해야 할 방식
window.innerWidth만 한 번 읽어 그리드 크기로 사용하기- 컨테이너의 높이 없이
height="100%"만 전달하기 - flex 항목에 기본
min-width: auto를 둬서 내용이 줄어들지 못하게 하기 - 그리드 밖의 임의 DOM 크기를 측정해 여러 그리드에 공통으로 전달하기
각 그리드는 자신을 감싸는 컨테이너를 개별 관찰하는 편이 가장 예측 가능하고, 분할 화면·접이식 패널·반응형 레이아웃에서도 안정적입니다.