diff --git a/apps/client/src/ee/base/components/base-table.tsx b/apps/client/src/ee/base/components/base-table.tsx index 9bef830a9..be449c3f9 100644 --- a/apps/client/src/ee/base/components/base-table.tsx +++ b/apps/client/src/ee/base/components/base-table.tsx @@ -18,7 +18,7 @@ type BaseTableProps = { isFetchingNextPage: boolean; onFetchNextPage: () => void; onCellUpdate: (rowId: string, propertyId: string, value: unknown) => void; - onAddRow: () => void; + onAddRow: (afterRowId?: string, focusPropertyId?: string) => void; onColumnReorder: (columnId: string, finishIndex: number) => void; onResizeEnd: () => void; onRowReorder: ( diff --git a/apps/client/src/ee/base/components/base-view.tsx b/apps/client/src/ee/base/components/base-view.tsx index 17bbca60d..22612f265 100644 --- a/apps/client/src/ee/base/components/base-view.tsx +++ b/apps/client/src/ee/base/components/base-view.tsx @@ -12,6 +12,7 @@ import { FilterGroup, ViewSortConfig, EditingCell, + FocusedCell, IBaseProperty, } from "@/ee/base/types/base.types"; import { @@ -25,6 +26,7 @@ import { useUpdateViewMutation } from "@/ee/base/queries/base-view-query"; import { activeViewIdAtomFamily, editingCellAtomFamily, + focusedCellAtomFamily, } from "@/ee/base/atoms/base-atoms"; import { useBaseTable } from "@/ee/base/hooks/use-base-table"; import { isSystemPropertyType } from "@/ee/base/property-types/property-type.registry"; @@ -89,6 +91,10 @@ export function BaseView({ pageId, embedded, editable = true, titleSlot }: BaseV editingCellAtomFamily(pageId), ) as unknown as [EditingCell, (val: EditingCell) => void]; + const [, setFocusedCell] = useAtom( + focusedCellAtomFamily(pageId), + ) as unknown as [FocusedCell, (val: FocusedCell) => void]; + const views = useMemo( () => [...(base?.views ?? [])].sort((a, b) => @@ -221,33 +227,42 @@ export function BaseView({ pageId, embedded, editable = true, titleSlot }: BaseV [editable, pageId, updateRow], ); - const handleAddRow = useCallback(() => { - if (!editable) return; - createRowMutation.mutate( - { pageId }, - { - onSuccess: (newRow) => { - const firstEditable = table.getVisibleLeafColumns().find((col) => { - if (col.id === "__row_number") return false; - const prop = col.columnDef.meta?.property as - | IBaseProperty - | undefined; - return ( - !!prop && - prop.type !== "checkbox" && - !isSystemPropertyType(prop.type) - ); - }); - const propertyId = ( - firstEditable?.columnDef.meta?.property as IBaseProperty | undefined - )?.id; - if (propertyId) { - setEditingCell({ rowId: newRow.id, propertyId }); - } + const handleAddRow = useCallback( + (afterRowId?: string, focusPropertyId?: string) => { + if (!editable) return; + createRowMutation.mutate( + { pageId, ...(afterRowId ? { afterRowId } : {}) }, + { + onSuccess: (newRow) => { + let propertyId = focusPropertyId; + if (!propertyId) { + const firstEditable = table.getVisibleLeafColumns().find((col) => { + if (col.id === "__row_number") return false; + const prop = col.columnDef.meta?.property as + | IBaseProperty + | undefined; + return ( + !!prop && + prop.type !== "checkbox" && + !isSystemPropertyType(prop.type) + ); + }); + propertyId = ( + firstEditable?.columnDef.meta?.property as + | IBaseProperty + | undefined + )?.id; + } + if (propertyId) { + setEditingCell({ rowId: newRow.id, propertyId }); + setFocusedCell({ rowId: newRow.id, propertyId }); + } + }, }, - }, - ); - }, [editable, pageId, createRowMutation, table, setEditingCell]); + ); + }, + [editable, pageId, createRowMutation, table, setEditingCell, setFocusedCell], + ); const handleViewChange = useCallback( (viewId: string) => { diff --git a/apps/client/src/ee/base/components/grid/grid-container.tsx b/apps/client/src/ee/base/components/grid/grid-container.tsx index 1c75dcf25..52bb6580f 100644 --- a/apps/client/src/ee/base/components/grid/grid-container.tsx +++ b/apps/client/src/ee/base/components/grid/grid-container.tsx @@ -67,7 +67,7 @@ type GridContainerProps = { table: Table; properties: IBaseProperty[]; onCellUpdate: (rowId: string, propertyId: string, value: unknown) => void; - onAddRow?: () => void; + onAddRow?: (afterRowId?: string, focusPropertyId?: string) => void; pageId: string; onColumnReorder?: (columnId: string, finishIndex: number) => void; onResizeEnd?: () => void; @@ -378,6 +378,13 @@ export function GridContainer({ [table, setFocusedCell], ); + const handleAddRowBelow = useCallback( + (afterRowId: string, focusPropertyId: string) => { + onAddRow?.(afterRowId, focusPropertyId); + }, + [onAddRow], + ); + useGridKeyboardNav({ table, properties, @@ -395,6 +402,7 @@ export function GridContainer({ deleteSelected, toggleRowSelection, expandRow, + addRow: handleAddRowBelow, }); const activeCell = editingCell ?? focusedCell; diff --git a/apps/client/src/ee/base/components/views/view-renderer.tsx b/apps/client/src/ee/base/components/views/view-renderer.tsx index c7761d75b..9595a3697 100644 --- a/apps/client/src/ee/base/components/views/view-renderer.tsx +++ b/apps/client/src/ee/base/components/views/view-renderer.tsx @@ -21,7 +21,7 @@ type ViewRendererProps = { isFetchingNextPage: boolean; onFetchNextPage: () => void; onCellUpdate: (rowId: string, propertyId: string, value: unknown) => void; - onAddRow: () => void; + onAddRow: (afterRowId?: string, focusPropertyId?: string) => void; onColumnReorder: (columnId: string, finishIndex: number) => void; onResizeEnd: () => void; onRowReorder: ( diff --git a/apps/client/src/ee/base/hooks/use-grid-keyboard-nav.ts b/apps/client/src/ee/base/hooks/use-grid-keyboard-nav.ts index f8bad8af1..09d140e48 100644 --- a/apps/client/src/ee/base/hooks/use-grid-keyboard-nav.ts +++ b/apps/client/src/ee/base/hooks/use-grid-keyboard-nav.ts @@ -1,4 +1,4 @@ -import { useCallback, useEffect } from "react"; +import { useCallback, useEffect, useMemo } from "react"; import { Table } from "@tanstack/react-table"; import { IBaseRow, @@ -26,6 +26,7 @@ type UseGridKeyboardNavOptions = { deleteSelected: () => void | Promise; toggleRowSelection: (rowId: string) => void; expandRow: (rowId: string) => void; + addRow: (afterRowId: string, focusPropertyId: string) => void; }; const isPrintableKey = (e: KeyboardEvent) => @@ -54,6 +55,7 @@ export function useGridKeyboardNav({ deleteSelected, toggleRowSelection, expandRow, + addRow, }: UseGridKeyboardNavOptions) { const getColIds = useCallback( () => @@ -79,6 +81,11 @@ export function useGridKeyboardNav({ [properties], ); + const primaryPropertyId = useMemo( + () => properties.find((p) => p.isPrimary)?.id, + [properties], + ); + const goEditing = useCallback( (next: CellCoord) => { (document.activeElement as HTMLElement | null)?.blur(); @@ -142,6 +149,12 @@ export function useGridKeyboardNav({ } case "Enter": { e.preventDefault(); + if (e.shiftKey && editingCell.propertyId === primaryPropertyId) { + (document.activeElement as HTMLElement | null)?.blur(); + setEditingCell(null); + addRow(editingCell.rowId, editingCell.propertyId); + break; + } const next = computeNextCell( getRowIds(), getColIds(), @@ -241,7 +254,13 @@ export function useGridKeyboardNav({ case "Enter": case "F2": e.preventDefault(); - if (focusedCell.propertyId === "__row_number") { + if ( + e.key === "Enter" && + e.shiftKey && + focusedCell.propertyId === primaryPropertyId + ) { + addRow(focusedCell.rowId, focusedCell.propertyId); + } else if (focusedCell.propertyId === "__row_number") { toggleRowSelection(focusedCell.rowId); } else { openEditor(focusedCell); @@ -284,6 +303,8 @@ export function useGridKeyboardNav({ deleteSelected, toggleRowSelection, expandRow, + primaryPropertyId, + addRow, ], );