編集可能データテーブルEditableDataTableExperimental

Editable line-item grid: rows of consumer-rendered editor cells with add/remove row, a totals/footer row, per-cell accessible labels, and a desktop table that stacks into mobile cards. For invoices, journals, estimates, and timesheets.

プレビュー

1行目
品目
数量
単価
金額¥120,000
2行目
品目
数量
単価
金額¥5,000
品目
合計
金額
¥125,000

Props

表は横にスクロールできます
プロパティ初期値説明
columnsEditableColumn<TRow>[]-Column defs. Each renders its cell editor via cell(row, ctx); ctx.ariaLabel is a ready-made label.
rowsTRow[]-The (controlled) row data — the consumer owns it.
getRowId(row, index) => string-Stable row key.
onAddRow() => void-Shows an add-row button when set.
onRemoveRow(index) => void-Shows a per-row remove button (hidden at/below minRows).
minRowsnumber0Minimum rows kept.
getRowError(row, index) => string | undefined-Marks a row invalid; the message is exposed to screen readers.
footerReactNode-Totals / balance row, rendered under the body on desktop + mobile.
renderRowCard(row, ctx) => ReactNode-Custom mobile card body (defaults to stacking each column).
variant"default" | "compact""default"Density.
labels / rowLabel / caption / className-Add/remove/empty labels, per-row label, caption, extra classes.

Usage

import { EditableDataTable, type EditableColumn, Input, NumberInput, formatCurrency } from "@gunjo/ui"

type LineItem = { id: string; name: string; qty: number; price: number }

export function Example() {
  const [rows, setRows] = React.useState<LineItem[]>([/* ... */])
  const update = (i: number, patch: Partial<LineItem>) =>
    setRows((rs) => rs.map((r, idx) => (idx === i ? { ...r, ...patch } : r)))

  const columns: EditableColumn<LineItem>[] = [
    { id: "name", header: "品目",
      cell: (row, ctx) => <Input value={row.name} aria-label={ctx.ariaLabel}
        onChange={(e) => update(ctx.rowIndex, { name: e.target.value })} /> },
    { id: "qty", header: "数量", align: "right",
      cell: (row, ctx) => <NumberInput value={row.qty} aria-label={ctx.ariaLabel}
        onValueChange={(v) => update(ctx.rowIndex, { qty: v })} /> },
    { id: "amount", header: "金額", align: "right",
      cell: (row) => formatCurrency(row.qty * row.price) },
  ]

  return (
    <EditableDataTable columns={columns} rows={rows} getRowId={(r) => r.id} minRows={1}
      onAddRow={() => setRows((rs) => [...rs, makeRow()])}
      onRemoveRow={(i) => setRows((rs) => rs.filter((_, idx) => idx !== i))}
      footer={<Totals rows={rows} />} />
  )
}