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:
parent
29018abc95
commit
c29188c629
4 changed files with 177 additions and 38 deletions
|
@ -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;
|
|
@ -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;
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue