From dae4e6ef09b7527e32bdf129af11c6d1fb434804 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 28 Mar 2026 16:03:46 +0100 Subject: [PATCH] scaffolder: improve actions page UX with pagination and card detail view Replace the unpaginated action list with a paginated table showing 20 items per page, giving users a clear count and navigation controls. Consolidate the two-column layout into a single column with the description shown as subtitle text in each row. Revamp the action detail view using Backstage UI Card and Accordion components for a structured, collapsible layout of input/output schemas and usage examples. Signed-off-by: Patrik Oldsberg Made-with: Cursor --- .../ActionsPage/ActionsPage.test.tsx | 4 +- .../components/ActionsPage/ActionsPage.tsx | 159 +++++++++--------- 2 files changed, 82 insertions(+), 81 deletions(-) diff --git a/plugins/scaffolder/src/components/ActionsPage/ActionsPage.test.tsx b/plugins/scaffolder/src/components/ActionsPage/ActionsPage.test.tsx index 9f91cd6e1c..d11b47498d 100644 --- a/plugins/scaffolder/src/components/ActionsPage/ActionsPage.test.tsx +++ b/plugins/scaffolder/src/components/ActionsPage/ActionsPage.test.tsx @@ -94,8 +94,8 @@ describe('ActionsPage', () => { await selectAction('test'); - expect(screen.getByText('Test title')).toBeVisible(); - expect(screen.getByText('foobar')).toBeVisible(); + expect(await screen.findByText('Test title')).toBeInTheDocument(); + expect(screen.getByText('foobar')).toBeInTheDocument(); }); it('renders action with input and output on row click', async () => { diff --git a/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx b/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx index 22c74c2556..1088a44708 100644 --- a/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx +++ b/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx @@ -16,10 +16,8 @@ import { useMemo, useState } from 'react'; import useAsync from 'react-use/esm/useAsync'; import { Action, scaffolderApiRef } from '@backstage/plugin-scaffolder-react'; -import Box from '@material-ui/core/Box'; import Typography from '@material-ui/core/Typography'; import { makeStyles } from '@material-ui/core/styles'; -import LinkIcon from '@material-ui/icons/Link'; import { useApi, useRouteRef } from '@backstage/core-plugin-api'; import { @@ -27,14 +25,21 @@ import { EmptyState, ErrorPanel, Header, - Link, - MarkdownContent, Page, } from '@backstage/core-components'; import { + Accordion, + AccordionGroup, + AccordionPanel, + AccordionTrigger, + Card, + CardBody, + CardHeader, CellText, + Flex, SearchField, Table, + Text, useTable, type ColumnConfig, type TableItem, @@ -76,9 +81,6 @@ const useStyles = makeStyles(theme => ({ color: theme.palette.error.light, }, }, - link: { - paddingLeft: theme.spacing(1), - }, })); interface ActionTableItem extends TableItem { @@ -96,64 +98,69 @@ function ActionDetail({ action }: { action: Action }) { headings: [], }; + const hasInput = !!action.schema?.input; + const hasOutput = !!action.schema?.output; + const hasExamples = !!action.examples; + return ( - - - - {action.id} - - - - - - {action.description && } - {action.schema?.input && ( - - - {t('actionsPage.action.input')} - - - + + + + + {action.id} + + {action.description && ( + + {action.description} + + )} + + + {(hasInput || hasOutput || hasExamples) && ( + + + {hasInput && ( + + + + + + + )} + {hasOutput && ( + + + + + + + )} + {hasExamples && ( + + + + + + + )} + + )} - {action.schema?.output && ( - - - {t('actionsPage.action.output')} - - - - )} - {action.examples && ( - - - {t('actionsPage.action.examples')} - - - - )} - + ); } @@ -163,13 +170,12 @@ const columnConfig: ColumnConfig[] = [ label: 'Name', isRowHeader: true, defaultWidth: '1fr', - cell: item => , - }, - { - id: 'description', - label: 'Description', - defaultWidth: '2fr', - cell: item => , + cell: item => ( + + ), }, ]; @@ -203,7 +209,7 @@ export const ActionPageContent = () => { const { tableProps, search } = useTable({ mode: 'complete', data: tableData, - paginationOptions: { type: 'none' }, + paginationOptions: { pageSize: 20 }, searchFn: (items, query) => { const lowerQuery = query.toLowerCase(); return items.filter( @@ -228,11 +234,10 @@ export const ActionPageContent = () => { } return ( - <> + { }, }} /> - {selectedAction && ( - - - - )} - + {selectedAction && } + ); };