Improve Header and HeaderPage content

Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
Charles de Dreuille
2025-07-22 21:38:23 +01:00
parent aa3133bb3b
commit 96e6e087cd
9 changed files with 334 additions and 71 deletions
@@ -57,16 +57,22 @@
.versionLinks {
display: flex;
align-items: center;
gap: 12px;
}
.versionLinks a {
color: var(--text-secondary);
transition: color 0.2s ease;
}
a {
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
color: var(--secondary);
transition: color 0.2s ease-in-out, background-color 0.2s ease-in-out;
border-radius: 48px;
.versionLinks a:hover {
color: var(--text-primary);
&:hover {
color: var(--primary);
background-color: var(--action);
}
}
}
@media (max-width: 768px) {
@@ -78,7 +78,8 @@
display: flex;
align-items: center;
justify-content: center;
background-color: var(--action);
/* background-color: var(--action); */
border: 1px solid var(--border2);
border-radius: 48px;
padding-inline: 20px;
color: var(--primary);
@@ -0,0 +1,40 @@
import { PropsTable } from '@/components/PropsTable';
import { CodeBlock } from '@/components/CodeBlock';
import { Snippet } from '@/components/Snippet';
import { HeaderPageSnippet } from '@/snippets/stories-snippets';
import {
propDefs,
usage,
simple,
defaultSnippet,
withTabs,
withBreadcrumbs,
withHeaderPage,
} from './header-page.props';
import { PageTitle } from '@/components/PageTitle';
import { Theming } from '@/components/Theming';
import { ChangelogComponent } from '@/components/ChangelogComponent';
<PageTitle
type="Component"
title="HeaderPage"
description="A header page component for the plugin that should sit under the Header component."
/>
<Snippet
py={4}
preview={<HeaderPageSnippet story="WithEverything" />}
code={defaultSnippet}
/>
## Usage
<CodeBlock code={usage} />
## API reference
<PropsTable data={propDefs} />
<Theming component="Header" />
<ChangelogComponent component="Header" />
@@ -0,0 +1,91 @@
import {
childrenPropDefs,
classNamePropDefs,
stylePropDefs,
type PropDef,
} from '@/utils/propDefs';
export const propDefs: Record<string, PropDef> = {
title: {
type: 'string',
default: 'Your plugin',
},
customActions: {
type: 'enum',
values: ['ReactNode'],
},
menuItems: {
type: 'complex',
complexType: {
name: 'MenuItem[]',
properties: {
label: {
type: 'string',
required: true,
description: 'Display text for the menu item',
},
value: {
type: 'string',
required: true,
description: 'Unique value for the menu item',
},
onClick: {
type: '() => void',
required: false,
description: 'Callback function when menu item is clicked',
},
},
},
},
tabs: {
type: 'complex',
complexType: {
name: 'HeaderTab[]',
properties: {
id: {
type: 'string',
required: true,
description: 'Unique identifier for the tab',
},
label: {
type: 'string',
required: true,
description: 'Display text for the tab',
},
href: {
type: 'string',
required: false,
description: 'URL to navigate to when tab is clicked',
},
matchStrategy: {
type: "'exact' | 'prefix'",
required: false,
description: 'How to match the current route to highlight the tab',
},
},
},
},
...childrenPropDefs,
...classNamePropDefs,
...stylePropDefs,
};
export const usage = `import { HeaderPage } from '@backstage/ui';
<HeaderPage />`;
export const defaultSnippet = `<HeaderPage
title="My plugin"
tabs={[
{ id: 'overview', label: 'Overview' },
{ id: 'checks', label: 'Checks' },
{ id: 'tracks', label: 'Tracks' },
{ id: 'campaigns', label: 'Campaigns' },
{ id: 'integrations', label: 'Integrations' },
]}
menuItems={[
{ label: 'Settings', value: 'settings' },
{ label: 'Invite new members', value: 'invite-new-members' },
]}
customActions={<Button>Custom action</Button>}
/>`;
+39 -23
View File
@@ -4,16 +4,13 @@ import { Snippet } from '@/components/Snippet';
import { HeaderSnippet } from '@/snippets/stories-snippets';
import {
propDefs,
flexUsageSnippet,
flexDefaultSnippet,
flexFAQ1Snippet,
flexSimpleSnippet,
flexResponsiveSnippet,
flexAlignSnippet,
usage,
simple,
defaultSnippet,
withTabs,
withBreadcrumbs,
withHeaderPage,
} from './header.props';
import { spacingPropDefs } from '@/utils/propDefs';
import { PageTitle } from '@/components/PageTitle';
import { Theming } from '@/components/Theming';
import { ChangelogComponent } from '@/components/ChangelogComponent';
@@ -26,7 +23,7 @@ import { ChangelogComponent } from '@/components/ChangelogComponent';
<Snippet
py={4}
preview={<HeaderSnippet story="WithAllComponents" />}
preview={<HeaderSnippet story="WithAllOptionsAndTabs" />}
code={defaultSnippet}
/>
@@ -38,31 +35,50 @@ import { ChangelogComponent } from '@/components/ChangelogComponent';
<PropsTable data={propDefs} />
The grid component also accepts all the spacing props from the Box component.
<PropsTable data={spacingPropDefs} />
## Examples
### Simple
### Simple header
A simple example of how to use the Flex component.
A simple example of how to use the Header component.
<CodeBlock code={flexSimpleSnippet} />
<Snippet
py={4}
preview={<HeaderSnippet story="WithAllOptions" />}
code={simple}
open
/>
### Responsive
### Header with tabs
The Flex component also supports responsive values, making it easy to create
responsive designs.
A simple example of how to use the Header component with tabs. All links are using React Router
under the hood and will be active when you are on the corresponding page.
<CodeBlock code={flexResponsiveSnippet} />
<Snippet
### Align
preview={<HeaderSnippet story="WithAllOptionsAndTabs" />}
code={withTabs}
open
/>
The Flex component also supports responsive alignment, making it easy to
create responsive designs.
### Header with breadcrumbs
<CodeBlock code={flexAlignSnippet} />
Breacrumbs should appear when you scroll down (and not directly visible as it is in the demo below).
<Snippet
preview={<HeaderSnippet story="WithBreadcrumbs" />}
code={withBreadcrumbs}
open
/>
### Header with HeaderPage
You can use the `Header` component inside the [HeaderPage](/components/header-page) component to compose your multi-level navigation.
<Snippet
preview={<HeaderSnippet story="WithHeaderPage" />}
code={withHeaderPage}
open
/>
<Theming component="Header" />
+86 -15
View File
@@ -91,6 +91,10 @@ export const propDefs: Record<string, PropDef> = {
},
},
},
onTabSelectionChange: {
type: 'enum',
values: ['(key: string) => void'],
},
...childrenPropDefs,
...classNamePropDefs,
...stylePropDefs,
@@ -128,20 +132,87 @@ export const defaultSnippet = `<Header
}
/>`;
export const flexSimpleSnippet = `<Flex gap="md">
<Box>Hello World</Box>
<Box>Hello World</Box>
<Box>Hello World</Box>
</Flex>`;
export const simple = `<Header
title="My plugin"
titleLink="/"
tabs={[
{ id: 'overview', label: 'Overview' },
{ id: 'checks', label: 'Checks' },
{ id: 'tracks', label: 'Tracks' },
{ id: 'campaigns', label: 'Campaigns' },
{ id: 'integrations', label: 'Integrations' },
]}
menuItems={[
{ label: 'Settings', value: 'settings' },
{ label: 'Invite new members', value: 'invite-new-members' },
]}
customActions={
<>
<ButtonIcon variant="tertiary" icon={<RiCloudy2Line />} />
<ButtonIcon variant="tertiary" icon={<RiEmotionHappyLine />} />
<ButtonIcon variant="tertiary" icon={<RiHeartLine />} />
</>
}
/>`;
export const flexResponsiveSnippet = `<Flex gap={{ xs: 'xs', md: 'md' }}>
<Box>Hello World</Box>
<Box>Hello World</Box>
<Box>Hello World</Box>
</Flex>`;
export const withTabs = `
<Header
title="My plugin"
titleLink="/"
tabs={[
{ id: 'overview', label: 'Overview' },
{ id: 'checks', label: 'Checks' },
{ id: 'tracks', label: 'Tracks' },
{ id: 'campaigns', label: 'Campaigns' },
{ id: 'integrations', label: 'Integrations' },
]}
menuItems={[
{ label: 'Settings', value: 'settings' },
{ label: 'Invite new members', value: 'invite-new-members' },
]}
customActions={
<>
<ButtonIcon variant="tertiary" icon={<RiCloudy2Line />} />
<ButtonIcon variant="tertiary" icon={<RiEmotionHappyLine />} />
<ButtonIcon variant="tertiary" icon={<RiHeartLine />} />
</>
}
tabs={[
{ id: 'overview', label: 'Overview' },
{ id: 'checks', label: 'Checks' },
{ id: 'tracks', label: 'Tracks' },
{ id: 'campaigns', label: 'Campaigns' },
{ id: 'integrations', label: 'Integrations' },
]}
/>
`;
export const flexAlignSnippet = `<Flex align={{ xs: 'start', md: 'center' }}>
<Box>Hello World</Box>
<Box>Hello World</Box>
<Box>Hello World</Box>
</Flex>`;
export const withBreadcrumbs = `<Header
title="My plugin"
titleLink="/"
breadcrumbs={[
{ label: 'Home', href: '/' },
{ label: 'Dashboard', href: '/dashboard' },
{ label: 'Settings', href: '/settings' },
]}
tabs={[
{ id: 'overview', label: 'Overview' },
{ id: 'checks', label: 'Checks' },
{ id: 'tracks', label: 'Tracks' },
{ id: 'campaigns', label: 'Campaigns' },
{ id: 'integrations', label: 'Integrations' },
]}
/>`;
export const withHeaderPage = `<Header
title="My plugin"
titleLink="/"
breadcrumbs={...}
tabs={...}
/>
<HeaderPage
title="Page title"
menuItems={...}
tabs={...}
customActions={<Button>Custom action</Button>}
/>`;
+2
View File
@@ -13,6 +13,7 @@
--font-weight-regular: 400;
--font-weight-bold: 600;
--border: #e5e5e5;
--border2: #cdcdcd;
--surface-1: #f4f4f4;
}
@@ -24,6 +25,7 @@
--action: #202020;
--link: #4856e8;
--border: #2a2a2a;
--border2: #2a2a2a;
--surface-1: #282828;
}
+5
View File
@@ -106,6 +106,11 @@ export const components: Page[] = [
slug: 'header',
status: 'alpha',
},
{
title: 'HeaderPage',
slug: 'header-page',
status: 'alpha',
},
{
title: 'Icon',
slug: 'icon',
@@ -169,30 +169,7 @@ export const WithOptions: Story = {
};
export const WithCustomActions: Story = {
args: {
menuItems,
},
decorators: [withRouter],
render: args => (
<Header {...args} customActions={<Button>Custom action</Button>} />
),
};
export const WithBreadcrumbs: Story = {
args: {
breadcrumbs,
},
decorators: [withRouter],
};
export const WithAllComponents: Story = {
args: {
title: 'My plugin',
titleLink: '/',
menuItems,
tabs,
breadcrumbs,
},
args: {},
decorators: [withRouter],
render: args => (
<Header
@@ -208,6 +185,60 @@ export const WithAllComponents: Story = {
),
};
export const WithAllOptions: Story = {
args: {
title: 'My plugin',
titleLink: '/',
menuItems,
},
decorators: [withRouter],
render: WithCustomActions.render,
};
export const WithBreadcrumbs: Story = {
args: {
breadcrumbs,
tabs,
},
decorators: [withRouter],
};
export const WithAllOptionsAndTabs: Story = {
args: {
...WithAllOptions.args,
tabs,
},
decorators: [withRouter],
render: WithAllOptions.render,
};
export const WithHeaderPage: Story = {
args: {
...WithAllOptionsAndTabs.args,
},
decorators: [withRouter],
render: args => (
<>
<Header
{...args}
customActions={
<>
<ButtonIcon variant="tertiary" icon={<RiCloudy2Line />} />
<ButtonIcon variant="tertiary" icon={<RiEmotionHappyLine />} />
<ButtonIcon variant="tertiary" icon={<RiHeartLine />} />
</>
}
/>
<HeaderPage
title="Page title"
menuItems={args.menuItems}
tabs={tabs2}
customActions={<Button>Custom action</Button>}
/>
</>
),
};
export const WithLayout: Story = {
args: {
menuItems,