Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat(components): post-language-switch web component #4044

Merged
merged 31 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
9100dd4
create web component
leagrdv Nov 1, 2024
d6c4f7f
Merge branch 'main' into 3633-component-language-switch
leagrdv Nov 13, 2024
4fa3087
add story and connect to language-option
leagrdv Nov 13, 2024
6ba7241
wip add post-menu
leagrdv Nov 13, 2024
848a2c7
wip language switch
leagrdv Nov 14, 2024
f5fc61a
wip language switch, set web components in post-language-option-switch
leagrdv Nov 15, 2024
d679f5d
Merge branch 'main' into 3633-component-language-switch
leagrdv Nov 18, 2024
b16b764
fix switch between types + start styling
leagrdv Nov 19, 2024
2b9c7fb
Merge branch 'main' into 3633-component-language-switch
leagrdv Nov 20, 2024
79cb196
finish design of post-language-option-switch + links
leagrdv Nov 21, 2024
62c034b
Add changeset and fix tests
leagrdv Nov 21, 2024
0f3601d
add tests
leagrdv Nov 21, 2024
7b57d13
Merge branch 'main' into 3633-component-language-switch
leagrdv Nov 21, 2024
fa71f9e
Merge branch 'main' into 3633-component-language-switch
leagrdv Nov 29, 2024
d0529b3
update snapshot with schemes
leagrdv Nov 29, 2024
2828efd
rename ID
leagrdv Nov 29, 2024
f564157
Merge branch 'main' into 3633-component-language-switch
leagrdv Dec 4, 2024
57d4026
proposal for a new internal structure
gfellerph Dec 4, 2024
6120984
Merge branch 'main' into 3633-component-language-switch
leagrdv Dec 5, 2024
66dc8cb
rename post-language-switch to post-language-switch-2 and post-langua…
leagrdv Dec 5, 2024
03c02dc
add styling
leagrdv Dec 5, 2024
b340b17
fix markup issue + add pointer
leagrdv Dec 5, 2024
3e70518
Merge branch 'main' into 3633-component-language-switch
leagrdv Dec 6, 2024
dd011a7
fix code smells
leagrdv Dec 6, 2024
cefc0bf
update language switch, delete unused code, dynamically set variant a…
leagrdv Dec 6, 2024
e897f77
Merge branch 'main' into 3633-component-language-switch
gfellerph Dec 9, 2024
3826d13
chore: update header sample markup (#4185)
gfellerph Dec 10, 2024
22ff9a9
Merge branch 'main' into 3633-component-language-switch
leagrdv Dec 10, 2024
84c761b
Merge branch 'main' into 3633-component-language-switch
leagrdv Dec 10, 2024
fe0265d
remove generated prop
gfellerph Dec 11, 2024
c15960d
remove name prop and use nanoid
gfellerph Dec 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/yellow-gifts-sit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@swisspost/design-system-documentation': minor
'@swisspost/design-system-components': minor
---

Added the post-language-option-switch component that enables users to change the language of a page.
63 changes: 63 additions & 0 deletions packages/components/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
import { HTMLStencilElement, JSXBase } from "@stencil/core/internal";
import { HeadingLevel } from "./types/index";
import { BannerType } from "./components/post-banner/banner-types";
import { SwitchVariant } from "./components/post-language-option-switch/switch-variants";
import { Placement } from "@floating-ui/dom";
export { HeadingLevel } from "./types/index";
export { BannerType } from "./components/post-banner/banner-types";
export { SwitchVariant } from "./components/post-language-option-switch/switch-variants";
export { Placement } from "@floating-ui/dom";
export namespace Components {
interface PostAccordion {
Expand Down Expand Up @@ -210,6 +212,10 @@ export namespace Components {
* The ISO 639 language code, formatted according to [RFC 5646 (also known as BCP 47)](https://datatracker.ietf.org/doc/html/rfc5646). For example, "de".
*/
"code": string;
/**
* Used on parent component (post-language-option-switch) to detect elements that are manually added
*/
"generated": boolean;
/**
* The full name of the language. For example, "Deutsch".
*/
Expand All @@ -222,6 +228,28 @@ export namespace Components {
* The URL used for the href attribute of the internal anchor. This field is optional; if not provided, a button will be used internally instead of an anchor.
*/
"url": string;
/**
* Variant that determines the rendering of the language option either as a list item (used on mobile in the header) or a dropdown item (used on desktop in the header)
*/
"variant": SwitchVariant;
}
interface PostLanguageOptionSwitch {
/**
* A title for the list of language options
*/
"caption": string;
/**
* A descriptive text for the list of language options
*/
"description": string;
/**
* The name of the language switch, which will be used on the dropdown as an ID
*/
"name": string;
/**
* 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)
*/
"variant": SwitchVariant;
}
interface PostList {
/**
Expand Down Expand Up @@ -599,6 +627,12 @@ declare global {
prototype: HTMLPostLanguageOptionElement;
new (): HTMLPostLanguageOptionElement;
};
interface HTMLPostLanguageOptionSwitchElement extends Components.PostLanguageOptionSwitch, HTMLStencilElement {
}
var HTMLPostLanguageOptionSwitchElement: {
prototype: HTMLPostLanguageOptionSwitchElement;
new (): HTMLPostLanguageOptionSwitchElement;
};
interface HTMLPostListElement extends Components.PostList, HTMLStencilElement {
}
var HTMLPostListElement: {
Expand Down Expand Up @@ -781,6 +815,7 @@ declare global {
"post-header": HTMLPostHeaderElement;
"post-icon": HTMLPostIconElement;
"post-language-option": HTMLPostLanguageOptionElement;
"post-language-option-switch": HTMLPostLanguageOptionSwitchElement;
"post-list": HTMLPostListElement;
"post-list-item": HTMLPostListItemElement;
"post-logo": HTMLPostLogoElement;
Expand Down Expand Up @@ -979,6 +1014,10 @@ declare namespace LocalJSX {
* The ISO 639 language code, formatted according to [RFC 5646 (also known as BCP 47)](https://datatracker.ietf.org/doc/html/rfc5646). For example, "de".
*/
"code": string;
/**
* Used on parent component (post-language-option-switch) to detect elements that are manually added
*/
"generated"?: boolean;
/**
* The full name of the language. For example, "Deutsch".
*/
Expand All @@ -991,6 +1030,28 @@ declare namespace LocalJSX {
* The URL used for the href attribute of the internal anchor. This field is optional; if not provided, a button will be used internally instead of an anchor.
*/
"url"?: string;
/**
* Variant that determines the rendering of the language option either as a list item (used on mobile in the header) or a dropdown item (used on desktop in the header)
*/
"variant"?: SwitchVariant;
}
interface PostLanguageOptionSwitch {
/**
* A title for the list of language options
*/
"caption"?: string;
/**
* A descriptive text for the list of language options
*/
"description"?: string;
/**
* The name of the language switch, which will be used on the dropdown as an ID
*/
"name"?: string;
/**
* 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)
*/
"variant"?: SwitchVariant;
}
interface PostList {
/**
Expand Down Expand Up @@ -1163,6 +1224,7 @@ declare namespace LocalJSX {
"post-header": PostHeader;
"post-icon": PostIcon;
"post-language-option": PostLanguageOption;
"post-language-option-switch": PostLanguageOptionSwitch;
"post-list": PostList;
"post-list-item": PostListItem;
"post-logo": PostLogo;
Expand Down Expand Up @@ -1204,6 +1266,7 @@ declare module "@stencil/core" {
*/
"post-icon": LocalJSX.PostIcon & JSXBase.HTMLAttributes<HTMLPostIconElement>;
"post-language-option": LocalJSX.PostLanguageOption & JSXBase.HTMLAttributes<HTMLPostLanguageOptionElement>;
"post-language-option-switch": LocalJSX.PostLanguageOptionSwitch & JSXBase.HTMLAttributes<HTMLPostLanguageOptionSwitchElement>;
"post-list": LocalJSX.PostList & JSXBase.HTMLAttributes<HTMLPostListElement>;
"post-list-item": LocalJSX.PostListItem & JSXBase.HTMLAttributes<HTMLPostListItemElement>;
"post-logo": LocalJSX.PostLogo & JSXBase.HTMLAttributes<HTMLPostLogoElement>;
Expand Down
2 changes: 2 additions & 0 deletions packages/components/src/components/post-icon/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ some content
- [post-breadcrumb-item](../post-breadcrumb-item)
- [post-card-control](../post-card-control)
- [post-closebutton](../post-closebutton)
- [post-language-option-switch](../post-language-option-switch)
- [post-rating](../post-rating)
- [post-tag](../post-tag)

Expand All @@ -36,6 +37,7 @@ graph TD;
post-breadcrumb-item --> post-icon
post-card-control --> post-icon
post-closebutton --> post-icon
post-language-option-switch --> post-icon
post-rating --> post-icon
post-tag --> post-icon
style post-icon fill:#f9f,stroke:#333,stroke-width:4px
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
@use '@swisspost/design-system-styles/core' as post;
leagrdv marked this conversation as resolved.
Show resolved Hide resolved

:host {
display: block;
}

post-language-option-switch {
post-list > div[role='list'] {
flex-direction: row;
gap: 8px;
leagrdv marked this conversation as resolved.
Show resolved Hide resolved
}

.hide {
display: none;
}

post-language-option {
width: 100%;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import { Component, Element, Host, h, Prop, Watch, State } from '@stencil/core';
import { checkEmptyOrOneOf, checkType } from '@/utils';
import { version } from '@root/package.json';
import { SWITCH_VARIANTS, SwitchVariant } from './switch-variants';

@Component({
tag: 'post-language-option-switch',
styleUrl: 'post-language-option-switch.scss',
shadow: false,
})
export class PostLanguageOptionSwitch {
@Element() host: HTMLPostLanguageOptionSwitchElement;

/**
* A title for the list of language options
*/
@Prop() caption: string;

@Watch('caption')
validateCaption(value = this.caption) {
checkType(
value,
'string',
'The "caption" property of the post-language-option-switch component must be a string.',
);
}

/**
* The name of the language switch, which will be used on the dropdown as an ID
*/
@Prop() name: string;

@Watch('name')
validateName(value = this.name) {
checkType(
value,
'string',
'The "name" property of the post-language-option-switch component must be a string.',
);
}

/**
* A descriptive text for the list of language options
*/
@Prop() description: string;

@Watch('description')
validateDescription(value = this.description) {
checkType(
value,
'string',
'The "description" property of the post-language-option-switch component must be a string.',
);
}

/**
* 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)
*/
@Prop() variant: SwitchVariant = 'list';

@Watch('variant')
validateVariant(value = this.variant) {
checkEmptyOrOneOf(
value,
SWITCH_VARIANTS,
`The "variant" property of the post-language-option-switch component must be: ${SWITCH_VARIANTS.join(
', ',
)}`,
);
}

/**
* The active language of the language switch
*/
@State() activeLang: string;

/**
* List of post-language-option in the slot of the component
*/
private elements: NodeListOf<HTMLPostLanguageOptionElement>;

private menuId: string;

connectedCallback() {
// Transforms name into an ID for the post-menu
this.menuId = this.name.replace(/\W/g, '_');

this.elements = this.host.querySelectorAll('post-language-option:not([generated="true"])');
this.elements.forEach(el => {
if (el.getAttribute('active') !== 'false') {
this.activeLang = el.getAttribute('code');
}
});
}

componentDidLoad() {
this.validateCaption();
this.validateDescription();
this.validateVariant();
this.validateName();

// Detects a change in the active language
this.host.addEventListener('postChange', (el: CustomEvent<string>) => {
this.activeLang = el.detail;

// Hides the dropdown when an option has been clicked
if (this.variant === 'dropdown') {
const menu = this.host.querySelector('post-menu') as HTMLPostMenuElement;
menu.toggle(menu);
}
});
}

render() {
return (
<Host data-version={version} slot="post-language-option-switch">
{this.variant === 'list' ? (
<post-list title-hidden="true">
<h3>
{this.caption}, {this.description}
</h3>
{Array.from(this.elements).map(item => (
<post-list-item role="none">
<post-language-option
variant={this.variant}
active={this.activeLang === item.getAttribute('code')}
code={item.getAttribute('code')}
url={item.getAttribute('url')}
name={item.getAttribute('name')}
generated={true}
>
{item.textContent}
</post-language-option>
</post-list-item>
))}
</post-list>
) : (
<div>
<post-menu-trigger for={this.menuId}>
<button class="btn btn-tertiary btn-sm">
<span class="visually-hidden">
{this.caption}, {this.description}
</span>
{this.activeLang.toUpperCase()}
<post-icon aria-hidden="true" name="2052"></post-icon>
</button>
</post-menu-trigger>
<post-menu id={this.menuId}>
{Array.from(this.elements).map(item => (
<post-menu-item>
<post-language-option
variant={this.variant}
active={this.activeLang === item.getAttribute('code')}
code={item.getAttribute('code')}
url={item.getAttribute('url')}
name={item.getAttribute('name')}
generated={true}
>
{item.textContent}
</post-language-option>
</post-menu-item>
))}
</post-menu>
</div>
)}
<div class="hide" aria-hidden="true">
<slot />
</div>
</Host>
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# post-language-option-switch

<!-- Auto Generated Below -->


## 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` |
| `name` | `name` | The name of the language switch, which will be used on the dropdown as an ID | `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'` |


## Dependencies

### Depends on

- [post-list](../post-list)
- [post-list-item](../post-list-item)
- [post-language-option](../post-language-option)
- [post-menu-trigger](../post-menu-trigger)
- [post-icon](../post-icon)
- [post-menu](../post-menu)
- [post-menu-item](../post-menu-item)

### Graph
```mermaid
graph TD;
post-language-option-switch --> post-list
post-language-option-switch --> post-list-item
post-language-option-switch --> post-language-option
post-language-option-switch --> post-menu-trigger
post-language-option-switch --> post-icon
post-language-option-switch --> post-menu
post-language-option-switch --> post-menu-item
post-menu --> post-popovercontainer
style post-language-option-switch fill:#f9f,stroke:#333,stroke-width:4px
```

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

*Built with [StencilJS](https://stenciljs.com/)*
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const SWITCH_VARIANTS = ['list', 'dropdown'] as const;

export type SwitchVariant = (typeof SWITCH_VARIANTS)[number];
Loading