diff --git a/packages/nicolium/src/pages/drive/drive.tsx b/packages/nicolium/src/pages/drive/drive.tsx index 5cc4d15ac..4d1c52424 100644 --- a/packages/nicolium/src/pages/drive/drive.tsx +++ b/packages/nicolium/src/pages/drive/drive.tsx @@ -2,7 +2,7 @@ import defaultIcon from '@phosphor-icons/core/regular/paperclip.svg'; import { Link, useNavigate } from '@tanstack/react-router'; import { clsx } from 'clsx'; import { mediaAttachmentSchema, type DriveFile, type DriveFolder } from 'pl-api'; -import React, { useMemo, useRef } from 'react'; +import React, { useMemo, useRef, useState } from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import * as v from 'valibot'; @@ -233,11 +233,14 @@ const Breadcrumbs: React.FC = ({ folderId, depth = 0, onClick }) = interface IFile { file: DriveFile; + index: number; + onMove: (index: number, direction: 'home' | 'end' | 'up' | 'down') => void; } -const File: React.FC = ({ file }) => { +const File: React.FC = ({ file, index, onMove }) => { const intl = useIntl(); const fileRef = useRef(null); + const [isDropdownOpen, setIsDropdownOpen] = useState(false); const { openModal } = useModalsActions(); const { mutate: updateFile } = useUpdateDriveFileMutation(file.id); @@ -274,12 +277,39 @@ const File: React.FC = ({ file }) => { }; const handleFileKeyDown: React.KeyboardEventHandler = (e) => { + if (isDropdownOpen) return; + switch (e.key) { case 'Enter': case ' ': handleView(); e.preventDefault(); e.stopPropagation(); + break; + case 'Home': + case 'PageUp': + onMove(index, 'home'); + e.preventDefault(); + e.stopPropagation(); + break; + case 'End': + case 'PageDown': + onMove(index, 'end'); + e.preventDefault(); + e.stopPropagation(); + break; + case 'ArrowUp': + case 'ArrowLeft': + onMove(index, 'up'); + e.preventDefault(); + e.stopPropagation(); + break; + case 'ArrowDown': + case 'ArrowRight': + onMove(index, 'down'); + e.preventDefault(); + e.stopPropagation(); + break; } }; @@ -473,9 +503,19 @@ const File: React.FC = ({ file }) => { onDoubleClick={handleView} onKeyDown={handleFileKeyDown} onContextMenu={handleContextMenu} + data-index={index} >
- + { + setIsDropdownOpen(true); + }} + onClose={() => { + setIsDropdownOpen(false); + }} + > = ({ file }) => { interface IFolder { folder: DriveFolder; + index: number; + onMove: (index: number, direction: 'home' | 'end' | 'up' | 'down') => void; } -const Folder: React.FC = ({ folder }) => { +const Folder: React.FC = ({ folder, index, onMove }) => { const navigate = useNavigate(); const intl = useIntl(); const folderRef = useRef(null); + const [isDropdownOpen, setIsDropdownOpen] = useState(false); const { openModal } = useModalsActions(); const { mutate: deleteFolder } = useDeleteDriveFolderMutation(folder.id!); @@ -517,11 +560,39 @@ const Folder: React.FC = ({ folder }) => { }; const handleFolderKeyDown: React.KeyboardEventHandler = (e) => { + if (isDropdownOpen) return; + switch (e.key) { case 'Enter': + case ' ': handleEnterFolder(); e.preventDefault(); e.stopPropagation(); + break; + case 'Home': + case 'PageUp': + onMove(index, 'home'); + e.preventDefault(); + e.stopPropagation(); + break; + case 'End': + case 'PageDown': + onMove(index, 'end'); + e.preventDefault(); + e.stopPropagation(); + break; + case 'ArrowUp': + case 'ArrowLeft': + onMove(index, 'up'); + e.preventDefault(); + e.stopPropagation(); + break; + case 'ArrowDown': + case 'ArrowRight': + onMove(index, 'down'); + e.preventDefault(); + e.stopPropagation(); + break; } }; @@ -630,9 +701,19 @@ const Folder: React.FC = ({ folder }) => { onDoubleClick={handleEnterFolder} onKeyDown={handleFolderKeyDown} onContextMenu={handleContextMenu} + data-index={index} >
- + { + setIsDropdownOpen(true); + }} + onClose={() => { + setIsDropdownOpen(false); + }} + > = ({ folder }) => { }; const DrivePage: React.FC = () => { - const { folderId } = driveRoute.useParams(); - + const filesRef = useRef(null); const intl = useIntl(); + const { folderId } = driveRoute.useParams(); + const { openModal } = useModalsActions(); const { data, isPending } = useDriveFolderQuery(folderId); @@ -704,6 +786,20 @@ const DrivePage: React.FC = () => { }, ]; + const handleMove = (index: number, direction: 'home' | 'end' | 'up' | 'down') => { + const totalItems = data!.files.length + data!.folders.length; + const newItem = + direction === 'home' + ? 0 + : direction === 'end' + ? totalItems - 1 + : direction === 'up' + ? index - 1 + : index + 1; + if (newItem < 0 || newItem >= totalItems) return; + (filesRef.current?.querySelector(`div[data-index="${newItem}"]`) as HTMLDivElement)?.focus(); + }; + if (isPending) { return ; } @@ -737,12 +833,17 @@ const DrivePage: React.FC = () => { icon={require('@phosphor-icons/core/regular/folder-open.svg')} /> ) : ( -
- {data?.folders.map((folder) => ( - +
+ {data?.folders.map((folder, index) => ( + ))} - {data?.files.map((file) => ( - + {data?.files.map((file, index) => ( + ))}
)}