Add custom actions to the header

Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
Charles de Dreuille
2025-07-04 13:51:45 +01:00
parent 55b00aaf43
commit 07142f1341
9 changed files with 34 additions and 29 deletions
@@ -119,11 +119,6 @@
padding-inline: var(--canon-space-3);
}
.canon-MenuTrigger {
all: unset;
display: inline-flex;
}
.canon-MenuPopup {
box-sizing: border-box;
max-width: 21.25rem;
+1 -1
View File
@@ -9601,7 +9601,7 @@
}
}
.canon-HeaderToolbarOptions {
.canon-HeaderToolbarControls {
right: var(--canon-space-2);
position: absolute;
top: 50%;
+4 -2
View File
@@ -513,7 +513,7 @@ export const componentDefinitions: {
readonly toolbar: 'canon-HeaderToolbar';
readonly toolbarWrapper: 'canon-HeaderToolbarWrapper';
readonly toolbarContent: 'canon-HeaderToolbarContent';
readonly toolbarOptions: 'canon-HeaderToolbarOptions';
readonly toolbarControls: 'canon-HeaderToolbarControls';
readonly toolbarIcon: 'canon-HeaderToolbarIcon';
readonly toolbarName: 'canon-HeaderToolbarName';
readonly breadcrumbs: 'canon-HeaderBreadcrumbs';
@@ -995,9 +995,11 @@ export interface HeaderProps {
// (undocumented)
breadcrumbs?: HeaderBreadcrumb[];
// (undocumented)
customActions?: React.ReactNode;
// (undocumented)
icon?: React.ReactNode;
// (undocumented)
options?: HeaderOption[];
menuItems?: HeaderOption[];
// (undocumented)
tabs?: HeaderTab[];
// (undocumented)
@@ -17,6 +17,7 @@
import type { Meta, StoryObj, StoryFn } from '@storybook/react';
import { Header } from './Header';
import { HeaderBreadcrumb, HeaderOption, HeaderTab } from './types';
import { Button } from '../Button';
const meta = {
title: 'Components/Header',
@@ -59,7 +60,7 @@ const breadcrumbs: HeaderBreadcrumb[] = [
},
];
const options: HeaderOption[] = [
const menuItems: HeaderOption[] = [
{
label: 'Settings',
value: 'settings',
@@ -124,7 +125,13 @@ export const WithTabs: Story = {
export const WithOptions: Story = {
args: {
options,
menuItems,
},
};
export const WithCustomActions: Story = {
args: {
customActions: <Button>Custom action</Button>,
},
};
@@ -136,7 +143,7 @@ export const WithBreadcrumbs: Story = {
export const WithAllComponents: Story = {
args: {
options,
menuItems,
tabs,
breadcrumbs,
},
@@ -144,7 +151,7 @@ export const WithAllComponents: Story = {
export const WithLayout: Story = {
args: {
options,
menuItems,
tabs,
breadcrumbs,
},
@@ -75,7 +75,7 @@
}
}
.canon-HeaderToolbarOptions {
.canon-HeaderToolbarControls {
position: absolute;
right: var(--canon-space-2);
top: 50%;
@@ -24,15 +24,16 @@ import { HeaderTabs } from './HeaderTabs';
* @public
*/
export const Header = (props: HeaderProps) => {
const { tabs, icon, title, options, breadcrumbs } = props;
const { tabs, icon, title, menuItems, breadcrumbs, customActions } = props;
return (
<>
<HeaderToolbar
icon={icon}
title={title}
options={options}
menuItems={menuItems}
breadcrumbs={breadcrumbs}
customActions={customActions}
/>
<HeaderTabs tabs={tabs} />
</>
@@ -17,7 +17,7 @@
import { Link } from 'react-aria-components';
import { useStyles } from '../../hooks/useStyles';
import { useRef, useState, useEffect } from 'react';
import { RiArrowRightSLine, RiMore2Line, RiShapesLine } from '@remixicon/react';
import { RiArrowRightSLine, RiMore2Line } from '@remixicon/react';
import type { HeaderProps } from './types';
import { ButtonIcon } from '../ButtonIcon';
import { Menu } from '../Menu';
@@ -29,7 +29,7 @@ import { motion, useScroll, useTransform } from 'motion/react';
* @internal
*/
export const HeaderToolbar = (props: HeaderProps) => {
const { icon, title, options, breadcrumbs } = props;
const { icon, title, menuItems, breadcrumbs, customActions } = props;
const { classNames } = useStyles('Header');
const { scrollY } = useScroll();
@@ -38,7 +38,7 @@ export const HeaderToolbar = (props: HeaderProps) => {
// Refs for collision detection
const toolbarWrapperRef = useRef<HTMLDivElement>(null);
const toolbarContentRef = useRef<HTMLDivElement>(null);
const toolbarOptionsRef = useRef<HTMLDivElement>(null);
const toolbarControlsRef = useRef<HTMLDivElement>(null);
// State for breadcrumb visibility
const [showBreadcrumbs, setShowBreadcrumbs] = useState(true);
@@ -51,7 +51,7 @@ export const HeaderToolbar = (props: HeaderProps) => {
const resizeObserver = new ResizeObserver(() => {
const wrapper = toolbarWrapperRef.current;
const content = toolbarContentRef.current;
const options = toolbarOptionsRef.current;
const options = toolbarControlsRef.current;
if (!wrapper || !content) return;
@@ -91,9 +91,7 @@ export const HeaderToolbar = (props: HeaderProps) => {
<div className={classNames.toolbarWrapper} ref={toolbarWrapperRef}>
<div className={classNames.toolbarContent} ref={toolbarContentRef}>
<div className={classNames.toolbarName}>
<div className={classNames.toolbarIcon}>
{icon || <RiShapesLine />}
</div>
{icon && <div className={classNames.toolbarIcon}>{icon}</div>}
{title || 'Your plugin'}
</div>
{breadcrumbs && (
@@ -128,23 +126,24 @@ export const HeaderToolbar = (props: HeaderProps) => {
</motion.div>
)}
</div>
<div className={classNames.toolbarOptions} ref={toolbarOptionsRef}>
{options && (
<div className={classNames.toolbarControls} ref={toolbarControlsRef}>
{customActions}
{menuItems && (
<Menu.Root>
<Menu.Trigger
render={props => (
<ButtonIcon
{...props}
size="small"
icon={<RiMore2Line />}
variant="secondary"
{...props}
/>
)}
/>
<Menu.Portal>
<Menu.Positioner sideOffset={4} align="end">
<Menu.Popup>
{options.map(option => (
{menuItems.map(option => (
<Menu.Item
key={option.value}
onClick={() => option.onClick?.()}
@@ -24,9 +24,10 @@ import { MutableRefObject } from 'react';
export interface HeaderProps {
icon?: React.ReactNode;
title?: string;
tabs?: HeaderTab[];
options?: HeaderOption[];
breadcrumbs?: HeaderBreadcrumb[];
customActions?: React.ReactNode;
menuItems?: HeaderOption[];
tabs?: HeaderTab[];
}
/**
@@ -240,7 +240,7 @@ export const componentDefinitions = {
toolbar: 'canon-HeaderToolbar',
toolbarWrapper: 'canon-HeaderToolbarWrapper',
toolbarContent: 'canon-HeaderToolbarContent',
toolbarOptions: 'canon-HeaderToolbarOptions',
toolbarControls: 'canon-HeaderToolbarControls',
toolbarIcon: 'canon-HeaderToolbarIcon',
toolbarName: 'canon-HeaderToolbarName',
breadcrumbs: 'canon-HeaderBreadcrumbs',