diff --git a/apps/studio/components/grid/components/menu/ColumnMenu.tsx b/apps/studio/components/grid/components/menu/ColumnMenu.tsx
index 5ad77c71d28..7cf199e619e 100644
--- a/apps/studio/components/grid/components/menu/ColumnMenu.tsx
+++ b/apps/studio/components/grid/components/menu/ColumnMenu.tsx
@@ -1,10 +1,13 @@
-import { ChevronDown, Edit, Lock, Trash, Unlock } from 'lucide-react'
+import type { Sort } from 'components/grid/types'
+import { ArrowDown, ArrowUp, ChevronDown, Edit, Lock, Trash, Unlock } from 'lucide-react'
import type { CalculatedColumn } from 'react-data-grid'
+import { useTableSort } from 'components/grid/hooks/useTableSort'
import { useTableEditorStateSnapshot } from 'state/table-editor'
import { useTableEditorTableStateSnapshot } from 'state/table-editor-table'
import {
Button,
+ cn,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
@@ -23,8 +26,10 @@ interface ColumnMenuProps {
const ColumnMenu = ({ column, isEncrypted }: ColumnMenuProps) => {
const tableEditorSnap = useTableEditorStateSnapshot()
const snap = useTableEditorTableStateSnapshot()
+ const { sorts, addOrUpdateSort, removeSort } = useTableSort()
const columnKey = column.key
+ const columnName = column.name as string
function onFreezeColumn() {
snap.freezeColumn(columnKey)
@@ -48,7 +53,22 @@ const ColumnMenu = ({ column, isEncrypted }: ColumnMenuProps) => {
}
}
+ function onSortColumn(ascending: boolean) {
+ if (!columnKey) return
+ const currentSort = sorts.find((s) => s.column === columnKey)
+
+ if (currentSort && currentSort.ascending === ascending) {
+ // Clicked the currently active sort: Remove it
+ removeSort(columnKey)
+ } else {
+ // Clicked the inactive sort or column wasn't sorted: Add or update it
+ addOrUpdateSort(columnKey, ascending)
+ }
+ }
+
function renderMenu() {
+ const currentSort: Sort | undefined = sorts.find((s) => s.column === columnKey)
+
return (
<>
{snap.editable && (
@@ -80,6 +100,29 @@ const ColumnMenu = ({ column, isEncrypted }: ColumnMenuProps) => {
>
)}
+
+ onSortColumn(true)}
+ >
+
+ Sort Ascending
+
+ onSortColumn(false)}
+ >
+
+ Sort Descending
+
{snap.editable && (
<>
diff --git a/apps/studio/components/grid/hooks/useTableSort.ts b/apps/studio/components/grid/hooks/useTableSort.ts
index b40352a25b4..9ba34d77d62 100644
--- a/apps/studio/components/grid/hooks/useTableSort.ts
+++ b/apps/studio/components/grid/hooks/useTableSort.ts
@@ -44,9 +44,58 @@ export function useTableSort() {
[snap, setParams, saveSortsAndTriggerSideEffects]
)
+ /**
+ * Adds a new sort for a column or updates the direction of an existing one.
+ * New sorts are added to the beginning of the array (highest precedence).
+ * If the column already exists, its `ascending` direction is updated.
+ * Calls `onApplySorts` to update URL parameters and trigger side effects.
+ *
+ * @param columnKey The key/name of the column to sort.
+ * @param ascending The sort direction (true for ascending, false for descending).
+ */
+ const addOrUpdateSort = useCallback(
+ (columnKey: string, ascending: boolean) => {
+ if (!tableName || !columnKey) return
+
+ // Use the derived 'sorts' state from the hook
+ const existingSortIndex = sorts.findIndex((s) => s.column === columnKey)
+ let newSorts = [...sorts] // Create a mutable copy
+
+ if (existingSortIndex !== -1) {
+ // Column already exists in sorts: Update the existing sort (toggle handled by removeSort)
+ newSorts[existingSortIndex] = { ...newSorts[existingSortIndex], ascending: ascending }
+ } else {
+ // Column doesn't exist in sorts: Add it to the beginning
+ newSorts.unshift({ table: tableName, column: columnKey, ascending: ascending })
+ }
+
+ onApplySorts(newSorts)
+ },
+ [tableName, sorts, onApplySorts] // Depend on derived sorts and callback
+ )
+
+ /**
+ * Removes a sort criterion for a specific column.
+ * Calls `onApplySorts` with the filtered array to update URL parameters and trigger side effects.
+ *
+ * @param columnKey The key/name of the column to remove from sorting.
+ */
+ const removeSort = useCallback(
+ (columnKey: string) => {
+ if (!tableName || !columnKey) return
+
+ // Use the derived 'sorts' state from the hook
+ const newSorts = sorts.filter((s) => s.column !== columnKey)
+ onApplySorts(newSorts)
+ },
+ [tableName, sorts, onApplySorts] // Depend on derived sorts and callback
+ )
+
return {
sorts,
urlSorts,
onApplySorts,
+ addOrUpdateSort,
+ removeSort,
}
}