From 4b01eccdfda5ddae8cf557b67b688cfd6d95c76f Mon Sep 17 00:00:00 2001 From: Alex Patin Date: Tue, 24 Feb 2026 21:30:27 -0500 Subject: [PATCH] feat: add sort controls on list view column headers Clickable Name/Size/Modified headers with ArrowUp/ArrowDown/ArrowUpDown indicators. Directories-first ordering preserved, sort within groups. --- packages/web/src/components/FileList.tsx | 85 ++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 5 deletions(-) diff --git a/packages/web/src/components/FileList.tsx b/packages/web/src/components/FileList.tsx index e397295..35965c0 100644 --- a/packages/web/src/components/FileList.tsx +++ b/packages/web/src/components/FileList.tsx @@ -1,4 +1,5 @@ -import { Download, Folder, Link2, Trash2 } from 'lucide-react' +import { ArrowDown, ArrowUp, ArrowUpDown, Download, Folder, Link2, Trash2 } from 'lucide-react' +import { useMemo, useState } from 'react' import type { FileEntry } from '../lib/api' import { api } from '../lib/api' import { getFileIcon } from '../lib/fileIcons' @@ -29,6 +30,9 @@ function formatDateShort(iso: string): string { }) } +type SortField = 'name' | 'size' | 'modified' +type SortDir = 'asc' | 'desc' + interface FileListProps { entries: FileEntry[] volume: string @@ -46,23 +50,94 @@ export default function FileList({ onPreview, onShare, }: FileListProps) { + const [sortField, setSortField] = useState('name') + const [sortDir, setSortDir] = useState('asc') + + const toggleSort = (field: SortField) => { + if (sortField === field) { + setSortDir((d) => (d === 'asc' ? 'desc' : 'asc')) + } else { + setSortField(field) + setSortDir('asc') + } + } + + const sortedEntries = useMemo(() => { + const dirs = entries.filter((e) => e.isDirectory) + const files = entries.filter((e) => !e.isDirectory) + + const compare = (a: FileEntry, b: FileEntry): number => { + let result = 0 + switch (sortField) { + case 'name': + result = a.name.localeCompare(b.name, undefined, { sensitivity: 'base' }) + break + case 'size': + result = a.size - b.size + break + case 'modified': + result = new Date(a.modifiedAt).getTime() - new Date(b.modifiedAt).getTime() + break + } + return sortDir === 'asc' ? result : -result + } + + return [...dirs.sort(compare), ...files.sort(compare)] + }, [entries, sortField, sortDir]) + if (entries.length === 0) { return } + const SortIcon = ({ field }: { field: SortField }) => { + if (sortField !== field) return + return sortDir === 'asc' ? ( + + ) : ( + + ) + } + return (
- - - + + + - {entries.map((entry) => ( + {sortedEntries.map((entry) => (
NameSizeModified + + + + + +