From c123727f3ab6f4c004ed9754faee595bb842b2dd Mon Sep 17 00:00:00 2001
From: FTCHD <144691102+FTCHD@users.noreply.github.com>
Date: Mon, 13 Jul 2026 03:03:16 +0300
Subject: [PATCH] commander sorting
---
src/components/navigator/FilePanel.tsx | 52 ++++++++++++--
src/components/navigator/useFileNavigation.ts | 72 ++++++++++++++++++-
2 files changed, 116 insertions(+), 8 deletions(-)
diff --git a/src/components/navigator/FilePanel.tsx b/src/components/navigator/FilePanel.tsx
index 3d32e6b..0257f89 100644
--- a/src/components/navigator/FilePanel.tsx
+++ b/src/components/navigator/FilePanel.tsx
@@ -1,6 +1,6 @@
import { Button, Divider, Tooltip } from '@heroui/react'
import { useQuery } from '@tanstack/react-query'
-import { FolderPlusIcon } from 'lucide-react'
+import { ChevronDownIcon, ChevronUpIcon, FolderPlusIcon } from 'lucide-react'
import {
forwardRef,
startTransition,
@@ -352,12 +352,54 @@ const FilePanel = forwardRef<
-
Name
-
Size
-
Last Modified
+ {(
+ [
+ { column: 'name', label: 'Name', className: 'pl-2' },
+ { column: 'size', label: 'Size', className: '' },
+ {
+ column: 'modTime',
+ label: 'Last Modified',
+ className: '',
+ },
+ ] as const
+ ).map(({ column, label, className }) => {
+ const isSorted = nav.sortDescriptor.column === column
+ const nextDirection =
+ isSorted && nav.sortDescriptor.direction === 'ascending'
+ ? 'descending'
+ : 'ascending'
+ const sortStatus = isSorted
+ ? `, sorted ${nav.sortDescriptor.direction}`
+ : ''
+ return (
+
+
+
+ )
+ })}
diff --git a/src/components/navigator/useFileNavigation.ts b/src/components/navigator/useFileNavigation.ts
index 673624c..0231609 100644
--- a/src/components/navigator/useFileNavigation.ts
+++ b/src/components/navigator/useFileNavigation.ts
@@ -27,6 +27,11 @@ import {
serializeRemotePath,
} from './utils'
+const nameCollator = new Intl.Collator(undefined, {
+ numeric: true,
+ sensitivity: 'base',
+})
+
export default function useFileNavigation({
initialRemote,
initialPath,
@@ -56,6 +61,10 @@ export default function useFileNavigation({
const [cwd, setCwd] = useState
(initialPath ?? '')
const [pathInput, setPathInput] = useState('')
const [searchTerm, setSearchTerm] = useState('')
+ const [sortDescriptor, setSortDescriptor] = useState<{
+ column: 'name' | 'size' | 'modTime'
+ direction: 'ascending' | 'descending'
+ }>({ column: 'name', direction: 'ascending' })
const [items, setItems] = useState([])
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState(null)
@@ -88,10 +97,65 @@ export default function useFileNavigation({
const visibleItems = useMemo(() => {
const base = allowFiles ? items : items.filter((it) => it.isDir)
- if (!searchTerm) return base
const lower = searchTerm.toLowerCase()
- return base.filter((item) => item.name.toLowerCase().includes(lower))
- }, [allowFiles, items, searchTerm])
+ const filtered = searchTerm
+ ? base.filter((item) => item.name.toLowerCase().includes(lower))
+ : base
+ const direction = sortDescriptor.direction === 'ascending' ? 1 : -1
+
+ return [...filtered].sort((a, b) => {
+ if (a.isDir !== b.isDir) return a.isDir ? -1 : 1
+
+ const nameComparison =
+ nameCollator.compare(a.name, b.name) ||
+ a.name.localeCompare(b.name) ||
+ a.key.localeCompare(b.key)
+
+ if (sortDescriptor.column === 'name') return nameComparison * direction
+
+ const aValue =
+ sortDescriptor.column === 'size'
+ ? typeof a.size === 'number' && a.size >= 0
+ ? a.size
+ : undefined
+ : a.modTime
+ ? Date.parse(a.modTime)
+ : undefined
+ const bValue =
+ sortDescriptor.column === 'size'
+ ? typeof b.size === 'number' && b.size >= 0
+ ? b.size
+ : undefined
+ : b.modTime
+ ? Date.parse(b.modTime)
+ : undefined
+ const normalizedA =
+ aValue !== undefined && Number.isFinite(aValue) ? aValue : undefined
+ const normalizedB =
+ bValue !== undefined && Number.isFinite(bValue) ? bValue : undefined
+
+ if (normalizedA === undefined && normalizedB !== undefined) return 1
+ if (normalizedA !== undefined && normalizedB === undefined) return -1
+ if (
+ normalizedA !== undefined &&
+ normalizedB !== undefined &&
+ normalizedA !== normalizedB
+ ) {
+ return (normalizedA - normalizedB) * direction
+ }
+ return nameComparison
+ })
+ }, [allowFiles, items, searchTerm, sortDescriptor])
+
+ const handleSort = useCallback((column: 'name' | 'size' | 'modTime') => {
+ setSortDescriptor((current) => ({
+ column,
+ direction:
+ current.column === column && current.direction === 'ascending'
+ ? 'descending'
+ : 'ascending',
+ }))
+ }, [])
const virtualizedItems: (VirtualizedEntry | PaddingItem)[] = useMemo(() => {
const base: (VirtualizedEntry | PaddingItem)[] = visibleItems.map((item) => ({
@@ -783,6 +847,7 @@ export default function useFileNavigation({
error,
isUpDisabled,
searchTerm,
+ sortDescriptor,
selectedPaths,
selectedCount,
isRemote,
@@ -795,6 +860,7 @@ export default function useFileNavigation({
// Actions
setPathInput,
setSearchTerm,
+ handleSort,
handleNavigate,
navigateUp,
navigateTo,