Skip to content

Commit 50d0aed

Browse files
Copilothotlong
andcommitted
fix: remove duplicate ObjectView toolbar, keep only single Airtable-style ListView toolbar
- Removed filter/sort/search controls from ObjectView toolbar (delegated to ListView) - Removed unused imports (FilterUI, SortUI, Search, Input, RefreshCw) and searchQuery state - ObjectView toolbar now only renders named view tabs + create button when needed - Single clean Airtable toolbar row: Hide fields | Filter | Group | Sort | Color | Row height | Search Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent 5011524 commit 50d0aed

2 files changed

Lines changed: 37 additions & 142 deletions

File tree

packages/plugin-view/src/ObjectView.tsx

Lines changed: 34 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -50,15 +50,12 @@ import {
5050
DrawerTitle,
5151
DrawerDescription,
5252
Button,
53-
Input,
5453
Tabs,
5554
TabsList,
5655
TabsTrigger,
5756
} from '@object-ui/components';
58-
import { Plus, Search, RefreshCw, SlidersHorizontal, ArrowUpDown, EyeOff, Group, Paintbrush, Ruler, X } from 'lucide-react';
57+
import { Plus } from 'lucide-react';
5958
import { ViewSwitcher } from './ViewSwitcher';
60-
import { FilterUI } from './FilterUI';
61-
import { SortUI } from './SortUI';
6259

6360
/**
6461
* Attempt to import SchemaRenderer from @object-ui/react.
@@ -211,8 +208,6 @@ export const ObjectView: React.FC<ObjectViewProps> = ({
211208
const [isFormOpen, setIsFormOpen] = useState(false);
212209
const [formMode, setFormMode] = useState<FormMode>('create');
213210
const [selectedRecord, setSelectedRecord] = useState<Record<string, unknown> | null>(null);
214-
const [searchQuery, setSearchQuery] = useState('');
215-
const [searchExpanded, setSearchExpanded] = useState(false);
216211
const [refreshKey, setRefreshKey] = useState(0);
217212

218213
// Data fetching state for non-grid views
@@ -853,146 +848,46 @@ export const ObjectView: React.FC<ObjectViewProps> = ({
853848
);
854849
};
855850

856-
// Render toolbar
851+
// Render toolbar — only named view tabs; filter/sort/search is handled by ListView
857852
const renderToolbar = () => {
858-
const showSearchBox = schema.showSearch !== false;
859853
const showCreateButton = schema.showCreate !== false && operations.create !== false;
860-
const showRefreshButton = schema.showRefresh !== false;
861854
const showViewSwitcherToggle = schema.showViewSwitcher === true; // Changed: default to false (hidden)
862855

863-
return (
864-
<div className="flex flex-col">
865-
{/* Row 1: View tabs + ViewSwitcher */}
866-
<div className="flex items-center justify-between border-b px-4 py-1">
867-
<div className="flex items-center gap-2">
868-
{renderNamedViewTabs()}
869-
</div>
870-
{showViewSwitcherToggle && viewSwitcherSchema && (
871-
<ViewSwitcher
872-
schema={viewSwitcherSchema}
873-
onViewChange={handleViewTypeChange}
874-
className="overflow-x-auto"
875-
/>
876-
)}
877-
</div>
856+
const namedViewTabs = renderNamedViewTabs();
878857

879-
{/* Row 2: Tool buttons (Airtable-style) */}
880-
<div className="flex items-center justify-between border-b px-4 py-1 gap-2">
881-
{/* Left: Tool buttons */}
882-
<div className="flex items-center gap-0.5 overflow-hidden">
883-
{/* Hide Fields */}
884-
<Button
885-
variant="ghost"
886-
size="sm"
887-
className="h-7 px-2 text-muted-foreground hover:text-primary text-xs"
888-
disabled
889-
>
890-
<EyeOff className="h-3.5 w-3.5 mr-1.5" />
891-
<span className="hidden sm:inline">Hide fields</span>
892-
</Button>
893-
894-
{/* Filter */}
895-
{filterSchema && (
896-
<FilterUI schema={filterSchema} onChange={setFilterValues} />
897-
)}
898-
899-
{/* Group */}
900-
<Button
901-
variant="ghost"
902-
size="sm"
903-
className="h-7 px-2 text-muted-foreground hover:text-primary text-xs"
904-
disabled
905-
>
906-
<Group className="h-3.5 w-3.5 mr-1.5" />
907-
<span className="hidden sm:inline">Group</span>
908-
</Button>
909-
910-
{/* Sort */}
911-
{sortSchema && (
912-
<SortUI schema={sortSchema} onChange={(sort) => setSortConfig(sort ?? [])} />
913-
)}
914-
915-
{/* Color */}
916-
<Button
917-
variant="ghost"
918-
size="sm"
919-
className="h-7 px-2 text-muted-foreground hover:text-primary text-xs"
920-
disabled
921-
>
922-
<Paintbrush className="h-3.5 w-3.5 mr-1.5" />
923-
<span className="hidden sm:inline">Color</span>
924-
</Button>
925-
926-
{/* Row Height */}
927-
<Button
928-
variant="ghost"
929-
size="sm"
930-
className="h-7 px-2 text-muted-foreground hover:text-primary text-xs hidden lg:flex"
931-
disabled
932-
>
933-
<Ruler className="h-3.5 w-3.5 mr-1.5" />
934-
<span className="hidden sm:inline">Row height</span>
935-
</Button>
936-
</div>
858+
// Hide toolbar entirely if there is nothing to show
859+
if (!namedViewTabs && !showViewSwitcherToggle && !showCreateButton && !toolbarAddon) return null;
937860

938-
{/* Right: Search + Actions */}
939-
<div className="flex items-center gap-1">
940-
{showSearchBox && (
941-
<>
942-
{searchExpanded ? (
943-
<div className="relative w-48 lg:w-64">
944-
<Search className="absolute left-2 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground" />
945-
<Input
946-
type="search"
947-
placeholder={`Search ${(objectSchema?.label as string) || schema.objectName}...`}
948-
value={searchQuery}
949-
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setSearchQuery(e.target.value)}
950-
className="pl-7 h-7 text-xs"
951-
autoFocus
952-
onBlur={() => {
953-
if (!searchQuery) setSearchExpanded(false);
954-
}}
955-
/>
956-
<Button
957-
variant="ghost"
958-
size="sm"
959-
className="absolute right-0.5 top-1/2 -translate-y-1/2 h-5 w-5 p-0 hover:bg-muted-foreground/20"
960-
onClick={() => {
961-
setSearchQuery('');
962-
setSearchExpanded(false);
963-
}}
964-
>
965-
<X className="h-3 w-3" />
966-
</Button>
967-
</div>
968-
) : (
969-
<Button
970-
variant="ghost"
971-
size="sm"
972-
className="h-7 px-2 text-muted-foreground hover:text-primary text-xs"
973-
onClick={() => setSearchExpanded(true)}
974-
>
975-
<Search className="h-3.5 w-3.5 mr-1.5" />
976-
<span className="hidden sm:inline">Search</span>
977-
</Button>
978-
)}
979-
</>
980-
)}
981-
982-
{showRefreshButton && (
983-
<Button variant="ghost" size="sm" className="h-7 px-2 text-muted-foreground hover:text-primary" onClick={handleRefresh}>
984-
<RefreshCw className="h-3.5 w-3.5" />
985-
</Button>
986-
)}
987-
{toolbarAddon}
988-
{showCreateButton && (
989-
<Button size="sm" className="h-7 text-xs" onClick={handleCreate}>
990-
<Plus className="h-3.5 w-3.5 mr-1" />
991-
Create
992-
</Button>
993-
)}
861+
return (
862+
<div className="flex flex-col gap-3">
863+
{/* Named view tabs (if any) */}
864+
{namedViewTabs}
865+
866+
{/* ViewSwitcher + action buttons row */}
867+
{(showViewSwitcherToggle || showCreateButton || toolbarAddon) && (
868+
<div className="flex items-center justify-between gap-4">
869+
<div className="flex items-center gap-2">
870+
{showViewSwitcherToggle && viewSwitcherSchema && (
871+
<ViewSwitcher
872+
schema={viewSwitcherSchema}
873+
onViewChange={handleViewTypeChange}
874+
className="overflow-x-auto"
875+
/>
876+
)}
877+
</div>
878+
879+
{/* Right side: Actions */}
880+
<div className="flex items-center gap-2">
881+
{toolbarAddon}
882+
{showCreateButton && (
883+
<Button size="sm" onClick={handleCreate}>
884+
<Plus className="h-4 w-4" />
885+
Create
886+
</Button>
887+
)}
888+
</div>
994889
</div>
995-
</div>
890+
)}
996891
</div>
997892
);
998893
};

packages/plugin-view/src/__tests__/ObjectView.test.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,18 +104,18 @@ describe('ObjectView', () => {
104104
expect(screen.getByText('Manage your contacts')).toBeDefined();
105105
});
106106

107-
it('should render search button by default', () => {
107+
it('should not render search box (delegated to ListView toolbar)', () => {
108108
const schema: ObjectViewSchema = {
109109
type: 'object-view',
110110
objectName: 'contacts',
111111
};
112112

113113
render(<ObjectView schema={schema} dataSource={mockDataSource} />);
114114

115-
expect(screen.getByRole('button', { name: /search/i })).toBeDefined();
115+
expect(screen.queryByPlaceholderText(/search/i)).toBeNull();
116116
});
117117

118-
it('should hide search box when showSearch is false', () => {
118+
it('should not render search box when showSearch is false', () => {
119119
const schema: ObjectViewSchema = {
120120
type: 'object-view',
121121
objectName: 'contacts',

0 commit comments

Comments
 (0)