Skip to content

Commit

Permalink
va-language-toggle: add component (#1386)
Browse files Browse the repository at this point in the history
* add component

* update component

* revert accidental changes

* update api to only allow for en, es, tl

* update styling

* tweak story

* update story

* fix story name

* add lang support to va-link

* fix tests

* add test

* update test text
  • Loading branch information
it-harrison authored Nov 19, 2024
1 parent 3a7ff73 commit 7756f11
Show file tree
Hide file tree
Showing 7 changed files with 438 additions and 2 deletions.
68 changes: 68 additions & 0 deletions packages/storybook/stories/va-language-toggle.stories.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React, { Fragment } from 'react';
import { getWebComponentDocs, StoryDocs, propStructure } from './wc-helpers';
import { VaLanguageToggle } from '@department-of-veterans-affairs/web-components/react-bindings';

VaLanguageToggle.displayName = 'VaLanguageToggle';

const languageToggleDocs = getWebComponentDocs('va-language-toggle');

export default {
title: 'Components/Language Toggle',
id: 'components/va-language-toggle',
parameters: {
componentSubtitle: 'va-language-toggle web component',
docs: {
page: () => <StoryDocs storyDefault={Default} data={languageToggleDocs} />,
},
},
};

const url = new URL(window.parent.location.href);
url.searchParams.set('path', '/docs/components-va-language-toggle--docs');

const Template = ({}) => {
let lang = sessionStorage.getItem('va-language-toggle-lang') ?? 'en';
function handleLanguageToggle(e) {
const { language } = e.detail;
sessionStorage.setItem('va-language-toggle-lang', language)
}

return (
<VaLanguageToggle
language={lang}
enHref={url.href}
esHref={url.href}
tlHref={url.href}
onVaLanguageToggle={handleLanguageToggle}
/>
);
};

const WithRouterLinksTemplate = ({}) => {

function handleLanguageToggle(e) {
console.log(`the language has been toggled to ${e.detail.language}`);
}

return (
<Fragment>
<div>This example illustrates how to use the component with a router. When <code>router-links</code> is
set to <code>true</code>, clicking on a link will not navigate to a new page (i.e. <code>event.preventDefault()</code> is called).
By capturing the <code>language-toggle</code> event page content can be updated as needed to reflect the selected language.
</div>
<br/>
<VaLanguageToggle
enHref={url.href}
esHref={url.href}
tlHref={url.href}
routerLinks={true}
onVaLanguageToggle={handleLanguageToggle}
/>
</Fragment>
)
}

export const Default = Template.bind(null);
Default.argTypes = propStructure(languageToggleDocs);

export const WithRouterLinks = WithRouterLinksTemplate.bind(null);
104 changes: 103 additions & 1 deletion packages/web-components/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,33 @@ export namespace Components {
*/
"srtext"?: string;
}
/**
* @componentName Language Toggle
* @maturityCategory caution
* @maturityLevel candidate
*/
interface VaLanguageToggle {
/**
* The English language href for the page. Required.
*/
"enHref": string;
/**
* The Spanish language href for the page. Optional.
*/
"esHref"?: string;
/**
* The ISO language code for the page. Default is 'en'.
*/
"language": string;
/**
* If true, specifies that the toggle is being used on a page with a router and clicking on a link will not result in page navigation.
*/
"routerLinks"?: boolean;
/**
* The Tagalog language href for the page. Optional.
*/
"tlHref"?: string;
}
/**
* @componentName Link
* @maturityCategory caution
Expand Down Expand Up @@ -698,6 +725,10 @@ export namespace Components {
* Adds an aria-label attribute to the link element.
*/
"label"?: string;
/**
* The lang attribute for the anchor tag in the Default va-link. Also used for hreflang.
*/
"language"?: string;
/**
* The number of pages of the file. Only displayed if download is `true`.
*/
Expand Down Expand Up @@ -1825,6 +1856,10 @@ export interface VaFileInputMultipleCustomEvent<T> extends CustomEvent<T> {
detail: T;
target: HTMLVaFileInputMultipleElement;
}
export interface VaLanguageToggleCustomEvent<T> extends CustomEvent<T> {
detail: T;
target: HTMLVaLanguageToggleElement;
}
export interface VaLinkCustomEvent<T> extends CustomEvent<T> {
detail: T;
target: HTMLVaLinkElement;
Expand Down Expand Up @@ -2330,6 +2365,29 @@ declare global {
prototype: HTMLVaIconElement;
new (): HTMLVaIconElement;
};
interface HTMLVaLanguageToggleElementEventMap {
"vaLanguageToggle": any;
"component-library-analytics": any;
}
/**
* @componentName Language Toggle
* @maturityCategory caution
* @maturityLevel candidate
*/
interface HTMLVaLanguageToggleElement extends Components.VaLanguageToggle, HTMLStencilElement {
addEventListener<K extends keyof HTMLVaLanguageToggleElementEventMap>(type: K, listener: (this: HTMLVaLanguageToggleElement, ev: VaLanguageToggleCustomEvent<HTMLVaLanguageToggleElementEventMap[K]>) => any, options?: boolean | AddEventListenerOptions): void;
addEventListener<K extends keyof DocumentEventMap>(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
addEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
removeEventListener<K extends keyof HTMLVaLanguageToggleElementEventMap>(type: K, listener: (this: HTMLVaLanguageToggleElement, ev: VaLanguageToggleCustomEvent<HTMLVaLanguageToggleElementEventMap[K]>) => any, options?: boolean | EventListenerOptions): void;
removeEventListener<K extends keyof DocumentEventMap>(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
removeEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
}
var HTMLVaLanguageToggleElement: {
prototype: HTMLVaLanguageToggleElement;
new (): HTMLVaLanguageToggleElement;
};
interface HTMLVaLinkElementEventMap {
"component-library-analytics": any;
}
Expand Down Expand Up @@ -2972,6 +3030,7 @@ declare global {
"va-file-input-multiple": HTMLVaFileInputMultipleElement;
"va-header-minimal": HTMLVaHeaderMinimalElement;
"va-icon": HTMLVaIconElement;
"va-language-toggle": HTMLVaLanguageToggleElement;
"va-link": HTMLVaLinkElement;
"va-link-action": HTMLVaLinkActionElement;
"va-loading-indicator": HTMLVaLoadingIndicatorElement;
Expand Down Expand Up @@ -3730,6 +3789,41 @@ declare namespace LocalJSX {
*/
"srtext"?: string;
}
/**
* @componentName Language Toggle
* @maturityCategory caution
* @maturityLevel candidate
*/
interface VaLanguageToggle {
/**
* The English language href for the page. Required.
*/
"enHref": string;
/**
* The Spanish language href for the page. Optional.
*/
"esHref"?: string;
/**
* The ISO language code for the page. Default is 'en'.
*/
"language"?: string;
/**
* The event used to track usage of the component.
*/
"onComponent-library-analytics"?: (event: VaLanguageToggleCustomEvent<any>) => void;
/**
* Event fired when a link is clicked. Includes the selected language's ISO code.
*/
"onVaLanguageToggle"?: (event: VaLanguageToggleCustomEvent<any>) => void;
/**
* If true, specifies that the toggle is being used on a page with a router and clicking on a link will not result in page navigation.
*/
"routerLinks"?: boolean;
/**
* The Tagalog language href for the page. Optional.
*/
"tlHref"?: string;
}
/**
* @componentName Link
* @maturityCategory caution
Expand Down Expand Up @@ -3793,8 +3887,9 @@ declare namespace LocalJSX {
*/
"label"?: string;
/**
* The event used to track usage of the component.
* The lang attribute for the anchor tag in the Default va-link. Also used for hreflang.
*/
"language"?: string;
"onComponent-library-analytics"?: (event: VaLinkCustomEvent<any>) => void;
/**
* The number of pages of the file. Only displayed if download is `true`.
Expand Down Expand Up @@ -5031,6 +5126,7 @@ declare namespace LocalJSX {
"va-file-input-multiple": VaFileInputMultiple;
"va-header-minimal": VaHeaderMinimal;
"va-icon": VaIcon;
"va-language-toggle": VaLanguageToggle;
"va-link": VaLink;
"va-link-action": VaLinkAction;
"va-loading-indicator": VaLoadingIndicator;
Expand Down Expand Up @@ -5206,6 +5302,12 @@ declare module "@stencil/core" {
* @maturityLevel candidate
*/
"va-icon": LocalJSX.VaIcon & JSXBase.HTMLAttributes<HTMLVaIconElement>;
/**
* @componentName Language Toggle
* @maturityCategory caution
* @maturityLevel candidate
*/
"va-language-toggle": LocalJSX.VaLanguageToggle & JSXBase.HTMLAttributes<HTMLVaLanguageToggleElement>;
/**
* @componentName Link
* @maturityCategory caution
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { newE2EPage } from "@stencil/core/testing";
import { axeCheck } from "../../../testing/test-helpers";

describe('va-language-toggle', () => {
const enHref = "/resources/the-pact-act-and-your-va-benefits/";
const esHref = "/resources/the-pact-act-and-your-va-benefits-esp/"
const tlHref = "/resources/the-pact-act-and-your-va-benefits-tl/"

it('renders', async () => {
const page = await newE2EPage();
await page.setContent(`<va-language-toggle en-href="${enHref}" es-href="${esHref}" tl-href="${tlHref}" />`);
const element = await page.find('va-language-toggle');
expect(element).toHaveClass('hydrated');
});

it('English is the default language', async () => {
const page = await newE2EPage();
await page.setContent(`<va-language-toggle en-href="${enHref}" es-href="${esHref}" tl-href="${tlHref}" />`);
const anchor = await page.find('va-language-toggle >>> va-link');
expect(anchor).toHaveClass('is-current-lang');
});

it('only renders links for those languages with supplied hrefs', async () => {
const page = await newE2EPage();
await page.setContent(`<va-language-toggle en-href="${enHref}" es-href="${esHref}" />`);
const anchors = await page.findAll('va-language-toggle >>> a');
expect(anchors).toHaveLength(2);
})

it('if language prop is set the matching language is bolded', async () => {
const page = await newE2EPage();
await page.setContent(`<va-language-toggle language="es" en-href="${enHref}" es-href="${esHref}" tl-href="${tlHref}" />`);
const [_, anchor] = await page.findAll('va-language-toggle >>> va-link');
expect(anchor).toHaveClass('is-current-lang');
});

it('if router-links is set, clicking an anchor tag does not result in page navigation', async () => {
const page = await newE2EPage();
await page.setContent(`<va-language-toggle router-links="true" en-href="${enHref}" es-href="${esHref}" tl-href="${tlHref}" />`);
const [startUrl] = page.url().split('?');
const [_, anchor] = await page.findAll('va-language-toggle >>> a');
await anchor.click();
const [endUrl] = page.url().split('?');
expect(startUrl).toEqual(endUrl);
});

it('fires a language-toggle event when a link is clicked', async () => {
const page = await newE2EPage();
await page.setContent(`<va-language-toggle en-href="${enHref}" es-href="${esHref}" tl-href="${tlHref}" />`);
const toggleSpy = await page.spyOnEvent('vaLanguageToggle');
const [_, anchor] = await page.findAll('va-language-toggle >>> a');
await anchor.click();
expect(toggleSpy).toHaveReceivedEvent();
});

it('fires a component-analytics event when a link is clicked', async () => {
const page = await newE2EPage();
await page.setContent(`<va-language-toggle en-href="${enHref}" es-href="${esHref}" tl-href="${tlHref}" />`);
const toggleSpy = await page.spyOnEvent('component-library-analytics');
const anchor = await page.find('va-language-toggle >>> a');
await anchor.click();
expect(toggleSpy).toHaveReceivedEvent();
});

it('passes an aXe check', async () => {
const page = await newE2EPage();
await page.setContent(`<va-language-toggle en-href="${enHref}" es-href="${esHref}" tl-href="${tlHref}" />`);
await axeCheck(page);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
:host div {
display: inline-block;

div.inner-div {
border-right: 1px solid var(--vads-color-base-light);
padding-right: 8px;
margin-right: 8px;
}

va-link {
&::part(anchor) {
text-decoration: none;
}
&.is-current-lang {
font-weight: var(--font-weight-bold);
}
color: var(--vads-color-link);
padding-bottom: 2px;
border-bottom: 1px solid var(--vads-color-link);
}
}
Loading

0 comments on commit 7756f11

Please sign in to comment.