@@ -28,14 +28,15 @@ import { getCellRenderer, formatCurrency, formatCompactCurrency, formatDate, for
2828import {
2929 Badge , Button , NavigationOverlay ,
3030 Popover , PopoverContent , PopoverTrigger ,
31- DropdownMenu , DropdownMenuContent , DropdownMenuItem , DropdownMenuTrigger ,
3231} from '@object-ui/components' ;
3332import { usePullToRefresh } from '@object-ui/mobile' ;
34- import { Edit , Trash2 , MoreVertical , ChevronRight , ChevronDown , Download , Rows2 , Rows3 , Rows4 , AlignJustify , Type , Hash , Calendar , CheckSquare , User , Tag , Clock } from 'lucide-react' ;
33+ import { ChevronRight , ChevronDown , Download , Rows2 , Rows3 , Rows4 , AlignJustify , Type , Hash , Calendar , CheckSquare , User , Tag , Clock } from 'lucide-react' ;
3534import { useRowColor } from './useRowColor' ;
3635import { useGroupedData } from './useGroupedData' ;
3736import { GroupRow } from './GroupRow' ;
3837import { useColumnSummary } from './useColumnSummary' ;
38+ import { RowActionMenu , formatActionLabel } from './components/RowActionMenu' ;
39+ import { BulkActionBar } from './components/BulkActionBar' ;
3940
4041export interface ObjectGridProps {
4142 schema : ObjectGridSchema ;
@@ -52,14 +53,6 @@ export interface ObjectGridProps {
5253 onAddRecord ?: ( ) => void ;
5354}
5455
55- /**
56- * Format an action identifier string into a human-readable label.
57- * e.g., 'send_email' → 'Send Email'
58- */
59- function formatActionLabel ( action : string ) : string {
60- return action . replace ( / _ / g, ' ' ) . replace ( / \b \w / g, c => c . toUpperCase ( ) ) ;
61- }
62-
6356/**
6457 * Helper to get data configuration from schema
6558 * Handles both new ViewData format and legacy inline data
@@ -137,6 +130,7 @@ export const ObjectGrid: React.FC<ObjectGridProps> = ({
137130 const [ refreshKey , setRefreshKey ] = useState ( 0 ) ;
138131 const [ showExport , setShowExport ] = useState ( false ) ;
139132 const [ rowHeightMode , setRowHeightMode ] = useState < 'compact' | 'short' | 'medium' | 'tall' | 'extra_tall' > ( schema . rowHeight ?? 'medium' ) ;
133+ const [ selectedRows , setSelectedRows ] = useState < any [ ] > ( [ ] ) ;
140134
141135 // Column state persistence (order and widths)
142136 const columnStorageKey = React . useMemo ( ( ) => {
@@ -756,37 +750,15 @@ export const ObjectGrid: React.FC<ObjectGridProps> = ({
756750 header : 'Actions' ,
757751 accessorKey : '_actions' ,
758752 cell : ( _value : any , row : any ) => (
759- < DropdownMenu >
760- < DropdownMenuTrigger asChild >
761- < Button variant = "ghost" size = "icon" className = "h-8 w-8 min-h-[44px] min-w-[44px] sm:min-h-0 sm:min-w-0" data-testid = "row-action-trigger" >
762- < MoreVertical className = "h-4 w-4" />
763- < span className = "sr-only" > Open menu</ span >
764- </ Button >
765- </ DropdownMenuTrigger >
766- < DropdownMenuContent align = "end" >
767- { operations ?. update && onEdit && (
768- < DropdownMenuItem onClick = { ( ) => onEdit ( row ) } >
769- < Edit className = "mr-2 h-4 w-4" />
770- Edit
771- </ DropdownMenuItem >
772- ) }
773- { operations ?. delete && onDelete && (
774- < DropdownMenuItem onClick = { ( ) => onDelete ( row ) } >
775- < Trash2 className = "mr-2 h-4 w-4" />
776- Delete
777- </ DropdownMenuItem >
778- ) }
779- { schema . rowActions ?. map ( action => (
780- < DropdownMenuItem
781- key = { action }
782- onClick = { ( ) => executeAction ( { type : action , params : { record : row } } ) }
783- data-testid = { `row-action-${ action } ` }
784- >
785- { formatActionLabel ( action ) }
786- </ DropdownMenuItem >
787- ) ) }
788- </ DropdownMenuContent >
789- </ DropdownMenu >
753+ < RowActionMenu
754+ row = { row }
755+ rowActions = { schema . rowActions }
756+ canEdit = { ! ! ( operations ?. update && onEdit ) }
757+ canDelete = { ! ! ( operations ?. delete && onDelete ) }
758+ onEdit = { onEdit }
759+ onDelete = { onDelete }
760+ onAction = { ( action , r ) => executeAction ( { type : action , params : { record : r } } ) }
761+ />
790762 ) ,
791763 sortable : false ,
792764 } ,
@@ -869,7 +841,10 @@ export const ObjectGrid: React.FC<ObjectGridProps> = ({
869841 onAddRecord : onAddRecord ,
870842 rowClassName : schema . rowColor ? ( row : any , _idx : number ) => getRowClassName ( row ) : undefined ,
871843 frozenColumns : effectiveFrozenColumns ,
872- onSelectionChange : onRowSelect ,
844+ onSelectionChange : ( rows : any [ ] ) => {
845+ setSelectedRows ( rows ) ;
846+ onRowSelect ?.( rows ) ;
847+ } ,
873848 onRowClick : navigation . handleClick ,
874849 onCellChange : onCellChange ,
875850 onRowSave : onRowSave ,
@@ -1250,6 +1225,9 @@ export const ObjectGrid: React.FC<ObjectGridProps> = ({
12501225 </ div >
12511226 ) : null ;
12521227
1228+ // Bulk actions — support both batchActions (ObjectUI) and bulkActions (spec) names
1229+ const effectiveBulkActions = schema . batchActions ?? ( schema as any ) . bulkActions ;
1230+
12531231 // Render grid content: grouped (multiple tables with headers) or flat (single table)
12541232 const gridContent = isGrouped ? (
12551233 < div className = "space-y-2" >
@@ -1280,7 +1258,18 @@ export const ObjectGrid: React.FC<ObjectGridProps> = ({
12801258 < NavigationOverlay
12811259 { ...navigation }
12821260 title = { detailTitle }
1283- mainContent = { < > { gridToolbar } { gridContent } </ > }
1261+ mainContent = {
1262+ < >
1263+ { gridToolbar }
1264+ { gridContent }
1265+ < BulkActionBar
1266+ selectedRows = { selectedRows }
1267+ actions = { effectiveBulkActions ?? [ ] }
1268+ onAction = { ( action , rows ) => executeAction ( { type : action , params : { records : rows } } ) }
1269+ onClearSelection = { ( ) => setSelectedRows ( [ ] ) }
1270+ />
1271+ </ >
1272+ }
12841273 >
12851274 { ( record ) => renderRecordDetail ( record ) }
12861275 </ NavigationOverlay >
@@ -1299,6 +1288,12 @@ export const ObjectGrid: React.FC<ObjectGridProps> = ({
12991288 ) }
13001289 { gridToolbar }
13011290 { gridContent }
1291+ < BulkActionBar
1292+ selectedRows = { selectedRows }
1293+ actions = { effectiveBulkActions ?? [ ] }
1294+ onAction = { ( action , rows ) => executeAction ( { type : action , params : { records : rows } } ) }
1295+ onClearSelection = { ( ) => setSelectedRows ( [ ] ) }
1296+ />
13021297 { navigation . isOverlay && (
13031298 < NavigationOverlay
13041299 { ...navigation }
0 commit comments