Move the HeaderPage into its own component
Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
@@ -20,6 +20,7 @@ import { HTMLAttributes } from 'react';
|
||||
import { JSX as JSX_2 } from 'react/jsx-runtime';
|
||||
import { LinkProps as LinkProps_2 } from 'react-aria-components';
|
||||
import { Menu as Menu_2 } from '@base-ui-components/react/menu';
|
||||
import { MutableRefObject } from 'react';
|
||||
import type { RadioGroupProps as RadioGroupProps_2 } from 'react-aria-components';
|
||||
import type { RadioProps as RadioProps_2 } from 'react-aria-components';
|
||||
import { ReactElement } from 'react';
|
||||
@@ -936,6 +937,109 @@ export interface GridProps extends SpaceProps {
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
// @public
|
||||
export const Header: (props: HeaderProps) => JSX_2.Element;
|
||||
|
||||
// @public
|
||||
export interface HeaderBreadcrumb {
|
||||
// (undocumented)
|
||||
href: string;
|
||||
// (undocumented)
|
||||
label: string;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface HeaderIndicatorsProps {
|
||||
// (undocumented)
|
||||
hoveredKey: string | null;
|
||||
// (undocumented)
|
||||
prevHoveredKey: MutableRefObject<string | null>;
|
||||
// (undocumented)
|
||||
tabRefs: MutableRefObject<Map<string, HTMLDivElement>>;
|
||||
// (undocumented)
|
||||
tabsRef: MutableRefObject<HTMLDivElement | null>;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface HeaderOption {
|
||||
// (undocumented)
|
||||
label: string;
|
||||
// (undocumented)
|
||||
onClick?: () => void;
|
||||
// (undocumented)
|
||||
value: string;
|
||||
}
|
||||
|
||||
// @public
|
||||
export const HeaderPage: (props: HeaderPageProps) => JSX_2.Element;
|
||||
|
||||
// @public
|
||||
export interface HeaderPageOption {
|
||||
// (undocumented)
|
||||
label: string;
|
||||
// (undocumented)
|
||||
onClick?: () => void;
|
||||
// (undocumented)
|
||||
value: string;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface HeaderPageProps {
|
||||
// (undocumented)
|
||||
description?: string;
|
||||
// (undocumented)
|
||||
name?: string;
|
||||
// (undocumented)
|
||||
options?: HeaderPageOption[];
|
||||
// (undocumented)
|
||||
tabs?: HeaderPageTab[];
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface HeaderPageTab {
|
||||
// (undocumented)
|
||||
href?: string;
|
||||
// (undocumented)
|
||||
label: string;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface HeaderProps {
|
||||
// (undocumented)
|
||||
breadcrumbs?: HeaderBreadcrumb[];
|
||||
// (undocumented)
|
||||
icon?: React.ReactNode;
|
||||
// (undocumented)
|
||||
name?: string;
|
||||
// (undocumented)
|
||||
options?: HeaderOption[];
|
||||
// (undocumented)
|
||||
tabs?: HeaderTab[];
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface HeaderTab {
|
||||
// (undocumented)
|
||||
href?: string;
|
||||
// (undocumented)
|
||||
label: string;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface HeaderTabProps {
|
||||
// (undocumented)
|
||||
setHoveredKey: (key: string | null) => void;
|
||||
// (undocumented)
|
||||
setTabRef: (key: string, element: HTMLDivElement | null) => void;
|
||||
// (undocumented)
|
||||
tab: HeaderTab;
|
||||
}
|
||||
|
||||
// @public
|
||||
export const HeaderTabsIndicators: (
|
||||
props: HeaderIndicatorsProps,
|
||||
) => JSX_2.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const Heading: <T extends ElementType = 'h1'>(
|
||||
props: HeadingProps<T> & {
|
||||
|
||||
@@ -16,12 +16,7 @@
|
||||
|
||||
import type { Meta, StoryObj, StoryFn } from '@storybook/react';
|
||||
import { Header } from './Header';
|
||||
import {
|
||||
HeaderBreadcrumb,
|
||||
HeaderOption,
|
||||
HeaderProps,
|
||||
HeaderTab,
|
||||
} from './types';
|
||||
import { HeaderBreadcrumb, HeaderOption, HeaderTab } from './types';
|
||||
|
||||
const meta = {
|
||||
title: 'Components/Header',
|
||||
@@ -75,13 +70,6 @@ const options: HeaderOption[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const subNavigation: HeaderProps['subNavigation'] = {
|
||||
name: 'Sub Navigation',
|
||||
description: 'Sub Navigation Description',
|
||||
tabs: tabs,
|
||||
options: options,
|
||||
};
|
||||
|
||||
// Extract layout decorator as a reusable constant
|
||||
const layoutDecorator = [
|
||||
(Story: StoryFn) => (
|
||||
@@ -146,19 +134,11 @@ export const WithBreadcrumbs: Story = {
|
||||
},
|
||||
};
|
||||
|
||||
export const WithTabsAndSubNavigation: Story = {
|
||||
args: {
|
||||
tabs,
|
||||
subNavigation,
|
||||
},
|
||||
};
|
||||
|
||||
export const WithAllComponents: Story = {
|
||||
args: {
|
||||
options,
|
||||
tabs,
|
||||
breadcrumbs,
|
||||
subNavigation,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -173,16 +153,3 @@ export const WithLayout: Story = {
|
||||
layout: 'fullscreen',
|
||||
},
|
||||
};
|
||||
|
||||
export const WithSubNavigation: Story = {
|
||||
args: {
|
||||
options,
|
||||
tabs,
|
||||
breadcrumbs,
|
||||
subNavigation,
|
||||
},
|
||||
decorators: layoutDecorator,
|
||||
parameters: {
|
||||
layout: 'fullscreen',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
import type { HeaderProps } from './types';
|
||||
import { HeaderToolbar } from './HeaderToolbar';
|
||||
import { HeaderTabs } from './HeaderTabs';
|
||||
import { HeaderSubNav } from './HeaderSubNav';
|
||||
|
||||
/**
|
||||
* A component that renders a toolbar.
|
||||
@@ -25,7 +24,7 @@ import { HeaderSubNav } from './HeaderSubNav';
|
||||
* @public
|
||||
*/
|
||||
export const Header = (props: HeaderProps) => {
|
||||
const { tabs, icon, name, options, breadcrumbs, subNavigation } = props;
|
||||
const { tabs, icon, name, options, breadcrumbs } = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -36,7 +35,6 @@ export const Header = (props: HeaderProps) => {
|
||||
breadcrumbs={breadcrumbs}
|
||||
/>
|
||||
<HeaderTabs tabs={tabs} />
|
||||
<HeaderSubNav subNavigation={subNavigation} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -27,12 +27,6 @@ export interface HeaderProps {
|
||||
tabs?: HeaderTab[];
|
||||
options?: HeaderOption[];
|
||||
breadcrumbs?: HeaderBreadcrumb[];
|
||||
subNavigation?: {
|
||||
name?: string;
|
||||
description?: string;
|
||||
tabs?: HeaderTab[];
|
||||
options?: HeaderOption[];
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright 2024 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 type { Meta, StoryObj, StoryFn } from '@storybook/react';
|
||||
import { HeaderPage } from './HeaderPage';
|
||||
import { HeaderPageOption, HeaderPageTab } from './types';
|
||||
|
||||
const meta = {
|
||||
title: 'Components/HeaderPage',
|
||||
component: HeaderPage,
|
||||
} satisfies Meta<typeof HeaderPage>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
const tabs: HeaderPageTab[] = [
|
||||
{
|
||||
label: 'Overview',
|
||||
},
|
||||
{
|
||||
label: 'Checks',
|
||||
},
|
||||
{
|
||||
label: 'Tracks',
|
||||
},
|
||||
{
|
||||
label: 'Campaigns',
|
||||
},
|
||||
{
|
||||
label: 'Integrations',
|
||||
},
|
||||
];
|
||||
|
||||
const options: HeaderPageOption[] = [
|
||||
{
|
||||
label: 'Settings',
|
||||
value: 'settings',
|
||||
},
|
||||
{
|
||||
label: 'Invite new members',
|
||||
value: 'invite-new-members',
|
||||
},
|
||||
];
|
||||
|
||||
// Extract layout decorator as a reusable constant
|
||||
const layoutDecorator = [
|
||||
(Story: StoryFn) => (
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
width: '250px',
|
||||
position: 'fixed',
|
||||
left: 8,
|
||||
top: 8,
|
||||
bottom: 8,
|
||||
backgroundColor: 'var(--canon-bg-surface-1',
|
||||
borderRadius: 'var(--canon-radius-2)',
|
||||
border: '1px solid var(--canon-border)',
|
||||
zIndex: 1,
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
paddingInline: '266px',
|
||||
minHeight: '200vh',
|
||||
}}
|
||||
>
|
||||
<Story />
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
width: '250px',
|
||||
position: 'fixed',
|
||||
right: 8,
|
||||
top: 8,
|
||||
bottom: 8,
|
||||
backgroundColor: 'var(--canon-bg-surface-1',
|
||||
borderRadius: 'var(--canon-radius-2)',
|
||||
border: '1px solid var(--canon-border)',
|
||||
zIndex: 1,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
];
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
name: 'Header Page',
|
||||
},
|
||||
};
|
||||
|
||||
export const WithDescription: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
description: 'Header Page Description',
|
||||
},
|
||||
};
|
||||
|
||||
export const WithTabs: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
tabs,
|
||||
},
|
||||
};
|
||||
|
||||
export const WithOptions: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
options,
|
||||
},
|
||||
};
|
||||
|
||||
export const WithEverything: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
description: 'Header Page Description',
|
||||
tabs,
|
||||
options,
|
||||
},
|
||||
};
|
||||
|
||||
export const WithLayout: Story = {
|
||||
args: {
|
||||
...WithEverything.args,
|
||||
},
|
||||
decorators: layoutDecorator,
|
||||
parameters: {
|
||||
layout: 'fullscreen',
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
+13
-9
@@ -13,7 +13,8 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import type { HeaderProps } from './types';
|
||||
|
||||
import type { HeaderPageProps } from './types';
|
||||
import { Heading } from '../Heading';
|
||||
import { Text } from '../Text';
|
||||
import { Flex } from '../Flex';
|
||||
@@ -21,19 +22,22 @@ import { Menu } from '../Menu';
|
||||
import { ButtonIcon } from '../ButtonIcon';
|
||||
import { RiMore2Line } from '@remixicon/react';
|
||||
|
||||
export const HeaderSubNav = (props: Pick<HeaderProps, 'subNavigation'>) => {
|
||||
const { subNavigation } = props;
|
||||
|
||||
if (!subNavigation) return null;
|
||||
/**
|
||||
* A component that renders a header page.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const HeaderPage = (props: HeaderPageProps) => {
|
||||
const { name, description, options } = props;
|
||||
|
||||
return (
|
||||
<Flex pl="4" pr="2" mt="6" justify="between">
|
||||
<Flex direction="column" gap="2">
|
||||
<Heading variant="title4">{subNavigation.name}</Heading>
|
||||
<Text>{subNavigation.description}</Text>
|
||||
<Heading variant="title4">{name}</Heading>
|
||||
<Text>{description}</Text>
|
||||
</Flex>
|
||||
<div>
|
||||
{subNavigation.options && (
|
||||
{options && (
|
||||
<Menu.Root>
|
||||
<Menu.Trigger
|
||||
render={props => (
|
||||
@@ -48,7 +52,7 @@ export const HeaderSubNav = (props: Pick<HeaderProps, 'subNavigation'>) => {
|
||||
<Menu.Portal>
|
||||
<Menu.Positioner sideOffset={4} align="end">
|
||||
<Menu.Popup>
|
||||
{subNavigation.options.map(option => (
|
||||
{options.map(option => (
|
||||
<Menu.Item
|
||||
key={option.value}
|
||||
onClick={() => option.onClick?.()}
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2024 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 * from './HeaderPage';
|
||||
export * from './types';
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Props for the main HeaderPage component.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface HeaderPageProps {
|
||||
name?: string;
|
||||
description?: string;
|
||||
tabs?: HeaderPageTab[];
|
||||
options?: HeaderPageOption[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a tab item in the header page.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface HeaderPageTab {
|
||||
label: string;
|
||||
href?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an option item in the header page.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface HeaderPageOption {
|
||||
label: string;
|
||||
value: string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
@@ -28,6 +28,7 @@
|
||||
@import '../components/Flex/styles.css';
|
||||
@import '../components/Grid/styles.css';
|
||||
@import '../components/Header/Header.styles.css';
|
||||
@import '../components/HeaderPage/HeaderPage.styles.css';
|
||||
@import '../components/Heading/styles.css';
|
||||
@import '../components/Icon/styles.css';
|
||||
@import '../components/Link/styles.css';
|
||||
|
||||
@@ -37,6 +37,8 @@ export * from './components/Button';
|
||||
export * from './components/Collapsible';
|
||||
export * from './components/DataTable';
|
||||
export * from './components/FieldLabel';
|
||||
export * from './components/Header';
|
||||
export * from './components/HeaderPage';
|
||||
export * from './components/Icon';
|
||||
export * from './components/ButtonIcon';
|
||||
export * from './components/ButtonLink';
|
||||
|
||||
Reference in New Issue
Block a user