Fix links on the header toolbar

Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
Charles de Dreuille
2025-07-22 08:09:12 +01:00
parent 8c39e322fd
commit 68d2df9084
5 changed files with 109 additions and 74 deletions
+2
View File
@@ -1045,6 +1045,8 @@ export interface HeaderProps {
tabs?: HeaderTab[];
// (undocumented)
title?: string;
// (undocumented)
titleLink?: string;
}
// @public
@@ -230,6 +230,7 @@ export const WithEverything: Story = {
menuItems,
breadcrumbs,
tabs,
titleLink: '/',
},
decorators: layoutDecorator,
render: args => (
+12 -2
View File
@@ -32,7 +32,16 @@ declare module 'react-aria-components' {
* @public
*/
export const Header = (props: HeaderProps) => {
const { tabs, icon, title, menuItems, breadcrumbs, customActions } = props;
const {
tabs,
icon,
title,
titleLink,
menuItems,
breadcrumbs,
customActions,
onTabSelectionChange,
} = props;
const { classNames } = useStyles('Header');
@@ -43,6 +52,7 @@ export const Header = (props: HeaderProps) => {
<HeaderToolbar
icon={icon}
title={title}
titleLink={titleLink}
menuItems={menuItems}
breadcrumbs={breadcrumbs}
customActions={customActions}
@@ -50,7 +60,7 @@ export const Header = (props: HeaderProps) => {
/>
{tabs && (
<div className={classNames.tabsWrapper}>
<Tabs onSelectionChange={props.onTabSelectionChange}>
<Tabs onSelectionChange={onTabSelectionChange}>
<TabList>
{tabs?.map(tab => (
<Tab key={tab.id} id={tab.id} href={tab.href}>
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { Link } from 'react-aria-components';
import { Link, RouterProvider } from 'react-aria-components';
import { useStyles } from '../../hooks/useStyles';
import { useRef, useState, useEffect } from 'react';
import { RiArrowRightSLine, RiMore2Line, RiShapesLine } from '@remixicon/react';
@@ -23,6 +23,7 @@ import { ButtonIcon } from '../ButtonIcon';
import { Menu } from '../Menu';
import { Text } from '../Text';
import { motion, useScroll, useTransform } from 'motion/react';
import { useNavigate, useHref } from 'react-router-dom';
/**
* A component that renders a toolbar.
@@ -30,9 +31,17 @@ import { motion, useScroll, useTransform } from 'motion/react';
* @internal
*/
export const HeaderToolbar = (props: HeaderToolbarProps) => {
const { icon, title, menuItems, breadcrumbs, customActions, hasTabs } = props;
const {
icon,
title,
titleLink,
menuItems,
breadcrumbs,
customActions,
hasTabs,
} = props;
const { classNames } = useStyles('Header');
let navigate = useNavigate();
const { scrollY } = useScroll();
const breadcrumbOpacity = useTransform(scrollY, [80, 120], [0, 1]);
@@ -87,80 +96,91 @@ export const HeaderToolbar = (props: HeaderToolbarProps) => {
};
}, []);
const titleContent = (
<>
<div className={classNames.toolbarIcon}>{icon || <RiShapesLine />}</div>
<Text variant="body">{title || 'Your plugin'}</Text>
</>
);
return (
<div className={classNames.toolbar} data-has-tabs={hasTabs}>
<div className={classNames.toolbarWrapper} ref={toolbarWrapperRef}>
<div className={classNames.toolbarContent} ref={toolbarContentRef}>
<div className={classNames.toolbarName}>
<div className={classNames.toolbarIcon}>
{icon || <RiShapesLine />}
</div>
<Text variant="body">{title || 'Your plugin'}</Text>
<RouterProvider navigate={navigate} useHref={useHref}>
{' '}
<div className={classNames.toolbar} data-has-tabs={hasTabs}>
<div className={classNames.toolbarWrapper} ref={toolbarWrapperRef}>
<div className={classNames.toolbarContent} ref={toolbarContentRef}>
{titleLink ? (
<Link className={classNames.toolbarName} href={titleLink}>
{titleContent}
</Link>
) : (
<div className={classNames.toolbarName}>{titleContent}</div>
)}
{breadcrumbs && (
<motion.div
className={classNames.breadcrumbs}
style={{
opacity: breadcrumbOpacity,
visibility: showBreadcrumbs ? 'visible' : 'hidden',
}}
>
<RiArrowRightSLine
size={16}
className={classNames.breadcrumbSeparator}
/>
{breadcrumbs.map((breadcrumb, index) => (
<div key={breadcrumb.label} className={classNames.breadcrumb}>
<Link
href={breadcrumb.href}
className={classNames.breadcrumbLink}
data-active={index === breadcrumbs.length - 1}
>
{breadcrumb.label}
</Link>
{index < breadcrumbs.length - 1 && (
<RiArrowRightSLine
size={16}
className={classNames.breadcrumbSeparator}
/>
)}
</div>
))}
</motion.div>
)}
</div>
{breadcrumbs && (
<motion.div
className={classNames.breadcrumbs}
style={{
opacity: breadcrumbOpacity,
visibility: showBreadcrumbs ? 'visible' : 'hidden',
}}
>
<RiArrowRightSLine
size={16}
className={classNames.breadcrumbSeparator}
/>
{breadcrumbs.map((breadcrumb, index) => (
<div key={breadcrumb.label} className={classNames.breadcrumb}>
<Link
href={breadcrumb.href}
className={classNames.breadcrumbLink}
data-active={index === breadcrumbs.length - 1}
>
{breadcrumb.label}
</Link>
{index < breadcrumbs.length - 1 && (
<RiArrowRightSLine
size={16}
className={classNames.breadcrumbSeparator}
<div className={classNames.toolbarControls} ref={toolbarControlsRef}>
{customActions}
{menuItems && (
<Menu.Root>
<Menu.Trigger
render={props => (
<ButtonIcon
size="small"
icon={<RiMore2Line />}
variant="tertiary"
{...props}
/>
)}
</div>
))}
</motion.div>
)}
</div>
<div className={classNames.toolbarControls} ref={toolbarControlsRef}>
{customActions}
{menuItems && (
<Menu.Root>
<Menu.Trigger
render={props => (
<ButtonIcon
size="small"
icon={<RiMore2Line />}
variant="tertiary"
{...props}
/>
)}
/>
<Menu.Portal>
<Menu.Positioner sideOffset={4} align="end">
<Menu.Popup>
{menuItems.map(option => (
<Menu.Item
key={option.value}
onClick={() => option.onClick?.()}
>
{option.label}
</Menu.Item>
))}
</Menu.Popup>
</Menu.Positioner>
</Menu.Portal>
</Menu.Root>
)}
/>
<Menu.Portal>
<Menu.Positioner sideOffset={4} align="end">
<Menu.Popup>
{menuItems.map(option => (
<Menu.Item
key={option.value}
onClick={() => option.onClick?.()}
>
{option.label}
</Menu.Item>
))}
</Menu.Popup>
</Menu.Positioner>
</Menu.Portal>
</Menu.Root>
)}
</div>
</div>
</div>
</div>
</RouterProvider>
);
};
@@ -24,6 +24,7 @@ import { TabsProps } from 'react-aria-components';
export interface HeaderProps {
icon?: React.ReactNode;
title?: string;
titleLink?: string;
breadcrumbs?: HeaderBreadcrumb[];
customActions?: React.ReactNode;
menuItems?: HeaderMenuItem[];
@@ -71,6 +72,7 @@ export interface HeaderBreadcrumb {
export interface HeaderToolbarProps {
icon?: HeaderProps['icon'];
title?: HeaderProps['title'];
titleLink?: HeaderProps['titleLink'];
breadcrumbs?: HeaderProps['breadcrumbs'];
customActions?: HeaderProps['customActions'];
menuItems?: HeaderProps['menuItems'];