Skip to content

Commit

Permalink
refactor(dev-only): files table view (#2118)
Browse files Browse the repository at this point in the history
  • Loading branch information
amanharwara committed Dec 24, 2022
1 parent 631be2e commit 7e7c5e1
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import LinkedItemBubble from '../LinkedItems/LinkedItemBubble'
import LinkedItemsPanel from '../LinkedItems/LinkedItemsPanel'
import { LinkingController } from '@/Controllers/LinkingController'
import { FeaturesController } from '@/Controllers/FeaturesController'
import { MutuallyExclusiveMediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery'

const ContextMenuCell = ({ files, filesController }: { files: FileItem[]; filesController: FilesController }) => {
const [contextMenuVisible, setContextMenuVisible] = useState(false)
Expand Down Expand Up @@ -152,16 +153,18 @@ const FilesTableView = ({ application, filesController, featuresController, link
const [contextMenuFile, setContextMenuFile] = useState<FileItem | undefined>(undefined)
const [contextMenuPosition, setContextMenuPosition] = useState<{ x: number; y: number } | undefined>(undefined)

const isSmallBreakpoint = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.sm)

const columnDefs: TableColumn<FileItem>[] = useMemo(
() => [
{
name: 'Name',
sortBy: 'title',
cell: (file) => {
return (
<div className="flex max-w-[40vw] items-center gap-3 whitespace-normal">
<div className="flex items-center gap-3 whitespace-normal">
{getFileIconComponent(getIconForFileType(file.mimeType), 'w-6 h-6 flex-shrink-0')}
<span className="text-sm font-medium">{file.title}</span>
<span className="overflow-hidden text-ellipsis whitespace-nowrap text-sm font-medium">{file.title}</span>
{file.protected && (
<span className="flex items-center" title="File is protected">
<Icon
Expand All @@ -182,15 +185,18 @@ const FilesTableView = ({ application, filesController, featuresController, link
cell: (file) => {
return formatDateForContextMenu(file.created_at)
},
hidden: isSmallBreakpoint,
},
{
name: 'Size',
cell: (file) => {
return formatSizeToReadableString(file.decryptedSize)
},
hidden: isSmallBreakpoint,
},
{
name: 'Attached to',
hidden: isSmallBreakpoint,
cell: (file) => {
const links = [
...naturalSort(application.items.referencesForItem(file), 'title').map((item) =>
Expand Down Expand Up @@ -223,7 +229,7 @@ const FilesTableView = ({ application, filesController, featuresController, link
},
},
],
[application.items],
[application.items, isSmallBreakpoint],
)

const getRowId = useCallback((file: FileItem) => file.uuid, [])
Expand Down
13 changes: 12 additions & 1 deletion packages/web/src/javascripts/Components/Table/CommonTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,18 @@ export type TableColumn<Data> = {
name: string
sortBy?: TableSortBy
cell: (data: Data) => ReactNode
hidden?: boolean
}

type TableCell = {
render: ReactNode
hidden: boolean
colIndex: number
}

export type TableRow<Data> = {
id: string
cells: ReactNode[]
cells: TableCell[]
isSelected: boolean
rowData: Data
rowActions?: ReactNode
Expand All @@ -23,11 +30,15 @@ export type TableHeader = {
sortBy?: TableSortBy
sortReversed: boolean | undefined
onSortChange: () => void
hidden: boolean
colIndex: number
}

export type Table<Data> = {
headers: TableHeader[]
rows: TableRow<Data>[]
rowCount: number
colCount: number
handleRowClick: (id: string) => MouseEventHandler<HTMLTableRowElement>
handleRowDoubleClick: (id: string) => MouseEventHandler<HTMLTableRowElement>
handleRowContextMenu: (id: string) => MouseEventHandler<HTMLTableRowElement>
Expand Down
169 changes: 121 additions & 48 deletions packages/web/src/javascripts/Components/Table/Table.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,122 @@
import { classNames } from '@standardnotes/snjs'
import { useState } from 'react'
import Icon from '../Icon/Icon'
import { Table } from './CommonTypes'
import { Table, TableRow } from './CommonTypes'

function TableRow<Data>({
row,
index: rowIndex,
canSelectRows,
handleRowClick,
handleRowContextMenu,
handleRowDoubleClick,
}: {
row: TableRow<Data>
index: number
canSelectRows: Table<Data>['canSelectRows']
handleRowClick: Table<Data>['handleRowClick']
handleRowContextMenu: Table<Data>['handleRowContextMenu']
handleRowDoubleClick: Table<Data>['handleRowDoubleClick']
}) {
const [isHovered, setIsHovered] = useState(false)

const visibleCells = row.cells.filter((cell) => !cell.hidden)

return (
<div
role="row"
aria-rowindex={rowIndex + 2}
className="group relative contents"
onMouseEnter={() => {
setIsHovered(true)
}}
onMouseLeave={() => {
setIsHovered(false)
}}
onClick={handleRowClick(row.id)}
onDoubleClick={handleRowDoubleClick(row.id)}
onContextMenu={handleRowContextMenu(row.id)}
>
{visibleCells.map((cell, index, array) => {
return (
<div
role="gridcell"
aria-rowindex={rowIndex + 2}
aria-colindex={cell.colIndex + 1}
key={index}
className={classNames(
'relative overflow-hidden border-b border-border py-3 px-3',
index === 0 && 'ml-3',
index === array.length - 1 && 'mr-3',
row.isSelected && 'bg-info-backdrop',
canSelectRows && 'cursor-pointer',
canSelectRows && isHovered && 'bg-contrast',
)}
>
{cell.render}
{row.rowActions && index === array.length - 1 && (
<div
className={classNames(
'absolute right-3 top-1/2 -translate-y-1/2',
row.isSelected ? '' : isHovered ? '' : 'invisible',
)}
>
{row.rowActions}
</div>
)}
</div>
)
})}
</div>
)
}

function Table<Data>({ table }: { table: Table<Data> }) {
const {
headers,
rows,
colCount,
rowCount,
handleRowClick,
handleRowContextMenu,
handleRowDoubleClick,
selectedRows,
selectionActions,
canSelectRows,
showSelectionActions,
} = table

return (
<div className="block min-h-0 overflow-auto">
{table.showSelectionActions && table.selectedRows.length >= 2 && (
{showSelectionActions && selectedRows.length >= 2 && (
<div className="flex items-center justify-between border-b border-border px-3 py-2">
<span className="text-info-0 text-sm font-medium">{table.selectedRows.length} selected</span>
{table.selectedRows.length > 0 && table.selectionActions}
<span className="text-info-0 text-sm font-medium">{selectedRows.length} selected</span>
{selectedRows.length > 0 && selectionActions}
</div>
)}
<table className="w-full">
<thead>
<tr>
{table.headers.map((header, index) => {
<div
className="relative grid w-full overflow-x-hidden"
role="grid"
aria-colcount={colCount}
aria-rowcount={rowCount}
>
<div role="row" aria-rowindex={1} className="contents">
{headers
.filter((header) => !header.hidden)
.map((header, index) => {
return (
<th
<div
role="columnheader"
aria-rowindex={1}
aria-colindex={header.colIndex + 1}
aria-sort={header.isSorting ? (header.sortReversed ? 'descending' : 'ascending') : 'none'}
className={classNames(
'border-b border-border px-3 pt-3 pb-2 text-left text-sm font-medium text-passive-0',
header.sortBy && 'cursor-pointer hover:bg-info-backdrop hover:underline',
)}
style={{
gridColumn: index + 1,
}}
onClick={header.onSortChange}
key={index.toString()}
>
Expand All @@ -34,47 +130,24 @@ function Table<Data>({ table }: { table: Table<Data> }) {
/>
)}
</div>
</th>
</div>
)
})}
</tr>
</thead>
<tbody className="divide-y divide-border whitespace-nowrap">
{table.rows.map((row) => {
return (
<tr
key={row.id}
className={classNames(
'group relative',
row.isSelected && 'bg-info-backdrop',
table.canSelectRows && 'cursor-pointer hover:bg-contrast',
)}
onClick={table.handleRowClick(row.id)}
onDoubleClick={table.handleRowDoubleClick(row.id)}
onContextMenu={table.handleRowContextMenu(row.id)}
>
{row.cells.map((cell, index) => {
return (
<td key={index} className="py-3 px-3">
{cell}
</td>
)
})}
{row.rowActions ? (
<div
className={classNames(
'absolute right-3 top-1/2 -translate-y-1/2',
row.isSelected ? '' : 'invisible group-hover:visible',
)}
>
{row.rowActions}
</div>
) : null}
</tr>
)
})}
</tbody>
</table>
</div>
<div className="contents divide-y divide-border whitespace-nowrap">
{rows.map((row, index) => (
<TableRow
row={row}
key={row.id}
index={index}
canSelectRows={canSelectRows}
handleRowClick={handleRowClick}
handleRowContextMenu={handleRowContextMenu}
handleRowDoubleClick={handleRowDoubleClick}
/>
))}
</div>
</div>
</div>
)
}
Expand Down
23 changes: 18 additions & 5 deletions packages/web/src/javascripts/Components/Table/useTable.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { MouseEventHandler, ReactNode, useCallback, useEffect, useMemo, useState } from 'react'
import { useApplication } from '../ApplicationProvider'
import { Table, TableColumn, TableRow, TableSortBy } from './CommonTypes'
import { Table, TableColumn, TableHeader, TableRow, TableSortBy } from './CommonTypes'

type TableSortOptions =
| {
Expand Down Expand Up @@ -78,9 +78,9 @@ export function useTable<Data>({
}
}, [selectedRows, onRowSelectionChange])

const headers = useMemo(
const headers: TableHeader[] = useMemo(
() =>
columns.map((column) => {
columns.map((column, index) => {
return {
name: column.name,
isSorting: sortBy && sortBy === column.sortBy,
Expand All @@ -92,6 +92,8 @@ export function useTable<Data>({
}
onSortChange(column.sortBy, sortBy === column.sortBy ? !sortReversed : false)
},
hidden: column.hidden || false,
colIndex: index,
}
}),
[columns, onSortChange, sortBy, sortReversed],
Expand All @@ -100,8 +102,12 @@ export function useTable<Data>({
const rows: TableRow<Data>[] = useMemo(
() =>
data.map((rowData, index) => {
const cells = columns.map((column) => {
return column.cell(rowData)
const cells = columns.map((column, index) => {
return {
render: column.cell(rowData),
hidden: column.hidden || false,
colIndex: index,
}
})
const id = getRowId ? getRowId(rowData) : index.toString()
const row: TableRow<Data> = {
Expand Down Expand Up @@ -175,10 +181,15 @@ export function useTable<Data>({
[onRowContextMenu, rows],
)

const colCount = useMemo(() => columns.length, [columns])
const rowCount = useMemo(() => data.length, [data.length])

const table: Table<Data> = useMemo(
() => ({
headers,
rows,
colCount,
rowCount,
handleRowClick,
handleRowDoubleClick,
handleRowContextMenu,
Expand All @@ -188,11 +199,13 @@ export function useTable<Data>({
showSelectionActions: showSelectionActions || false,
}),
[
colCount,
enableRowSelection,
handleRowClick,
handleRowContextMenu,
handleRowDoubleClick,
headers,
rowCount,
rows,
selectedRows,
selectionActions,
Expand Down

0 comments on commit 7e7c5e1

Please sign in to comment.