feat: Airtable-style Grid/List UX optimizations#907
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
- Date fields use browser locale for humanized formatting - Row hover shows "Open >" text button instead of icon-only - Add singleClickEdit prop for click-to-edit mode - Default compact row height in ObjectGrid - Column headers use minimal font-normal style - Empty table shows ghost row placeholders Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
…fix TS warning
- Filter badges use rounded-full for Airtable-style pill appearance
- Add selectionStyle prop ('always'|'hover') for checkbox visibility
- Add auto column width estimation from data content
- Fix unused variable in ghost row template
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
…and helpers - Extract GHOST_ROW_COUNT constant with documentation - Extract ghostCellWidth() helper function for readability - Add clarifying comments for auto-sizing memo - Update ROADMAP.md with P1.13 Airtable Grid UX section Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
|
@copilot 仔细评估是否已经完成了任务中的所有开发要求 |
- ListView default density changed from 'comfortable' to 'compact' - ListView density toggle highlight checks against 'compact' (new default) - ObjectGrid singleClickEdit defaults to true (click-to-edit by default) - Update test to match new default density - Update ROADMAP.md with complete status Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
经过仔细审查,发现了 3 处遗漏并在
所有 10 项需求现已全部完成:
所有 675 个相关测试通过。 |
There was a problem hiding this comment.
Pull request overview
Delivers an Airtable-style UX pass across ObjectUI’s Grid/List experiences by updating defaults (density/row height), adding schema toggles for editing + selection behavior, improving table empty states, and making date formatting locale-aware.
Changes:
- Add
singleClickEditandselectionStyleto table/grid schema types and wiresingleClickEditthrough ObjectGrid → DataTable. - Update DataTable UX: auto column width fallback, minimal header styling, hover “Open” affordance, and ghost skeleton rows on empty.
- Align ListView density default to
compact, update filter pill styling, and localize date formatting via browser locale.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| packages/types/src/objectql.ts | Adds singleClickEdit to ObjectGridSchema and updates rowHeight default doc to compact. |
| packages/types/src/data-display.ts | Adds selectionStyle and singleClickEdit to DataTableSchema. |
| packages/plugin-list/src/tests/ListView.test.tsx | Updates test expectation for ListView default density (compact). |
| packages/plugin-list/src/UserFilters.tsx | Switches filter badges/buttons to pill styling (rounded-full). |
| packages/plugin-list/src/ListView.tsx | Changes default density to compact and adjusts density toggle highlight logic. |
| packages/plugin-grid/src/ObjectGrid.tsx | Defaults grid row height to compact and defaults singleClickEdit to true when building the DataTable schema. |
| packages/fields/src/index.tsx | Uses browser locale (undefined) for date/time formatting instead of hardcoded en-US. |
| packages/components/src/renderers/complex/data-table.tsx | Implements auto column sizing fallback, ghost rows empty state, selection hover style, “Open” hover affordance, and single-click editing behavior. |
| ROADMAP.md | Marks the Airtable Grid/List UX optimization work as complete and documents the delivered items. |
Comments suppressed due to low confidence (4)
packages/types/src/objectql.ts:506
ObjectGridSchema.singleClickEditis documented as@default false, but ObjectGrid currently passesschema.singleClickEdit ?? truedown to the data-table. Please align the JSDoc default with the actual runtime default (or change the runtime default) to avoid misleading API docs.
/**
* Enable single-click editing mode
* When true with editable, clicking a cell enters edit mode (instead of double-click)
* @default false
*/
singleClickEdit?: boolean;
ROADMAP.md:774
- ROADMAP entry repeats the Single-Click Edit Mode section twice. Please remove the duplicate block to keep the roadmap concise and avoid conflicting future edits.
**Single-Click Edit Mode:**
- [x] Added `singleClickEdit` prop to `DataTableSchema` and `ObjectGridSchema`
- [x] When true, clicking a cell enters edit mode (instead of double-click)
**Default Compact Row Height:**
- [x] ObjectGrid default changed from `'medium'` to `'compact'` (32-36px rows)
- [x] ListView default density changed from `'comfortable'` to `'compact'`
- [x] Row height toggle preserved in toolbar
**Single-Click Edit Mode:**
- [x] Added `singleClickEdit` prop to `DataTableSchema` and `ObjectGridSchema`
- [x] ObjectGrid defaults `singleClickEdit` to `true` (click-to-edit by default)
- [x] InlineEditing component already compatible (click-to-edit native)
packages/components/src/renderers/complex/data-table.tsx:209
- There are existing tests for DataTable editing, but the newly added behaviors (
singleClickEditclick-to-edit,selectionStyle: 'hover', ghost rows when empty, and auto column width fallback) aren't covered. Adding a few focused RTL/Vitest tests would help prevent regressions for these UX-critical features.
editable = false,
singleClickEdit = false,
selectionStyle = 'always',
rowClassName,
rowStyle,
className,
frozenColumns = 0,
showRowNumbers = false,
showAddRow = false,
} = schema;
// i18n support for pagination labels
const { t } = useTableTranslation();
// Ensure data is always an array – provider config objects or null/undefined
// must not reach array operations like .filter() / .some()
const data = Array.isArray(rawData) ? rawData : [];
// Normalize columns to support legacy keys (label/name) from existing JSONs
const initialColumns = useMemo(() => {
return rawColumns.map((col: any) => ({
...col,
header: col.header || col.label,
accessorKey: col.accessorKey || col.name
}));
}, [rawColumns]);
// Auto-size columns: estimate width from header and data content for columns without explicit widths
const autoSizedWidths = useMemo(() => {
const widths: Record<string, number> = {};
const cols = rawColumns.map((col: any) => ({
header: col.header || col.label,
accessorKey: col.accessorKey || col.name,
width: col.width,
}));
for (const col of cols) {
if (col.width) continue; // Skip columns with explicit widths
const headerLen = (col.header || '').length;
let maxLen = headerLen;
// Sample up to 50 rows for content width estimation
const sampleRows = data.slice(0, 50);
for (const row of sampleRows) {
const val = row[col.accessorKey];
const len = val != null ? String(val).length : 0;
if (len > maxLen) maxLen = len;
}
// Estimate pixel width: ~8px per character + 48px padding, min 80, max 400
widths[col.accessorKey] = Math.min(400, Math.max(80, maxLen * 8 + 48));
}
return widths;
}, [rawColumns, data]);
packages/components/src/renderers/complex/data-table.tsx:872
- In
selectionStyle === 'hover'mode the checkbox is hidden viaopacity: 0, but it remains focusable/clickable (tab navigation can land on an invisible control). Consider usinginvisible/pointer-events-none(and/or managingtabIndex/aria-hidden) when not hovered and not selected to keep keyboard/a11y behavior consistent.
<TableCell className={cn(frozenColumns > 0 && "sticky left-0 z-10 bg-background", selectionStyle === 'hover' && "relative")}>
{selectionStyle === 'hover' ? (
<div className={cn("transition-opacity", isSelected ? "opacity-100" : "opacity-0 group-hover/row:opacity-100")}>
<Checkbox
checked={isSelected}
onCheckedChange={(checked) => handleSelectRow(rowId, checked as boolean)}
/>
</div>
| }} | ||
| onDoubleClick={() => isEditable && startEdit(rowIndex, col.accessorKey)} | ||
| onDoubleClick={() => isEditable && !singleClickEdit && startEdit(rowIndex, col.accessorKey)} | ||
| onClick={() => isEditable && singleClickEdit && startEdit(rowIndex, col.accessorKey)} |
There was a problem hiding this comment.
singleClickEdit click handler will bubble to the row's onClick handler. When schema.onRowClick is set (e.g., ObjectGrid navigation), clicking an editable cell in single-click mode will both enter edit mode and trigger row navigation. Prevent propagation (or add a guard in the row click heuristic) when starting edit to avoid accidental navigation.
| onClick={() => isEditable && singleClickEdit && startEdit(rowIndex, col.accessorKey)} | |
| onClick={(e) => { | |
| if (isEditable && singleClickEdit) { | |
| e.stopPropagation(); | |
| startEdit(rowIndex, col.accessorKey); | |
| } | |
| }} |
Comprehensive UX pass to close the gap between ObjectUI's Grid/List components and Airtable's table experience across date formatting, interaction patterns, density, and empty states.
Date formatting
formatDate,formatDateTime,DateTimeCellRenderernow use browser locale (undefined) instead of hardcoded'en-US', so dates render as localized human-readable strings automatically.Row interaction
<Expand>to"Open >"text +<ChevronRight>— matches Airtable's row-open affordance.singleClickEditprop onDataTableSchema/ObjectGridSchema— ObjectGrid defaults totrue, so cells enter edit mode on single click by default (matching Airtable behavior). Can be set tofalseto restore double-click editing.Density & headers
rowHeightchanged from'medium'to'compact'(32px rows).'comfortable'to'compact', matching ObjectGrid. Density toggle highlight updated accordingly.text-[11px] font-semibold uppercase tracking-wider→text-xs font-normal text-muted-foreground.Selection
selectionStyle: 'always' | 'hover'onDataTableSchema—'hover'fades checkboxes in only on row hover.Filters
rounded-fullfor pill/chip appearance (wasrounded-md).Column sizing
Empty state
GHOST_ROW_COUNTconstant +ghostCellWidth()helper) to maintain visual table structure.Type changes
Original prompt
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.