Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
… switching, and i18n Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
|
@copilot 仔细评估确认已经完成了所有工作,并且已经集成到console |
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
经过评估发现
完整数据流: 所有验收标准现已满足:
|
There was a problem hiding this comment.
Pull request overview
This PR implements a TabBar component for ListView multi-tab view switching, addressing the requirement for rendering ViewTabSchema configurations in the UI. The implementation includes a reusable TabBar component, full ListView integration with filter switching, schema propagation through console/plugin-view layers, comprehensive test coverage, and i18n support for 10 locales.
Changes:
- New TabBar component with controlled/uncontrolled state, Lucide icon resolution, pinned tab support, order sorting, and ARIA compliance
- ListView integration that applies tab filter configurations to the data fetching layer via currentFilters state
- Schema propagation from ObjectView → plugin-view renderContent → console renderListView → ListView → TabBar
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/plugin-list/src/components/TabBar.tsx | New reusable TabBar component with controlled/uncontrolled state, icon resolution, visibility filtering, and ARIA support |
| packages/plugin-list/src/tests/TabBar.test.tsx | Comprehensive unit tests for TabBar (17 tests covering rendering, state, icons, ARIA, filters) |
| packages/plugin-list/src/ListView.tsx | Integrates TabBar component, replaces inline tab rendering, adds handleTabChange for filter application |
| packages/plugin-list/src/index.tsx | Exports TabBar component and ViewTab type for external use |
| packages/plugin-view/src/ObjectView.tsx | Propagates schema.tabs to renderContent for ListView |
| packages/plugin-view/src/tests/config-sync-integration.test.tsx | Integration test verifying tabs propagation through renderListView |
| apps/console/src/components/ObjectView.tsx | Propagates listSchema.tabs to ListView in renderListView |
| packages/i18n/src/locales/*.ts | Adds list.tabs and list.allRecords i18n keys to all 10 locale files |
| ROADMAP.md | Documents TabBar implementation in P1 spec compliance section |
| visible?: string | boolean; | ||
| } |
There was a problem hiding this comment.
Type inconsistency: The ViewTab interface defines visible?: string | boolean, but the type definition in packages/types/src/objectql.ts (lines 1675-1685) only defines visible?: string. The spec alignment comment states it should follow @objectstack/spec ListViewSchema.tabs, which uses string for the visible property (as an expression).
The TabBar component's handling of boolean false (line 51) diverges from the spec's string-only type. Either update the type in objectql.ts to match ViewTab, or remove boolean support from TabBar to maintain strict spec compliance.
| }, | ||
| [onFilterChange], | ||
| ); | ||
|
|
There was a problem hiding this comment.
Missing test coverage for the default tab filter application on mount. While TabBar tests verify that the default tab is selected (line 43-49), there are no tests verifying that when a default tab has a filter configuration, that filter is applied to the ListView on initial render and triggers data fetching with those filters.
Add an integration test in ListView.test.tsx that verifies the default tab's filter is applied on mount and affects the data fetching behavior.
| // Apply default/active tab filter on mount and when tabs change | |
| React.useEffect(() => { | |
| if (!schema.tabs || schema.tabs.length === 0) return; | |
| const activeTabConfig = | |
| schema.tabs.find(t => t.name === activeTab) ?? | |
| schema.tabs.find(t => t.isDefault) ?? | |
| schema.tabs[0]; | |
| if (!activeTabConfig) return; | |
| if (activeTabConfig.filter) { | |
| const tabFilters: FilterGroup = { | |
| id: `tab-filter-${activeTabConfig.name}`, | |
| logic: activeTabConfig.filter.logic || 'and', | |
| conditions: activeTabConfig.filter.conditions || [], | |
| }; | |
| setCurrentFilters(tabFilters); | |
| onFilterChange?.(tabFilters); | |
| } else { | |
| const emptyFilters: FilterGroup = { id: 'root', logic: 'and', conditions: [] }; | |
| setCurrentFilters(emptyFilters); | |
| onFilterChange?.(emptyFilters); | |
| } | |
| }, [activeTab, schema.tabs, onFilterChange]); |
| }, [visibleTabs]); | ||
|
|
||
| const [internalActiveTab, setInternalActiveTab] = React.useState<string | undefined>(defaultTab); | ||
|
|
There was a problem hiding this comment.
The internalActiveTab state is initialized with defaultTab (line 74), but if the tabs prop changes and a different tab becomes the default, the internal state won't update automatically. This could lead to stale state if tabs are dynamically changed.
Consider adding a useEffect to sync internalActiveTab with defaultTab when defaultTab changes, or ensure that tab changes trigger a remount of the TabBar component.
| React.useEffect(() => { | |
| setInternalActiveTab(defaultTab); | |
| }, [defaultTab]); |
| }, | ||
| [onFilterChange], | ||
| ); | ||
|
|
There was a problem hiding this comment.
The default tab's filter is not applied on initial mount. The activeTab state is initialized correctly, but the corresponding filter from that tab is never applied to currentFilters. This means that if a default tab has a filter configuration, that filter won't be active until the user clicks on the tab.
Consider adding a useEffect that applies the default tab's filter on mount, or initialize currentFilters with the default tab's filter in the useState initializer.
| // Apply default tab filter on initial mount | |
| React.useEffect(() => { | |
| // Only run if there are tabs and the filters are still at their initial empty state | |
| if (!schema.tabs || schema.tabs.length === 0) return; | |
| if (currentFilters.id !== 'root' || currentFilters.conditions.length > 0) return; | |
| const initialTab = | |
| schema.tabs.find((t) => t.name === activeTab) ?? | |
| schema.tabs.find((t) => t.isDefault) ?? | |
| schema.tabs[0]; | |
| if (!initialTab) return; | |
| if (initialTab.filter) { | |
| const tabFilters: FilterGroup = { | |
| id: `tab-filter-${initialTab.name}`, | |
| logic: initialTab.filter.logic || 'and', | |
| conditions: initialTab.filter.conditions || [], | |
| }; | |
| setCurrentFilters(tabFilters); | |
| onFilterChange?.(tabFilters); | |
| } else { | |
| const emptyFilters: FilterGroup = { id: 'root', logic: 'and', conditions: [] }; | |
| // currentFilters is already empty, but notify listeners of the initial state | |
| onFilterChange?.(emptyFilters); | |
| } | |
| }, []); |
| 'list.tabs': 'Tabs', | ||
| 'list.allRecords': 'All Records', |
There was a problem hiding this comment.
The i18n keys 'list.tabs' and 'list.allRecords' are added to all locale files and the default translations object (line 208-209), but they are never actually used in the code. The TabBar component doesn't use any i18n translation functions, and there's no code that calls t('list.tabs') or t('list.allRecords').
Either remove these unused i18n keys from all locale files, or integrate them into the TabBar component if they're intended for future use.
| 'list.tabs': 'Tabs', | |
| 'list.allRecords': 'All Records', |
ViewTabSchematypes exist but no tab UI renders. This adds aTabBarcomponent that renders above the ListView toolbar, with active tab state management and filter switching on tab change, fully integrated into the console app.New:
TabBarcomponent (packages/plugin-list/src/components/TabBar.tsx)isDefaulttabpinnedtabs always visible regardless ofvisibleflagvisible: 'false' | falsefilteringrole="tablist"/role="tab"/aria-selectedListView integration
TabBarhandleTabChangeapplies tab'sfilterconfig tocurrentFiltersstate and firesonFilterChangetab-filter-${tab.name})Console & plugin-view integration
packages/plugin-view/src/ObjectView.tsx—renderContentschema now propagatestabsfrom schema level to ListViewapps/console/src/components/ObjectView.tsx—renderListViewfullSchema now propagatestabsfrom listSchemaObjectViewSchema.tabs→ PluginObjectViewrenderContent→ ConsolerenderListView→ListView→TabBari18n
list.tabsandlist.allRecordskeys added to all 10 locale files + fallback translationsUsage
Tests
config-sync-integration.test.tsxverifying tabs propagation throughrenderListViewOriginal prompt
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.