Built-in Editors

Configure the built-in text editor and the provided Select and Date plugins, including value parsing, formatting, and editor icons.

#text-editor#select-editor#date-editor#parseValue#formatValue
Last reviewed: 2026-08-20
GitHub
import * as React from 'react';
import { BGrid, type BGridColumn } from 'beautiful-grid';
import { createDateEditorPlugin, createSelectEditorPlugin } from 'beautiful-grid/editors';
import DataGridContainer from '../components/DataGridContainer';
import { useContainerSize } from '../hooks/useContainerSize';
import { CalendarIcon, ChevronDownIcon } from './editing/editorIcons';
import {
  applyEditingDataChange,
  cloneEditingOrders,
  type EditingOrder,
  withEditingCellClasses,
} from './editing/shared';

function formatDate(value: unknown) {
  if (typeof value !== 'string') return '';
  const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
  return match ? `${match[1]}.${match[2]}.${match[3]}` : value;
}

const statusEditor = createSelectEditorPlugin<EditingOrder, EditingOrder['status']>({
  id: 'built-in-status',
  ariaLabel: '주문 상태 선택',
  options: [
    { value: '접수', label: '접수' },
    { value: '진행', label: '진행' },
    { value: '완료', label: '완료' },
  ],
});

const deliveryDateEditor = createDateEditorPlugin<EditingOrder>({
  id: 'built-in-delivery-date',
  ariaLabel: '납기일 선택',
  min: '2026-08-01',
  max: '2026-12-31',
});

export default function BuiltInEditorsExample() {
  const [data, setData] = React.useState(cloneEditingOrders);
  const containerRef = React.useRef<HTMLDivElement>(null);
  const { width, height } = useContainerSize(containerRef);

  const columns = React.useMemo<BGridColumn<EditingOrder>[]>(
    () => withEditingCellClasses<EditingOrder>([
      { key: 'orderCode', label: '주문 코드', width: 145, editable: false },
      {
        key: 'customerName',
        label: '내장 text',
        width: 180,
        editable: true,
        editor: {
          type: 'text',
          inputProps: { maxLength: 50, autoComplete: 'off' },
        },
      },
      {
        key: 'status',
        label: '기본 Select',
        width: 150,
        editable: true,
        editTrigger: 'click',
        editor: statusEditor,
        editorIcon: { render: <ChevronDownIcon />, ariaLabel: '주문 상태 선택', visibility: 'always' },
      },
      {
        key: 'deliveryDate',
        label: '기본 Date',
        width: 170,
        editable: true,
        editTrigger: 'click',
        editor: deliveryDateEditor,
        itemRender: ({ value }) => formatDate(value),
        editorIcon: { render: <CalendarIcon />, ariaLabel: '납기일 선택', visibility: 'always' },
      },
    ]),
    [],
  );

  return (
    <div className='flex min-h-0 flex-col gap-3'>
      <p className='m-0 rounded-lg border border-slate-200 bg-slate-50 p-3 text-sm leading-6 text-slate-700'>
        text 입력은 라이브러리 내장 편집기이며 Select와 Date는 <code>beautiful-grid/editors</code>가 제공하는 의존성
        없는 plugin입니다. 화살표와 달력 아이콘을 누르거나 셀을 한 번 클릭해 선택하세요.
      </p>
      <DataGridContainer ref={containerRef} style={{ height: 340 }}>
        <BGrid<EditingOrder>
          width={width}
          height={height}
          data={data}
          columns={columns}
          rowKey='id'
          editable
          variant='vertical-bordered'
          onChangeData={(sourceIndex, _columnIndex, values, _column, meta) => {
            setData(current => applyEditingDataChange(current, sourceIndex, values, meta));
          }}
        />
      </DataGridContainer>
    </div>
  );
}

Use the built-in text editor for free-form input, and the standard plugins from beautiful-grid/editors for selecting predefined values and dates. These plugins add no dependency on an external UI framework.

Text

{
  key: 'quantity',
  editable: true,
  editor: {
    type: 'text',
    inputProps: { inputMode: 'numeric', autoComplete: 'off' },
    formatValue: value => String(value ?? ''),
    parseValue: text => {
      const value = Number(text);
      if (!Number.isFinite(value)) throw new Error('Enter a number.');
      return value;
    },
  },
}

If parseValue throws, the Grid does not save the value. It keeps the editor open and sets aria-invalid="true". With commitOnBlur: false, moving focus outside the editor cancels the edit instead of saving it.

Select and Date

const statusEditor = createSelectEditorPlugin<Order, Order['status']>({
  id: 'order-status',
  options: [
    { value: 'ready', label: 'Ready' },
    { value: 'done', label: 'Completed' },
  ],
});

const dateEditor = createDateEditorPlugin<Order>({
  id: 'delivery-date',
  min: '2026-01-01',
  max: '2026-12-31',
});

Create each factory result once, either outside the component or inside useMemo. Creating a new plugin object on every column render can remount the input component.

The standard Select opens its native option picker as soon as the editor mounts after a cell or icon click. Set openOnMount: false in the factory options to disable this automatic opening behavior.

The standard Date editor activates only the numeric date input when entered through the cell body. It opens the native calendar picker only when entered through editorIcon. An editor plugin can distinguish these entry paths through the activation value ('cell' | 'editorIcon').

{
  key: 'status',
  editable: true,
  editTrigger: 'click',
  editor: statusEditor,
  editorIcon: { render: <ChevronDownIcon />, ariaLabel: 'Select status' },
}

The Grid does not infer an icon from the editor type. Provide an icon that matches your product design system through editorIcon.render.