MUI 에디터 플러그인 (MUI Editor Plugins)

@beautifuljs/grid-mui로 MUI Select, DatePicker, ColorPicker, TimePicker를 BeautifulGrid 컬럼에 연결합니다.

#editor-plugin#mui#material-ui#portal#date-picker#time-picker
검토일: 2026-09-24
LIVE DEMOMUI 에디터 플러그인 (MUI Editor Plugins) 인터랙티브 예제
인터랙티브 데모를 불러오는 중입니다…
GitHub
import { t } from './i18n';
import * as React from 'react';
import { BGrid, type BGridColumn, type BGridDataItem } from 'beautiful-grid';
import {
  createMuiColorPickerEditorPlugin,
  createMuiDatePickerEditorPlugin,
  createMuiSelectEditorPlugin,
  createMuiTimePickerEditorPlugin,
} from '@beautifuljs/grid-mui';
import '@beautifuljs/grid-mui/style.css';
import DataGridContainer from '../components/DataGridContainer';
import { useContainerSize } from '../hooks/useContainerSize';
import { CalendarIcon, ChevronDownIcon, ClockIcon } from './editing/editorIcons';
import {
  applyEditingDataChange,
  cloneEditingOrders,
  type EditingOrder,
  withEditingCellClasses,
} from './editing/shared';

type MuiEditorOrder = EditingOrder & {
  labelColor: string;
  deliveryTime: string;
};

const statusEditor = createMuiSelectEditorPlugin<MuiEditorOrder, EditingOrder['status']>({
  id: 'mui-status',
  ariaLabel: t('MUI 주문 상태 선택', 'MUI Select Order Status'),
  options: [
    { value: t('접수', 'Receipt'), label: t('접수', 'Receipt') },
    { value: t('진행', 'In Progress'), label: t('진행', 'In Progress') },
    { value: t('완료', 'Completed'), label: t('완료', 'Completed') },
  ],
});

const deliveryDateEditor = createMuiDatePickerEditorPlugin<MuiEditorOrder>({
  id: 'mui-delivery-date',
  ariaLabel: t('MUI 납기일 선택', 'MUI Select Delivery Date'),
});

const labelColorEditor = createMuiColorPickerEditorPlugin<MuiEditorOrder>({
  id: 'mui-label-color',
  ariaLabel: t('MUI 라벨 색상 선택', 'MUI Select Label Color'),
  colors: ['#1976D2', '#00897B', '#7B1FA2', '#ED6C02'],
});

const deliveryTimeEditor = createMuiTimePickerEditorPlugin<MuiEditorOrder>({
  id: 'mui-delivery-time',
  ariaLabel: t('MUI 배송 시간 선택', 'MUI Select Delivery Time'),
  minuteStep: 5,
});

const initialColors = ['#1976D2', '#00897B', '#7B1FA2', '#ED6C02'];
const initialDeliveryTimes = ['09:30', '11:00', '14:30', '16:00'];

const cloneMuiEditorOrders = (): BGridDataItem<MuiEditorOrder>[] =>
  cloneEditingOrders().map((item, index) => ({
    ...item,
    values: {
      ...item.values,
      labelColor: initialColors[index % initialColors.length],
      deliveryTime: initialDeliveryTimes[index % initialDeliveryTimes.length],
    },
  }));

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

  const columns = React.useMemo<BGridColumn<MuiEditorOrder>[]>(
    () =>
      withEditingCellClasses<MuiEditorOrder>([
        { key: 'orderCode', label: t('주문 코드', 'Order Code'), width: 140, editable: false },
        {
          key: 'status',
          label: 'MUI Select',
          width: 165,
          editable: true,
          editor: statusEditor,
          editorIcon: { render: <ChevronDownIcon />, ariaLabel: t('MUI 상태 선택', 'MUI Select Status') },
        },
        {
          key: 'deliveryDate',
          label: 'MUI DatePicker',
          width: 175,
          editable: true,
          editor: deliveryDateEditor,
          editorIcon: { render: <CalendarIcon />, ariaLabel: t('MUI 납기일 선택', 'MUI Select Delivery Date') },
        },
        {
          key: 'labelColor',
          label: 'MUI ColorPicker',
          width: 175,
          editable: true,
          editor: labelColorEditor,
          editorIcon: {
            render: ({ value }) => (
              <span
                className='bgrid-color-swatch'
                style={{ backgroundColor: typeof value === 'string' ? value : 'transparent' }}
                aria-hidden='true'
              />
            ),
            ariaLabel: t('MUI 라벨 색상 선택', 'MUI Select Label Color'),
          },
        },
        {
          key: 'deliveryTime',
          label: 'MUI TimePicker',
          width: 165,
          editable: true,
          editor: deliveryTimeEditor,
          editorIcon: { render: <ClockIcon />, ariaLabel: t('MUI 배송 시간 선택', 'MUI Select Delivery Time') },
        },
      ]),
    [],
  );

  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 dark:border-slate-800 dark:bg-slate-900 dark:text-slate-300'>
        <code>@beautifuljs/grid-mui</code>
        {t(
          '의 Select, DatePicker, ColorPicker, TimePicker를 사용합니다. 셀을 더블클릭하거나 아이콘을 누르면 Grid 전용 portal 안에서 편집기가 열립니다.',
          ' provides Select, DatePicker, ColorPicker, and TimePicker. Double-click a cell or use its icon to open the editor in the Grid portal.',
        )}
      </p>
      <DataGridContainer ref={containerRef} style={{ height: 340 }}>
        <BGrid<MuiEditorOrder>
          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>
  );
}

@beautifuljs/grid-mui는 MUI 컴포넌트를 BeautifulGrid plugin editor 수명주기에 연결하는 공식 패키지입니다. Select, DatePicker, ColorPicker, TimePicker를 제공하며 popup은 Grid 전용 portal에 렌더링됩니다.

설치

npm install @beautifuljs/grid-mui @mui/material @mui/x-date-pickers @emotion/react @emotion/styled dayjs

애플리케이션 진입점에서 BeautifulGrid와 plugin 스타일을 한 번 불러옵니다. 앱에 기존 MUI 테마가 있다면 그 ThemeProvider를 그대로 사용하세요.

import { ThemeProvider } from '@mui/material/styles';
import 'beautiful-grid/style.css';
import '@beautifuljs/grid-mui/style.css';

root.render(
  <ThemeProvider theme={appTheme}>
    <App />
  </ThemeProvider>,
);

Editor factory 연결

import {
  createMuiColorPickerEditorPlugin,
  createMuiDatePickerEditorPlugin,
  createMuiSelectEditorPlugin,
  createMuiTimePickerEditorPlugin,
} from '@beautifuljs/grid-mui';

const statusEditor = createMuiSelectEditorPlugin<Order, Order['status']>({
  id: 'order-status',
  ariaLabel: '주문 상태 선택',
  options: [
    { value: 'ready', label: '접수' },
    { value: 'done', label: '완료' },
  ],
});

const dateEditor = createMuiDatePickerEditorPlugin<Order>({
  id: 'order-date',
  ariaLabel: '납기일 선택',
  format: 'YYYY-MM-DD',
});

const colorEditor = createMuiColorPickerEditorPlugin<Order>({
  id: 'order-color',
  ariaLabel: '라벨 색상 선택',
  colors: ['#1976D2', '#00897B', '#ED6C02'],
});

const timeEditor = createMuiTimePickerEditorPlugin<Order>({
  id: 'order-time',
  ariaLabel: '배송 시간 선택',
  minuteStep: 5,
});

생성된 설정을 editable 컬럼에 지정합니다.

const columns: BGridColumn<Order>[] = [
  { key: 'status', label: '상태', editable: true, editor: statusEditor },
  { key: 'deliveryDate', label: '납기일', editable: true, editor: dateEditor },
  { key: 'labelColor', label: '색상', editable: true, editor: colorEditor },
  { key: 'deliveryTime', label: '시간', editable: true, editor: timeEditor },
];

지원 범위와 값 형식

  • Select는 string | number option 값을 그대로 저장합니다.
  • DatePicker는 기본적으로 YYYY-MM-DD 문자열을 저장하며 format으로 변경할 수 있습니다.
  • ColorPicker는 #RRGGBB를 대문자로 정규화하고 Apply 시점에 저장합니다.
  • TimePicker는 기본적으로 HH:mm 문자열을 저장하고 확인 동작 후 편집을 종료합니다.
  • Cascader와 TreeSelect는 MUI 공식 통합 범위에 포함되지 않습니다.

각 popup은 getPortalContainer()에 연결되어 Grid의 바깥 클릭 판정과 스크롤 경계를 공유합니다. 사용자는 Escape로 원래 값을 유지한 채 취소할 수 있고, 저장 또는 취소 후 포커스는 활성 셀로 복원됩니다.

패키지 API와 라이선스 정보는 @beautifuljs/grid-mui npm 페이지에서 확인할 수 있습니다.