diff --git a/.changeset/stupid-pianos-rush.md b/.changeset/stupid-pianos-rush.md new file mode 100644 index 0000000000..b0ed6e883e --- /dev/null +++ b/.changeset/stupid-pianos-rush.md @@ -0,0 +1,5 @@ +--- +'@swisspost/internet-header': minor +--- + +Updated the logo size, the post logo now spans the meta-navigation and scales down on scroll. diff --git a/packages/documentation/src/stories/internet-header/components/header/post-internet-header.stories.tsx b/packages/documentation/src/stories/internet-header/components/header/post-internet-header.stories.tsx index b997cf4eed..85633f1c6b 100644 --- a/packages/documentation/src/stories/internet-header/components/header/post-internet-header.stories.tsx +++ b/packages/documentation/src/stories/internet-header/components/header/post-internet-header.stories.tsx @@ -288,7 +288,7 @@ const Template = (args: Args) => {
-

CWF Internet Header

+

Design System Internet Header

diff --git a/packages/internet-header/cypress/e2e/header.cy.ts b/packages/internet-header/cypress/e2e/header.cy.ts index 3d46c7f2e3..adeb87f697 100644 --- a/packages/internet-header/cypress/e2e/header.cy.ts +++ b/packages/internet-header/cypress/e2e/header.cy.ts @@ -10,8 +10,8 @@ describe('header', () => { cy.get('swisspost-internet-header').should('have.class', 'hydrated'); }); - it(`has title 'CWF Internet Header'`, () => { - cy.get('h1').should('contain.text', 'CWF Internet Header'); + it(`has title 'Design System Internet Header'`, () => { + cy.get('h1').should('contain.text', 'Design System Internet Header'); }); it(`has nav item 'Briefe versenden' selected`, () => { diff --git a/packages/internet-header/src/components/post-internet-header/logo-animation/logo-animation.scss b/packages/internet-header/src/components/post-internet-header/logo-animation/logo-animation.scss new file mode 100644 index 0000000000..c70f6d327b --- /dev/null +++ b/packages/internet-header/src/components/post-internet-header/logo-animation/logo-animation.scss @@ -0,0 +1,24 @@ +@use '../../../utils/mixins.scss'; + +/** + * Default position: scrolled + * Logo is being scaled up for the initial view (scrollY = 0). This enables media queries to override + * mobile behaviour without re-calculation +*/ +:host { + --logo-scale: 1; + + @include mixins.max(lg) { + --logo-scale: 1 !important; + } +} + +post-logo { + height: var(--header-height); + transform-origin: bottom left; + transform: scale(var(--logo-scale)); +} + +post-main-navigation { + margin-left: calc((var(--header-height) * var(--logo-scale)) - var(--header-height)); +} diff --git a/packages/internet-header/src/components/post-internet-header/logo-animation/logo-animation.ts b/packages/internet-header/src/components/post-internet-header/logo-animation/logo-animation.ts new file mode 100644 index 0000000000..ce95ceadcd --- /dev/null +++ b/packages/internet-header/src/components/post-internet-header/logo-animation/logo-animation.ts @@ -0,0 +1,59 @@ +import { debounce } from 'throttle-debounce'; +import { state } from '../../../data/store'; + +export const registerLogoAnimationObserver = ( + target: HTMLElement, + headerRef: HTMLSwisspostInternetHeaderElement, +) => { + /** + * Set intersection ratio as CSS custom property + */ + const handleScroll = () => { + const fullStickyness = state.stickyness === 'full'; + let scale = 1; + // Minus 1px border at the bottom that the logo is not covering + const adjustedHeaderHeight = headerRef.clientHeight - 1; + const scrollY = fullStickyness ? 0 : window.scrollY; + + // If meta navigation is not visible (mobile, not configured), scale should just be 1 + if (target.clientHeight > 0) { + scale = Math.max( + (adjustedHeaderHeight - Math.max(scrollY, 0)) / + (adjustedHeaderHeight - target.clientHeight), + 1, + ); + } + headerRef.style.setProperty('--logo-scale', scale.toString()); + }; + + const debounced = debounce(150, handleScroll); + + /** + * Observe the meta navigation in order to not track scroll events throughout the whole page. + * This ensures that the scroll listener is only active while the meta navigation is visible. + */ + const observer = new IntersectionObserver( + entries => { + entries.forEach(entry => { + if (entry.isIntersecting && entry.intersectionRatio > 0) { + window.addEventListener('scroll', handleScroll, { passive: true }); + window.addEventListener('resize', debounced); + + // Ensure callback is called at least once in case main thread is too busy while scrolling up + window.requestAnimationFrame(handleScroll); + } else { + window.removeEventListener('scroll', handleScroll); + window.removeEventListener('resize', debounced); + window.requestAnimationFrame(handleScroll); + } + }); + }, + { + // Fires when element leaves viewport (0) and when it just about enters it (0.001) + threshold: [0, 0.001], + }, + ); + observer.observe(target); + + return handleScroll; +}; diff --git a/packages/internet-header/src/components/post-internet-header/post-internet-header.scss b/packages/internet-header/src/components/post-internet-header/post-internet-header.scss index a3e3bee625..399bc0a865 100644 --- a/packages/internet-header/src/components/post-internet-header/post-internet-header.scss +++ b/packages/internet-header/src/components/post-internet-header/post-internet-header.scss @@ -1,6 +1,7 @@ @use '@swisspost/design-system-styles/variables/color'; @use '../../utils/utils.scss'; @use '../../utils/mixins.scss'; +@use './logo-animation/logo-animation.scss'; :host { display: block; diff --git a/packages/internet-header/src/components/post-internet-header/post-internet-header.tsx b/packages/internet-header/src/components/post-internet-header/post-internet-header.tsx index 6d6a02aba6..20e8bf286b 100644 --- a/packages/internet-header/src/components/post-internet-header/post-internet-header.tsx +++ b/packages/internet-header/src/components/post-internet-header/post-internet-header.tsx @@ -27,6 +27,7 @@ import { IAvailableLanguage } from '../../models/language.model'; import { translate } from '../../services/language.service'; import { If } from '../../utils/if.component'; import packageJson from '../../../package.json'; +import { registerLogoAnimationObserver } from './logo-animation/logo-animation'; @Component({ tag: 'swisspost-internet-header', @@ -123,7 +124,7 @@ export class PostInternetHeader { @State() activeFlyout: string | null = null; @State() activeDropdownElement: DropdownElement | null = null; - @Element() host: HTMLElement; + @Element() host: HTMLSwisspostInternetHeaderElement; /** * Get the currently set language as a two letter string ("de", "fr" "it" or "en") @@ -135,10 +136,12 @@ export class PostInternetHeader { } private mainNav?: HTMLPostMainNavigationElement; + private metaNav?: HTMLPostMetaNavigationElement; private lastScrollTop = window.scrollY || document.documentElement.scrollTop; private throttledScroll: throttle<() => void>; private debouncedResize: debounce<() => void>; private lastWindowWidth: number = window.innerWidth; + private updateLogoAnimation: () => void; constructor() { if (this.project === undefined || this.project === '' || !isValidProjectId(this.project)) { @@ -167,6 +170,7 @@ export class PostInternetHeader { // Wait for the config to arrive, then render the header try { state.projectId = this.project; + state.stickyness = this.stickyness; state.environment = this.environment.toLocaleLowerCase() as Environment; if (this.language !== undefined) state.currentLanguage = this.language; state.languageSwitchOverrides = @@ -205,6 +209,9 @@ export class PostInternetHeader { this.handleResize(); this.headerLoaded.emit(); this.host.classList.add('header-loaded'); + if (this.meta && this.metaNav) { + this.updateLogoAnimation = registerLogoAnimationObserver(this.metaNav, this.host); + } }); if (this.stickyness === 'full') @@ -299,6 +306,12 @@ export class PostInternetHeader { this.handleLanguageChange(event.detail); } + @Watch('stickyness') + handleStickynessChange(newValue: StickynessOptions) { + state.stickyness = newValue; + this.updateLogoAnimation(); + } + private handleClickOutsideBound = this.handleClickOutside.bind(this); private handleClickOutside(event: Event) { @@ -439,6 +452,7 @@ export class PostInternetHeader { orientation="horizontal" class="hidden-lg" full-width={this.fullWidth} + ref={el => (this.metaNav = el)} > void>; - private resizeObserver: ResizeObserver; - - constructor() { - // Register window resize event listener and a resize observer on the mainnav controls (they change size while controls are being loaded) to display an accurately sized logo - this.throttledResize = throttle(300, () => this.handleResize()); - window.addEventListener('resize', this.throttledResize, { passive: true }); - this.resizeObserver = new ResizeObserver(this.handleResize.bind(this)); - - // Initially call the resize handler - this.handleResize(); - } - - componentDidLoad() { - const mainNavControls = this.host.parentElement?.querySelector('.main-navigation-controls'); - if (mainNavControls) { - this.resizeObserver.observe(mainNavControls); - } - } - - disconnectedCallback() { - window.removeEventListener('resize', this.throttledResize); - this.resizeObserver.disconnect(); - } - - handleResize() { - const mainNavControls = this.host.parentElement?.querySelector('.main-navigation-controls'); - const menuButton = this.host.parentElement?.querySelector('.menu-button'); - if (mainNavControls && menuButton) - this.showFaviconLogo = - window.innerWidth - (150 + mainNavControls.clientWidth + menuButton.clientWidth) <= 0; - } render() { if (state.localizedConfig?.header.logo === undefined) return; diff --git a/packages/internet-header/src/components/post-meta-navigation/post-meta-navigation.scss b/packages/internet-header/src/components/post-meta-navigation/post-meta-navigation.scss index 401a8438c8..495f4a5b15 100644 --- a/packages/internet-header/src/components/post-meta-navigation/post-meta-navigation.scss +++ b/packages/internet-header/src/components/post-meta-navigation/post-meta-navigation.scss @@ -1,11 +1,10 @@ -@use "@swisspost/design-system-styles/variables/color"; -@use "@swisspost/design-system-styles/functions"; -@use "../../utils/utils.scss"; -@use "../../utils/mixins.scss"; +@use '@swisspost/design-system-styles/variables/color'; +@use '@swisspost/design-system-styles/functions'; +@use '../../utils/utils.scss'; +@use '../../utils/mixins.scss'; :host { display: block; - background: color.$gray-background-light; font-size: functions.px-to-rem(14px); } @@ -15,6 +14,8 @@ align-items: center; min-height: functions.px-to-rem(48px); + background: color.$gray-background-light; + @media (min-width: 1441px) { &:not(.full-width) { margin: 0 auto; @@ -40,11 +41,23 @@ --separator-display: block; --separator-height: #{functions.px-to-rem(34px)}; } + + &::before { + display: block; + content: ''; + position: absolute; + top: 0px; + height: var(--meta-header-height); + background-color: color.$gray-background-light; + left: 50%; + width: 50%; + } } } .meta-navigation { .horizontal & { + position: relative; padding-right: 1rem; } } diff --git a/packages/internet-header/src/data/store.ts b/packages/internet-header/src/data/store.ts index c85d1fe84b..0c99a42261 100644 --- a/packages/internet-header/src/data/store.ts +++ b/packages/internet-header/src/data/store.ts @@ -2,6 +2,7 @@ import { createStore } from '@stencil/store'; import { Environment, ILocalizedConfig, ILocalizedCustomConfig } from '../models/general.model'; import { NavMainEntity } from '../models/header.model'; import { IAvailableLanguage } from '../models/language.model'; +import { StickynessOptions } from '../components'; export interface HeaderState { localizedConfig: ILocalizedConfig | null; @@ -14,6 +15,7 @@ export interface HeaderState { languageSwitchOverrides?: IAvailableLanguage[]; localizedCustomConfig?: ILocalizedCustomConfig; osFlyoutOverrides?: NavMainEntity; + stickyness: StickynessOptions; } export const { state, reset, dispose } = createStore({ @@ -24,4 +26,5 @@ export const { state, reset, dispose } = createStore({ search: true, login: true, meta: true, + stickyness: 'minimal', });