Skip to content

feat: ViewTabBar — context menu, overflow, indicators, inline rename, save-as-view - #628

Merged
hotlong merged 3 commits into
mainfrom
copilot/optimize-view-switching-management
Feb 19, 2026
Merged

feat: ViewTabBar — context menu, overflow, indicators, inline rename, save-as-view#628
hotlong merged 3 commits into
mainfrom
copilot/optimize-view-switching-management

Conversation

Copilot AI commented Feb 19, 2026

Copy link
Copy Markdown
Contributor

The view tab bar lacked standard management UX found in Airtable/Salesforce: no inline add button, no right-click menu, no overflow handling, no filter indicators, no save-as-view flow.

New: ViewTabBar component (packages/plugin-view/src/ViewTabBar.tsx)

Extracted from console's inline tab rendering into a reusable, schema-configurable component:

  • Inline "+" Add View — always-visible button at tab bar end
  • Right-click context menu — Rename, Duplicate, Share, Set as Default, Delete (via Radix ContextMenu)
  • Tab overflow — tabs beyond maxVisibleTabs (default 6) collapse into a "More" dropdown
  • Filter/sort indicator — primary-colored dot badge on tabs with active filters or sort
  • Save as View — amber indicator with Save/Reset buttons when user has unsaved filter/sort state
  • Double-click inline rename — Enter commits, Escape cancels

Types (@object-ui/types)

export interface ViewTabBarConfig {
  showAddButton?: boolean;      // default: true
  inlineRename?: boolean;       // default: true
  contextMenu?: boolean;        // default: true
  reorderable?: boolean;        // default: false
  maxVisibleTabs?: number;      // default: 6
  showIndicators?: boolean;     // default: true
  showSaveAsView?: boolean;     // default: true
}

Added viewTabBar?: ViewTabBarConfig to ObjectViewSchema.

Console integration

apps/console/src/components/ObjectView.tsx now uses <ViewTabBar> with all action callbacks wired (add navigates to view designer; rename/duplicate/delete/share log for future backend integration).

Tests

26 new tests covering rendering, overflow, indicators, context menu, save-as-view, and inline rename. All 130 plugin-view tests pass.

Original prompt

This section details on the original issue you should resolve

<issue_title>优化视图切换��视图管理:对标主流低代码平台的管理体验</issue_title>
<issue_description>### 背景
目前 ObjectUI 已支持网格、看板、日历等多种视图,采用了类似 Airtable 的顶部标签切换。但相比微软 Power Apps、Salesforce Lightning、Airtable 等主流低代码平台,现有界面在"新增、切换、修改、管理"视图的便捷性和丰富性上还有提升空间。

主要问题/差距

  • "新增视图"入口不够直观(多为下拉菜单)
  • 视图重命名、删除、复制流程不够流畅
  • 无法快速保存当前过滤/排序为新视图(Save as View)
  • 视图标签无右键菜单,操作入口分散
  • 视图标签不可拖动排序,无溢出下拉(More)
  • 无过滤/排序等指示器
  • 个性化视图和共用视图区分不明显

参考竞品

  • Salesforce Lightning:支持标签右键菜单、拖拽排序、Pin View、表头指示
  • Microsoft Power Apps、Airtable、Zoho Creator 等:新增视图按钮直观,快速保存当前过滤,支持私有与共享

推荐优化点(建议分阶段实施,优先高频痛点)

第一阶段(快速迭代,可一周内完成)

  1. 视图标签栏直接加"+新建"按钮,取消下拉入口
  2. 标签支持右键菜单(重命名、复制、删除、设为默认、分享)
  3. 标签溢出时自动收敛为"更多"下拉(More...)
  4. 有过滤/排序指示小圆点
  5. 工具栏增加"保存为新视图"(Save as View)按钮,支持从当前过滤/排序直接存为新视图

第二阶段(增强易用性)

  1. 支持视图标签拖拽排序
  2. 支持标签双击直接重命名
  3. 个性/共享视图专区分组与筛选
  4. 支持 Pin 常用视图
  5. 视图类型切换 palette/一键切版

技术方案建议

  • 将视图 Tab 渲染部分提取为独立组件,并向 schema 增加视图管理相关配置项(详见 code scan 建议)
  • 利用现有 useViewSharing hook 丰富 UI 入口
  • 实现 ContextMenu/Overflow/Indicator 通用能力,兼容移动端
  • 校验所有主流整合场景的可测性

Roadmap 建议

  • 第一阶段建议按上述顺序拆分子任务实现(可在一周内完成主体验证)
  • 实现后需完善测试用例,并同步补充文档/Storybook/教程

参考实现和竞品 UX 分析详见本 issue 评论区/文档。欢迎评��细化补充。

主界面任务视图示例

相关资料

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

@hotlong

📊 Current State Analysis

ObjectUI already has a solid view management foundation:

✅ What's Already Built

Component Location Capability
ViewSwitcher (plugin-view) packages/plugin-view/src/ViewSwitcher.tsx Tabs / Buttons / Dropdown switching between view types
ViewSwitcher (plugin-list) packages/plugin-list/src/ViewSwitcher.tsx Grid/Kanban/Gallery/Calendar/Timeline/Gantt/Map
ObjectView (plugin-view) packages/plugin-view/src/ObjectView.tsx Unified CRUD view with multi-view, named views, navigation
ObjectView (console) apps/console/src/components/ObjectView.tsx Airtable-style named-view tabs, URL routing
ViewDesigner packages/plugin-designer/src/ViewDesigner.tsx Create/edit view configs (columns, filters, sort, type)
useViewSharing packages/react/src/hooks/useViewSharing.ts Save/delete/duplicate/share views with visibility
View Switching Tests packages/react/src/__tests__/view-switching.test.tsx State preservation (selection, filters, scroll, sort)

Current UX Flow (from Screenshot)

Your demo shows an Airtable-inspired tab bar: All Tasks | Board | Calendar | Active with a "进入设计模式" (Enter Design Mode) button. The toolbar has Hide fields | Filter | Group | Sort | Color | Comfortable controls.


🔍 Gap Analysis: ObjectUI vs. Mainstream Platforms

Comparison Matrix

Capability Salesforce Power Apps Airtable Zoho ObjectUI Gap
Named views (pre-configured)
View type switching (Grid↔Board↔Calendar)
Inline view creation (one-click) ⚠️ ⚠️ Gap A
View rename inline Gap B
View duplication ✅ (hook only) Gap C
View delete with confirmation ⚠️ (designer only) Gap D
Drag-reorder view tabs Gap E
View context menu (right-click) Gap F
Personal vs. shared views ✅ (hook exists) Gap G
Pin/favorite views Gap H
View filter indicator (badge) Gap I
"Save as new view" from current filters ⚠️ (no UI) Gap J
View overflow (many views → "More" dropdown) Gap K

🎯 Recommended Improvements (Prioritized)

Phase 1: Quick Wins — View Tab Management UX (1-2 weeks)

These close the biggest UX gaps with the least effort:

1A. Inline "Add View" Button on Tab Bar

Currently the "Add View" is buried in a dropdown menu. Salesforce and Airtable show a + button directly on the tab bar.

// Proposed enhancement to ObjectViewSchema
interface ObjectViewSchema {
  // ... existing props
  
  /**
   * View tab bar configuration
   * Controls inline view management UX
   */
  viewTabBar?: {
    /** Show inline "+" button to create new views */
    showAddButton?: boolean;       // default: true
    /** Allow inline renam...

</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

- Fixes objectstack-ai/objectui#627

<!-- START COPILOT CODING AGENT TIPS -->
---

💡 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](https://gh.io/copilot-coding-agent-tips) in the docs.

@vercel

vercel Bot commented Feb 19, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
objectui Error Error Feb 19, 2026 0:25am
objectui-demo Error Error Feb 19, 2026 0:25am
objectui-storybook Error Error Feb 19, 2026 0:25am

Request Review

…s, and save-as-view

- Add ViewTabBarConfig type to @object-ui/types
- Create reusable ViewTabBar component in packages/plugin-view
- Integrate ViewTabBar into console ObjectView
- Add 26 tests covering all Phase 1 features
- All 130 plugin-view tests pass

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Copilot AI changed the title [WIP] Improve view switching and management experience feat: ViewTabBar — context menu, overflow, indicators, inline rename, save-as-view Feb 19, 2026
Copilot AI requested a review from hotlong February 19, 2026 12:27
@hotlong
hotlong marked this pull request as ready for review February 19, 2026 12:39
Copilot AI review requested due to automatic review settings February 19, 2026 12:40
@hotlong
hotlong merged commit 2f5d9a6 into main Feb 19, 2026
5 of 8 checks passed

Copilot AI 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

Adds a reusable, schema-configurable ViewTabBar to standardize “Airtable/Salesforce-style” view management UX across the ecosystem, and wires it into the console ObjectView.

Changes:

  • Introduces ViewTabBar (tabs, overflow, indicators, context menu, inline rename, save-as-view indicator) in @object-ui/plugin-view with new test coverage.
  • Extends ObjectViewSchema in @object-ui/types with viewTabBar?: ViewTabBarConfig and exports the new type.
  • Updates console ObjectView to use ViewTabBar instead of inline tab rendering, and documents the milestone in ROADMAP.md.

Reviewed changes

Copilot reviewed 7 out of 8 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
pnpm-lock.yaml Lockfile updated, but hotcrm workspace importers were removed.
packages/types/src/objectql.ts Adds ViewTabBarConfig and ObjectViewSchema.viewTabBar.
packages/types/src/index.ts Re-exports ViewTabBarConfig.
packages/plugin-view/src/index.tsx Exports ViewTabBar + related types.
packages/plugin-view/src/ViewTabBar.tsx New reusable ViewTabBar component implementation.
packages/plugin-view/src/tests/ViewTabBar.test.tsx New unit tests for ViewTabBar behavior.
apps/console/src/components/ObjectView.tsx Replaces inline tabs with ViewTabBar integration.
ROADMAP.md Adds completed checklist for View Tab Management UX milestone.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

Comment thread pnpm-lock.yaml
Comment on lines 512 to 578
examples/crm:
dependencies:
'@hono/node-server':
specifier: ^1.19.9
version: 1.19.9(hono@4.11.9)
'@objectstack/core':
specifier: ^3.0.7
version: 3.0.7(pino@8.21.0)
'@objectstack/driver-memory':
specifier: ^3.0.7
version: 3.0.7(pino@8.21.0)
'@objectstack/objectql':
specifier: ^3.0.7
version: 3.0.7(pino@8.21.0)
'@objectstack/plugin-auth':
specifier: ^3.0.7
version: 3.0.7(next@16.1.6(@babel/core@7.29.0)(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(pino@8.21.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vitest@4.0.18)
'@objectstack/plugin-hono-server':
specifier: ^3.0.7
version: 3.0.7(pino@8.21.0)
'@objectstack/runtime':
specifier: ^3.0.7
version: 3.0.7(pino@8.21.0)
'@objectstack/spec':
specifier: ^3.0.7
version: 3.0.7
hono:
specifier: ^4.11.9
version: 4.11.9
pino:
specifier: ^8.21.0
version: 8.21.0
pino-pretty:
specifier: ^13.1.3
version: 13.1.3
devDependencies:
'@objectstack/cli':
specifier: ^3.0.7
version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0)
tsx:
specifier: ^4.21.0
version: 4.21.0
typescript:
specifier: ^5.9.3
version: 5.9.3

examples/hello-world:
dependencies:
'@object-ui/components':
specifier: workspace:*
version: link:../../packages/components
'@object-ui/core':
specifier: workspace:*
version: link:../../packages/core
'@object-ui/react':
specifier: workspace:*
version: link:../../packages/react
react:
specifier: 19.2.4
version: 19.2.4
react-dom:
specifier: 19.2.4
version: 19.2.4(react@19.2.4)

examples/hotcrm/packages/ai:
dependencies:
'@objectstack/spec':
specifier: ^3.0.3
version: 3.0.7
axios:
specifier: ^1.13.5
version: 1.13.5
zod:
specifier: ^4.3.6
version: 4.3.6
devDependencies:
'@objectstack/cli':
specifier: ^3.0.3
version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0)
typescript:
specifier: ^5.9.3
version: 5.9.3

examples/hotcrm/packages/analytics:
dependencies:
'@hotcrm/ai':
specifier: workspace:*
version: link:../ai
'@objectstack/spec':
specifier: ^3.0.3
version: 3.0.7
zod:
specifier: ^4.3.6
version: 4.3.6
devDependencies:
'@objectstack/cli':
specifier: ^3.0.3
version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0)
typescript:
specifier: ^5.9.3
version: 5.9.3

examples/hotcrm/packages/community:
dependencies:
'@objectstack/spec':
specifier: ^3.0.3
version: 3.0.7
zod:
specifier: ^4.3.6
version: 4.3.6
devDependencies:
'@objectstack/cli':
specifier: ^3.0.3
version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0)
typescript:
specifier: ^5.9.3
version: 5.9.3

examples/hotcrm/packages/core:
dependencies:
'@objectstack/spec':
specifier: ^3.0.2
version: 3.0.7
zod:
specifier: ^4.3.6
version: 4.3.6

examples/hotcrm/packages/crm:
dependencies:
'@hotcrm/ai':
specifier: workspace:*
version: link:../ai
'@objectstack/spec':
specifier: ^3.0.3
version: 3.0.7
zod:
specifier: ^4.3.6
version: 4.3.6
devDependencies:
'@objectstack/cli':
specifier: ^3.0.3
version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0)
typescript:
specifier: ^5.9.3
version: 5.9.3

examples/hotcrm/packages/education:
dependencies:
'@objectstack/spec':
specifier: ^3.0.3
version: 3.0.7
zod:
specifier: ^4.3.6
version: 4.3.6
devDependencies:
'@objectstack/cli':
specifier: ^3.0.3
version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0)
typescript:
specifier: ^5.9.3
version: 5.9.3

examples/hotcrm/packages/finance:
dependencies:
'@objectstack/spec':
specifier: ^3.0.3
version: 3.0.7
devDependencies:
'@objectstack/cli':
specifier: ^3.0.3
version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0)
typescript:
specifier: ^5.9.3
version: 5.9.3

examples/hotcrm/packages/financial-services:
dependencies:
'@objectstack/spec':
specifier: ^3.0.3
version: 3.0.7
zod:
specifier: ^4.3.6
version: 4.3.6
devDependencies:
'@objectstack/cli':
specifier: ^3.0.3
version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0)
typescript:
specifier: ^5.9.3
version: 5.9.3

examples/hotcrm/packages/healthcare:
dependencies:
'@objectstack/spec':
specifier: ^3.0.3
version: 3.0.7
zod:
specifier: ^4.3.6
version: 4.3.6
devDependencies:
'@objectstack/cli':
specifier: ^3.0.3
version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0)
typescript:
specifier: ^5.9.3
version: 5.9.3

examples/hotcrm/packages/hr:
dependencies:
'@objectstack/spec':
specifier: ^3.0.3
version: 3.0.7
devDependencies:
'@objectstack/cli':
specifier: ^3.0.3
version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0)
typescript:
specifier: ^5.9.3
version: 5.9.3

examples/hotcrm/packages/integration:
dependencies:
'@hotcrm/ai':
specifier: workspace:*
version: link:../ai
'@objectstack/spec':
specifier: ^3.0.3
version: 3.0.7
zod:
specifier: ^4.3.6
version: 4.3.6
devDependencies:
'@objectstack/cli':
specifier: ^3.0.3
version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0)
typescript:
specifier: ^5.9.3
version: 5.9.3

examples/hotcrm/packages/marketing:
dependencies:
'@objectstack/spec':
specifier: ^3.0.3
version: 3.0.7
devDependencies:
'@objectstack/cli':
specifier: ^3.0.3
version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0)
typescript:
specifier: ^5.9.3
version: 5.9.3

examples/hotcrm/packages/products:
dependencies:
'@objectstack/spec':
specifier: ^3.0.3
version: 3.0.7
devDependencies:
'@objectstack/cli':
specifier: ^3.0.3
version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0)
typescript:
specifier: ^5.9.3
version: 5.9.3

examples/hotcrm/packages/real-estate:
dependencies:
'@objectstack/spec':
specifier: ^3.0.3
version: 3.0.7
zod:
specifier: ^4.3.6
version: 4.3.6
devDependencies:
'@objectstack/cli':
specifier: ^3.0.3
version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0)
typescript:
specifier: ^5.9.3
version: 5.9.3

examples/hotcrm/packages/server:
dependencies:
'@objectstack/cli':
specifier: ^3.0.2
version: 3.0.7(@objectstack/core@3.0.7(pino@10.3.1))(esbuild@0.27.3)(pino@10.3.1)
'@objectstack/core':
specifier: ^3.0.2
version: 3.0.7(pino@10.3.1)
'@objectstack/metadata':
specifier: ^3.0.2
version: 3.0.7(pino@10.3.1)
'@objectstack/objectql':
specifier: ^3.0.2
version: 3.0.7(pino@10.3.1)
'@objectstack/plugin-hono-server':
specifier: ^3.0.2
version: 3.0.7(pino@10.3.1)
'@objectstack/runtime':
specifier: ^3.0.2
version: 3.0.7(pino@10.3.1)
'@objectstack/spec':
specifier: ^3.0.2
version: 3.0.7
pino:
specifier: ^10.3.1
version: 10.3.1
devDependencies:
'@types/js-yaml':
specifier: ^4.0.9
version: 4.0.9
ts-node:
specifier: ^10.9.2
version: 10.9.2(@swc/core@1.15.11)(@types/node@25.2.3)(typescript@5.9.3)
typescript:
specifier: ^5.9.3
version: 5.9.3

examples/hotcrm/packages/support:
dependencies:
'@objectstack/spec':
specifier: ^3.0.3
version: 3.0.7
devDependencies:
'@objectstack/cli':
specifier: ^3.0.3
version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0)
typescript:
specifier: ^5.9.3
version: 5.9.3

examples/kitchen-sink:
dependencies:
'@objectstack/spec':

Copilot AI Feb 19, 2026

Copy link

Choose a reason for hiding this comment

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

pnpm-workspace.yaml includes examples/hotcrm/packages/*, but the lockfile no longer has any examples/hotcrm/packages/... importers. This will typically break pnpm install --frozen-lockfile in CI/Vercel when the hotcrm submodule is checked out. Regenerate pnpm-lock.yaml with git submodule update --init examples/hotcrm and a fresh pnpm install --no-frozen-lockfile so all hotcrm importers are present again.

Copilot uses AI. Check for mistakes.
Comment on lines +231 to +238
{onRenameView && (
<ContextMenuItem
data-testid={`context-menu-rename-${view.id}`}
onClick={() => startRename(view.id)}
>
<Pencil className="h-4 w-4 mr-2" /> Rename
</ContextMenuItem>
)}

Copilot AI Feb 19, 2026

Copy link

Choose a reason for hiding this comment

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

The Rename context-menu item is rendered whenever onRenameView is provided, but clicking it calls startRename(), which is a no-op when config.inlineRename is false. This leaves a visible menu item that does nothing. Either hide the menu item when inlineRename is false, or allow context-menu rename even when inline rename is disabled.

Copilot uses AI. Check for mistakes.
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,

Copilot AI Feb 19, 2026

Copy link

Choose a reason for hiding this comment

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

DropdownMenuSeparator is imported from @object-ui/components but never used, which will trigger @typescript-eslint/no-unused-vars warnings. Remove the unused import or use it in the overflow dropdown if intended.

Suggested change
DropdownMenuSeparator,

Copilot uses AI. Check for mistakes.
Comment on lines +370 to +371
hasActiveFilters: Array.isArray((view as any).filter) && (view as any).filter.length > 0,
hasActiveSort: Array.isArray((view as any).sort) && (view as any).sort.length > 0,

Copilot AI Feb 19, 2026

Copy link

Choose a reason for hiding this comment

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

The views.map(...) callback is already typed with optional filter/sort, but the code still casts to any to read them. Using view.filter / view.sort directly would keep this type-safe and avoid unnecessary any casts.

Suggested change
hasActiveFilters: Array.isArray((view as any).filter) && (view as any).filter.length > 0,
hasActiveSort: Array.isArray((view as any).sort) && (view as any).sort.length > 0,
hasActiveFilters: Array.isArray(view.filter) && view.filter.length > 0,
hasActiveSort: Array.isArray(view.sort) && view.sort.length > 0,

Copilot uses AI. Check for mistakes.
Sign up for free to 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.

3 participants