-
Notifications
You must be signed in to change notification settings - Fork 2
feat: Extract RowActionMenu + add BulkActionBar to ObjectGrid #784
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
63e76ff
f168837
049b1bb
30f4f2d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,14 +28,15 @@ import { getCellRenderer, formatCurrency, formatCompactCurrency, formatDate, for | |
| import { | ||
| Badge, Button, NavigationOverlay, | ||
| Popover, PopoverContent, PopoverTrigger, | ||
| DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, | ||
| } from '@object-ui/components'; | ||
| import { usePullToRefresh } from '@object-ui/mobile'; | ||
| import { Edit, Trash2, MoreVertical, ChevronRight, ChevronDown, Download, Rows2, Rows3, Rows4, AlignJustify, Type, Hash, Calendar, CheckSquare, User, Tag, Clock } from 'lucide-react'; | ||
| import { ChevronRight, ChevronDown, Download, Rows2, Rows3, Rows4, AlignJustify, Type, Hash, Calendar, CheckSquare, User, Tag, Clock } from 'lucide-react'; | ||
| import { useRowColor } from './useRowColor'; | ||
| import { useGroupedData } from './useGroupedData'; | ||
| import { GroupRow } from './GroupRow'; | ||
| import { useColumnSummary } from './useColumnSummary'; | ||
| import { RowActionMenu, formatActionLabel } from './components/RowActionMenu'; | ||
| import { BulkActionBar } from './components/BulkActionBar'; | ||
|
|
||
| export interface ObjectGridProps { | ||
| schema: ObjectGridSchema; | ||
|
|
@@ -52,14 +53,6 @@ export interface ObjectGridProps { | |
| onAddRecord?: () => void; | ||
| } | ||
|
|
||
| /** | ||
| * Format an action identifier string into a human-readable label. | ||
| * e.g., 'send_email' → 'Send Email' | ||
| */ | ||
| function formatActionLabel(action: string): string { | ||
| return action.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase()); | ||
| } | ||
|
|
||
| /** | ||
| * Helper to get data configuration from schema | ||
| * Handles both new ViewData format and legacy inline data | ||
|
|
@@ -137,6 +130,7 @@ export const ObjectGrid: React.FC<ObjectGridProps> = ({ | |
| const [refreshKey, setRefreshKey] = useState(0); | ||
| const [showExport, setShowExport] = useState(false); | ||
| const [rowHeightMode, setRowHeightMode] = useState<'compact' | 'short' | 'medium' | 'tall' | 'extra_tall'>(schema.rowHeight ?? 'medium'); | ||
| const [selectedRows, setSelectedRows] = useState<any[]>([]); | ||
|
|
||
| // Column state persistence (order and widths) | ||
| const columnStorageKey = React.useMemo(() => { | ||
|
|
@@ -756,37 +750,15 @@ export const ObjectGrid: React.FC<ObjectGridProps> = ({ | |
| header: 'Actions', | ||
| accessorKey: '_actions', | ||
| cell: (_value: any, row: any) => ( | ||
| <DropdownMenu> | ||
| <DropdownMenuTrigger asChild> | ||
| <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"> | ||
| <MoreVertical className="h-4 w-4" /> | ||
| <span className="sr-only">Open menu</span> | ||
| </Button> | ||
| </DropdownMenuTrigger> | ||
| <DropdownMenuContent align="end"> | ||
| {operations?.update && onEdit && ( | ||
| <DropdownMenuItem onClick={() => onEdit(row)}> | ||
| <Edit className="mr-2 h-4 w-4" /> | ||
| Edit | ||
| </DropdownMenuItem> | ||
| )} | ||
| {operations?.delete && onDelete && ( | ||
| <DropdownMenuItem onClick={() => onDelete(row)}> | ||
| <Trash2 className="mr-2 h-4 w-4" /> | ||
| Delete | ||
| </DropdownMenuItem> | ||
| )} | ||
| {schema.rowActions?.map(action => ( | ||
| <DropdownMenuItem | ||
| key={action} | ||
| onClick={() => executeAction({ type: action, params: { record: row } })} | ||
| data-testid={`row-action-${action}`} | ||
| > | ||
| {formatActionLabel(action)} | ||
| </DropdownMenuItem> | ||
| ))} | ||
| </DropdownMenuContent> | ||
| </DropdownMenu> | ||
| <RowActionMenu | ||
| row={row} | ||
| rowActions={schema.rowActions} | ||
| canEdit={!!(operations?.update && onEdit)} | ||
| canDelete={!!(operations?.delete && onDelete)} | ||
| onEdit={onEdit} | ||
| onDelete={onDelete} | ||
| onAction={(action, r) => executeAction({ type: action, params: { record: r } })} | ||
| /> | ||
| ), | ||
| sortable: false, | ||
| }, | ||
|
|
@@ -869,7 +841,10 @@ export const ObjectGrid: React.FC<ObjectGridProps> = ({ | |
| onAddRecord: onAddRecord, | ||
| rowClassName: schema.rowColor ? (row: any, _idx: number) => getRowClassName(row) : undefined, | ||
| frozenColumns: effectiveFrozenColumns, | ||
| onSelectionChange: onRowSelect, | ||
| onSelectionChange: (rows: any[]) => { | ||
| setSelectedRows(rows); | ||
| onRowSelect?.(rows); | ||
| }, | ||
| onRowClick: navigation.handleClick, | ||
| onCellChange: onCellChange, | ||
| onRowSave: onRowSave, | ||
|
|
@@ -1250,6 +1225,9 @@ export const ObjectGrid: React.FC<ObjectGridProps> = ({ | |
| </div> | ||
| ) : null; | ||
|
|
||
| // Bulk actions — support both batchActions (ObjectUI) and bulkActions (spec) names | ||
| const effectiveBulkActions = schema.batchActions ?? (schema as any).bulkActions; | ||
|
|
||
| // Render grid content: grouped (multiple tables with headers) or flat (single table) | ||
| const gridContent = isGrouped ? ( | ||
| <div className="space-y-2"> | ||
|
|
@@ -1280,7 +1258,18 @@ export const ObjectGrid: React.FC<ObjectGridProps> = ({ | |
| <NavigationOverlay | ||
| {...navigation} | ||
| title={detailTitle} | ||
| mainContent={<>{gridToolbar}{gridContent}</>} | ||
| mainContent={ | ||
| <> | ||
| {gridToolbar} | ||
| {gridContent} | ||
| <BulkActionBar | ||
| selectedRows={selectedRows} | ||
| actions={effectiveBulkActions ?? []} | ||
| onAction={(action, rows) => executeAction({ type: action, params: { records: rows } })} | ||
| onClearSelection={() => setSelectedRows([])} | ||
| /> | ||
|
Comment on lines
+1265
to
+1270
|
||
| </> | ||
| } | ||
| > | ||
| {(record) => renderRecordDetail(record)} | ||
| </NavigationOverlay> | ||
|
|
@@ -1299,6 +1288,12 @@ export const ObjectGrid: React.FC<ObjectGridProps> = ({ | |
| )} | ||
| {gridToolbar} | ||
| {gridContent} | ||
| <BulkActionBar | ||
| selectedRows={selectedRows} | ||
| actions={effectiveBulkActions ?? []} | ||
| onAction={(action, rows) => executeAction({ type: action, params: { records: rows } })} | ||
| onClearSelection={() => setSelectedRows([])} | ||
| /> | ||
|
Comment on lines
+1291
to
+1296
|
||
| {navigation.isOverlay && ( | ||
| <NavigationOverlay | ||
| {...navigation} | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
formatActionLabelis imported from./components/RowActionMenubut not used in this file anymore. This will typically fail lint/typecheck with an unused import. Remove the unused import (or use it if intended).