From 9a6d9969a6fedf2e4a9fe8e388c1cf5ab4432785 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 28 Mar 2026 18:28:41 +0100 Subject: [PATCH] scaffolder: use CSS grid layout to fix search field focus loss The search field was being rendered in two separate conditional branches (empty state vs list state), causing React to remount it when toggling between them and losing focus. Restructure to a CSS grid layout where the search field is always in a stable tree position. Signed-off-by: Patrik Oldsberg Made-with: Cursor --- .../ActionsPage/ActionsPage.test.tsx | 38 +++++++ .../components/ActionsPage/ActionsPage.tsx | 105 ++++++++---------- 2 files changed, 87 insertions(+), 56 deletions(-) diff --git a/plugins/scaffolder/src/components/ActionsPage/ActionsPage.test.tsx b/plugins/scaffolder/src/components/ActionsPage/ActionsPage.test.tsx index b56d652a83..cc789d8ea9 100644 --- a/plugins/scaffolder/src/components/ActionsPage/ActionsPage.test.tsx +++ b/plugins/scaffolder/src/components/ActionsPage/ActionsPage.test.tsx @@ -689,4 +689,42 @@ describe('ActionsPage', () => { screen.getByRole('row', { name: /github:repo:push/ }), ).toBeInTheDocument(); }); + + it('should keep search field focused when filtering causes empty then non-empty results', async () => { + scaffolderApiMock.listActions.mockResolvedValue([ + { + id: 'github:repo:create', + description: 'Create a new Github repository', + schema: {}, + }, + ]); + + await renderInTestApp( + + + , + { + mountedRoutes: { + '/create/actions': rootRouteRef, + }, + }, + ); + + const searchField = screen.getByPlaceholderText('Search for an action'); + await userEvent.click(searchField); + expect(searchField).toHaveFocus(); + + await userEvent.type(searchField, 'zzz-no-match'); + expect(searchField).toHaveFocus(); + expect( + screen.queryByRole('row', { name: /github:repo:create/ }), + ).not.toBeInTheDocument(); + + await userEvent.clear(searchField); + await userEvent.type(searchField, 'create'); + expect(searchField).toHaveFocus(); + expect( + await screen.findByRole('row', { name: /github:repo:create/ }), + ).toBeInTheDocument(); + }); }); diff --git a/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx b/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx index caf14c520b..342d3e377d 100644 --- a/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx +++ b/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx @@ -149,68 +149,61 @@ export const ActionPageContent = () => { ); } - if (!loading && !filteredActions.length) { - return ( - - + return ( +
+ + {!loading && !filteredActions.length ? ( - - ); - } - - const listElement = ( - { - if (selection === 'all') { - return; - } - const selected = [...selection][0] as string | undefined; - setSelectedActionId(prev => (prev === selected ? undefined : selected)); - }} - > - {filteredActions.map(action => ( - { + if (selection === 'all') { + return; + } + const selected = [...selection][0] as string | undefined; + setSelectedActionId(prev => + prev === selected ? undefined : selected, + ); + }} > - {action.id} - - ))} - - ); - - return ( - - - - {listElement} - + {filteredActions.map(action => ( + + {action.id} + + ))} + + )} {selectedAction && ( - + {selectedAction.id} @@ -222,7 +215,7 @@ export const ActionPageContent = () => { )} - +
); };