Toolbar -> Header

Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
Charles de Dreuille
2025-06-30 21:06:45 +01:00
parent f4109f84ad
commit ebd42e6222
9 changed files with 264 additions and 220 deletions
+170 -130
View File
@@ -9540,6 +9540,176 @@
display: grid;
}
.canon-HeaderToolbar {
z-index: 10;
padding-top: 8px;
position: sticky;
top: 0;
&:before {
content: "";
background-color: var(--canon-bg);
z-index: 0;
height: 16px;
position: absolute;
top: 0;
left: 0;
right: 0;
}
}
.canon-HeaderToolbarWrapper {
z-index: 1;
background-color: var(--canon-bg-surface-1);
padding-inline: var(--canon-space-3);
border-radius: var(--canon-radius-2);
border: 1px solid var(--canon-border);
color: var(--canon-fg-primary);
flex-direction: row;
justify-content: space-between;
align-items: center;
height: 52px;
display: flex;
position: relative;
}
.canon-HeaderToolbarContent {
align-items: center;
gap: var(--canon-space-2);
flex-direction: row;
display: flex;
}
.canon-HeaderToolbarName {
align-items: center;
gap: var(--canon-space-2);
font-size: var(--canon-font-size-3);
font-weight: var(--canon-font-weight-regular);
flex-direction: row;
flex-shrink: 0;
display: flex;
}
.canon-HeaderToolbarIcon {
width: 16px;
height: 16px;
color: var(--canon-fg-primary);
& svg {
width: 100%;
height: 100%;
}
}
.canon-HeaderToolbarOptions {
right: var(--canon-space-2);
position: absolute;
top: 50%;
transform: translateY(-50%);
}
.canon-HeaderBreadcrumbs {
align-items: center;
gap: var(--canon-space-2);
font-size: var(--canon-font-size-3);
font-weight: var(--canon-font-weight-regular);
flex-direction: row;
display: flex;
}
.canon-HeaderBreadcrumb {
align-items: center;
gap: var(--canon-space-2);
flex-direction: row;
display: flex;
}
.canon-HeaderBreadcrumbLink {
color: var(--canon-fg-secondary);
cursor: pointer;
text-decoration: none;
&[data-active="true"] {
color: var(--canon-fg-primary);
}
}
.canon-HeaderBreadcrumbSeparator {
width: 16px;
height: 16px;
color: var(--canon-fg-secondary);
}
.canon-HeaderTabs {
background-color: var(--canon-bg-surface-1);
padding-inline: var(--canon-space-1);
border-radius: var(--canon-radius-2);
border: 1px solid var(--canon-border);
--active-tab-left: 0px;
--active-tab-right: 0px;
--active-tab-top: 0px;
--active-tab-bottom: 0px;
--active-tab-width: 0px;
--active-tab-height: 0px;
--hovered-tab-left: 0px;
--hovered-tab-right: 0px;
--hovered-tab-top: 0px;
--hovered-tab-bottom: 0px;
--hovered-tab-width: 0px;
--hovered-tab-height: 0px;
--hovered-tab-opacity: 0;
--hovered-transition-duration: 0s;
flex-direction: row;
align-items: center;
margin-top: 8px;
display: flex;
position: relative;
}
.canon-HeaderTabList {
flex-direction: row;
display: flex;
}
.canon-HeaderTab {
font-size: var(--canon-font-size-3);
color: var(--canon-fg-primary);
cursor: pointer;
z-index: 2;
height: 36px;
padding-inline: var(--canon-space-2);
justify-content: center;
align-items: center;
display: flex;
position: relative;
}
.canon-HeaderTabActive {
content: "";
left: calc(var(--active-tab-left) + var(--canon-space-2));
width: calc(var(--active-tab-width) - var(--canon-space-4));
background-color: var(--canon-fg-primary);
opacity: 1;
border-radius: 4px;
height: 1px;
transition: all .25s ease-out;
position: absolute;
bottom: -1px;
}
.canon-HeaderTabHovered {
content: "";
left: var(--hovered-tab-left);
top: calc(var(--hovered-tab-top) + 4px);
width: var(--hovered-tab-width);
height: calc(var(--hovered-tab-height) - 8px);
background-color: var(--canon-gray-2);
opacity: var(--hovered-tab-opacity);
transition: left var(--hovered-transition-duration) ease-out, top var(--hovered-transition-duration) ease-out, width var(--hovered-transition-duration) ease-out, height var(--hovered-transition-duration) ease-out, opacity .15s ease-out;
border-radius: 4px;
position: absolute;
}
.canon-Heading {
font-family: var(--canon-font-regular);
color: var(--canon-fg-primary);
@@ -10265,136 +10435,6 @@
height: 1rem;
}
.canon-Toolbar {
z-index: 10;
padding-top: 8px;
position: sticky;
top: 0;
&:before {
content: "";
background-color: var(--canon-bg);
z-index: 0;
height: 16px;
position: absolute;
top: 0;
left: 0;
right: 0;
}
}
.canon-ToolbarContentWrapper {
z-index: 1;
background-color: var(--canon-bg-surface-1);
padding-inline: var(--canon-space-3);
border-radius: var(--canon-radius-2);
border: 1px solid var(--canon-border);
color: var(--canon-fg-primary);
flex-direction: row;
justify-content: space-between;
align-items: center;
height: 52px;
display: flex;
position: relative;
}
.canon-ToolbarContent {
align-items: center;
gap: var(--canon-space-2);
flex-direction: row;
display: flex;
}
.canon-ToolbarName {
align-items: center;
gap: var(--canon-space-2);
font-size: var(--canon-font-size-3);
font-weight: var(--canon-font-weight-regular);
flex-direction: row;
display: flex;
}
.canon-ToolbarIcon {
width: 16px;
height: 16px;
color: var(--canon-fg-primary);
& svg {
width: 100%;
height: 100%;
}
}
.canon-ToolbarTabs {
background-color: var(--canon-bg-surface-1);
padding-inline: var(--canon-space-1);
border-radius: var(--canon-radius-2);
border: 1px solid var(--canon-border);
--active-tab-left: 0px;
--active-tab-right: 0px;
--active-tab-top: 0px;
--active-tab-bottom: 0px;
--active-tab-width: 0px;
--active-tab-height: 0px;
--hovered-tab-left: 0px;
--hovered-tab-right: 0px;
--hovered-tab-top: 0px;
--hovered-tab-bottom: 0px;
--hovered-tab-width: 0px;
--hovered-tab-height: 0px;
--hovered-tab-opacity: 0;
--hovered-transition-duration: 0s;
flex-direction: row;
align-items: center;
margin-top: 8px;
display: flex;
position: relative;
}
.canon-ToolbarTabList {
flex-direction: row;
display: flex;
}
.canon-ToolbarTab {
font-size: var(--canon-font-size-3);
color: var(--canon-fg-primary);
cursor: pointer;
z-index: 2;
height: 36px;
padding-inline: var(--canon-space-2);
justify-content: center;
align-items: center;
display: flex;
position: relative;
}
.canon-ToolbarActiveIndicator {
content: "";
left: calc(var(--active-tab-left) + var(--canon-space-2));
width: calc(var(--active-tab-width) - var(--canon-space-4));
background-color: var(--canon-fg-primary);
opacity: 1;
border-radius: 4px;
height: 1px;
transition: all .25s ease-out;
position: absolute;
bottom: -1px;
}
.canon-ToolbarHoveredIndicator {
content: "";
left: var(--hovered-tab-left);
top: calc(var(--hovered-tab-top) + 4px);
width: var(--hovered-tab-width);
height: calc(var(--hovered-tab-height) - 8px);
background-color: var(--canon-gray-2);
opacity: var(--hovered-tab-opacity);
transition: left var(--hovered-transition-duration) ease-out, top var(--hovered-transition-duration) ease-out, width var(--hovered-transition-duration) ease-out, height var(--hovered-transition-duration) ease-out, opacity .15s ease-out;
border-radius: 4px;
position: absolute;
}
.canon-TooltipPopup {
box-sizing: border-box;
transform-origin: var(--transform-origin);
@@ -15,18 +15,18 @@
*/
import type { Meta, StoryObj, StoryFn } from '@storybook/react';
import { Toolbar } from './Toolbar';
import { ToolbarBreadcrumb, ToolbarOption, ToolbarTab } from './types';
import { Header } from './Header';
import { HeaderBreadcrumb, HeaderOption, HeaderTab } from './types';
const meta = {
title: 'Components/Toolbar',
component: Toolbar,
} satisfies Meta<typeof Toolbar>;
title: 'Components/Header',
component: Header,
} satisfies Meta<typeof Header>;
export default meta;
type Story = StoryObj<typeof meta>;
const tabs: ToolbarTab[] = [
const tabs: HeaderTab[] = [
{
label: 'Overview',
},
@@ -44,7 +44,7 @@ const tabs: ToolbarTab[] = [
},
];
const breadcrumbs: ToolbarBreadcrumb[] = [
const breadcrumbs: HeaderBreadcrumb[] = [
{
label: 'Home',
href: '/',
@@ -59,7 +59,7 @@ const breadcrumbs: ToolbarBreadcrumb[] = [
},
];
const options: ToolbarOption[] = [
const options: HeaderOption[] = [
{
label: 'Settings',
value: 'settings',
@@ -14,7 +14,7 @@
* limitations under the License.
*/
.canon-Toolbar {
.canon-HeaderToolbar {
position: sticky;
top: 0;
z-index: 10;
@@ -32,7 +32,7 @@
}
}
.canon-ToolbarContentWrapper {
.canon-HeaderToolbarWrapper {
position: relative;
z-index: 1;
display: flex;
@@ -47,14 +47,14 @@
height: 52px;
}
.canon-ToolbarContent {
.canon-HeaderToolbarContent {
display: flex;
flex-direction: row;
align-items: center;
gap: var(--canon-space-2);
}
.canon-ToolbarName {
.canon-HeaderToolbarName {
display: flex;
flex-direction: row;
align-items: center;
@@ -64,7 +64,7 @@
flex-shrink: 0;
}
.canon-ToolbarIcon {
.canon-HeaderToolbarIcon {
width: 16px;
height: 16px;
color: var(--canon-fg-primary);
@@ -75,7 +75,14 @@
}
}
.canon-ToolbarBreadcrumbs {
.canon-HeaderToolbarOptions {
position: absolute;
right: var(--canon-space-2);
top: 50%;
transform: translateY(-50%);
}
.canon-HeaderBreadcrumbs {
display: flex;
flex-direction: row;
align-items: center;
@@ -84,14 +91,14 @@
font-weight: var(--canon-font-weight-regular);
}
.canon-ToolbarBreadcrumb {
.canon-HeaderBreadcrumb {
display: flex;
flex-direction: row;
align-items: center;
gap: var(--canon-space-2);
}
.canon-ToolbarBreadcrumbLink {
.canon-HeaderBreadcrumbLink {
color: var(--canon-fg-secondary);
text-decoration: none;
cursor: pointer;
@@ -101,20 +108,13 @@
}
}
.canon-ToolbarBreadcrumbSeparator {
.canon-HeaderBreadcrumbSeparator {
width: 16px;
height: 16px;
color: var(--canon-fg-secondary);
}
.canon-ToolbarOptions {
position: absolute;
right: var(--canon-space-2);
top: 50%;
transform: translateY(-50%);
}
.canon-ToolbarTabs {
.canon-HeaderTabs {
display: flex;
flex-direction: row;
align-items: center;
@@ -143,12 +143,12 @@
--hovered-transition-duration: 0s;
}
.canon-ToolbarTabList {
.canon-HeaderTabList {
display: flex;
flex-direction: row;
}
.canon-ToolbarTab {
.canon-HeaderTab {
font-size: var(--canon-font-size-3);
color: var(--canon-fg-primary);
height: 36px;
@@ -162,7 +162,7 @@
}
/* Active tab indicator */
.canon-ToolbarActiveIndicator {
.canon-HeaderTabActive {
content: '';
position: absolute;
left: calc(var(--active-tab-left) + var(--canon-space-2));
@@ -176,7 +176,7 @@
}
/* Hovered tab indicator */
.canon-ToolbarHoveredIndicator {
.canon-HeaderTabHovered {
content: '';
position: absolute;
left: var(--hovered-tab-left);
@@ -17,9 +17,9 @@
import { Tabs, TabList, Tab as AriaTab, Link } from 'react-aria-components';
import { useStyles } from '../../hooks/useStyles';
import { useId, useRef, useState, useEffect } from 'react';
import { ToolbarTabsIndicators } from './Indicators';
import { HeaderTabsIndicators } from './Indicators';
import { RiArrowRightSLine, RiMore2Line, RiShapesLine } from '@remixicon/react';
import type { ToolbarProps, ToolbarTabProps } from './types';
import type { HeaderProps, HeaderTabProps } from './types';
import { ButtonIcon } from '../ButtonIcon';
import { Menu } from '../Menu';
import { motion, useScroll, useTransform } from 'framer-motion';
@@ -29,9 +29,9 @@ import { motion, useScroll, useTransform } from 'framer-motion';
*
* @public
*/
export const Toolbar = (props: ToolbarProps) => {
export const Header = (props: HeaderProps) => {
const { tabs, icon, name, options, breadcrumbs } = props;
const { classNames } = useStyles('Toolbar');
const { classNames } = useStyles('Header');
const tabsRef = useRef<HTMLDivElement>(null);
const tabRefs = useRef<Map<string, HTMLDivElement>>(new Map());
const [hoveredKey, setHoveredKey] = useState<string | null>(null);
@@ -101,13 +101,12 @@ export const Toolbar = (props: ToolbarProps) => {
return (
<>
<div className={classNames.toolbar}>
<div
className={classNames.toolbarContentWrapper}
ref={toolbarWrapperRef}
>
<div className={classNames.toolbarWrapper} ref={toolbarWrapperRef}>
<div className={classNames.toolbarContent} ref={toolbarContentRef}>
<div className={classNames.name}>
<div className={classNames.icon}>{icon || <RiShapesLine />}</div>
<div className={classNames.toolbarName}>
<div className={classNames.toolbarIcon}>
{icon || <RiShapesLine />}
</div>
{name || 'Your plugin'}
</div>
{breadcrumbs && (
@@ -192,7 +191,7 @@ export const Toolbar = (props: ToolbarProps) => {
);
})}
</TabList>
<ToolbarTabsIndicators
<HeaderTabsIndicators
tabRefs={tabRefs}
tabsRef={tabsRef}
hoveredKey={hoveredKey}
@@ -209,10 +208,10 @@ export const Toolbar = (props: ToolbarProps) => {
*
* @public
*/
const Tab = (props: ToolbarTabProps) => {
const Tab = (props: HeaderTabProps) => {
const { tab, setTabRef, setHoveredKey } = props;
const id = useId();
const { classNames } = useStyles('Toolbar');
const { classNames } = useStyles('Header');
return (
<AriaTab
@@ -17,16 +17,16 @@
import { TabListStateContext } from 'react-aria-components';
import { useStyles } from '../../hooks/useStyles';
import { useContext, useEffect, useCallback } from 'react';
import type { ToolbarIndicatorsProps } from './types';
import type { HeaderIndicatorsProps } from './types';
/**
* A component that renders the indicators for the toolbar.
*
* @public
*/
export const ToolbarTabsIndicators = (props: ToolbarIndicatorsProps) => {
export const HeaderTabsIndicators = (props: HeaderIndicatorsProps) => {
const { tabRefs, tabsRef, hoveredKey, prevHoveredKey } = props;
const { classNames } = useStyles('Toolbar');
const { classNames } = useStyles('Header');
const state = useContext(TabListStateContext);
const updateCSSVariables = useCallback(() => {
@@ -154,8 +154,8 @@ export const ToolbarTabsIndicators = (props: ToolbarIndicatorsProps) => {
return (
<>
<div className={classNames.activeIndicator} />
<div className={classNames.hoveredIndicator} />
<div className={classNames.tabActive} />
<div className={classNames.tabHovered} />
</>
);
};
@@ -14,6 +14,6 @@
* limitations under the License.
*/
export * from './Toolbar';
export * from './Header';
export * from './Indicators';
export * from './types';
@@ -17,66 +17,72 @@
import { MutableRefObject } from 'react';
/**
* Represents a tab item in the toolbar navigation.
* Props for the main Header component.
*
* @public
*/
export interface ToolbarTab {
export interface HeaderProps {
icon?: React.ReactNode;
name?: string;
tabs?: HeaderTab[];
options?: HeaderOption[];
breadcrumbs?: HeaderBreadcrumb[];
subNavigation?: {
name?: string;
description?: string;
tabs?: HeaderTab[];
options?: HeaderOption[];
};
}
/**
* Represents a tab item in the header navigation.
*
* @public
*/
export interface HeaderTab {
label: string;
href?: string;
}
/**
* Represents an option item in the toolbar dropdown menu.
* Represents an option item in the header dropdown menu.
*
* @public
*/
export interface ToolbarOption {
export interface HeaderOption {
label: string;
value: string;
onClick?: () => void;
}
/**
* Represents a breadcrumb item in the toolbar.
* Represents a breadcrumb item in the header.
*
* @public
*/
export interface ToolbarBreadcrumb {
export interface HeaderBreadcrumb {
label: string;
href: string;
}
/**
* Props for the main Toolbar component.
* Props for individual header tab components.
*
* @public
*/
export interface ToolbarProps {
icon?: React.ReactNode;
name?: string;
tabs?: ToolbarTab[];
options?: ToolbarOption[];
breadcrumbs?: ToolbarBreadcrumb[];
}
/**
* Props for individual toolbar tab components.
*
* @public
*/
export interface ToolbarTabProps {
tab: ToolbarTab;
export interface HeaderTabProps {
tab: HeaderTab;
setTabRef: (key: string, element: HTMLDivElement | null) => void;
setHoveredKey: (key: string | null) => void;
}
/**
* Props for the toolbar indicators component that handles tab highlighting and animation.
* Props for the header indicators component that handles tab highlighting and animation.
*
* @public
*/
export interface ToolbarIndicatorsProps {
export interface HeaderIndicatorsProps {
tabRefs: MutableRefObject<Map<string, HTMLDivElement>>;
tabsRef: MutableRefObject<HTMLDivElement | null>;
hoveredKey: string | null;
+1 -1
View File
@@ -27,6 +27,7 @@
@import '../components/FieldLabel/FieldLabel.styles.css';
@import '../components/Flex/styles.css';
@import '../components/Grid/styles.css';
@import '../components/Header/Header.styles.css';
@import '../components/Heading/styles.css';
@import '../components/Icon/styles.css';
@import '../components/Link/styles.css';
@@ -41,7 +42,6 @@
@import '../components/Text/styles.css';
@import '../components/TextField/TextField.styles.css';
@import '../components/SearchField/SearchField.styles.css';
@import '../components/Toolbar/Toolbar.styles.css';
@import '../components/Tooltip/Tooltip.styles.css';
@import '../components/ScrollArea/ScrollArea.styles.css';
@import '../components/Select/Select.styles.css';
@@ -235,24 +235,23 @@ export const componentDefinitions = {
disabled: [true, false] as const,
},
},
Toolbar: {
Header: {
classNames: {
toolbar: 'canon-Toolbar',
toolbarContentWrapper: 'canon-ToolbarContentWrapper',
toolbarOverlay: 'canon-ToolbarOverlay',
toolbarContent: 'canon-ToolbarContent',
toolbarOptions: 'canon-ToolbarOptions',
icon: 'canon-ToolbarIcon',
name: 'canon-ToolbarName',
breadcrumbs: 'canon-ToolbarBreadcrumbs',
breadcrumb: 'canon-ToolbarBreadcrumb',
breadcrumbLink: 'canon-ToolbarBreadcrumbLink',
breadcrumbSeparator: 'canon-ToolbarBreadcrumbSeparator',
tabs: 'canon-ToolbarTabs',
tabList: 'canon-ToolbarTabList',
tab: 'canon-ToolbarTab',
activeIndicator: 'canon-ToolbarActiveIndicator',
hoveredIndicator: 'canon-ToolbarHoveredIndicator',
toolbar: 'canon-HeaderToolbar',
toolbarWrapper: 'canon-HeaderToolbarWrapper',
toolbarContent: 'canon-HeaderToolbarContent',
toolbarOptions: 'canon-HeaderToolbarOptions',
toolbarIcon: 'canon-HeaderToolbarIcon',
toolbarName: 'canon-HeaderToolbarName',
breadcrumbs: 'canon-HeaderBreadcrumbs',
breadcrumb: 'canon-HeaderBreadcrumb',
breadcrumbLink: 'canon-HeaderBreadcrumbLink',
breadcrumbSeparator: 'canon-HeaderBreadcrumbSeparator',
tabs: 'canon-HeaderTabs',
tabList: 'canon-HeaderTabList',
tab: 'canon-HeaderTab',
tabActive: 'canon-HeaderTabActive',
tabHovered: 'canon-HeaderTabHovered',
},
},
Tooltip: {