Skip to content

Commit 882fc0f

Browse files
os-zhuangclaude
andauthored
feat(grid): type-aware inline cell editors (select dropdown, boolean checkbox) (#2097)
The data-table's inline editor used a plain text box for every field. Extend it to render the field-type-appropriate control, matching the form's controls: - ObjectGrid forwards each column's field type + select options (a single enrichment over the finalized columns, read from objectSchema — additive, never overrides a type/options a column already set) - data-table: select/status -> a <Select> dropdown of the field's options (opens on edit, commits on pick); boolean -> a <Checkbox> - saveEdit accepts an explicit value so discrete pickers commit the chosen value without waiting for setEditValue to flush (date / datetime / number editors already existed.) Verified live: the Studio grid's 行业 select cell opens a dropdown of its options; type-check + lint clean on both packages. Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent ce57ad7 commit 882fc0f

2 files changed

Lines changed: 70 additions & 14 deletions

File tree

packages/components/src/renderers/complex/data-table.tsx

Lines changed: 55 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import {
2626
import { Button } from '../../ui/button';
2727
import { Input } from '../../ui/input';
2828
import { Checkbox } from '../../ui/checkbox';
29-
import {
29+
import {
3030
Select,
3131
SelectContent,
3232
SelectItem,
@@ -624,30 +624,34 @@ const DataTableRenderer = ({ schema }: { schema: DataTableSchema }) => {
624624
setEditValue(valueToEdit);
625625
};
626626

627-
const saveEdit = (force: boolean = false) => {
627+
const saveEdit = (force: boolean = false, explicitValue?: any) => {
628628
if (!editingCell) return;
629-
629+
630630
// Don't save if we're in cancelled state (unless forced)
631631
if (!force && editingCell === null) return;
632-
632+
633633
const { rowIndex, columnKey } = editingCell;
634634
const globalIndex = (effectivePage - 1) * pageSize + rowIndex;
635635
// Under manual pagination `sortedData` IS the current page, so address it
636636
// page-locally; otherwise it's the full in-memory set indexed absolutely.
637637
const row = sortedData[manualPagination ? rowIndex : globalIndex];
638-
638+
639+
// Discrete editors (select / checkbox) commit the chosen value synchronously
640+
// via `explicitValue` — their `setEditValue` hasn't flushed to state yet.
641+
const valueToStage = explicitValue !== undefined ? explicitValue : editValue;
642+
639643
// Update pending changes
640644
const newPendingChanges = new Map(pendingChanges);
641645
const rowChanges = newPendingChanges.get(rowIndex) || {};
642-
rowChanges[columnKey] = editValue;
646+
rowChanges[columnKey] = valueToStage;
643647
newPendingChanges.set(rowIndex, rowChanges);
644648
setPendingChanges(newPendingChanges);
645-
649+
646650
// Call the legacy onCellChange callback if provided
647651
if (schema.onCellChange) {
648-
schema.onCellChange(globalIndex, columnKey, editValue, row);
652+
schema.onCellChange(globalIndex, columnKey, valueToStage, row);
649653
}
650-
654+
651655
setEditingCell(null);
652656
setEditValue('');
653657
};
@@ -1237,10 +1241,48 @@ const DataTableRenderer = ({ schema }: { schema: DataTableSchema }) => {
12371241
);
12381242
}
12391243

1240-
// NOTE: `select`/`boolean` editors are intentionally not
1241-
// wired here — the data-table column does not currently
1242-
// carry option metadata. Extension point: when `col.options`
1243-
// is forwarded from ObjectGrid, render a <Select>/checkbox.
1244+
// Select / single-choice: a dropdown of the field's options
1245+
// (forwarded as `col.options` from ObjectGrid), matching the
1246+
// form's select control. Opens immediately and commits on pick.
1247+
const editOptions = (col as any).options as
1248+
| Array<{ value: unknown; label: string }>
1249+
| undefined;
1250+
if (
1251+
(editType === 'select' || editType === 'status') &&
1252+
Array.isArray(editOptions) &&
1253+
editOptions.length > 0
1254+
) {
1255+
return (
1256+
<Select
1257+
defaultOpen
1258+
value={editValue == null ? '' : String(editValue)}
1259+
onValueChange={(v) => saveEdit(true, v)}
1260+
>
1261+
<SelectTrigger className="h-8 px-2 py-1">
1262+
<SelectValue placeholder="—" />
1263+
</SelectTrigger>
1264+
<SelectContent>
1265+
{editOptions.map((o) => (
1266+
<SelectItem key={String(o.value)} value={String(o.value)}>
1267+
{o.label}
1268+
</SelectItem>
1269+
))}
1270+
</SelectContent>
1271+
</Select>
1272+
);
1273+
}
1274+
1275+
// Boolean: a checkbox, like the form's boolean control.
1276+
if (editType === 'boolean') {
1277+
return (
1278+
<div className="flex h-8 items-center px-2">
1279+
<Checkbox
1280+
checked={editValue === true || editValue === 'true' || editValue === 1}
1281+
onCheckedChange={(c) => saveEdit(true, !!c)}
1282+
/>
1283+
</div>
1284+
);
1285+
}
12441286

12451287
// Fallback: plain text input (original behavior).
12461288
return (

packages/plugin-grid/src/ObjectGrid.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1320,7 +1320,21 @@ export const ObjectGrid: React.FC<ObjectGridProps> = ({
13201320
);
13211321
}
13221322

1323-
const columns = generateColumns();
1323+
const columns = generateColumns().map((col: any) => {
1324+
// Enrich each column with its field type + select options so the
1325+
// data-table's type-aware inline editor can pick the matching control
1326+
// (dropdown for select, checkbox for boolean) the form uses, instead of a
1327+
// plain text box. Additive: never overrides a type/options a path already set.
1328+
if (!col || col.accessorKey === '_actions') return col;
1329+
const fieldDef = (objectSchema as any)?.fields?.[col.accessorKey];
1330+
if (!fieldDef) return col;
1331+
const next: any = { ...col };
1332+
if (next.type == null && fieldDef.type) next.type = fieldDef.type;
1333+
if (next.options == null && fieldDef.options) {
1334+
next.options = translateOptions(schema.objectName, col.accessorKey, fieldDef.options);
1335+
}
1336+
return next;
1337+
});
13241338

13251339
// Apply persisted column order and widths
13261340
let persistedColumns = [...columns];

0 commit comments

Comments
 (0)