Skip to content

Commit

Permalink
fix: Language selector: add prop for display lang for bug fix (#2622)
Browse files Browse the repository at this point in the history
  • Loading branch information
kleinschmidtj authored Oct 18, 2023
1 parent d38b31b commit df68d0d
Show file tree
Hide file tree
Showing 3 changed files with 165 additions and 57 deletions.
76 changes: 75 additions & 1 deletion src/components/LanguageSelector/LanguageSelector.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import React from 'react'
import React, { useState } from 'react'
import { LanguageSelector, LanguageDefinition } from './LanguageSelector'
import { ComponentStory } from '@storybook/react'

export default {
title: 'Components/LanguageSelector',
component: LanguageSelector,
argTypes: {
displayLang: { control: 'string' },
small: { control: 'boolean' },
},
parameters: {
Expand All @@ -19,6 +21,78 @@ Source: https://designsystem.digital.gov/components/language-selector/
},
},
}

const TwoLanguagesTemplate: ComponentStory<typeof LanguageSelector> = (
args
) => {
const [lang, setLang] = useState<string | undefined>(args.displayLang)

const languagesDisplayProp: LanguageDefinition[] = [
{
label: '简体字',
label_local: 'Chinese - Simplified',
attr: 'zh',
on_click: () => {
setLang(`en`)
},
},
{
label: 'English',
attr: 'en',
on_click: () => {
setLang(`zh`)
},
},
]

return <LanguageSelector langs={languagesDisplayProp} displayLang={lang} />
}

export const LanguagesDisplayPropSandbox = TwoLanguagesTemplate.bind({})
LanguagesDisplayPropSandbox.args = {
displayLang: `en`,
}

const MoreThanTwoLanguagesTemplate: ComponentStory<typeof LanguageSelector> = (
args
) => {
const [lang, setLang] = useState<string | undefined>(args.displayLang)

const languagesDisplayProp: LanguageDefinition[] = [
{
label: 'العربية',
label_local: 'Arabic',
attr: 'ar',
on_click: () => {
setLang(`ar`)
},
},
{
label: '简体字',
label_local: 'Chinese - Simplified',
attr: 'zh',
on_click: () => {
setLang(`zh`)
},
},
{
label: 'English',
attr: 'en',
on_click: () => {
setLang(`en`)
},
},
]

return <LanguageSelector langs={languagesDisplayProp} displayLang={lang} />
}

export const LanguagesDisplayMoreThanTwoLanguagesPropSandbox =
MoreThanTwoLanguagesTemplate.bind({})
LanguagesDisplayPropSandbox.args = {
displayLang: `en`,
}

type StorybookArguments = {
small?: boolean
}
Expand Down
66 changes: 10 additions & 56 deletions src/components/LanguageSelector/LanguageSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import React, { useState } from 'react'
import classnames from 'classnames'
import { Menu } from '../header/Menu/Menu'
import { LanguageSelectorButton } from './LanguageSelectorButton'
import { Button } from '../Button/Button'
import LanguageSelectorDropdown from './LanguageSelectorDropdown'

export type LanguageDefinition = {
label: string
Expand All @@ -11,18 +10,20 @@ export type LanguageDefinition = {
on_click: string | (() => void)
}

type LanguageSelectorProps = {
export type LanguageSelectorProps = {
label?: string
langs: LanguageDefinition[]
small?: boolean
className?: string
displayLang?: string
}

export const LanguageSelector = ({
label,
langs,
small,
className,
displayLang,
...divProps
}: LanguageSelectorProps &
JSX.IntrinsicElements['div']): React.ReactElement => {
Expand All @@ -34,66 +35,19 @@ export const LanguageSelector = ({
className
)

const [isOpen, setIsOpen] = useState(false)
const [langIndex, setLangIndex] = useState(false)
if (langs.length > 2) {
const items = []
for (let i = 0; i < langs.length; i++) {
// eslint-disable-next-line security/detect-object-injection
const lang: LanguageDefinition = langs[i]
if (typeof lang.on_click === 'string') {
items.push(
<a href={lang.on_click} data-testid={lang.attr}>
<span lang={lang.attr}>
<strong>{lang.label}</strong>
</span>
{lang.label_local && ` (${lang.label_local})`}
</a>
)
} else {
items.push(
<Button
onClick={lang.on_click}
data-testid={lang.attr}
type="button"
unstyled>
<span lang={lang.attr}>
<strong>{lang.label}</strong>
</span>
{lang.label_local && ` (${lang.label_local})`}
</Button>
)
}
}
return (
<div className={classes} data-testid="languageSelector" {...divProps}>
<ul className="usa-language__primary usa-accordion">
<li className="usa-language__primary-item">
<LanguageSelectorButton
className={classes}
label={label || langs[0].label}
isOpen={isOpen}
onToggle={() => {
setIsOpen((prevIsOpen) => !prevIsOpen)
}}
/>
<Menu
items={items}
isOpen={isOpen}
id="language-options"
type="language"
/>
</li>
</ul>
</div>
)
const dropdownProps = { label, langs, small, displayLang }
return <LanguageSelectorDropdown {...dropdownProps} className={className} />
} else {
if (label) {
console.warn(
"LanguageSelector's label is not used when only two languages are available."
)
}
const curLang = langs[Number(langIndex)]
const curLang =
langs.find((langDef) => langDef.attr === displayLang) ||
langs[Number(langIndex)]
const onClickString: string =
typeof curLang.on_click === 'string' ? curLang.on_click : ''
const onClick =
Expand All @@ -110,7 +64,7 @@ export const LanguageSelector = ({
labelAttr={curLang.attr}
onToggle={() => {
onClick()
setLangIndex((prevLangIndex) => !prevLangIndex)
if (!displayLang) setLangIndex((prevLangIndex) => !prevLangIndex)
}}
/>
</div>
Expand Down
80 changes: 80 additions & 0 deletions src/components/LanguageSelector/LanguageSelectorDropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import React, { useState } from 'react'
import { Menu } from '../header/Menu/Menu'
import { LanguageSelectorButton } from './LanguageSelectorButton'
import classnames from 'classnames'
import { LanguageDefinition, LanguageSelectorProps } from './LanguageSelector'
import { Button } from '../Button/Button'

const generateMenuItems = (langs: LanguageDefinition[]) => {
return langs.map((lang, index) => {
const label = (
<>
<span lang={lang.attr}>
<strong>{lang.label}</strong>
</span>
{lang.label_local && ` (${lang.label_local})`}
</>
)
if (typeof lang.on_click === 'string') {
return (
<a key={index} href={lang.on_click} data-testid={lang.attr}>
{label}
</a>
)
} else {
return (
<Button
key={index}
onClick={lang.on_click}
data-testid={lang.attr}
type="button"
unstyled>
{label}
</Button>
)
}
})
}

const LanguageSelectorDropdown: React.FC<LanguageSelectorProps> = ({
label,
langs,
small,
className,
displayLang,
...divProps
}) => {
const [isOpen, setIsOpen] = useState(false)

const classes = classnames(
'usa-language-container',
{
[`usa-language--small`]: small !== undefined,
},
className
)
const displayLabel = langs.find((langDef) => langDef.attr === displayLang)

return (
<div className={classes} data-testid="languageSelector" {...divProps}>
<ul className="usa-language__primary usa-accordion">
<li className="usa-language__primary-item">
<LanguageSelectorButton
className={classes}
label={displayLabel?.label || label || langs[0].label}
isOpen={isOpen}
onToggle={() => setIsOpen((prevIsOpen) => !prevIsOpen)}
/>
<Menu
items={generateMenuItems(langs)}
isOpen={isOpen}
id="language-options"
type="language"
/>
</li>
</ul>
</div>
)
}

export default LanguageSelectorDropdown

0 comments on commit df68d0d

Please sign in to comment.