feat(ui): add href support to HeaderMetadataUsers, remove bold from description, full-width header

- Add `href` to `HeaderMetadataUser`: avatar becomes a link and name renders as a primary `Link` with `standalone` (no underline at rest)
- Add `WithMetadataUsersNoLinks` story alongside `WithMetadataUsers` to cover both link and non-link variants
- Remove `strong`/`em` from ReactMarkdown `allowedElements` in Header description — only plain text and inline links are now supported
- Replace `<Container>` root with a plain `<div>` so the Header spans its full parent width
- Add `HeaderMetadataStatus` component and CSS (new files)
- Update docs: fixtures, snippets, props-definition, and page.mdx to reflect all changes

Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
Made-with: Cursor
This commit is contained in:
Charles de Dreuille
2026-04-19 11:41:55 +02:00
parent 0e8edce069
commit 6310790556
12 changed files with 406 additions and 56 deletions
@@ -2,6 +2,7 @@
import { Header } from '../../../../../packages/ui/src/components/Header/Header';
import { HeaderMetadataUsers } from '../../../../../packages/ui/src/components/Header/HeaderMetadataUsers';
import { HeaderMetadataStatus } from '../../../../../packages/ui/src/components/Header/HeaderMetadataStatus';
import { Button } from '../../../../../packages/ui/src/components/Button/Button';
import { ButtonIcon } from '../../../../../packages/ui/src/components/ButtonIcon/ButtonIcon';
import {
@@ -16,10 +17,23 @@ const users = {
giles: {
name: 'Giles Peyton-Nicoll',
src: 'https://i.pravatar.cc/150?u=giles',
href: '/users/giles',
},
alice: {
name: 'Alice Johnson',
src: 'https://i.pravatar.cc/150?u=alice42',
href: '/users/alice',
},
bob: {
name: 'Bob Smith',
src: 'https://i.pravatar.cc/150?u=bob',
href: '/users/bob',
},
carol: {
name: 'Carol Williams',
src: 'https://i.pravatar.cc/150?u=carol',
href: '/users/carol',
},
alice: { name: 'Alice Johnson', src: 'https://i.pravatar.cc/150?u=alice42' },
bob: { name: 'Bob Smith', src: 'https://i.pravatar.cc/150?u=bob' },
carol: { name: 'Carol Williams', src: 'https://i.pravatar.cc/150?u=carol' },
};
const tabs = [
@@ -43,11 +57,14 @@ const breadcrumbs = [
const tags = [
{ label: 'TypeScript' },
{ label: 'Platform', href: '/platform' },
{ label: 'Gold' },
];
const metadataUsers = [
{ label: 'Type', value: 'website' },
{
label: 'Status',
value: <HeaderMetadataStatus label="Passing" color="success" />,
},
{
label: 'Owner',
value: <HeaderMetadataUsers users={[users.giles]} />,
@@ -65,7 +82,7 @@ export const WithEverything = () => (
<Header
title="Page Title"
tags={tags}
description="A short description of this page. Supports [inline links](https://backstage.io) and **bold text**."
description="A short description of this page. Supports [inline links](https://backstage.io)."
metadata={metadataUsers}
tabs={tabs.slice(0, 2)}
customActions={
@@ -80,7 +97,24 @@ export const WithEverything = () => (
export const WithMetadataUsers = () => (
<MemoryRouter>
<Header title="Page Title" metadata={metadataUsers.slice(0, 2)} />
<Header
title="Page Title"
metadata={[
{ label: 'Type', value: 'website' },
{
label: 'Owner',
value: <HeaderMetadataUsers users={[users.giles]} />,
},
{
label: 'Contributors',
value: (
<HeaderMetadataUsers
users={[users.alice, users.bob, users.carol]}
/>
),
},
]}
/>
</MemoryRouter>
);
@@ -94,7 +128,7 @@ export const WithDescription = () => (
<MemoryRouter>
<Header
title="Page Title"
description="A short description of this page. Supports [inline links](https://backstage.io) and **bold text**."
description="A short description of this page. Supports [inline links](https://backstage.io)."
/>
</MemoryRouter>
);
@@ -106,7 +140,34 @@ export const WithMetadata = () => (
metadata={[
{ label: 'Owner', value: 'platform-team' },
{ label: 'Type', value: 'website' },
{ label: 'Tier', value: 'gold' },
]}
/>
</MemoryRouter>
);
export const WithMetadataStatus = () => (
<MemoryRouter>
<Header
title="Page Title"
metadata={[
{
label: 'Status',
value: <HeaderMetadataStatus label="Passing" color="success" />,
},
{
label: 'Build',
value: (
<HeaderMetadataStatus
label="Failed"
color="danger"
href="/builds/123"
/>
),
},
{
label: 'Coverage',
value: <HeaderMetadataStatus label="Warning" color="warning" />,
},
]}
/>
</MemoryRouter>
+13 -3
View File
@@ -8,10 +8,11 @@ import {
WithDescription,
WithMetadata,
WithMetadataUsers,
WithMetadataStatus,
WithCustomActions,
WithMenu,
} from './components';
import { headerPagePropDefs } from './props-definition';
import { headerPagePropDefs, headerMetadataUsersPropDefs } from './props-definition';
import {
usage,
defaultSnippet,
@@ -20,6 +21,7 @@ import {
withDescription,
withMetadata,
withMetadataUsers,
withMetadataStatus,
withCustomActions,
withMenu,
} from './snippets';
@@ -53,7 +55,7 @@ Tags are rendered above the title. Each tag with an `href` renders as a link; ta
### Description
The description accepts a markdown string. Only inline elements are supported — links, bold, and italic.
The description accepts a markdown string with support for inline links. Bold, italic, and block-level markdown are not rendered.
<Snippet open preview={<WithDescription />} code={withDescription} />
@@ -65,10 +67,18 @@ Key-value pairs displayed below the description.
### Metadata with users
Use `HeaderMetadataUsers` as the metadata value to display users as avatars. A single user shows the avatar with their name beside it. Multiple users show a row of avatars — hover to reveal each name via tooltip.
Use `HeaderMetadataUsers` as the metadata value to display users as avatars. A single user shows the avatar with their name beside it. Multiple users show a row of avatars — hover to reveal each name via tooltip. When a user has an `href`, the avatar and name become links.
<Snippet open preview={<WithMetadataUsers />} code={withMetadataUsers} />
<PropsTable data={headerMetadataUsersPropDefs} />
### Metadata with status
Use `HeaderMetadataStatus` as the metadata value to display a status indicator. The dot colour is driven by the `color` prop which maps to BUI status tokens. Pass an `href` to make the label a link.
<Snippet open preview={<WithMetadataStatus />} code={withMetadataStatus} />
### Tabs
Tabs auto-detect the active tab from the current route when `activeTabId` is omitted. Pass an explicit `activeTabId` to override, or `null` for no active tab.
@@ -28,7 +28,7 @@ export const headerPagePropDefs: Record<string, PropDef> = {
description: {
type: 'string',
description:
'Markdown string rendered below the title. Only inline elements are supported: links, bold, and italic. Block-level markdown such as headings or lists is not rendered.',
'Markdown string rendered below the title. Only inline links are supported. Bold, italic, and block-level markdown are not rendered.',
},
metadata: {
type: 'complex',
@@ -114,3 +114,33 @@ export const headerPagePropDefs: Record<string, PropDef> = {
},
...classNamePropDefs,
};
export const headerMetadataUsersPropDefs: Record<string, PropDef> = {
users: {
type: 'complex',
description:
'List of users to display. A single user shows the avatar with their name beside it. Multiple users show a row of avatars with names revealed on hover via tooltip.',
complexType: {
name: 'HeaderMetadataUser[]',
properties: {
name: {
type: 'string',
required: true,
description:
'Display name shown beside the avatar (single) or in the tooltip (multiple).',
},
src: {
type: 'string',
required: false,
description: 'URL for the avatar image.',
},
href: {
type: 'string',
required: false,
description:
'When provided, the avatar becomes a link and the name is rendered as a Link component.',
},
},
},
},
};
+36 -13
View File
@@ -2,30 +2,33 @@ export const usage = `import { Header } from '@backstage/ui';
<Header title="Page Title" />`;
export const defaultSnippet = `import { Header, HeaderMetadataUsers } from '@backstage/ui';
export const defaultSnippet = `import { Header, HeaderMetadataUsers, HeaderMetadataStatus } from '@backstage/ui';
<Header
title="Page Title"
tags={[
{ label: 'TypeScript' },
{ label: 'Platform', href: '/platform' },
{ label: 'Gold' },
]}
description="A short description. Supports [inline links](https://backstage.io) and **bold text**."
description="A short description. Supports [inline links](https://backstage.io)."
metadata={[
{ label: 'Type', value: 'website' },
{
label: 'Status',
value: <HeaderMetadataStatus label="Passing" color="success" />,
},
{
label: 'Owner',
value: <HeaderMetadataUsers users={[{ name: 'Giles Peyton-Nicoll', src: '...' }]} />,
value: <HeaderMetadataUsers users={[{ name: 'Giles Peyton-Nicoll', src: '...', href: '/users/giles' }]} />,
},
{
label: 'Contributors',
value: (
<HeaderMetadataUsers
users={[
{ name: 'Alice Johnson', src: '...' },
{ name: 'Bob Smith', src: '...' },
{ name: 'Carol Williams', src: '...' },
{ name: 'Alice Johnson', src: '...', href: '/users/alice' },
{ name: 'Bob Smith', src: '...', href: '/users/bob' },
{ name: 'Carol Williams', src: '...', href: '/users/carol' },
]}
/>
),
@@ -89,7 +92,7 @@ export const withTags = `<Header
export const withDescription = `<Header
title="Page Title"
description="A short description. Supports [inline links](https://backstage.io) and **bold text**."
description="A short description. Supports [inline links](https://backstage.io)."
/>`;
export const withMetadata = `<Header
@@ -97,7 +100,26 @@ export const withMetadata = `<Header
metadata={[
{ label: 'Owner', value: 'platform-team' },
{ label: 'Type', value: 'website' },
{ label: 'Tier', value: 'gold' },
]}
/>`;
export const withMetadataStatus = `import { Header, HeaderMetadataStatus } from '@backstage/ui';
<Header
title="Page Title"
metadata={[
{
label: 'Status',
value: <HeaderMetadataStatus label="Passing" color="success" />,
},
{
label: 'Build',
value: <HeaderMetadataStatus label="Failed" color="danger" href="/builds/123" />,
},
{
label: 'Coverage',
value: <HeaderMetadataStatus label="Warning" color="warning" />,
},
]}
/>`;
@@ -106,18 +128,19 @@ export const withMetadataUsers = `import { Header, HeaderMetadataUsers } from '@
<Header
title="Page Title"
metadata={[
{ label: 'Type', value: 'website' },
{
label: 'Owner',
value: <HeaderMetadataUsers users={[{ name: 'Giles Peyton-Nicoll', src: '...' }]} />,
value: <HeaderMetadataUsers users={[{ name: 'Giles Peyton-Nicoll', src: '...', href: '/users/giles' }]} />,
},
{
label: 'Contributors',
value: (
<HeaderMetadataUsers
users={[
{ name: 'Alice Johnson', src: '...' },
{ name: 'Bob Smith', src: '...' },
{ name: 'Carol Williams', src: '...' },
{ name: 'Alice Johnson', src: '...', href: '/users/alice' },
{ name: 'Bob Smith', src: '...', href: '/users/bob' },
{ name: 'Carol Williams', src: '...', href: '/users/carol' },
]}
/>
),