Fixed/improved issues with effective message bundles tab (#26760)

* added default locale to the search

Signed-off-by: Agnieszka Gancarczyk <agancarc@redhat.com>

* filtered out duplicate locales

Signed-off-by: Agnieszka Gancarczyk <agancarc@redhat.com>

* fixed dropdown and locales

Signed-off-by: Agnieszka Gancarczyk <agancarc@redhat.com>

* fixed dropdown and locales

Signed-off-by: Agnieszka Gancarczyk <agancarc@redhat.com>

* locale chip to lower case

Signed-off-by: Agnieszka Gancarczyk <agancarc@redhat.com>

* fixed type

Signed-off-by: Agnieszka Gancarczyk <agancarc@redhat.com>

* fixed empty chip issue

Signed-off-by: Agnieszka Gancarczyk <agancarc@redhat.com>

* added DropdownPanel component

Signed-off-by: Agnieszka Gancarczyk <agancarc@redhat.com>

* improved css

Signed-off-by: Agnieszka Gancarczyk <agancarc@redhat.com>

* improved css

Signed-off-by: Agnieszka Gancarczyk <agancarc@redhat.com>

---------

Signed-off-by: Agnieszka Gancarczyk <agancarc@redhat.com>
Co-authored-by: Agnieszka Gancarczyk <agancarc@redhat.com>
This commit is contained in:
agagancarczyk 2024-02-06 10:15:30 +00:00 committed by GitHub
parent 29018abc95
commit c29188c629
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 177 additions and 38 deletions

View file

@ -0,0 +1,90 @@
import { useEffect, useRef } from "react";
import { Button, Icon } from "@patternfly/react-core";
import { CaretDownIcon } from "@patternfly/react-icons";
import "./dropdown-panel.css";
type DropdownPanelProps = {
actionButtonText: string;
actionButtonVariant:
| "primary"
| "secondary"
| "tertiary"
| "danger"
| "link"
| "plain"
| "control";
buttonText: string;
children: React.ReactNode;
setSearchDropdownOpen: (open: boolean) => void;
searchDropdownOpen: boolean;
onSubmitAction: () => void;
width: string;
};
const DropdownPanel: React.FC<DropdownPanelProps> = ({
actionButtonText,
actionButtonVariant,
buttonText,
setSearchDropdownOpen,
searchDropdownOpen,
children,
onSubmitAction,
width,
}) => {
const dropdownRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (
dropdownRef.current &&
!dropdownRef.current.contains(event.target as Node)
) {
setSearchDropdownOpen(false);
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, [setSearchDropdownOpen]);
useEffect(() => {
const handleVisibilityChange = () => {
if (document.visibilityState === "hidden") {
setSearchDropdownOpen(false);
}
};
document.addEventListener("visibilitychange", handleVisibilityChange);
return () =>
document.removeEventListener("visibilitychange", handleVisibilityChange);
}, [setSearchDropdownOpen]);
return (
<div ref={dropdownRef}>
<button
className="kc-dropdown-panel"
onClick={() => setSearchDropdownOpen(!searchDropdownOpen)}
aria-label={buttonText}
style={{ width }}
>
{buttonText}
<Icon className="kc-dropdown-panel-icon">
<CaretDownIcon />
</Icon>
</button>
{searchDropdownOpen && (
<div className="kc-dropdown-panel-content">{children}</div>
)}
<Button
variant={actionButtonVariant}
className="pf-u-ml-md"
onClick={onSubmitAction}
data-testid="dropdownPanelBtn"
>
{actionButtonText}
</Button>
</div>
);
};
export default DropdownPanel;

View file

@ -0,0 +1,37 @@
.kc-dropdown-panel {
background-color: transparent;
padding: var(--pf-global--spacer--form-element) var(--pf-global--spacer--sm)
var(--pf-global--spacer--form-element) var(--pf-global--spacer--sm);
border-color: var(--pf-global--BorderColor--300)
var(--pf-global--BorderColor--300) var(--pf-global--BorderColor--200)
var(--pf-global--BorderColor--300);
border-width: var(--pf-global--BorderWidth--sm);
border-style: solid;
text-align: left;
cursor: pointer;
}
.kc-dropdown-panel:hover {
border-bottom-color: var(--pf-global--primary-color--100);
}
.kc-dropdown-panel:focus,
.kc-dropdown-panel:active {
border-bottom-color: var(--pf-global--active-color--100);
border-bottom-width: var(--pf-global--BorderWidth--md);
}
.kc-dropdown-panel-icon {
float: right;
margin-top: 0.2rem;
}
.kc-dropdown-panel-content {
display: block;
position: absolute;
background-color: var(--pf-global--Color--light-100);
min-width: 10rem;
box-shadow: 0px 0.5rem 1rem 0px rgba(0, 0, 0, 0.2);
padding: 0.75rem;
z-index: 1;
}

View file

@ -4,8 +4,6 @@ import {
Chip,
ChipGroup,
Divider,
Dropdown,
DropdownToggle,
Flex,
FlexItem,
Form,
@ -31,9 +29,11 @@ import { useWhoAmI } from "../../context/whoami/WhoAmI";
import { DEFAULT_LOCALE } from "../../i18n/i18n";
import { localeToDisplayName } from "../../util";
import useLocaleSort, { mapByKey } from "../../utils/useLocaleSort";
import DropdownPanel from "../../components/dropdown-panel/DropdownPanel";
type EffectiveMessageBundlesProps = {
defaultSupportedLocales: string[];
defaultLocales: string[];
};
type EffectiveMessageBundlesSearchForm = {
@ -52,6 +52,7 @@ const defaultValues: EffectiveMessageBundlesSearchForm = {
export const EffectiveMessageBundles = ({
defaultSupportedLocales,
defaultLocales,
}: EffectiveMessageBundlesProps) => {
const { t } = useTranslation();
const { realm } = useRealm();
@ -83,13 +84,17 @@ export const EffectiveMessageBundles = ({
}
return localeSort(
Object.values(themes)
Object.values(themes as Record<string, { name: string }[]>)
.flatMap((theme) => theme.map((item) => item.name))
.filter((value, index, self) => self.indexOf(value) === index),
(name) => name,
);
}, [themes]);
const combinedLocales = useMemo(() => {
return Array.from(new Set([...defaultLocales, ...defaultSupportedLocales]));
}, [defaultLocales, defaultSupportedLocales]);
const filterLabels: Record<keyof EffectiveMessageBundlesSearchForm, string> =
{
theme: t("theme"),
@ -193,23 +198,20 @@ export const EffectiveMessageBundles = ({
</TextContent>
</FlexItem>
<FlexItem>
<Dropdown
id="effective-message-bundles-search-select"
data-testid="EffectiveMessageBundlesSearchSelector"
toggle={
<DropdownToggle
data-testid="effectiveMessageBundlesSearchSelectorToggle"
onToggle={(isOpen) => setSearchDropdownOpen(isOpen)}
>
{t("searchForEffectiveMessageBundles")}
</DropdownToggle>
}
isOpen={searchDropdownOpen}
<DropdownPanel
actionButtonText={t("refresh")}
actionButtonVariant="primary"
buttonText={t("searchForEffectiveMessageBundles")}
setSearchDropdownOpen={setSearchDropdownOpen}
searchDropdownOpen={searchDropdownOpen}
onSubmitAction={submitSearch}
width="20vw"
>
<Form
isHorizontal
className="pf-c-form pf-u-mx-lg pf-u-mb-lg pf-u-w-25vw"
className="pf-u-w-25vw"
data-testid="effectiveMessageBundlesSearchForm"
onSubmit={(e) => e.preventDefault()}
>
<FormGroup label={t("theme")} fieldId="kc-theme" isRequired>
<Controller
@ -229,10 +231,11 @@ export const EffectiveMessageBundles = ({
}}
variant={SelectVariant.single}
typeAheadAriaLabel="Select"
onToggle={(isOpen) => setSelectThemesOpen(isOpen)}
onToggle={setSelectThemesOpen}
selections={field.value}
onSelect={(_, selectedValue) => {
field.onChange(selectedValue.toString());
setSelectThemesOpen(false);
}}
onClear={(theme) => {
theme.stopPropagation();
@ -293,10 +296,11 @@ export const EffectiveMessageBundles = ({
}}
variant={SelectVariant.single}
typeAheadAriaLabel="Select"
onToggle={(isOpen) => setSelectThemeTypeOpen(isOpen)}
onToggle={setSelectThemeTypeOpen}
selections={field.value}
onSelect={(_, selectedValue) => {
field.onChange(selectedValue.toString());
setSelectThemeTypeOpen(false);
}}
onClear={(themeType) => {
themeType.stopPropagation();
@ -353,10 +357,11 @@ export const EffectiveMessageBundles = ({
}}
variant={SelectVariant.single}
typeAheadAriaLabel="Select"
onToggle={(isOpen) => setSelectLanguageOpen(isOpen)}
onToggle={setSelectLanguageOpen}
selections={field.value}
onSelect={(_, selectedValue) => {
field.onChange(selectedValue.toString());
setSelectLanguageOpen(false);
}}
onClear={(language) => {
language.stopPropagation();
@ -392,7 +397,7 @@ export const EffectiveMessageBundles = ({
isDisabled
/>,
].concat(
defaultSupportedLocales.map((option) => (
combinedLocales.map((option) => (
<SelectOption key={option} value={option}>
{localeToDisplayName(option, whoAmI.getLocale())}
</SelectOption>
@ -414,10 +419,16 @@ export const EffectiveMessageBundles = ({
value={field.value.join(" ")}
onChange={(e) => {
const target = e.target as HTMLInputElement;
const words = target.value
.split(" ")
.map((word) => word.trim());
field.onChange(words);
const input = target.value;
if (input.trim().length === 0) {
field.onChange([]);
} else {
const words = input
.split(" ")
.map((word) => word.trim());
field.onChange(words);
}
}}
/>
<ChipGroup>
@ -440,7 +451,7 @@ export const EffectiveMessageBundles = ({
)}
/>
</FormGroup>
<ActionGroup>
<ActionGroup className="pf-u-mt-sm">
<Button
variant={"primary"}
onClick={() => {
@ -462,25 +473,16 @@ export const EffectiveMessageBundles = ({
</Button>
</ActionGroup>
</Form>
</Dropdown>
<Button
variant="primary"
className="pf-u-ml-md"
onClick={() => submitSearch()}
data-testid="refresh-effective-message-bundles-btn"
>
{t("refresh")}
</Button>
</DropdownPanel>
</FlexItem>
<FlexItem>
{Object.entries(activeFilters).length > 0 && (
<div className="keycloak__searchChips pf-u-ml-md">
<>
{Object.entries(activeFilters).map((filter) => {
const [key, value] = filter as [
keyof EffectiveMessageBundlesSearchForm,
string | string[],
];
return (
<ChipGroup
className="pf-u-mt-md pf-u-mr-md"
@ -492,7 +494,10 @@ export const EffectiveMessageBundles = ({
{typeof value === "string" ? (
<Chip isReadOnly>
{key === "locale"
? localeToDisplayName(value, whoAmI.getLocale())
? localeToDisplayName(
value,
whoAmI.getLocale(),
)?.toLowerCase()
: value}
</Chip>
) : (
@ -508,7 +513,7 @@ export const EffectiveMessageBundles = ({
</ChipGroup>
);
})}
</div>
</>
)}
</FlexItem>
</Flex>

View file

@ -68,6 +68,12 @@ export const LocalizationTab = ({ save, realm }: LocalizationTabProps) => {
defaultValue: realm.internationalizationEnabled,
});
const defaultLocales = useWatch({
name: "defaultLocale",
control,
defaultValue: realm.defaultLocale ? [realm.defaultLocale] : [],
});
return (
<Tabs
activeKey={activeTab}
@ -248,6 +254,7 @@ export const LocalizationTab = ({ save, realm }: LocalizationTabProps) => {
>
<EffectiveMessageBundles
defaultSupportedLocales={defaultSupportedLocales}
defaultLocales={defaultLocales}
/>
</Tab>
</Tabs>