diff --git a/.changeset/few-ways-beam.md b/.changeset/few-ways-beam.md new file mode 100644 index 0000000000..b0026452fd --- /dev/null +++ b/.changeset/few-ways-beam.md @@ -0,0 +1,6 @@ +--- +'@swisspost/design-system-documentation': major +'@swisspost/design-system-components': major +--- + +Renamed the "dropdown" variant to "menu" for the `post-language-switch` and `post-language-option` components. diff --git a/packages/components/cypress/e2e/language-option.cy.ts b/packages/components/cypress/e2e/language-option.cy.ts index a3ad3c5b74..72ce65db0b 100644 --- a/packages/components/cypress/e2e/language-option.cy.ts +++ b/packages/components/cypress/e2e/language-option.cy.ts @@ -4,7 +4,7 @@ describe('language-option', () => { describe('button', () => { beforeEach(() => { cy.getComponent('language-option', LANGUAGE_OPTION_ID); - cy.get('@language-option').shadow().find('button').as('button'); + cy.get('@language-option').find('button').as('button'); }); it('should render', () => { @@ -12,7 +12,7 @@ describe('language-option', () => { }); it('should not render an anchor', () => { - cy.get('@language-option').shadow().find('a').should('not.exist'); + cy.get('@language-option').find('a').should('not.exist'); }); it('should render a button with correct properties', () => { @@ -35,7 +35,7 @@ describe('language-option', () => { describe('anchor', () => { beforeEach(() => { cy.getComponent('language-option', LANGUAGE_OPTION_ID, 'anchor'); - cy.get('@language-option').shadow().find('a').as('anchor'); + cy.get('@language-option').find('a').as('anchor'); }); it('should render', () => { @@ -43,7 +43,7 @@ describe('language-option', () => { }); it('should not render a button', () => { - cy.get('@language-option').shadow().find('button').should('not.exist'); + cy.get('@language-option').find('button').should('not.exist'); }); it('should render an anchor', () => { diff --git a/packages/components/src/components.d.ts b/packages/components/src/components.d.ts index afdaeb0109..76172f1f38 100644 --- a/packages/components/src/components.d.ts +++ b/packages/components/src/components.d.ts @@ -311,10 +311,6 @@ export namespace Components { * Hides the popover menu and restores focus to the previously focused element. */ "hide": () => Promise; - /** - * Whether or not the post-menu is used within a post-language-switch component as the children structure is not the same. - */ - "isLanguageSwitch": boolean; /** * Defines the placement of the tooltip according to the floating-ui options available at https://floating-ui.com/docs/computePosition#placement. Tooltips are automatically flipped to the opposite side if there is not enough available space and are shifted towards the viewport if they would overlap edge boundaries. */ @@ -1155,10 +1151,6 @@ declare namespace LocalJSX { "for": string; } interface PostMenu { - /** - * Whether or not the post-menu is used within a post-language-switch component as the children structure is not the same. - */ - "isLanguageSwitch"?: boolean; /** * Emits when the menu is shown or hidden. The event payload is a boolean: `true` when the menu was opened, `false` when it was closed. */ diff --git a/packages/components/src/components/post-header/post-header.tsx b/packages/components/src/components/post-header/post-header.tsx index 9e04361563..9f888e3c1c 100644 --- a/packages/components/src/components/post-header/post-header.tsx +++ b/packages/components/src/components/post-header/post-header.tsx @@ -123,7 +123,7 @@ export class PostHeader { } private switchLanguageSwitchMode() { - const variant: SwitchVariant = this.device === 'desktop' ? 'dropdown' : 'list'; + const variant: SwitchVariant = this.device === 'desktop' ? 'menu' : 'list'; this.host.querySelector('post-language-switch')?.setAttribute('variant', variant); } diff --git a/packages/components/src/components/post-language-option/post-language-option.scss b/packages/components/src/components/post-language-option/post-language-option.scss index 3ebe463d28..7496c54cfb 100644 --- a/packages/components/src/components/post-language-option/post-language-option.scss +++ b/packages/components/src/components/post-language-option/post-language-option.scss @@ -1,83 +1,85 @@ @use '@swisspost/design-system-styles/core' as post; -:host { +post-language-option { display: inline-block; -} - -:host([variant='dropdown']) { - width: 100%; -} -button { - @include post.reset-button; -} + button { + @include post.reset-button; + } -a { - color: inherit; - text-decoration: none; -} + a { + color: inherit; + text-decoration: none; + } -:is(a, button) { - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - height: 100%; - width: 100%; - padding: var(--post-language-option-padding); + :is(a, button) { + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + height: 100%; + width: 100%; + padding: var(--post-language-option-padding); + } } -.post-language-option-list { - @include post.focus-style; - border-radius: 2px; - width: 40px; - height: 40px; +post-language-option:where([variant='list']) { + :is(a, button) { + @include post.focus-style; + border-radius: 2px; + width: 40px; + height: 40px; - &:hover { - color: #504f4b; - } + &:hover { + color: #504f4b; + } - &[aria-current='true'], - &[aria-current='page'] { - background-color: #050400; - color: #fff; + &[aria-current='true'], + &[aria-current='page'] { + background-color: #050400; + color: #fff; - &:hover { - background-color: #504f4b; + &:hover { + background-color: #504f4b; + } } } } -.post-language-option-dropdown { - padding-block: 13px; - padding-inline: 24px; - box-sizing: border-box; - position: relative; +post-language-option:where([variant='menu']) { + width: 100%; - &[aria-current='true'], - &[aria-current='page'] { - &::after { - content: ''; - left: -2px; - height: 3px; - background-color: #050400; - width: calc(100% + 4px); - display: block; - position: absolute; - bottom: 3px; - } + :is(a, button) { + padding-block: 13px; + padding-inline: 24px; + box-sizing: border-box; + position: relative; - &:focus::after { - width: calc(100% - 5px); - left: 2px; - } + &[aria-current='true'], + &[aria-current='page'] { + &::after { + content: ''; + left: -2px; + height: 3px; + background-color: #050400; + width: calc(100% + 4px); + display: block; + position: absolute; + bottom: 3px; + } + + &:focus::after { + width: calc(100% - 5px); + left: 2px; + } - &:hover::after { - background-color: #504f4b; + &:hover::after { + background-color: #504f4b; + } } - } - &:hover { - color: #504f4b; + &:hover { + color: #504f4b; + } } } diff --git a/packages/components/src/components/post-language-option/post-language-option.tsx b/packages/components/src/components/post-language-option/post-language-option.tsx index 6e8bd502a1..d4dd6b9c26 100644 --- a/packages/components/src/components/post-language-option/post-language-option.tsx +++ b/packages/components/src/components/post-language-option/post-language-option.tsx @@ -19,7 +19,6 @@ import { SwitchVariant } from '../post-language-switch/switch-variants'; @Component({ tag: 'post-language-option', styleUrl: 'post-language-option.scss', - shadow: true, }) export class PostLanguageOption { @Element() host: HTMLPostLanguageOptionElement; @@ -125,10 +124,9 @@ export class PostLanguageOption { const lang = this.code.toLowerCase(); return ( - + {this.url ? ( ) : ( - -
- -
+ +
); diff --git a/packages/components/src/components/post-language-switch/readme.md b/packages/components/src/components/post-language-switch/readme.md index 8beb610dfa..483f8d0e4a 100644 --- a/packages/components/src/components/post-language-switch/readme.md +++ b/packages/components/src/components/post-language-switch/readme.md @@ -5,11 +5,11 @@ ## Properties -| Property | Attribute | Description | Type | Default | -| ------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | ----------- | -| `caption` | `caption` | A title for the list of language options | `string` | `undefined` | -| `description` | `description` | A descriptive text for the list of language options | `string` | `undefined` | -| `variant` | `variant` | Variant that determines the rendering of the language switch either as a list (used on mobile in the header) or a dropdown (used on desktop in the header) | `"dropdown" \| "list"` | `'list'` | +| Property | Attribute | Description | Type | Default | +| ------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | ----------- | +| `caption` | `caption` | A title for the list of language options | `string` | `undefined` | +| `description` | `description` | A descriptive text for the list of language options | `string` | `undefined` | +| `variant` | `variant` | Variant that determines the rendering of the language switch either as a list (used on mobile in the header) or a dropdown (used on desktop in the header) | `"list" \| "menu"` | `'list'` | ## Dependencies diff --git a/packages/components/src/components/post-language-switch/switch-variants.ts b/packages/components/src/components/post-language-switch/switch-variants.ts index 2473e44f8f..7220d28a4e 100644 --- a/packages/components/src/components/post-language-switch/switch-variants.ts +++ b/packages/components/src/components/post-language-switch/switch-variants.ts @@ -1,3 +1,3 @@ -export const SWITCH_VARIANTS = ['list', 'dropdown'] as const; +export const SWITCH_VARIANTS = ['list', 'menu'] as const; export type SwitchVariant = (typeof SWITCH_VARIANTS)[number]; diff --git a/packages/components/src/components/post-menu-item/post-menu-item.scss b/packages/components/src/components/post-menu-item/post-menu-item.scss deleted file mode 100644 index f09d32524a..0000000000 --- a/packages/components/src/components/post-menu-item/post-menu-item.scss +++ /dev/null @@ -1,3 +0,0 @@ -:host { - display: block; -} \ No newline at end of file diff --git a/packages/components/src/components/post-menu-item/post-menu-item.tsx b/packages/components/src/components/post-menu-item/post-menu-item.tsx index b4c7de0469..5f6685d5d9 100644 --- a/packages/components/src/components/post-menu-item/post-menu-item.tsx +++ b/packages/components/src/components/post-menu-item/post-menu-item.tsx @@ -1,14 +1,10 @@ -import { Component, h, Element, Host } from '@stencil/core'; +import { Component, h, Host } from '@stencil/core'; import { version } from '@root/package.json'; @Component({ tag: 'post-menu-item', - shadow: true, - styleUrl: 'post-menu-item.scss', }) export class PostMenuItem { - @Element() host: HTMLPostMenuItemElement; - render() { return ( diff --git a/packages/components/src/components/post-menu/post-menu.scss b/packages/components/src/components/post-menu/post-menu.scss index 95b15284b7..d207ad3f3d 100644 --- a/packages/components/src/components/post-menu/post-menu.scss +++ b/packages/components/src/components/post-menu/post-menu.scss @@ -8,4 +8,9 @@ post-popovercontainer { padding: var(--post-menu-padding); background-color: var(--post-menu-bg, #ffffff); border-color: var(--post-menu-bg, #ffffff); -} \ No newline at end of file +} + +.popover-container { + display: flex; + flex-direction: column; +} diff --git a/packages/components/src/components/post-menu/post-menu.tsx b/packages/components/src/components/post-menu/post-menu.tsx index 8e6edd5cfb..775c831172 100644 --- a/packages/components/src/components/post-menu/post-menu.tsx +++ b/packages/components/src/components/post-menu/post-menu.tsx @@ -11,7 +11,7 @@ import { } from '@stencil/core'; import { Placement } from '@floating-ui/dom'; import { version } from '@root/package.json'; -import { isFocusable } from '@/utils/is-focusable'; +import { getFocusableChildren } from '@/utils/get-focusable-children'; import { getRoot } from '@/utils'; @Component({ @@ -43,11 +43,6 @@ export class PostMenu { */ @Prop() readonly placement?: Placement = 'bottom'; - /** - * Whether or not the post-menu is used within a post-language-switch component as the children structure is not the same. - */ - @Prop() isLanguageSwitch: boolean = false; - /** * Holds the current visibility state of the menu. * This state is internally managed to track whether the menu is open (`true`) or closed (`false`), @@ -151,11 +146,10 @@ export class PostMenu { return; } - const currentFocusedElement = this.isLanguageSwitch - ? (document.activeElement.shadowRoot.querySelector('button') as HTMLElement) - : (this.root.activeElement as HTMLElement); // Use root's active element - - let currentIndex = menuItems.findIndex(el => el === currentFocusedElement); + let currentIndex = menuItems.findIndex(el => { + // Check if the item is currently focused within its rendered scope (document or shadow root) + return el === getRoot(el).activeElement; + }); switch (e.key) { case this.KEYCODES.UP: @@ -191,24 +185,16 @@ export class PostMenu { private getSlottedItems(): Element[] { const slot = this.host.shadowRoot.querySelector('slot'); const slottedElements = slot ? slot.assignedElements() : []; - let menuItems; - if (this.isLanguageSwitch) { - menuItems = Array.from(document.querySelectorAll('post-language-option[variant="dropdown"]')) - .map(el => el.shadowRoot.querySelector('button')) - .flat(); - } else { - menuItems = slottedElements - .filter(el => el.tagName === 'POST-MENU-ITEM') - .map(el => { - const slot = el.shadowRoot.querySelector('slot'); - const assignedElements = slot ? slot.assignedElements() : []; - return assignedElements.filter(isFocusable); - }) - .flat(); - } - - return menuItems; + return ( + slottedElements + // If the element is a slot, get the assigned elements + .flatMap(el => (el instanceof HTMLSlotElement ? el.assignedElements() : el)) + // Filter out elements that have a 'menuitem' role + .filter(el => el.getAttribute('role') === 'menuitem') + // For each menu item, get any focusable children (e.g., buttons, links) + .flatMap(el => Array.from(getFocusableChildren(el))) + ); } render() { diff --git a/packages/components/src/components/post-menu/readme.md b/packages/components/src/components/post-menu/readme.md index 4ec09714b4..3361253421 100644 --- a/packages/components/src/components/post-menu/readme.md +++ b/packages/components/src/components/post-menu/readme.md @@ -7,10 +7,9 @@ ## Properties -| Property | Attribute | Description | Type | Default | -| ------------------ | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | -| `isLanguageSwitch` | `is-language-switch` | Whether or not the post-menu is used within a post-language-switch component as the children structure is not the same. | `boolean` | `false` | -| `placement` | `placement` | Defines the placement of the tooltip according to the floating-ui options available at https://floating-ui.com/docs/computePosition#placement. Tooltips are automatically flipped to the opposite side if there is not enough available space and are shifted towards the viewport if they would overlap edge boundaries. | `"bottom" \| "bottom-end" \| "bottom-start" \| "left" \| "left-end" \| "left-start" \| "right" \| "right-end" \| "right-start" \| "top" \| "top-end" \| "top-start"` | `'bottom'` | +| Property | Attribute | Description | Type | Default | +| ----------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | +| `placement` | `placement` | Defines the placement of the tooltip according to the floating-ui options available at https://floating-ui.com/docs/computePosition#placement. Tooltips are automatically flipped to the opposite side if there is not enough available space and are shifted towards the viewport if they would overlap edge boundaries. | `"bottom" \| "bottom-end" \| "bottom-start" \| "left" \| "left-end" \| "left-start" \| "right" \| "right-end" \| "right-start" \| "top" \| "top-end" \| "top-start"` | `'bottom'` | ## Events diff --git a/packages/components/src/utils/is-focusable.ts b/packages/components/src/utils/get-focusable-children.ts similarity index 76% rename from packages/components/src/utils/is-focusable.ts rename to packages/components/src/utils/get-focusable-children.ts index 66aa7d74dc..68b0679ab6 100644 --- a/packages/components/src/utils/is-focusable.ts +++ b/packages/components/src/utils/get-focusable-children.ts @@ -23,6 +23,6 @@ const focusDisablingSelector = `:where(${[ 'details:not([open]) > *:not(details > summary:first-of-type) *', ].join(',')})`; -export const isFocusable = (element: Element) => { - return element?.matches(focusableSelector) && !element?.matches(focusDisablingSelector); +export const getFocusableChildren = (element: Element): NodeListOf => { + return element.querySelectorAll(`& > ${focusableSelector}:not(${focusDisablingSelector})`); }; diff --git a/packages/components/src/utils/get-root.ts b/packages/components/src/utils/get-root.ts index 0c84953d8c..ad33d956ab 100644 --- a/packages/components/src/utils/get-root.ts +++ b/packages/components/src/utils/get-root.ts @@ -1,4 +1,4 @@ -export function getRoot(element: HTMLElement): Document | ShadowRoot { +export function getRoot(element: Element): Document | ShadowRoot { const root = element.getRootNode(); if (root instanceof Document || root instanceof ShadowRoot) { diff --git a/packages/documentation/src/stories/components/language-switch/language-switch.stories.ts b/packages/documentation/src/stories/components/language-switch/language-switch.stories.ts index e3cb30133e..0b71cac4e9 100644 --- a/packages/documentation/src/stories/components/language-switch/language-switch.stories.ts +++ b/packages/documentation/src/stories/components/language-switch/language-switch.stories.ts @@ -65,7 +65,7 @@ function renderLanguageSwitchAsLinks(args: Partial DE , ) => { - return schemes( - () => html` -
- ${meta.render?.({ ...context.args }, context)} -
- `, - ); + return schemes(() => html` ${meta.render?.({ ...context.args }, context)} `); }, };