From a9bfcbe6fd627ebd41fd145b08e63c1d6600d7e9 Mon Sep 17 00:00:00 2001 From: Johan Persson Date: Thu, 26 Feb 2026 16:35:55 +0100 Subject: [PATCH 01/25] feat(catalog-react): add EntityInfoCard BUI wrapper component Signed-off-by: Johan Persson --- plugins/catalog-react/report.api.md | 17 +++ .../EntityInfoCard/EntityInfoCard.test.tsx | 102 ++++++++++++++++++ .../EntityInfoCard/EntityInfoCard.tsx | 56 ++++++++++ .../src/components/EntityInfoCard/index.ts | 18 ++++ plugins/catalog-react/src/components/index.ts | 1 + 5 files changed, 194 insertions(+) create mode 100644 plugins/catalog-react/src/components/EntityInfoCard/EntityInfoCard.test.tsx create mode 100644 plugins/catalog-react/src/components/EntityInfoCard/EntityInfoCard.tsx create mode 100644 plugins/catalog-react/src/components/EntityInfoCard/index.ts diff --git a/plugins/catalog-react/report.api.md b/plugins/catalog-react/report.api.md index 14c832d545..6a930671c2 100644 --- a/plugins/catalog-react/report.api.md +++ b/plugins/catalog-react/report.api.md @@ -277,6 +277,23 @@ export type EntityFilter = { toQueryValue?: () => string | string[]; }; +// @public (undocumented) +export function EntityInfoCard(props: EntityInfoCardProps): JSX_2.Element; + +// @public (undocumented) +export interface EntityInfoCardProps { + // (undocumented) + children?: ReactNode; + // (undocumented) + className?: string; + // (undocumented) + footerActions?: ReactNode; + // (undocumented) + headerActions?: ReactNode; + // (undocumented) + title?: ReactNode; +} + // @public export class EntityKindFilter implements EntityFilter { constructor(value: string, label: string); diff --git a/plugins/catalog-react/src/components/EntityInfoCard/EntityInfoCard.test.tsx b/plugins/catalog-react/src/components/EntityInfoCard/EntityInfoCard.test.tsx new file mode 100644 index 0000000000..9fa1e351ff --- /dev/null +++ b/plugins/catalog-react/src/components/EntityInfoCard/EntityInfoCard.test.tsx @@ -0,0 +1,102 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { renderInTestApp } from '@backstage/test-utils'; +import { screen } from '@testing-library/react'; +import { EntityInfoCard } from './EntityInfoCard'; + +describe('', () => { + it('renders children in the card body', async () => { + await renderInTestApp( + +
Body content
+
, + ); + + expect(screen.getByText('Body content')).toBeInTheDocument(); + }); + + it('renders the title when provided', async () => { + await renderInTestApp( + +
Body
+
, + ); + + expect( + screen.getByRole('heading', { name: 'My Card Title' }), + ).toBeInTheDocument(); + }); + + it('does not render a heading when title is not provided', async () => { + await renderInTestApp( + +
Body
+
, + ); + + expect(screen.queryByRole('heading')).not.toBeInTheDocument(); + }); + + it('renders header actions next to the title', async () => { + await renderInTestApp( + Edit}> +
Body
+
, + ); + + expect(screen.getByRole('heading', { name: 'Title' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Edit' })).toBeInTheDocument(); + }); + + it('renders footer actions when provided', async () => { + await renderInTestApp( + Next Page}> +
Body
+
, + ); + + expect( + screen.getByRole('button', { name: 'Next Page' }), + ).toBeInTheDocument(); + }); + + it('does not render footer when footerActions is not provided', async () => { + const { container } = await renderInTestApp( + +
Body
+
, + ); + + expect(container.querySelector('.bui-CardFooter')).not.toBeInTheDocument(); + }); + + it('renders JSX titles (e.g., icon + text)', async () => { + await renderInTestApp( + + 🏠 Home Card + + } + > +
Body
+
, + ); + + expect(screen.getByText('Home Card')).toBeInTheDocument(); + }); +}); diff --git a/plugins/catalog-react/src/components/EntityInfoCard/EntityInfoCard.tsx b/plugins/catalog-react/src/components/EntityInfoCard/EntityInfoCard.tsx new file mode 100644 index 0000000000..29bcd59fdf --- /dev/null +++ b/plugins/catalog-react/src/components/EntityInfoCard/EntityInfoCard.tsx @@ -0,0 +1,56 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ReactNode } from 'react'; +import { + Card, + CardHeader, + CardBody, + CardFooter, + Text, + Flex, +} from '@backstage/ui'; + +/** @public */ +export interface EntityInfoCardProps { + title?: ReactNode; + headerActions?: ReactNode; + footerActions?: ReactNode; + children?: ReactNode; + className?: string; +} + +/** @public */ +export function EntityInfoCard(props: EntityInfoCardProps) { + const { title, headerActions, footerActions, children, className } = props; + + return ( + + {title && ( + + + + {title} + + {headerActions} + + + )} + {children} + {footerActions && {footerActions}} + + ); +} diff --git a/plugins/catalog-react/src/components/EntityInfoCard/index.ts b/plugins/catalog-react/src/components/EntityInfoCard/index.ts new file mode 100644 index 0000000000..1999a228b1 --- /dev/null +++ b/plugins/catalog-react/src/components/EntityInfoCard/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { EntityInfoCard } from './EntityInfoCard'; +export type { EntityInfoCardProps } from './EntityInfoCard'; diff --git a/plugins/catalog-react/src/components/index.ts b/plugins/catalog-react/src/components/index.ts index 8a1ebd1835..971d6086f7 100644 --- a/plugins/catalog-react/src/components/index.ts +++ b/plugins/catalog-react/src/components/index.ts @@ -35,3 +35,4 @@ export * from './EntityProcessingStatusPicker'; export * from './EntityNamespacePicker'; export * from './EntityAutocompletePicker'; export * from './MissingAnnotationEmptyState'; +export * from './EntityInfoCard'; From 8dfd13a0670e0c1ac4cd43a18632cd255063aac5 Mon Sep 17 00:00:00 2001 From: Johan Persson Date: Thu, 26 Feb 2026 16:38:47 +0100 Subject: [PATCH 02/25] feat(catalog): migrate EntityLinksCard to BUI EntityInfoCard Signed-off-by: Johan Persson --- plugins/catalog/src/alpha/entityCards.tsx | 10 +++------- .../components/EntityLinksCard/EntityLinksCard.tsx | 14 ++++++++------ 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/plugins/catalog/src/alpha/entityCards.tsx b/plugins/catalog/src/alpha/entityCards.tsx index 556a130dff..7be14eb445 100644 --- a/plugins/catalog/src/alpha/entityCards.tsx +++ b/plugins/catalog/src/alpha/entityCards.tsx @@ -66,9 +66,7 @@ export const catalogAboutEntityCard = EntityCardBlueprint.makeWithOverrides({ const { InternalAboutCard } = await import( '../components/AboutCard/AboutCard' ); - return ( - } /> - ); + return } />; }, }); }, @@ -80,9 +78,7 @@ export const catalogLinksEntityCard = EntityCardBlueprint.make({ type: 'info', filter: { 'metadata.links': { $exists: true } }, loader: async () => - import('../components/EntityLinksCard').then(m => ( - - )), + import('../components/EntityLinksCard').then(m => ), }, }); @@ -93,7 +89,7 @@ export const catalogLabelsEntityCard = EntityCardBlueprint.make({ filter: { 'metadata.labels': { $exists: true } }, loader: async () => import('../components/EntityLabelsCard').then(m => ( - + )), }, }); diff --git a/plugins/catalog/src/components/EntityLinksCard/EntityLinksCard.tsx b/plugins/catalog/src/components/EntityLinksCard/EntityLinksCard.tsx index 39088b0a16..60b5811ce9 100644 --- a/plugins/catalog/src/components/EntityLinksCard/EntityLinksCard.tsx +++ b/plugins/catalog/src/components/EntityLinksCard/EntityLinksCard.tsx @@ -14,24 +14,26 @@ * limitations under the License. */ -import { useEntity } from '@backstage/plugin-catalog-react'; +import { useEntity, EntityInfoCard } from '@backstage/plugin-catalog-react'; import LanguageIcon from '@material-ui/icons/Language'; import { EntityLinksEmptyState } from './EntityLinksEmptyState'; import { LinksGridList } from './LinksGridList'; import { ColumnBreakpoints } from './types'; import { IconComponent, useApp } from '@backstage/core-plugin-api'; -import { InfoCard, InfoCardVariants } from '@backstage/core-components'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; import { catalogTranslationRef } from '../../alpha/translation'; /** @public */ export interface EntityLinksCardProps { cols?: ColumnBreakpoints | number; - variant?: InfoCardVariants; + // Accepted for API compatibility but not applied. + // The new entity page layout handles card sizing. + // TODO: Discuss removal in code review. + variant?: string; } export const EntityLinksCard = (props: EntityLinksCardProps) => { - const { cols = undefined, variant } = props; + const { cols = undefined, variant: _variant } = props; const { entity } = useEntity(); const app = useApp(); const { t } = useTranslationRef(catalogTranslationRef); @@ -42,7 +44,7 @@ export const EntityLinksCard = (props: EntityLinksCardProps) => { const links = entity?.metadata?.links; return ( - + {!links || links.length === 0 ? ( ) : ( @@ -55,6 +57,6 @@ export const EntityLinksCard = (props: EntityLinksCardProps) => { }))} /> )} - +
); }; From 195481a2f87dac9c7a851ec641a08b412565feab Mon Sep 17 00:00:00 2001 From: Johan Persson Date: Thu, 26 Feb 2026 16:38:57 +0100 Subject: [PATCH 03/25] feat(catalog): migrate EntityLabelsCard to BUI EntityInfoCard Signed-off-by: Johan Persson --- .../EntityLabelsCard/EntityLabelsCard.tsx | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/plugins/catalog/src/components/EntityLabelsCard/EntityLabelsCard.tsx b/plugins/catalog/src/components/EntityLabelsCard/EntityLabelsCard.tsx index 04c182b5be..070d1266e2 100644 --- a/plugins/catalog/src/components/EntityLabelsCard/EntityLabelsCard.tsx +++ b/plugins/catalog/src/components/EntityLabelsCard/EntityLabelsCard.tsx @@ -14,13 +14,8 @@ * limitations under the License. */ -import { useEntity } from '@backstage/plugin-catalog-react'; -import { - InfoCard, - InfoCardVariants, - Table, - TableColumn, -} from '@backstage/core-components'; +import { useEntity, EntityInfoCard } from '@backstage/plugin-catalog-react'; +import { Table, TableColumn } from '@backstage/core-components'; import { EntityLabelsEmptyState } from './EntityLabelsEmptyState'; import Typography from '@material-ui/core/Typography'; import { makeStyles } from '@material-ui/core/styles'; @@ -29,7 +24,10 @@ import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** @public */ export interface EntityLabelsCardProps { - variant?: InfoCardVariants; + // Accepted for API compatibility but not applied. + // The new entity page layout handles card sizing. + // TODO: Discuss removal in code review. + variant?: string; title?: string; } @@ -40,7 +38,7 @@ const useStyles = makeStyles(_ => ({ })); export const EntityLabelsCard = (props: EntityLabelsCardProps) => { - const { variant, title } = props; + const { variant: _variant, title } = props; const { entity } = useEntity(); const classes = useStyles(); const { t } = useTranslationRef(catalogTranslationRef); @@ -63,7 +61,7 @@ export const EntityLabelsCard = (props: EntityLabelsCardProps) => { const labels = entity?.metadata?.labels; return ( - + {!labels || Object.keys(labels).length === 0 ? ( ) : ( @@ -85,6 +83,6 @@ export const EntityLabelsCard = (props: EntityLabelsCardProps) => { }} /> )} - + ); }; From bcb0b24cfa71667555861986895bfd09e564b883 Mon Sep 17 00:00:00 2001 From: Johan Persson Date: Thu, 26 Feb 2026 16:39:00 +0100 Subject: [PATCH 04/25] feat(org): migrate UserProfileCard to BUI EntityInfoCard Signed-off-by: Johan Persson --- .../src/components/AboutCard/AboutCard.tsx | 128 ++++++------------ .../User/UserProfileCard/UserProfileCard.tsx | 32 ++--- 2 files changed, 59 insertions(+), 101 deletions(-) diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.tsx index 8e90659520..3c2dcb1b34 100644 --- a/plugins/catalog/src/components/AboutCard/AboutCard.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutCard.tsx @@ -16,11 +16,6 @@ import { useCallback } from 'react'; -import { makeStyles } from '@material-ui/core/styles'; -import Card from '@material-ui/core/Card'; -import CardContent from '@material-ui/core/CardContent'; -import CardHeader from '@material-ui/core/CardHeader'; -import Divider from '@material-ui/core/Divider'; import IconButton from '@material-ui/core/IconButton'; import CachedIcon from '@material-ui/icons/Cached'; import EditIcon from '@material-ui/icons/Edit'; @@ -31,9 +26,9 @@ import { AppIcon, HeaderIconLinkRow, IconLinkVerticalProps, - InfoCardVariants, Link, } from '@backstage/core-components'; +import { EntityInfoCard } from '@backstage/plugin-catalog-react'; import { alertApiRef, errorApiRef, @@ -152,42 +147,25 @@ function DefaultAboutCardSubheader() { return ; } -const useStyles = makeStyles({ - gridItemCard: { - display: 'flex', - flexDirection: 'column', - height: 'calc(100% - 10px)', // for pages without content header - marginBottom: '10px', - }, - fullHeightCard: { - display: 'flex', - flexDirection: 'column', - height: '100%', - }, - gridItemCardContent: { - flex: 1, - }, - fullHeightCardContent: { - flex: 1, - }, -}); - /** * Props for {@link EntityAboutCard}. * * @public */ export type AboutCardProps = { - variant?: InfoCardVariants; + // Accepted for API compatibility but not applied. + // The new entity page layout handles card sizing. + // TODO: Discuss removal in code review. + variant?: string; }; export interface InternalAboutCardProps extends AboutCardProps { - subheader?: JSX.Element; + /** Icon link row rendered at the top of the card body. */ + iconLinks?: JSX.Element; } export function InternalAboutCard(props: InternalAboutCardProps) { - const { variant, subheader } = props; - const classes = useStyles(); + const { variant: _variant, iconLinks } = props; const { entity } = useEntity(); const catalogApi = useApi(catalogApiRef); const alertApi = useApi(alertApiRef); @@ -202,20 +180,6 @@ export function InternalAboutCard(props: InternalAboutCardProps) { const entityMetadataEditUrl = entity.metadata.annotations?.[ANNOTATION_EDIT_URL]; - let cardClass = ''; - if (variant === 'gridItem') { - cardClass = classes.gridItemCard; - } else if (variant === 'fullHeight') { - cardClass = classes.fullHeightCard; - } - - let cardContentClass = ''; - if (variant === 'gridItem') { - cardContentClass = classes.gridItemCardContent; - } else if (variant === 'fullHeight') { - cardContentClass = classes.fullHeightCardContent; - } - const entityLocation = entity.metadata.annotations?.[ANNOTATION_LOCATION]; // Limiting the ability to manually refresh to the less expensive locations const allowRefresh = @@ -234,50 +198,46 @@ export function InternalAboutCard(props: InternalAboutCardProps) { }, [catalogApi, entity, alertApi, t, errorApi]); return ( - - - {allowRefresh && canRefresh && ( - - - - )} + + {allowRefresh && canRefresh && ( + + + + )} + + + + {sourceTemplateRef && templateRoute && ( - + - {sourceTemplateRef && templateRoute && ( - - - - )} - - } - subheader={subheader ?? } - /> - - - - - + )} + + } + > + {iconLinks ?? } + + ); } diff --git a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx index ba3ee9d561..67c0a2c52a 100644 --- a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx +++ b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx @@ -19,14 +19,10 @@ import { RELATION_MEMBER_OF, UserEntity, } from '@backstage/catalog-model'; -import { - Avatar, - InfoCard, - InfoCardVariants, - Link, -} from '@backstage/core-components'; +import { Avatar, Link } from '@backstage/core-components'; +import { EntityInfoCard } from '@backstage/plugin-catalog-react'; +import { Flex, Text } from '@backstage/ui'; import { createStyles, makeStyles } from '@material-ui/core/styles'; -import Box from '@material-ui/core/Box'; import Grid from '@material-ui/core/Grid'; import BaseButton from '@material-ui/core/ButtonBase'; import IconButton from '@material-ui/core/IconButton'; @@ -86,20 +82,23 @@ const useStyles = makeStyles( const CardTitle = (props: { title?: string }) => props.title ? ( - + - {props.title} - + {props.title} + ) : null; /** @public */ export const UserProfileCard = (props: { - variant?: InfoCardVariants; + // Accepted for API compatibility but not applied. + // The new entity page layout handles card sizing. + // TODO: Discuss removal in code review. + variant?: string; showLinks?: boolean; maxRelations?: number; hideIcons?: boolean; }) => { - const { maxRelations, hideIcons } = props; + const { maxRelations, hideIcons, variant: _variant } = props; const classes = useStyles(); const { entity: user } = useEntity(); @@ -132,11 +131,9 @@ export const UserProfileCard = (props: { }); return ( - } - subheader={description} - variant={props.variant} - action={ + headerActions={ <> {entityMetadataEditUrl && ( } > + {description && {description}} @@ -238,6 +236,6 @@ export const UserProfileCard = (props: { - + ); }; From aea7bd9f0e2eb67a425f8e149a36130adca4a97a Mon Sep 17 00:00:00 2001 From: Johan Persson Date: Thu, 26 Feb 2026 16:39:10 +0100 Subject: [PATCH 05/25] feat(org): migrate GroupProfileCard to BUI EntityInfoCard Signed-off-by: Johan Persson --- .../Group/GroupProfile/GroupProfileCard.tsx | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx index 89eba12b97..1cda4c7399 100644 --- a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx +++ b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx @@ -22,12 +22,7 @@ import { RELATION_PARENT_OF, stringifyEntityRef, } from '@backstage/catalog-model'; -import { - Avatar, - InfoCard, - InfoCardVariants, - Link, -} from '@backstage/core-components'; +import { Avatar, Link } from '@backstage/core-components'; import Box from '@material-ui/core/Box'; import IconButton from '@material-ui/core/IconButton'; import List from '@material-ui/core/List'; @@ -36,6 +31,7 @@ import ListItemIcon from '@material-ui/core/ListItemIcon'; import ListItemText from '@material-ui/core/ListItemText'; import Tooltip from '@material-ui/core/Tooltip'; import { + EntityInfoCard, EntityRefLinks, catalogApiRef, getEntityRelations, @@ -57,6 +53,7 @@ import { catalogEntityRefreshPermission } from '@backstage/plugin-catalog-common import { useTranslationRef } from '@backstage/frontend-plugin-api'; import { orgTranslationRef } from '../../../../translation'; import { makeStyles } from '@material-ui/core/styles'; +import { Flex, Text } from '@backstage/ui'; const useStyles = makeStyles(theme => ({ container: { @@ -72,17 +69,21 @@ const useStyles = makeStyles(theme => ({ })); const CardTitle = (props: { title: string }) => ( - + - {props.title} - + {props.title} + ); /** @public */ export const GroupProfileCard = (props: { - variant?: InfoCardVariants; + // Accepted for API compatibility but not applied. + // The new entity page layout handles card sizing. + // TODO: Discuss removal in code review. + variant?: string; showLinks?: boolean; }) => { + const { variant: _variant } = props; const catalogApi = useApi(catalogApiRef); const alertApi = useApi(alertApiRef); const { entity: group } = useEntity(); @@ -148,11 +149,9 @@ export const GroupProfileCard = (props: { ); return ( - } - subheader={description} - variant={props.variant} - action={ + headerActions={ <> {allowRefresh && canRefresh && ( } > + {description && {description}} @@ -237,6 +237,6 @@ export const GroupProfileCard = (props: { {props?.showLinks && } - + ); }; From 5fc35bbe9be16e28728ac9d561baa395cbde2528 Mon Sep 17 00:00:00 2001 From: Johan Persson Date: Thu, 26 Feb 2026 17:18:36 +0100 Subject: [PATCH 06/25] chore: add changeset and update API reports for BUI card migration Signed-off-by: Johan Persson --- .changeset/thirty-kiwis-trade.md | 9 +++++++++ plugins/catalog/report.api.md | 6 +++--- plugins/org/package.json | 1 + plugins/org/report.api.md | 8 ++++---- yarn.lock | 1 + 5 files changed, 18 insertions(+), 7 deletions(-) create mode 100644 .changeset/thirty-kiwis-trade.md diff --git a/.changeset/thirty-kiwis-trade.md b/.changeset/thirty-kiwis-trade.md new file mode 100644 index 0000000000..e84fafd525 --- /dev/null +++ b/.changeset/thirty-kiwis-trade.md @@ -0,0 +1,9 @@ +--- +'@backstage/plugin-catalog-react': minor +'@backstage/plugin-catalog': patch +'@backstage/plugin-org': patch +--- + +Added `EntityInfoCard` component to `@backstage/plugin-catalog-react` as a BUI-based card wrapper for entity page cards. Migrated `EntityAboutCard`, `EntityLinksCard`, `EntityLabelsCard`, `GroupProfileCard`, and `UserProfileCard` from MUI/InfoCard to use the new BUI card layout. + +**Affected components:** EntityInfoCard, EntityAboutCard, EntityLinksCard, EntityLabelsCard, GroupProfileCard, UserProfileCard diff --git a/plugins/catalog/report.api.md b/plugins/catalog/report.api.md index 52b13e1c43..f0696387fb 100644 --- a/plugins/catalog/report.api.md +++ b/plugins/catalog/report.api.md @@ -42,7 +42,7 @@ import { UserListFilterKind } from '@backstage/plugin-catalog-react'; // @public export type AboutCardProps = { - variant?: InfoCardVariants; + variant?: string; }; // @public (undocumented) @@ -385,7 +385,7 @@ export interface EntityLabelsCardProps { // (undocumented) title?: string; // (undocumented) - variant?: InfoCardVariants; + variant?: string; } // @public (undocumented) @@ -436,7 +436,7 @@ export interface EntityLinksCardProps { // (undocumented) cols?: ColumnBreakpoints | number; // (undocumented) - variant?: InfoCardVariants; + variant?: string; } // @public (undocumented) diff --git a/plugins/org/package.json b/plugins/org/package.json index 62a4c9fd1b..6955b1a0d0 100644 --- a/plugins/org/package.json +++ b/plugins/org/package.json @@ -57,6 +57,7 @@ "@backstage/frontend-plugin-api": "workspace:^", "@backstage/plugin-catalog-common": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", + "@backstage/ui": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", diff --git a/plugins/org/report.api.md b/plugins/org/report.api.md index 13143d0e4c..776affbb9f 100644 --- a/plugins/org/report.api.md +++ b/plugins/org/report.api.md @@ -18,7 +18,7 @@ export type ComponentsGridClassKey = // @public (undocumented) export const EntityGroupProfileCard: (props: { - variant?: InfoCardVariants; + variant?: string; showLinks?: boolean; }) => JSX_2.Element; @@ -48,7 +48,7 @@ export type EntityRelationAggregation = 'direct' | 'aggregated'; // @public (undocumented) export const EntityUserProfileCard: (props: { - variant?: InfoCardVariants; + variant?: string; showLinks?: boolean; maxRelations?: number; hideIcons?: boolean; @@ -56,7 +56,7 @@ export const EntityUserProfileCard: (props: { // @public (undocumented) export const GroupProfileCard: (props: { - variant?: InfoCardVariants; + variant?: string; showLinks?: boolean; }) => JSX_2.Element; @@ -116,7 +116,7 @@ export type OwnershipCardClassKey = // @public (undocumented) export const UserProfileCard: (props: { - variant?: InfoCardVariants; + variant?: string; showLinks?: boolean; maxRelations?: number; hideIcons?: boolean; diff --git a/yarn.lock b/yarn.lock index 634d7ade79..5d2d15c94c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6525,6 +6525,7 @@ __metadata: "@backstage/plugin-permission-react": "workspace:^" "@backstage/test-utils": "workspace:^" "@backstage/types": "workspace:^" + "@backstage/ui": "workspace:^" "@material-ui/core": "npm:^4.12.2" "@material-ui/icons": "npm:^4.9.1" "@material-ui/lab": "npm:4.0.0-alpha.61" From 06e4cdae070542619f62003d80aeb335b4a7e2e1 Mon Sep 17 00:00:00 2001 From: Johan Persson Date: Thu, 26 Feb 2026 18:50:32 +0100 Subject: [PATCH 07/25] refactor(catalog): replace MUI ImageList with BUI Grid in LinksGridList Signed-off-by: Johan Persson --- .../src/components/EntityLinksCard/LinksGridList.tsx | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/plugins/catalog/src/components/EntityLinksCard/LinksGridList.tsx b/plugins/catalog/src/components/EntityLinksCard/LinksGridList.tsx index 797428df33..1556d30e22 100644 --- a/plugins/catalog/src/components/EntityLinksCard/LinksGridList.tsx +++ b/plugins/catalog/src/components/EntityLinksCard/LinksGridList.tsx @@ -14,8 +14,7 @@ * limitations under the License. */ -import ImageList from '@material-ui/core/ImageList'; -import ImageListItem from '@material-ui/core/ImageListItem'; +import { Grid } from '@backstage/ui'; import { IconLink } from './IconLink'; import { ColumnBreakpoints } from './types'; import { useDynamicColumns } from './useDynamicColumns'; @@ -37,12 +36,10 @@ export function LinksGridList(props: LinksGridListProps) { const numOfCols = useDynamicColumns(cols); return ( - + {items.map(({ text, href, Icon }, i) => ( - - - + ))} - + ); } From 6d82107beb49779b5e84ed31162707ec3263b987 Mon Sep 17 00:00:00 2001 From: Johan Persson Date: Thu, 26 Feb 2026 18:50:41 +0100 Subject: [PATCH 08/25] refactor(catalog): replace MUI Grid with BUI Grid in AboutContent Signed-off-by: Johan Persson --- .../src/components/AboutCard/AboutContent.tsx | 8 ++++---- .../src/components/AboutCard/AboutField.tsx | 17 ++++++++++++----- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/plugins/catalog/src/components/AboutCard/AboutContent.tsx b/plugins/catalog/src/components/AboutCard/AboutContent.tsx index d23a29a7c5..ee2c8571d5 100644 --- a/plugins/catalog/src/components/AboutCard/AboutContent.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutContent.tsx @@ -26,8 +26,8 @@ import { } from '@backstage/plugin-catalog-react'; import { JsonArray } from '@backstage/types'; import Chip from '@material-ui/core/Chip'; -import Grid from '@material-ui/core/Grid'; import { makeStyles } from '@material-ui/core/styles'; +import { Grid } from '@backstage/ui'; import { MarkdownContent } from '@backstage/core-components'; import { AboutField } from './AboutField'; import { LinksGridList } from '../EntityLinksCard/LinksGridList'; @@ -115,10 +115,10 @@ export function AboutContent(props: AboutContentProps) { } return ( - + ; children?: ReactNode; className?: string; + style?: CSSProperties; } /** @public */ export function AboutField(props: AboutFieldProps) { - const { label, value, gridSizes, children, className } = props; + const { + label, + value, + gridSizes: _gridSizes, + children, + className, + style, + } = props; const classes = useStyles(); const { t } = useTranslationRef(catalogTranslationRef); @@ -71,11 +78,11 @@ export function AboutField(props: AboutFieldProps) { ); return ( - +
{label} {content} - +
); } From e2e5a1498011a23a8eb028d30a750bcbb651fd9c Mon Sep 17 00:00:00 2001 From: Johan Persson Date: Thu, 26 Feb 2026 18:56:05 +0100 Subject: [PATCH 09/25] fix(catalog): use Grid.Root instead of Grid for BUI Grid component Signed-off-by: Johan Persson --- plugins/catalog/src/components/AboutCard/AboutContent.tsx | 4 ++-- .../catalog/src/components/EntityLinksCard/LinksGridList.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/catalog/src/components/AboutCard/AboutContent.tsx b/plugins/catalog/src/components/AboutCard/AboutContent.tsx index ee2c8571d5..d35891cd05 100644 --- a/plugins/catalog/src/components/AboutCard/AboutContent.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutContent.tsx @@ -115,7 +115,7 @@ export function AboutContent(props: AboutContentProps) { } return ( - + )} - + ); } diff --git a/plugins/catalog/src/components/EntityLinksCard/LinksGridList.tsx b/plugins/catalog/src/components/EntityLinksCard/LinksGridList.tsx index 1556d30e22..90b7e2058b 100644 --- a/plugins/catalog/src/components/EntityLinksCard/LinksGridList.tsx +++ b/plugins/catalog/src/components/EntityLinksCard/LinksGridList.tsx @@ -36,10 +36,10 @@ export function LinksGridList(props: LinksGridListProps) { const numOfCols = useDynamicColumns(cols); return ( - + {items.map(({ text, href, Icon }, i) => ( ))} - + ); } From 35ce598bc09249ca0d8da06e6bd48edcb3f60123 Mon Sep 17 00:00:00 2001 From: Johan Persson Date: Mon, 2 Mar 2026 11:26:31 +0100 Subject: [PATCH 10/25] fix(catalog-react): group header actions in EntityInfoCard Signed-off-by: Johan Persson --- .../src/components/EntityInfoCard/EntityInfoCard.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/catalog-react/src/components/EntityInfoCard/EntityInfoCard.tsx b/plugins/catalog-react/src/components/EntityInfoCard/EntityInfoCard.tsx index 29bcd59fdf..84cca25b21 100644 --- a/plugins/catalog-react/src/components/EntityInfoCard/EntityInfoCard.tsx +++ b/plugins/catalog-react/src/components/EntityInfoCard/EntityInfoCard.tsx @@ -45,7 +45,11 @@ export function EntityInfoCard(props: EntityInfoCardProps) { {title} - {headerActions} + {headerActions && ( + + {headerActions} + + )} )} From 809eefb91c1af070d12dc4055c0bc18ede533d22 Mon Sep 17 00:00:00 2001 From: Johan Persson Date: Mon, 2 Mar 2026 11:27:02 +0100 Subject: [PATCH 11/25] refactor(catalog): migrate EntityLabelsCard table to BUI Table Signed-off-by: Johan Persson --- .../EntityLabelsCard/EntityLabelsCard.tsx | 61 ++++++++----------- 1 file changed, 27 insertions(+), 34 deletions(-) diff --git a/plugins/catalog/src/components/EntityLabelsCard/EntityLabelsCard.tsx b/plugins/catalog/src/components/EntityLabelsCard/EntityLabelsCard.tsx index 070d1266e2..8c096932f8 100644 --- a/plugins/catalog/src/components/EntityLabelsCard/EntityLabelsCard.tsx +++ b/plugins/catalog/src/components/EntityLabelsCard/EntityLabelsCard.tsx @@ -15,10 +15,13 @@ */ import { useEntity, EntityInfoCard } from '@backstage/plugin-catalog-react'; -import { Table, TableColumn } from '@backstage/core-components'; import { EntityLabelsEmptyState } from './EntityLabelsEmptyState'; -import Typography from '@material-ui/core/Typography'; -import { makeStyles } from '@material-ui/core/styles'; +import { + Table, + CellText, + type ColumnConfig, + type TableItem, +} from '@backstage/ui'; import { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; @@ -31,33 +34,31 @@ export interface EntityLabelsCardProps { title?: string; } -const useStyles = makeStyles(_ => ({ - key: { - fontWeight: 'bold', +interface LabelItem extends TableItem { + id: string; + key: string; + value: string; +} + +const columnConfig: ColumnConfig[] = [ + { + id: 'key', + label: 'Label', + isRowHeader: true, + cell: item => , }, -})); + { + id: 'value', + label: 'Value', + cell: item => , + }, +]; export const EntityLabelsCard = (props: EntityLabelsCardProps) => { const { variant: _variant, title } = props; const { entity } = useEntity(); - const classes = useStyles(); const { t } = useTranslationRef(catalogTranslationRef); - const columns: TableColumn<{ key: string; value: string }>[] = [ - { - render: row => { - return ( - - {row.key} - - ); - }, - }, - { - field: 'value', - }, - ]; - const labels = entity?.metadata?.labels; return ( @@ -66,21 +67,13 @@ export const EntityLabelsCard = (props: EntityLabelsCardProps) => { ) : ( ({ + id: labelKey, key: labelKey, value: labels[labelKey], }))} - options={{ - search: false, - showTitle: true, - loadingType: 'linear', - header: false, - padding: 'dense', - pageSize: 5, - toolbar: false, - paging: Object.keys(labels).length > 5, - }} + pagination={{ type: 'none' }} /> )} From 951997f7f0813b753cbd1f943d6faa34539d2bb6 Mon Sep 17 00:00:00 2001 From: Johan Persson Date: Mon, 2 Mar 2026 11:27:19 +0100 Subject: [PATCH 12/25] refactor(org): use BUI Avatar in GroupProfileCard and UserProfileCard titles Signed-off-by: Johan Persson --- .../Group/GroupProfile/GroupProfileCard.tsx | 21 +-- .../User/UserProfileCard/UserProfileCard.tsx | 121 +++++++++--------- 2 files changed, 70 insertions(+), 72 deletions(-) diff --git a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx index 1cda4c7399..0bd1100633 100644 --- a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx +++ b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx @@ -22,8 +22,7 @@ import { RELATION_PARENT_OF, stringifyEntityRef, } from '@backstage/catalog-model'; -import { Avatar, Link } from '@backstage/core-components'; -import Box from '@material-ui/core/Box'; +import { Link } from '@backstage/core-components'; import IconButton from '@material-ui/core/IconButton'; import List from '@material-ui/core/List'; import ListItem from '@material-ui/core/ListItem'; @@ -53,7 +52,7 @@ import { catalogEntityRefreshPermission } from '@backstage/plugin-catalog-common import { useTranslationRef } from '@backstage/frontend-plugin-api'; import { orgTranslationRef } from '../../../../translation'; import { makeStyles } from '@material-ui/core/styles'; -import { Flex, Text } from '@backstage/ui'; +import { Avatar, Flex, Box, Text } from '@backstage/ui'; const useStyles = makeStyles(theme => ({ container: { @@ -68,9 +67,14 @@ const useStyles = makeStyles(theme => ({ }, })); -const CardTitle = (props: { title: string }) => ( - - +const CardTitle = (props: { title: string; pictureSrc?: string }) => ( + + {props.title} ); @@ -150,7 +154,7 @@ export const GroupProfileCard = (props: { return ( } + title={} headerActions={ <> {allowRefresh && canRefresh && ( @@ -167,8 +171,7 @@ export const GroupProfileCard = (props: { } > {description && {description}} - - + diff --git a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx index 67c0a2c52a..12a996cb4b 100644 --- a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx +++ b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx @@ -19,11 +19,10 @@ import { RELATION_MEMBER_OF, UserEntity, } from '@backstage/catalog-model'; -import { Avatar, Link } from '@backstage/core-components'; +import { Link } from '@backstage/core-components'; import { EntityInfoCard } from '@backstage/plugin-catalog-react'; -import { Flex, Text } from '@backstage/ui'; +import { Avatar, Box, Flex, Text } from '@backstage/ui'; import { createStyles, makeStyles } from '@material-ui/core/styles'; -import Grid from '@material-ui/core/Grid'; import BaseButton from '@material-ui/core/ButtonBase'; import IconButton from '@material-ui/core/IconButton'; import List from '@material-ui/core/List'; @@ -48,7 +47,6 @@ import EditIcon from '@material-ui/icons/Edit'; import EmailIcon from '@material-ui/icons/Email'; import GroupIcon from '@material-ui/icons/Group'; import { LinksGroup } from '../../Meta'; -import PersonIcon from '@material-ui/icons/Person'; import { useCallback, useState } from 'react'; import { useTranslationRef } from '@backstage/frontend-plugin-api'; @@ -80,10 +78,15 @@ const useStyles = makeStyles( { name: 'PluginOrgUserProfileCard' }, ); -const CardTitle = (props: { title?: string }) => +const CardTitle = (props: { title: string; pictureSrc?: string }) => props.title ? ( - - + + {props.title} ) : null; @@ -132,7 +135,7 @@ export const UserProfileCard = (props: { return ( } + title={} headerActions={ <> {entityMetadataEditUrl && ( @@ -149,62 +152,54 @@ export const UserProfileCard = (props: { } > {description && {description}} - - - - + + + {profile?.email && ( + + + + + + + + {profile.email} + + + )} - - - {profile?.email && ( - - - - - - - - {profile.email} - - - )} - - {maxRelations === undefined || maxRelations > 0 ? ( - - - - - - - - - {maxRelations && memberOfRelations.length > maxRelations ? ( - <> - , - - {t('userProfileCard.moreGroupButtonTitle', { - number: String( - memberOfRelations.length - maxRelations, - ), - })} - - - ) : null} - - - ) : null} - {props?.showLinks && } - - - + {maxRelations === undefined || maxRelations > 0 ? ( + + + + + + + + + {maxRelations && memberOfRelations.length > maxRelations ? ( + <> + , + + {t('userProfileCard.moreGroupButtonTitle', { + number: String(memberOfRelations.length - maxRelations), + })} + + + ) : null} + + + ) : null} + {props?.showLinks && } + + Date: Mon, 2 Mar 2026 11:55:14 +0100 Subject: [PATCH 13/25] test(org): update UserProfileCard test for BUI Avatar Signed-off-by: Johan Persson --- .../Cards/User/UserProfileCard/UserProfileCard.test.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.test.tsx b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.test.tsx index b5932dfb8e..d1ecc09b01 100644 --- a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.test.tsx +++ b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.test.tsx @@ -61,10 +61,10 @@ describe('UserSummary Test', () => { ); expect(screen.getByText('calum-leavy@example.com')).toBeInTheDocument(); - expect(screen.getByAltText('Calum Leavy')).toHaveAttribute( - 'src', - 'https://example.com/staff/calum.jpeg', - ); + // BUI Avatar is decorative (aria-hidden), so the name must be + // present as text for the information to be accessible. + expect(screen.getByRole('img', { hidden: true })).toBeInTheDocument(); + expect(screen.getByText('Calum Leavy')).toBeInTheDocument(); expect(screen.getByText('examplegroup').closest('a')).toHaveAttribute( 'href', '/catalog/default/group/examplegroup', From 99ef00b4e4bc65ebd4062f35ca840ab003ba9ba7 Mon Sep 17 00:00:00 2001 From: Johan Persson Date: Mon, 2 Mar 2026 12:27:27 +0100 Subject: [PATCH 14/25] chore: update changeset and API reports Signed-off-by: Johan Persson --- .changeset/thirty-kiwis-trade.md | 4 +--- plugins/catalog/report.api.md | 3 +++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.changeset/thirty-kiwis-trade.md b/.changeset/thirty-kiwis-trade.md index e84fafd525..b2cdc5fb01 100644 --- a/.changeset/thirty-kiwis-trade.md +++ b/.changeset/thirty-kiwis-trade.md @@ -4,6 +4,4 @@ '@backstage/plugin-org': patch --- -Added `EntityInfoCard` component to `@backstage/plugin-catalog-react` as a BUI-based card wrapper for entity page cards. Migrated `EntityAboutCard`, `EntityLinksCard`, `EntityLabelsCard`, `GroupProfileCard`, and `UserProfileCard` from MUI/InfoCard to use the new BUI card layout. - -**Affected components:** EntityInfoCard, EntityAboutCard, EntityLinksCard, EntityLabelsCard, GroupProfileCard, UserProfileCard +Added `EntityInfoCard` component to `@backstage/plugin-catalog-react` as a BUI-based card wrapper for entity page cards. Migrated `EntityAboutCard`, `EntityLinksCard`, `EntityLabelsCard`, `GroupProfileCard`, and `UserProfileCard` from MUI/InfoCard to use the new BUI card layout. Replaced MUI ImageList and Grid internals in LinksGridList and AboutContent with BUI Grid. Migrated EntityLabelsCard from core-components Table to BUI Table. Updated GroupProfileCard and UserProfileCard to use BUI Avatar in card titles. diff --git a/plugins/catalog/report.api.md b/plugins/catalog/report.api.md index f0696387fb..4539364917 100644 --- a/plugins/catalog/report.api.md +++ b/plugins/catalog/report.api.md @@ -8,6 +8,7 @@ import { BackstagePlugin } from '@backstage/core-plugin-api'; import { CatalogApi } from '@backstage/plugin-catalog-react'; import { ComponentEntity } from '@backstage/catalog-model'; import { CompoundEntityRef } from '@backstage/catalog-model'; +import { CSSProperties } from 'react'; import { DomainEntity } from '@backstage/catalog-model'; import { ElementType } from 'react'; import { Entity } from '@backstage/catalog-model'; @@ -68,6 +69,8 @@ export interface AboutFieldProps { // (undocumented) label: string; // (undocumented) + style?: CSSProperties; + // (undocumented) value?: string; } From be4810234c5975f8a913289d0aa54a8d547945e0 Mon Sep 17 00:00:00 2001 From: Johan Persson Date: Mon, 2 Mar 2026 14:58:50 +0100 Subject: [PATCH 15/25] chore: polish comments. Signed-off-by: Johan Persson --- .../src/components/EntityInfoCard/EntityInfoCard.test.tsx | 2 +- .../src/components/EntityInfoCard/EntityInfoCard.tsx | 2 +- .../Cards/User/UserProfileCard/UserProfileCard.test.tsx | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/catalog-react/src/components/EntityInfoCard/EntityInfoCard.test.tsx b/plugins/catalog-react/src/components/EntityInfoCard/EntityInfoCard.test.tsx index 9fa1e351ff..b3b0efd596 100644 --- a/plugins/catalog-react/src/components/EntityInfoCard/EntityInfoCard.test.tsx +++ b/plugins/catalog-react/src/components/EntityInfoCard/EntityInfoCard.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/catalog-react/src/components/EntityInfoCard/EntityInfoCard.tsx b/plugins/catalog-react/src/components/EntityInfoCard/EntityInfoCard.tsx index 84cca25b21..c31bd133e0 100644 --- a/plugins/catalog-react/src/components/EntityInfoCard/EntityInfoCard.tsx +++ b/plugins/catalog-react/src/components/EntityInfoCard/EntityInfoCard.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2025 The Backstage Authors + * Copyright 2026 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.test.tsx b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.test.tsx index d1ecc09b01..bd446c8c6e 100644 --- a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.test.tsx +++ b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.test.tsx @@ -61,8 +61,8 @@ describe('UserSummary Test', () => { ); expect(screen.getByText('calum-leavy@example.com')).toBeInTheDocument(); - // BUI Avatar is decorative (aria-hidden), so the name must be - // present as text for the information to be accessible. + // BUI Avatar is decorative (aria-hidden), because the name is + // present as text right beside it. expect(screen.getByRole('img', { hidden: true })).toBeInTheDocument(); expect(screen.getByText('Calum Leavy')).toBeInTheDocument(); expect(screen.getByText('examplegroup').closest('a')).toHaveAttribute( From c4832a18d1d22a745ea71e415ae54bf3fb6125e3 Mon Sep 17 00:00:00 2001 From: Johan Persson Date: Mon, 2 Mar 2026 16:12:18 +0100 Subject: [PATCH 16/25] refactor: Remove obsolete variant prop from Entity card components. Signed-off-by: Johan Persson --- .../src/components/catalog/EntityPage.tsx | 12 ++++----- .../src/collectLegacyRoutes.test.tsx | 2 +- plugins/catalog/report-alpha.api.md | 8 +++--- plugins/catalog/report.api.md | 11 +------- .../src/components/AboutCard/AboutCard.tsx | 26 +++++-------------- .../catalog/src/components/AboutCard/index.ts | 1 - .../EntityLabelsCard/EntityLabelsCard.tsx | 6 +---- .../EntityLinksCard/EntityLinksCard.tsx | 6 +---- plugins/catalog/src/index.ts | 1 - plugins/catalog/src/plugin.ts | 18 ++++++------- plugins/org/report.api.md | 4 --- .../Group/GroupProfile/GroupProfileCard.tsx | 12 +++------ .../UserProfileCard.stories.tsx | 6 ++--- .../UserProfileCard/UserProfileCard.test.tsx | 18 ++++++------- .../User/UserProfileCard/UserProfileCard.tsx | 3 +-- 15 files changed, 45 insertions(+), 89 deletions(-) diff --git a/packages/app-legacy/src/components/catalog/EntityPage.tsx b/packages/app-legacy/src/components/catalog/EntityPage.tsx index 08d4d921b5..32249cb84a 100644 --- a/packages/app-legacy/src/components/catalog/EntityPage.tsx +++ b/packages/app-legacy/src/components/catalog/EntityPage.tsx @@ -166,7 +166,7 @@ const overviewContent = ( {entityWarningContent} - + @@ -330,7 +330,7 @@ const userPage = ( {entityWarningContent} - + {entityWarningContent} - + {entityWarningContent} - + @@ -418,7 +418,7 @@ const domainPage = ( {entityWarningContent} - + @@ -437,7 +437,7 @@ const resourcePage = ( {entityWarningContent} - + diff --git a/packages/core-compat-api/src/collectLegacyRoutes.test.tsx b/packages/core-compat-api/src/collectLegacyRoutes.test.tsx index c5138dac3d..f9e8793613 100644 --- a/packages/core-compat-api/src/collectLegacyRoutes.test.tsx +++ b/packages/core-compat-api/src/collectLegacyRoutes.test.tsx @@ -156,7 +156,7 @@ describe('collectLegacyRoutes', () => { if={isKind('component')} children={ - + } /> diff --git a/plugins/catalog/report-alpha.api.md b/plugins/catalog/report-alpha.api.md index 82b6837cc7..0b8376c8f6 100644 --- a/plugins/catalog/report-alpha.api.md +++ b/plugins/catalog/report-alpha.api.md @@ -74,8 +74,8 @@ export const catalogTranslationRef: TranslationRef< readonly 'aboutCard.targetsField.label': 'Targets'; readonly 'searchResultItem.type': 'Type'; readonly 'searchResultItem.kind': 'Kind'; - readonly 'searchResultItem.owner': 'Owner'; readonly 'searchResultItem.lifecycle': 'Lifecycle'; + readonly 'searchResultItem.owner': 'Owner'; readonly 'catalogTable.allFilters': 'All'; readonly 'catalogTable.warningPanelTitle': 'Could not fetch catalog entities.'; readonly 'catalogTable.viewActionTitle': 'View'; @@ -95,14 +95,14 @@ export const catalogTranslationRef: TranslationRef< readonly 'entityContextMenu.unregisterMenuTitle': 'Unregister entity'; readonly 'entityContextMenu.moreButtonAriaLabel': 'more'; readonly 'entityLabelsCard.title': 'Labels'; - readonly 'entityLabelsCard.readMoreButtonTitle': 'Read more'; readonly 'entityLabelsCard.emptyDescription': 'No labels defined for this entity. You can add labels to your entity YAML as shown in the highlighted example below:'; - readonly 'entityLabels.ownerLabel': 'Owner'; + readonly 'entityLabelsCard.readMoreButtonTitle': 'Read more'; readonly 'entityLabels.warningPanelTitle': 'Entity not found'; + readonly 'entityLabels.ownerLabel': 'Owner'; readonly 'entityLabels.lifecycleLabel': 'Lifecycle'; readonly 'entityLinksCard.title': 'Links'; - readonly 'entityLinksCard.readMoreButtonTitle': 'Read more'; readonly 'entityLinksCard.emptyDescription': 'No links defined for this entity. You can add links to your entity YAML as shown in the highlighted example below:'; + readonly 'entityLinksCard.readMoreButtonTitle': 'Read more'; readonly 'entityNotFound.title': 'Entity was not found'; readonly 'entityNotFound.description': 'Want to help us build this? Check out our Getting Started documentation.'; readonly 'entityNotFound.docButtonTitle': 'DOCS'; diff --git a/plugins/catalog/report.api.md b/plugins/catalog/report.api.md index 4539364917..411d60076e 100644 --- a/plugins/catalog/report.api.md +++ b/plugins/catalog/report.api.md @@ -41,11 +41,6 @@ import { TableProps } from '@backstage/core-components'; import { TabProps } from '@material-ui/core/Tab'; import { UserListFilterKind } from '@backstage/plugin-catalog-react'; -// @public -export type AboutCardProps = { - variant?: string; -}; - // @public (undocumented) export function AboutContent(props: AboutContentProps): JSX_2.Element; @@ -337,7 +332,7 @@ export interface DependsOnResourcesCardProps { } // @public -export const EntityAboutCard: (props: AboutCardProps) => JSX.Element; +export const EntityAboutCard: () => JSX.Element; // @public (undocumented) export type EntityContextMenuClassKey = 'button'; @@ -387,8 +382,6 @@ export const EntityLabelsCard: (props: EntityLabelsCardProps) => JSX_2.Element; export interface EntityLabelsCardProps { // (undocumented) title?: string; - // (undocumented) - variant?: string; } // @public (undocumented) @@ -438,8 +431,6 @@ export const EntityLinksCard: (props: EntityLinksCardProps) => JSX_2.Element; export interface EntityLinksCardProps { // (undocumented) cols?: ColumnBreakpoints | number; - // (undocumented) - variant?: string; } // @public (undocumented) diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.tsx index 3c2dcb1b34..e516432dad 100644 --- a/plugins/catalog/src/components/AboutCard/AboutCard.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutCard.tsx @@ -147,25 +147,13 @@ function DefaultAboutCardSubheader() { return ; } -/** - * Props for {@link EntityAboutCard}. - * - * @public - */ -export type AboutCardProps = { - // Accepted for API compatibility but not applied. - // The new entity page layout handles card sizing. - // TODO: Discuss removal in code review. - variant?: string; -}; - -export interface InternalAboutCardProps extends AboutCardProps { +export interface InternalAboutCardProps { /** Icon link row rendered at the top of the card body. */ iconLinks?: JSX.Element; } export function InternalAboutCard(props: InternalAboutCardProps) { - const { variant: _variant, iconLinks } = props; + const { iconLinks } = props; const { entity } = useEntity(); const catalogApi = useApi(catalogApiRef); const alertApi = useApi(alertApiRef); @@ -244,10 +232,10 @@ export function InternalAboutCard(props: InternalAboutCardProps) { /** * Exported publicly via the EntityAboutCard * - * NOTE: We generally do not accept pull requests to extend this class with more - * props and customizability. If you need to tweak it, consider making a bespoke - * card in your own repository instead, that is perfect for your own needs. + * NOTE: We generally do not accept pull requests to extend this class with props + * and customizability. If you need to tweak it, consider making a bespoke card + * in your own repository instead, that is perfect for your own needs. */ -export function AboutCard(props: AboutCardProps) { - return ; +export function AboutCard() { + return ; } diff --git a/plugins/catalog/src/components/AboutCard/index.ts b/plugins/catalog/src/components/AboutCard/index.ts index 9660a5e2e2..f6a11dfbb7 100644 --- a/plugins/catalog/src/components/AboutCard/index.ts +++ b/plugins/catalog/src/components/AboutCard/index.ts @@ -15,7 +15,6 @@ */ export { AboutCard } from './AboutCard'; -export type { AboutCardProps } from './AboutCard'; export { AboutContent } from './AboutContent'; export type { AboutContentProps } from './AboutContent'; export { AboutField } from './AboutField'; diff --git a/plugins/catalog/src/components/EntityLabelsCard/EntityLabelsCard.tsx b/plugins/catalog/src/components/EntityLabelsCard/EntityLabelsCard.tsx index 8c096932f8..403ecefd1f 100644 --- a/plugins/catalog/src/components/EntityLabelsCard/EntityLabelsCard.tsx +++ b/plugins/catalog/src/components/EntityLabelsCard/EntityLabelsCard.tsx @@ -27,10 +27,6 @@ import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** @public */ export interface EntityLabelsCardProps { - // Accepted for API compatibility but not applied. - // The new entity page layout handles card sizing. - // TODO: Discuss removal in code review. - variant?: string; title?: string; } @@ -55,7 +51,7 @@ const columnConfig: ColumnConfig[] = [ ]; export const EntityLabelsCard = (props: EntityLabelsCardProps) => { - const { variant: _variant, title } = props; + const { title } = props; const { entity } = useEntity(); const { t } = useTranslationRef(catalogTranslationRef); diff --git a/plugins/catalog/src/components/EntityLinksCard/EntityLinksCard.tsx b/plugins/catalog/src/components/EntityLinksCard/EntityLinksCard.tsx index 60b5811ce9..e090effb55 100644 --- a/plugins/catalog/src/components/EntityLinksCard/EntityLinksCard.tsx +++ b/plugins/catalog/src/components/EntityLinksCard/EntityLinksCard.tsx @@ -26,14 +26,10 @@ import { catalogTranslationRef } from '../../alpha/translation'; /** @public */ export interface EntityLinksCardProps { cols?: ColumnBreakpoints | number; - // Accepted for API compatibility but not applied. - // The new entity page layout handles card sizing. - // TODO: Discuss removal in code review. - variant?: string; } export const EntityLinksCard = (props: EntityLinksCardProps) => { - const { cols = undefined, variant: _variant } = props; + const { cols = undefined } = props; const { entity } = useEntity(); const app = useApp(); const { t } = useTranslationRef(catalogTranslationRef); diff --git a/plugins/catalog/src/index.ts b/plugins/catalog/src/index.ts index f3cc7d3d0b..a59283c677 100644 --- a/plugins/catalog/src/index.ts +++ b/plugins/catalog/src/index.ts @@ -23,7 +23,6 @@ export * from './apis'; export type { - AboutCardProps, AboutContentProps, AboutFieldProps, } from './components/AboutCard'; diff --git a/plugins/catalog/src/plugin.ts b/plugins/catalog/src/plugin.ts index 50d858a45f..6fe531418b 100644 --- a/plugins/catalog/src/plugin.ts +++ b/plugins/catalog/src/plugin.ts @@ -43,7 +43,6 @@ import { SearchResultListItemExtensionProps, } from '@backstage/plugin-search-react'; import { DefaultStarredEntitiesApi } from './apis'; -import { AboutCardProps } from './components/AboutCard'; import { DefaultCatalogPageProps } from './components/CatalogPage'; import { DependencyOfComponentsCardProps } from './components/DependencyOfComponentsCard'; import { DependsOnComponentsCardProps } from './components/DependsOnComponentsCard'; @@ -128,15 +127,14 @@ export const CatalogEntityPage: () => JSX.Element = catalogPlugin.provide( * not extremely customizable; feel free to make a copy of it as a starting * point if you like. */ -export const EntityAboutCard: (props: AboutCardProps) => JSX.Element = - catalogPlugin.provide( - createComponentExtension({ - name: 'EntityAboutCard', - component: { - lazy: () => import('./components/AboutCard').then(m => m.AboutCard), - }, - }), - ); +export const EntityAboutCard: () => JSX.Element = catalogPlugin.provide( + createComponentExtension({ + name: 'EntityAboutCard', + component: { + lazy: () => import('./components/AboutCard').then(m => m.AboutCard), + }, + }), +); /** @public */ export const EntityLinksCard = catalogPlugin.provide( diff --git a/plugins/org/report.api.md b/plugins/org/report.api.md index 776affbb9f..0d6b15b308 100644 --- a/plugins/org/report.api.md +++ b/plugins/org/report.api.md @@ -18,7 +18,6 @@ export type ComponentsGridClassKey = // @public (undocumented) export const EntityGroupProfileCard: (props: { - variant?: string; showLinks?: boolean; }) => JSX_2.Element; @@ -48,7 +47,6 @@ export type EntityRelationAggregation = 'direct' | 'aggregated'; // @public (undocumented) export const EntityUserProfileCard: (props: { - variant?: string; showLinks?: boolean; maxRelations?: number; hideIcons?: boolean; @@ -56,7 +54,6 @@ export const EntityUserProfileCard: (props: { // @public (undocumented) export const GroupProfileCard: (props: { - variant?: string; showLinks?: boolean; }) => JSX_2.Element; @@ -116,7 +113,6 @@ export type OwnershipCardClassKey = // @public (undocumented) export const UserProfileCard: (props: { - variant?: string; showLinks?: boolean; maxRelations?: number; hideIcons?: boolean; diff --git a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx index 0bd1100633..5810a0447d 100644 --- a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx +++ b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx @@ -80,14 +80,8 @@ const CardTitle = (props: { title: string; pictureSrc?: string }) => ( ); /** @public */ -export const GroupProfileCard = (props: { - // Accepted for API compatibility but not applied. - // The new entity page layout handles card sizing. - // TODO: Discuss removal in code review. - variant?: string; - showLinks?: boolean; -}) => { - const { variant: _variant } = props; +export const GroupProfileCard = (props: { showLinks?: boolean }) => { + const { showLinks } = props; const catalogApi = useApi(catalogApiRef); const alertApi = useApi(alertApiRef); const { entity: group } = useEntity(); @@ -237,7 +231,7 @@ export const GroupProfileCard = (props: { secondary={t('groupProfileCard.listItemTitle.childGroups')} /> - {props?.showLinks && } + {showLinks && } diff --git a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.stories.tsx b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.stories.tsx index 0463cc78ad..aa1bf978e6 100644 --- a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.stories.tsx +++ b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.stories.tsx @@ -55,7 +55,7 @@ export const Default = () => ( - + @@ -82,7 +82,7 @@ export const NoImage = () => ( - + @@ -134,7 +134,7 @@ export const ExtraDetails = () => ( - + diff --git a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.test.tsx b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.test.tsx index bd446c8c6e..45936c564f 100644 --- a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.test.tsx +++ b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.test.tsx @@ -51,7 +51,7 @@ describe('UserSummary Test', () => { it('Display Profile Card', async () => { await renderInTestApp( - + , { mountedRoutes: { @@ -87,7 +87,7 @@ describe('UserSummary Test', () => { it('Should limit the number of displayed groups in the card', async () => { await renderInTestApp( - + , { mountedRoutes: { @@ -109,7 +109,7 @@ describe('UserSummary Test', () => { it('Show more groups button when there are more user relations than the maximum', async () => { await renderInTestApp( - + , { mountedRoutes: { @@ -128,7 +128,7 @@ describe('UserSummary Test', () => { it('Hide more groups button when limit value is less than or equal to user relations', async () => { await renderInTestApp( - + , { mountedRoutes: { @@ -148,7 +148,7 @@ describe('UserSummary Test', () => { it('Hide all groups when max relations is equals to zero', async () => { await renderInTestApp( - + , { mountedRoutes: { @@ -193,7 +193,7 @@ describe('Edit Button', () => { await renderInTestApp( - + , { mountedRoutes: { @@ -234,7 +234,7 @@ describe('Edit Button', () => { await renderInTestApp( - + , { mountedRoutes: { @@ -285,7 +285,7 @@ describe('Edit Button', () => { await renderInTestApp( - + , { mountedRoutes: { @@ -337,7 +337,7 @@ describe('Edit Button', () => { await renderInTestApp( - + , { mountedRoutes: { diff --git a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx index 12a996cb4b..d35682101e 100644 --- a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx +++ b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx @@ -96,12 +96,11 @@ export const UserProfileCard = (props: { // Accepted for API compatibility but not applied. // The new entity page layout handles card sizing. // TODO: Discuss removal in code review. - variant?: string; showLinks?: boolean; maxRelations?: number; hideIcons?: boolean; }) => { - const { maxRelations, hideIcons, variant: _variant } = props; + const { maxRelations, hideIcons } = props; const classes = useStyles(); const { entity: user } = useEntity(); From c6080eb0b067386ccd46c1140c1057dba7be019f Mon Sep 17 00:00:00 2001 From: Johan Persson Date: Mon, 2 Mar 2026 16:17:57 +0100 Subject: [PATCH 17/25] chore: update changeset. Signed-off-by: Johan Persson --- .changeset/social-worlds-report.md | 5 +++++ .changeset/thirty-kiwis-trade.md | 18 ++++++++++++++---- 2 files changed, 19 insertions(+), 4 deletions(-) create mode 100644 .changeset/social-worlds-report.md diff --git a/.changeset/social-worlds-report.md b/.changeset/social-worlds-report.md new file mode 100644 index 0000000000..58acda40a8 --- /dev/null +++ b/.changeset/social-worlds-report.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': minor +--- + +Added `EntityInfoCard` component to `@backstage/plugin-catalog-react` as a BUI-based card wrapper for entity page cards. diff --git a/.changeset/thirty-kiwis-trade.md b/.changeset/thirty-kiwis-trade.md index b2cdc5fb01..73fc21724b 100644 --- a/.changeset/thirty-kiwis-trade.md +++ b/.changeset/thirty-kiwis-trade.md @@ -1,7 +1,17 @@ --- -'@backstage/plugin-catalog-react': minor -'@backstage/plugin-catalog': patch -'@backstage/plugin-org': patch +'@backstage/plugin-catalog': major +'@backstage/plugin-org': major --- -Added `EntityInfoCard` component to `@backstage/plugin-catalog-react` as a BUI-based card wrapper for entity page cards. Migrated `EntityAboutCard`, `EntityLinksCard`, `EntityLabelsCard`, `GroupProfileCard`, and `UserProfileCard` from MUI/InfoCard to use the new BUI card layout. Replaced MUI ImageList and Grid internals in LinksGridList and AboutContent with BUI Grid. Migrated EntityLabelsCard from core-components Table to BUI Table. Updated GroupProfileCard and UserProfileCard to use BUI Avatar in card titles. +Migrated `EntityAboutCard`, `EntityLinksCard`, `EntityLabelsCard`, `GroupProfileCard`, and `UserProfileCard` from MUI/InfoCard to use the new BUI card layout and BUI components where possible. + +**BREAKING**: Removed `variant` prop from EntityAboutCard, EntityUserProfileCard, EntityGroupProfileCard, EntityLabelsCard, EntityLinksCard + +**Migration:** + +Simply delete the obsolete `variant` prop, e.g: + +```diff +- ++ +``` From 7388c6516d725f5d21c1b288298ce0b618720dd8 Mon Sep 17 00:00:00 2001 From: Johan Persson Date: Mon, 2 Mar 2026 16:40:47 +0100 Subject: [PATCH 18/25] refactor(catalog): use Grid.Item colSpan for full-width AboutFields Replace inline `style={{ gridColumn: '1 / -1' }}` with BUI-native `Grid.Item colSpan` for full-width fields in AboutContent. Extract grid columns into a shared variable used by both Grid.Root and Grid.Item to keep them in sync. Remove the `style` prop from AboutField that was only added to support the inline gridColumn approach. Signed-off-by: Johan Persson --- .../src/components/AboutCard/AboutContent.tsx | 64 ++++++++++--------- .../src/components/AboutCard/AboutField.tsx | 14 +--- 2 files changed, 36 insertions(+), 42 deletions(-) diff --git a/plugins/catalog/src/components/AboutCard/AboutContent.tsx b/plugins/catalog/src/components/AboutCard/AboutContent.tsx index d35891cd05..503d963575 100644 --- a/plugins/catalog/src/components/AboutCard/AboutContent.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutContent.tsx @@ -114,20 +114,21 @@ export function AboutContent(props: AboutContentProps) { entitySourceLocation = undefined; } + const columns = { initial: '1', sm: '2', lg: '3' } as const; + return ( - - - - + + + + + + {isLocation && (entity?.spec?.targets || entity?.spec?.target) && ( - - target as string) - .map(target => ({ - text: target, - href: getLocationTargetHref( - target, - (entity?.spec?.type || t('aboutCard.unknown')) as string, - entitySourceLocation!, - ), - }))} - /> - + + + target as string) + .map(target => ({ + text: target, + href: getLocationTargetHref( + target, + (entity?.spec?.type || t('aboutCard.unknown')) as string, + entitySourceLocation!, + ), + }))} + /> + + )} ); diff --git a/plugins/catalog/src/components/AboutCard/AboutField.tsx b/plugins/catalog/src/components/AboutCard/AboutField.tsx index 87f817c643..36a0406876 100644 --- a/plugins/catalog/src/components/AboutCard/AboutField.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutField.tsx @@ -17,7 +17,7 @@ import { useElementFilter } from '@backstage/core-plugin-api'; import Typography from '@material-ui/core/Typography'; import { makeStyles } from '@material-ui/core/styles'; -import { CSSProperties, ReactNode } from 'react'; +import { ReactNode } from 'react'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; import { catalogTranslationRef } from '../../alpha/translation'; @@ -50,19 +50,11 @@ export interface AboutFieldProps { gridSizes?: Record; children?: ReactNode; className?: string; - style?: CSSProperties; } /** @public */ export function AboutField(props: AboutFieldProps) { - const { - label, - value, - gridSizes: _gridSizes, - children, - className, - style, - } = props; + const { label, value, gridSizes: _gridSizes, children, className } = props; const classes = useStyles(); const { t } = useTranslationRef(catalogTranslationRef); @@ -78,7 +70,7 @@ export function AboutField(props: AboutFieldProps) { ); return ( -
+
{label} From 2aaacb50951533a3773544af95a023ed66a61230 Mon Sep 17 00:00:00 2001 From: Johan Persson Date: Mon, 2 Mar 2026 16:49:04 +0100 Subject: [PATCH 19/25] refactor(catalog): remove obsolete gridSizes prop from AboutField Remove the unused `gridSizes` prop from AboutField. Grid layout is now owned by the parent Grid.Root in AboutContent, making this prop unnecessary. Update changeset with breaking change and migration guide. Signed-off-by: Johan Persson --- .changeset/thirty-kiwis-trade.md | 9 +++++++-- plugins/catalog/report.api.md | 5 ----- .../catalog/src/components/AboutCard/AboutContent.tsx | 7 ------- plugins/catalog/src/components/AboutCard/AboutField.tsx | 3 +-- .../Cards/User/UserProfileCard/UserProfileCard.tsx | 3 --- 5 files changed, 8 insertions(+), 19 deletions(-) diff --git a/.changeset/thirty-kiwis-trade.md b/.changeset/thirty-kiwis-trade.md index 73fc21724b..08ddbaa1a4 100644 --- a/.changeset/thirty-kiwis-trade.md +++ b/.changeset/thirty-kiwis-trade.md @@ -5,13 +5,18 @@ Migrated `EntityAboutCard`, `EntityLinksCard`, `EntityLabelsCard`, `GroupProfileCard`, and `UserProfileCard` from MUI/InfoCard to use the new BUI card layout and BUI components where possible. -**BREAKING**: Removed `variant` prop from EntityAboutCard, EntityUserProfileCard, EntityGroupProfileCard, EntityLabelsCard, EntityLinksCard +**BREAKING**: Removed `variant` prop from EntityAboutCard, EntityUserProfileCard, EntityGroupProfileCard, EntityLabelsCard, EntityLinksCard. Removed `gridSizes` prop from `AboutField`. **Migration:** -Simply delete the obsolete `variant` prop, e.g: +Simply delete the obsolete `variant` and `gridSizes` props, e.g: ```diff - + ``` + +```diff +- ++ +``` diff --git a/plugins/catalog/report.api.md b/plugins/catalog/report.api.md index 411d60076e..48e3f53de2 100644 --- a/plugins/catalog/report.api.md +++ b/plugins/catalog/report.api.md @@ -8,7 +8,6 @@ import { BackstagePlugin } from '@backstage/core-plugin-api'; import { CatalogApi } from '@backstage/plugin-catalog-react'; import { ComponentEntity } from '@backstage/catalog-model'; import { CompoundEntityRef } from '@backstage/catalog-model'; -import { CSSProperties } from 'react'; import { DomainEntity } from '@backstage/catalog-model'; import { ElementType } from 'react'; import { Entity } from '@backstage/catalog-model'; @@ -60,12 +59,8 @@ export interface AboutFieldProps { // (undocumented) className?: string; // (undocumented) - gridSizes?: Record; - // (undocumented) label: string; // (undocumented) - style?: CSSProperties; - // (undocumented) value?: string; } diff --git a/plugins/catalog/src/components/AboutCard/AboutContent.tsx b/plugins/catalog/src/components/AboutCard/AboutContent.tsx index 503d963575..eff9ae7da3 100644 --- a/plugins/catalog/src/components/AboutCard/AboutContent.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutContent.tsx @@ -133,7 +133,6 @@ export function AboutContent(props: AboutContentProps) { label={t('aboutCard.ownerField.label')} value={t('aboutCard.ownerField.value')} className={classes.description} - gridSizes={{ xs: 12, sm: 6, lg: 4 }} > {ownedByRelations.length > 0 && ( @@ -143,7 +142,6 @@ export function AboutContent(props: AboutContentProps) { {partOfDomainRelations.length > 0 && ( {partOfSystemRelations.length > 0 && ( )} {(isAPI || @@ -201,13 +196,11 @@ export function AboutContent(props: AboutContentProps) { )} {(entity?.metadata?.tags || []).map(tag => ( diff --git a/plugins/catalog/src/components/AboutCard/AboutField.tsx b/plugins/catalog/src/components/AboutCard/AboutField.tsx index 36a0406876..725a82612a 100644 --- a/plugins/catalog/src/components/AboutCard/AboutField.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutField.tsx @@ -47,14 +47,13 @@ const useStyles = makeStyles(theme => ({ export interface AboutFieldProps { label: string; value?: string; - gridSizes?: Record; children?: ReactNode; className?: string; } /** @public */ export function AboutField(props: AboutFieldProps) { - const { label, value, gridSizes: _gridSizes, children, className } = props; + const { label, value, children, className } = props; const classes = useStyles(); const { t } = useTranslationRef(catalogTranslationRef); diff --git a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx index d35682101e..e34adde670 100644 --- a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx +++ b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx @@ -93,9 +93,6 @@ const CardTitle = (props: { title: string; pictureSrc?: string }) => /** @public */ export const UserProfileCard = (props: { - // Accepted for API compatibility but not applied. - // The new entity page layout handles card sizing. - // TODO: Discuss removal in code review. showLinks?: boolean; maxRelations?: number; hideIcons?: boolean; From 3d422028d3d4595f56e306ffa6ba7cf6160ac116 Mon Sep 17 00:00:00 2001 From: Johan Persson Date: Mon, 2 Mar 2026 17:56:08 +0100 Subject: [PATCH 20/25] fix(catalog): address review feedback for BUI card migration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace `as any` cast with type-safe `as Columns` in LinksGridList, clamping column count to the valid 1–12 range - Restore pagination (pageSize: 5) in EntityLabelsCard using the BUI `useTable` hook - Use translation ref for EntityLabelsCard column headers instead of hard-coded English strings Signed-off-by: Johan Persson --- plugins/catalog/src/alpha/translation.ts | 2 + .../EntityLabelsCard/EntityLabelsCard.tsx | 59 +++++++++++-------- .../EntityLinksCard/LinksGridList.tsx | 7 ++- 3 files changed, 43 insertions(+), 25 deletions(-) diff --git a/plugins/catalog/src/alpha/translation.ts b/plugins/catalog/src/alpha/translation.ts index f346c408b9..3d48c0915a 100644 --- a/plugins/catalog/src/alpha/translation.ts +++ b/plugins/catalog/src/alpha/translation.ts @@ -111,6 +111,8 @@ export const catalogTranslationRef = createTranslationRef({ }, entityLabelsCard: { title: 'Labels', + columnKeyLabel: 'Label', + columnValueLabel: 'Value', emptyDescription: 'No labels defined for this entity. You can add labels to your entity YAML as shown in the highlighted example below:', readMoreButtonTitle: 'Read more', diff --git a/plugins/catalog/src/components/EntityLabelsCard/EntityLabelsCard.tsx b/plugins/catalog/src/components/EntityLabelsCard/EntityLabelsCard.tsx index 403ecefd1f..95985f31c3 100644 --- a/plugins/catalog/src/components/EntityLabelsCard/EntityLabelsCard.tsx +++ b/plugins/catalog/src/components/EntityLabelsCard/EntityLabelsCard.tsx @@ -19,11 +19,13 @@ import { EntityLabelsEmptyState } from './EntityLabelsEmptyState'; import { Table, CellText, + useTable, type ColumnConfig, type TableItem, } from '@backstage/ui'; import { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { useMemo } from 'react'; /** @public */ export interface EntityLabelsCardProps { @@ -36,20 +38,6 @@ interface LabelItem extends TableItem { value: string; } -const columnConfig: ColumnConfig[] = [ - { - id: 'key', - label: 'Label', - isRowHeader: true, - cell: item => , - }, - { - id: 'value', - label: 'Value', - cell: item => , - }, -]; - export const EntityLabelsCard = (props: EntityLabelsCardProps) => { const { title } = props; const { entity } = useEntity(); @@ -57,20 +45,45 @@ export const EntityLabelsCard = (props: EntityLabelsCardProps) => { const labels = entity?.metadata?.labels; + const columnConfig: ColumnConfig[] = useMemo( + () => [ + { + id: 'key', + label: t('entityLabelsCard.columnKeyLabel'), + isRowHeader: true, + cell: item => , + }, + { + id: 'value', + label: t('entityLabelsCard.columnValueLabel'), + cell: item => , + }, + ], + [t], + ); + + const data = useMemo( + () => + Object.keys(labels ?? {}).map(labelKey => ({ + id: labelKey, + key: labelKey, + value: labels![labelKey], + })), + [labels], + ); + + const { tableProps } = useTable({ + mode: 'complete', + data, + paginationOptions: { pageSize: 5 }, + }); + return ( {!labels || Object.keys(labels).length === 0 ? ( ) : ( -
({ - id: labelKey, - key: labelKey, - value: labels[labelKey], - }))} - pagination={{ type: 'none' }} - /> +
)} ); diff --git a/plugins/catalog/src/components/EntityLinksCard/LinksGridList.tsx b/plugins/catalog/src/components/EntityLinksCard/LinksGridList.tsx index 90b7e2058b..dfb0b280ff 100644 --- a/plugins/catalog/src/components/EntityLinksCard/LinksGridList.tsx +++ b/plugins/catalog/src/components/EntityLinksCard/LinksGridList.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { Grid } from '@backstage/ui'; +import { Grid, type Columns } from '@backstage/ui'; import { IconLink } from './IconLink'; import { ColumnBreakpoints } from './types'; import { useDynamicColumns } from './useDynamicColumns'; @@ -36,7 +36,10 @@ export function LinksGridList(props: LinksGridListProps) { const numOfCols = useDynamicColumns(cols); return ( - + {items.map(({ text, href, Icon }, i) => ( ))} From 20cb5117e1351fbb6ab4774a5b620d1324bb3d9c Mon Sep 17 00:00:00 2001 From: Johan Persson Date: Mon, 2 Mar 2026 17:58:19 +0100 Subject: [PATCH 21/25] fix: remove variant prop from create-app template for migrated cards The E2E tests scaffold a new app from the create-app template, which still passed variant="gridItem" to EntityAboutCard, EntityUserProfileCard, and EntityGroupProfileCard after the prop was removed. Signed-off-by: Johan Persson --- .../packages/app/src/components/catalog/EntityPage.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx b/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx index f75d984366..750dfe6cea 100644 --- a/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx @@ -128,7 +128,7 @@ const overviewContent = ( {entityWarningContent} - + @@ -298,7 +298,7 @@ const userPage = ( {entityWarningContent} - + @@ -314,7 +314,7 @@ const groupPage = ( {entityWarningContent} - + @@ -336,7 +336,7 @@ const systemPage = ( {entityWarningContent} - + @@ -383,7 +383,7 @@ const domainPage = ( {entityWarningContent} - + From cccf0c47e28912dbf026ff8f286ac4b7b4cc06f6 Mon Sep 17 00:00:00 2001 From: Johan Persson Date: Tue, 3 Mar 2026 09:14:10 +0100 Subject: [PATCH 22/25] Update api report, code-sample files and docs. Signed-off-by: Johan Persson --- .../integrating-plugin-into-software-catalog.md | 2 +- plugins/catalog/report-alpha.api.md | 10 ++++++---- .../docs/code/code-sample.md | 2 +- .../documented-component/docs/code/code-sample.md | 2 +- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/plugins/integrating-plugin-into-software-catalog.md b/docs/plugins/integrating-plugin-into-software-catalog.md index 911d74b019..417b932451 100644 --- a/docs/plugins/integrating-plugin-into-software-catalog.md +++ b/docs/plugins/integrating-plugin-into-software-catalog.md @@ -94,7 +94,7 @@ const systemPage = ( - + diff --git a/plugins/catalog/report-alpha.api.md b/plugins/catalog/report-alpha.api.md index 0b8376c8f6..fbe90f461c 100644 --- a/plugins/catalog/report-alpha.api.md +++ b/plugins/catalog/report-alpha.api.md @@ -74,8 +74,8 @@ export const catalogTranslationRef: TranslationRef< readonly 'aboutCard.targetsField.label': 'Targets'; readonly 'searchResultItem.type': 'Type'; readonly 'searchResultItem.kind': 'Kind'; - readonly 'searchResultItem.lifecycle': 'Lifecycle'; readonly 'searchResultItem.owner': 'Owner'; + readonly 'searchResultItem.lifecycle': 'Lifecycle'; readonly 'catalogTable.allFilters': 'All'; readonly 'catalogTable.warningPanelTitle': 'Could not fetch catalog entities.'; readonly 'catalogTable.viewActionTitle': 'View'; @@ -95,14 +95,16 @@ export const catalogTranslationRef: TranslationRef< readonly 'entityContextMenu.unregisterMenuTitle': 'Unregister entity'; readonly 'entityContextMenu.moreButtonAriaLabel': 'more'; readonly 'entityLabelsCard.title': 'Labels'; - readonly 'entityLabelsCard.emptyDescription': 'No labels defined for this entity. You can add labels to your entity YAML as shown in the highlighted example below:'; readonly 'entityLabelsCard.readMoreButtonTitle': 'Read more'; - readonly 'entityLabels.warningPanelTitle': 'Entity not found'; + readonly 'entityLabelsCard.columnKeyLabel': 'Label'; + readonly 'entityLabelsCard.columnValueLabel': 'Value'; + readonly 'entityLabelsCard.emptyDescription': 'No labels defined for this entity. You can add labels to your entity YAML as shown in the highlighted example below:'; readonly 'entityLabels.ownerLabel': 'Owner'; + readonly 'entityLabels.warningPanelTitle': 'Entity not found'; readonly 'entityLabels.lifecycleLabel': 'Lifecycle'; readonly 'entityLinksCard.title': 'Links'; - readonly 'entityLinksCard.emptyDescription': 'No links defined for this entity. You can add links to your entity YAML as shown in the highlighted example below:'; readonly 'entityLinksCard.readMoreButtonTitle': 'Read more'; + readonly 'entityLinksCard.emptyDescription': 'No links defined for this entity. You can add links to your entity YAML as shown in the highlighted example below:'; readonly 'entityNotFound.title': 'Entity was not found'; readonly 'entityNotFound.description': 'Want to help us build this? Check out our Getting Started documentation.'; readonly 'entityNotFound.docButtonTitle': 'DOCS'; diff --git a/plugins/techdocs-backend/examples/documented-component-uffizzi/docs/code/code-sample.md b/plugins/techdocs-backend/examples/documented-component-uffizzi/docs/code/code-sample.md index f41ab1d506..d1172aaceb 100644 --- a/plugins/techdocs-backend/examples/documented-component-uffizzi/docs/code/code-sample.md +++ b/plugins/techdocs-backend/examples/documented-component-uffizzi/docs/code/code-sample.md @@ -10,7 +10,7 @@ const serviceEntityPage = ( - + diff --git a/plugins/techdocs-backend/examples/documented-component/docs/code/code-sample.md b/plugins/techdocs-backend/examples/documented-component/docs/code/code-sample.md index f41ab1d506..d1172aaceb 100644 --- a/plugins/techdocs-backend/examples/documented-component/docs/code/code-sample.md +++ b/plugins/techdocs-backend/examples/documented-component/docs/code/code-sample.md @@ -10,7 +10,7 @@ const serviceEntityPage = ( - + From 74fd81c28dc5f1e783ade8d1c4388e9b4c0f4e66 Mon Sep 17 00:00:00 2001 From: Johan Persson Date: Tue, 3 Mar 2026 11:31:05 +0100 Subject: [PATCH 23/25] fix(catalog-react): make EntityInfoCard fill available container height Set `height: 100%` on EntityInfoCard so cards fill their grid cell, matching the row height of sibling cards. This replaces the behavior previously provided by `variant="gridItem"` on the old InfoCard. Signed-off-by: Johan Persson --- .../src/components/EntityInfoCard/EntityInfoCard.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/plugins/catalog-react/src/components/EntityInfoCard/EntityInfoCard.tsx b/plugins/catalog-react/src/components/EntityInfoCard/EntityInfoCard.tsx index c31bd133e0..a0c7c20ffe 100644 --- a/plugins/catalog-react/src/components/EntityInfoCard/EntityInfoCard.tsx +++ b/plugins/catalog-react/src/components/EntityInfoCard/EntityInfoCard.tsx @@ -23,6 +23,14 @@ import { Text, Flex, } from '@backstage/ui'; +import { makeStyles } from '@material-ui/core/styles'; +import classNames from 'classnames'; + +const useStyles = makeStyles({ + root: { + height: '100%', + }, +}); /** @public */ export interface EntityInfoCardProps { @@ -36,9 +44,10 @@ export interface EntityInfoCardProps { /** @public */ export function EntityInfoCard(props: EntityInfoCardProps) { const { title, headerActions, footerActions, children, className } = props; + const classes = useStyles(); return ( - + {title && ( From aef79e0af46c85e177d104347453829144bb946f Mon Sep 17 00:00:00 2001 From: Johan Persson Date: Tue, 3 Mar 2026 12:44:06 +0100 Subject: [PATCH 24/25] fix(catalog): style icon links section in AboutCard Add horizontal borders and bottom margin to the icon links container in the AboutCard to visually separate it from the card content. Signed-off-by: Johan Persson --- .../src/components/AboutCard/AboutCard.tsx | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.tsx index e516432dad..065d786a13 100644 --- a/plugins/catalog/src/components/AboutCard/AboutCard.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutCard.tsx @@ -72,6 +72,16 @@ import { createFromTemplateRouteRef, viewTechDocRouteRef } from '../../routes'; import { catalogTranslationRef } from '../../alpha/translation'; import { useSourceTemplateCompoundEntityRef } from './hooks'; import { AboutContent } from './AboutContent'; +import { makeStyles } from '@material-ui/core/styles'; + +const useStyles = makeStyles({ + linkContainer: { + border: '1px solid var(--bui-border-1)', + borderLeft: 'none', + borderRight: 'none', + marginBottom: 'var(--bui-space-6)', + }, +}); export function useCatalogSourceIconLinkProps() { const { entity } = useEntity(); @@ -154,6 +164,7 @@ export interface InternalAboutCardProps { export function InternalAboutCard(props: InternalAboutCardProps) { const { iconLinks } = props; + const classes = useStyles(); const { entity } = useEntity(); const catalogApi = useApi(catalogApiRef); const alertApi = useApi(alertApiRef); @@ -223,7 +234,9 @@ export function InternalAboutCard(props: InternalAboutCardProps) { } > - {iconLinks ?? } +
+ {iconLinks ?? } +
); From a0310d57ec4baf413054e846a35e63bbca21be18 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 4 Mar 2026 10:49:09 +0100 Subject: [PATCH 25/25] Apply suggestion from @Rugvip Signed-off-by: Patrik Oldsberg --- .changeset/thirty-kiwis-trade.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/thirty-kiwis-trade.md b/.changeset/thirty-kiwis-trade.md index 08ddbaa1a4..d88d804dac 100644 --- a/.changeset/thirty-kiwis-trade.md +++ b/.changeset/thirty-kiwis-trade.md @@ -1,6 +1,6 @@ --- '@backstage/plugin-catalog': major -'@backstage/plugin-org': major +'@backstage/plugin-org': minor --- Migrated `EntityAboutCard`, `EntityLinksCard`, `EntityLabelsCard`, `GroupProfileCard`, and `UserProfileCard` from MUI/InfoCard to use the new BUI card layout and BUI components where possible.