diff --git a/packages/ui/report.api.md b/packages/ui/report.api.md index 1e549de36d..d49a512536 100644 --- a/packages/ui/report.api.md +++ b/packages/ui/report.api.md @@ -1579,10 +1579,21 @@ export const HeaderDefinition: { readonly styles: { readonly [key: string]: string; }; + readonly bg: 'consumer'; readonly classNames: { readonly root: 'bui-Header'; + readonly beforeSticky: 'bui-HeaderBeforeSticky'; + readonly stickySentinel: 'bui-HeaderStickySentinel'; readonly content: 'bui-HeaderContent'; + readonly afterSticky: 'bui-HeaderAfterSticky'; readonly breadcrumbs: 'bui-HeaderBreadcrumbs'; + readonly breadcrumbsSmall: 'bui-HeaderBreadcrumbsSmall'; + readonly breadcrumbLink: 'bui-HeaderBreadcrumbLink'; + readonly breadcrumbLinkSmall: 'bui-HeaderBreadcrumbLinkSmall'; + readonly breadcrumbSeparator: 'bui-HeaderBreadcrumbSeparator'; + readonly titleStack: 'bui-HeaderTitleStack'; + readonly title: 'bui-HeaderTitle'; + readonly titleSmall: 'bui-HeaderTitleSmall'; readonly tabsWrapper: 'bui-HeaderTabsWrapper'; readonly controls: 'bui-HeaderControls'; readonly tags: 'bui-HeaderTags'; @@ -1601,6 +1612,7 @@ export const HeaderDefinition: { readonly tags: {}; readonly metadata: {}; readonly className: {}; + readonly sticky: {}; }; }; @@ -1732,6 +1744,8 @@ export interface HeaderOwnProps { // (undocumented) metadata?: HeaderMetadataItem[]; // (undocumented) + sticky?: boolean; + // (undocumented) tabs?: HeaderNavTabItem[]; // (undocumented) tags?: HeaderTag[]; @@ -1750,10 +1764,21 @@ export const HeaderPageDefinition: { readonly styles: { readonly [key: string]: string; }; + readonly bg: 'consumer'; readonly classNames: { readonly root: 'bui-Header'; + readonly beforeSticky: 'bui-HeaderBeforeSticky'; + readonly stickySentinel: 'bui-HeaderStickySentinel'; readonly content: 'bui-HeaderContent'; + readonly afterSticky: 'bui-HeaderAfterSticky'; readonly breadcrumbs: 'bui-HeaderBreadcrumbs'; + readonly breadcrumbsSmall: 'bui-HeaderBreadcrumbsSmall'; + readonly breadcrumbLink: 'bui-HeaderBreadcrumbLink'; + readonly breadcrumbLinkSmall: 'bui-HeaderBreadcrumbLinkSmall'; + readonly breadcrumbSeparator: 'bui-HeaderBreadcrumbSeparator'; + readonly titleStack: 'bui-HeaderTitleStack'; + readonly title: 'bui-HeaderTitle'; + readonly titleSmall: 'bui-HeaderTitleSmall'; readonly tabsWrapper: 'bui-HeaderTabsWrapper'; readonly controls: 'bui-HeaderControls'; readonly tags: 'bui-HeaderTags'; @@ -1772,6 +1797,7 @@ export const HeaderPageDefinition: { readonly tags: {}; readonly metadata: {}; readonly className: {}; + readonly sticky: {}; }; }; diff --git a/packages/ui/src/components/Header/Header.module.css b/packages/ui/src/components/Header/Header.module.css index 08626268f7..0834767d2c 100644 --- a/packages/ui/src/components/Header/Header.module.css +++ b/packages/ui/src/components/Header/Header.module.css @@ -113,26 +113,93 @@ flex-shrink: 0; } - .bui-HeaderBreadcrumbs { + .bui-HeaderTitleStack { + position: relative; + flex: 1 1 auto; + height: calc(var(--bui-font-size-6) * 1.4); + min-width: 0; + overflow: hidden; + } + + .bui-HeaderBreadcrumbs, + .bui-HeaderBreadcrumbsSmall { + position: absolute; + inset-inline: 0; + top: 50%; display: flex; flex-direction: row; align-items: center; gap: var(--bui-space-2); min-width: 0; + transition: opacity 200ms ease, transform 200ms ease; } - .bui-HeaderTitle { - margin: 0; - padding: 0; + .bui-HeaderBreadcrumbs { + opacity: 1; + transform: translateY(-50%); + } + + .bui-HeaderBreadcrumbsSmall { + gap: var(--bui-space-1); + opacity: 0; + transform: translateY(25%); + } + + .bui-HeaderBreadcrumbs .bui-HeaderBreadcrumbLink, + .bui-HeaderBreadcrumbsSmall .bui-HeaderBreadcrumbLinkSmall { + max-width: 240px; overflow: hidden; font-family: var(--bui-font-regular); - font-size: var(--bui-font-size-6); font-weight: var(--bui-font-weight-bold); line-height: 140%; text-overflow: ellipsis; white-space: nowrap; } + .bui-HeaderBreadcrumbs .bui-HeaderBreadcrumbLink { + font-size: var(--bui-font-size-6); + } + + .bui-HeaderBreadcrumbsSmall .bui-HeaderBreadcrumbLinkSmall { + font-size: var(--bui-font-size-4); + } + + .bui-HeaderBreadcrumbSeparator { + flex-shrink: 0; + } + + .bui-HeaderTitle, + .bui-HeaderTitleSmall { + margin: 0; + padding: 0; + flex: 1 1 auto; + min-width: 0; + font-family: var(--bui-font-regular); + font-weight: var(--bui-font-weight-bold); + line-height: 140%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .bui-HeaderTitle { + font-size: var(--bui-font-size-6); + } + + .bui-HeaderTitleSmall { + font-size: var(--bui-font-size-4); + } + + .bui-HeaderContent[data-stuck] .bui-HeaderBreadcrumbs { + opacity: 0; + transform: translateY(-125%); + } + + .bui-HeaderContent[data-stuck] .bui-HeaderBreadcrumbsSmall { + opacity: 1; + transform: translateY(-50%); + } + .bui-HeaderTags { display: flex; flex-direction: row; diff --git a/packages/ui/src/components/Header/Header.stories.tsx b/packages/ui/src/components/Header/Header.stories.tsx index 09dadce9f4..b77a4c16c1 100644 --- a/packages/ui/src/components/Header/Header.stories.tsx +++ b/packages/ui/src/components/Header/Header.stories.tsx @@ -483,3 +483,38 @@ export const StickyWithLongTitle = meta.story({ ), }); + +export const StickyWithBreadcrumbsAndLongTitle = meta.story({ + decorators: [withRouter], + render: () => ( + <> +
Custom action} + /> + + {Array.from({ length: 60 }, (_, i) => ( +

+ Scroll down to see the breadcrumb and long title bar stick to the + top while the tags, description, and metadata scroll away. Line{' '} + {i + 1}. +

+ ))} +
+ + ), +}); diff --git a/packages/ui/src/components/Header/Header.tsx b/packages/ui/src/components/Header/Header.tsx index ae992f4b41..1313555daa 100644 --- a/packages/ui/src/components/Header/Header.tsx +++ b/packages/ui/src/components/Header/Header.tsx @@ -158,25 +158,55 @@ export const Header = (props: HeaderProps) => { data-sticky={sticky || undefined} data-stuck={isStuck || undefined} > -
- {breadcrumbs && - breadcrumbs.map(breadcrumb => ( - - - {breadcrumb.label} - - - - ))} -

{title}

+
+
+ {breadcrumbs && + breadcrumbs.map(breadcrumb => ( + + + {breadcrumb.label} + + + + ))} +

{title}

+
+
+ {breadcrumbs && + breadcrumbs.map(breadcrumb => ( + + + {breadcrumb.label} + + + + ))} +

{title}

+
{customActions}
diff --git a/packages/ui/src/components/Header/definition.ts b/packages/ui/src/components/Header/definition.ts index ace08f5195..4811871094 100644 --- a/packages/ui/src/components/Header/definition.ts +++ b/packages/ui/src/components/Header/definition.ts @@ -32,7 +32,13 @@ export const HeaderDefinition = defineComponent()({ content: 'bui-HeaderContent', afterSticky: 'bui-HeaderAfterSticky', breadcrumbs: 'bui-HeaderBreadcrumbs', + breadcrumbsSmall: 'bui-HeaderBreadcrumbsSmall', + breadcrumbLink: 'bui-HeaderBreadcrumbLink', + breadcrumbLinkSmall: 'bui-HeaderBreadcrumbLinkSmall', + breadcrumbSeparator: 'bui-HeaderBreadcrumbSeparator', + titleStack: 'bui-HeaderTitleStack', title: 'bui-HeaderTitle', + titleSmall: 'bui-HeaderTitleSmall', tabsWrapper: 'bui-HeaderTabsWrapper', controls: 'bui-HeaderControls', tags: 'bui-HeaderTags',