Skip to content

feat: Airtable-style Grid/List UX optimizations - #907

Merged
hotlong merged 5 commits into
mainfrom
copilot/optimize-grid-list-ux
Feb 28, 2026
Merged

feat: Airtable-style Grid/List UX optimizations#907
hotlong merged 5 commits into
mainfrom
copilot/optimize-grid-list-ux

Conversation

CopilotAI commented Feb 28, 2026

Copy link
Copy Markdown
Contributor

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, DateTimeCellRenderer now use browser locale (undefined) instead of hardcoded 'en-US', so dates render as localized human-readable strings automatically.

Row interaction

  • Hover expand button changed from icon-only <Expand> to "Open >" text + <ChevronRight> — matches Airtable's row-open affordance.
  • New singleClickEdit prop on DataTableSchema / ObjectGridSchema — ObjectGrid defaults to true, so cells enter edit mode on single click by default (matching Airtable behavior). Can be set to false to restore double-click editing.

Density & headers

  • ObjectGrid default rowHeight changed from 'medium' to 'compact' (32px rows).
  • ListView default density changed from 'comfortable' to 'compact', matching ObjectGrid. Density toggle highlight updated accordingly.
  • Column header labels: text-[11px] font-semibold uppercase tracking-widertext-xs font-normal text-muted-foreground.

Selection

  • New selectionStyle: 'always' | 'hover' on DataTableSchema'hover' fades checkboxes in only on row hover.

Filters

  • Filter badges use rounded-full for pill/chip appearance (was rounded-md).

Column sizing

  • Auto column width estimation from header length and data content (samples ≤50 rows, clamps 80–400px). Applied as fallback when no explicit width is set.

Empty state

  • Empty tables render 3 ghost skeleton rows (extracted as GHOST_ROW_COUNT constant + ghostCellWidth() helper) to maintain visual table structure.

Type changes

// DataTableSchema additions
singleClickEdit?: boolean;// click-to-edit instead of double-click
selectionStyle?: 'always'|'hover';// checkbox visibility mode// ObjectGridSchema
singleClickEdit?: boolean;// default: true (click-to-edit)
rowHeight default: 'compact'// was 'medium'
Original prompt

This section details on the original issue you should resolve

<issue_title>全面对标 Airtable 的表格(Grid/List)UX 优化任务</issue_title>
<issue_description>## 目标
对比当前 ObjectUI 的表格组件(Grid/List/ObjectGrid)与 Airtable 的体验,综合落地以下优化,以实现用户感知上的高一致度。所有子项可单独提交 PR。


1. 日期字段人性化格式默认展示

  • 将所有日期型列的值从 ISO 格式(如 2024-01-15T00:00:00.000Z)自动格式化为本地化、人类可读格式(如 2024/2/28 12:57am),优先跟随浏览器 locale。
  • 涉及:cell renderer、Grid/ListView/fields 层的 formatDate 自动调用。

2. 行悬浮"Open >"按钮/行为规范

  • Grid 和 ListView 统一:鼠标悬浮于首列或整行时,出现 "Open >" 文字按钮(含展开 icon),点击直达详情页(record detail)。
  • 优化现有 row-expand-button,去除仅 icon/tooltip 的做法,改用文字按钮。

3. 单击单元格即进入编辑状态(单击编辑)

  • grid cell 默认支持 "点击即编辑"(editMode: 'click'),双击可作为快捷保存。
  • 相关属性:singleClickEdit、editMode,新建选项,默认值为 click。
  • InlineEditing 组件兼容此交互。

4. 默认紧凑行高/密度模式

  • Grid 和 List 行高默认32-36px(densityMode: 'compact')。
  • 工具栏保留密度切换,但默认使用紧凑。

5. 列头样式极简化

  • 表头背景统一为浅灰(bg-muted/30),去除冗余分割线/色块。
  • 列名用 font-normal text-muted-foreground,排序箭头与文本内联。
  • 字段类型图标(如A,#,📅)全部启用。

6. 搜索框移至工具栏

  • 搜索框与筛选、分组按钮并列在 toolbar 区域,而不用嵌入表头或首行。

7. 筛选器 pill/chip 样式美化

  • 快速筛选条件区块采用更轻量化、Airtable 风格的 pill/chip,支持多条件并列、x移除。

8. 列宽自适应内容

  • 启用自动列宽(autoSizeColumns),智能为文本/数值类等分配最优宽度。

9. 行选择 Checkbox 交互样式

  • 新增 selectionStyle: 'hover'|'always',支持始终显示 checkbox,适配批量操作。

10. 空表格 ghost row 占位

  • 无数据时,底部以半透明 ghost row 显示示例数据轮廓,占位整体表格高度。

  • 工具栏支持 dock/浮动切换
  • Dashboard/table 编辑右侧联动配置 panel 交互再精细

对应参考图:
ObjectUI演示截图
Airtable截图
</issue_description>

Comments on the Issue (you are @copilot in this section)


💡 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.

@vercel

vercelBot commented Feb 28, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
objectuiReadyReadyPreview, CommentFeb 28, 2026 7:37am
objectui-demoReadyReadyPreview, CommentFeb 28, 2026 7:37am
objectui-storybookReadyReadyPreview, CommentFeb 28, 2026 7:37am

Request Review

- 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>
CopilotAIand others added 2 commits February 28, 2026 06:58
…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>
@hotlong

Copy link
Copy Markdown
Contributor

@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>

CopilotAI commented Feb 28, 2026

Copy link
Copy Markdown
ContributorAuthor

@copilot 仔细评估是否已经完成了任务中的所有开发要求

经过仔细审查,发现了 3 处遗漏并在 03cdb5d 中修复:

  1. ListView 默认密度 — 原来仍是 'comfortable',已改为 'compact'(与 ObjectGrid 一致)
  2. singleClickEdit 默认值 — ObjectGrid 原来默认 false,已改为 true(点击即编辑)
  3. 密度切换按钮高亮 — ListView 工具栏密度按钮高亮逻辑从对比 'comfortable' 改为对比 'compact'

所有 10 项需求现已全部完成:

#需求状态
1日期字段人性化格式formatDate/formatDateTime/DateTimeCellRenderer 使用浏览器 locale
2行悬浮 "Open >" 按钮✅ 文字按钮 + ChevronRight 图标
3单击编辑singleClickEdit 默认 true,InlineEditing 原生兼容
4默认紧凑行高✅ ObjectGrid + ListView 均默认 'compact' (32px)
5列头极简化font-normal text-muted-foreground,字段类型图标已启用
6搜索框在工具栏✅ data-table toolbar + ListView search popover
7筛选器 pill/chiprounded-full 风格
8列宽自适应✅ 基于内容自动估算 (80-400px)
9Checkbox 交互selectionStyle: 'always'|'hover'
10空表格 ghost row✅ 3 行骨架占位

所有 675 个相关测试通过。

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 singleClickEdit and selectionStyle to table/grid schema types and wire singleClickEdit through 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
FileDescription
packages/types/src/objectql.tsAdds singleClickEdit to ObjectGridSchema and updates rowHeight default doc to compact.
packages/types/src/data-display.tsAdds selectionStyle and singleClickEdit to DataTableSchema.
packages/plugin-list/src/tests/ListView.test.tsxUpdates test expectation for ListView default density (compact).
packages/plugin-list/src/UserFilters.tsxSwitches filter badges/buttons to pill styling (rounded-full).
packages/plugin-list/src/ListView.tsxChanges default density to compact and adjusts density toggle highlight logic.
packages/plugin-grid/src/ObjectGrid.tsxDefaults grid row height to compact and defaults singleClickEdit to true when building the DataTable schema.
packages/fields/src/index.tsxUses browser locale (undefined) for date/time formatting instead of hardcoded en-US.
packages/components/src/renderers/complex/data-table.tsxImplements auto column sizing fallback, ghost rows empty state, selection hover style, “Open” hover affordance, and single-click editing behavior.
ROADMAP.mdMarks 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.singleClickEdit is documented as @default false, but ObjectGrid currently passes schema.singleClickEdit ?? true down 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 (singleClickEdit click-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 via opacity: 0, but it remains focusable/clickable (tab navigation can land on an invisible control). Consider using invisible/pointer-events-none (and/or managing tabIndex/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)}

CopilotAIFeb 28, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
onClick={()=>isEditable&&singleClickEdit&&startEdit(rowIndex,col.accessorKey)}
onClick={(e)=>{
if(isEditable&&singleClickEdit){
e.stopPropagation();
startEdit(rowIndex,col.accessorKey);
}
}}

Copilot uses AI. Check for mistakes.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

全面对标 Airtable 的表格(Grid/List)UX 优化任务

3 participants

@hotlong