move keycloak select to ui-shared and fix typeahead (#30209)

* move keycloak select to ui-shared and fix typeahead

Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>

* Fix the account console test

Signed-off-by: Hynek Mlnarik <hmlnarik@redhat.com>

* Fix cypress tests

Signed-off-by: Hynek Mlnarik <hmlnarik@redhat.com>

* fix for when value is an array

Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>

* fix for when value is an array

Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>

* add support for array selecting single value

Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>

* fixed saying open once clicked outside and value

Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>

* small issue when pressing enter

Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>

---------

Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>
Signed-off-by: Hynek Mlnarik <hmlnarik@redhat.com>
Co-authored-by: Hynek Mlnarik <hmlnarik@redhat.com>
This commit is contained in:
Erik Jan de Wit 2024-06-07 18:05:03 +02:00 committed by GitHub
parent 28fd38d13d
commit d2e8092c7f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
48 changed files with 258 additions and 251 deletions

View file

@ -63,10 +63,10 @@ test.describe("Personal info with userprofile enabled", () => {
test("render long select options as typeahead", async ({ page }) => {
await login(page, user, "jdoe", realm);
await page.getByText("Alternate Language").click();
await page.locator("#alternatelang").click();
await page.waitForSelector("text=Italiano");
await page.getByText("Alternate Language").click();
await page.locator("#alternatelang").click();
await page.locator("*:focus").press("Control+A");
await page.locator("*:focus").pressSequentially("S");
await expect(page.getByText("Italiano")).toHaveCount(0);

View file

@ -9,8 +9,8 @@ export default defineConfig({
video: isCI,
projectId: "j4yhox",
chromeWebSecurity: false,
viewportWidth: 1360,
viewportHeight: 768,
viewportWidth: 1920,
viewportHeight: 1200,
defaultCommandTimeout: 30000,
numTestsKeptInMemory: 30,
experimentalMemoryManagement: true,

View file

@ -371,7 +371,7 @@ describe("User profile tabs", () => {
createUserPage
.goToCreateUser()
.assertAttributeLabel(attrName, attrName)
.assertAttributeSelect(attrName, supportedOptions, "Choose...")
.assertAttributeSelect(attrName, supportedOptions, "Select an option")
.setUsername(userName)
.setAttributeValueOnSelect(attrName, opt1)
.create()

View file

@ -1,4 +1,5 @@
import type ResourceRepresentation from "@keycloak/keycloak-admin-client/lib/defs/resourceRepresentation";
import { KeycloakSelect, SelectVariant } from "@keycloak/keycloak-ui-shared";
import { Button, SelectOption, TextInput } from "@patternfly/react-core";
import { MinusCircleIcon, PlusCircleIcon } from "@patternfly/react-icons";
import { Table, Tbody, Td, Th, Thead, Tr } from "@patternfly/react-table";
@ -6,14 +7,9 @@ import { camelCase } from "lodash-es";
import { useEffect, useMemo, useState } from "react";
import { Controller, useFieldArray, useFormContext } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { defaultContextAttributes } from "../utils";
import "./key-based-attribute-input.css";
import {
KeycloakSelect,
SelectVariant,
} from "../../components/select/KeycloakSelect";
export type AttributeType = {
key?: string;

View file

@ -3,6 +3,7 @@ import { DecisionStrategy } from "@keycloak/keycloak-admin-client/lib/defs/polic
import {
FormErrorText,
HelpItem,
SelectVariant,
TextAreaControl,
TextControl,
} from "@keycloak/keycloak-ui-shared";
@ -39,7 +40,6 @@ import {
} from "../routes/PermissionDetails";
import { ResourcesPolicySelect } from "./ResourcesPolicySelect";
import { ScopeSelect } from "./ScopeSelect";
import { SelectVariant } from "../../components/select/KeycloakSelect";
type FormFields = PolicyRepresentation & {
resourceType: string;

View file

@ -26,7 +26,7 @@ import {
KeycloakSelect,
SelectVariant,
Variant,
} from "../../components/select/KeycloakSelect";
} from "@keycloak/keycloak-ui-shared";
import { useRealm } from "../../context/realm-context/RealmContext";
import { useFetch } from "../../utils/useFetch";
import useToggle from "../../utils/useToggle";

View file

@ -1,15 +1,15 @@
import type ScopeRepresentation from "@keycloak/keycloak-admin-client/lib/defs/scopeRepresentation";
import { HelpItem } from "@keycloak/keycloak-ui-shared";
import {
HelpItem,
KeycloakSelect,
SelectVariant,
} from "@keycloak/keycloak-ui-shared";
import { FormGroup, SelectOption } from "@patternfly/react-core";
import { useState } from "react";
import { Controller, useFormContext } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { useAdminClient } from "../../admin-client";
import { KeycloakSpinner } from "../../components/keycloak-spinner/KeycloakSpinner";
import {
KeycloakSelect,
SelectVariant,
} from "../../components/select/KeycloakSelect";
import { useFetch } from "../../utils/useFetch";
type Scope = {

View file

@ -1,14 +1,11 @@
import type ScopeRepresentation from "@keycloak/keycloak-admin-client/lib/defs/scopeRepresentation";
import { KeycloakSelect, SelectVariant } from "@keycloak/keycloak-ui-shared";
import { SelectOption } from "@patternfly/react-core";
import { useRef, useState } from "react";
import { Controller, useFormContext } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { useAdminClient } from "../../admin-client";
import { useFetch } from "../../utils/useFetch";
import {
KeycloakSelect,
SelectVariant,
} from "../../components/select/KeycloakSelect";
import { SelectOption } from "@patternfly/react-core";
type ScopeSelectProps = {
clientId: string;

View file

@ -1,4 +1,5 @@
import type ClientScopeRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientScopeRepresentation";
import { KeycloakSelect } from "@keycloak/keycloak-ui-shared";
import {
Button,
ButtonVariant,
@ -17,7 +18,6 @@ import {
} from "@patternfly/react-icons";
import { useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import {
ClientScopeType,
clientScopeTypesDropdown,
@ -26,7 +26,6 @@ import { ListEmptyState } from "../../components/list-empty-state/ListEmptyState
import { KeycloakDataTable } from "../../components/table-toolbar/KeycloakDataTable";
import useToggle from "../../utils/useToggle";
import { getProtocolName } from "../utils";
import { KeycloakSelect } from "../../components/select/KeycloakSelect";
import "./client-scopes.css";

View file

@ -2,6 +2,12 @@ import type ClientScopeRepresentation from "@keycloak/keycloak-admin-client/lib/
import type ProtocolMapperRepresentation from "@keycloak/keycloak-admin-client/lib/defs/protocolMapperRepresentation";
import type RoleRepresentation from "@keycloak/keycloak-admin-client/lib/defs/roleRepresentation";
import type { ProtocolMapperTypeRepresentation } from "@keycloak/keycloak-admin-client/lib/defs/serverInfoRepesentation";
import {
HelpItem,
KeycloakSelect,
SelectVariant,
useHelp,
} from "@keycloak/keycloak-ui-shared";
import {
ClipboardCopy,
Form,
@ -23,7 +29,6 @@ import { QuestionCircleIcon } from "@patternfly/react-icons";
import { useEffect, useRef, useState } from "react";
import { FormProvider, useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { HelpItem, useHelp } from "@keycloak/keycloak-ui-shared";
import { useAdminClient } from "../../admin-client";
import { KeycloakDataTable } from "../../components/table-toolbar/KeycloakDataTable";
import { UserSelect } from "../../components/users/UserSelect";
@ -35,10 +40,6 @@ import { useFetch } from "../../utils/useFetch";
import { GeneratedCodeTab } from "./GeneratedCodeTab";
import "./evaluate.css";
import {
KeycloakSelect,
SelectVariant,
} from "../../components/select/KeycloakSelect";
export type EvaluateScopesProps = {
clientId: string;

View file

@ -1,11 +1,14 @@
import { HelpItem } from "@keycloak/keycloak-ui-shared";
import {
HelpItem,
KeycloakSelect,
SelectVariant,
} from "@keycloak/keycloak-ui-shared";
import { FormGroup, SelectOption } from "@patternfly/react-core";
import { useState } from "react";
import { Controller, useFormContext } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { convertToName } from "./DynamicComponents";
import type { ComponentProps } from "./components";
import { KeycloakSelect, SelectVariant } from "../select/KeycloakSelect";
export const ListComponent = ({
name,

View file

@ -1,9 +1,12 @@
import { HelpItem } from "@keycloak/keycloak-ui-shared";
import {
HelpItem,
KeycloakSelect,
SelectVariant,
} from "@keycloak/keycloak-ui-shared";
import { FormGroup, SelectOption } from "@patternfly/react-core";
import { useState } from "react";
import { Controller, useFormContext } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { KeycloakSelect, SelectVariant } from "../select/KeycloakSelect";
import { convertToName } from "./DynamicComponents";
import type { ComponentProps } from "./components";

View file

@ -1,3 +1,4 @@
import { KeycloakSelect } from "@keycloak/keycloak-ui-shared";
import {
Grid,
GridItem,
@ -9,7 +10,6 @@ import { UseControllerProps, useController } from "react-hook-form";
import { useTranslation } from "react-i18next";
import useToggle from "../../utils/useToggle";
import { DefaultValue } from "./KeyValueInput";
import { KeycloakSelect } from "../select/KeycloakSelect";
type KeySelectProp = UseControllerProps & {
selectItems: DefaultValue[];

View file

@ -1,8 +1,8 @@
import { KeycloakSelect } from "@keycloak/keycloak-ui-shared";
import { SelectOption, TextInput } from "@patternfly/react-core";
import { useMemo, useState } from "react";
import { UseControllerProps, useController } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { KeycloakSelect } from "../select/KeycloakSelect";
import { DefaultValue } from "./KeyValueInput";
type ValueSelectProps = UseControllerProps & {

View file

@ -1,5 +1,8 @@
import { useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import {
KeycloakSelect,
KeycloakSelectProps,
SelectVariant,
} from "@keycloak/keycloak-ui-shared";
import {
SelectOption,
Split,
@ -7,11 +10,8 @@ import {
TextInput,
TextInputProps,
} from "@patternfly/react-core";
import {
KeycloakSelect,
KeycloakSelectProps,
SelectVariant,
} from "../select/KeycloakSelect";
import { useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
export type Unit = "second" | "minute" | "hour" | "day";

View file

@ -1,4 +1,9 @@
import type { UserProfileConfig } from "@keycloak/keycloak-admin-client/lib/defs/userProfileMetadata";
import {
KeycloakSelect,
SelectVariant,
label,
} from "@keycloak/keycloak-ui-shared";
import {
ActionGroup,
Alert,
@ -18,11 +23,8 @@ import { ReactNode, useState } from "react";
import { useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { Form } from "react-router-dom";
import { SelectVariant, label } from "@keycloak/keycloak-ui-shared";
import { useAlerts } from "../alert/Alerts";
import { UserAttribute } from "./UserDataTable";
import { KeycloakSelect } from "../select/KeycloakSelect";
type UserDataTableAttributeSearchFormProps = {
activeFilters: UserAttribute[];

View file

@ -1,5 +1,9 @@
import type AdminEventRepresentation from "@keycloak/keycloak-admin-client/lib/defs/adminEventRepresentation";
import { TextControl } from "@keycloak/keycloak-ui-shared";
import {
KeycloakSelect,
SelectVariant,
TextControl,
} from "@keycloak/keycloak-ui-shared";
import { CodeEditor, Language } from "@patternfly/react-code-editor";
import {
ActionGroup,
@ -32,10 +36,6 @@ import { useTranslation } from "react-i18next";
import { useAdminClient } from "../admin-client";
import DropdownPanel from "../components/dropdown-panel/DropdownPanel";
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
import {
KeycloakSelect,
SelectVariant,
} from "../components/select/KeycloakSelect";
import {
Action,
KeycloakDataTable,

View file

@ -1,7 +1,11 @@
import type EventRepresentation from "@keycloak/keycloak-admin-client/lib/defs/eventRepresentation";
import type EventType from "@keycloak/keycloak-admin-client/lib/defs/eventTypes";
import type { RealmEventsConfigRepresentation } from "@keycloak/keycloak-admin-client/lib/defs/realmEventsConfigRepresentation";
import { TextControl } from "@keycloak/keycloak-ui-shared";
import {
KeycloakSelect,
SelectVariant,
TextControl,
} from "@keycloak/keycloak-ui-shared";
import {
ActionGroup,
Button,
@ -37,10 +41,6 @@ import {
RoutableTabs,
useRoutableTab,
} from "../components/routable-tabs/RoutableTabs";
import {
KeycloakSelect,
SelectVariant,
} from "../components/select/KeycloakSelect";
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
import { ViewHeader } from "../components/view-header/ViewHeader";
import { useRealm } from "../context/realm-context/RealmContext";

View file

@ -2,17 +2,15 @@ import type IdentityProviderMapperRepresentation from "@keycloak/keycloak-admin-
import type { IdentityProviderMapperTypeRepresentation } from "@keycloak/keycloak-admin-client/lib/defs/identityProviderMapperTypeRepresentation";
import {
HelpItem,
KeycloakSelect,
SelectControl,
SelectVariant,
TextControl,
} from "@keycloak/keycloak-ui-shared";
import { FormGroup, SelectOption } from "@patternfly/react-core";
import { useState } from "react";
import { Controller, UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
import {
KeycloakSelect,
SelectVariant,
} from "../../components/select/KeycloakSelect";
import type { IdPMapperRepresentationWithAttributes } from "./AddMapper";
type AddMapperFormProps = {

View file

@ -3,7 +3,9 @@ import type IdentityProviderRepresentation from "@keycloak/keycloak-admin-client
import {
FormErrorText,
HelpItem,
KeycloakSelect,
SelectControl,
SelectVariant,
} from "@keycloak/keycloak-ui-shared";
import {
FormGroup,
@ -16,10 +18,6 @@ import { useState } from "react";
import { Controller, useFormContext, useWatch } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { useAdminClient } from "../../admin-client";
import {
KeycloakSelect,
SelectVariant,
} from "../../components/select/KeycloakSelect";
import { useFetch } from "../../utils/useFetch";
import useIsFeatureEnabled, { Feature } from "../../utils/useIsFeatureEnabled";
import type { FieldProps } from "../component/FormGroupField";

View file

@ -1,3 +1,8 @@
import {
HelpItem,
KeycloakSelect,
SelectVariant,
} from "@keycloak/keycloak-ui-shared";
import {
ExpandableSection,
Form,
@ -8,9 +13,6 @@ import {
import { useState } from "react";
import { Controller, useFormContext } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { HelpItem, SelectVariant } from "@keycloak/keycloak-ui-shared";
import { KeycloakSelect } from "../../components/select/KeycloakSelect";
import { FormGroupField } from "../component/FormGroupField";
import { SwitchField } from "../component/SwitchField";
import { TextField } from "../component/TextField";

View file

@ -1,18 +1,17 @@
import {
HelpItem,
KeycloakSelect,
SelectVariant,
} from "@keycloak/keycloak-ui-shared";
import { FormGroup, SelectOption } from "@patternfly/react-core";
import { useState } from "react";
import { Controller, useFormContext, useWatch } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { HelpItem } from "@keycloak/keycloak-ui-shared";
import { useServerInfo } from "../../context/server-info/ServerInfoProvider";
import { sortProviders } from "../../util";
import { ClientIdSecret } from "../component/ClientIdSecret";
import { SwitchField } from "../component/SwitchField";
import { sortProviders } from "../../util";
import { useServerInfo } from "../../context/server-info/ServerInfoProvider";
import { TextField } from "../component/TextField";
import {
KeycloakSelect,
SelectVariant,
} from "../../components/select/KeycloakSelect";
const clientAuthentications = [
"client_secret_post",

View file

@ -1,11 +1,13 @@
import {
HelpItem,
KeycloakSelect,
SelectVariant,
} from "@keycloak/keycloak-ui-shared";
import { FormGroup, SelectOption } from "@patternfly/react-core";
import { useState } from "react";
import { Controller, useFormContext } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { HelpItem, SelectVariant } from "@keycloak/keycloak-ui-shared";
import { MultiLineInput } from "../../components/multi-line-input/MultiLineInput";
import { KeycloakSelect } from "../../components/select/KeycloakSelect";
const comparisonValues = ["exact", "minimum", "maximum", "better"];

View file

@ -1,7 +1,11 @@
import type { ConfigPropertyRepresentation } from "@keycloak/keycloak-admin-client/lib/defs/authenticatorConfigInfoRepresentation";
import type ClientProfileRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientProfileRepresentation";
import type ComponentTypeRepresentation from "@keycloak/keycloak-admin-client/lib/defs/componentTypeRepresentation";
import { HelpItem } from "@keycloak/keycloak-ui-shared";
import {
HelpItem,
KeycloakSelect,
SelectVariant,
} from "@keycloak/keycloak-ui-shared";
import {
ActionGroup,
AlertVariant,
@ -18,10 +22,6 @@ import { useAdminClient } from "../admin-client";
import { useAlerts } from "../components/alert/Alerts";
import { DynamicComponents } from "../components/dynamic/DynamicComponents";
import { FormAccess } from "../components/form/FormAccess";
import {
KeycloakSelect,
SelectVariant,
} from "../components/select/KeycloakSelect";
import { ViewHeader } from "../components/view-header/ViewHeader";
import { useServerInfo } from "../context/server-info/ServerInfoProvider";
import { useFetch } from "../utils/useFetch";

View file

@ -2,7 +2,11 @@ import type { ConfigPropertyRepresentation } from "@keycloak/keycloak-admin-clie
import type ClientPolicyConditionRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientPolicyConditionRepresentation";
import type ClientPolicyRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientPolicyRepresentation";
import type ComponentTypeRepresentation from "@keycloak/keycloak-admin-client/lib/defs/componentTypeRepresentation";
import { HelpItem } from "@keycloak/keycloak-ui-shared";
import {
HelpItem,
KeycloakSelect,
SelectVariant,
} from "@keycloak/keycloak-ui-shared";
import {
ActionGroup,
AlertVariant,
@ -20,10 +24,6 @@ import { useAdminClient } from "../admin-client";
import { useAlerts } from "../components/alert/Alerts";
import { DynamicComponents } from "../components/dynamic/DynamicComponents";
import { FormAccess } from "../components/form/FormAccess";
import {
KeycloakSelect,
SelectVariant,
} from "../components/select/KeycloakSelect";
import { ViewHeader } from "../components/view-header/ViewHeader";
import { useRealm } from "../context/realm-context/RealmContext";
import { useServerInfo } from "../context/server-info/ServerInfoProvider";

View file

@ -5,6 +5,7 @@ import type {
PartialImportResult,
} from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
import type RoleRepresentation from "@keycloak/keycloak-admin-client/lib/defs/roleRepresentation";
import { KeycloakSelect } from "@keycloak/keycloak-ui-shared";
import {
Alert,
Button,
@ -30,7 +31,6 @@ import { useTranslation } from "react-i18next";
import { useAdminClient } from "../admin-client";
import { useAlerts } from "../components/alert/Alerts";
import { JsonFileUpload } from "../components/json-file-upload/JsonFileUpload";
import { KeycloakSelect } from "../components/select/KeycloakSelect";
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
import { useRealm } from "../context/realm-context/RealmContext";

View file

@ -1,4 +1,9 @@
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
import {
HelpItem,
KeycloakSelect,
SelectVariant,
} from "@keycloak/keycloak-ui-shared";
import {
ActionGroup,
Button,
@ -9,13 +14,7 @@ import {
import { useEffect, useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { HelpItem } from "@keycloak/keycloak-ui-shared";
import { FormAccess } from "../components/form/FormAccess";
import {
KeycloakSelect,
SelectVariant,
} from "../components/select/KeycloakSelect";
import { useServerInfo } from "../context/server-info/ServerInfoProvider";
import { convertToFormValues } from "../util";

View file

@ -1,4 +1,10 @@
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
import {
FormPanel,
HelpItem,
KeycloakSelect,
SelectVariant,
} from "@keycloak/keycloak-ui-shared";
import {
ActionGroup,
Button,
@ -17,8 +23,6 @@ import {
import { useEffect, useState } from "react";
import { Controller, useForm, useWatch } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { FormPanel, HelpItem } from "@keycloak/keycloak-ui-shared";
import { FormAccess } from "../components/form/FormAccess";
import {
TimeSelector,
@ -30,10 +34,6 @@ import { beerify, convertToFormValues, sortProviders } from "../util";
import useIsFeatureEnabled, { Feature } from "../utils/useIsFeatureEnabled";
import "./realm-settings-section.css";
import {
KeycloakSelect,
SelectVariant,
} from "../components/select/KeycloakSelect";
type RealmSettingsSessionsTabProps = {
realm: RealmRepresentation;

View file

@ -1,5 +1,6 @@
import type ComponentRepresentation from "@keycloak/keycloak-admin-client/lib/defs/componentRepresentation";
import type { KeyMetadataRepresentation } from "@keycloak/keycloak-admin-client/lib/defs/keyMetadataRepresentation";
import { KeycloakSelect, SelectVariant } from "@keycloak/keycloak-ui-shared";
import {
Button,
ButtonVariant,
@ -15,10 +16,6 @@ import { useAdminClient } from "../../admin-client";
import { useConfirmDialog } from "../../components/confirm-dialog/ConfirmDialog";
import { KeycloakSpinner } from "../../components/keycloak-spinner/KeycloakSpinner";
import { ListEmptyState } from "../../components/list-empty-state/ListEmptyState";
import {
KeycloakSelect,
SelectVariant,
} from "../../components/select/KeycloakSelect";
import { KeycloakDataTable } from "../../components/table-toolbar/KeycloakDataTable";
import { useRealm } from "../../context/realm-context/RealmContext";
import { emptyFormatter } from "../../util";

View file

@ -1,3 +1,4 @@
import { KeycloakSelect, SelectVariant } from "@keycloak/keycloak-ui-shared";
import {
ActionGroup,
Button,
@ -28,10 +29,6 @@ import { useWhoAmI } from "../../context/whoami/WhoAmI";
import { DEFAULT_LOCALE } from "../../i18n/i18n";
import { localeToDisplayName } from "../../util";
import useLocaleSort, { mapByKey } from "../../utils/useLocaleSort";
import {
KeycloakSelect,
SelectVariant,
} from "../../components/select/KeycloakSelect";
type EffectiveMessageBundlesProps = {
defaultSupportedLocales: string[];

View file

@ -1,22 +1,23 @@
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
import { KeycloakSelect, SelectVariant } from "@keycloak/keycloak-ui-shared";
import {
AlertVariant,
Button,
ButtonVariant,
Divider,
Dropdown,
DropdownItem,
DropdownList,
Form,
FormGroup,
MenuToggle,
SelectGroup,
SelectOption,
Text,
TextContent,
TextInput,
TextVariants,
ToolbarItem,
SelectGroup,
SelectOption,
Dropdown,
MenuToggle,
DropdownList,
DropdownItem,
} from "@patternfly/react-core";
import {
CheckIcon,
@ -51,10 +52,6 @@ import { useWhoAmI } from "../../context/whoami/WhoAmI";
import { DEFAULT_LOCALE } from "../../i18n/i18n";
import { localeToDisplayName } from "../../util";
import { AddTranslationModal } from "../AddTranslationModal";
import {
KeycloakSelect,
SelectVariant,
} from "../../components/select/KeycloakSelect";
type RealmOverridesProps = {
internationalizationEnabled: boolean;

View file

@ -1,4 +1,10 @@
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
import {
HelpItem,
KeycloakSelect,
NumberControl,
SelectVariant,
} from "@keycloak/keycloak-ui-shared";
import {
ActionGroup,
Button,
@ -8,14 +14,9 @@ import {
import { useEffect, useState } from "react";
import { FormProvider, useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { HelpItem, NumberControl } from "@keycloak/keycloak-ui-shared";
import { FormAccess } from "../../components/form/FormAccess";
import { convertToFormValues } from "../../util";
import { Time } from "./Time";
import {
KeycloakSelect,
SelectVariant,
} from "../../components/select/KeycloakSelect";
type BruteForceDetectionProps = {
realm: RealmRepresentation;

View file

@ -1,4 +1,5 @@
import type { UserProfileAttribute } from "@keycloak/keycloak-admin-client/lib/defs/userProfileMetadata";
import { KeycloakSelect, SelectVariant } from "@keycloak/keycloak-ui-shared";
import {
Button,
ButtonVariant,
@ -13,20 +14,16 @@ import { uniqBy } from "lodash-es";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { Link, useNavigate } from "react-router-dom";
import { useAdminClient } from "../../admin-client";
import { DraggableTable } from "../../authentication/components/DraggableTable";
import { useConfirmDialog } from "../../components/confirm-dialog/ConfirmDialog";
import { KeycloakSpinner } from "../../components/keycloak-spinner/KeycloakSpinner";
import { useRealm } from "../../context/realm-context/RealmContext";
import useLocale from "../../utils/useLocale";
import useToggle from "../../utils/useToggle";
import { toAddAttribute } from "../routes/AddAttribute";
import { toAttribute } from "../routes/Attribute";
import { useUserProfile } from "./UserProfileContext";
import useLocale from "../../utils/useLocale";
import { useAdminClient } from "../../admin-client";
import {
KeycloakSelect,
SelectVariant,
} from "../../components/select/KeycloakSelect";
const RESTRICTED_ATTRIBUTES = ["username", "email"];

View file

@ -3,7 +3,9 @@ import type { UserProfileConfig } from "@keycloak/keycloak-admin-client/lib/defs
import {
FormErrorText,
HelpItem,
KeycloakSelect,
SelectControl,
SelectVariant,
} from "@keycloak/keycloak-ui-shared";
import {
Alert,
@ -30,10 +32,6 @@ import { useTranslation } from "react-i18next";
import { useAdminClient } from "../../../admin-client";
import { FormAccess } from "../../../components/form/FormAccess";
import { KeycloakSpinner } from "../../../components/keycloak-spinner/KeycloakSpinner";
import {
KeycloakSelect,
SelectVariant,
} from "../../../components/select/KeycloakSelect";
import { useRealm } from "../../../context/realm-context/RealmContext";
import { useFetch } from "../../../utils/useFetch";
import { useParams } from "../../../utils/useParams";

View file

@ -1,8 +1,8 @@
import ComponentTypeRepresentation from "@keycloak/keycloak-admin-client/lib/defs/componentTypeRepresentation";
import { KeycloakSelect } from "@keycloak/keycloak-ui-shared";
import { FormGroup, SelectOption } from "@patternfly/react-core";
import { useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { KeycloakSelect } from "../../../components/select/KeycloakSelect";
import { useServerInfo } from "../../../context/server-info/ServerInfoProvider";
import useToggle from "../../../utils/useToggle";

View file

@ -1,4 +1,5 @@
import UserSessionRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userSessionRepresentation";
import { KeycloakSelect } from "@keycloak/keycloak-ui-shared";
import {
DropdownItem,
PageSection,
@ -10,7 +11,6 @@ import { useTranslation } from "react-i18next";
import { useAdminClient } from "../admin-client";
import { useAlerts } from "../components/alert/Alerts";
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
import { KeycloakSelect } from "../components/select/KeycloakSelect";
import { ViewHeader } from "../components/view-header/ViewHeader";
import { fetchAdminUI } from "../context/auth/admin-ui-endpoint";
import { useRealm } from "../context/realm-context/RealmContext";

View file

@ -1,4 +1,12 @@
import type TestLdapConnectionRepresentation from "@keycloak/keycloak-admin-client/lib/defs/testLdapConnection";
import {
HelpItem,
KeycloakSelect,
PasswordControl,
SelectControl,
SelectVariant,
TextControl,
} from "@keycloak/keycloak-ui-shared";
import {
AlertVariant,
Button,
@ -15,21 +23,11 @@ import {
useWatch,
} from "react-hook-form";
import { useTranslation } from "react-i18next";
import {
HelpItem,
PasswordControl,
SelectControl,
TextControl,
} from "@keycloak/keycloak-ui-shared";
import { useAdminClient } from "../../admin-client";
import { useAlerts } from "../../components/alert/Alerts";
import { FormAccess } from "../../components/form/FormAccess";
import { WizardSectionHeader } from "../../components/wizard-section-header/WizardSectionHeader";
import { useRealm } from "../../context/realm-context/RealmContext";
import {
KeycloakSelect,
SelectVariant,
} from "../../components/select/KeycloakSelect";
export type LdapSettingsConnectionProps = {
form: UseFormReturn;

View file

@ -1,16 +1,17 @@
import ComponentRepresentation from "@keycloak/keycloak-admin-client/lib/defs/componentRepresentation";
import {
HelpItem,
KeycloakSelect,
SelectVariant,
TextControl,
} from "@keycloak/keycloak-ui-shared";
import { FormGroup, SelectOption } from "@patternfly/react-core";
import { HelpItem, TextControl } from "@keycloak/keycloak-ui-shared";
import { useEffect, useState } from "react";
import { Controller, FormProvider, UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { FormAccess } from "../../components/form/FormAccess";
import { WizardSectionHeader } from "../../components/wizard-section-header/WizardSectionHeader";
import { useRealm } from "../../context/realm-context/RealmContext";
import {
KeycloakSelect,
SelectVariant,
} from "../../components/select/KeycloakSelect";
export type LdapSettingsGeneralProps = {
form: UseFormReturn<ComponentRepresentation>;

View file

@ -1,6 +1,12 @@
import type ComponentRepresentation from "@keycloak/keycloak-admin-client/lib/defs/componentRepresentation";
import type ComponentTypeRepresentation from "@keycloak/keycloak-admin-client/lib/defs/componentTypeRepresentation";
import { DirectionType } from "@keycloak/keycloak-admin-client/lib/resources/userStorageProvider";
import {
HelpItem,
KeycloakSelect,
SelectVariant,
TextControl,
} from "@keycloak/keycloak-ui-shared";
import {
ActionGroup,
AlertVariant,
@ -15,7 +21,6 @@ import { useState } from "react";
import { Controller, FormProvider, useForm, useWatch } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import { HelpItem, TextControl } from "@keycloak/keycloak-ui-shared";
import { useAdminClient } from "../../../admin-client";
import { useAlerts } from "../../../components/alert/Alerts";
import { useConfirmDialog } from "../../../components/confirm-dialog/ConfirmDialog";
@ -32,10 +37,6 @@ import { useFetch } from "../../../utils/useFetch";
import { useParams } from "../../../utils/useParams";
import { toUserFederationLdap } from "../../routes/UserFederationLdap";
import { UserFederationLdapMapperParams } from "../../routes/UserFederationLdapMapper";
import {
KeycloakSelect,
SelectVariant,
} from "../../../components/select/KeycloakSelect";
export default function LdapMapperDetails() {
const { adminClient } = useAdminClient();

View file

@ -1,15 +1,16 @@
import {
HelpItem,
KeycloakSelect,
SelectControl,
SelectVariant,
} from "@keycloak/keycloak-ui-shared";
import { FormGroup, NumberInput, SelectOption } from "@patternfly/react-core";
import { isEqual } from "lodash-es";
import { Controller, UseFormReturn, useWatch } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { FormAccess } from "../../components/form/FormAccess";
import { HelpItem, SelectControl } from "@keycloak/keycloak-ui-shared";
import { WizardSectionHeader } from "../../components/wizard-section-header/WizardSectionHeader";
import useToggle from "../../utils/useToggle";
import {
KeycloakSelect,
SelectVariant,
} from "../../components/select/KeycloakSelect";
export type SettingsCacheProps = {
form: UseFormReturn;

View file

@ -1,3 +1,4 @@
import { KeycloakSelect } from "@keycloak/keycloak-ui-shared";
import {
Dropdown,
DropdownItem,
@ -9,7 +10,6 @@ import {
import { FilterIcon } from "@patternfly/react-icons";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { KeycloakSelect } from "../../components/select/KeycloakSelect";
export type SearchType = "default" | "attribute";

View file

@ -39,13 +39,6 @@ export const SingleSelectControl = <
} = useFormContext();
const [open, setOpen] = useState(false);
const convert = () =>
options.map((option) => (
<SelectOption key={key(option)} value={key(option)}>
{isString(option) ? option : option.value}
</SelectOption>
));
return (
<FormLabel
name={name}
@ -62,6 +55,7 @@ export const SingleSelectControl = <
<Select
{...rest}
onClick={() => setOpen(!open)}
onOpenChange={() => setOpen(false)}
selected={
isSelectBasedOptions(options)
? options
@ -84,28 +78,27 @@ export const SingleSelectControl = <
aria-label="toggle"
>
{isSelectBasedOptions(options)
? options.find((o) => o.key === value)?.value
? options.find(
(o) =>
o.key === (Array.isArray(value) ? value[0] : value),
)?.value
: value}
</MenuToggle>
)}
onSelect={(event, v) => {
event?.stopPropagation();
if (Array.isArray(value)) {
onSelect={(_event, v) => {
const option = v?.toString();
const selected = key(option!);
if (value.includes(key)) {
onChange(value.filter((item: string) => item !== selected));
} else {
onChange([...value, option]);
}
} else {
onChange(v);
onChange(Array.isArray(value) ? [option] : option);
setOpen(false);
}
}}
isOpen={open}
>
<SelectList>{convert()}</SelectList>
<SelectList>
{options.map((option) => (
<SelectOption key={key(option)} value={key(option)}>
{isString(option) ? option : option.value}
</SelectOption>
))}
</SelectList>
</Select>
)}
/>

View file

@ -89,11 +89,14 @@ export const TypeaheadSelectControl = <
if (variant !== SelectVariant.typeaheadMulti) {
setFilterValue(getValue(focusedItem));
} else {
setFilterValue("");
}
field.onChange(
Array.isArray(field.value)
? [...field.value, getValue(focusedItem)]
: getValue(focusedItem),
? [...field.value, key(focusedItem)]
: key(focusedItem),
);
setOpen(false);
setFocusedItemIndex(0);
@ -106,6 +109,12 @@ export const TypeaheadSelectControl = <
field.onChange(undefined);
break;
}
case "Backspace": {
if (variant === SelectVariant.typeahead) {
field.onChange("");
}
break;
}
case "ArrowUp":
case "ArrowDown": {
event.preventDefault();
@ -150,6 +159,7 @@ export const TypeaheadSelectControl = <
<Select
{...rest}
onClick={() => setOpen(!open)}
onOpenChange={() => setOpen(false)}
selected={
isSelectBasedOptions(options)
? options
@ -174,7 +184,19 @@ export const TypeaheadSelectControl = <
<TextInputGroup isPlain>
<TextInputGroupMain
placeholder={placeholderText}
value={filterValue}
value={
variant === SelectVariant.typeahead && field.value
? isSelectBasedOptions(options)
? options.find(
(o) =>
o.key ===
(Array.isArray(field.value)
? field.value[0]
: field.value),
)?.value
: field.value
: filterValue
}
onClick={() => setOpen(!open)}
onChange={(_, value) => {
setFilterValue(value);
@ -234,21 +256,19 @@ export const TypeaheadSelectControl = <
onSelect={(event, v) => {
event?.stopPropagation();
const option = v?.toString();
if (Array.isArray(field.value)) {
const select = key(option!);
if (field.value.includes(key)) {
if (
variant === SelectVariant.typeaheadMulti &&
Array.isArray(field.value)
) {
if (field.value.includes(option)) {
field.onChange(
field.value.filter((item: string) => item !== select),
field.value.filter((item: string) => item !== option),
);
} else {
field.onChange([...field.value, option]);
}
} else {
const val = isSelectBasedOptions(options)
? options.find((o) => o.key === option)?.value
: option;
field.onChange(val);
setFilterValue(val || "");
field.onChange(Array.isArray(field.value) ? [option] : option);
setOpen(false);
}
}}

View file

@ -58,3 +58,5 @@ export { isDefined } from "./utils/isDefined";
export { useRequiredContext } from "./utils/useRequiredContext";
export { useStoredState } from "./utils/useStoredState";
export { default as KeycloakMasthead } from "./masthead/Masthead";
export { KeycloakSelect } from "./select/KeycloakSelect";
export type { Variant, KeycloakSelectProps } from "./select/KeycloakSelect";

View file

@ -54,6 +54,7 @@ export const SingleSelect = ({
}}
{...props}
onClick={toggle}
onOpenChange={() => setOpen(false)}
selected={selections}
onSelect={(_, value) => {
onSelect?.(value || "");

View file

@ -75,6 +75,12 @@ export const TypeaheadSelect = ({
onToggle?.(false);
break;
}
case "Backspace": {
if (variant === SelectVariant.typeahead) {
onSelect?.("");
}
break;
}
case "ArrowUp":
case "ArrowDown": {
event.preventDefault();
@ -107,6 +113,7 @@ export const TypeaheadSelect = ({
<Select
{...rest}
onClick={toggle}
onOpenChange={() => onToggle?.(false)}
onSelect={(_, value) => onSelect?.(value || "")}
maxMenuHeight={propertyToString(maxHeight)}
popperProps={{ direction, width: propertyToString(width) }}
@ -124,7 +131,11 @@ export const TypeaheadSelect = ({
<TextInputGroup isPlain>
<TextInputGroupMain
placeholder={placeholderText}
value={filterValue}
value={
variant === SelectVariant.typeahead && selections
? (selections as string)
: filterValue
}
onClick={toggle}
onChange={(_, value) => {
setFilterValue(value);
@ -165,6 +176,7 @@ export const TypeaheadSelect = ({
onClick={() => {
onSelect?.("");
setFilterValue("");
onFilter?.("");
textInputRef?.current?.focus();
}}
aria-label="Clear input value"

View file

@ -1,13 +1,7 @@
import {
Chip,
ChipGroup,
MenuToggle,
Select,
SelectList,
SelectOption,
} from "@patternfly/react-core";
import { SelectOption } from "@patternfly/react-core";
import { useState } from "react";
import { Controller, ControllerRenderProps } from "react-hook-form";
import { KeycloakSelect, SelectVariant } from "../select/KeycloakSelect";
import {
OptionLabel,
Options,
@ -19,6 +13,7 @@ import { UserFormFields, fieldName, label } from "./utils";
export const SelectComponent = (props: UserProfileFieldProps) => {
const { t, form, inputType, attribute } = props;
const [open, setOpen] = useState(false);
const [filter, setFilter] = useState("");
const isMultiValue = inputType === "multiselect";
const setValue = (
@ -36,7 +31,7 @@ export const SelectComponent = (props: UserProfileFieldProps) => {
}
}
} else {
field.onChange(value);
field.onChange(value === field.value ? "" : value);
}
};
@ -45,9 +40,25 @@ export const SelectComponent = (props: UserProfileFieldProps) => {
const optionLabel =
(attribute.annotations?.["inputOptionLabels"] as OptionLabel) || {};
const fetchLabel = (option: string) =>
label(props.t, optionLabel[option], option);
const convertOptions = (selected: string) =>
options
.filter((o) =>
fetchLabel(o)!.toLowerCase().includes(filter.toLowerCase()),
)
.map((option) => (
<SelectOption
selected={selected === option}
key={option}
value={option}
>
{fetchLabel(option)}
</SelectOption>
));
return (
<UserProfileGroup {...props}>
<Controller
@ -55,58 +66,39 @@ export const SelectComponent = (props: UserProfileFieldProps) => {
defaultValue=""
control={form.control}
render={({ field }) => (
<Select
toggle={(ref) => (
<MenuToggle
id={attribute.name}
ref={ref}
onClick={() => setOpen(!open)}
isExpanded={open}
isFullWidth
isDisabled={attribute.readOnly}
>
{(isMultiValue && Array.isArray(field.value) ? (
<ChipGroup>
{field.value.map((selection, index: number) => (
<Chip
key={index}
onClick={(ev) => {
ev.stopPropagation();
setValue(selection, field);
}}
>
{selection}
</Chip>
))}
</ChipGroup>
) : (
fetchLabel(field.value)
)) || t("choose")}
</MenuToggle>
)}
onSelect={(_, value) => {
const option = value?.toString() || "";
<KeycloakSelect
toggleId={attribute.name}
onToggle={(b) => setOpen(b)}
onClear={() => setValue("", field)}
onSelect={(value) => {
const option = value.toString();
setValue(option, field);
if (!isMultiValue) {
if (!Array.isArray(field.value)) {
setOpen(false);
}
}}
selected={field.value}
selections={
isMultiValue && Array.isArray(field.value)
? field.value
: fetchLabel(field.value)
}
variant={
isMultiValue
? SelectVariant.typeaheadMulti
: options.length >= 10
? SelectVariant.typeahead
: SelectVariant.single
}
aria-label={t("selectOne")}
isOpen={open}
isDisabled={attribute.readOnly}
onFilter={(value) => {
setFilter(value);
return convertOptions(field.value);
}}
>
<SelectList>
{options.map((option) => (
<SelectOption
selected={field.value === option}
key={option}
value={option}
>
{fetchLabel(option)}
</SelectOption>
))}
</SelectList>
</Select>
{convertOptions(field.value)}
</KeycloakSelect>
)}
/>
</UserProfileGroup>