Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(components): post-header #3837

Merged
merged 13 commits into from
Nov 12, 2024
7 changes: 7 additions & 0 deletions .changeset/popular-mirrors-cross.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@swisspost/design-system-components': minor
'@swisspost/design-system-components-angular': minor
'@swisspost/design-system-components-react': minor
---

Added a provisional post-header component with some basic functionality in place. This component is not finished in this state.
1 change: 1 addition & 0 deletions packages/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
"rimraf": "6.0.1",
"rollup-plugin-postcss": "4.0.2",
"sass": "1.78.0",
"throttle-debounce": "5.0.2",
"ts-jest": "29.2.4",
"typescript": "5.5.4"
},
Expand Down
13 changes: 13 additions & 0 deletions packages/components/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ export namespace Components {
*/
"update": () => Promise<void>;
}
interface PostHeader {
}
/**
* @class PostIcon - representing a stencil component
*/
Expand Down Expand Up @@ -470,6 +472,12 @@ declare global {
prototype: HTMLPostCollapsibleTriggerElement;
new (): HTMLPostCollapsibleTriggerElement;
};
interface HTMLPostHeaderElement extends Components.PostHeader, HTMLStencilElement {
}
var HTMLPostHeaderElement: {
prototype: HTMLPostHeaderElement;
new (): HTMLPostHeaderElement;
};
/**
* @class PostIcon - representing a stencil component
*/
Expand Down Expand Up @@ -592,6 +600,7 @@ declare global {
"post-card-control": HTMLPostCardControlElement;
"post-collapsible": HTMLPostCollapsibleElement;
"post-collapsible-trigger": HTMLPostCollapsibleTriggerElement;
"post-header": HTMLPostHeaderElement;
"post-icon": HTMLPostIconElement;
"post-language-option": HTMLPostLanguageOptionElement;
"post-logo": HTMLPostLogoElement;
Expand Down Expand Up @@ -736,6 +745,8 @@ declare namespace LocalJSX {
*/
"for"?: string;
}
interface PostHeader {
}
/**
* @class PostIcon - representing a stencil component
*/
Expand Down Expand Up @@ -909,6 +920,7 @@ declare namespace LocalJSX {
"post-card-control": PostCardControl;
"post-collapsible": PostCollapsible;
"post-collapsible-trigger": PostCollapsibleTrigger;
"post-header": PostHeader;
"post-icon": PostIcon;
"post-language-option": PostLanguageOption;
"post-logo": PostLogo;
Expand Down Expand Up @@ -936,6 +948,7 @@ declare module "@stencil/core" {
"post-card-control": LocalJSX.PostCardControl & JSXBase.HTMLAttributes<HTMLPostCardControlElement>;
"post-collapsible": LocalJSX.PostCollapsible & JSXBase.HTMLAttributes<HTMLPostCollapsibleElement>;
"post-collapsible-trigger": LocalJSX.PostCollapsibleTrigger & JSXBase.HTMLAttributes<HTMLPostCollapsibleTriggerElement>;
"post-header": LocalJSX.PostHeader & JSXBase.HTMLAttributes<HTMLPostHeaderElement>;
/**
* @class PostIcon - representing a stencil component
*/
Expand Down
137 changes: 137 additions & 0 deletions packages/components/src/components/post-header/post-header.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
@use '@swisspost/design-system-styles/mixins/media';

*,
::before,
::after {
box-sizing: border-box;
}

:host {
--global-header-height: 72px;
--main-header-height: 56px;
--header-height: calc(var(--global-header-height) + var(--main-header-height));

@include media.max(lg) {
--global-header-height: 64px;
}
}

.d-flex {
display: flex;
}

.space-between {
justify-content: space-between;
}

.global-header {
background-color: #ffcc00;
display: flex;
justify-content: space-between;
align-items: center;
position: sticky;
padding-inline-start: 4px;
padding-inline-end: 12px;

height: var(--global-header-height);

@include media.max(lg) {
top: 0;
}

@include media.min(lg) {
top: calc((var(--global-header-height) - 24px) * -1);
}
}

slot[name='post-logo'] {
align-self: flex-end;
}

.global-sub {
display: flex;
align-items: center;
gap: 2rem;
height: var(--global-header-height);
}

.align-end {
align-items: flex-end;
}

.logo {
flex: 1 0 auto;
height: var(--global-header-height);
width: var(--global-header-height);
min-height: 24px;
align-self: flex-end;

@include media.min(lg) {
height: calc(var(--global-header-height) - var(--header-scroll-top));
}
}

::slotted(ul),
::slotted(post-mainnavigation) {
margin-block: 0;
list-style: none;
display: flex;
padding-left: 0;
gap: 1rem;
}

.title-header,
.main-navigation {
display: flex;
align-items: center;
padding-inline: 12px;
background: white;
}

.title-header {
height: var(--main-header-height);
display: flex;
align-items: center;

@include media.max(lg) {
border-bottom: 1px solid black;
}
}
:host(:not(:has([slot='title']))) .title-header {
display: none;
}

::slotted(h1) {
margin: 0 !important;
font-size: 28px !important;
}

.main-navigation {
position: sticky;
top: 24px;
height: var(--main-header-height);

@include media.min(lg) {
border-bottom: 1px solid black;
}

@include media.max(lg) {
display: none;
position: absolute;
top: var(--header-height);
bottom: 0;
width: 100%;
background-color: white;
height: auto;

&.extended {
display: block;
}
}
}

.mobile-toggle {
@include media.min(lg) {
display: none;
}
}
127 changes: 127 additions & 0 deletions packages/components/src/components/post-header/post-header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { Component, h, Host, State, Element } from '@stencil/core';
import { throttle } from 'throttle-debounce';
import { version } from '@root/package.json';

@Component({
tag: 'post-header',
shadow: true,
styleUrl: './post-header.scss',
})
export class PostHeader {
@Element() host: HTMLPostHeaderElement;
@State() device: 'mobile' | 'tablet' | 'desktop' = null;
@State() mobileMenuExtended: boolean = false;

private scrollParent = null;
private throttledScroll = () => this.handleScrollEvent();
private debouncedResize = throttle(50, () => this.handleResize());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There seem to be a mismatch with the throttled/debounced naming.


componentWillRender() {
this.scrollParent = this.getScrollParent(this.host);
this.scrollParent.addEventListener('scroll', this.throttledScroll, { passive: true });
window.addEventListener('resize', this.debouncedResize, { passive: true });
this.handleResize();
this.handleScrollEvent();
}

private handleScrollEvent() {
// Credits: "https://github.com/qeremy/so/blob/master/so.dom.js#L426"
const st = Math.max(
0,
this.scrollParent instanceof Document
? this.scrollParent.documentElement.scrollTop
: this.scrollParent.scrollTop,
);

this.host.style.setProperty('--header-scroll-top', `${st}px`);
}

private getScrollParent(node: Element): Element | Document {
let currentParent = node.parentElement;
while (currentParent) {
if (currentParent.nodeName === 'BODY') {
return document;
}
if (this.isScrollable(currentParent)) {
return currentParent;
}
currentParent = currentParent.parentElement;
}
return document;
}

private isScrollable(node: Element) {
if (!(node instanceof HTMLElement || node instanceof SVGElement)) {
return false;
}
const style = getComputedStyle(node);
return ['overflow', 'overflow-x', 'overflow-y'].some(propertyName => {
const value = style.getPropertyValue(propertyName);
return value === 'auto' || value === 'scroll';
});
}

private handleResize() {
const width = window?.innerWidth;
if (width >= 1024) {
this.device = 'desktop';
} else if (width >= 600) {
this.device = 'tablet';
} else {
this.device = 'mobile';
}
}

private handleMobileMenuToggle() {
this.mobileMenuExtended = !this.mobileMenuExtended;
}

render() {
const mainNavClasses = ['main-navigation'];
if (this.mobileMenuExtended) {
mainNavClasses.push('extended');
}

return (
<Host version={version}>
<div class="global-header">
<div class="global-sub">
<div class="logo">
<slot name="post-logo"></slot>
</div>
{this.device === 'desktop' && <slot name="audience-navigation"></slot>}
</div>
<div class="global-sub">
{this.device === 'desktop' && <slot name="meta-navigation"></slot>}
<slot name="global-controls"></slot>
{this.device === 'desktop' && <slot name="post-language-switch"></slot>}
<div onClick={() => this.handleMobileMenuToggle()} class="mobile-toggle">
<slot name="post-togglebutton"></slot>
</div>
</div>
</div>

<div class="title-header d-flex space-between align-center">
<slot name="title"></slot>
<div class="global-sub">
<slot name="local-controls"></slot>
<slot></slot>
</div>
</div>

<div class={mainNavClasses.join(' ')}>
{(this.device === 'mobile' || this.device === 'tablet') && (
<slot name="audience-navigation"></slot>
)}
<slot name="post-mainnavigation"></slot>
{(this.device === 'mobile' || this.device === 'tablet') && (
<slot name="meta-navigation"></slot>
)}
{(this.device === 'mobile' || this.device === 'tablet') && (
<slot name="post-language-switch"></slot>
)}
</div>
</Host>
);
}
}
10 changes: 10 additions & 0 deletions packages/components/src/components/post-header/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# post-header



<!-- Auto Generated Below -->


----------------------------------------------

*Built with [StencilJS](https://stenciljs.com/)*
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,3 @@
.description {
@include utilities.visuallyhidden;
}

Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export class PostLogo {
const LogoTag = logoLink ? 'a' : 'span';

return (
<Host data-version={version}>
<Host data-version={version} slot="post-logo">
<LogoTag class="logo" {...(logoLink ? { href: logoLink } : {})}>
<span class="description">
<slot onSlotchange={() => this.checkDescription()}></slot>
Expand Down
Loading