99 * - ListView delegation for non-grid view types (kanban, calendar, chart, etc.)
1010 */
1111
12- import { useMemo , useState , useCallback } from 'react' ;
12+ import { useMemo , useState , useCallback , type ComponentType } from 'react' ;
1313import { useParams , useSearchParams , useNavigate } from 'react-router-dom' ;
1414import { ObjectChart } from '@object-ui/plugin-charts' ;
1515import { ListView } from '@object-ui/plugin-list' ;
@@ -19,12 +19,24 @@ import { ObjectView as PluginObjectView } from '@object-ui/plugin-view';
1919import '@object-ui/plugin-grid' ;
2020import '@object-ui/plugin-kanban' ;
2121import '@object-ui/plugin-calendar' ;
22- import { Button , Empty , EmptyTitle , EmptyDescription , Sheet , SheetContent , DropdownMenu , DropdownMenuContent , DropdownMenuItem , DropdownMenuTrigger , DropdownMenuSeparator } from '@object-ui/components' ;
23- import { Plus , Table as TableIcon , Settings2 , MoreVertical , Wrench } from 'lucide-react' ;
22+ import { cn , Button , Empty , EmptyTitle , EmptyDescription , Sheet , SheetContent , DropdownMenu , DropdownMenuContent , DropdownMenuItem , DropdownMenuTrigger , DropdownMenuSeparator } from '@object-ui/components' ;
23+ import { Plus , Table as TableIcon , Settings2 , MoreVertical , Wrench , KanbanSquare , Calendar , LayoutGrid , Activity , GanttChart , MapPin , BarChart3 } from 'lucide-react' ;
2424import type { ListViewSchema } from '@object-ui/types' ;
2525import { MetadataToggle , MetadataPanel , useMetadataInspector } from './MetadataInspector' ;
2626import { useObjectActions } from '../hooks/useObjectActions' ;
2727
28+ /** Map view types to Lucide icons (Airtable-style) */
29+ const VIEW_TYPE_ICONS : Record < string , ComponentType < { className ?: string } > > = {
30+ grid : TableIcon ,
31+ kanban : KanbanSquare ,
32+ calendar : Calendar ,
33+ gallery : LayoutGrid ,
34+ timeline : Activity ,
35+ gantt : GanttChart ,
36+ map : MapPin ,
37+ chart : BarChart3 ,
38+ } ;
39+
2840export function ObjectView ( { dataSource, objects, onEdit, onRowClick } : any ) {
2941 const navigate = useNavigate ( ) ;
3042 const { objectName, viewId } = useParams ( ) ;
@@ -273,6 +285,33 @@ export function ObjectView({ dataSource, objects, onEdit, onRowClick }: any) {
273285 </ div >
274286 </ div >
275287
288+ { /* View Tabs — Airtable-style named-view switcher */ }
289+ { views . length > 1 && (
290+ < div className = "border-b px-3 sm:px-4 bg-background overflow-x-auto shrink-0" >
291+ < div className = "flex items-center gap-0.5 -mb-px" >
292+ { views . map ( ( view : { id : string ; label : string ; type : string } ) => {
293+ const isActive = view . id === activeViewId ;
294+ const ViewIcon = VIEW_TYPE_ICONS [ view . type ] || TableIcon ;
295+ return (
296+ < button
297+ key = { view . id }
298+ onClick = { ( ) => handleViewChange ( view . id ) }
299+ className = { cn (
300+ "inline-flex items-center gap-1.5 px-3 py-2 text-sm font-medium border-b-2 transition-colors whitespace-nowrap" ,
301+ isActive
302+ ? "border-primary text-primary"
303+ : "border-transparent text-muted-foreground hover:text-foreground hover:border-border"
304+ ) }
305+ >
306+ < ViewIcon className = "h-3.5 w-3.5" />
307+ { view . label }
308+ </ button >
309+ ) ;
310+ } ) }
311+ </ div >
312+ </ div >
313+ ) }
314+
276315 { /* 2. Content — Plugin ObjectView with ViewSwitcher + Filter + Sort */ }
277316 < div className = "flex-1 overflow-hidden relative flex flex-row" >
278317 < div className = "flex-1 relative h-full" >
0 commit comments