-
Notifications
You must be signed in to change notification settings - Fork 12
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
va-language-toggle: add component #1386
Changes from 6 commits
6df795d
60b523e
2c71555
440173b
d062e63
18f0492
1b91400
6f927bb
61001d8
c24f1cf
92eed55
27bd031
f2d9c9a
26681db
72d0373
087c34e
1d24a81
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import React, { Fragment } from 'react'; | ||
import { getWebComponentDocs, StoryDocs, propStructure } from './wc-helpers'; | ||
import { VaLanguageToggle } from '@department-of-veterans-affairs/web-components/react-bindings'; | ||
|
||
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 defaultArgs = { | ||
urls: [ | ||
{ "href": url.href, "lang": "en", "label": "English" }, | ||
{ "href": url.href, "lang": "es", "label": "Español" }, | ||
{ "href": url.href, "lang": "tl", "label": "Tagalog" } | ||
], | ||
} | ||
|
||
const Template = ({ urls }) => { | ||
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} urls={urls} onVaLanguageToggle={handleLanguageToggle}/> | ||
); | ||
}; | ||
|
||
const WithRouterLinksTemplate = ({urls}) => { | ||
|
||
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> | ||
<VaLanguageToggle urls={urls} routerLinks={true} onVaLanguageToggle={handleLanguageToggle}/> | ||
</Fragment> | ||
) | ||
} | ||
|
||
export const Default = Template.bind(null); | ||
Default.args = { | ||
...defaultArgs, | ||
}; | ||
Default.argTypes = propStructure(languageToggleDocs); | ||
|
||
export const WithRouterLinks = WithRouterLinksTemplate.bind(null); | ||
WithRouterLinks.args = { ...defaultArgs } |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import { newE2EPage } from "@stencil/core/testing"; | ||
import { axeCheck } from "../../../testing/test-helpers"; | ||
|
||
describe('va-language-toggle', () => { | ||
const urls = '[{"href":"/resources/the-pact-act-and-your-va-benefits/","lang":"en","label":"English"},{"href":"/resources/the-pact-act-and-your-va-benefits-esp/","lang":"es","label":"Español"},{"href":"/resources/the-pact-act-and-your-va-benefits-tl/","lang":"tg","label":"Tagalog"}]'; | ||
|
||
it('renders', async () => { | ||
const page = await newE2EPage(); | ||
await page.setContent(`<va-language-toggle urls=${urls} />`); | ||
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 urls=${urls} />`); | ||
const anchor = await page.find('va-language-toggle >>> a'); | ||
expect(anchor).toHaveClass('is-current-lang'); | ||
}); | ||
|
||
it('if language prop is set the matching language is bolded', async () => { | ||
const page = await newE2EPage(); | ||
await page.setContent(`<va-language-toggle language="es" urls=${urls} />`); | ||
const [_, anchor] = await page.findAll('va-language-toggle >>> a'); | ||
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" urls=${urls} />`); | ||
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 urls=${urls} />`); | ||
const toggleSpy = await page.spyOnEvent('vaLanguageToggle'); | ||
const [_, anchor] = await page.findAll('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 urls=${urls} />`); | ||
await axeCheck(page); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
@import '../../mixins/focusable.css'; | ||
|
||
:host { | ||
display: inline-block; | ||
margin: 32px 0 24px 0; | ||
border-bottom: 1px solid var(--vads-color-base-dark); | ||
|
||
span { | ||
margin: 0 4px 0 4px; | ||
color: var(--vads-color-base-dark); | ||
} | ||
|
||
a { | ||
&.is-current-lang { | ||
font-weight: var(--font-weight-bold); | ||
text-decoration: none; | ||
color: var(--vads-color-base); | ||
} | ||
&:hover { | ||
background-color: rgba(0, 0, 0, 0.05); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import { Component, Host, h, State, Event, EventEmitter, Prop, Fragment } from '@stencil/core'; | ||
import classNames from 'classnames'; | ||
|
||
export type LangUrl = { | ||
label: string; | ||
href: string; | ||
lang: string; | ||
}; | ||
|
||
/** | ||
* @componentName Language Toggle | ||
* @maturityCategory caution | ||
* @maturityLevel candidate | ||
*/ | ||
|
||
@Component({ | ||
tag: 'va-language-toggle', | ||
styleUrl: 'va-language-toggle.scss', | ||
shadow: true, | ||
}) | ||
export class VaLanguageToggle { | ||
/** | ||
* The ISO language code for the page. Default is 'en'. | ||
*/ | ||
@Prop() language: string = 'en'; | ||
|
||
/** | ||
* A JSON array of objects with link data. Each object should have an href, lang (ISO language code), and label properties. If using the pure web component provide as a string. Example: `[{"href": "/one", "lang": "en", "label": "English"}, ...]`. | ||
*/ | ||
@Prop() urls: LangUrl[] | string; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't know if we want to add this type of flexibility. Our guidelines say that we only support 3 languages; English, Espanol, Tagalog
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @jamigibbs - good point - I updated the component to only allow en, es, or tl. |
||
|
||
/** | ||
* 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. | ||
*/ | ||
@Prop() routerLinks?: boolean = false; | ||
|
||
/** | ||
* The urls as a Javascript array of objects, each with href, lang, and label properties. | ||
*/ | ||
@State() formattedUrls: LangUrl[] = []; | ||
|
||
/** | ||
* Event fired when a link is clicked. Includes the selected language's ISO code. | ||
*/ | ||
@Event() | ||
vaLanguageToggle: EventEmitter; | ||
|
||
// get the current page's url with language as a query param. | ||
// allows for marking "pages" as visited | ||
getUrl(langCode: string): string { | ||
const url = new URL(window.location.href); | ||
url.searchParams.set('lang', langCode); | ||
return url.href; | ||
} | ||
|
||
// This method is fired whenever a link is clicked | ||
handleToggle(e: Event, langCode: string) { | ||
// don't navigate from current page but set new language | ||
if (this.routerLinks) { | ||
e.preventDefault(); | ||
// change browser url so that :visited styles apply to links | ||
window.history.replaceState(null, null, this.getUrl(langCode)); | ||
this.language = langCode; | ||
} | ||
|
||
this.vaLanguageToggle.emit({ language: langCode }); | ||
} | ||
|
||
componentWillLoad() { | ||
// parse urls if needed | ||
this.formattedUrls = (typeof this.urls === 'string' || this.urls instanceof String) | ||
? JSON.parse(this.urls as string) | ||
: this.urls; | ||
} | ||
|
||
render() { | ||
const { language, formattedUrls } = this; | ||
return ( | ||
<Host> | ||
{this.formattedUrls.map(({href, lang, label}, i) => { | ||
const anchorClass = classNames({ | ||
'is-current-lang': lang === language | ||
}); | ||
return ( | ||
<Fragment> | ||
<a | ||
class={anchorClass} | ||
href={href} | ||
lang={lang} | ||
hrefLang={lang} | ||
onClick={(e) => this.handleToggle(e, lang)} | ||
> | ||
{label} | ||
</a> | ||
{ (i < formattedUrls.length - 1) && <span>|</span> } | ||
</Fragment> | ||
) | ||
})} | ||
</Host> | ||
); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have a Figma link that can be added to description of the PR? I think it would be worthwhile to get a review from one of the designers as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jamigibbs I'm reaching out to Barb on this