-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #273 from open-formulieren/issue/2253-language-sel…
…ection-component [#2253] Language Selection Component
- Loading branch information
Showing
28 changed files
with
856 additions
and
57 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Submodule design-tokens
updated
5 files
+2 −2 | package-lock.json | |
+1 −1 | package.json | |
+1 −1 | src/community/utrecht/textarea.tokens.json | |
+1 −1 | src/community/utrecht/textbox.tokens.json | |
+7 −0 | src/components/language-selection.tokens.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,35 +1,55 @@ | ||
import React from 'react'; | ||
import ReactDOM from 'react-dom'; | ||
import PropTypes from 'prop-types'; | ||
import {Switch, Route} from 'react-router-dom'; | ||
|
||
import Form from 'components/Form'; | ||
import { Layout, LayoutRow } from 'components/Layout'; | ||
import ManageAppointment from 'components/appointments/ManageAppointment'; | ||
import LanguageSelection from 'components/LanguageSelection'; | ||
|
||
|
||
const LanguageSwitcher = ({ target = null }) => ( | ||
target ? ( | ||
ReactDOM.createPortal(<LanguageSelection />, target) | ||
) : ( | ||
<LayoutRow> | ||
<LanguageSelection /> | ||
</LayoutRow> | ||
) | ||
); | ||
|
||
LanguageSwitcher.propTypes = { | ||
target: PropTypes.instanceOf(Element), | ||
}; | ||
|
||
/* | ||
Top level router - routing between an actual form or supporting screens. | ||
*/ | ||
const App = (props) => { | ||
const App = ({ languageSelectorTarget, ...props }) => { | ||
return ( | ||
<Layout> | ||
<LanguageSwitcher target={languageSelectorTarget} /> | ||
|
||
<LayoutRow> | ||
|
||
<Switch> | ||
|
||
{/* Anything dealing with appointments gets routed to it's own sub-router */} | ||
<Route path="/afspraak*" component={ManageAppointment} /> | ||
|
||
{/* All the rest goes to the actual form flow */} | ||
<Route path="/"> | ||
<Form {...props} /> | ||
</Route> | ||
|
||
</Switch> | ||
|
||
</LayoutRow> | ||
</Layout> | ||
); | ||
}; | ||
|
||
App.propTypes = { | ||
languageSelectorTarget: PropTypes.instanceOf(Element), | ||
}; | ||
|
||
export default App; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
import PropTypes from 'prop-types'; | ||
import React, { useContext, useState } from 'react'; | ||
import { FormattedMessage, useIntl } from 'react-intl'; | ||
import { useAsync } from 'react-use'; | ||
|
||
import { get, put } from 'api'; | ||
import { ConfigContext } from 'Context'; | ||
import Loader from 'components/Loader'; | ||
|
||
import LanguageSelectionDisplay from './LanguageSelectionDisplay'; | ||
|
||
|
||
const DEFAULT_HEADING = ( | ||
<FormattedMessage | ||
description="Language selection heading" | ||
defaultMessage="Choose language" | ||
/> | ||
); | ||
|
||
|
||
const LanguageSelection = ({ | ||
heading=DEFAULT_HEADING, | ||
headingLevel=2, | ||
onLanguageChanged=console.log, | ||
}) => { | ||
// Hook uses | ||
const { baseUrl } = useContext(ConfigContext); | ||
const { locale } = useIntl(); | ||
const [ updatingLanguage, setUpdatingLanguage ] = useState(false); | ||
const [ err, setErr ] = useState(null); | ||
|
||
// fetch language information from API | ||
const { | ||
loading, | ||
value: languageInfo, | ||
error, | ||
} = useAsync( | ||
async () => { | ||
const result = await get(`${baseUrl}i18n/info`); | ||
// the browser preferences may have activated a different language than the | ||
// client-side default language. In that case, we need to inform the parent | ||
// components that the UI language needs to update. | ||
// | ||
// This will trigger the value of `locale` to change from the `useIntl()` hook. | ||
if (result.current !== locale) { | ||
onLanguageChanged(result.current); | ||
} | ||
return result; | ||
}, | ||
[baseUrl, locale] | ||
); | ||
|
||
const anyError = err || error ; | ||
if (anyError) { | ||
throw anyError; // bubble up to boundary | ||
} | ||
if (loading || updatingLanguage) { | ||
return <Loader modifiers={["small"]} />; | ||
} | ||
|
||
const { languages } = languageInfo; | ||
// transform language information for display | ||
const items = languages.map( ({ code, name }) => ({ | ||
lang: code, | ||
textContent: code.toUpperCase(), | ||
label: name, | ||
current: code === locale, | ||
})); | ||
|
||
/** | ||
* Event handler for user interaction to change the language. | ||
* @param {String} languageCode The code of the (new) language to activate. | ||
* @return {Void} | ||
*/ | ||
const onLanguageChange = async (languageCode) => { | ||
// do nothing if this is already the active language | ||
// or if an update is being processed. | ||
if (updatingLanguage || languageCode === locale) return; | ||
|
||
setUpdatingLanguage(true); | ||
// activate other language in backend | ||
try { | ||
await put(`${baseUrl}i18n/language`, { code: languageCode }); | ||
} catch (err) { | ||
// set error in state, which gets re-thrown in render and bubbles up | ||
// to error bounary | ||
setUpdatingLanguage(false); | ||
setErr(err); | ||
} | ||
// update UI language | ||
setUpdatingLanguage(false); | ||
onLanguageChanged(languageCode); | ||
}; | ||
|
||
return ( | ||
<LanguageSelectionDisplay | ||
onLanguageChange={onLanguageChange} | ||
items={items} | ||
headingId="of-language-selection" | ||
heading={heading} | ||
headingLevel={headingLevel} | ||
/> | ||
); | ||
}; | ||
|
||
LanguageSelection.propTypes = { | ||
heading: PropTypes.node, | ||
headingLevel: PropTypes.number, | ||
onLanguageChanged: PropTypes.func, | ||
}; | ||
|
||
export default LanguageSelection; |
Oops, something went wrong.