@@ -31,7 +31,7 @@ import {
3131 DropdownMenu , DropdownMenuContent , DropdownMenuItem , DropdownMenuTrigger ,
3232} from '@object-ui/components' ;
3333import { usePullToRefresh } from '@object-ui/mobile' ;
34- import { Edit , Trash2 , MoreVertical , ChevronRight , ChevronDown , Download , Rows3 , Rows4 , AlignJustify } from 'lucide-react' ;
34+ import { Edit , Trash2 , MoreVertical , ChevronRight , ChevronDown , Download , Rows3 , Rows4 , AlignJustify , Type , Hash , Calendar , CheckSquare , User , Tag , Clock } from 'lucide-react' ;
3535import { useRowColor } from './useRowColor' ;
3636import { useGroupedData } from './useGroupedData' ;
3737
@@ -47,6 +47,7 @@ export interface ObjectGridProps {
4747 onRowSave ?: ( rowIndex : number , changes : Record < string , any > , row : any ) => void | Promise < void > ;
4848 onBatchSave ?: ( changes : Array < { rowIndex : number ; changes : Record < string , any > ; row : any } > ) => void | Promise < void > ;
4949 onRowSelect ?: ( selectedRows : any [ ] ) => void ;
50+ onAddRecord ?: ( ) => void ;
5051}
5152
5253/**
@@ -115,6 +116,7 @@ export const ObjectGrid: React.FC<ObjectGridProps> = ({
115116 onCellChange,
116117 onRowSave,
117118 onBatchSave,
119+ onAddRecord,
118120 ...rest
119121} ) => {
120122 const [ data , setData ] = useState < any [ ] > ( [ ] ) ;
@@ -342,6 +344,23 @@ export const ObjectGrid: React.FC<ObjectGridProps> = ({
342344 const { groups, isGrouped, toggleGroup } = useGroupedData ( schema . grouping , data ) ;
343345
344346 const generateColumns = useCallback ( ( ) => {
347+ // Map field type to column header icon (Airtable-style)
348+ const getTypeIcon = ( fieldType : string | null ) : React . ReactNode => {
349+ if ( ! fieldType ) return < Type className = "h-3.5 w-3.5" /> ;
350+ const iconMap : Record < string , React . ReactNode > = {
351+ text : < Type className = "h-3.5 w-3.5" /> ,
352+ number : < Hash className = "h-3.5 w-3.5" /> ,
353+ currency : < Hash className = "h-3.5 w-3.5" /> ,
354+ percent : < Hash className = "h-3.5 w-3.5" /> ,
355+ date : < Calendar className = "h-3.5 w-3.5" /> ,
356+ datetime : < Clock className = "h-3.5 w-3.5" /> ,
357+ boolean : < CheckSquare className = "h-3.5 w-3.5" /> ,
358+ user : < User className = "h-3.5 w-3.5" /> ,
359+ select : < Tag className = "h-3.5 w-3.5" /> ,
360+ } ;
361+ return iconMap [ fieldType ] || < Type className = "h-3.5 w-3.5" /> ;
362+ } ;
363+
345364 // Auto-infer column type from field name and data values (Airtable-style)
346365 const inferColumnType = ( col : ListColumn ) : string | null => {
347366 if ( col . type ) return col . type ; // Explicit type takes priority
@@ -354,6 +373,12 @@ export const ObjectGrid: React.FC<ObjectGridProps> = ({
354373 return 'boolean' ;
355374 }
356375
376+ // Infer datetime fields (fields with time component: created_time, modified_time, *_at patterns)
377+ const datetimePatterns = [ 'created_time' , 'modified_time' , 'updated_time' , 'created_at' , 'updated_at' , 'modified_at' , 'last_login' , 'logged_at' ] ;
378+ if ( datetimePatterns . some ( p => fieldLower === p || fieldLower . endsWith ( `_${ p } ` ) ) ) {
379+ return 'datetime' ;
380+ }
381+
357382 // Infer date fields from name patterns
358383 const datePatterns = [ 'date' , 'due' , 'created' , 'updated' , 'deadline' , 'start' , 'end' , 'expires' ] ;
359384 if ( datePatterns . some ( p => fieldLower . includes ( p ) ) ) {
@@ -489,6 +514,27 @@ export const ObjectGrid: React.FC<ObjectGridProps> = ({
489514 ) ;
490515 }
491516
517+ // Wrap with prefix compound cell renderer (Airtable-style: [Badge] Text in same cell)
518+ const prefixConfig = ( col as any ) . prefix ;
519+ if ( prefixConfig ?. field ) {
520+ const baseCellRenderer = cellRenderer ;
521+ const PrefixRenderer = prefixConfig . type === 'badge' ? getCellRenderer ( 'select' ) : null ;
522+ cellRenderer = ( value : any , row : any ) => {
523+ const prefixValue = row [ prefixConfig . field ] ;
524+ const prefixEl = prefixValue != null && prefixValue !== ''
525+ ? PrefixRenderer
526+ ? < PrefixRenderer value = { prefixValue } field = { { name : prefixConfig . field , type : 'select' } as any } />
527+ : < span className = "text-muted-foreground text-xs mr-1.5" > { String ( prefixValue ) } </ span >
528+ : null ;
529+ return (
530+ < span className = "flex items-center gap-1.5" >
531+ { prefixEl }
532+ { baseCellRenderer ( value , row ) }
533+ </ span >
534+ ) ;
535+ } ;
536+ }
537+
492538 // Auto-infer alignment from field type if not explicitly set
493539 const numericTypes = [ 'number' , 'currency' , 'percent' ] ;
494540 const effectiveType = inferredType || col . type ;
@@ -500,6 +546,7 @@ export const ObjectGrid: React.FC<ObjectGridProps> = ({
500546 return {
501547 header,
502548 accessorKey : col . field ,
549+ headerIcon : getTypeIcon ( inferredType ) ,
503550 ...( ! isEssential && { className : 'hidden sm:table-cell' } ) ,
504551 ...( col . width && { width : col . width } ) ,
505552 ...( inferredAlign && { align : inferredAlign } ) ,
@@ -743,6 +790,8 @@ export const ObjectGrid: React.FC<ObjectGridProps> = ({
743790 ? 'px-3 py-2.5 text-sm'
744791 : 'px-3 py-1.5 text-[13px] leading-normal' ,
745792 showRowNumbers : true ,
793+ showAddRow : ! ! operations ?. create ,
794+ onAddRecord : onAddRecord ,
746795 rowClassName : schema . rowColor ? ( row : any , _idx : number ) => getRowClassName ( row ) : undefined ,
747796 frozenColumns : schema . frozenColumns ?? 1 ,
748797 onSelectionChange : onRowSelect ,
0 commit comments