diff --git a/.changeset/gold-news-repair.md b/.changeset/gold-news-repair.md new file mode 100644 index 0000000000..f10c73d5a3 --- /dev/null +++ b/.changeset/gold-news-repair.md @@ -0,0 +1,5 @@ +--- +'@swisspost/design-system-components': minor +--- + +Added the `post-megadropdown` component. diff --git a/packages/components/src/components.d.ts b/packages/components/src/components.d.ts index a7a6738ea0..075f07063b 100644 --- a/packages/components/src/components.d.ts +++ b/packages/components/src/components.d.ts @@ -281,25 +281,20 @@ export namespace Components { } interface PostMegadropdown { /** - * Hide megadropdown - * @returns boolean + * Displays the popover dropdown + * @param target - The HTML element relative to which the popover dropdown should be displayed. */ - "hide": () => Promise; - /** - * Show megadropdown - * @param element HTMLElement - * @returns boolean - */ - "show": (element: HTMLElement) => Promise; + "show": (target: HTMLElement) => Promise; /** - * Toggle megadropdown - * @param element HTMLElement - * @param force boolean - * @returns boolean + * Toggles the dropdown visibility based on its current state. */ - "toggle": (element: HTMLElement, force?: boolean) => Promise; + "toggle": (target: HTMLElement) => Promise; } interface PostMegadropdownTrigger { + /** + * ID of the mega dropdown element that this trigger is linked to. Used to open and close the specified mega dropdown. + */ + "for": string; } interface PostMenu { /** @@ -497,9 +492,9 @@ export interface PostMainnavigationCustomEvent extends CustomEvent { detail: T; target: HTMLPostMainnavigationElement; } -export interface PostMegadropdownTriggerCustomEvent extends CustomEvent { +export interface PostMegadropdownCustomEvent extends CustomEvent { detail: T; - target: HTMLPostMegadropdownTriggerElement; + target: HTMLPostMegadropdownElement; } export interface PostMenuCustomEvent extends CustomEvent { detail: T; @@ -694,25 +689,25 @@ declare global { prototype: HTMLPostMainnavigationElement; new (): HTMLPostMainnavigationElement; }; - interface HTMLPostMegadropdownElement extends Components.PostMegadropdown, HTMLStencilElement { - } - var HTMLPostMegadropdownElement: { - prototype: HTMLPostMegadropdownElement; - new (): HTMLPostMegadropdownElement; - }; - interface HTMLPostMegadropdownTriggerElementEventMap { - "postToggle": any; + interface HTMLPostMegadropdownElementEventMap { + "postToggleMegadropdown": boolean; } - interface HTMLPostMegadropdownTriggerElement extends Components.PostMegadropdownTrigger, HTMLStencilElement { - addEventListener(type: K, listener: (this: HTMLPostMegadropdownTriggerElement, ev: PostMegadropdownTriggerCustomEvent) => any, options?: boolean | AddEventListenerOptions): void; + interface HTMLPostMegadropdownElement extends Components.PostMegadropdown, HTMLStencilElement { + addEventListener(type: K, listener: (this: HTMLPostMegadropdownElement, ev: PostMegadropdownCustomEvent) => any, options?: boolean | AddEventListenerOptions): void; addEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; addEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; - removeEventListener(type: K, listener: (this: HTMLPostMegadropdownTriggerElement, ev: PostMegadropdownTriggerCustomEvent) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: K, listener: (this: HTMLPostMegadropdownElement, ev: PostMegadropdownCustomEvent) => any, options?: boolean | EventListenerOptions): void; removeEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | EventListenerOptions): void; removeEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void; removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; } + var HTMLPostMegadropdownElement: { + prototype: HTMLPostMegadropdownElement; + new (): HTMLPostMegadropdownElement; + }; + interface HTMLPostMegadropdownTriggerElement extends Components.PostMegadropdownTrigger, HTMLStencilElement { + } var HTMLPostMegadropdownTriggerElement: { prototype: HTMLPostMegadropdownTriggerElement; new (): HTMLPostMegadropdownTriggerElement; @@ -1117,12 +1112,16 @@ declare namespace LocalJSX { "onPostToggle"?: (event: PostMainnavigationCustomEvent) => void; } interface PostMegadropdown { + /** + * Emits when the dropdown is shown or hidden. The event payload is a boolean: `true` when the dropdown was opened, `false` when it was closed. + */ + "onPostToggleMegadropdown"?: (event: PostMegadropdownCustomEvent) => void; } interface PostMegadropdownTrigger { /** - * Emits after each toggle + * ID of the mega dropdown element that this trigger is linked to. Used to open and close the specified mega dropdown. */ - "onPostToggle"?: (event: PostMegadropdownTriggerCustomEvent) => void; + "for": string; } interface PostMenu { /** diff --git a/packages/components/src/components/post-closebutton/post-closebutton.tsx b/packages/components/src/components/post-closebutton/post-closebutton.tsx index 68d13ef51a..0d447ab844 100644 --- a/packages/components/src/components/post-closebutton/post-closebutton.tsx +++ b/packages/components/src/components/post-closebutton/post-closebutton.tsx @@ -15,7 +15,7 @@ export class PostClosebutton { return ( diff --git a/packages/components/src/components/post-megadropdown-trigger/readme.md b/packages/components/src/components/post-megadropdown-trigger/readme.md index 870d32aabc..9488d51aa9 100644 --- a/packages/components/src/components/post-megadropdown-trigger/readme.md +++ b/packages/components/src/components/post-megadropdown-trigger/readme.md @@ -5,11 +5,11 @@ -## Events +## Properties -| Event | Description | Type | -| ------------ | ----------------------- | ------------------ | -| `postToggle` | Emits after each toggle | `CustomEvent` | +| Property | Attribute | Description | Type | Default | +| ------------------ | --------- | ------------------------------------------------------------------------------------------------------------------- | -------- | ----------- | +| `for` _(required)_ | `for` | ID of the mega dropdown element that this trigger is linked to. Used to open and close the specified mega dropdown. | `string` | `undefined` | ---------------------------------------------- diff --git a/packages/components/src/components/post-megadropdown/post-megadropdown.scss b/packages/components/src/components/post-megadropdown/post-megadropdown.scss index ffc7413fc4..ba5e0d7465 100644 --- a/packages/components/src/components/post-megadropdown/post-megadropdown.scss +++ b/packages/components/src/components/post-megadropdown/post-megadropdown.scss @@ -1,6 +1,24 @@ @use '@swisspost/design-system-styles/mixins/media'; @use '@swisspost/design-system-styles/variables/color'; +@keyframes slide-in { + 0% { + transform: translateX(100%); + } + 100% { + transform: translateX(0%); + } +} + +@keyframes slide-out { + from { + transform: translateX(0%); + } + to { + transform: translateX(100%); + } +} + *, ::before, ::after { @@ -8,64 +26,175 @@ } post-popovercontainer { + --post-global-header-height: 72px; + --post-main-header-height: 56px; + --post-header-height: calc(var(--post-global-header-height) + var(--post-main-header-height)); + background-color: #fafafa; + border: none; + border-top: 1px solid #e1e0dc; width: 100%; - border-bottom: 1px solid black; + border-radius: 0; + box-shadow: 0 8px 6px rgba(0, 0, 0, 16%); @include media.max(lg) { - // Overrides floating-ui inline styles on mobile + --post-global-header-height: 64px; position: absolute; - top: var(--header-height) !important; + top: var(--post-header-height) !important; bottom: 0; left: 0; width: 100%; height: auto; + + &.slide-in { + animation: slide-in; + animation-duration: 350ms; + animation-fill-mode: forwards; + } + + &.slide-out { + animation: slide-out; + animation-duration: 350ms; + animation-fill-mode: forwards; + } } } .megadropdown { - display: flex; - gap: 1rem; - padding: 1.5rem; - border-radius: 0 0 3px 3px; + padding: 31px 40px 40px; @include media.max(lg) { - flex-direction: column; + padding: 16px 32px 24px; + } + + .megadropdown-content { + gap: 1.5rem; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(min(15vw, 100%), 1fr)); + grid-auto-rows: 1fr auto; + grid-auto-flow: dense; + + @include media.max(lg) { + grid-template-columns: repeat(auto-fit, minmax(min(35vw, 100%), 1fr)); + } + } + + h2 { + display: none; + margin-top: 0; + margin-bottom: 8px; + font-weight: 900; + + a { + text-decoration: none; + font-weight: inherit; + border-bottom: 0; + border-top: 0; + } + + @include media.max(lg) { + display: block; + font-size: 20px; + } + } + + // Unset styles added by parent main navigation + a:hover { + border-block-width: 0; + + &::after { + content: unset; + } } post-list { - display: flex; - flex-direction: column; - gap: 0.5rem; + display: grid; + grid-row: span 2; + grid-template-rows: subgrid; + row-gap: 0px; + + > [role='list'] { + flex-direction: unset; + display: unset; + } + + > * { + width: 100%; + } + + .list-title { + display: flex; + flex-direction: column-reverse; + height: 100%; + + h3 { + font-size: 20px; + border-bottom: 1px solid #050400; + padding: 15px 12px 14px 0; + display: block; + font-weight: 900; + + a { + text-decoration: none; + width: 100%; + font-size: inherit; + display: inline-block; + font-weight: inherit; + height: unset; + padding: 0; + border: 0; + } + + @include media.max(lg) { + font-size: 16px; + padding: 12px 12px 11px 0; + } + } + } } post-list-item { - border-bottom: 1px solid gray; + border-bottom: 1px solid #050400; > a { - padding-block: 0.25rem; + padding: 13px 12px 12px 0; display: block; text-decoration: none; + width: 100%; + border-bottom: 0; + height: auto; + border-top: 0; + + @include media.max(lg) { + padding: 12px 12px 11px 0; + } } } -} -.back-button { - display: none; + .back-button { + display: none; + margin-bottom: 2rem; + + .btn { + width: auto; + } - @include media.max(lg) { - display: block; + post-icon { + transform: rotate(180deg); + } + + @include media.max(lg) { + display: block; + } } -} -post-closebutton { - margin-left: auto; + .close-button { + position: absolute; + top: 1rem; + right: 1rem; - @include media.max(lg) { - display: none; + @include media.max(lg) { + display: none; + } } } - -h3 { - font-size: 20px; -} diff --git a/packages/components/src/components/post-megadropdown/post-megadropdown.tsx b/packages/components/src/components/post-megadropdown/post-megadropdown.tsx index 3709c59e5a..03d0ff5db9 100644 --- a/packages/components/src/components/post-megadropdown/post-megadropdown.tsx +++ b/packages/components/src/components/post-megadropdown/post-megadropdown.tsx @@ -1,56 +1,105 @@ -import { Component, Host, Method, h } from '@stencil/core'; +import { Component, Element, Event, EventEmitter, h, Host, Method, State } from '@stencil/core'; @Component({ tag: 'post-megadropdown', + styleUrl: 'post-megadropdown.scss', shadow: false, - styleUrl: './post-megadropdown.scss', }) export class PostMegadropdown { private popoverRef: HTMLPostPopovercontainerElement; + @Element() host: HTMLPostMegadropdownElement; + /** - * Show megadropdown - * @param element HTMLElement - * @returns boolean + * Holds the current visibility state of the dropdown. + * This state is internally managed to track whether the dropdown is open (`true`) or closed (`false`), + * and updates automatically when the dropdown is toggled. */ - @Method() - async show(element: HTMLElement) { - return this.popoverRef.show(element); + @State() isVisible: boolean = false; + + @State() animationClass: string | null = null; + + /** + * Emits when the dropdown is shown or hidden. + * The event payload is a boolean: `true` when the dropdown was opened, `false` when it was closed. + **/ + @Event() postToggleMegadropdown: EventEmitter; + + componentDidLoad() { + this.popoverRef.addEventListener('postToggle', (event: CustomEvent) => { + this.isVisible = event.detail; + this.postToggleMegadropdown.emit(this.isVisible); + }); + + this.popoverRef.addEventListener('animationend', () => { + if (this.animationClass === 'slide-out') { + this.hide(); + } + }); } /** - * Hide megadropdown - * @returns boolean + * Toggles the dropdown visibility based on its current state. */ @Method() - async hide() { - return this.popoverRef.hide(); + async toggle(target: HTMLElement) { + this.isVisible ? this.hide() : await this.show(target); } /** - * Toggle megadropdown - * @param element HTMLElement - * @param force boolean - * @returns boolean + * Displays the popover dropdown + * + * @param target - The HTML element relative to which the popover dropdown should be displayed. */ @Method() - async toggle(element: HTMLElement, force?: boolean) { - return this.popoverRef.toggle(element, force ?? undefined); + async show(target: HTMLElement) { + if (this.popoverRef) { + await this.popoverRef.show(target); + this.animationClass = 'slide-in'; + } else { + console.error('show: popoverRef is null or undefined'); + } + } + + /** + * Hides the popover dropdown + */ + private hide() { + if (this.popoverRef) { + this.popoverRef.hide(); + } else { + console.error('hide: popoverRef is null or undefined'); + } } private handleBackButtonClick() { - this.hide(); + this.animationClass = 'slide-out'; + } + + private handleCloseButtonClick() { + this.popoverRef.hide(); } render() { return ( - - (this.popoverRef = el)}> + + (this.popoverRef = el)} + >
this.handleBackButtonClick()} class="back-button">
- +
this.handleCloseButtonClick()} class="close-button"> + +
+ +
+ +
diff --git a/packages/components/src/components/post-megadropdown/readme.md b/packages/components/src/components/post-megadropdown/readme.md index 532147ae8e..368d1a787f 100644 --- a/packages/components/src/components/post-megadropdown/readme.md +++ b/packages/components/src/components/post-megadropdown/readme.md @@ -5,50 +5,46 @@ -## Methods - -### `hide() => Promise` +## Events -Hide megadropdown +| Event | Description | Type | +| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | +| `postToggleMegadropdown` | Emits when the dropdown is shown or hidden. The event payload is a boolean: `true` when the dropdown was opened, `false` when it was closed. | `CustomEvent` | -#### Returns - -Type: `Promise` -boolean +## Methods -### `show(element: HTMLElement) => Promise` +### `show(target: HTMLElement) => Promise` -Show megadropdown +Displays the popover dropdown #### Parameters -| Name | Type | Description | -| --------- | ------------- | ----------- | -| `element` | `HTMLElement` | HTMLElement | +| Name | Type | Description | +| -------- | ------------- | ------------------------------------------------------------------------------ | +| `target` | `HTMLElement` | - The HTML element relative to which the popover dropdown should be displayed. | #### Returns Type: `Promise` -boolean -### `toggle(element: HTMLElement, force?: boolean) => Promise` -Toggle megadropdown +### `toggle(target: HTMLElement) => Promise` + +Toggles the dropdown visibility based on its current state. #### Parameters -| Name | Type | Description | -| --------- | ------------- | ----------- | -| `element` | `HTMLElement` | HTMLElement | -| `force` | `boolean` | boolean | +| Name | Type | Description | +| -------- | ------------- | ----------- | +| `target` | `HTMLElement` | | #### Returns -Type: `Promise` +Type: `Promise` + -boolean ## Dependencies diff --git a/packages/components/src/index.html b/packages/components/src/index.html index 112991f4e6..60605cbfb7 100644 --- a/packages/components/src/index.html +++ b/packages/components/src/index.html @@ -80,10 +80,14 @@

Main Navigation

- Briefe - - -

Briefe title

+ Briefe + + + Schliessen +

Briefe title

Briefe senden

Briefe Schweiz @@ -98,14 +102,17 @@

Schritt für Schritt

Waren Ausland Express und Kurier
- Schliessen
- Pakete - - -

Pakete title

+ Pakete + + + Schliessen +

Pakete title

Pakete senden

Pakete Schweiz @@ -120,7 +127,6 @@

Schritt für Schritt

Waren Ausland Express und Kurier
- Schliessen
diff --git a/packages/documentation/src/stories/raw-components/header/components/header.markup.ts b/packages/documentation/src/stories/raw-components/header/components/header.markup.ts index 37813dd19d..d6bf86c4f4 100644 --- a/packages/documentation/src/stories/raw-components/header/components/header.markup.ts +++ b/packages/documentation/src/stories/raw-components/header/components/header.markup.ts @@ -63,10 +63,14 @@ export default html` - Briefe - - -

Briefe title

+ Briefe + + + Schliessen +

Briefe title

Briefe senden

Briefe Schweiz @@ -81,14 +85,17 @@ export default html` Waren Ausland Express und Kurier
- Schliessen
- Pakete - - -

Pakete title

+ Pakete + + + Schliessen +

Pakete title

Pakete senden

Pakete Schweiz @@ -103,7 +110,6 @@ export default html` Waren Ausland Express und Kurier
- Schliessen
diff --git a/packages/styles/src/components/icon-close-button.scss b/packages/styles/src/components/icon-close-button.scss index c8eb34d175..8cf04ff0c1 100644 --- a/packages/styles/src/components/icon-close-button.scss +++ b/packages/styles/src/components/icon-close-button.scss @@ -16,6 +16,7 @@ tokens.$default-map: components.$post-close; color: tokens.get('close-enabled-fg'); > post-icon { + min-width: tokens.get('close-icon-size'); width: tokens.get('close-icon-size'); height: tokens.get('close-icon-size'); }