Skip to content

Commit bf82db5

Browse files
ktssrovflowdTeja Sai Sandeep Reddy Konalashanpriyanmikeesto
committed
chore(migration): migrate language selector component (#5266)
Co-authored-by: Claudio Wunder <[email protected]> Co-authored-by: Teja Sai Sandeep Reddy Konala <[email protected]> Co-authored-by: Shanmughapriyan S <[email protected]> Co-authored-by: Michael Esteban <[email protected]>
1 parent b72d29f commit bf82db5

File tree

12 files changed

+296
-4
lines changed

12 files changed

+296
-4
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { fireEvent, render, screen } from '@testing-library/react';
2+
import React from 'react';
3+
import Dropdown from '..';
4+
import type { DropdownItem } from '../../../../types';
5+
6+
describe('Dropdown component', () => {
7+
const items: DropdownItem[] = [
8+
{ label: 'item1', title: 'Item 1', active: false, onClick: jest.fn() },
9+
{ label: 'item2', title: 'Item 2', active: true, onClick: jest.fn() },
10+
{ label: 'item3', title: 'Item 3', active: false, onClick: jest.fn() },
11+
];
12+
13+
it('should render the items and apply active styles', () => {
14+
render(<Dropdown items={items} shouldShow={true} styles={{}} />);
15+
16+
items.forEach(item => {
17+
const button = screen.getByText(item.title);
18+
expect(button).toBeInTheDocument();
19+
20+
if (item.active) {
21+
expect(button).toHaveStyle('font-weight: bold');
22+
} else {
23+
expect(button).not.toHaveStyle('font-weight: bold');
24+
}
25+
});
26+
});
27+
28+
it('should call the onClick function when an item is clicked', () => {
29+
render(<Dropdown items={items} shouldShow={true} styles={{}} />);
30+
const button = screen.getByText(items[2].title);
31+
fireEvent.click(button);
32+
expect(items[2].onClick).toHaveBeenCalledTimes(1);
33+
});
34+
35+
it('should call the onClick function when Enter or Space is pressed', () => {
36+
render(<Dropdown items={items} shouldShow={true} styles={{}} />);
37+
const button = screen.getByText(items[1].title);
38+
fireEvent.keyDown(button, { key: 'Enter', code: 'Enter' });
39+
fireEvent.keyDown(button, { key: ' ', code: 'Space' });
40+
expect(items[1].onClick).toHaveBeenCalledTimes(2);
41+
});
42+
43+
it('should not render the items when shouldShow prop is false', () => {
44+
render(<Dropdown items={items} shouldShow={false} styles={{}} />);
45+
items.forEach(item => {
46+
const button = screen.queryByText(item.title);
47+
expect(button).not.toBeVisible();
48+
});
49+
});
50+
51+
it('should apply styles passed in the styles prop', () => {
52+
const customStyles: React.CSSProperties = {
53+
backgroundColor: 'green',
54+
padding: '10px',
55+
borderRadius: '5px',
56+
};
57+
render(<Dropdown items={items} shouldShow={true} styles={customStyles} />);
58+
59+
const dropdownList = screen.getByRole('list');
60+
expect(dropdownList).toHaveStyle('background-color: green');
61+
expect(dropdownList).toHaveStyle('padding: 10px');
62+
expect(dropdownList).toHaveStyle('border-radius: 5px');
63+
});
64+
});
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
.dropdownList {
2+
background-color: var(--color-dropdown-background);
3+
border-radius: 5px;
4+
height: fit-content;
5+
list-style-type: none;
6+
max-height: 200px;
7+
min-width: 150px;
8+
overflow-y: auto;
9+
padding: 0;
10+
position: absolute;
11+
width: fit-content;
12+
13+
> li {
14+
> button {
15+
background: none;
16+
border: none;
17+
18+
color: var(--color-text-primary);
19+
cursor: pointer;
20+
font-size: var(--font-size-body1);
21+
padding: var(--space-12);
22+
text-align: center;
23+
width: 100%;
24+
25+
&:hover {
26+
background-color: var(--color-dropdown-hover);
27+
}
28+
}
29+
}
30+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import type { StoryObj } from '@storybook/react';
2+
import Dropdown from '.';
3+
4+
type Story = StoryObj<typeof Dropdown>;
5+
6+
export default {
7+
component: Dropdown,
8+
};
9+
10+
const items = [...Array(10).keys()].map(item => ({
11+
title: `Item ${item + 1}`,
12+
label: `item-${item + 1}`,
13+
active: false,
14+
onClick: () => {},
15+
}));
16+
17+
items[2].active = true;
18+
19+
export const withItems: Story = {
20+
args: {
21+
items: items,
22+
shouldShow: true,
23+
},
24+
};
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import React from 'react';
2+
import type { DropdownItem } from '../../../types';
3+
import styles from './index.module.scss';
4+
5+
export interface DropdownProps {
6+
items: Array<DropdownItem>;
7+
shouldShow: boolean;
8+
styles: Object;
9+
}
10+
11+
const Dropdown = ({ items, shouldShow, styles: css }: DropdownProps) => {
12+
const mappedElements = items.map(item => {
13+
const extraStyles = { fontWeight: item.active ? 'bold' : 'normal' };
14+
15+
const handleKeyPress = (e: React.KeyboardEvent<HTMLButtonElement>) => {
16+
if (e.key === 'Enter' || e.key === ' ') {
17+
item.onClick();
18+
}
19+
};
20+
21+
return (
22+
<li key={`dropdown-item-${item.label}`}>
23+
<button
24+
style={extraStyles}
25+
onClick={item.onClick}
26+
onKeyDown={handleKeyPress}
27+
type="button"
28+
>
29+
{item.title}
30+
</button>
31+
</li>
32+
);
33+
});
34+
35+
const dropdownStyles = { display: shouldShow ? 'block' : 'none', ...css };
36+
37+
return (
38+
<ul className={styles.dropdownList} style={dropdownStyles}>
39+
{mappedElements}
40+
</ul>
41+
);
42+
};
43+
44+
export default Dropdown;
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { fireEvent, render, screen } from '@testing-library/react';
2+
import LanguageSelector from '..';
3+
4+
jest.mock('../../../../hooks/useLocale', () => ({
5+
useLocale: () => ({
6+
availableLocales: [
7+
{ code: 'en', name: 'English', localName: 'English' },
8+
{ code: 'es', name: 'Spanish', localName: 'Español' },
9+
],
10+
currentLocale: { code: 'en', name: 'English', localName: 'English' },
11+
}),
12+
}));
13+
14+
describe('LanguageSelector', () => {
15+
test('clicking the language switch button toggles the dropdown display', () => {
16+
render(<LanguageSelector />);
17+
const button = screen.getByRole('button');
18+
expect(screen.queryByText('English')).not.toBeVisible();
19+
fireEvent.click(button);
20+
expect(screen.queryByText('English')).toBeVisible();
21+
fireEvent.click(button);
22+
expect(screen.queryByText('English')).not.toBeVisible();
23+
});
24+
25+
test('renders the Dropdown component with correct style', () => {
26+
render(<LanguageSelector />);
27+
const button = screen.getByRole('button');
28+
fireEvent.click(button);
29+
const dropdown = screen.getByRole('list');
30+
expect(dropdown).toHaveStyle(
31+
'position: absolute; top: 60%; right: 0; margin: 0;'
32+
);
33+
});
34+
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
.languageSwitch {
2+
background: none;
3+
border: none;
4+
color: var(--color-text-accent);
5+
cursor: pointer;
6+
line-height: 0;
7+
padding: 0;
8+
}
9+
10+
.container {
11+
display: inline;
12+
position: relative;
13+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import LanguageSelector from '.';
2+
export default { component: LanguageSelector };
3+
4+
export const Default = () => (
5+
<div
6+
style={{
7+
backgroundColor: 'FloralWhite',
8+
textAlign: 'right',
9+
padding: '20px',
10+
}}
11+
>
12+
<LanguageSelector />
13+
</div>
14+
);
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { useMemo, useState } from 'react';
2+
import { MdOutlineTranslate } from 'react-icons/md';
3+
import { useLocale } from '../../../hooks/useLocale';
4+
import Dropdown from '../Dropdown';
5+
import styles from './index.module.scss';
6+
7+
const dropdownStyle = {
8+
position: 'absolute',
9+
top: '60%',
10+
right: '0',
11+
margin: 0,
12+
};
13+
14+
const LanguageSelector = () => {
15+
const [showDropdown, setShowDropdown] = useState(false);
16+
17+
const { availableLocales, currentLocale } = useLocale();
18+
19+
const dropdownItems = useMemo(
20+
() =>
21+
availableLocales.map(locale => ({
22+
title: locale.localName,
23+
label: locale.name,
24+
onClick: () => {
25+
// TODO: "locale changing logic yet to be implemented"
26+
},
27+
active: currentLocale.code === locale.code,
28+
})),
29+
[availableLocales, currentLocale]
30+
);
31+
32+
return (
33+
<div className={styles.container}>
34+
<button
35+
type="button"
36+
className={styles.languageSwitch}
37+
onClick={() => setShowDropdown(!showDropdown)}
38+
aria-expanded={showDropdown}
39+
>
40+
<span className="sr-only">Switch Language</span>
41+
<MdOutlineTranslate />
42+
</button>
43+
<Dropdown
44+
items={dropdownItems}
45+
shouldShow={showDropdown}
46+
styles={dropdownStyle}
47+
/>
48+
</div>
49+
);
50+
};
51+
52+
export default LanguageSelector;

package-lock.json

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"npm-run-all": "^4.1.5",
4949
"react": "^18.2.0",
5050
"react-dom": "^18.2.0",
51+
"react-icons": "^4.8.0",
5152
"react-intl": "^6.3.2",
5253
"remark-gfm": "^3.0.1",
5354
"sass": "^1.60.0",

0 commit comments

Comments
 (0)