Skip to content

Commit

Permalink
web: Start adapting locale selection
Browse files Browse the repository at this point in the history
  • Loading branch information
dgdavid committed May 16, 2024
1 parent c5a5381 commit 53d2d1c
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 269 deletions.
26 changes: 21 additions & 5 deletions web/src/components/core/ListSearch.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
* find current contact information at www.suse.com.
*/

import React from "react";

import React, { useState } from "react";
import { SearchInput } from "@patternfly/react-core";
import { _ } from "~/i18n";
import { noop, useDebounce } from "~/utils";

Expand All @@ -38,6 +38,7 @@ const search = (elements, term) => {
};

/**
* TODO: Rename
* Input field for searching in a given list of elements.
* @component
*
Expand All @@ -51,11 +52,26 @@ export default function ListSearch({
elements = [],
onChange: onChangeProp = noop
}) {
const searchHandler = useDebounce(term => onChangeProp(search(elements, term)), 500);
const [value, setValue] = useState("");
const [resultSize, setResultSize] = useState(elements.length);
const searchHandler = useDebounce(term => {
const result = search(elements, term);
setResultSize(result.length);
onChangeProp(result);
}, 500);

const onChange = (e) => searchHandler(e.target.value);
const onChange = (value) => {
setValue(value);
searchHandler(value);
};

return (
<input role="search" type="text" placeholder={placeholder} onChange={onChange} />
<SearchInput
placeholder={placeholder}
value={value}
onChange={(_, value) => onChange(value)}
onClear={() => onChangeProp(elements)}
resultsCount={resultSize}
/>
);
}
2 changes: 1 addition & 1 deletion web/src/components/core/ListSearch.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ const FruitList = ({ fruits }) => {
it("searches for elements matching the given term (case-insensitive)", async () => {
const { user } = plainRender(<FruitList fruits={fruits} />);

const searchInput = screen.getByRole("search");
const searchInput = screen.getByRole("textbox");

// Search for "medium" size fruit
await user.type(searchInput, "medium");
Expand Down
120 changes: 11 additions & 109 deletions web/src/components/l10n/L10nPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@
*/

import React, { useState } from "react";
import { Link } from "react-router-dom";
import { Button, Form } from "@patternfly/react-core";
import { sprintf } from "sprintf-js";

import { useInstallerClient } from "~/context/installer";
import { _ } from "~/i18n";
import { If, Page, Popup, Section } from "~/components/core";
import { KeymapSelector, LocaleSelector, TimezoneSelector } from "~/components/l10n";
import { If, Popup, Section } from "~/components/core";
import { KeymapSelector, TimezoneSelector } from "~/components/l10n";
import { noop } from "~/utils";
import { useL10n } from "~/context/l10n";
import { useProduct } from "~/context/product";
Expand Down Expand Up @@ -145,120 +146,22 @@ const TimezoneSection = () => {
);
};

/**
* Popup for selecting a locale.
* @component
*
* @param {object} props
* @param {function} props.onFinish - Callback to be called when the locale is correctly selected.
* @param {function} props.onCancel - Callback to be called when the locale selection is canceled.
*/
const LocalePopup = ({ onFinish = noop, onCancel = noop }) => {
const { l10n } = useInstallerClient();
const { locales, selectedLocales } = useL10n();
const { selectedProduct } = useProduct();
const [localeId, setLocaleId] = useState(selectedLocales[0]?.id);

const sortedLocales = locales.sort((locale1, locale2) => {
const localeText = l => [l.name, l.territory].join('').toLowerCase();
return localeText(locale1) > localeText(locale2) ? 1 : -1;
});

const onSubmit = async (e) => {
e.preventDefault();

const [locale] = selectedLocales;

if (localeId !== locale?.id) {
await l10n.setLocales([localeId]);
}

onFinish();
};

return (
<Popup
isOpen
title={_("Select language")}
description={sprintf(_("%s will use the selected language."), selectedProduct.name)}
blockSize="large"
>
<Form id="localeForm" onSubmit={onSubmit}>
<LocaleSelector value={localeId} locales={sortedLocales} onChange={setLocaleId} />
</Form>
<Popup.Actions>
<Popup.Confirm form="localeForm" type="submit">
{_("Accept")}
</Popup.Confirm>
<Popup.Cancel onClick={onCancel} />
</Popup.Actions>
</Popup>
);
};

/**
* Button for opening the selection of locales.
* @component
*
* @param {object} props
* @param {React.ReactNode} props.children - Button children.
*/
const LocaleButton = ({ children }) => {
const [isPopupOpen, setIsPopupOpen] = useState(false);

const openPopup = () => setIsPopupOpen(true);
const closePopup = () => setIsPopupOpen(false);

return (
<>
<Button
variant="link"
className="p-0"
onClick={openPopup}
>
{children}
</Button>

<If
condition={isPopupOpen}
then={
<LocalePopup
isOpen
onFinish={closePopup}
onCancel={closePopup}
/>
}
/>
</>
);
};

/**
* Section for configuring locales.
* @component
*/
const LocaleSection = () => {
const { selectedLocales } = useL10n();

const [locale] = selectedLocales;

return (
<Section title={_("Language")} icon="translate">
<If
condition={locale}
then={
<>
<p>{locale?.name} - {locale?.territory}</p>
<LocaleButton>{_("Change language")}</LocaleButton>
</>
}
else={
<>
<p>{_("Language not selected yet")}</p>
<LocaleButton>{_("Select language")}</LocaleButton>
</>
}
/>
<p>
{locale ? `${locale.name} - ${locale.territory}` : _("Language not selected yet")}
</p>
<Link to="language/select">
{locale ? _("Change language") : _("Select language")}
</Link>
</Section>
);
};
Expand Down Expand Up @@ -380,11 +283,10 @@ const KeymapSection = () => {
*/
export default function L10nPage() {
return (
// TRANSLATORS: page title
<Page icon="globe" title={_("Localization")}>
<>
<LocaleSection />
<KeymapSection />
<TimezoneSection />
</Page>
</>
);
}
104 changes: 104 additions & 0 deletions web/src/components/l10n/LocaleSelection.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* Copyright (c) [2023-2024] SUSE LLC
*
* All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of version 2 of the GNU General Public License as published
* by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, contact SUSE LLC.
*
* To contact SUSE LLC about this file by physical or electronic mail, you may
* find current contact information at www.suse.com.
*/

import React, { useEffect, useState } from "react";
import {
Form, FormGroup,
Radio,
Stack,
Text
} from "@patternfly/react-core";
import { useNavigate } from "react-router-dom";
import { _ } from "~/i18n";
import { useL10n } from "~/context/l10n";
import { useInstallerClient } from "~/context/installer";
import { ListSearch, Page } from "~/components/core";
import textStyles from '@patternfly/react-styles/css/utilities/Text/text';

// TODO: Add documentation and typechecking
// TODO: Evaluate if worth it extracting the selector
export default function LocaleSelection() {
const { l10n } = useInstallerClient();
const { locales, selectedLocales } = useL10n();
const [selected, setSelected] = useState(selectedLocales[0]);
const [filteredLocales, setFilteredLocales] = useState(locales);
const navigate = useNavigate();

const searchHelp = _("Filter by language, territory or locale code");

useEffect(() => {
setFilteredLocales(locales);
}, [locales, setFilteredLocales]);

const onSubmit = async (e) => {
e.preventDefault();
const dataForm = new FormData(e.target);
const nextLocaleId = JSON.parse(dataForm.get("locale"))?.id;

if (nextLocaleId !== selectedLocales[0]?.id) {
await l10n.setLocales([nextLocaleId]);
}

navigate("..");
};

return (
<>
<Page.MainContent>
<Stack hasGutter>
<ListSearch placeholder={searchHelp} elements={locales} onChange={setFilteredLocales} />
<Form id="localeSelection" onSubmit={onSubmit}>
<FormGroup isStack>
{filteredLocales.map((locale) => (
<Radio
key={locale.id}
name="locale"
id={locale.id}
onChange={() => setSelected(locale)}
label={
<>
<span className={`${textStyles.fontSizeLg}`}>
<b>{locale.name}</b>
</span> <Text component="small">{locale.id}</Text>
</>
}
description={
<>
<span className={textStyles.fontSizeMd}>{locale.territory}</span>
</>
}
value={JSON.stringify(locale)}
checked={locale === selected}
/>
))}
</FormGroup>
</Form>
</Stack>
</Page.MainContent>
<Page.NextActions>
<Page.CancelAction />
<Page.Action type="submit" form="localeSelection">
{_("Select")}
</Page.Action>
</Page.NextActions>
</>
);
}
72 changes: 0 additions & 72 deletions web/src/components/l10n/LocaleSelector.jsx

This file was deleted.

Loading

0 comments on commit 53d2d1c

Please sign in to comment.