diff --git a/packages/canon/report.api.md b/packages/canon/report.api.md index ed471bab5a..0d7dec2d83 100644 --- a/packages/canon/report.api.md +++ b/packages/canon/report.api.md @@ -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; + // (undocumented) + tabRefs: MutableRefObject>; + // (undocumented) + tabsRef: MutableRefObject; +} + +// @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: ( props: HeadingProps & { diff --git a/packages/canon/src/components/Header/Header.stories.tsx b/packages/canon/src/components/Header/Header.stories.tsx index 047b7f0c94..0554648bc9 100644 --- a/packages/canon/src/components/Header/Header.stories.tsx +++ b/packages/canon/src/components/Header/Header.stories.tsx @@ -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', - }, -}; diff --git a/packages/canon/src/components/Header/Header.tsx b/packages/canon/src/components/Header/Header.tsx index 195c7a59b1..9ea06ab98d 100644 --- a/packages/canon/src/components/Header/Header.tsx +++ b/packages/canon/src/components/Header/Header.tsx @@ -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} /> - ); }; diff --git a/packages/canon/src/components/Header/types.ts b/packages/canon/src/components/Header/types.ts index b8bcf179c9..b6a8eab779 100644 --- a/packages/canon/src/components/Header/types.ts +++ b/packages/canon/src/components/Header/types.ts @@ -27,12 +27,6 @@ export interface HeaderProps { tabs?: HeaderTab[]; options?: HeaderOption[]; breadcrumbs?: HeaderBreadcrumb[]; - subNavigation?: { - name?: string; - description?: string; - tabs?: HeaderTab[]; - options?: HeaderOption[]; - }; } /** diff --git a/packages/canon/src/components/HeaderPage/HeaderPage.stories.tsx b/packages/canon/src/components/HeaderPage/HeaderPage.stories.tsx new file mode 100644 index 0000000000..501eb715c4 --- /dev/null +++ b/packages/canon/src/components/HeaderPage/HeaderPage.stories.tsx @@ -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; + +export default meta; +type Story = StoryObj; + +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) => ( + <> +
+
+ +
+
+ + ), +]; + +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', + }, +}; diff --git a/packages/canon/src/components/HeaderPage/HeaderPage.styles.css b/packages/canon/src/components/HeaderPage/HeaderPage.styles.css new file mode 100644 index 0000000000..94adcafa53 --- /dev/null +++ b/packages/canon/src/components/HeaderPage/HeaderPage.styles.css @@ -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. + */ diff --git a/packages/canon/src/components/Header/HeaderSubNav.tsx b/packages/canon/src/components/HeaderPage/HeaderPage.tsx similarity index 81% rename from packages/canon/src/components/Header/HeaderSubNav.tsx rename to packages/canon/src/components/HeaderPage/HeaderPage.tsx index 90f2afb73a..987ea14783 100644 --- a/packages/canon/src/components/Header/HeaderSubNav.tsx +++ b/packages/canon/src/components/HeaderPage/HeaderPage.tsx @@ -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) => { - 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 ( - {subNavigation.name} - {subNavigation.description} + {name} + {description}
- {subNavigation.options && ( + {options && ( ( @@ -48,7 +52,7 @@ export const HeaderSubNav = (props: Pick) => { - {subNavigation.options.map(option => ( + {options.map(option => ( option.onClick?.()} diff --git a/packages/canon/src/components/HeaderPage/index.tsx b/packages/canon/src/components/HeaderPage/index.tsx new file mode 100644 index 0000000000..3ce5e08d05 --- /dev/null +++ b/packages/canon/src/components/HeaderPage/index.tsx @@ -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'; diff --git a/packages/canon/src/components/HeaderPage/types.ts b/packages/canon/src/components/HeaderPage/types.ts new file mode 100644 index 0000000000..722f069478 --- /dev/null +++ b/packages/canon/src/components/HeaderPage/types.ts @@ -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; +} diff --git a/packages/canon/src/css/components.css b/packages/canon/src/css/components.css index 5fbacdc6f5..98eb4ff566 100644 --- a/packages/canon/src/css/components.css +++ b/packages/canon/src/css/components.css @@ -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'; diff --git a/packages/canon/src/index.ts b/packages/canon/src/index.ts index 88063eab3c..61fdb5e8cd 100644 --- a/packages/canon/src/index.ts +++ b/packages/canon/src/index.ts @@ -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';