diff --git a/packages/components/cypress/e2e/header.cy.ts b/packages/components/cypress/e2e/header.cy.ts index 8ded6e02cd..7591f2a4ea 100644 --- a/packages/components/cypress/e2e/header.cy.ts +++ b/packages/components/cypress/e2e/header.cy.ts @@ -124,8 +124,8 @@ describe('header', () => { cy.get('post-megadropdown#letters a[href="/kl"]').first().as('lettersSecondLink'); cy.get('post-megadropdown#packages a[href="/sch"]').first().as('packagesLink'); - cy.get('post-megadropdown-trigger button').first().as('lettersTrigger'); - cy.get('post-megadropdown-trigger button').eq(1).as('packagesTrigger'); + cy.get('post-megadropdown-trigger').find('button').first().as('lettersTrigger'); + cy.get('post-megadropdown-trigger').find('button').eq(1).as('packagesTrigger'); // Activate first link cy.get('@lettersFirstLink').then($link => $link.attr('aria-current', 'page')); diff --git a/packages/components/cypress/e2e/mainnavigation.cy.ts b/packages/components/cypress/e2e/mainnavigation.cy.ts index d3935c2a03..fad6b59f87 100644 --- a/packages/components/cypress/e2e/mainnavigation.cy.ts +++ b/packages/components/cypress/e2e/mainnavigation.cy.ts @@ -23,6 +23,12 @@ describe('mainnavigation', { baseUrl: null, includeShadowDom: true }, () => { return Math.ceil(left) >= leftEdge && Math.floor(right) <= rightEdge; } + function clickUntilHidden($el: JQuery) { + if ($el.is(':visible')) { + cy.wrap($el).click().then(clickUntilHidden); + } + } + describe('default', () => { beforeEach(() => { cy.visit('./cypress/fixtures/post-mainnavigation.test.html'); @@ -42,10 +48,13 @@ describe('mainnavigation', { baseUrl: null, includeShadowDom: true }, () => { cy.get('post-mainnavigation[data-hydrated]').as('mainnavigation'); cy.get('@mainnavigation') - .find(':is(a,button):not(post-megadropdown *)') + .find('a:not(post-megadropdown *), post-megadropdown-trigger') .should('have.length', 20) .as('navigationItems'); + cy.get('@navigationItems').first().find('button').as('firstButton'); + cy.get('@navigationItems').last().find('button').as('lastButton'); + // remove smooth scroll to speed up the tests cy.get('@mainnavigation') .shadow() @@ -56,10 +65,10 @@ describe('mainnavigation', { baseUrl: null, includeShadowDom: true }, () => { }); it('should always show the navigation item that is currently focused', () => { - cy.get('@navigationItems').last().as('last').focus(); + cy.get('@navigationItems').last().as('last').find('button').focus(); cy.get('@last').should('be.visible'); - cy.get('@navigationItems').first().as('first').focus(); + cy.get('@navigationItems').first().as('first').find('button').focus(); cy.get('@first').should('be.visible'); }); @@ -79,12 +88,10 @@ describe('mainnavigation', { baseUrl: null, includeShadowDom: true }, () => { it('should click until the last navigation item is visible', () => { cy.get('@rightScroll').should('be.visible'); - cy.get('@navigationItems').each($el => { - if (!isFullyVisible($el)) cy.get('@rightScroll').click(); - cy.wrap($el).then(isFullyVisible).should('be.true'); - }); + cy.get('@rightScroll').then(clickUntilHidden); cy.get('@rightScroll').should('be.hidden'); + cy.get('@navigationItems').last().then(isFullyVisible).should('be.true'); }); it('should scroll continuously until the last navigation item is visible', () => { @@ -100,7 +107,12 @@ describe('mainnavigation', { baseUrl: null, includeShadowDom: true }, () => { cy.get('@rightScroll').should('be.visible'); cy.get('@navigationItems').each($el => { - cy.wrap($el).focus().then(isVisible).should('be.true'); + if ($el.prop('localName') === 'a') { + cy.wrap($el).focus(); + } else { + cy.wrap($el).find('button').focus(); + } + cy.wrap($el).then(isVisible).should('be.true'); }); cy.get('@rightScroll').should('be.hidden'); @@ -120,20 +132,20 @@ describe('mainnavigation', { baseUrl: null, includeShadowDom: true }, () => { // click on the position where the scroll right button was, the last item should not be triggered click(); - cy.get('@navigationItems').last().invoke('attr', 'aria-expanded').should('not.eq', 'true'); + cy.get('@lastButton').invoke('attr', 'aria-expanded').should('not.eq', 'true'); // wait and click again on the position where the scroll right button was, the last item should then be triggered cy.wait(400); click(); - cy.get('@navigationItems').last().invoke('attr', 'aria-expanded').should('eq', 'true'); + cy.get('@lastButton').invoke('attr', 'aria-expanded').should('eq', 'true'); }); it('should show the mega-dropdown at the correct position after scroll', () => { // click until the last navigation item is visible - cy.get('@navigationItems').last().focus(); + cy.get('@lastButton').focus(); // open the last mega-dropdown - cy.get('@navigationItems').last().click(); + cy.get('@lastButton').click({ force: true }); // check the mega-dropdown visible and position cy.get('@mainnavigation') @@ -148,7 +160,7 @@ describe('mainnavigation', { baseUrl: null, includeShadowDom: true }, () => { describe('left scroll', () => { beforeEach(() => { - cy.get('@navigationItems').last().focus(); + cy.get('@lastButton').focus(); cy.get('@navigationItems') .then($options => $options.get().reverse()) @@ -170,15 +182,12 @@ describe('mainnavigation', { baseUrl: null, includeShadowDom: true }, () => { it('should click until the first navigation item is visible', () => { cy.get('@leftScroll').should('be.visible'); - cy.get('@navigationItemsReversed').each($el => { - if (!isFullyVisible($el)) { - cy.get('@leftScroll').click(); - } - cy.wrap($el).then(isFullyVisible).should('be.true'); - }); + cy.get('@leftScroll').then(clickUntilHidden); cy.get('@leftScroll').should('be.hidden'); + cy.get('@navigationItems').first().should('be.visible'); }); + it('should scroll continuously until the first navigation item is visible', () => { const leftScrollPosition = [5, 5]; cy.get('@mainnavigation').trigger('mousedown', ...leftScrollPosition, { button: 0 }); @@ -192,7 +201,12 @@ describe('mainnavigation', { baseUrl: null, includeShadowDom: true }, () => { cy.get('@leftScroll').should('be.visible'); cy.get('@navigationItemsReversed').each($el => { - cy.wrap($el).focus().then(isVisible).should('be.true'); + if ($el.prop('localName') === 'a') { + cy.wrap($el).focus(); + } else { + cy.wrap($el).find('button').focus(); + } + cy.wrap($el).then(isVisible).should('be.true'); }); cy.get('@leftScroll').should('be.hidden'); @@ -211,12 +225,12 @@ describe('mainnavigation', { baseUrl: null, includeShadowDom: true }, () => { // click on the position where the scroll left button was, the first item should not be triggered click(); - cy.get('@navigationItems').first().invoke('attr', 'aria-expanded').should('not.eq', 'true'); + cy.get('@firstButton').invoke('attr', 'aria-expanded').should('not.eq', 'true'); // wait and click again on the position where the scroll left button was, the first item should then be triggered cy.wait(400); click(); - cy.get('@navigationItems').first().invoke('attr', 'aria-expanded').should('eq', 'true'); + cy.get('@firstButton').invoke('attr', 'aria-expanded').should('eq', 'true'); }); }); diff --git a/packages/components/cypress/e2e/megadropdown.cy.ts b/packages/components/cypress/e2e/megadropdown.cy.ts index a6a47b291d..d5fd32a02d 100644 --- a/packages/components/cypress/e2e/megadropdown.cy.ts +++ b/packages/components/cypress/e2e/megadropdown.cy.ts @@ -5,12 +5,10 @@ describe('megadropdown', () => { describe('desktop', () => { beforeEach(() => { cy.viewport(1920, 1080); - cy.getComponents( - MEGADROPDOWN_ID, - 'tests', - 'post-megadropdown', - 'post-megadropdown-trigger', - ); + cy.getComponents(MEGADROPDOWN_ID, 'tests', 'post-megadropdown'); + cy.get('post-megadropdown-trigger[data-hydrated]') + .find('button') + .as('megadropdown-trigger'); cy.get('@megadropdown').find('.close-button').as('close-btn'); cy.get('@megadropdown').find('.megadropdown-container').as('megadropdown-container'); }); @@ -24,22 +22,22 @@ describe('megadropdown', () => { it('should open on trigger click', () => { cy.get('@megadropdown-trigger').should('exist'); - cy.get('@megadropdown-trigger').click(); + cy.get('@megadropdown-trigger').click({ force: true }); cy.get('@megadropdown-container').should('be.visible'); }); it('should show close button', () => { - cy.get('@megadropdown-trigger').click(); + cy.get('@megadropdown-trigger').click({ force: true }); cy.get('@close-btn').should('be.visible'); }); it('should not show back button', () => { - cy.get('@megadropdown-trigger').click(); + cy.get('@megadropdown-trigger').click({ force: true }); cy.get('@megadropdown').find('.back-button').should('not.exist'); }); it('should close on close button click', () => { - cy.get('@megadropdown-trigger').click(); + cy.get('@megadropdown-trigger').click({ force: true }); cy.get('@close-btn').click(); cy.get('@megadropdown-container').should('be.hidden'); }); @@ -48,27 +46,25 @@ describe('megadropdown', () => { describe('mobile', () => { beforeEach(() => { cy.viewport(500, 1200); - cy.getComponents( - MEGADROPDOWN_ID, - 'tests', - 'post-megadropdown', - 'post-megadropdown-trigger', - ); + cy.getComponents(MEGADROPDOWN_ID, 'tests', 'post-megadropdown'); + cy.get('post-megadropdown-trigger[data-hydrated]') + .find('button') + .as('megadropdown-trigger'); cy.get('@megadropdown').find('.back-button').as('back-btn'); }); it('should open on trigger click', () => { - cy.get('@megadropdown-trigger').click(); + cy.get('@megadropdown-trigger').click({ force: true }); cy.get('@megadropdown').should('be.visible'); }); it('should show back button', () => { - cy.get('@megadropdown-trigger').click(); + cy.get('@megadropdown-trigger').click({ force: true }); cy.get('@back-btn').should('be.visible'); }); it('should not show close button', () => { - cy.get('@megadropdown-trigger').click(); + cy.get('@megadropdown-trigger').click({ force: true }); cy.get('@megadropdown').find('.close-button').should('not.exist'); }); }); diff --git a/packages/components/src/components.d.ts b/packages/components/src/components.d.ts index 7b5c2e10b9..aaae3cb042 100644 --- a/packages/components/src/components.d.ts +++ b/packages/components/src/components.d.ts @@ -342,6 +342,11 @@ export namespace Components { "toggle": () => Promise; } interface PostMegadropdownTrigger { + /** + * Sets the trigger state to be active or inactive. + * @default false + */ + "active": boolean; /** * ID of the mega dropdown element that this trigger is linked to. Used to open and close the specified mega dropdown. */ @@ -1369,6 +1374,11 @@ declare namespace LocalJSX { "onPostToggleMegadropdown"?: (event: PostMegadropdownCustomEvent<{ isVisible: boolean; focusParent?: boolean }>) => void; } interface PostMegadropdownTrigger { + /** + * Sets the trigger state to be active or inactive. + * @default false + */ + "active"?: boolean; /** * ID of the mega dropdown element that this trigger is linked to. Used to open and close the specified mega dropdown. */ diff --git a/packages/components/src/components/post-mainnavigation/post-mainnavigation.tsx b/packages/components/src/components/post-mainnavigation/post-mainnavigation.tsx index 132546445c..e4ead9c485 100644 --- a/packages/components/src/components/post-mainnavigation/post-mainnavigation.tsx +++ b/packages/components/src/components/post-mainnavigation/post-mainnavigation.tsx @@ -48,7 +48,6 @@ export class PostMainnavigation { this.validateCaption(); setTimeout(() => { - this.fixLayoutShift(); this.checkScrollability(); }); @@ -92,26 +91,13 @@ export class PostMainnavigation { ), ); - this.fixLayoutShift(); this.checkScrollability(); } private get navigationItems(): HTMLElement[] { - return Array.from(this.host.querySelectorAll(':is(a, button):not(post-megadropdown *)')); - } - - /** - * Hack to fix the layout shift due to bold text on active elements - */ - private fixLayoutShift() { - this.navigationItems - .filter(item => !item.matches(':has(.shown-when-inactive)')) - .forEach(item => { - item.innerHTML = ` - - ${item.innerHTML} - `; - }); + return Array.from( + this.host.querySelectorAll('a:not(post-megadropdown *), post-megadropdown-trigger'), + ); } /** diff --git a/packages/components/src/components/post-megadropdown-trigger/post-megadropdown-trigger.scss b/packages/components/src/components/post-megadropdown-trigger/post-megadropdown-trigger.scss index 3b25025981..018438d4b4 100644 --- a/packages/components/src/components/post-megadropdown-trigger/post-megadropdown-trigger.scss +++ b/packages/components/src/components/post-megadropdown-trigger/post-megadropdown-trigger.scss @@ -1,17 +1,44 @@ @use '@swisspost/design-system-styles/mixins/media'; @use '@swisspost/design-system-styles/components/header/mixins' as header-mx; +@use '@swisspost/design-system-styles/components/header/placeholders' as *; -post-megadropdown-trigger { - width: 100%; +:host { position: relative; + display: inline-block; +} + +button { + @extend %mainnavigation-control; + @include header-mx.nav-item-rotating-chevron; + + // Bellow styles prevent layout shifts caused by the real button toggling between normal and bold font-weights. + > span { + position: relative; + + // The hidden text provides size to the host. + > span[aria-hidden] { + font-weight: 700; + visibility: hidden; + } - button { - @include header-mx.nav-item-rotating-chevron; + // The visible text is positioned on top of the hidden one and adopts the same height and width. + > span:not([aria-hidden]) { + position: absolute; + inset: 0; + } + } + + @include media.only(desktop) { + @include header-mx.active-state { + font-weight: 700; + } + } - @include media.max(desktop) { - post-icon { - transform: rotate(-90deg); - } + @include media.max(desktop) { + post-icon { + margin-inline-start: auto; + transform: rotate(-90deg); + font-size: var(--post-nav-item-icon-size); } } } diff --git a/packages/components/src/components/post-megadropdown-trigger/post-megadropdown-trigger.tsx b/packages/components/src/components/post-megadropdown-trigger/post-megadropdown-trigger.tsx index 0945a0843a..ef1efe47cb 100644 --- a/packages/components/src/components/post-megadropdown-trigger/post-megadropdown-trigger.tsx +++ b/packages/components/src/components/post-megadropdown-trigger/post-megadropdown-trigger.tsx @@ -1,126 +1,123 @@ -import { Component, Element, Prop, h, Host, State, Watch } from '@stencil/core'; +import { Component, Element, Prop, h, Host, Watch, State } from '@stencil/core'; import { version } from '@root/package.json'; -import { checkRequiredAndType, EventFrom } from '@/utils'; +import { checkRequiredAndType, EventFrom, IS_BROWSER } from '@/utils'; @Component({ tag: 'post-megadropdown-trigger', styleUrl: 'post-megadropdown-trigger.scss', - shadow: false, + shadow: true, }) export class PostMegadropdownTrigger { - /** - * ID of the mega dropdown element that this trigger is linked to. Used to open and close the specified mega dropdown. - */ - @Prop({ reflect: true }) for!: string; - @Element() host: HTMLPostMegadropdownTriggerElement; - /** - * Manages the accessibility attribute `aria-expanded` to indicate whether the associated mega dropdown is expanded or collapsed. - */ - @State() ariaExpanded: boolean = false; + private mutationObserver = new MutationObserver(this.cloneSlottedButton.bind(this)); + private interactiveButton: HTMLButtonElement; - /** - * Reference to the slotted button within the trigger, if present. - * Used to manage click and key events for mega dropdown control. - */ - private slottedButton: HTMLButtonElement | null = null; + @State() private isMegadropdownExpanded: boolean = false; + @State() private slottedContent: string = null; /** - * Tracks whether this trigger's dropdown was expanded before a state change. - * Used to determine if this trigger should handle focus when its dropdown closes. + * Sets the trigger state to be active or inactive. */ - private wasExpanded: boolean = false; + @Prop({ reflect: true }) active: boolean = false; /** - * Watch for changes to the `for` property to validate its type and ensure it is a string. - * @param forValue - The new value of the `for` property. + * ID of the mega dropdown element that this trigger is linked to. Used to open and close the specified mega dropdown. */ + @Prop({ reflect: true }) for!: string; + @Watch('for') - validateControlFor() { + validateFor() { checkRequiredAndType(this, 'for', 'string'); } - private get megadropdown(): HTMLPostMegadropdownElement | null { - const ref = document.getElementById(this.for); - return ref && ref.localName === 'post-megadropdown' - ? (ref as HTMLPostMegadropdownElement) - : null; + constructor() { + this.onMegadropdownToggled = this.onMegadropdownToggled.bind(this); } - private handleToggle() { - if (this.megadropdown) { - this.megadropdown.toggle(); - } else { - console.warn(`No post-megadropdown found with ID: ${this.for}`); - } + connectedCallback() { + this.mutationObserver.observe(this.host, { childList: true, subtree: true }); } - private handleKeyDown = (event: KeyboardEvent) => { - if (event.key === 'Enter' || event.key === ' ') { - event.preventDefault(); - this.handleToggle(); - if (this.megadropdown && !this.ariaExpanded) { - setTimeout(() => this.megadropdown.focusFirst(), 100); - } - } - }; + componentWillLoad() { + this.cloneSlottedButton(); + } - @EventFrom('post-megadropdown', { ignoreNestedComponents: false }) - private handleToggleMegadropdown( - event: CustomEvent<{ isVisible: boolean; focusParent: boolean }>, - ) { - if ((event.target as HTMLPostMegadropdownElement).id === this.for) { - this.ariaExpanded = event.detail.isVisible; + componentDidLoad() { + this.validateFor(); - // Focus on the trigger parent of the dropdown after it's closed if the close button had been clicked - if (this.wasExpanded && !this.ariaExpanded && event.detail.focusParent) { - setTimeout(() => { - this.slottedButton?.focus(); - }, 100); - } - this.wasExpanded = this.ariaExpanded; - - if (this.slottedButton) { - this.slottedButton.setAttribute('aria-expanded', this.ariaExpanded.toString()); - } - } + // Check if the mega dropdown attached to the trigger is expanded or not + if (IS_BROWSER) document.addEventListener('postToggleMegadropdown', this.onMegadropdownToggled); } - constructor() { - this.handleToggleMegadropdown = this.handleToggleMegadropdown.bind(this); + disconnectedCallback() { + document.removeEventListener('postToggleMegadropdown', this.onMegadropdownToggled); } - componentDidLoad() { - this.validateControlFor(); + private cloneSlottedButton() { + this.slottedContent = this.host.innerHTML; + } - // Check if the mega dropdown attached to the trigger is expanded or not - document.addEventListener('postToggleMegadropdown', this.handleToggleMegadropdown); - - this.slottedButton = this.host.querySelector('button'); - if (this.slottedButton) { - this.slottedButton.setAttribute('aria-haspopup', 'menu'); - this.slottedButton.addEventListener('click', () => { - this.handleToggle(); - }); - this.slottedButton.addEventListener('keydown', this.handleKeyDown); - } else { - console.warn('No button found within post-megadropdown-trigger'); + private get megadropdown(): HTMLPostMegadropdownElement | null { + const ref = document.getElementById(this.for); + + if (ref?.localName !== 'post-megadropdown') { + console.warn(`No post-megadropdown found with ID: ${this.for}`); + return null; } + + return ref as HTMLPostMegadropdownElement; } - disconnectedCallback() { - document.removeEventListener('postToggleMegadropdown', this.handleToggleMegadropdown); + private onClick() { + this.megadropdown?.toggle(); + } + + private onKeyDown(event: KeyboardEvent) { + if (event.key !== 'Enter' && event.key !== ' ') return; + + const megadropdown = this.megadropdown; + if (!megadropdown) return; + + event.preventDefault(); + this.megadropdown.toggle(); + } + + @EventFrom('post-megadropdown', { ignoreNestedComponents: false }) + private onMegadropdownToggled(event: CustomEvent<{ isVisible: boolean; focusParent: boolean }>) { + if ((event.target as HTMLPostMegadropdownElement).id === this.for) { + const wasMegadropdownExpanded = this.isMegadropdownExpanded; + this.isMegadropdownExpanded = event.detail.isVisible; + + const haveBeenClosed = wasMegadropdownExpanded && !this.isMegadropdownExpanded; + if (!haveBeenClosed || !event.detail.focusParent) return; + + // Focus on the trigger parent of the dropdown after it's closed if the close button had been clicked + setTimeout(() => { + this.interactiveButton.focus(); + }, 100); + } } render() { return ( - - ); diff --git a/packages/components/src/components/post-megadropdown-trigger/readme.md b/packages/components/src/components/post-megadropdown-trigger/readme.md index 2cc767159f..d62f2daafe 100644 --- a/packages/components/src/components/post-megadropdown-trigger/readme.md +++ b/packages/components/src/components/post-megadropdown-trigger/readme.md @@ -5,9 +5,10 @@ ## Properties -| 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` | +| Property | Attribute | Description | Type | Default | +| ------------------ | --------- | ------------------------------------------------------------------------------------------------------------------- | --------- | ----------- | +| `active` | `active` | Sets the trigger state to be active or inactive. | `boolean` | `false` | +| `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` | ## Dependencies diff --git a/packages/components/src/components/post-megadropdown/post-megadropdown.tsx b/packages/components/src/components/post-megadropdown/post-megadropdown.tsx index 4d2102c25b..f72b4c2883 100644 --- a/packages/components/src/components/post-megadropdown/post-megadropdown.tsx +++ b/packages/components/src/components/post-megadropdown/post-megadropdown.tsx @@ -72,9 +72,7 @@ export class PostMegadropdown { private get megadropdownTrigger(): Element | null { const hostId = this.host.getAttribute('id'); - return hostId - ? document.querySelector(`post-megadropdown-trigger[for="${hostId}"] > button`) - : null; + return hostId ? document.querySelector(`post-megadropdown-trigger[for="${hostId}"]`) : null; } /** @@ -132,8 +130,8 @@ export class PostMegadropdown { @Method() async show() { if (this.device !== 'desktop') { - const triggerLabel = this.megadropdownTrigger?.querySelector('.nav-el-active'); - if (triggerLabel) this.megadropdownTitle = triggerLabel.innerHTML; + const trigger = this.megadropdownTrigger; + if (trigger) this.megadropdownTitle = trigger.innerHTML; } if (PostMegadropdown.activeDropdown && PostMegadropdown.activeDropdown !== this) { @@ -299,13 +297,7 @@ export class PostMegadropdown { */ private setTriggerActive(isActive: boolean) { const trigger = this.megadropdownTrigger; - if (!trigger) return; - - if (isActive) { - trigger.classList.add('active'); - } else { - trigger.classList.remove('active'); - } + if (trigger) trigger.setAttribute('active', isActive.toString()); } /** diff --git a/packages/components/src/components/post-megadropdown/readme.md b/packages/components/src/components/post-megadropdown/readme.md index 9cada31d3b..9c4ae2c4e6 100644 --- a/packages/components/src/components/post-megadropdown/readme.md +++ b/packages/components/src/components/post-megadropdown/readme.md @@ -68,6 +68,13 @@ Type: `Promise` +## Slots + +| Slot | Description | +| ----------- | ------------------------- | +| `"default"` | Slot for placing content. | + + ## Dependencies ### Depends on diff --git a/packages/styles/src/components/header/_mixins.scss b/packages/styles/src/components/header/_mixins.scss index ce8ed87abd..9075b755ba 100644 --- a/packages/styles/src/components/header/_mixins.scss +++ b/packages/styles/src/components/header/_mixins.scss @@ -2,6 +2,7 @@ @use '../../mixins/button'; @use '../../mixins/media'; @use '../../mixins/utilities'; +@use '../../mixins/icons'; @use '../../tokens/components'; @use '../../tokens/helpers'; @@ -18,8 +19,14 @@ /** selects active nav items */ -@mixin active-state { - &:is(.active, [aria-current], [aria-pressed='true'], [aria-expanded='true']) { +@mixin active-state($with-class: true) { + $selectors: '[aria-current], [aria-pressed="true"], [aria-expanded="true"]'; + + @if $with-class { + $selectors: '#{$selectors}, .active'; + } + + &:is(#{$selectors}) { @content; } } @@ -59,6 +66,7 @@ @mixin nav-item-block-layout { border-radius: 0; padding: 0 0.75rem; + line-height: 1.5; &:hover { @include button.button-variant-def('hover', 'primary'); @@ -86,14 +94,10 @@ inset-block-end: 0; height: 1px; } - - > post-icon:where(:last-child) { - margin-inline-start: auto; - } } @include media.only(mobile) { - padding: 0.5rem 0.75rem calc(0.5rem - 1px) 0.5rem; + padding: calc(0.5rem + 1.5px) 0.75rem calc(0.5rem + 1.5px - 1px) 0.5rem; gap: 0.5rem; font-size: 0.875rem; @@ -135,51 +139,13 @@ transition: transform 250ms cubic-bezier(0.4, 0, 0.2, 1); } - @include active-state { + @include active-state($with-class: false) { post-icon { transform: rotate(180deg); } } } -@mixin nav-item-bold-hover { - > .shown-when-inactive { - display: none; - } - - @include media.only(desktop) { - &:has(> .shown-when-inactive) { - font-weight: 700; - } - - > .shown-when-inactive { - all: inherit; - font-weight: 400; - position: absolute; - inset: 0; - } - - @include active-state { - > .shown-when-inactive { - display: none; - } - } - - &:where(post-megadropdown-trigger > *) { - display: grid; - grid-template-columns: 1fr auto; - - > post-icon { - z-index: 1; - } - - > .shown-when-inactive > post-icon { - visibility: hidden; - } - } - } -} - /** deprecated */ diff --git a/packages/styles/src/components/header/_placeholders.scss b/packages/styles/src/components/header/_placeholders.scss index 724d641ec1..d91a9424eb 100644 --- a/packages/styles/src/components/header/_placeholders.scss +++ b/packages/styles/src/components/header/_placeholders.scss @@ -20,3 +20,9 @@ %header-control { @include nav-item-base; } + +%mainnavigation-control { + @extend %header-control; + @include nav-item-block-layout; + height: 100%; +} diff --git a/packages/styles/src/components/header/_post-header.scss b/packages/styles/src/components/header/_post-header.scss index aced27a9ea..a300c9c8a3 100644 --- a/packages/styles/src/components/header/_post-header.scss +++ b/packages/styles/src/components/header/_post-header.scss @@ -68,7 +68,7 @@ post-header { } // meta navigation on desktop - > ul[slot='meta-navigation'] { + > ul[slot='global-nav-secondary'] { @include media.max(desktop) { &:where(ul) { flex-direction: column; diff --git a/packages/styles/src/components/header/_post-mainnavigation.scss b/packages/styles/src/components/header/_post-mainnavigation.scss index d22a8b3427..fcdcc83608 100644 --- a/packages/styles/src/components/header/_post-mainnavigation.scss +++ b/packages/styles/src/components/header/_post-mainnavigation.scss @@ -13,18 +13,14 @@ post-mainnavigation { flex-wrap: nowrap; } - > *, - post-megadropdown-trigger > * { + > * { @include interactive-elements { - @extend %header-control; - height: 100%; - - @include nav-item-block-layout; - @include nav-item-bold-hover; - - @include media.only(desktop) { - @include button.button-variant-def('enabled', 'secondary'); - } + @extend %mainnavigation-control; } } + + post-megadropdown-trigger { + height: 100%; + width: 100%; + } } diff --git a/packages/styles/src/mixins/_button.scss b/packages/styles/src/mixins/_button.scss index e760fefa6e..7b8fe7cd24 100644 --- a/packages/styles/src/mixins/_button.scss +++ b/packages/styles/src/mixins/_button.scss @@ -47,6 +47,7 @@ font-weight: tokens.get('button-label-font-weight', components.$post-button); text-decoration: none; white-space: nowrap; // Long content should never break in buttons + cursor: pointer; &:hover { text-decoration: none;