Skip to content
This repository has been archived by the owner on Sep 18, 2024. It is now read-only.

Commit

Permalink
Merge pull request #1357 from navikt/forbedre-sprakvelger-for-skjerml…
Browse files Browse the repository at this point in the history
…eser

Forenkle språkvelger for bedre bruk i Jaws
  • Loading branch information
terjeofnorway authored Feb 8, 2023
2 parents 79f8282 + 05a2a17 commit f166508
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 166 deletions.
50 changes: 0 additions & 50 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@
"dayjs": "1.11.5",
"detect-browser": "5.3.0",
"dotenv": "8.6.0",
"downshift": "6.1.12",
"express": "4.18.1",
"express-urlrewrite": "2.0.0",
"html-react-parser": "1.4.12",
Expand Down
77 changes: 51 additions & 26 deletions src/komponenter/header/common/sprakvelger/SprakVelger.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@
}

.container:only-child {
width: 100%;
display: flex;
flex-direction: row-reverse;
width: 100%;
}

.knapp {
padding: 0.5rem 0.5rem;
width: 100%;
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 0.5rem 0.5rem;
width: 100%;

.chevronNed {
align-self: center;
Expand All @@ -37,43 +37,41 @@
}

.ikon {
width: 20px;
height: 20px;
filter: invert(50%);
height: 20px;
margin-right: 1em;
}

.option {
display: flex;
flex-direction: row;
align-items: center;

svg {
margin-right: 0.1rem;
}

.not-selected {
margin-left: 1.25rem;
}
width: 20px;
}

.sirkel {
margin-right: 0.25rem;
}

.menu {
border: none;
box-sizing: border-box;
display: none;
margin: 1px 0 0;
padding: 0;
z-index: 100;
max-height: 10rem;
position: absolute;
overflow-y: auto;
overflow-x: hidden;
overflow-y: auto;
padding: 0;
position: absolute;
width: 100%;
box-sizing: border-box;
z-index: 100;
}
.menuList {
padding: 0.5rem;

.menuOpenState {
background-color: white;
border-radius: 0 0 4px 4px;
border-top: none;
border: 1px solid var(--a-gray-200);
box-shadow: 0 0.05rem 0.25rem 0.125rem rgba(0, 0, 0, 0.08);
display: block;
outline: none;
}

.menuListItem {
cursor: pointer;
display: flex;
border: 1px solid var(--a-gray-200);
Expand All @@ -83,3 +81,30 @@
border-bottom: 0;
}
}

.option {
align-items: center;
background-color: transparent;
border: 0;
cursor: pointer;
display: flex;
flex-direction: row;
margin: 0;
outline: none;
padding: 0.5rem;
width: 100%;

svg {
margin-right: 0.1rem;
}

.notSelected {
margin-left: 1.25rem;
}

&:hover,
&:focus-visible {
background-color: var(--a-blue-600);
color: white;
}
}
130 changes: 73 additions & 57 deletions src/komponenter/header/common/sprakvelger/SprakVelger.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import React from 'react';
import { BodyShort } from '@navikt/ds-react';
import Globe from 'ikoner/globe.svg';
import React, { useEffect } from 'react';

import { BodyShort } from '@navikt/ds-react';
import { Expand } from '@navikt/ds-icons';
import { useSelect } from 'downshift';
import classNames from 'classnames';
import { decoratorLanguageCookie } from '../../Header';
import { cookieOptions } from '../../../../server/cookieSettings';
import { AvailableLanguage } from 'store/reducers/language-duck';
Expand All @@ -13,16 +12,10 @@ import { useCookies } from 'react-cookie';
import { useSelector, useStore } from 'react-redux';
import { AppState } from 'store/reducers';
import { Bilde } from '../../../common/bilde/Bilde';
import Globe from 'ikoner/globe.svg';
import SprakVelgerItem from './SprakVelgerItem';
import style from 'komponenter/header/common/sprakvelger/SprakVelger.module.scss';

export const farger = {
navGra20: '#C6C2BF',
navBla: '#0067C5',
};

const selectorLabel = 'Språk/Language';

export type LocaleOption = AvailableLanguage & { label: string };

interface Props {
Expand All @@ -33,73 +26,96 @@ export const SprakVelger = (props: Props) => {
const store = useStore();
const availableLanguages = props.languages;
const { language } = useSelector((state: AppState) => state.language);
const [isMenuOpen, setIsMenuOpen] = React.useState(false);
const [selectedLocale, setSelectedLocale] = React.useState<LocaleOption | null>(null);
const [, setCookie] = useCookies([decoratorLanguageCookie]);
const options = transformOptions(availableLanguages).sort((a, b) => (a.label > b.label ? -1 : 1));
const languageOptions = transformOptions(availableLanguages).sort((a, b) => (a.label > b.label ? -1 : 1));

useEffect(() => {
if (selectedLocale === null) {
const selected = languageOptions.find((option) => option.locale === language);
setSelectedLocale(selected || null);
}
}, []);

const onChange = (selected: LocaleOption) => {
const { label, ...selectedLanguage } = selected;

setSelectedLocale(selected);
setCookie(decoratorLanguageCookie, selectedLanguage.locale, cookieOptions);
store.dispatch(languageDuck.actionCreator({ language: selectedLanguage.locale }));

if (selectedLanguage.handleInApp) {
postMessageToApp('languageSelect', selectedLanguage);
} else {
window.location.assign(selectedLanguage.url);
}

toggleMenu(false);
};

const onKeyUp = (e: KeyboardEvent) => {
if (e.key === 'Tab') {
const buttons = document.querySelectorAll(`.${style.sprakvelger} button`);
// If focus is not on any of the language option buttons, close the menu
const hasOptionFocus = Array.from(buttons).some((button) => button === document.activeElement);
if (!hasOptionFocus) {
toggleMenu(false);
}
}

if (e.key === 'Escape') {
toggleMenu(false);
}
};

const onClick = (e: MouseEvent) => {
const isClickOutside = !e.composedPath().some((el) => el === document.querySelector(`.${style.sprakvelger}`));
if (isClickOutside) {
toggleMenu(false);
}
};

const toggleMenu = (open?: boolean) => {
const isIntentToOpen = open !== undefined ? open : !isMenuOpen;

if (isIntentToOpen) {
window.addEventListener('keyup', onKeyUp);
window.addEventListener('click', onClick);
} else {
window.removeEventListener('keyup', onKeyUp);
window.removeEventListener('click', onClick);
}

setIsMenuOpen(isIntentToOpen);
};
const { isOpen, selectedItem, getToggleButtonProps, getMenuProps, highlightedIndex, getItemProps } = useSelect({
items: options,
itemToString: (item) => item?.label || '',
onSelectedItemChange: ({ selectedItem }) => onChange(selectedItem as LocaleOption),
selectedItem: options.find((option) => option.locale === language),
});

const ulStyle = isOpen
? {
boxShadow: '0 0.05rem 0.25rem 0.125rem rgba(0, 0, 0, 0.08)',
border: '1px solid',
borderRadius: '0 0 4px 4px',
outline: 'none',
borderColor: farger.navGra20,
borderTop: 'none',
}
: {
display: 'none',
border: 'none',
};
// Adding aria-controls and removing aria-labelledby prop from downshift (to avoid an extra reading of label on screen readers)
let buttonProps = getToggleButtonProps();
delete buttonProps['aria-labelledby'];
let menuProps = getMenuProps();
delete menuProps['aria-labelledby'];
menuProps.id = 'sprakvelger_menuID';
buttonProps['aria-controls'] = menuProps.id;

return (
<div className={style.container}>
<nav className={style.sprakvelger}>
<button {...buttonProps} className={`${style.knapp} skjemaelement__input`} type="button">
<span className={style.knappTekst}>
<button
className={`${style.knapp} skjemaelement__input`}
type="button"
aria-expanded={isMenuOpen}
onClick={() => toggleMenu()}
>
<div className={style.knappTekst}>
<Bilde asset={Globe} className={style.ikon} />
<BodyShort size="small" as={'span'}>
{selectorLabel}
<span lang="no">Språk</span>/<span lang="en">Language</span>
</BodyShort>
</span>
</div>
<Expand className={style.chevronNed} aria-hidden />
</button>
<ul {...menuProps} className={style.menu} style={ulStyle}>
<>
{options.map((item, index) => (
<SprakVelgerItem
key={index}
item={item}
index={index}
highlightedIndex={highlightedIndex}
itemProps={getItemProps({ item, index })}
selectedItem={selectedItem}
/>
))}
</>
<ul className={classNames(style.menu, isMenuOpen && style.menuOpenState)}>
{languageOptions.map((option, index) => (
<SprakVelgerItem
key={index}
item={option}
index={index}
selectedItem={selectedLocale}
onSelectedItemChange={() => onChange(option as LocaleOption)}
/>
))}
</ul>
</nav>
</div>
Expand Down
Loading

0 comments on commit f166508

Please sign in to comment.