Files
standardnotes-app-web/packages/web/src/javascripts/Components/Table/useTable.tsx
2022-12-20 19:01:24 +05:30

205 lines
5.8 KiB
TypeScript

import { MouseEventHandler, ReactNode, useCallback, useEffect, useMemo, useState } from 'react'
import { useApplication } from '../ApplicationProvider'
import { Table, TableColumn, TableRow, TableSortBy } from './CommonTypes'
type TableSortOptions =
| {
sortBy: TableSortBy
sortReversed: boolean
onSortChange: (sortBy: TableSortBy, reversed: boolean) => void
}
| {
sortBy?: never
sortReversed?: never
onSortChange?: never
}
type TableSelectionOptions =
| {
enableRowSelection: boolean
enableMultipleRowSelection?: boolean
selectedRowIds?: string[]
onRowSelectionChange?: (rowIds: string[]) => void
selectionActions?: (selected: string[]) => ReactNode
showSelectionActions?: boolean
}
| {
enableRowSelection?: never
enableMultipleRowSelection?: never
selectedRowIds?: never
onRowSelectionChange?: never
selectionActions?: never
showSelectionActions?: never
}
type TableRowOptions<Data> = {
getRowId?: (data: Data) => string
onRowDoubleClick?: (data: Data) => void
onRowContextMenu?: (x: number, y: number, data: Data) => void
rowActions?: (data: Data) => ReactNode
}
export type UseTableOptions<Data> = {
data: Data[]
columns: TableColumn<Data>[]
} & TableRowOptions<Data> &
TableSortOptions &
TableSelectionOptions
export function useTable<Data>({
data,
columns,
sortBy,
sortReversed,
onSortChange,
getRowId,
enableRowSelection,
enableMultipleRowSelection,
selectedRowIds,
onRowSelectionChange,
onRowDoubleClick,
onRowContextMenu,
rowActions,
selectionActions,
showSelectionActions,
}: UseTableOptions<Data>): Table<Data> {
const application = useApplication()
const [selectedRows, setSelectedRows] = useState<string[]>(selectedRowIds || [])
useEffect(() => {
if (selectedRowIds) {
setSelectedRows(selectedRowIds)
}
}, [selectedRowIds])
useEffect(() => {
if (onRowSelectionChange) {
onRowSelectionChange(selectedRows)
}
}, [selectedRows, onRowSelectionChange])
const headers = useMemo(
() =>
columns.map((column) => {
return {
name: column.name,
isSorting: sortBy && sortBy === column.sortBy,
sortBy: column.sortBy,
sortReversed: sortReversed,
onSortChange: () => {
if (!onSortChange || !column.sortBy) {
return
}
onSortChange(column.sortBy, sortBy === column.sortBy ? !sortReversed : false)
},
}
}),
[columns, onSortChange, sortBy, sortReversed],
)
const rows: TableRow<Data>[] = useMemo(
() =>
data.map((rowData, index) => {
const cells = columns.map((column) => {
return column.cell(rowData)
})
const id = getRowId ? getRowId(rowData) : index.toString()
const row: TableRow<Data> = {
id,
isSelected: enableRowSelection ? selectedRows.includes(id) : false,
cells,
rowData,
rowActions: rowActions ? rowActions(rowData) : undefined,
}
return row
}),
[columns, data, enableRowSelection, getRowId, rowActions, selectedRows],
)
const handleRowClick = useCallback(
(id: string) => {
const handler: MouseEventHandler<HTMLTableRowElement> = (event) => {
if (!enableRowSelection) {
return
}
const isCmdOrCtrlPressed = application.keyboardService.isMac ? event.metaKey : event.ctrlKey
if (isCmdOrCtrlPressed && enableMultipleRowSelection) {
setSelectedRows((prev) => (prev.includes(id) ? prev.filter((rowId) => rowId !== id) : [...prev, id]))
} else if (event.shiftKey && enableMultipleRowSelection) {
const lastSelectedIndex = rows.findIndex((row) => row.id === selectedRows[selectedRows.length - 1])
const currentIndex = rows.findIndex((row) => row.id === id)
const start = Math.min(lastSelectedIndex, currentIndex)
const end = Math.max(lastSelectedIndex, currentIndex)
const newSelectedRows = rows.slice(start, end + 1).map((row) => row.id)
setSelectedRows(newSelectedRows)
} else {
setSelectedRows([id])
}
}
return handler
},
[application.keyboardService.isMac, enableMultipleRowSelection, enableRowSelection, rows, selectedRows],
)
const handleRowDoubleClick = useCallback(
(id: string) => {
const handler: MouseEventHandler<HTMLTableRowElement> = () => {
if (!onRowDoubleClick) {
return
}
const rowData = rows.find((row) => row.id === id)?.rowData
if (rowData) {
onRowDoubleClick(rowData)
}
}
return handler
},
[onRowDoubleClick, rows],
)
const handleRowContextMenu = useCallback(
(id: string) => {
const handler: MouseEventHandler<HTMLTableRowElement> = (event) => {
if (!onRowContextMenu) {
return
}
event.preventDefault()
const rowData = rows.find((row) => row.id === id)?.rowData
if (rowData) {
setSelectedRows([id])
onRowContextMenu(event.clientX, event.clientY, rowData)
}
}
return handler
},
[onRowContextMenu, rows],
)
const table: Table<Data> = useMemo(
() => ({
headers,
rows,
handleRowClick,
handleRowDoubleClick,
handleRowContextMenu,
selectedRows,
canSelectRows: enableRowSelection || false,
selectionActions: selectionActions ? selectionActions(selectedRows) : undefined,
showSelectionActions: showSelectionActions || false,
}),
[
enableRowSelection,
handleRowClick,
handleRowContextMenu,
handleRowDoubleClick,
headers,
rows,
selectedRows,
selectionActions,
showSelectionActions,
],
)
return table
}