ui: rename Header to PluginHeader

Renamed the Header component to PluginHeader for clarity, along with
all associated exports (HeaderProps, HeaderDefinition) and CSS class
names. Updated docs-ui documentation to match.

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2026-02-17 11:25:11 +01:00
parent 8c7f4e1aa8
commit 0ec3c0e50e
22 changed files with 162 additions and 127 deletions
@@ -0,0 +1,27 @@
---
'@backstage/ui': minor
---
**BREAKING**: Renamed the `Header` component to `PluginHeader` for clarity.
The following exports have been renamed:
- `Header``PluginHeader`
- `HeaderProps``PluginHeaderProps`
- `HeaderDefinition``PluginHeaderDefinition`
The `HeaderTab` type is unchanged as it is shared with `HeaderPage`.
CSS class names have been updated from `bui-Header*` to `bui-PluginHeader*`.
**Migration:**
```diff
-import { Header, HeaderDefinition } from '@backstage/ui';
+import { PluginHeader, PluginHeaderDefinition } from '@backstage/ui';
-<Header title="My plugin" />
+<PluginHeader title="My plugin" />
```
**Affected components:** plugin-header
@@ -1,6 +1,6 @@
'use client';
import { Header } from '../../../../../packages/ui/src/components/Header/Header';
import { PluginHeader } from '../../../../../packages/ui/src/components/PluginHeader/PluginHeader';
import { HeaderPage } from '../../../../../packages/ui/src/components/HeaderPage/HeaderPage';
import { ButtonIcon } from '../../../../../packages/ui/src/components/ButtonIcon/ButtonIcon';
import { Button } from '../../../../../packages/ui/src/components/Button/Button';
@@ -27,7 +27,7 @@ const tabs2 = [
export const WithAllOptionsAndTabs = () => (
<MemoryRouter>
<Header
<PluginHeader
title="My plugin"
titleLink="/"
tabs={tabs}
@@ -44,7 +44,7 @@ export const WithAllOptionsAndTabs = () => (
export const WithAllOptions = () => (
<MemoryRouter>
<Header
<PluginHeader
title="My plugin"
titleLink="/"
customActions={
@@ -61,7 +61,7 @@ export const WithAllOptions = () => (
export const WithHeaderPage = () => (
<MemoryRouter>
<>
<Header title="My plugin" titleLink="/" tabs={tabs.slice(0, 2)} />
<PluginHeader title="My plugin" titleLink="/" tabs={tabs.slice(0, 2)} />
<HeaderPage
title="Page title"
tabs={tabs2}
@@ -16,11 +16,11 @@ import {
} from './snippets';
import { PageTitle } from '@/components/PageTitle';
import { Theming } from '@/components/Theming';
import { HeaderDefinition } from '../../../utils/definitions';
import { PluginHeaderDefinition } from '../../../utils/definitions';
import { ChangelogComponent } from '@/components/ChangelogComponent';
<PageTitle
title="Header"
title="PluginHeader"
description="A plugin header with icon, title, custom actions, and navigation tabs."
/>
@@ -36,22 +36,22 @@ import { ChangelogComponent } from '@/components/ChangelogComponent';
## Examples
### Simple header
### Simple plugin header
<Snippet py={4} preview={<WithAllOptions />} code={simple} open />
### Header with tabs
### Plugin header with tabs
Tabs use React Router and highlight automatically based on the current route.
<Snippet preview={<WithAllOptionsAndTabs />} code={withTabs} open />
### Header with HeaderPage
### Plugin header with HeaderPage
Combine with [HeaderPage](/components/header-page) for multi-level navigation.
<Snippet preview={<WithHeaderPage />} code={withHeaderPage} open />
<Theming definition={HeaderDefinition} />
<Theming definition={PluginHeaderDefinition} />
<ChangelogComponent component="header" />
<ChangelogComponent component="plugin-header" />
@@ -1,8 +1,8 @@
export const usage = `import { Header } from '@backstage/ui';
export const usage = `import { PluginHeader } from '@backstage/ui';
<Header title="My plugin" />`;
<PluginHeader title="My plugin" />`;
export const defaultSnippet = `<Header
export const defaultSnippet = `<PluginHeader
title="My plugin"
titleLink="/"
tabs={[
@@ -19,7 +19,7 @@ export const defaultSnippet = `<Header
}
/>`;
export const simple = `<Header
export const simple = `<PluginHeader
title="My plugin"
titleLink="/"
customActions={
@@ -31,7 +31,7 @@ export const simple = `<Header
}
/>`;
export const withTabs = `<Header
export const withTabs = `<PluginHeader
title="My plugin"
titleLink="/"
tabs={[
@@ -43,7 +43,7 @@ export const withTabs = `<Header
]}
/>`;
export const withHeaderPage = `<Header
export const withHeaderPage = `<PluginHeader
title="My plugin"
titleLink="/"
tabs={[
+10 -3
View File
@@ -109,7 +109,14 @@ After:
commitSha: 'a67670d',
},
{
components: ['menu', 'switch', 'skeleton', 'header', 'header-page', 'tabs'],
components: [
'menu',
'switch',
'skeleton',
'plugin-header',
'header-page',
'tabs',
],
version: '0.9.0',
prs: ['31496'],
description: `**BREAKING**: Changed className prop behavior to augment default styles instead of being ignored or overriding them.
@@ -171,10 +178,10 @@ import { Disclosure, Button, DisclosurePanel } from 'react-aria-components';
commitSha: '816af0f',
},
{
components: ['header'],
components: ['plugin-header'],
version: '0.9.0',
prs: ['31525'],
description: `Fix broken external links in Backstage UI Header component.`,
description: `Fix broken external links in Backstage UI PluginHeader component.`,
commitSha: 'd01de00',
},
+2 -2
View File
@@ -58,8 +58,8 @@ export const components: Page[] = [
slug: 'grid',
},
{
title: 'Header',
slug: 'header',
title: 'PluginHeader',
slug: 'plugin-header',
},
{
title: 'HeaderPage',
+1 -1
View File
@@ -14,7 +14,7 @@ export type Component =
| 'dialog'
| 'flex'
| 'grid'
| 'header'
| 'plugin-header'
| 'header-page'
| 'heading'
| 'icon'
+35 -35
View File
@@ -1153,23 +1153,6 @@ export interface GridProps extends SpaceProps {
style?: React.CSSProperties;
}
// @public
export const Header: (props: HeaderProps) => JSX_2.Element;
// @public
export const HeaderDefinition: {
readonly classNames: {
readonly root: 'bui-Header';
readonly toolbar: 'bui-HeaderToolbar';
readonly toolbarWrapper: 'bui-HeaderToolbarWrapper';
readonly toolbarContent: 'bui-HeaderToolbarContent';
readonly toolbarControls: 'bui-HeaderToolbarControls';
readonly toolbarIcon: 'bui-HeaderToolbarIcon';
readonly toolbarName: 'bui-HeaderToolbarName';
readonly tabsWrapper: 'bui-HeaderTabsWrapper';
};
};
// @public
export const HeaderPage: (props: HeaderPageProps) => JSX_2.Element;
@@ -1206,24 +1189,6 @@ export interface HeaderPageProps {
title?: string;
}
// @public
export interface HeaderProps {
// (undocumented)
className?: string;
// (undocumented)
customActions?: React.ReactNode;
// (undocumented)
icon?: React.ReactNode;
// (undocumented)
onTabSelectionChange?: TabsProps_2['onSelectionChange'];
// (undocumented)
tabs?: HeaderTab[];
// (undocumented)
title?: string;
// (undocumented)
titleLink?: string;
}
// @public
export interface HeaderTab {
// (undocumented)
@@ -1574,6 +1539,41 @@ export interface PasswordFieldProps
size?: 'small' | 'medium' | Partial<Record<Breakpoint, 'small' | 'medium'>>;
}
// @public
export const PluginHeader: (props: PluginHeaderProps) => JSX_2.Element;
// @public
export const PluginHeaderDefinition: {
readonly classNames: {
readonly root: 'bui-PluginHeader';
readonly toolbar: 'bui-PluginHeaderToolbar';
readonly toolbarWrapper: 'bui-PluginHeaderToolbarWrapper';
readonly toolbarContent: 'bui-PluginHeaderToolbarContent';
readonly toolbarControls: 'bui-PluginHeaderToolbarControls';
readonly toolbarIcon: 'bui-PluginHeaderToolbarIcon';
readonly toolbarName: 'bui-PluginHeaderToolbarName';
readonly tabsWrapper: 'bui-PluginHeaderTabsWrapper';
};
};
// @public
export interface PluginHeaderProps {
// (undocumented)
className?: string;
// (undocumented)
customActions?: React.ReactNode;
// (undocumented)
icon?: React.ReactNode;
// (undocumented)
onTabSelectionChange?: TabsProps_2['onSelectionChange'];
// (undocumented)
tabs?: HeaderTab[];
// (undocumented)
title?: string;
// (undocumented)
titleLink?: string;
}
// @public
export const Popover: ForwardRefExoticComponent<
PopoverProps & RefAttributes<HTMLDivElement>
@@ -17,10 +17,10 @@
import preview from '../../../../../.storybook/preview';
import type { StoryFn } from '@storybook/react-vite';
import { FullPage } from './FullPage';
import { Header } from '../Header';
import { PluginHeader } from '../PluginHeader';
import { Container } from '../Container';
import { Text } from '../Text';
import type { HeaderTab } from '../Header/types';
import type { HeaderTab } from '../PluginHeader/types';
import { MemoryRouter } from 'react-router-dom';
const meta = preview.meta({
@@ -57,7 +57,7 @@ export const Default = meta.story({
decorators: [withRouter],
render: () => (
<>
<Header title="My Plugin" />
<PluginHeader title="My Plugin" />
<FullPage style={{ backgroundColor: '#c3f0ff' }}>
<Container>
<Text as="p">
@@ -73,7 +73,7 @@ export const WithScrollableContent = meta.story({
decorators: [withRouter],
render: () => (
<>
<Header title="My Plugin" />
<PluginHeader title="My Plugin" />
<FullPage>
<Container>
<Text as="h2" variant="title-medium">
@@ -94,7 +94,7 @@ export const WithTabs = meta.story({
decorators: [withRouter],
render: () => (
<>
<Header title="My Plugin" tabs={tabs} />
<PluginHeader title="My Plugin" tabs={tabs} />
<FullPage>
<Container>
<Text as="p">
@@ -17,7 +17,7 @@
import preview from '../../../../../.storybook/preview';
import type { StoryFn } from '@storybook/react-vite';
import { HeaderPage } from './HeaderPage';
import type { HeaderTab } from '../Header/types';
import type { HeaderTab } from '../PluginHeader/types';
import { MemoryRouter } from 'react-router-dom';
import {
Button,
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import type { HeaderTab } from '../Header/types';
import type { HeaderTab } from '../PluginHeader/types';
/**
* Props for the main HeaderPage component.
@@ -17,11 +17,11 @@
@layer tokens, base, components, utilities;
@layer components {
.bui-Header {
.bui-PluginHeader {
display: block;
}
.bui-HeaderToolbar {
.bui-PluginHeaderToolbar {
&::before {
content: '';
position: absolute;
@@ -34,7 +34,7 @@
}
}
.bui-HeaderToolbarWrapper {
.bui-PluginHeaderToolbarWrapper {
position: relative;
z-index: 1;
display: flex;
@@ -48,14 +48,14 @@
height: 52px;
}
.bui-HeaderToolbarContent {
.bui-PluginHeaderToolbarContent {
display: flex;
flex-direction: row;
align-items: center;
gap: var(--bui-space-2);
}
.bui-HeaderToolbarName {
.bui-PluginHeaderToolbarName {
display: flex;
flex-direction: row;
align-items: center;
@@ -65,7 +65,7 @@
flex-shrink: 0;
}
.bui-HeaderToolbarIcon {
.bui-PluginHeaderToolbarIcon {
width: 16px;
height: 16px;
color: var(--bui-fg-primary);
@@ -76,7 +76,7 @@
}
}
.bui-HeaderToolbarControls {
.bui-PluginHeaderToolbarControls {
position: absolute;
right: var(--bui-space-5);
top: 50%;
@@ -87,7 +87,7 @@
gap: var(--bui-space-2);
}
.bui-HeaderTabsWrapper {
.bui-PluginHeaderTabsWrapper {
padding-inline: var(--bui-space-3);
border-bottom: 1px solid var(--bui-border-1);
background-color: var(--bui-bg-neutral-1);
@@ -16,7 +16,7 @@
import preview from '../../../../../.storybook/preview';
import type { StoryFn } from '@storybook/react-vite';
import { Header } from './Header';
import { PluginHeader } from './PluginHeader';
import type { HeaderTab } from './types';
import {
Button,
@@ -38,8 +38,8 @@ import {
import { HeaderPageBreadcrumb } from '../HeaderPage/types';
const meta = preview.meta({
title: 'Backstage UI/Header',
component: Header,
title: 'Backstage UI/PluginHeader',
component: PluginHeader,
parameters: {
layout: 'fullscreen',
},
@@ -213,7 +213,7 @@ export const WithCustomActions = meta.story({
args: {},
decorators: [withRouter],
render: args => (
<Header
<PluginHeader
{...args}
customActions={
<>
@@ -253,7 +253,7 @@ export const WithHeaderPage = meta.story({
decorators: [withRouter],
render: args => (
<>
<Header
<PluginHeader
{...args}
customActions={
<>
@@ -277,7 +277,7 @@ export const WithLayout = meta.story({
decorators: layoutDecorator,
render: args => (
<>
<Header {...args} tabs={tabs} />
<PluginHeader {...args} tabs={tabs} />
<HeaderPage
title="Page title"
tabs={tabs2}
@@ -292,7 +292,7 @@ export const WithLayoutNoTabs = meta.story({
decorators: layoutDecorator,
render: args => (
<>
<Header {...args} />
<PluginHeader {...args} />
<HeaderPage title="Page title" tabs={tabs2} />
</>
),
@@ -306,7 +306,7 @@ export const WithEverything = meta.story({
decorators: layoutDecorator,
render: args => (
<>
<Header
<PluginHeader
{...args}
customActions={
<>
@@ -336,7 +336,7 @@ export const WithMockedURLCampaigns = meta.story({
},
render: args => (
<MemoryRouter initialEntries={['/campaigns']}>
<Header {...args} />
<PluginHeader {...args} />
<Container>
<Text as="p">
Current URL is mocked to be: <strong>/campaigns</strong>
@@ -356,7 +356,7 @@ export const WithMockedURLIntegrations = meta.story({
},
render: args => (
<MemoryRouter initialEntries={['/integrations']}>
<Header {...args} />
<PluginHeader {...args} />
<Container>
<Text as="p">
Current URL is mocked to be: <strong>/integrations</strong>
@@ -376,7 +376,7 @@ export const WithMockedURLNoMatch = meta.story({
},
render: args => (
<MemoryRouter initialEntries={['/some-other-page']}>
<Header {...args} />
<PluginHeader {...args} />
<Container>
<Text as="p">
Current URL is mocked to be: <strong>/some-other-page</strong>
@@ -424,7 +424,7 @@ export const WithTabsMatchingStrategies = meta.story({
},
render: args => (
<MemoryRouter initialEntries={['/mentorship/events']}>
<Header {...args} />
<PluginHeader {...args} />
<Container>
<Text>
<strong>Current URL:</strong> /mentorship/events
@@ -477,7 +477,7 @@ export const WithTabsExactMatching = meta.story({
},
render: args => (
<MemoryRouter initialEntries={['/mentorship/events']}>
<Header {...args} />
<PluginHeader {...args} />
<Container>
<Text>
<strong>Current URL:</strong> /mentorship/events
@@ -519,7 +519,7 @@ export const WithTabsPrefixMatchingDeep = meta.story({
},
render: args => (
<MemoryRouter initialEntries={['/catalog/users/john/details']}>
<Header {...args} />
<PluginHeader {...args} />
<Container>
<Text as="p">
<strong>Current URL:</strong> /catalog/users/john/details
@@ -14,15 +14,15 @@
* limitations under the License.
*/
import type { HeaderProps } from './types';
import { HeaderToolbar } from './HeaderToolbar';
import type { PluginHeaderProps } from './types';
import { PluginHeaderToolbar } from './PluginHeaderToolbar';
import { Tabs, TabList, Tab } from '../Tabs';
import { useStyles } from '../../hooks/useStyles';
import { HeaderDefinition } from './definition';
import { PluginHeaderDefinition } from './definition';
import { type NavigateOptions } from 'react-router-dom';
import { useRef } from 'react';
import { useIsomorphicLayoutEffect } from '../../hooks/useIsomorphicLayoutEffect';
import styles from './Header.module.css';
import styles from './PluginHeader.module.css';
import clsx from 'clsx';
declare module 'react-aria-components' {
@@ -32,12 +32,13 @@ declare module 'react-aria-components' {
}
/**
* A component that renders a toolbar.
* A component that renders a plugin header with icon, title, custom actions,
* and navigation tabs.
*
* @public
*/
export const Header = (props: HeaderProps) => {
const { classNames, cleanedProps } = useStyles(HeaderDefinition, props);
export const PluginHeader = (props: PluginHeaderProps) => {
const { classNames, cleanedProps } = useStyles(PluginHeaderDefinition, props);
const {
className,
tabs,
@@ -88,7 +89,7 @@ export const Header = (props: HeaderProps) => {
ref={headerRef}
className={clsx(classNames.root, styles[classNames.root], className)}
>
<HeaderToolbar
<PluginHeaderToolbar
icon={icon}
title={title}
titleLink={titleLink}
@@ -16,21 +16,21 @@
import { Link } from 'react-aria-components';
import { useStyles } from '../../hooks/useStyles';
import { HeaderDefinition } from './definition';
import { PluginHeaderDefinition } from './definition';
import { useRef } from 'react';
import { RiShapesLine } from '@remixicon/react';
import type { HeaderToolbarProps } from './types';
import type { PluginHeaderToolbarProps } from './types';
import { Text } from '../Text';
import styles from './Header.module.css';
import styles from './PluginHeader.module.css';
import clsx from 'clsx';
/**
* A component that renders a toolbar.
* A component that renders the toolbar section of a plugin header.
*
* @internal
*/
export const HeaderToolbar = (props: HeaderToolbarProps) => {
const { classNames, cleanedProps } = useStyles(HeaderDefinition, props);
export const PluginHeaderToolbar = (props: PluginHeaderToolbarProps) => {
const { classNames, cleanedProps } = useStyles(PluginHeaderDefinition, props);
const { className, icon, title, titleLink, customActions, hasTabs } =
cleanedProps;
@@ -17,18 +17,18 @@
import type { ComponentDefinition } from '../../types';
/**
* Component definition for Header
* Component definition for PluginHeader
* @public
*/
export const HeaderDefinition = {
export const PluginHeaderDefinition = {
classNames: {
root: 'bui-Header',
toolbar: 'bui-HeaderToolbar',
toolbarWrapper: 'bui-HeaderToolbarWrapper',
toolbarContent: 'bui-HeaderToolbarContent',
toolbarControls: 'bui-HeaderToolbarControls',
toolbarIcon: 'bui-HeaderToolbarIcon',
toolbarName: 'bui-HeaderToolbarName',
tabsWrapper: 'bui-HeaderTabsWrapper',
root: 'bui-PluginHeader',
toolbar: 'bui-PluginHeaderToolbar',
toolbarWrapper: 'bui-PluginHeaderToolbarWrapper',
toolbarContent: 'bui-PluginHeaderToolbarContent',
toolbarControls: 'bui-PluginHeaderToolbarControls',
toolbarIcon: 'bui-PluginHeaderToolbarIcon',
toolbarName: 'bui-PluginHeaderToolbarName',
tabsWrapper: 'bui-PluginHeaderTabsWrapper',
},
} as const satisfies ComponentDefinition;
@@ -14,6 +14,6 @@
* limitations under the License.
*/
export { Header } from './Header';
export { HeaderDefinition } from './definition';
export type { HeaderProps, HeaderTab } from './types';
export { PluginHeader } from './PluginHeader';
export { PluginHeaderDefinition } from './definition';
export type { PluginHeaderProps, HeaderTab } from './types';
@@ -18,11 +18,11 @@ import { TabsProps } from 'react-aria-components';
import { TabMatchStrategy } from '../Tabs';
/**
* Props for the main Header component.
* Props for the {@link PluginHeader} component.
*
* @public
*/
export interface HeaderProps {
export interface PluginHeaderProps {
icon?: React.ReactNode;
title?: string;
titleLink?: string;
@@ -50,15 +50,15 @@ export interface HeaderTab {
}
/**
* Props for the HeaderToolbar component.
* Props for the PluginHeaderToolbar component.
*
* @internal
*/
export interface HeaderToolbarProps {
icon?: HeaderProps['icon'];
title?: HeaderProps['title'];
titleLink?: HeaderProps['titleLink'];
customActions?: HeaderProps['customActions'];
export interface PluginHeaderToolbarProps {
icon?: PluginHeaderProps['icon'];
title?: PluginHeaderProps['title'];
titleLink?: PluginHeaderProps['titleLink'];
customActions?: PluginHeaderProps['customActions'];
hasTabs?: boolean;
className?: string;
}
@@ -23,7 +23,7 @@ import { FieldLabel } from '../FieldLabel';
import { ButtonIcon } from '../ButtonIcon';
import { RiCactusLine, RiEBike2Line } from '@remixicon/react';
import { Button } from '../Button';
import { Header } from '../Header';
import { PluginHeader } from '../PluginHeader';
import { MemoryRouter } from 'react-router-dom';
const meta = preview.meta({
@@ -192,7 +192,7 @@ export const InHeader = meta.story({
],
render: args => (
<>
<Header
<PluginHeader
title="Title"
customActions={
<>
@@ -229,7 +229,7 @@ export const StartCollapsedInHeader = meta.story({
],
render: args => (
<>
<Header
<PluginHeader
title="Title"
customActions={
<>
+1 -1
View File
@@ -42,7 +42,7 @@ export {
GridDefinition,
GridItemDefinition,
} from './components/Grid/definition';
export { HeaderDefinition } from './components/Header/definition';
export { PluginHeaderDefinition } from './components/PluginHeader/definition';
export { HeaderPageDefinition } from './components/HeaderPage/definition';
export { LinkDefinition } from './components/Link/definition';
export { MenuDefinition } from './components/Menu/definition';
+1 -1
View File
@@ -35,7 +35,7 @@ export * from './components/Button';
export * from './components/Card';
export * from './components/Dialog';
export * from './components/FieldLabel';
export * from './components/Header';
export * from './components/PluginHeader';
export * from './components/HeaderPage';
export * from './components/ButtonIcon';
export * from './components/ButtonLink';