Skip to content

Commit c129135

Browse files
committed
add shift-click range selection and selection-aware context menu for files
- Extend SelectableConfig.onSelectRow with optional shiftKey param; DataRow captures shiftKey before onCheckedChange fires via a ref so the Radix Checkbox interaction chain stays intact - Implement shift-click range selection in files.tsx using lastSelectedIndexRef; tracks last-selected index in visibleRowIds to compute the range - Reset lastSelectedIndexRef on deselect and select-all - Add selectedCount prop to FileRowContextMenu; hide Open and Rename when multiple items are selected, show "Delete N items" / "Download N items" labels in multi-select mode
1 parent c176e50 commit c129135

3 files changed

Lines changed: 52 additions & 20 deletions

File tree

apps/sim/app/workspace/[workspaceId]/components/resource/resource.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export interface ResourceRow {
3939

4040
export interface SelectableConfig {
4141
selectedIds: Set<string>
42-
onSelectRow: (id: string, checked: boolean) => void
42+
onSelectRow: (id: string, checked: boolean, shiftKey?: boolean) => void
4343
onSelectAll: (checked: boolean) => void
4444
isAllSelected: boolean
4545
disabled?: boolean
@@ -510,9 +510,17 @@ const DataRow = memo(function DataRow({
510510
[onRowContextMenu, row.id]
511511
)
512512

513+
const shiftKeyRef = useRef(false)
514+
515+
const handleSelectRowClick = useCallback((e: React.MouseEvent) => {
516+
e.stopPropagation()
517+
shiftKeyRef.current = e.shiftKey
518+
}, [])
519+
513520
const handleSelectRow = useCallback(
514521
(checked: boolean | 'indeterminate') => {
515-
selectable?.onSelectRow(row.id, checked as boolean)
522+
selectable?.onSelectRow(row.id, checked as boolean, shiftKeyRef.current)
523+
shiftKeyRef.current = false
516524
},
517525
[selectable, row.id]
518526
)
@@ -585,7 +593,7 @@ const DataRow = memo(function DataRow({
585593
onCheckedChange={handleSelectRow}
586594
disabled={selectable.disabled}
587595
aria-label='Select row'
588-
onClick={stopPropagation}
596+
onClick={handleSelectRowClick}
589597
/>
590598
</td>
591599
)}

apps/sim/app/workspace/[workspaceId]/files/components/file-row-context-menu/file-row-context-menu.tsx

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ interface FileRowContextMenuProps {
2222
onRename: () => void
2323
onDelete: () => void
2424
canEdit: boolean
25+
selectedCount: number
2526
}
2627

2728
export const FileRowContextMenu = memo(function FileRowContextMenu({
@@ -33,7 +34,10 @@ export const FileRowContextMenu = memo(function FileRowContextMenu({
3334
onRename,
3435
onDelete,
3536
canEdit,
37+
selectedCount,
3638
}: FileRowContextMenuProps) {
39+
const isMultiSelect = selectedCount > 1
40+
3741
return (
3842
<DropdownMenu open={isOpen} onOpenChange={(open) => !open && onClose()} modal={false}>
3943
<DropdownMenuTrigger asChild>
@@ -56,26 +60,30 @@ export const FileRowContextMenu = memo(function FileRowContextMenu({
5660
sideOffset={4}
5761
onCloseAutoFocus={(e) => e.preventDefault()}
5862
>
59-
<DropdownMenuItem onSelect={onOpen}>
60-
<Eye />
61-
Open
62-
</DropdownMenuItem>
63+
{!isMultiSelect && (
64+
<DropdownMenuItem onSelect={onOpen}>
65+
<Eye />
66+
Open
67+
</DropdownMenuItem>
68+
)}
6369
{onDownload && (
6470
<DropdownMenuItem onSelect={onDownload}>
6571
<Download />
66-
Download
72+
{isMultiSelect ? `Download ${selectedCount} items` : 'Download'}
6773
</DropdownMenuItem>
6874
)}
6975
{canEdit && (
7076
<>
7177
<DropdownMenuSeparator />
72-
<DropdownMenuItem onSelect={onRename}>
73-
<Pencil />
74-
Rename
75-
</DropdownMenuItem>
78+
{!isMultiSelect && (
79+
<DropdownMenuItem onSelect={onRename}>
80+
<Pencil />
81+
Rename
82+
</DropdownMenuItem>
83+
)}
7684
<DropdownMenuItem onSelect={onDelete}>
7785
<Trash2 />
78-
Delete
86+
{isMultiSelect ? `Delete ${selectedCount} items` : 'Delete'}
7987
</DropdownMenuItem>
8088
</>
8189
)}

apps/sim/app/workspace/[workspaceId]/files/files.tsx

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ export function Files() {
253253
const [showUnsavedChangesAlert, setShowUnsavedChangesAlert] = useState(false)
254254
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false)
255255
const contextMenuItemRef = useRef<FileResourceItem | null>(null)
256+
const lastSelectedIndexRef = useRef<number>(-1)
256257
const draggedRowIdsRef = useRef<string[]>([])
257258
const dragGhostRef = useRef<HTMLElement | null>(null)
258259
const [deleteTarget, setDeleteTarget] = useState<{
@@ -498,15 +499,29 @@ export function Files() {
498499
() => ({
499500
selectedIds: selectedRowIds,
500501
isAllSelected,
501-
onSelectRow: (rowId: string, checked: boolean) => {
502-
setSelectedRowIds((prev) => {
503-
const next = new Set(prev)
504-
if (checked) next.add(rowId)
505-
else next.delete(rowId)
506-
return next
507-
})
502+
onSelectRow: (rowId: string, checked: boolean, shiftKey?: boolean) => {
503+
const currentIndex = visibleRowIds.indexOf(rowId)
504+
if (shiftKey && lastSelectedIndexRef.current !== -1 && currentIndex !== -1) {
505+
const start = Math.min(lastSelectedIndexRef.current, currentIndex)
506+
const end = Math.max(lastSelectedIndexRef.current, currentIndex)
507+
setSelectedRowIds((prev) => {
508+
const next = new Set(prev)
509+
for (let i = start; i <= end; i++) next.add(visibleRowIds[i])
510+
return next
511+
})
512+
} else {
513+
setSelectedRowIds((prev) => {
514+
const next = new Set(prev)
515+
if (checked) next.add(rowId)
516+
else next.delete(rowId)
517+
return next
518+
})
519+
if (checked) lastSelectedIndexRef.current = currentIndex
520+
else lastSelectedIndexRef.current = -1
521+
}
508522
},
509523
onSelectAll: (checked: boolean) => {
524+
lastSelectedIndexRef.current = -1
510525
setSelectedRowIds((prev) => {
511526
const next = new Set(prev)
512527
for (const rowId of visibleRowIds) {
@@ -1785,6 +1800,7 @@ export function Files() {
17851800
onRename={handleContextMenuRename}
17861801
onDelete={handleContextMenuDelete}
17871802
canEdit={canEdit}
1803+
selectedCount={selectedRowIds.size}
17881804
/>
17891805

17901806
<DeleteConfirmModal

0 commit comments

Comments
 (0)