From 6277a728e4a615514c7226fba1a41a4ac2d4d650 Mon Sep 17 00:00:00 2001 From: leagrdv Date: Fri, 13 Dec 2024 15:54:23 +0100 Subject: [PATCH 1/4] fix(components): update style of language switch and fix keyboard navigation --- .changeset/moody-coats-kneel.md | 5 ++ packages/components/src/components.d.ts | 8 +++ .../post-language-option.scss | 23 +++++++- .../post-language-switch.scss | 4 ++ .../post-language-switch.tsx | 2 +- .../src/components/post-menu/post-menu.tsx | 52 ++++++++++++++----- .../src/components/post-menu/readme.md | 7 +-- .../language-switch.stories.ts | 2 +- 8 files changed, 83 insertions(+), 20 deletions(-) create mode 100644 .changeset/moody-coats-kneel.md diff --git a/.changeset/moody-coats-kneel.md b/.changeset/moody-coats-kneel.md new file mode 100644 index 0000000000..53f3529886 --- /dev/null +++ b/.changeset/moody-coats-kneel.md @@ -0,0 +1,5 @@ +--- +'@swisspost/design-system-components': patch +--- + +Updated style and keyboard navigation of `post-language-switch`. diff --git a/packages/components/src/components.d.ts b/packages/components/src/components.d.ts index 075f07063b..42366d63dc 100644 --- a/packages/components/src/components.d.ts +++ b/packages/components/src/components.d.ts @@ -301,6 +301,10 @@ 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. */ @@ -1124,6 +1128,10 @@ 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-language-option/post-language-option.scss b/packages/components/src/components/post-language-option/post-language-option.scss index 314839d3d8..3ebe463d28 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 @@ -2,7 +2,10 @@ :host { display: inline-block; - width: fit-content; +} + +:host([variant='dropdown']) { + width: 100%; } button { @@ -30,10 +33,18 @@ a { width: 40px; height: 40px; + &:hover { + color: #504f4b; + } + &[aria-current='true'], &[aria-current='page'] { background-color: #050400; color: #fff; + + &:hover { + background-color: #504f4b; + } } } @@ -49,7 +60,7 @@ a { content: ''; left: -2px; height: 3px; - background-color: #504f4b; + background-color: #050400; width: calc(100% + 4px); display: block; position: absolute; @@ -60,5 +71,13 @@ a { width: calc(100% - 5px); left: 2px; } + + &:hover::after { + background-color: #504f4b; + } + } + + &:hover { + color: #504f4b; } } diff --git a/packages/components/src/components/post-language-switch/post-language-switch.scss b/packages/components/src/components/post-language-switch/post-language-switch.scss index f0cc236912..62e6a7730b 100644 --- a/packages/components/src/components/post-language-switch/post-language-switch.scss +++ b/packages/components/src/components/post-language-switch/post-language-switch.scss @@ -13,6 +13,10 @@ tokens.$default-map: components.$post-button; .post-language-switch-dropdown-container { display: flex; flex-direction: column; + + > * { + width: 100%; + } } .post-language-switch-trigger { diff --git a/packages/components/src/components/post-language-switch/post-language-switch.tsx b/packages/components/src/components/post-language-switch/post-language-switch.tsx index c26cf8b7f8..91f8512f1d 100644 --- a/packages/components/src/components/post-language-switch/post-language-switch.tsx +++ b/packages/components/src/components/post-language-switch/post-language-switch.tsx @@ -133,7 +133,7 @@ export class PostLanguageSwitch { - +
diff --git a/packages/components/src/components/post-menu/post-menu.tsx b/packages/components/src/components/post-menu/post-menu.tsx index dd5c90f3bf..ce8ce9d2eb 100644 --- a/packages/components/src/components/post-menu/post-menu.tsx +++ b/packages/components/src/components/post-menu/post-menu.tsx @@ -1,4 +1,14 @@ -import { Component, Element, Event, EventEmitter, h, Host, Method, Prop, State } from '@stencil/core'; +import { + Component, + Element, + Event, + EventEmitter, + h, + Host, + Method, + Prop, + State, +} from '@stencil/core'; import { Placement } from '@floating-ui/dom'; import { version } from '@root/package.json'; import { isFocusable } from '@/utils/is-focusable'; @@ -20,7 +30,7 @@ export class PostMenu { TAB: 'Tab', HOME: 'Home', END: 'End', - ESCAPE: 'Escape' + ESCAPE: 'Escape', }; @Element() host: HTMLPostMenuElement; @@ -32,6 +42,11 @@ 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`), @@ -72,7 +87,7 @@ export class PostMenu { /** * Displays the popover menu, focusing the first menu item. - * + * * @param target - The HTML element relative to which the popover menu should be displayed. */ @Method() @@ -127,11 +142,15 @@ export class PostMenu { private controlKeyDownHandler(e: KeyboardEvent) { const menuItems = this.getSlottedItems(); + if (!menuItems.length) { return; } - const currentFocusedElement = document.activeElement as HTMLElement; + const currentFocusedElement = this.isLanguageSwitch + ? (document.activeElement.shadowRoot.querySelector('button') as HTMLElement) + : (document.activeElement as HTMLElement); + let currentIndex = menuItems.findIndex(el => el === currentFocusedElement); switch (e.key) { @@ -165,18 +184,25 @@ export class PostMenu { } } - private getSlottedItems() { + private getSlottedItems(): Element[] { const slot = this.host.shadowRoot.querySelector('slot'); const slottedElements = slot ? slot.assignedElements() : []; + let menuItems; - const 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(); + 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; } diff --git a/packages/components/src/components/post-menu/readme.md b/packages/components/src/components/post-menu/readme.md index 2372e47c18..70b32d65fc 100644 --- a/packages/components/src/components/post-menu/readme.md +++ b/packages/components/src/components/post-menu/readme.md @@ -7,9 +7,10 @@ ## Properties -| 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'` | +| 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'` | ## Events diff --git a/packages/documentation/src/stories/components/header/language-switch/language-switch.stories.ts b/packages/documentation/src/stories/components/header/language-switch/language-switch.stories.ts index 013f6d01d9..8c8858c0f6 100644 --- a/packages/documentation/src/stories/components/header/language-switch/language-switch.stories.ts +++ b/packages/documentation/src/stories/components/header/language-switch/language-switch.stories.ts @@ -4,7 +4,7 @@ import { MetaComponent } from '@root/types'; const meta: MetaComponent = { id: 'decbb10c-2b39-4f47-b67d-337d8111a3ae', - title: 'Components/Header/Language Switch', + title: 'Raw Components/Language Switch', tags: ['package:WebComponents'], component: 'post-language-switch', render: renderLanguageSwitch, From f5054b1b639b6be1f6f5eb48300f10fca9cf66bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aliz=C3=A9=20Debray?= <33580481+alizedebray@users.noreply.github.com> Date: Thu, 19 Dec 2024 18:57:20 +0100 Subject: [PATCH 2/4] fix(components): fix the language switch keyboard navigation (#4340) The changes to the post-menu are part of the PR #4337 that should be merge before this one. :information_source: **Only the commit https://github.com/swisspost/design-system/pull/4340/commits/b899725c21332a535e2f7640689f0bdf3d98fa28 needs to be reviewed in this PR.** --------- Co-authored-by: Lea --- .changeset/few-ways-beam.md | 6 + .../cypress/e2e/language-option.cy.ts | 8 +- packages/components/src/components.d.ts | 8 -- .../components/post-header/post-header.tsx | 2 +- .../post-language-option.scss | 124 +++++++++--------- .../post-language-option.tsx | 5 +- .../components/post-language-option/readme.md | 14 +- .../post-language-switch.tsx | 10 +- .../components/post-language-switch/readme.md | 10 +- .../post-language-switch/switch-variants.ts | 2 +- .../post-menu-item/post-menu-item.scss | 3 - .../post-menu-item/post-menu-item.tsx | 6 +- .../src/components/post-menu/post-menu.scss | 7 +- .../src/components/post-menu/post-menu.tsx | 42 ++---- .../src/components/post-menu/readme.md | 7 +- ...focusable.ts => get-focusable-children.ts} | 4 +- packages/components/src/utils/get-root.ts | 2 +- .../language-switch.stories.ts | 2 +- .../language-option.snapshot.stories.ts | 8 +- 19 files changed, 121 insertions(+), 149 deletions(-) create mode 100644 .changeset/few-ways-beam.md delete mode 100644 packages/components/src/components/post-menu-item/post-menu-item.scss rename packages/components/src/utils/{is-focusable.ts => get-focusable-children.ts} (76%) 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)} `); }, }; From 0a50c013fcd6418326d48835f357bdd54f8fd089 Mon Sep 17 00:00:00 2001 From: leagrdv Date: Fri, 20 Dec 2024 11:37:45 +0100 Subject: [PATCH 3/4] fix focus ring on ff + choose lang on enter + gap in mobile --- .../post-language-option.scss | 16 +++++++--------- .../post-language-option.tsx | 10 ++++++++++ .../post-language-switch.scss | 6 ++++++ 3 files changed, 23 insertions(+), 9 deletions(-) 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 7496c54cfb..a1e9dd1695 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,4 +1,5 @@ @use '@swisspost/design-system-styles/core' as post; +@use '@swisspost/design-system-styles/mixins/utilities' as utilities-mx; post-language-option { display: inline-block; @@ -20,13 +21,13 @@ post-language-option { height: 100%; width: 100%; padding: var(--post-language-option-padding); + border-radius: 2px; + @include utilities-mx.focus-style; } } post-language-option:where([variant='list']) { :is(a, button) { - @include post.focus-style; - border-radius: 2px; width: 40px; height: 40px; @@ -54,25 +55,22 @@ post-language-option:where([variant='menu']) { padding-inline: 24px; box-sizing: border-box; position: relative; + left: -1px; + width: calc(100% + 2px); &[aria-current='true'], &[aria-current='page'] { &::after { content: ''; - left: -2px; + left: 0; height: 3px; background-color: #050400; - width: calc(100% + 4px); + width: 100%; display: block; position: absolute; bottom: 3px; } - &:focus::after { - width: calc(100% - 5px); - left: 2px; - } - &:hover::after { background-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 d4dd6b9c26..0022850217 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 @@ -133,6 +133,11 @@ export class PostLanguageOption { hrefLang={lang} lang={lang} onClick={() => this.emitChange()} + onKeyDown={e => { + if (e.key === 'Enter' || e.key === ' ') { + this.emitChange(); + } + }} >
@@ -142,6 +147,11 @@ export class PostLanguageOption { aria-label={this.name} lang={lang} onClick={() => this.emitChange()} + onKeyDown={e => { + if (e.key === 'Enter' || e.key === ' ') { + this.emitChange(); + } + }} > diff --git a/packages/components/src/components/post-language-switch/post-language-switch.scss b/packages/components/src/components/post-language-switch/post-language-switch.scss index 62e6a7730b..769dfde594 100644 --- a/packages/components/src/components/post-language-switch/post-language-switch.scss +++ b/packages/components/src/components/post-language-switch/post-language-switch.scss @@ -3,11 +3,17 @@ @use '@swisspost/design-system-styles/mixins/button' as button-mx; @use '@swisspost/design-system-styles/mixins/utilities' as utilities-mx; @use '@swisspost/design-system-styles/components/header/mixins' as header-mx; +@use '@swisspost/design-system-styles/mixins/media'; tokens.$default-map: components.$post-button; :host { display: block; + + @include media.max(lg) { + display: flex !important; + gap: 0.5rem; + } } .post-language-switch-dropdown-container { From aab2fa6ae7b5402a71961817437e5c5dcecdb5ab Mon Sep 17 00:00:00 2001 From: leagrdv Date: Fri, 20 Dec 2024 15:20:47 +0100 Subject: [PATCH 4/4] fix PR comment --- .../post-language-option.tsx | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) 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 0022850217..3e993a989c 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 @@ -123,6 +123,12 @@ export class PostLanguageOption { render() { const lang = this.code.toLowerCase(); + const emitOnKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Enter' || e.key === ' ') { + this.emitChange(); + } + }; + return ( {this.url ? ( @@ -133,11 +139,7 @@ export class PostLanguageOption { hrefLang={lang} lang={lang} onClick={() => this.emitChange()} - onKeyDown={e => { - if (e.key === 'Enter' || e.key === ' ') { - this.emitChange(); - } - }} + onKeyDown={emitOnKeyDown} > @@ -147,11 +149,7 @@ export class PostLanguageOption { aria-label={this.name} lang={lang} onClick={() => this.emitChange()} - onKeyDown={e => { - if (e.key === 'Enter' || e.key === ' ') { - this.emitChange(); - } - }} + onKeyDown={emitOnKeyDown} >