Files
supabase/apps/studio/hooks/ui/useCsvFileDrop.ts
T
Joshen Lim 4651480f28 Joshen/fe 2540 fix safari csv drag drop typeerror in table editor (#42852)
## Context

Addresses an error when drag and dropping a CSV in the Table Editor when
the table is empty, specifically on Safari leading to a `undefined is
not an object (evaluating 'n.type')` error

For context, Safari obscures the item data from the drag event and only
exposes it on the drop event
([ref](https://github.com/mdn/browser-compat-data/issues/24898)) hence
why this issue

## Changes involved

Am opting to change the UX instead of fixing this browser specific
behaviour
- Ignore file validity when dragging, let the on drop handle validate
the type and reject unsupported files instead
- This is better either way as currently our Table Editor shows "CSV
files only", when we actually also support TSV files
- Also made a slight adjustment to the drag over UI - border around the
grid empty area, rather than the whole grid
  - Before:
<img width="1151" height="911" alt="image"
src="https://github.com/user-attachments/assets/049199aa-86bc-4e88-8444-5a7ee236c8d0"
/>
  - After:
<img width="1150" height="912" alt="image"
src="https://github.com/user-attachments/assets/698ca8f3-a326-4a82-b1b4-4448aa8d777a"
/>


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Refactor**
* Simplified the empty-table display and CSV import interface for a more
consistent user experience.
* Import button and drag-and-drop hints now remain visible when the
table is empty, removing prior conditional hiding during drag.
* Streamlined empty-state styling and messaging for a cleaner, more
predictable appearance.

* **Tests**
* Updated end-to-end test expectations to reflect the persistent
empty-state message during drag-over.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-02-16 07:06:01 -07:00

71 lines
1.9 KiB
TypeScript

import { type ImportDataFileDroppedEvent } from 'common/telemetry-constants'
import { flagInvalidFileImport } from 'components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetImport.utils'
import { useCallback, useState, type DragEvent } from 'react'
interface UseCsvFileDropOptions {
enabled: boolean
onFileDropped: (file: File) => void
onTelemetryEvent?: (eventName: ImportDataFileDroppedEvent['action']) => void
}
interface UseCsvFileDropReturn {
isDraggedOver: boolean
onDragOver: (event: DragEvent<HTMLDivElement>) => void
onFileDrop: (event: DragEvent<HTMLDivElement>) => void
}
export function useCsvFileDrop({
enabled,
onFileDropped,
onTelemetryEvent,
}: UseCsvFileDropOptions): UseCsvFileDropReturn {
const [isDraggedOver, setIsDraggedOver] = useState(false)
const onDragOver = useCallback(
(event: DragEvent<HTMLDivElement>) => {
if (!enabled) return
const [item] = event.dataTransfer.items
// ignore non files drop, like column headers
if (item && item.kind !== 'file') return
if (event.type === 'dragover' && !isDraggedOver) {
setIsDraggedOver(true)
} else if (event.type === 'dragleave' || event.type === 'drop') {
setIsDraggedOver(false)
}
event.stopPropagation()
event.preventDefault()
},
[enabled, isDraggedOver]
)
const onFileDrop = useCallback(
(event: DragEvent<HTMLDivElement>) => {
if (!enabled) return
onDragOver(event)
const [file] = event.dataTransfer.files
const [item] = event.dataTransfer.items
// ignore non files drop, like column headers
if (item && item.kind !== 'file') return
if (flagInvalidFileImport(file)) return
onFileDropped(file)
onTelemetryEvent?.('import_data_dropzone_file_added')
},
[enabled, onDragOver, onFileDropped, onTelemetryEvent]
)
return {
isDraggedOver,
onDragOver,
onFileDrop,
}
}