refactor: files table view -> content table view

This commit is contained in:
Aman Harwara
2023-01-06 02:02:07 +05:30
parent ba017d42f5
commit 77ec9ca335
8 changed files with 201 additions and 90 deletions

View File

@@ -36,9 +36,10 @@ import { FeatureName } from '@/Controllers/FeatureName'
import { PanelResizedData } from '@/Types/PanelResizedData' import { PanelResizedData } from '@/Types/PanelResizedData'
import { useForwardedRef } from '@/Hooks/useForwardedRef' import { useForwardedRef } from '@/Hooks/useForwardedRef'
import FloatingAddButton from './FloatingAddButton' import FloatingAddButton from './FloatingAddButton'
import FilesTableView from '../FilesTableView/FilesTableView' import ContentTableView from '../ContentTableView/ContentTableView'
import { FeaturesController } from '@/Controllers/FeaturesController' import { FeaturesController } from '@/Controllers/FeaturesController'
import { MutuallyExclusiveMediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery' import { MutuallyExclusiveMediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery'
import { HistoryModalController } from '@/Controllers/NoteHistory/HistoryModalController'
type Props = { type Props = {
accountMenuController: AccountMenuController accountMenuController: AccountMenuController
@@ -52,6 +53,7 @@ type Props = {
searchOptionsController: SearchOptionsController searchOptionsController: SearchOptionsController
linkingController: LinkingController linkingController: LinkingController
featuresController: FeaturesController featuresController: FeaturesController
historyModalController: HistoryModalController
className?: string className?: string
id: string id: string
children?: React.ReactNode children?: React.ReactNode
@@ -72,6 +74,7 @@ const ContentListView = forwardRef<HTMLDivElement, Props>(
searchOptionsController, searchOptionsController,
linkingController, linkingController,
featuresController, featuresController,
historyModalController,
className, className,
id, id,
children, children,
@@ -353,12 +356,15 @@ const ContentListView = forwardRef<HTMLDivElement, Props>(
) : null} ) : null}
{!dailyMode && renderedItems.length ? ( {!dailyMode && renderedItems.length ? (
shouldShowFilesTableView ? ( shouldShowFilesTableView ? (
<FilesTableView <ContentTableView
items={items}
application={application} application={application}
filesController={filesController} filesController={filesController}
featuresController={featuresController} featuresController={featuresController}
linkingController={linkingController} linkingController={linkingController}
navigationController={navigationController} navigationController={navigationController}
notesController={notesController}
historyModalController={historyModalController}
/> />
) : ( ) : (
<ContentList <ContentList

View File

@@ -5,7 +5,6 @@ import { formatDateForContextMenu } from '@/Utils/DateUtils'
import { getIconForFileType } from '@/Utils/Items/Icons/getIconForFileType' import { getIconForFileType } from '@/Utils/Items/Icons/getIconForFileType'
import { formatSizeToReadableString } from '@standardnotes/filepicker' import { formatSizeToReadableString } from '@standardnotes/filepicker'
import { import {
ContentType,
FileItem, FileItem,
SortableItem, SortableItem,
PrefKey, PrefKey,
@@ -13,6 +12,8 @@ import {
naturalSort, naturalSort,
FileBackupRecord, FileBackupRecord,
SystemViewId, SystemViewId,
DecryptedItemInterface,
SNNote,
} from '@standardnotes/snjs' } from '@standardnotes/snjs'
import { useState, useEffect, useCallback, useMemo, useRef } from 'react' import { useState, useEffect, useCallback, useMemo, useRef } from 'react'
import { FileItemActionType } from '../AttachedFilesPopover/PopoverFileItemAction' import { FileItemActionType } from '../AttachedFilesPopover/PopoverFileItemAction'
@@ -32,21 +33,42 @@ import { FeaturesController } from '@/Controllers/FeaturesController'
import { MutuallyExclusiveMediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery' import { MutuallyExclusiveMediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery'
import { useApplication } from '../ApplicationProvider' import { useApplication } from '../ApplicationProvider'
import { NavigationController } from '@/Controllers/Navigation/NavigationController' import { NavigationController } from '@/Controllers/Navigation/NavigationController'
import { getIconAndTintForNoteType } from '@/Utils/Items/Icons/getIconAndTintForNoteType'
import NotesOptions from '../NotesOptions/NotesOptions'
import { NotesController } from '@/Controllers/NotesController/NotesController'
import { HistoryModalController } from '@/Controllers/NoteHistory/HistoryModalController'
const ContextMenuCell = ({ const ContextMenuCell = ({
files, items,
filesController, filesController,
navigationController, navigationController,
linkingController, linkingController,
notesController,
historyModalController,
}: { }: {
files: FileItem[] items: DecryptedItemInterface[]
filesController: FilesController filesController: FilesController
navigationController: NavigationController navigationController: NavigationController
linkingController: LinkingController linkingController: LinkingController
notesController: NotesController
historyModalController: HistoryModalController
}) => { }) => {
const application = useApplication()
const [contextMenuVisible, setContextMenuVisible] = useState(false) const [contextMenuVisible, setContextMenuVisible] = useState(false)
const anchorElementRef = useRef<HTMLButtonElement>(null) const anchorElementRef = useRef<HTMLButtonElement>(null)
const allItemsAreNotes = useMemo(() => {
return items.every((item) => item instanceof SNNote)
}, [items])
const allItemsAreFiles = useMemo(() => {
return items.every((item) => item instanceof FileItem)
}, [items])
if (!allItemsAreNotes && !allItemsAreFiles) {
return null
}
return ( return (
<> <>
<button <button
@@ -71,30 +93,45 @@ const ContextMenuCell = ({
className="py-2" className="py-2"
> >
<Menu a11yLabel="File context menu" isOpen={contextMenuVisible}> <Menu a11yLabel="File context menu" isOpen={contextMenuVisible}>
<FileMenuOptions {allItemsAreFiles && (
linkingController={linkingController} <FileMenuOptions
navigationController={navigationController} linkingController={linkingController}
closeMenu={() => { navigationController={navigationController}
setContextMenuVisible(false) closeMenu={() => {
}} setContextMenuVisible(false)
filesController={filesController} }}
shouldShowRenameOption={false} filesController={filesController}
shouldShowAttachOption={false} shouldShowRenameOption={false}
selectedFiles={files} shouldShowAttachOption={false}
/> selectedFiles={items as FileItem[]}
/>
)}
{allItemsAreNotes && (
<NotesOptions
notes={items as SNNote[]}
application={application}
navigationController={navigationController}
notesController={notesController}
linkingController={linkingController}
historyModalController={historyModalController}
closeMenu={() => {
setContextMenuVisible(false)
}}
/>
)}
</Menu> </Menu>
</Popover> </Popover>
</> </>
) )
} }
const FileLinksCell = ({ const ItemLinksCell = ({
file, item,
filesController, filesController,
linkingController, linkingController,
featuresController, featuresController,
}: { }: {
file: FileItem item: DecryptedItemInterface
filesController: FilesController filesController: FilesController
linkingController: LinkingController linkingController: LinkingController
featuresController: FeaturesController featuresController: FeaturesController
@@ -130,25 +167,38 @@ const FileLinksCell = ({
filesController={filesController} filesController={filesController}
featuresController={featuresController} featuresController={featuresController}
isOpen={contextMenuVisible} isOpen={contextMenuVisible}
item={file} item={item}
/> />
</Popover> </Popover>
</> </>
) )
} }
const FileNameCell = ({ file }: { file: FileItem }) => { const ItemNameCell = ({ item }: { item: DecryptedItemInterface }) => {
const application = useApplication() const application = useApplication()
const [backupInfo, setBackupInfo] = useState<FileBackupRecord | undefined>(undefined) const [backupInfo, setBackupInfo] = useState<FileBackupRecord | undefined>(undefined)
const isItemFile = item instanceof FileItem
const noteType =
item instanceof SNNote
? item.noteType || application.componentManager.editorForNote(item)?.package_info.note_type
: undefined
const [noteIcon, noteIconTint] = getIconAndTintForNoteType(noteType)
useEffect(() => { useEffect(() => {
void application.fileBackups?.getFileBackupInfo(file).then(setBackupInfo) if (isItemFile) {
}, [application, file]) void application.fileBackups?.getFileBackupInfo(item).then(setBackupInfo)
}
}, [application, isItemFile, item])
return ( return (
<div className="flex items-center gap-3 whitespace-normal"> <div className="flex items-center gap-3 whitespace-normal">
<span className="relative"> <span className="relative">
{getFileIconComponent(getIconForFileType(file.mimeType), 'w-6 h-6 flex-shrink-0')} {isItemFile ? (
getFileIconComponent(getIconForFileType(item.mimeType), 'w-6 h-6 flex-shrink-0')
) : (
<Icon type={noteIcon} className={`text-accessory-tint-${noteIconTint}`} />
)}
{backupInfo && ( {backupInfo && (
<div <div
className="absolute bottom-1 right-1 translate-x-1/2 translate-y-1/2 rounded-full bg-default text-success" className="absolute bottom-1 right-1 translate-x-1/2 translate-y-1/2 rounded-full bg-default text-success"
@@ -158,8 +208,8 @@ const FileNameCell = ({ file }: { file: FileItem }) => {
</div> </div>
)} )}
</span> </span>
<span className="overflow-hidden text-ellipsis whitespace-nowrap text-sm font-medium">{file.title}</span> <span className="overflow-hidden text-ellipsis whitespace-nowrap text-sm font-medium">{item.title}</span>
{file.protected && ( {item.protected && (
<span className="flex items-center" title="File is protected"> <span className="flex items-center" title="File is protected">
<Icon ariaLabel="File is protected" type="lock-filled" className="h-3.5 w-3.5 text-passive-1" size="custom" /> <Icon ariaLabel="File is protected" type="lock-filled" className="h-3.5 w-3.5 text-passive-1" size="custom" />
</span> </span>
@@ -170,22 +220,26 @@ const FileNameCell = ({ file }: { file: FileItem }) => {
type Props = { type Props = {
application: WebApplication application: WebApplication
items: DecryptedItemInterface[]
filesController: FilesController filesController: FilesController
featuresController: FeaturesController featuresController: FeaturesController
linkingController: LinkingController linkingController: LinkingController
navigationController: NavigationController navigationController: NavigationController
notesController: NotesController
historyModalController: HistoryModalController
} }
const FilesTableView = ({ const ContentTableView = ({
application, application,
items,
filesController, filesController,
featuresController, featuresController,
linkingController, linkingController,
navigationController, navigationController,
notesController,
historyModalController,
}: Props) => { }: Props) => {
const files = application.items const listHasFiles = items.some((item) => item instanceof FileItem)
.getDisplayableNotesAndFiles()
.filter((item) => item.content_type === ContentType.File) as FileItem[]
const getSortByPreference = useCallback(() => { const getSortByPreference = useCallback(() => {
const globalPrefValue = application.getPreference(PrefKey.SortNotesBy, PrefDefaults[PrefKey.SortNotesBy]) const globalPrefValue = application.getPreference(PrefKey.SortNotesBy, PrefDefaults[PrefKey.SortNotesBy])
@@ -231,48 +285,48 @@ const FilesTableView = ({
[application], [application],
) )
const [contextMenuFile, setContextMenuFile] = useState<FileItem | undefined>(undefined) const [contextMenuItem, setContextMenuItem] = useState<DecryptedItemInterface | undefined>(undefined)
const [contextMenuPosition, setContextMenuPosition] = useState<{ x: number; y: number } | undefined>(undefined) const [contextMenuPosition, setContextMenuPosition] = useState<{ x: number; y: number } | undefined>(undefined)
const isSmallBreakpoint = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.sm) const isSmallBreakpoint = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.sm)
const isMediumBreakpoint = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.md) const isMediumBreakpoint = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.md)
const isLargeBreakpoint = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.lg) const isLargeBreakpoint = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.lg)
const columnDefs: TableColumn<FileItem>[] = useMemo( const columnDefs: TableColumn<DecryptedItemInterface>[] = useMemo(
() => [ () => [
{ {
name: 'Name', name: 'Name',
sortBy: 'title', sortBy: 'title',
cell: (file) => <FileNameCell file={file} />, cell: (item) => <ItemNameCell item={item} />,
}, },
{ {
name: 'Upload date', name: 'Upload date',
sortBy: 'created_at', sortBy: 'created_at',
cell: (file) => { cell: (item) => {
return formatDateForContextMenu(file.created_at) return formatDateForContextMenu(item.created_at)
}, },
hidden: isSmallBreakpoint, hidden: isSmallBreakpoint,
}, },
{ {
name: 'Size', name: 'Size',
sortBy: 'decryptedSize', sortBy: 'decryptedSize',
cell: (file) => { cell: (item) => {
return formatSizeToReadableString(file.decryptedSize) return item instanceof FileItem ? formatSizeToReadableString(item.decryptedSize) : null
}, },
hidden: isSmallBreakpoint, hidden: isSmallBreakpoint || !listHasFiles,
}, },
{ {
name: 'Attached to', name: 'Attached to',
hidden: isSmallBreakpoint || isMediumBreakpoint || isLargeBreakpoint, hidden: isSmallBreakpoint || isMediumBreakpoint || isLargeBreakpoint,
cell: (file) => { cell: (item) => {
const links = [ const links = [
...naturalSort(application.items.referencesForItem(file), 'title').map((item) => ...naturalSort(application.items.referencesForItem(item), 'title').map((item) =>
createLinkFromItem(item, 'linked'), createLinkFromItem(item, 'linked'),
), ),
...naturalSort(application.items.itemsReferencingItem(file), 'title').map((item) => ...naturalSort(application.items.itemsReferencingItem(item), 'title').map((item) =>
createLinkFromItem(item, 'linked-by'), createLinkFromItem(item, 'linked-by'),
), ),
...application.items.getSortedTagsForItem(file).map((item) => createLinkFromItem(item, 'linked')), ...application.items.getSortedTagsForItem(item).map((item) => createLinkFromItem(item, 'linked')),
] ]
if (!links.length) { if (!links.length) {
@@ -286,7 +340,7 @@ const FilesTableView = ({
link={links[0]} link={links[0]}
key={links[0].id} key={links[0].id}
unlinkItem={async (itemToUnlink) => { unlinkItem={async (itemToUnlink) => {
void application.items.unlinkItems(file, itemToUnlink) void application.items.unlinkItems(item, itemToUnlink)
}} }}
isBidirectional={false} isBidirectional={false}
/> />
@@ -296,13 +350,13 @@ const FilesTableView = ({
}, },
}, },
], ],
[application.items, isLargeBreakpoint, isMediumBreakpoint, isSmallBreakpoint], [application.items, isLargeBreakpoint, isMediumBreakpoint, isSmallBreakpoint, listHasFiles],
) )
const getRowId = useCallback((file: FileItem) => file.uuid, []) const getRowId = useCallback((item: DecryptedItemInterface) => item.uuid, [])
const table = useTable({ const table = useTable({
data: files, data: items,
sortBy, sortBy,
sortReversed, sortReversed,
onSortChange, onSortChange,
@@ -310,80 +364,103 @@ const FilesTableView = ({
columns: columnDefs, columns: columnDefs,
enableRowSelection: true, enableRowSelection: true,
enableMultipleRowSelection: true, enableMultipleRowSelection: true,
onRowActivate(file) { onRowActivate(item) {
void filesController.handleFileAction({ if (item instanceof FileItem) {
type: FileItemActionType.PreviewFile, void filesController.handleFileAction({
payload: { type: FileItemActionType.PreviewFile,
file, payload: {
otherFiles: files, file: item,
}, otherFiles: items.filter((i) => i instanceof FileItem) as FileItem[],
}) },
})
}
}, },
onRowContextMenu(x, y, file) { onRowContextMenu(x, y, file) {
setContextMenuPosition({ x, y }) setContextMenuPosition({ x, y })
setContextMenuFile(file) setContextMenuItem(file)
}, },
rowActions: (file) => { rowActions: (item) => {
return ( return (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<FileLinksCell <ItemLinksCell
file={file} item={item}
filesController={filesController} filesController={filesController}
featuresController={featuresController} featuresController={featuresController}
linkingController={linkingController} linkingController={linkingController}
/> />
<ContextMenuCell <ContextMenuCell
files={[file]} items={[item]}
filesController={filesController} filesController={filesController}
linkingController={linkingController} linkingController={linkingController}
navigationController={navigationController} navigationController={navigationController}
notesController={notesController}
historyModalController={historyModalController}
/> />
</div> </div>
) )
}, },
selectionActions: (fileIds) => ( selectionActions: (itemIds) => (
<ContextMenuCell <ContextMenuCell
files={files.filter((file) => fileIds.includes(file.uuid))} items={items.filter((item) => itemIds.includes(item.uuid))}
filesController={filesController} filesController={filesController}
linkingController={linkingController} linkingController={linkingController}
navigationController={navigationController} navigationController={navigationController}
notesController={notesController}
historyModalController={historyModalController}
/> />
), ),
showSelectionActions: true, showSelectionActions: true,
}) })
const closeContextMenu = () => {
setContextMenuPosition(undefined)
setContextMenuItem(undefined)
}
return ( return (
<> <>
<Table table={table} /> <Table table={table} />
{contextMenuPosition && contextMenuFile && ( {contextMenuPosition && contextMenuItem && (
<Popover <Popover
open={true} open={true}
anchorPoint={contextMenuPosition} anchorPoint={contextMenuPosition}
togglePopover={() => { togglePopover={() => {
setContextMenuPosition(undefined) setContextMenuPosition(undefined)
setContextMenuFile(undefined) setContextMenuItem(undefined)
}} }}
side="bottom" side="bottom"
align="start" align="start"
className="py-2" className="py-2"
> >
<Menu a11yLabel="File context menu" isOpen={true}> {contextMenuItem instanceof FileItem && (
<FileMenuOptions <Menu a11yLabel="File context menu" isOpen={true}>
closeMenu={() => { <FileMenuOptions
setContextMenuPosition(undefined) closeMenu={closeContextMenu}
setContextMenuFile(undefined) filesController={filesController}
}} shouldShowRenameOption={false}
filesController={filesController} shouldShowAttachOption={false}
shouldShowRenameOption={false} selectedFiles={[contextMenuItem]}
shouldShowAttachOption={false} linkingController={linkingController}
selectedFiles={[contextMenuFile]} navigationController={navigationController}
linkingController={linkingController} />
navigationController={navigationController} </Menu>
/> )}
</Menu> {contextMenuItem instanceof SNNote && (
<Menu className="select-none" a11yLabel="Note context menu" isOpen={true}>
<NotesOptions
notes={[contextMenuItem]}
application={application}
navigationController={navigationController}
notesController={notesController}
linkingController={linkingController}
historyModalController={historyModalController}
closeMenu={closeContextMenu}
/>
</Menu>
)}
</Popover> </Popover>
)} )}
</> </>
) )
} }
export default FilesTableView export default ContentTableView

View File

@@ -47,6 +47,7 @@ const NotesContextMenu = ({
> >
<Menu className="select-none" a11yLabel="Note context menu" isOpen={contextMenuOpen}> <Menu className="select-none" a11yLabel="Note context menu" isOpen={contextMenuOpen}>
<NotesOptions <NotesOptions
notes={notesController.selectedNotes}
application={application} application={application}
navigationController={navigationController} navigationController={navigationController}
notesController={notesController} notesController={notesController}

View File

@@ -39,6 +39,7 @@ const iconClassWarning = `text-warning mr-2 ${iconSize}`
const iconClassSuccess = `text-success mr-2 ${iconSize}` const iconClassSuccess = `text-success mr-2 ${iconSize}`
const NotesOptions = ({ const NotesOptions = ({
notes,
application, application,
navigationController, navigationController,
notesController, notesController,
@@ -61,7 +62,6 @@ const NotesOptions = ({
return notesMatchingAttribute.length > notesNotMatchingAttribute.length return notesMatchingAttribute.length > notesNotMatchingAttribute.length
} }
const notes = notesController.selectedNotes
const hidePreviews = toggleOn((note) => note.hidePreview) const hidePreviews = toggleOn((note) => note.hidePreview)
const locked = toggleOn((note) => note.locked) const locked = toggleOn((note) => note.locked)
const protect = toggleOn((note) => note.protected) const protect = toggleOn((note) => note.protected)

View File

@@ -55,6 +55,7 @@ const NotesOptionsPanel = ({
> >
<Menu a11yLabel="Note options menu" isOpen={isOpen}> <Menu a11yLabel="Note options menu" isOpen={isOpen}>
<NotesOptions <NotesOptions
notes={notesController.selectedNotes}
application={application} application={application}
navigationController={navigationController} navigationController={navigationController}
notesController={notesController} notesController={notesController}

View File

@@ -3,8 +3,10 @@ import { HistoryModalController } from '@/Controllers/NoteHistory/HistoryModalCo
import { NavigationController } from '@/Controllers/Navigation/NavigationController' import { NavigationController } from '@/Controllers/Navigation/NavigationController'
import { NotesController } from '@/Controllers/NotesController/NotesController' import { NotesController } from '@/Controllers/NotesController/NotesController'
import { LinkingController } from '@/Controllers/LinkingController' import { LinkingController } from '@/Controllers/LinkingController'
import { SNNote } from '@standardnotes/snjs'
export type NotesOptionsProps = { export type NotesOptionsProps = {
notes: SNNote[]
application: WebApplication application: WebApplication
navigationController: NavigationController navigationController: NavigationController
notesController: NotesController notesController: NotesController

View File

@@ -304,6 +304,7 @@ const PanesSystemComponent = () => {
searchOptionsController={viewControllerManager.searchOptionsController} searchOptionsController={viewControllerManager.searchOptionsController}
linkingController={viewControllerManager.linkingController} linkingController={viewControllerManager.linkingController}
featuresController={viewControllerManager.featuresController} featuresController={viewControllerManager.featuresController}
historyModalController={viewControllerManager.historyModalController}
> >
{showPanelResizers && listRef && ( {showPanelResizers && listRef && (
<PanelResizer <PanelResizer

View File

@@ -162,7 +162,8 @@ function Table<Data>({ table }: { table: Table<Data> }) {
const currentRow = Array.from(allRenderedRows).find( const currentRow = Array.from(allRenderedRows).find(
(row) => row.getAttribute('aria-rowindex') === focusedRowIndex.current.toString(), (row) => row.getAttribute('aria-rowindex') === focusedRowIndex.current.toString(),
) )
const allFocusableCells = currentRow?.querySelectorAll<HTMLElement>('[tabindex]') const allFocusableCells = Array.from(currentRow ? currentRow.querySelectorAll<HTMLElement>('[tabindex]') : [])
const allRenderedColumnsLength = headers.length
const focusCell = (rowIndex: number, colIndex: number) => { const focusCell = (rowIndex: number, colIndex: number) => {
const row = gridElement.querySelector(`[role="row"][aria-rowindex="${rowIndex}"]`) const row = gridElement.querySelector(`[role="row"][aria-rowindex="${rowIndex}"]`)
@@ -190,20 +191,42 @@ function Table<Data>({ table }: { table: Table<Data> }) {
focusCell(nextRow, focusedCellIndex.current) focusCell(nextRow, focusedCellIndex.current)
} }
break break
case KeyboardKey.Left: case KeyboardKey.Left: {
event.preventDefault() event.preventDefault()
if (focusedCellIndex.current > 1) { if (!allFocusableCells) {
const previousCell = focusedCellIndex.current - 1 return
focusCell(focusedRowIndex.current, previousCell)
} }
const currentCellIndex = allFocusableCells.findIndex(
(cell) => parseInt(cell.getAttribute('aria-colindex') || '0') === focusedCellIndex.current,
)
if (currentCellIndex === 0) {
return
}
const previousCell = allFocusableCells[currentCellIndex - 1]
if (!previousCell) {
return
}
previousCell.focus()
break break
case KeyboardKey.Right: }
case KeyboardKey.Right: {
event.preventDefault() event.preventDefault()
if (focusedCellIndex.current < colCount) { if (!allFocusableCells) {
const nextCell = focusedCellIndex.current + 1 return
focusCell(focusedRowIndex.current, nextCell)
} }
const currentCellIndex = allFocusableCells.findIndex(
(cell) => parseInt(cell.getAttribute('aria-colindex') || '0') === focusedCellIndex.current,
)
if (currentCellIndex === allFocusableCells.length - 1) {
return
}
const nextCell = allFocusableCells[currentCellIndex + 1]
if (!nextCell) {
return
}
nextCell.focus()
break break
}
case KeyboardKey.Home: case KeyboardKey.Home:
event.preventDefault() event.preventDefault()
if (event.ctrlKey) { if (event.ctrlKey) {
@@ -225,7 +248,7 @@ function Table<Data>({ table }: { table: Table<Data> }) {
case KeyboardKey.End: { case KeyboardKey.End: {
event.preventDefault() event.preventDefault()
if (event.ctrlKey) { if (event.ctrlKey) {
focusCell(allRenderedRows.length, headers.length || colCount) focusCell(allRenderedRows.length, allRenderedColumnsLength || colCount)
return return
} }
if (!allFocusableCells) { if (!allFocusableCells) {
@@ -338,7 +361,7 @@ function Table<Data>({ table }: { table: Table<Data> }) {
return ( return (
<div className="block min-h-0 overflow-auto" onScroll={onScroll}> <div className="block min-h-0 overflow-auto" onScroll={onScroll}>
{showSelectionActions && selectedRows.length >= 2 && ( {showSelectionActions && selectedRows.length >= 2 && (
<div className="flex items-center justify-between border-b border-border px-3 py-2"> <div className="sticky top-0 z-[2] flex items-center justify-between border-b border-border bg-default px-3 py-2">
<span className="text-info-0 text-sm font-medium">{selectedRows.length} selected</span> <span className="text-info-0 text-sm font-medium">{selectedRows.length} selected</span>
{selectedRows.length > 0 && selectionActions} {selectedRows.length > 0 && selectionActions}
</div> </div>