Focus and Active Row
Connect cell clicks to selectedRowKey and visually highlight the currently selected row.
import * as React from 'react';
import { BGrid, BGridColumn, BGridDataItem } from 'beautiful-grid';
import DataGridContainer from '../components/DataGridContainer';
import { useContainerSize } from '../hooks/useContainerSize';
interface KnowledgeArticle {
articleId: number;
category: string;
title: string;
owner: string;
status: '게시' | '검토 중' | '초안';
updatedAt: string;
}
const articleTitles = [
'신규 입사자 계정 발급 절차',
'법인카드 비용 정산 가이드',
'고객 데이터 보안 등급 정책',
'원격 근무 VPN 접속 방법',
'장애 상황 긴급 연락 체계',
'구매 요청 및 승인 프로세스',
];
const categories = ['인사', '재무', '보안', 'IT 운영', '고객지원'];
const owners = ['김서준', '이하린', '박도윤', '최지우'];
const data: BGridDataItem<KnowledgeArticle>[] = Array.from({ length: 120 }, (_, index) => ({
values: {
articleId: index + 1,
category: categories[index % categories.length],
title: articleTitles[index % articleTitles.length],
owner: owners[index % owners.length],
status: index % 7 === 0 ? '초안' : index % 4 === 0 ? '검토 중' : '게시',
updatedAt: `2026-08-${String((index % 23) + 1).padStart(2, '0')}`,
},
}));
export default function FocusExample() {
const [selectedRowKey, setSelectedRowKey] = React.useState<number>();
const [columns, setColumns] = React.useState<BGridColumn<KnowledgeArticle>[]>([
{ key: 'articleId', label: '문서번호', width: 90, align: 'center' },
{ key: 'category', label: '분류', width: 100, align: 'center' },
{ key: 'title', label: '문서 제목', width: 320 },
{ key: 'owner', label: '담당자', width: 100, align: 'center' },
{ key: 'status', label: '게시 상태', width: 100, align: 'center' },
{ key: 'updatedAt', label: '최종 수정일', width: 120, align: 'center', sortDisable: true },
]);
const containerRef = React.useRef<HTMLDivElement>(null);
const { width, height } = useContainerSize(containerRef);
const selectedArticle = data.find(item => item.values.articleId === selectedRowKey)?.values;
return (
<>
<div data-testid='selected-article' style={{ marginBottom: 10 }}>
<strong>선택 문서:</strong> {selectedArticle ? `${selectedArticle.articleId}. ${selectedArticle.title}` : '없음'}
</div>
<DataGridContainer ref={containerRef}>
<BGrid<KnowledgeArticle>
width={width}
height={height}
headerHeight={35}
data={data}
columns={columns}
rowKey='articleId'
selectedRowKey={selectedRowKey}
onChangeColumns={(_columnIndex, { columns }) => setColumns(columns)}
onClick={({ item }) => setSelectedRowKey(item.articleId)}
/>
</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,
};
}1. When and why is it needed?
Use selectedRowKey when a clicked row must remain highlighted, such as when connecting a list to a detail panel. This is a controlled pattern: store the original row’s key in state from onClick, then pass that value back through selectedRowKey.
2. Practical example: highlight a row and connect a detail view
import React, { useState } from 'react';
import { BGrid, type BGridColumn, type BGridDataItem } from 'beautiful-grid';
interface ArticleItem {
id: number;
title: string;
author: string;
createdAt: string;
}
export default function FocusGrid() {
const [selectedKey, setSelectedKey] = useState<string | number>(2);
const [data] = useState<BGridDataItem<ArticleItem>[]>([
{ values: { id: 1, title: 'BeautifulGrid v1.11 release notes', author: 'Admin', createdAt: '2026-08-10' } },
{ values: { id: 2, title: 'Tips for optimizing high-performance virtual scrolling', author: 'Engineering', createdAt: '2026-08-12' } },
{ values: { id: 3, title: 'React 19 compatibility and TypeScript support', author: 'Frontend', createdAt: '2026-08-15' } },
]);
const columns: BGridColumn<ArticleItem>[] = [
{ key: 'id', label: 'No.', width: 70, align: 'center' },
{ key: 'title', label: 'Title', width: 320 },
{ key: 'author', label: 'Author', width: 100, align: 'center' },
{ key: 'createdAt', label: 'Published', width: 120, align: 'center' },
];
return (
<div>
<BGrid<ArticleItem>
width={650}
height={220}
columns={columns}
data={data}
rowKey="id"
selectedRowKey={selectedKey} // Unique key of the selected row (applies the active style)
onClick={({ item }) => {
setSelectedKey(item.id);
}}
/>
</div>
);
}