Merge pull request #33504 from backstage/bui-pluginheader
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
---
|
||||
'@backstage/ui': minor
|
||||
---
|
||||
|
||||
**BREAKING**: Removed the `toolbarWrapper` element from `PluginHeader` and dropped `toolbarWrapper` from `PluginHeaderDefinition.classNames`. Toolbar layout styles now live on `toolbar` (`.bui-PluginHeaderToolbar`).
|
||||
|
||||
**Migration:** Update custom CSS that targeted `.bui-PluginHeaderToolbarWrapper` to use `.bui-PluginHeaderToolbar` instead.
|
||||
|
||||
**Affected components:** PluginHeader
|
||||
@@ -183,21 +183,6 @@
|
||||
font-weight: var(--bui-font-weight-regular);
|
||||
}
|
||||
|
||||
.bui-PluginHeaderToolbarWrapper {
|
||||
padding: 0;
|
||||
height: 32px;
|
||||
border: none;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.bui-PluginHeaderTabsWrapper {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
margin-left: -8px;
|
||||
border: none;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.bui-Input {
|
||||
border-radius: var(--bui-radius-3);
|
||||
}
|
||||
@@ -228,14 +213,6 @@
|
||||
--bui-border-success: #53db83;
|
||||
|
||||
--bui-ring: rgba(0, 0, 0, 0.2);
|
||||
|
||||
.bui-HeaderToolbarWrapper {
|
||||
border: 1px solid var(--bui-border-2);
|
||||
}
|
||||
|
||||
.bui-HeaderTabsWrapper {
|
||||
border: 1px solid var(--bui-border-2);
|
||||
}
|
||||
}
|
||||
|
||||
[data-theme-mode='dark'][data-theme-name='spotify'] {
|
||||
@@ -266,3 +243,24 @@
|
||||
|
||||
--bui-ring: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
/*
|
||||
* Plugin header (@backstage/ui) and story shell header — kept at the bottom of
|
||||
* this file for easier scanning alongside other component overrides above.
|
||||
*/
|
||||
[data-theme-name='spotify'] {
|
||||
.bui-PluginHeaderToolbar {
|
||||
padding: 0;
|
||||
height: 32px;
|
||||
border: none;
|
||||
background: none;
|
||||
margin-bottom: var(--bui-space-2);
|
||||
}
|
||||
|
||||
.bui-PluginHeaderTabsWrapper {
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: none;
|
||||
margin-left: -8px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1949,7 +1949,6 @@ 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';
|
||||
|
||||
@@ -95,42 +95,6 @@ const withRouter = (Story: StoryFn) => (
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
// Extract layout decorator as a reusable constant
|
||||
const layoutDecorator = [
|
||||
(Story: StoryFn) => (
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
width: '250px',
|
||||
position: 'fixed',
|
||||
left: 'var(--sb-panel-left)',
|
||||
top: 'var(--sb-panel-top)',
|
||||
bottom: 'var(--sb-panel-bottom)',
|
||||
backgroundColor: 'var(--sb-sidebar-bg)',
|
||||
borderRadius: 'var(--sb-panel-radius)',
|
||||
border: 'var(--sb-sidebar-border)',
|
||||
borderRight: 'var(--sb-sidebar-border-right)',
|
||||
zIndex: 1,
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
paddingLeft: 'var(--sb-content-padding-inline)',
|
||||
minHeight: '200vh',
|
||||
}}
|
||||
>
|
||||
<Story />
|
||||
<Container>
|
||||
<Text>
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam,
|
||||
quos.
|
||||
</Text>
|
||||
</Container>
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
];
|
||||
|
||||
export const Default = meta.story({
|
||||
args: {
|
||||
title: 'Page Title',
|
||||
@@ -208,10 +172,6 @@ export const WithEverything = meta.story({
|
||||
),
|
||||
});
|
||||
|
||||
export const WithLayout = WithEverything.extend({
|
||||
decorators: [...layoutDecorator],
|
||||
});
|
||||
|
||||
export const WithTabsMatchingStrategies = meta.story({
|
||||
args: {
|
||||
title: 'Route Matching Demo',
|
||||
|
||||
@@ -17,15 +17,15 @@
|
||||
@layer tokens, base, components, utilities;
|
||||
|
||||
@layer components {
|
||||
.bui-PluginHeaderToolbarWrapper {
|
||||
.bui-PluginHeaderToolbar {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-inline: var(--bui-space-5);
|
||||
border-bottom: 1px solid var(--bui-border-1);
|
||||
color: var(--bui-fg-primary);
|
||||
height: 52px;
|
||||
border-bottom: 1px solid var(--bui-border-1);
|
||||
}
|
||||
|
||||
.bui-PluginHeaderToolbarContent {
|
||||
@@ -46,13 +46,18 @@
|
||||
}
|
||||
|
||||
.bui-PluginHeaderToolbarIcon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
border-radius: var(--bui-radius-2);
|
||||
color: var(--bui-fg-primary);
|
||||
|
||||
& svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,8 +19,6 @@ import type { StoryFn } from '@storybook/react-vite';
|
||||
import { PluginHeader } from './PluginHeader';
|
||||
import type { HeaderTab } from './types';
|
||||
import {
|
||||
Button,
|
||||
Header,
|
||||
Container,
|
||||
Text,
|
||||
ButtonIcon,
|
||||
@@ -36,7 +34,6 @@ import {
|
||||
RiCloudy2Line,
|
||||
RiMore2Line,
|
||||
} from '@remixicon/react';
|
||||
import { HeaderBreadcrumb } from '../Header/types';
|
||||
|
||||
const meta = preview.meta({
|
||||
title: 'Backstage UI/PluginHeader',
|
||||
@@ -82,24 +79,6 @@ const tabs: HeaderTab[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const tabs2: HeaderTab[] = [
|
||||
{
|
||||
id: 'Banana',
|
||||
label: 'Banana',
|
||||
href: '/banana',
|
||||
},
|
||||
{
|
||||
id: 'Apple',
|
||||
label: 'Apple',
|
||||
href: '/apple',
|
||||
},
|
||||
{
|
||||
id: 'Orange',
|
||||
label: 'Orange',
|
||||
href: '/orange',
|
||||
},
|
||||
];
|
||||
|
||||
const menuItems = [
|
||||
{
|
||||
label: 'Settings',
|
||||
@@ -120,86 +99,6 @@ const menuItems = [
|
||||
},
|
||||
];
|
||||
|
||||
const breadcrumbs: HeaderBreadcrumb[] = [
|
||||
{
|
||||
label: 'Home',
|
||||
href: '/',
|
||||
},
|
||||
{
|
||||
label: 'Dashboard',
|
||||
href: '/dashboard',
|
||||
},
|
||||
{
|
||||
label: 'Settings',
|
||||
href: '/settings',
|
||||
},
|
||||
];
|
||||
|
||||
// Extract layout decorator as a reusable constant
|
||||
const layoutDecorator = [
|
||||
(Story: StoryFn) => (
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
width: '250px',
|
||||
position: 'fixed',
|
||||
left: 'var(--sb-panel-left)',
|
||||
top: 'var(--sb-panel-top)',
|
||||
bottom: 'var(--sb-panel-bottom)',
|
||||
backgroundColor: 'var(--sb-sidebar-bg)',
|
||||
borderRadius: 'var(--sb-panel-radius)',
|
||||
border: 'var(--sb-sidebar-border)',
|
||||
borderRight: 'var(--sb-sidebar-border-right)',
|
||||
zIndex: 1,
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
paddingLeft: 'var(--sb-content-padding-inline)',
|
||||
minHeight: '200vh',
|
||||
}}
|
||||
>
|
||||
<Story />
|
||||
<Container>
|
||||
<Text as="p">
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam,
|
||||
quos.
|
||||
</Text>
|
||||
<Text as="p">
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam,
|
||||
quos.
|
||||
</Text>
|
||||
<Text as="p">
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam,
|
||||
quos.
|
||||
</Text>
|
||||
<Text as="p">
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam,
|
||||
quos.
|
||||
</Text>
|
||||
<Text as="p">
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam,
|
||||
quos.
|
||||
</Text>
|
||||
<Text as="p">
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam,
|
||||
quos.
|
||||
</Text>
|
||||
<Text as="p">
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam,
|
||||
quos.
|
||||
</Text>
|
||||
<Text as="p">
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam,
|
||||
quos.
|
||||
</Text>
|
||||
</Container>
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
withRouter,
|
||||
];
|
||||
|
||||
export const Default = meta.story({
|
||||
args: {},
|
||||
decorators: [withRouter],
|
||||
@@ -220,11 +119,11 @@ export const WithCustomActions = meta.story({
|
||||
{...args}
|
||||
customActions={
|
||||
<>
|
||||
<ButtonIcon variant="tertiary" icon={<RiCloudy2Line />} />
|
||||
<ButtonIcon variant="tertiary" icon={<RiEmotionHappyLine />} />
|
||||
<ButtonIcon variant="tertiary" icon={<RiHeartLine />} />
|
||||
<ButtonIcon variant="secondary" icon={<RiCloudy2Line />} />
|
||||
<ButtonIcon variant="secondary" icon={<RiEmotionHappyLine />} />
|
||||
<ButtonIcon variant="secondary" icon={<RiHeartLine />} />
|
||||
<MenuTrigger>
|
||||
<ButtonIcon variant="tertiary" icon={<RiMore2Line />} />
|
||||
<ButtonIcon variant="secondary" icon={<RiMore2Line />} />
|
||||
<Menu placement="bottom end">
|
||||
{menuItems.map(option => (
|
||||
<MenuItem
|
||||
@@ -249,90 +148,6 @@ export const WithAllOptionsAndTabs = WithCustomActions.extend({
|
||||
},
|
||||
});
|
||||
|
||||
export const WithHeader = meta.story({
|
||||
args: {
|
||||
...WithAllOptionsAndTabs.input.args,
|
||||
},
|
||||
decorators: [withRouter],
|
||||
render: args => (
|
||||
<>
|
||||
<PluginHeader
|
||||
{...args}
|
||||
customActions={
|
||||
<>
|
||||
<ButtonIcon variant="tertiary" icon={<RiCloudy2Line />} />
|
||||
<ButtonIcon variant="tertiary" icon={<RiEmotionHappyLine />} />
|
||||
<ButtonIcon variant="tertiary" icon={<RiHeartLine />} />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Header
|
||||
title="Page title"
|
||||
tabs={tabs2}
|
||||
customActions={<Button>Custom action</Button>}
|
||||
breadcrumbs={breadcrumbs}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
});
|
||||
|
||||
export const WithLayout = meta.story({
|
||||
decorators: layoutDecorator,
|
||||
render: args => (
|
||||
<>
|
||||
<PluginHeader {...args} tabs={tabs} />
|
||||
<Header
|
||||
title="Page title"
|
||||
tabs={tabs2}
|
||||
customActions={<Button>Custom action</Button>}
|
||||
breadcrumbs={breadcrumbs}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
});
|
||||
|
||||
export const WithLayoutNoTabs = meta.story({
|
||||
decorators: layoutDecorator,
|
||||
render: args => (
|
||||
<>
|
||||
<PluginHeader {...args} />
|
||||
<Header title="Page title" tabs={tabs2} />
|
||||
</>
|
||||
),
|
||||
});
|
||||
|
||||
export const WithEverything = meta.story({
|
||||
args: {
|
||||
tabs,
|
||||
titleLink: '/',
|
||||
},
|
||||
decorators: layoutDecorator,
|
||||
render: args => (
|
||||
<>
|
||||
<PluginHeader
|
||||
{...args}
|
||||
customActions={
|
||||
<>
|
||||
<ButtonIcon variant="tertiary" icon={<RiCloudy2Line />} />
|
||||
<ButtonIcon variant="tertiary" icon={<RiEmotionHappyLine />} />
|
||||
<ButtonIcon variant="tertiary" icon={<RiHeartLine />} />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Header
|
||||
title="Page title"
|
||||
tabs={tabs2}
|
||||
customActions={
|
||||
<>
|
||||
<Button variant="secondary">Secondary</Button>
|
||||
<Button variant="primary">Primary</Button>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
});
|
||||
|
||||
export const WithMockedURLCampaigns = meta.story({
|
||||
args: {
|
||||
tabs,
|
||||
@@ -341,7 +156,7 @@ export const WithMockedURLCampaigns = meta.story({
|
||||
<MemoryRouter initialEntries={['/campaigns']}>
|
||||
<BUIProvider>
|
||||
<PluginHeader {...args} />
|
||||
<Container>
|
||||
<Container mt="6">
|
||||
<Text as="p">
|
||||
Current URL is mocked to be: <strong>/campaigns</strong>
|
||||
</Text>
|
||||
@@ -363,7 +178,7 @@ export const WithMockedURLIntegrations = meta.story({
|
||||
<MemoryRouter initialEntries={['/integrations']}>
|
||||
<BUIProvider>
|
||||
<PluginHeader {...args} />
|
||||
<Container>
|
||||
<Container mt="6">
|
||||
<Text as="p">
|
||||
Current URL is mocked to be: <strong>/integrations</strong>
|
||||
</Text>
|
||||
@@ -385,7 +200,7 @@ export const WithMockedURLNoMatch = meta.story({
|
||||
<MemoryRouter initialEntries={['/some-other-page']}>
|
||||
<BUIProvider>
|
||||
<PluginHeader {...args} />
|
||||
<Container>
|
||||
<Container mt="6">
|
||||
<Text as="p">
|
||||
Current URL is mocked to be: <strong>/some-other-page</strong>
|
||||
</Text>
|
||||
@@ -435,7 +250,7 @@ export const WithTabsMatchingStrategies = meta.story({
|
||||
<MemoryRouter initialEntries={['/mentorship/events']}>
|
||||
<BUIProvider>
|
||||
<PluginHeader {...args} />
|
||||
<Container>
|
||||
<Container mt="6">
|
||||
<Text>
|
||||
<strong>Current URL:</strong> /mentorship/events
|
||||
</Text>
|
||||
@@ -490,7 +305,7 @@ export const WithTabsExactMatching = meta.story({
|
||||
<MemoryRouter initialEntries={['/mentorship/events']}>
|
||||
<BUIProvider>
|
||||
<PluginHeader {...args} />
|
||||
<Container>
|
||||
<Container mt="6">
|
||||
<Text>
|
||||
<strong>Current URL:</strong> /mentorship/events
|
||||
</Text>
|
||||
@@ -534,7 +349,7 @@ export const WithTabsPrefixMatchingDeep = meta.story({
|
||||
<MemoryRouter initialEntries={['/catalog/users/john/details']}>
|
||||
<BUIProvider>
|
||||
<PluginHeader {...args} />
|
||||
<Container>
|
||||
<Container mt="6">
|
||||
<Text as="p">
|
||||
<strong>Current URL:</strong> /catalog/users/john/details
|
||||
</Text>
|
||||
|
||||
@@ -22,9 +22,10 @@ import { type NavigateOptions } from 'react-router-dom';
|
||||
import { Children, useMemo, useRef } from 'react';
|
||||
import { useIsomorphicLayoutEffect } from '../../hooks/useIsomorphicLayoutEffect';
|
||||
import { Box } from '../Box';
|
||||
import { Link } from 'react-aria-components';
|
||||
import { Link } from '../Link';
|
||||
import { RiShapesLine } from '@remixicon/react';
|
||||
import { Text } from '../Text';
|
||||
import { BgReset } from '../../hooks/useBg';
|
||||
|
||||
declare module 'react-aria-components' {
|
||||
interface RouterConfig {
|
||||
@@ -33,8 +34,9 @@ declare module 'react-aria-components' {
|
||||
}
|
||||
|
||||
/**
|
||||
* A component that renders a plugin header with icon, title, custom actions,
|
||||
* and navigation tabs.
|
||||
* Renders a plugin header with icon, title, custom actions, and optional tabs.
|
||||
* Always participates in the background context system so descendants (e.g. buttons)
|
||||
* get the correct `data-on-bg` styling inside the toolbar and tabs.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
@@ -52,9 +54,6 @@ export const PluginHeader = (props: PluginHeaderProps) => {
|
||||
|
||||
const hasTabs = tabs && tabs.length > 0;
|
||||
const headerRef = useRef<HTMLElement>(null);
|
||||
const toolbarWrapperRef = useRef<HTMLDivElement>(null);
|
||||
const toolbarContentRef = useRef<HTMLDivElement>(null);
|
||||
const toolbarControlsRef = useRef<HTMLDivElement>(null);
|
||||
const animationFrameRef = useRef<number | undefined>(undefined);
|
||||
const lastAppliedHeightRef = useRef<number | undefined>(undefined);
|
||||
|
||||
@@ -123,53 +122,53 @@ export const PluginHeader = (props: PluginHeaderProps) => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const titleContent = (
|
||||
<>
|
||||
<div className={classes.toolbarIcon} aria-hidden="true">
|
||||
{icon || <RiShapesLine />}
|
||||
</div>
|
||||
<Text variant="body-medium">{title || 'Your plugin'}</Text>
|
||||
</>
|
||||
);
|
||||
const titleText = title || 'Your plugin';
|
||||
|
||||
return (
|
||||
<header ref={headerRef} className={classes.root}>
|
||||
<div className={classes.toolbar} data-has-tabs={hasTabs}>
|
||||
<div className={classes.toolbarWrapper} ref={toolbarWrapperRef}>
|
||||
<div className={classes.toolbarContent} ref={toolbarContentRef}>
|
||||
<Text as="h1" variant="body-medium">
|
||||
<BgReset>
|
||||
<header ref={headerRef} className={classes.root}>
|
||||
<Box bg="neutral" className={classes.toolbar} data-has-tabs={hasTabs}>
|
||||
<div className={classes.toolbarContent}>
|
||||
<Box
|
||||
bg="neutral"
|
||||
className={classes.toolbarIcon}
|
||||
aria-hidden="true"
|
||||
>
|
||||
{icon || <RiShapesLine />}
|
||||
</Box>
|
||||
<h1 className={classes.toolbarName}>
|
||||
{titleLink ? (
|
||||
<Link className={classes.toolbarName} href={titleLink}>
|
||||
{titleContent}
|
||||
<Link href={titleLink} standalone variant="body-medium">
|
||||
{titleText}
|
||||
</Link>
|
||||
) : (
|
||||
<div className={classes.toolbarName}>{titleContent}</div>
|
||||
<Text as="span" variant="body-medium">
|
||||
{titleText}
|
||||
</Text>
|
||||
)}
|
||||
</Text>
|
||||
</h1>
|
||||
</div>
|
||||
<div className={classes.toolbarControls} ref={toolbarControlsRef}>
|
||||
{actionChildren}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{tabs && (
|
||||
<Box bg="neutral" className={classes.tabs}>
|
||||
<Tabs onSelectionChange={onTabSelectionChange}>
|
||||
<TabList>
|
||||
{tabs?.map(tab => (
|
||||
<Tab
|
||||
key={tab.id}
|
||||
id={tab.id}
|
||||
href={tab.href}
|
||||
matchStrategy={tab.matchStrategy}
|
||||
>
|
||||
{tab.label}
|
||||
</Tab>
|
||||
))}
|
||||
</TabList>
|
||||
</Tabs>
|
||||
<div className={classes.toolbarControls}>{actionChildren}</div>
|
||||
</Box>
|
||||
)}
|
||||
</header>
|
||||
{tabs && (
|
||||
<Box bg="neutral" className={classes.tabs}>
|
||||
<Tabs onSelectionChange={onTabSelectionChange}>
|
||||
<TabList>
|
||||
{tabs?.map(tab => (
|
||||
<Tab
|
||||
key={tab.id}
|
||||
id={tab.id}
|
||||
href={tab.href}
|
||||
matchStrategy={tab.matchStrategy}
|
||||
>
|
||||
{tab.label}
|
||||
</Tab>
|
||||
))}
|
||||
</TabList>
|
||||
</Tabs>
|
||||
</Box>
|
||||
)}
|
||||
</header>
|
||||
</BgReset>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -27,7 +27,6 @@ export const PluginHeaderDefinition = defineComponent<PluginHeaderOwnProps>()({
|
||||
classNames: {
|
||||
root: 'bui-PluginHeader',
|
||||
toolbar: 'bui-PluginHeaderToolbar',
|
||||
toolbarWrapper: 'bui-PluginHeaderToolbarWrapper',
|
||||
toolbarContent: 'bui-PluginHeaderToolbarContent',
|
||||
toolbarControls: 'bui-PluginHeaderToolbarControls',
|
||||
toolbarIcon: 'bui-PluginHeaderToolbarIcon',
|
||||
|
||||
@@ -0,0 +1,258 @@
|
||||
/*
|
||||
* 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 preview from '../../../../.storybook/preview';
|
||||
import type { StoryFn } from '@storybook/react-vite';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { BUIProvider } from '../provider';
|
||||
import {
|
||||
PluginHeader,
|
||||
Header,
|
||||
Button,
|
||||
ButtonIcon,
|
||||
Card,
|
||||
Container,
|
||||
Flex,
|
||||
MenuTrigger,
|
||||
Menu,
|
||||
MenuItem,
|
||||
} from '..';
|
||||
import {
|
||||
RiBookOpenLine,
|
||||
RiBox3Line,
|
||||
RiCodeSSlashLine,
|
||||
RiDownloadLine,
|
||||
RiEdit2Line,
|
||||
RiGitBranchLine,
|
||||
RiMore2Line,
|
||||
RiPlayLine,
|
||||
RiRefreshLine,
|
||||
RiSettings4Line,
|
||||
RiShieldCheckLine,
|
||||
RiShareBoxLine,
|
||||
RiTerminalLine,
|
||||
} from '@remixicon/react';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Shared page content placeholder
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const PageContent = () => (
|
||||
<Container mt="6">
|
||||
<Flex direction="row" gap="4">
|
||||
<Card style={{ minHeight: 120, flex: 1 }} />
|
||||
<Card style={{ minHeight: 120, flex: 1 }} />
|
||||
<Card style={{ minHeight: 120, flex: 1 }} />
|
||||
</Flex>
|
||||
</Container>
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Shared layout decorator
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const withLayout = (Story: StoryFn) => (
|
||||
<MemoryRouter>
|
||||
<BUIProvider>
|
||||
<Story />
|
||||
</BUIProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Meta
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const meta = preview.meta({
|
||||
title: 'Recipes/PluginHeader and Header',
|
||||
parameters: {
|
||||
layout: 'fullscreen',
|
||||
},
|
||||
});
|
||||
|
||||
export const NoHeader = meta.story({
|
||||
decorators: [withLayout],
|
||||
render: () => (
|
||||
<>
|
||||
<PluginHeader icon={<RiCodeSSlashLine />} title="APIs" />
|
||||
<PageContent />
|
||||
</>
|
||||
),
|
||||
});
|
||||
|
||||
export const SimpleHeader = meta.story({
|
||||
decorators: [withLayout],
|
||||
render: () => (
|
||||
<>
|
||||
<PluginHeader icon={<RiCodeSSlashLine />} title="APIs" />
|
||||
<Header title="payments-api" />
|
||||
<PageContent />
|
||||
</>
|
||||
),
|
||||
});
|
||||
|
||||
export const WithTabs = meta.story({
|
||||
decorators: [withLayout],
|
||||
render: () => (
|
||||
<>
|
||||
<PluginHeader
|
||||
icon={<RiBox3Line />}
|
||||
title="Catalog"
|
||||
titleLink="/"
|
||||
tabs={[
|
||||
{ id: 'catalog', label: 'Catalog', href: '/catalog' },
|
||||
{ id: 'apis', label: 'APIs', href: '/apis' },
|
||||
{ id: 'resources', label: 'Resources', href: '/resources' },
|
||||
{ id: 'templates', label: 'Templates', href: '/templates' },
|
||||
{ id: 'docs', label: 'Docs', href: '/docs' },
|
||||
]}
|
||||
customActions={
|
||||
<>
|
||||
<ButtonIcon
|
||||
variant="secondary"
|
||||
icon={<RiSettings4Line />}
|
||||
aria-label="Settings"
|
||||
/>
|
||||
<MenuTrigger>
|
||||
<ButtonIcon
|
||||
variant="secondary"
|
||||
icon={<RiMore2Line />}
|
||||
aria-label="More options"
|
||||
/>
|
||||
<Menu placement="bottom end">
|
||||
<MenuItem href="/catalog/import">Import component</MenuItem>
|
||||
<MenuItem href="/catalog/register">Register existing</MenuItem>
|
||||
<MenuItem href="/catalog/docs">View documentation</MenuItem>
|
||||
</Menu>
|
||||
</MenuTrigger>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Header
|
||||
title="payment-service"
|
||||
customActions={
|
||||
<>
|
||||
<Button variant="secondary" iconStart={<RiEdit2Line />}>
|
||||
Edit
|
||||
</Button>
|
||||
<Button variant="primary" iconStart={<RiShareBoxLine />}>
|
||||
Unregister
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<PageContent />
|
||||
</>
|
||||
),
|
||||
});
|
||||
|
||||
export const WithBreadcrumb = meta.story({
|
||||
decorators: [withLayout],
|
||||
render: () => (
|
||||
<>
|
||||
<PluginHeader
|
||||
icon={<RiGitBranchLine />}
|
||||
title="CI/CD"
|
||||
titleLink="/"
|
||||
tabs={[
|
||||
{ id: 'builds', label: 'Builds', href: '/builds' },
|
||||
{ id: 'pipelines', label: 'Pipelines', href: '/pipelines' },
|
||||
{ id: 'deployments', label: 'Deployments', href: '/deployments' },
|
||||
{ id: 'settings', label: 'Settings', href: '/settings' },
|
||||
]}
|
||||
customActions={
|
||||
<>
|
||||
<ButtonIcon
|
||||
variant="secondary"
|
||||
icon={<RiRefreshLine />}
|
||||
aria-label="Refresh"
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Header
|
||||
title="main · #842"
|
||||
breadcrumbs={[
|
||||
{ label: 'Catalog', href: '/catalog' },
|
||||
{ label: 'Services', href: '/catalog?kind=Component' },
|
||||
]}
|
||||
customActions={
|
||||
<>
|
||||
<Button variant="secondary" iconStart={<RiDownloadLine />}>
|
||||
Download logs
|
||||
</Button>
|
||||
<Button variant="primary" iconStart={<RiPlayLine />}>
|
||||
Re-run pipeline
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<PageContent />
|
||||
</>
|
||||
),
|
||||
});
|
||||
|
||||
export const WithSubTabs = meta.story({
|
||||
decorators: [withLayout],
|
||||
render: () => (
|
||||
<>
|
||||
<PluginHeader
|
||||
icon={<RiGitBranchLine />}
|
||||
title="CI/CD"
|
||||
titleLink="/"
|
||||
tabs={[
|
||||
{ id: 'builds', label: 'Builds', href: '/builds' },
|
||||
{ id: 'pipelines', label: 'Pipelines', href: '/pipelines' },
|
||||
{ id: 'deployments', label: 'Deployments', href: '/deployments' },
|
||||
{ id: 'settings', label: 'Settings', href: '/settings' },
|
||||
]}
|
||||
customActions={
|
||||
<>
|
||||
<ButtonIcon
|
||||
variant="secondary"
|
||||
icon={<RiRefreshLine />}
|
||||
aria-label="Refresh"
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Header
|
||||
title="main · #842"
|
||||
tabs={[
|
||||
{ id: 'summary', label: 'Summary', href: '/summary' },
|
||||
{ id: 'steps', label: 'Steps', href: '/steps' },
|
||||
{ id: 'artifacts', label: 'Artifacts', href: '/artifacts' },
|
||||
{ id: 'logs', label: 'Logs', href: '/logs' },
|
||||
]}
|
||||
breadcrumbs={[
|
||||
{ label: 'Catalog', href: '/catalog' },
|
||||
{ label: 'Services', href: '/catalog?kind=Component' },
|
||||
]}
|
||||
customActions={
|
||||
<>
|
||||
<Button variant="secondary" iconStart={<RiDownloadLine />}>
|
||||
Download logs
|
||||
</Button>
|
||||
<Button variant="primary" iconStart={<RiPlayLine />}>
|
||||
Re-run pipeline
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<PageContent />
|
||||
</>
|
||||
),
|
||||
});
|
||||
Reference in New Issue
Block a user