Add useToggle hook to manage toggleable state (#1600)

This commit is contained in:
Jon Koops 2021-11-30 14:07:44 +01:00 committed by GitHub
parent 31e1415758
commit 61b2689864
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 197 additions and 112 deletions

61
package-lock.json generated
View file

@ -38,6 +38,7 @@
"@testing-library/cypress": "^8.0.2", "@testing-library/cypress": "^8.0.2",
"@testing-library/jest-dom": "^5.15.1", "@testing-library/jest-dom": "^5.15.1",
"@testing-library/react": "^12.1.1", "@testing-library/react": "^12.1.1",
"@testing-library/react-hooks": "^7.0.2",
"@types/dagre": "^0.7.45", "@types/dagre": "^0.7.45",
"@types/file-saver": "^2.0.4", "@types/file-saver": "^2.0.4",
"@types/lodash": "^4.14.177", "@types/lodash": "^4.14.177",
@ -4483,6 +4484,35 @@
"react-dom": "*" "react-dom": "*"
} }
}, },
"node_modules/@testing-library/react-hooks": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@testing-library/react-hooks/-/react-hooks-7.0.2.tgz",
"integrity": "sha512-dYxpz8u9m4q1TuzfcUApqi8iFfR6R0FaMbr2hjZJy1uC8z+bO/K4v8Gs9eogGKYQop7QsrBTFkv/BCF7MzD2Cg==",
"dev": true,
"dependencies": {
"@babel/runtime": "^7.12.5",
"@types/react": ">=16.9.0",
"@types/react-dom": ">=16.9.0",
"@types/react-test-renderer": ">=16.9.0",
"react-error-boundary": "^3.1.0"
},
"engines": {
"node": ">=12"
},
"peerDependencies": {
"react": ">=16.9.0",
"react-dom": ">=16.9.0",
"react-test-renderer": ">=16.9.0"
},
"peerDependenciesMeta": {
"react-dom": {
"optional": true
},
"react-test-renderer": {
"optional": true
}
}
},
"node_modules/@tootallnate/once": { "node_modules/@tootallnate/once": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
@ -5002,6 +5032,15 @@
"@types/react-router": "*" "@types/react-router": "*"
} }
}, },
"node_modules/@types/react-test-renderer": {
"version": "17.0.1",
"resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-17.0.1.tgz",
"integrity": "sha512-3Fi2O6Zzq/f3QR9dRnlnHso9bMl7weKCviFmfF6B4LS1Uat6Hkm15k0ZAQuDz+UBq6B3+g+NM6IT2nr5QgPzCw==",
"dev": true,
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@types/resolve": { "node_modules/@types/resolve": {
"version": "1.17.1", "version": "1.17.1",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
@ -24660,6 +24699,19 @@
"@testing-library/dom": "^8.0.0" "@testing-library/dom": "^8.0.0"
} }
}, },
"@testing-library/react-hooks": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@testing-library/react-hooks/-/react-hooks-7.0.2.tgz",
"integrity": "sha512-dYxpz8u9m4q1TuzfcUApqi8iFfR6R0FaMbr2hjZJy1uC8z+bO/K4v8Gs9eogGKYQop7QsrBTFkv/BCF7MzD2Cg==",
"dev": true,
"requires": {
"@babel/runtime": "^7.12.5",
"@types/react": ">=16.9.0",
"@types/react-dom": ">=16.9.0",
"@types/react-test-renderer": ">=16.9.0",
"react-error-boundary": "^3.1.0"
}
},
"@tootallnate/once": { "@tootallnate/once": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
@ -25176,6 +25228,15 @@
"@types/react-router": "*" "@types/react-router": "*"
} }
}, },
"@types/react-test-renderer": {
"version": "17.0.1",
"resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-17.0.1.tgz",
"integrity": "sha512-3Fi2O6Zzq/f3QR9dRnlnHso9bMl7weKCviFmfF6B4LS1Uat6Hkm15k0ZAQuDz+UBq6B3+g+NM6IT2nr5QgPzCw==",
"dev": true,
"requires": {
"@types/react": "*"
}
},
"@types/resolve": { "@types/resolve": {
"version": "1.17.1", "version": "1.17.1",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",

View file

@ -54,6 +54,7 @@
"@testing-library/cypress": "^8.0.2", "@testing-library/cypress": "^8.0.2",
"@testing-library/jest-dom": "^5.15.1", "@testing-library/jest-dom": "^5.15.1",
"@testing-library/react": "^12.1.1", "@testing-library/react": "^12.1.1",
"@testing-library/react-hooks": "^7.0.2",
"@types/dagre": "^0.7.45", "@types/dagre": "^0.7.45",
"@types/file-saver": "^2.0.4", "@types/file-saver": "^2.0.4",
"@types/lodash": "^4.14.177", "@types/lodash": "^4.14.177",

View file

@ -132,16 +132,12 @@ export const Header = () => {
const KebabDropdown = () => { const KebabDropdown = () => {
const [isDropdownOpen, setDropdownOpen] = useState(false); const [isDropdownOpen, setDropdownOpen] = useState(false);
const onDropdownToggle = () => {
setDropdownOpen(!isDropdownOpen);
};
return ( return (
<Dropdown <Dropdown
id="user-dropdown-kebab" id="user-dropdown-kebab"
isPlain isPlain
position="right" position="right"
toggle={<KebabToggle onToggle={onDropdownToggle} />} toggle={<KebabToggle onToggle={setDropdownOpen} />}
isOpen={isDropdownOpen} isOpen={isDropdownOpen}
dropdownItems={kebabDropdownItems} dropdownItems={kebabDropdownItems}
/> />
@ -152,10 +148,6 @@ export const Header = () => {
const { whoAmI } = useWhoAmI(); const { whoAmI } = useWhoAmI();
const [isDropdownOpen, setDropdownOpen] = useState(false); const [isDropdownOpen, setDropdownOpen] = useState(false);
const onDropdownToggle = () => {
setDropdownOpen(!isDropdownOpen);
};
return ( return (
<Dropdown <Dropdown
isPlain isPlain
@ -163,7 +155,7 @@ export const Header = () => {
id="user-dropdown" id="user-dropdown"
isOpen={isDropdownOpen} isOpen={isDropdownOpen}
toggle={ toggle={
<DropdownToggle onToggle={onDropdownToggle}> <DropdownToggle onToggle={setDropdownOpen}>
{whoAmI.getDisplayName()} {whoAmI.getDisplayName()}
</DropdownToggle> </DropdownToggle>
} }

View file

@ -24,6 +24,7 @@ import { useRealm } from "../context/realm-context/RealmContext";
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog"; import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
import { useAlerts } from "../components/alert/Alerts"; import { useAlerts } from "../components/alert/Alerts";
import { toUpperCase } from "../util"; import { toUpperCase } from "../util";
import useToggle from "../utils/useToggle";
import { DuplicateFlowModal } from "./DuplicateFlowModal"; import { DuplicateFlowModal } from "./DuplicateFlowModal";
import { toCreateFlow } from "./routes/CreateFlow"; import { toCreateFlow } from "./routes/CreateFlow";
import { toFlow } from "./routes/Flow"; import { toFlow } from "./routes/Flow";
@ -55,7 +56,7 @@ export default function AuthenticationSection() {
const { addAlert, addError } = useAlerts(); const { addAlert, addError } = useAlerts();
const [selectedFlow, setSelectedFlow] = useState<AuthenticationType>(); const [selectedFlow, setSelectedFlow] = useState<AuthenticationType>();
const [open, setOpen] = useState(false); const [open, toggleOpen, setOpen] = useToggle();
const loader = async () => { const loader = async () => {
const clients = await adminClient.clients.find(); const clients = await adminClient.clients.find();
@ -202,7 +203,7 @@ export default function AuthenticationSection() {
<DuplicateFlowModal <DuplicateFlowModal
name={selectedFlow ? selectedFlow.alias! : ""} name={selectedFlow ? selectedFlow.alias! : ""}
description={selectedFlow?.description!} description={selectedFlow?.description!}
toggleDialog={() => setOpen(!open)} toggleDialog={toggleOpen}
onComplete={() => { onComplete={() => {
refresh(); refresh();
setOpen(false); setOpen(false);

View file

@ -40,6 +40,7 @@ import { AddSubFlowModal, Flow } from "./components/modals/AddSubFlowModal";
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog"; import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
import { DuplicateFlowModal } from "./DuplicateFlowModal"; import { DuplicateFlowModal } from "./DuplicateFlowModal";
import { useRealm } from "../context/realm-context/RealmContext"; import { useRealm } from "../context/realm-context/RealmContext";
import useToggle from "../utils/useToggle";
import { toAuthentication } from "./routes/Authentication"; import { toAuthentication } from "./routes/Authentication";
import { EditFlowModal } from "./EditFlowModal"; import { EditFlowModal } from "./EditFlowModal";
@ -69,7 +70,7 @@ export default function FlowDetails() {
const [showAddSubFlowDialog, setShowSubFlowDialog] = useState<boolean>(); const [showAddSubFlowDialog, setShowSubFlowDialog] = useState<boolean>();
const [selectedExecution, setSelectedExecution] = const [selectedExecution, setSelectedExecution] =
useState<ExpandableExecution>(); useState<ExpandableExecution>();
const [open, setOpen] = useState(false); const [open, toggleOpen, setOpen] = useToggle();
const [edit, setEdit] = useState(false); const [edit, setEdit] = useState(false);
useFetch( useFetch(
@ -280,7 +281,7 @@ export default function FlowDetails() {
<DuplicateFlowModal <DuplicateFlowModal
name={flow?.alias!} name={flow?.alias!}
description={flow?.description!} description={flow?.description!}
toggleDialog={() => setOpen(!open)} toggleDialog={toggleOpen}
onComplete={() => { onComplete={() => {
refresh(); refresh();
setOpen(false); setOpen(false);

View file

@ -47,7 +47,7 @@ export const EditFlowDropdown = ({
data-testid={`${execution.displayName}-edit-dropdown`} data-testid={`${execution.displayName}-edit-dropdown`}
isOpen={open} isOpen={open}
toggle={ toggle={
<DropdownToggle onToggle={(open) => setOpen(open)}> <DropdownToggle onToggle={setOpen}>
<PlusIcon /> <PlusIcon />
</DropdownToggle> </DropdownToggle>
} }

View file

@ -28,7 +28,7 @@ export const FlowRequirementDropdown = ({
<Select <Select
className="keycloak__authentication__requirement-dropdown" className="keycloak__authentication__requirement-dropdown"
variant={SelectVariant.single} variant={SelectVariant.single}
onToggle={() => setOpen(!open)} onToggle={setOpen}
onSelect={(_event, value) => { onSelect={(_event, value) => {
flow.requirement = value.toString(); flow.requirement = value.toString();
onChange(flow); onChange(flow);

View file

@ -151,7 +151,7 @@ export const AddSubFlowModal = ({
<Select <Select
menuAppendTo="parent" menuAppendTo="parent"
toggleId="flowType" toggleId="flowType"
onToggle={() => setOpen(!open)} onToggle={setOpen}
onSelect={(_, value) => { onSelect={(_, value) => {
onChange(value as string); onChange(value as string);
setOpen(false); setOpen(false);
@ -194,9 +194,9 @@ export const AddSubFlowModal = ({
<Select <Select
menuAppendTo="parent" menuAppendTo="parent"
toggleId="provider" toggleId="provider"
onToggle={(toggle) => setOpenProvider(toggle)} onToggle={setOpenProvider}
onSelect={(_, value) => { onSelect={(_, value) => {
onChange(value as string); onChange(value.toString());
setOpenProvider(false); setOpenProvider(false);
}} }}
selections={value.displayName} selections={value.displayName}

View file

@ -37,9 +37,9 @@ export const FlowType = () => {
render={({ onChange, value }) => ( render={({ onChange, value }) => (
<Select <Select
toggleId="flowType" toggleId="flowType"
onToggle={() => setOpen(!open)} onToggle={setOpen}
onSelect={(_, value) => { onSelect={(_, value) => {
onChange(value as string); onChange(value.toString());
setOpen(false); setOpen(false);
}} }}
selections={t(`top-level-flow-type.${value}`)} selections={t(`top-level-flow-type.${value}`)}

View file

@ -37,7 +37,7 @@ export const ChangeTypeDropdown = ({
selections={[]} selections={[]}
isDisabled={selectedRows.length === 0} isDisabled={selectedRows.length === 0}
placeholderText={t("changeTypeTo")} placeholderText={t("changeTypeTo")}
onToggle={(isExpanded) => setOpen(isExpanded)} onToggle={setOpen}
onSelect={async (_, value) => { onSelect={async (_, value) => {
try { try {
await Promise.all( await Promise.all(

View file

@ -221,9 +221,7 @@ export default function ClientScopesSection() {
</ToolbarItem> </ToolbarItem>
<ToolbarItem> <ToolbarItem>
<Dropdown <Dropdown
toggle={ toggle={<KebabToggle onToggle={setKebabOpen} />}
<KebabToggle onToggle={() => setKebabOpen(!kebabOpen)} />
}
isOpen={kebabOpen} isOpen={kebabOpen}
isPlain isPlain
dropdownItems={[ dropdownItems={[

View file

@ -1,7 +1,7 @@
/** /**
* @jest-environment jsdom * @jest-environment jsdom
*/ */
import React, { useState } from "react"; import React from "react";
import { fireEvent, render, screen } from "@testing-library/react"; import { fireEvent, render, screen } from "@testing-library/react";
import { Button } from "@patternfly/react-core"; import { Button } from "@patternfly/react-core";
@ -9,6 +9,7 @@ import type { ServerInfoRepresentation } from "@keycloak/keycloak-admin-client/l
import type WhoAmIRepresentation from "@keycloak/keycloak-admin-client/lib/defs/whoAmIRepresentation"; import type WhoAmIRepresentation from "@keycloak/keycloak-admin-client/lib/defs/whoAmIRepresentation";
import { ServerInfoContext } from "../../context/server-info/ServerInfoProvider"; import { ServerInfoContext } from "../../context/server-info/ServerInfoProvider";
import serverInfo from "../../context/server-info/__tests__/mock.json"; import serverInfo from "../../context/server-info/__tests__/mock.json";
import useToggle from "../../utils/useToggle";
import { AddMapperDialog, AddMapperDialogModalProps } from "./MapperDialog"; import { AddMapperDialog, AddMapperDialogModalProps } from "./MapperDialog";
import { WhoAmI, WhoAmIContext } from "../../context/whoami/WhoAmI"; import { WhoAmI, WhoAmIContext } from "../../context/whoami/WhoAmI";
@ -16,7 +17,7 @@ import whoami from "../../context/whoami/__tests__/mock-whoami.json";
describe("MapperDialog", () => { describe("MapperDialog", () => {
const Test = (args: AddMapperDialogModalProps) => { const Test = (args: AddMapperDialogModalProps) => {
const [open, setOpen] = useState(false); const [open, toggleOpen, setOpen] = useToggle();
return ( return (
<ServerInfoContext.Provider <ServerInfoContext.Provider
@ -28,11 +29,7 @@ describe("MapperDialog", () => {
whoAmI: new WhoAmI(whoami as WhoAmIRepresentation), whoAmI: new WhoAmI(whoami as WhoAmIRepresentation),
}} }}
> >
<AddMapperDialog <AddMapperDialog {...args} open={open} toggleDialog={toggleOpen} />
{...args}
open={open}
toggleDialog={() => setOpen(!open)}
/>
<Button onClick={() => setOpen(true)}> <Button onClick={() => setOpen(true)}>
{!open ? "Show" : "Hide"} {!open ? "Show" : "Hide"}
</Button> </Button>

View file

@ -142,7 +142,7 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => {
variant={SelectVariant.single} variant={SelectVariant.single}
isOpen={openType} isOpen={openType}
selections={value} selections={value}
onToggle={() => setOpenType(!openType)} onToggle={setOpenType}
onSelect={(_, value) => { onSelect={(_, value) => {
onChange(value); onChange(value);
setOpenType(false); setOpenType(false);
@ -173,7 +173,7 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => {
<Select <Select
toggleId="kc-protocol" toggleId="kc-protocol"
required required
onToggle={() => isOpen(!open)} onToggle={isOpen}
onSelect={(_, value) => { onSelect={(_, value) => {
onChange(value as string); onChange(value as string);
isOpen(false); isOpen(false);

View file

@ -72,10 +72,7 @@ export const SearchDropdown = ({
<Dropdown <Dropdown
className="keycloak__client-scopes__searchtype" className="keycloak__client-scopes__searchtype"
toggle={ toggle={
<DropdownToggle <DropdownToggle id="toggle-id" onToggle={setSearchToggle}>
id="toggle-id"
onToggle={(open) => setSearchToggle(open)}
>
<FilterIcon /> {t(`clientScopeSearch.${searchType}`)} <FilterIcon /> {t(`clientScopeSearch.${searchType}`)}
</DropdownToggle> </DropdownToggle>
} }
@ -110,7 +107,7 @@ export const SearchToolbar = ({
<ToolbarItem> <ToolbarItem>
<Select <Select
className="keycloak__client-scopes__searchtype" className="keycloak__client-scopes__searchtype"
onToggle={(open) => setOpen(open)} onToggle={setOpen}
isOpen={open} isOpen={open}
selections={[ selections={[
type === AllClientScopes.none type === AllClientScopes.none
@ -142,7 +139,7 @@ export const SearchToolbar = ({
<ToolbarItem> <ToolbarItem>
<Select <Select
className="keycloak__client-scopes__searchtype" className="keycloak__client-scopes__searchtype"
onToggle={(open) => setOpen(open)} onToggle={setOpen}
isOpen={open} isOpen={open}
selections={[t(`protocolTypes.${protocol}`)]} selections={[t(`protocolTypes.${protocol}`)]}
onSelect={(_, value) => { onSelect={(_, value) => {

View file

@ -33,6 +33,7 @@ import {
ClientScopeDefaultOptionalType, ClientScopeDefaultOptionalType,
} from "../../components/client-scope/ClientScopeTypes"; } from "../../components/client-scope/ClientScopeTypes";
import { useRealm } from "../../context/realm-context/RealmContext"; import { useRealm } from "../../context/realm-context/RealmContext";
import useToggle from "../../utils/useToggle";
import { toMapper } from "../routes/Mapper"; import { toMapper } from "../routes/Mapper";
import { toClientScope } from "../routes/ClientScope"; import { toClientScope } from "../routes/ClientScope";
@ -42,7 +43,7 @@ export default function ClientScopeForm() {
useState<ClientScopeDefaultOptionalType>(); useState<ClientScopeDefaultOptionalType>();
const history = useHistory(); const history = useHistory();
const { realm } = useRealm(); const { realm } = useRealm();
const [hide, setHide] = useState(false); const [hide, toggleHide] = useToggle();
const adminClient = useAdminClient(); const adminClient = useAdminClient();
const { id, type } = useParams<{ id: string; type: AllClientScopes }>(); const { id, type } = useParams<{ id: string; type: AllClientScopes }>();
@ -249,7 +250,7 @@ export default function ClientScopeForm() {
dropdownItems={ dropdownItems={
clientScope clientScope
? [ ? [
<DropdownItem key="delete" onClick={() => toggleDeleteDialog()}> <DropdownItem key="delete" onClick={toggleDeleteDialog}>
{t("common:delete")} {t("common:delete")}
</DropdownItem>, </DropdownItem>,
] ]
@ -299,7 +300,7 @@ export default function ClientScopeForm() {
type={"client-scope"} type={"client-scope"}
loader={loader} loader={loader}
save={assignRoles} save={assignRoles}
onHideRolesToggle={() => setHide(!hide)} onHideRolesToggle={toggleHide}
/> />
</Tab> </Tab>
</KeycloakTabs> </KeycloakTabs>

View file

@ -300,7 +300,7 @@ export const AdvancedTab = ({
/> />
<ExpandableSection <ExpandableSection
toggleText={t("registeredClusterNodes")} toggleText={t("registeredClusterNodes")}
onToggle={() => setExpanded(!expanded)} onToggle={setExpanded}
isExpanded={expanded} isExpanded={expanded}
> >
<KeycloakDataTable <KeycloakDataTable

View file

@ -43,6 +43,7 @@ import {
convertToFormValues, convertToFormValues,
exportClient, exportClient,
} from "../util"; } from "../util";
import useToggle from "../utils/useToggle";
import { AdvancedTab } from "./AdvancedTab"; import { AdvancedTab } from "./AdvancedTab";
import { ClientSettings } from "./ClientSettings"; import { ClientSettings } from "./ClientSettings";
import { Credentials } from "./credentials/Credentials"; import { Credentials } from "./credentials/Credentials";
@ -115,7 +116,7 @@ const ClientDetailHeader = ({
}, [client, t]); }, [client, t]);
const dropdownItems = [ const dropdownItems = [
<DropdownItem key="download" onClick={() => toggleDownloadDialog()}> <DropdownItem key="download" onClick={toggleDownloadDialog}>
{t("downloadAdapterConfig")} {t("downloadAdapterConfig")}
</DropdownItem>, </DropdownItem>,
<DropdownItem key="export" onClick={() => exportClient(client)}> <DropdownItem key="export" onClick={() => exportClient(client)}>
@ -127,7 +128,7 @@ const ClientDetailHeader = ({
<DropdownItem <DropdownItem
data-testid="delete-client" data-testid="delete-client"
key="delete" key="delete"
onClick={() => toggleDeleteDialog()} onClick={toggleDeleteDialog}
> >
{t("common:delete")} {t("common:delete")}
</DropdownItem>, </DropdownItem>,
@ -180,11 +181,8 @@ export default function ClientDetails() {
const history = useHistory(); const history = useHistory();
const [downloadDialogOpen, setDownloadDialogOpen] = useState(false); const [downloadDialogOpen, toggleDownloadDialogOpen] = useToggle();
const toggleDownloadDialog = () => setDownloadDialogOpen(!downloadDialogOpen); const [changeAuthenticatorOpen, toggleChangeAuthenticatorOpen] = useToggle();
const [changeAuthenticatorOpen, setChangeAuthenticatorOpen] = useState(false);
const toggleChangeAuthenticator = () =>
setChangeAuthenticatorOpen(!changeAuthenticatorOpen);
const [clientScopeSubTab, setClientScopeSubTab] = useState(30); const [clientScopeSubTab, setClientScopeSubTab] = useState(30);
const [authorizationSubTab, setAuthorizationSubTab] = useState(40); const [authorizationSubTab, setAuthorizationSubTab] = useState(40);
@ -259,7 +257,7 @@ export default function ClientDetails() {
client?.clientAuthenticatorType !== clientAuthenticatorType && client?.clientAuthenticatorType !== clientAuthenticatorType &&
!confirmed !confirmed
) { ) {
toggleChangeAuthenticator(); toggleChangeAuthenticatorOpen();
return; return;
} }
const redirectUris = toValue(form.getValues()["redirectUris"]); const redirectUris = toValue(form.getValues()["redirectUris"]);
@ -343,7 +341,7 @@ export default function ClientDetails() {
clientAuthenticatorType: clientAuthenticatorType, clientAuthenticatorType: clientAuthenticatorType,
})} })}
open={changeAuthenticatorOpen} open={changeAuthenticatorOpen}
toggleDialog={toggleChangeAuthenticator} toggleDialog={toggleChangeAuthenticatorOpen}
onConfirm={() => save({ confirmed: true })} onConfirm={() => save({ confirmed: true })}
> >
<> <>
@ -360,7 +358,7 @@ export default function ClientDetails() {
id={client.id!} id={client.id!}
protocol={client.protocol} protocol={client.protocol}
open={downloadDialogOpen} open={downloadDialogOpen}
toggleDialog={toggleDownloadDialog} toggleDialog={toggleDownloadDialogOpen}
/> />
<Controller <Controller
name="enabled" name="enabled"
@ -373,7 +371,7 @@ export default function ClientDetails() {
client={client} client={client}
save={save} save={save}
toggleDeleteDialog={toggleDeleteDialog} toggleDeleteDialog={toggleDeleteDialog}
toggleDownloadDialog={toggleDownloadDialog} toggleDownloadDialog={toggleDownloadDialogOpen}
/> />
)} )}
/> />

View file

@ -182,9 +182,9 @@ export const ClientSettings = ({
render={({ onChange, value }) => ( render={({ onChange, value }) => (
<Select <Select
toggleId="loginTheme" toggleId="loginTheme"
onToggle={() => setLoginThemeOpen(!loginThemeOpen)} onToggle={setLoginThemeOpen}
onSelect={(_, value) => { onSelect={(_, value) => {
onChange(value as string); onChange(value.toString());
setLoginThemeOpen(false); setLoginThemeOpen(false);
}} }}
selections={value || t("common:choose")} selections={value || t("common:choose")}

View file

@ -42,9 +42,9 @@ export const GeneralSettings = () => {
render={({ onChange, value }) => ( render={({ onChange, value }) => (
<Select <Select
id="kc-type" id="kc-type"
onToggle={() => isOpen(!open)} onToggle={isOpen}
onSelect={(_, value) => { onSelect={(_, value) => {
onChange(value as string); onChange(value.toString());
isOpen(false); isOpen(false);
}} }}
selections={value} selections={value}

View file

@ -78,7 +78,7 @@ export const SamlConfig = () => {
render={({ onChange, value }) => ( render={({ onChange, value }) => (
<Select <Select
toggleId="samlNameIdFormat" toggleId="samlNameIdFormat"
onToggle={(open) => setNameFormatOpen(open)} onToggle={setNameFormatOpen}
onSelect={(_, value) => { onSelect={(_, value) => {
onChange(value.toString()); onChange(value.toString());
setNameFormatOpen(false); setNameFormatOpen(false);

View file

@ -85,7 +85,7 @@ export const SamlSignature = () => {
render={({ onChange, value }) => ( render={({ onChange, value }) => (
<Select <Select
toggleId="signatureAlgorithm" toggleId="signatureAlgorithm"
onToggle={(open) => setAlgOpen(open)} onToggle={setAlgOpen}
onSelect={(_, value) => { onSelect={(_, value) => {
onChange(value.toString()); onChange(value.toString());
setAlgOpen(false); setAlgOpen(false);
@ -126,7 +126,7 @@ export const SamlSignature = () => {
render={({ onChange, value }) => ( render={({ onChange, value }) => (
<Select <Select
toggleId="signatureKeyName" toggleId="signatureKeyName"
onToggle={(open) => setKeyOpen(open)} onToggle={setKeyOpen}
onSelect={(_, value) => { onSelect={(_, value) => {
onChange(value.toString()); onChange(value.toString());
setKeyOpen(false); setKeyOpen(false);
@ -167,7 +167,7 @@ export const SamlSignature = () => {
render={({ onChange, value }) => ( render={({ onChange, value }) => (
<Select <Select
toggleId="canonicalization" toggleId="canonicalization"
onToggle={(open) => setCanOpen(open)} onToggle={setCanOpen}
onSelect={(_, value) => { onSelect={(_, value) => {
onChange(value.toString()); onChange(value.toString());
setCanOpen(false); setCanOpen(false);

View file

@ -118,7 +118,7 @@ export const AdvancedSettings = ({
<Select <Select
toggleId="keyForCodeExchange" toggleId="keyForCodeExchange"
variant={SelectVariant.single} variant={SelectVariant.single}
onToggle={() => setOpen(!open)} onToggle={setOpen}
isOpen={open} isOpen={open}
onSelect={(_, value) => { onSelect={(_, value) => {
onChange(value); onChange(value);

View file

@ -76,7 +76,7 @@ export const AuthenticationOverrides = ({
<Select <Select
toggleId="browserFlow" toggleId="browserFlow"
variant={SelectVariant.single} variant={SelectVariant.single}
onToggle={() => setBrowserFlowOpen(!browserFlowOpen)} onToggle={setBrowserFlowOpen}
isOpen={browserFlowOpen} isOpen={browserFlowOpen}
onSelect={(_, value) => { onSelect={(_, value) => {
onChange(value); onChange(value);
@ -109,7 +109,7 @@ export const AuthenticationOverrides = ({
<Select <Select
toggleId="directGrant" toggleId="directGrant"
variant={SelectVariant.single} variant={SelectVariant.single}
onToggle={() => setDirectGrantOpen(!directGrantOpen)} onToggle={setDirectGrantOpen}
isOpen={directGrantOpen} isOpen={directGrantOpen}
onSelect={(_, value) => { onSelect={(_, value) => {
onChange(value); onChange(value);

View file

@ -125,7 +125,7 @@ export const FineGrainOpenIdConnect = ({
<Select <Select
toggleId="accessTokenSignatureAlgorithm" toggleId="accessTokenSignatureAlgorithm"
variant={SelectVariant.single} variant={SelectVariant.single}
onToggle={() => setAccessTokenOpen(!accessTokenOpen)} onToggle={setAccessTokenOpen}
isOpen={accessTokenOpen} isOpen={accessTokenOpen}
onSelect={(_, value) => { onSelect={(_, value) => {
onChange(value); onChange(value);
@ -157,7 +157,7 @@ export const FineGrainOpenIdConnect = ({
<Select <Select
toggleId="idTokenSignatureAlgorithm" toggleId="idTokenSignatureAlgorithm"
variant={SelectVariant.single} variant={SelectVariant.single}
onToggle={() => setIdTokenOpen(!idTokenOpen)} onToggle={setIdTokenOpen}
isOpen={idTokenOpen} isOpen={idTokenOpen}
onSelect={(_, value) => { onSelect={(_, value) => {
onChange(value); onChange(value);
@ -189,9 +189,7 @@ export const FineGrainOpenIdConnect = ({
<Select <Select
toggleId="idTokenEncryptionKeyManagementAlgorithm" toggleId="idTokenEncryptionKeyManagementAlgorithm"
variant={SelectVariant.single} variant={SelectVariant.single}
onToggle={() => onToggle={setIdTokenKeyManagementOpen}
setIdTokenKeyManagementOpen(!idTokenKeyManagementOpen)
}
isOpen={idTokenKeyManagementOpen} isOpen={idTokenKeyManagementOpen}
onSelect={(_, value) => { onSelect={(_, value) => {
onChange(value); onChange(value);
@ -223,7 +221,7 @@ export const FineGrainOpenIdConnect = ({
<Select <Select
toggleId="idTokenEncryptionContentEncryptionAlgorithm" toggleId="idTokenEncryptionContentEncryptionAlgorithm"
variant={SelectVariant.single} variant={SelectVariant.single}
onToggle={() => setIdTokenContentOpen(!idTokenContentOpen)} onToggle={setIdTokenContentOpen}
isOpen={idTokenContentOpen} isOpen={idTokenContentOpen}
onSelect={(_, value) => { onSelect={(_, value) => {
onChange(value); onChange(value);
@ -255,9 +253,7 @@ export const FineGrainOpenIdConnect = ({
<Select <Select
toggleId="userInfoSignedResponseAlgorithm" toggleId="userInfoSignedResponseAlgorithm"
variant={SelectVariant.single} variant={SelectVariant.single}
onToggle={() => onToggle={setUserInfoSignedResponseOpen}
setUserInfoSignedResponseOpen(!userInfoSignedResponseOpen)
}
isOpen={userInfoSignedResponseOpen} isOpen={userInfoSignedResponseOpen}
onSelect={(_, value) => { onSelect={(_, value) => {
onChange(value); onChange(value);
@ -289,9 +285,7 @@ export const FineGrainOpenIdConnect = ({
<Select <Select
toggleId="requestObjectSignatureAlgorithm" toggleId="requestObjectSignatureAlgorithm"
variant={SelectVariant.single} variant={SelectVariant.single}
onToggle={() => onToggle={setRequestObjectSignatureOpen}
setRequestObjectSignatureOpen(!requestObjectSignatureOpen)
}
isOpen={requestObjectSignatureOpen} isOpen={requestObjectSignatureOpen}
onSelect={(_, value) => { onSelect={(_, value) => {
onChange(value); onChange(value);
@ -323,9 +317,7 @@ export const FineGrainOpenIdConnect = ({
<Select <Select
toggleId="requestObjectRequired" toggleId="requestObjectRequired"
variant={SelectVariant.single} variant={SelectVariant.single}
onToggle={() => onToggle={setRequestObjectRequiredOpen}
setRequestObjectRequiredOpen(!requestObjectRequiredOpen)
}
isOpen={requestObjectRequiredOpen} isOpen={requestObjectRequiredOpen}
onSelect={(_, value) => { onSelect={(_, value) => {
onChange(value); onChange(value);

View file

@ -67,7 +67,7 @@ export const TokenLifespan = ({
<SplitItem> <SplitItem>
<Select <Select
variant={SelectVariant.single} variant={SelectVariant.single}
onToggle={(isExpanded) => setOpen(isExpanded)} onToggle={setOpen}
isOpen={open} isOpen={open}
onSelect={(_, value) => { onSelect={(_, value) => {
onChange(value); onChange(value);

View file

@ -155,7 +155,7 @@ export const Credentials = ({ clientId, save }: CredentialsProps) => {
<Select <Select
toggleId="kc-client-authenticator-type" toggleId="kc-client-authenticator-type"
required required
onToggle={() => isOpen(!open)} onToggle={isOpen}
onSelect={(_, value) => { onSelect={(_, value) => {
onChange(value as string); onChange(value as string);
isOpen(false); isOpen(false);

View file

@ -40,9 +40,9 @@ export const SignedJWT = () => {
<Select <Select
maxHeight={200} maxHeight={200}
toggleId="kc-signature-algorithm" toggleId="kc-signature-algorithm"
onToggle={() => isOpen(!open)} onToggle={isOpen}
onSelect={(_, value) => { onSelect={(_, value) => {
onChange(value as string); onChange(value.toString());
isOpen(false); isOpen(false);
}} }}
selections={value || t("anyAlgorithm")} selections={value || t("anyAlgorithm")}

View file

@ -61,7 +61,7 @@ export const KeyForm = ({
render={({ onChange, value }) => ( render={({ onChange, value }) => (
<Select <Select
toggleId="archiveFormat" toggleId="archiveFormat"
onToggle={(isExpanded) => setOpenArchiveFormat(isExpanded)} onToggle={setOpenArchiveFormat}
onSelect={(_, value) => { onSelect={(_, value) => {
onChange(value.toString()); onChange(value.toString());
setOpenArchiveFormat(false); setOpenArchiveFormat(false);

View file

@ -105,7 +105,7 @@ export const ImportKeyDialog = ({
render={({ onChange, value }) => ( render={({ onChange, value }) => (
<Select <Select
toggleId="archiveFormat" toggleId="archiveFormat"
onToggle={() => setOpenArchiveFormat(!openArchiveFormat)} onToggle={setOpenArchiveFormat}
onSelect={(_, value) => { onSelect={(_, value) => {
onChange(value as string); onChange(value as string);
setOpenArchiveFormat(false); setOpenArchiveFormat(false);

View file

@ -26,6 +26,7 @@ import type { ClientForm } from "../ClientDetails";
import { GenerateKeyDialog } from "./GenerateKeyDialog"; import { GenerateKeyDialog } from "./GenerateKeyDialog";
import { useFetch, useAdminClient } from "../../context/auth/AdminClient"; import { useFetch, useAdminClient } from "../../context/auth/AdminClient";
import { useAlerts } from "../../components/alert/Alerts"; import { useAlerts } from "../../components/alert/Alerts";
import useToggle from "../../utils/useToggle";
import { ImportKeyDialog, ImportFile } from "./ImportKeyDialog"; import { ImportKeyDialog, ImportFile } from "./ImportKeyDialog";
import { Certificate } from "./Certificate"; import { Certificate } from "./Certificate";
@ -47,8 +48,9 @@ export const Keys = ({ clientId, save }: KeysProps) => {
const { addAlert, addError } = useAlerts(); const { addAlert, addError } = useAlerts();
const [keyInfo, setKeyInfo] = useState<CertificateRepresentation>(); const [keyInfo, setKeyInfo] = useState<CertificateRepresentation>();
const [openGenerateKeys, setOpenGenerateKeys] = useState(false); const [openGenerateKeys, toggleOpenGenerateKeys, setOpenGenerateKeys] =
const [openImportKeys, setOpenImportKeys] = useState(false); useToggle();
const [openImportKeys, toggleOpenImportKeys, setOpenImportKeys] = useToggle();
const useJwksUrl = useWatch({ const useJwksUrl = useWatch({
control, control,
@ -104,15 +106,12 @@ export const Keys = ({ clientId, save }: KeysProps) => {
<PageSection variant="light" className="keycloak__form"> <PageSection variant="light" className="keycloak__form">
{openGenerateKeys && ( {openGenerateKeys && (
<GenerateKeyDialog <GenerateKeyDialog
toggleDialog={() => setOpenGenerateKeys(!openGenerateKeys)} toggleDialog={toggleOpenGenerateKeys}
save={generate} save={generate}
/> />
)} )}
{openImportKeys && ( {openImportKeys && (
<ImportKeyDialog <ImportKeyDialog toggleDialog={toggleOpenImportKeys} save={importKey} />
toggleDialog={() => setOpenImportKeys(!openImportKeys)}
save={importKey}
/>
)} )}
<Card isFlat> <Card isFlat>
<CardHeader> <CardHeader>

View file

@ -26,9 +26,10 @@ import {
clientScopeTypesDropdown, clientScopeTypesDropdown,
} from "../../components/client-scope/ClientScopeTypes"; } from "../../components/client-scope/ClientScopeTypes";
import { KeycloakDataTable } from "../../components/table-toolbar/KeycloakDataTable"; import { KeycloakDataTable } from "../../components/table-toolbar/KeycloakDataTable";
import { getProtocolName } from "../utils";
import useToggle from "../../utils/useToggle";
import "./client-scopes.css"; import "./client-scopes.css";
import { getProtocolName } from "../utils";
export type AddScopeDialogProps = { export type AddScopeDialogProps = {
clientScopes: ClientScopeRepresentation[]; clientScopes: ClientScopeRepresentation[];
@ -68,11 +69,11 @@ export const AddScopeDialog = ({
const [key, setKey] = useState(0); const [key, setKey] = useState(0);
const refresh = () => setKey(key + 1); const refresh = () => setKey(key + 1);
const [isFilterTypeDropdownOpen, setIsFilterTypeDropdownOpen] = const [isFilterTypeDropdownOpen, toggleIsFilterTypeDropdownOpen] =
useState(false); useToggle();
const [isProtocolTypeDropdownOpen, setIsProtocolTypeDropdownOpen] = const [isProtocolTypeDropdownOpen, toggleIsProtocolTypeDropdownOpen] =
useState(false); useToggle(false);
useEffect(() => { useEffect(() => {
refresh(); refresh();
@ -97,14 +98,6 @@ export const AddScopeDialog = ({
toggleDialog(); toggleDialog();
}; };
const onFilterTypeDropdownToggle = () => {
setIsFilterTypeDropdownOpen(!isFilterTypeDropdownOpen);
};
const onProtocolTypeDropdownToggle = () => {
setIsProtocolTypeDropdownOpen(!isProtocolTypeDropdownOpen);
};
const onFilterTypeDropdownSelect = (filterType: string) => { const onFilterTypeDropdownSelect = (filterType: string) => {
if (filterType === FilterType.Name) { if (filterType === FilterType.Name) {
setFilterType(FilterType.Protocol); setFilterType(FilterType.Protocol);
@ -112,7 +105,7 @@ export const AddScopeDialog = ({
setFilterType(FilterType.Name); setFilterType(FilterType.Name);
} }
setIsFilterTypeDropdownOpen(!isFilterTypeDropdownOpen); toggleIsFilterTypeDropdownOpen();
}; };
const onProtocolTypeDropdownSelect = (protocolType: string) => { const onProtocolTypeDropdownSelect = (protocolType: string) => {
@ -124,7 +117,7 @@ export const AddScopeDialog = ({
setProtocolType(ProtocolType.All); setProtocolType(ProtocolType.All);
} }
setIsProtocolTypeDropdownOpen(!isProtocolTypeDropdownOpen); toggleIsProtocolTypeDropdownOpen();
}; };
const protocolTypeOptions = [ const protocolTypeOptions = [
@ -226,7 +219,7 @@ export const AddScopeDialog = ({
toggle={ toggle={
<DropdownToggle <DropdownToggle
id="toggle-id-9" id="toggle-id-9"
onToggle={onFilterTypeDropdownToggle} onToggle={toggleIsFilterTypeDropdownOpen}
toggleIndicator={CaretDownIcon} toggleIndicator={CaretDownIcon}
icon={<FilterIcon />} icon={<FilterIcon />}
> >
@ -258,7 +251,7 @@ export const AddScopeDialog = ({
toggle={ toggle={
<DropdownToggle <DropdownToggle
id="toggle-id-9" id="toggle-id-9"
onToggle={onFilterTypeDropdownToggle} onToggle={toggleIsFilterTypeDropdownOpen}
toggleIndicator={CaretDownIcon} toggleIndicator={CaretDownIcon}
icon={<FilterIcon />} icon={<FilterIcon />}
> >
@ -279,7 +272,7 @@ export const AddScopeDialog = ({
variant={SelectVariant.single} variant={SelectVariant.single}
className="kc-protocolType-select" className="kc-protocolType-select"
aria-label="Select Input" aria-label="Select Input"
onToggle={onProtocolTypeDropdownToggle} onToggle={toggleIsProtocolTypeDropdownOpen}
onSelect={(_, value) => onSelect={(_, value) =>
onProtocolTypeDropdownSelect(value.toString()) onProtocolTypeDropdownSelect(value.toString())
} }

View file

@ -0,0 +1,41 @@
/**
* @jest-environment jsdom
*/
import { act, renderHook } from "@testing-library/react-hooks";
import useToggle from "./useToggle";
describe("useToggle", () => {
it("has a default value of false", () => {
const { result } = renderHook(() => useToggle());
const [value] = result.current;
expect(value).toBe(false);
});
it("uses the initial value", () => {
const { result } = renderHook(() => useToggle(true));
const [value] = result.current;
expect(value).toBe(true);
});
it("toggles the value", () => {
const { result } = renderHook(() => useToggle());
const [, toggleValue] = result.current;
act(() => toggleValue());
const [value] = result.current;
expect(value).toBe(true);
});
it("sets the value", () => {
const { result } = renderHook(() => useToggle());
const [, , setValue] = result.current;
act(() => setValue(true));
const [value] = result.current;
expect(value).toBe(true);
});
});

13
src/utils/useToggle.ts Normal file
View file

@ -0,0 +1,13 @@
import { useCallback, useState } from "react";
/**
* A hook that allows you toggle a boolean value, useful for toggle buttons, showing and hiding modals, etc.
*
* @param initialValue The initial value to use, false by default.
*/
export default function useToggle(initialValue = false) {
const [value, setValue] = useState(initialValue);
const toggleValue = useCallback(() => setValue((val) => !val), []);
return [value, toggleValue, setValue] as const;
}