init
This commit is contained in:
parent
9537cfb060
commit
82ab2fc8fd
@ -1,3 +1,4 @@
|
||||
import "./main.css";
|
||||
import { Suspense, lazy } from "react";
|
||||
import type { ClassKey } from "keycloakify/login";
|
||||
import type { KcContext } from "./KcContext";
|
||||
|
147
src/login/main.css
Normal file
147
src/login/main.css
Normal file
@ -0,0 +1,147 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,100;0,300;0,400;0,700;0,900;1,100;1,300;1,400;1,700;1,900&display=swap');
|
||||
|
||||
@font-face {
|
||||
font-family: "Hermit";
|
||||
src: url(https://penguinowl.dev/assets/Hermit-Regular.otf) format("opentype");
|
||||
}
|
||||
|
||||
html {
|
||||
background: #010b0f;
|
||||
}
|
||||
|
||||
body.kcBodyClass {
|
||||
background: linear-gradient(150deg, rgb(1, 11, 15) 0%, rgb(33, 33, 38) 100%);
|
||||
}
|
||||
|
||||
div.kcHeaderClass {
|
||||
color: #f0f0f0 !important;
|
||||
}
|
||||
|
||||
h1#kc-page-title {
|
||||
font-family: "Lato";
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
text-align: left;
|
||||
color: #f0f0f0;
|
||||
}
|
||||
|
||||
label.kcLabelClass {
|
||||
color: #f0f0f0;
|
||||
font-family: "Lato";
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
header.kcFormHeaderClass {
|
||||
flex-direction: row-reverse !important;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
input.kcButtonClass {
|
||||
font-family: "Hermit";
|
||||
font-size: 18px;
|
||||
background-color: green !important;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
#kc-current-locale-link {
|
||||
color: #e0e0e0 !important;
|
||||
}
|
||||
|
||||
button.kcFormPasswordVisibilityButtonClass {
|
||||
background-color: #333333 !important;
|
||||
border-color: yellow !important;
|
||||
height: 36px;
|
||||
border-width: 1px !important;
|
||||
border-radius: 2px !important;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
i.kcFormPasswordVisibilityIconShow {
|
||||
color: #f0f0f0 !important;
|
||||
}
|
||||
|
||||
i.kcFormPasswordVisibilityIconHide {
|
||||
color: #f0f0f0 !important;
|
||||
}
|
||||
|
||||
#kc-header {
|
||||
color: inherit;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
div.kcFormCardClass {
|
||||
border-radius: 30px;
|
||||
border-color: none;
|
||||
border-top: none;
|
||||
box-shadow: 0px 0px 40px #53504a70;
|
||||
border-style: solid;
|
||||
border-color: #9b9b19;
|
||||
background: #05061c;
|
||||
}
|
||||
|
||||
input.kcInputClass {
|
||||
background-color: #333333;
|
||||
border-color: yellow;
|
||||
border-radius: 2px;
|
||||
color: #f0f0f0;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
#kc-header-wrapper {
|
||||
color: #f0f0f0;
|
||||
margin-bottom: 13px;
|
||||
margin-left: 18px;
|
||||
}
|
||||
#kc-locale {
|
||||
top: 36px;
|
||||
}
|
||||
#kc-page-title {
|
||||
margin-bottom: -10px;
|
||||
}
|
||||
header.kcFormHeaderClass {
|
||||
margin-top: 10px;
|
||||
justify-content: left;
|
||||
}
|
||||
.login-pf-page .card-pf {
|
||||
max-width: none;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
padding-top: 2px;
|
||||
border-top-width: 2px;
|
||||
border-top-color: rgb(155, 155, 25) !important;
|
||||
border-top: solid;
|
||||
box-shadow: 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
#kc-form-buttons {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #e0e000;
|
||||
}
|
||||
|
||||
.login-pf a:hover {
|
||||
color: #a0f000;
|
||||
}
|
||||
|
||||
#kc-form-options .checkbox {
|
||||
color: #f0f0f0
|
||||
}
|
||||
|
||||
.pf-m-control::after {
|
||||
border-width: 0;
|
||||
border-bottom-width: 0 !important;
|
||||
}
|
||||
|
||||
.pf-c-button.pf-m-control:hover::after {
|
||||
border-bottom-width: 0;
|
||||
}
|
||||
|
||||
div.kcInputGroup {
|
||||
border-radius: 2px;
|
||||
border-width: 0px;
|
||||
background-color: yellow;
|
||||
}
|
360
src/login/pages/Login.stories.tsx
Normal file
360
src/login/pages/Login.stories.tsx
Normal file
@ -0,0 +1,360 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { createKcPageStory } from "../KcPageStory";
|
||||
|
||||
const { KcPageStory } = createKcPageStory({ pageId: "login.ftl" });
|
||||
|
||||
const meta = {
|
||||
title: "login/login.ftl",
|
||||
component: KcPageStory
|
||||
} satisfies Meta<typeof KcPageStory>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => <KcPageStory />
|
||||
};
|
||||
|
||||
export const WithInvalidCredential: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
login: {
|
||||
username: "johndoe"
|
||||
},
|
||||
messagesPerField: {
|
||||
// NOTE: The other functions of messagesPerField are derived from get() and
|
||||
// existsError() so they are the only ones that need to mock.
|
||||
existsError: (fieldName: string, ...otherFieldNames: string[]) => {
|
||||
const fieldNames = [fieldName, ...otherFieldNames];
|
||||
return fieldNames.includes("username") || fieldNames.includes("password");
|
||||
},
|
||||
get: (fieldName: string) => {
|
||||
if (fieldName === "username" || fieldName === "password") {
|
||||
return "Invalid username or password.";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithoutRegistration: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
realm: { registrationAllowed: false }
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithoutRememberMe: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
realm: { rememberMe: false }
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithoutPasswordReset: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
realm: { resetPasswordAllowed: false }
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithEmailAsUsername: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
realm: { loginWithEmailAllowed: false }
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithPresetUsername: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
login: { username: "max.mustermann@mail.com" }
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithImmutablePresetUsername: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
auth: {
|
||||
attemptedUsername: "max.mustermann@mail.com",
|
||||
showUsername: true
|
||||
},
|
||||
usernameHidden: true,
|
||||
message: {
|
||||
type: "info",
|
||||
summary: "Please re-authenticate to continue"
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithSocialProviders: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
social: {
|
||||
displayInfo: true,
|
||||
providers: [
|
||||
{
|
||||
loginUrl: "google",
|
||||
alias: "google",
|
||||
providerId: "google",
|
||||
displayName: "Google",
|
||||
iconClasses: "fa fa-google"
|
||||
},
|
||||
{
|
||||
loginUrl: "microsoft",
|
||||
alias: "microsoft",
|
||||
providerId: "microsoft",
|
||||
displayName: "Microsoft",
|
||||
iconClasses: "fa fa-windows"
|
||||
},
|
||||
{
|
||||
loginUrl: "facebook",
|
||||
alias: "facebook",
|
||||
providerId: "facebook",
|
||||
displayName: "Facebook",
|
||||
iconClasses: "fa fa-facebook"
|
||||
},
|
||||
{
|
||||
loginUrl: "instagram",
|
||||
alias: "instagram",
|
||||
providerId: "instagram",
|
||||
displayName: "Instagram",
|
||||
iconClasses: "fa fa-instagram"
|
||||
},
|
||||
{
|
||||
loginUrl: "twitter",
|
||||
alias: "twitter",
|
||||
providerId: "twitter",
|
||||
displayName: "Twitter",
|
||||
iconClasses: "fa fa-twitter"
|
||||
},
|
||||
{
|
||||
loginUrl: "linkedin",
|
||||
alias: "linkedin",
|
||||
providerId: "linkedin",
|
||||
displayName: "LinkedIn",
|
||||
iconClasses: "fa fa-linkedin"
|
||||
},
|
||||
{
|
||||
loginUrl: "stackoverflow",
|
||||
alias: "stackoverflow",
|
||||
providerId: "stackoverflow",
|
||||
displayName: "Stackoverflow",
|
||||
iconClasses: "fa fa-stack-overflow"
|
||||
},
|
||||
{
|
||||
loginUrl: "github",
|
||||
alias: "github",
|
||||
providerId: "github",
|
||||
displayName: "Github",
|
||||
iconClasses: "fa fa-github"
|
||||
},
|
||||
{
|
||||
loginUrl: "gitlab",
|
||||
alias: "gitlab",
|
||||
providerId: "gitlab",
|
||||
displayName: "Gitlab",
|
||||
iconClasses: "fa fa-gitlab"
|
||||
},
|
||||
{
|
||||
loginUrl: "bitbucket",
|
||||
alias: "bitbucket",
|
||||
providerId: "bitbucket",
|
||||
displayName: "Bitbucket",
|
||||
iconClasses: "fa fa-bitbucket"
|
||||
},
|
||||
{
|
||||
loginUrl: "paypal",
|
||||
alias: "paypal",
|
||||
providerId: "paypal",
|
||||
displayName: "PayPal",
|
||||
iconClasses: "fa fa-paypal"
|
||||
},
|
||||
{
|
||||
loginUrl: "openshift",
|
||||
alias: "openshift",
|
||||
providerId: "openshift",
|
||||
displayName: "OpenShift",
|
||||
iconClasses: "fa fa-cloud"
|
||||
}
|
||||
]
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithoutPasswordField: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
realm: { password: false }
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithErrorMessage: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
message: {
|
||||
summary: "The time allotted for the connection has elapsed.<br/>The login process will restart from the beginning.",
|
||||
type: "error"
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithOneSocialProvider: Story = {
|
||||
render: args => (
|
||||
<KcPageStory
|
||||
{...args}
|
||||
kcContext={{
|
||||
social: {
|
||||
displayInfo: true,
|
||||
providers: [
|
||||
{
|
||||
loginUrl: "google",
|
||||
alias: "google",
|
||||
providerId: "google",
|
||||
displayName: "Google",
|
||||
iconClasses: "fa fa-google"
|
||||
}
|
||||
]
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithTwoSocialProviders: Story = {
|
||||
render: args => (
|
||||
<KcPageStory
|
||||
{...args}
|
||||
kcContext={{
|
||||
social: {
|
||||
displayInfo: true,
|
||||
providers: [
|
||||
{
|
||||
loginUrl: "google",
|
||||
alias: "google",
|
||||
providerId: "google",
|
||||
displayName: "Google",
|
||||
iconClasses: "fa fa-google"
|
||||
},
|
||||
{
|
||||
loginUrl: "microsoft",
|
||||
alias: "microsoft",
|
||||
providerId: "microsoft",
|
||||
displayName: "Microsoft",
|
||||
iconClasses: "fa fa-windows"
|
||||
}
|
||||
]
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
export const WithNoSocialProviders: Story = {
|
||||
render: args => (
|
||||
<KcPageStory
|
||||
{...args}
|
||||
kcContext={{
|
||||
social: {
|
||||
displayInfo: true,
|
||||
providers: []
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
export const WithMoreThanTwoSocialProviders: Story = {
|
||||
render: args => (
|
||||
<KcPageStory
|
||||
{...args}
|
||||
kcContext={{
|
||||
social: {
|
||||
displayInfo: true,
|
||||
providers: [
|
||||
{
|
||||
loginUrl: "google",
|
||||
alias: "google",
|
||||
providerId: "google",
|
||||
displayName: "Google",
|
||||
iconClasses: "fa fa-google"
|
||||
},
|
||||
{
|
||||
loginUrl: "microsoft",
|
||||
alias: "microsoft",
|
||||
providerId: "microsoft",
|
||||
displayName: "Microsoft",
|
||||
iconClasses: "fa fa-windows"
|
||||
},
|
||||
{
|
||||
loginUrl: "facebook",
|
||||
alias: "facebook",
|
||||
providerId: "facebook",
|
||||
displayName: "Facebook",
|
||||
iconClasses: "fa fa-facebook"
|
||||
},
|
||||
{
|
||||
loginUrl: "twitter",
|
||||
alias: "twitter",
|
||||
providerId: "twitter",
|
||||
displayName: "Twitter",
|
||||
iconClasses: "fa fa-twitter"
|
||||
}
|
||||
]
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
export const WithSocialProvidersAndWithoutRememberMe: Story = {
|
||||
render: args => (
|
||||
<KcPageStory
|
||||
{...args}
|
||||
kcContext={{
|
||||
social: {
|
||||
displayInfo: true,
|
||||
providers: [
|
||||
{
|
||||
loginUrl: "google",
|
||||
alias: "google",
|
||||
providerId: "google",
|
||||
displayName: "Google",
|
||||
iconClasses: "fa fa-google"
|
||||
}
|
||||
]
|
||||
},
|
||||
realm: { rememberMe: false }
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
219
src/login/pages/Login.tsx
Normal file
219
src/login/pages/Login.tsx
Normal file
@ -0,0 +1,219 @@
|
||||
import type { JSX } from "keycloakify/tools/JSX";
|
||||
import { useState } from "react";
|
||||
import { kcSanitize } from "keycloakify/lib/kcSanitize";
|
||||
import { useIsPasswordRevealed } from "keycloakify/tools/useIsPasswordRevealed";
|
||||
import { clsx } from "keycloakify/tools/clsx";
|
||||
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
||||
import { getKcClsx, type KcClsx } from "keycloakify/login/lib/kcClsx";
|
||||
import type { KcContext } from "../KcContext";
|
||||
import type { I18n } from "../i18n";
|
||||
|
||||
export default function Login(props: PageProps<Extract<KcContext, { pageId: "login.ftl" }>, I18n>) {
|
||||
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
|
||||
|
||||
const { kcClsx } = getKcClsx({
|
||||
doUseDefaultCss,
|
||||
classes
|
||||
});
|
||||
|
||||
const { social, realm, url, usernameHidden, login, auth, registrationDisabled, messagesPerField } = kcContext;
|
||||
|
||||
const { msg, msgStr } = i18n;
|
||||
|
||||
const [isLoginButtonDisabled, setIsLoginButtonDisabled] = useState(false);
|
||||
|
||||
return (
|
||||
<Template
|
||||
kcContext={kcContext}
|
||||
i18n={i18n}
|
||||
doUseDefaultCss={doUseDefaultCss}
|
||||
classes={classes}
|
||||
displayMessage={!messagesPerField.existsError("username", "password")}
|
||||
headerNode={msg("loginAccountTitle")}
|
||||
displayInfo={realm.password && realm.registrationAllowed && !registrationDisabled}
|
||||
infoNode={
|
||||
<div id="kc-registration-container">
|
||||
<div id="kc-registration">
|
||||
<span>
|
||||
{msg("noAccount")}{" "}
|
||||
<a tabIndex={8} href={url.registrationUrl}>
|
||||
{msg("doRegister")}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
socialProvidersNode={
|
||||
<>
|
||||
{realm.password && social?.providers !== undefined && social.providers.length !== 0 && (
|
||||
<div id="kc-social-providers" className={kcClsx("kcFormSocialAccountSectionClass")}>
|
||||
<hr />
|
||||
<h2>{msg("identity-provider-login-label")}</h2>
|
||||
<ul className={kcClsx("kcFormSocialAccountListClass", social.providers.length > 3 && "kcFormSocialAccountListGridClass")}>
|
||||
{social.providers.map((...[p, , providers]) => (
|
||||
<li key={p.alias}>
|
||||
<a
|
||||
id={`social-${p.alias}`}
|
||||
className={kcClsx(
|
||||
"kcFormSocialAccountListButtonClass",
|
||||
providers.length > 3 && "kcFormSocialAccountGridItem"
|
||||
)}
|
||||
type="button"
|
||||
href={p.loginUrl}
|
||||
>
|
||||
{p.iconClasses && <i className={clsx(kcClsx("kcCommonLogoIdP"), p.iconClasses)} aria-hidden="true"></i>}
|
||||
<span
|
||||
className={clsx(kcClsx("kcFormSocialAccountNameClass"), p.iconClasses && "kc-social-icon-text")}
|
||||
dangerouslySetInnerHTML={{ __html: kcSanitize(p.displayName) }}
|
||||
></span>
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div id="kc-form">
|
||||
<div id="kc-form-wrapper">
|
||||
{realm.password && (
|
||||
<form
|
||||
id="kc-form-login"
|
||||
onSubmit={() => {
|
||||
setIsLoginButtonDisabled(true);
|
||||
return true;
|
||||
}}
|
||||
action={url.loginAction}
|
||||
method="post"
|
||||
>
|
||||
{!usernameHidden && (
|
||||
<div className={kcClsx("kcFormGroupClass")}>
|
||||
<label htmlFor="username" className={kcClsx("kcLabelClass")}>
|
||||
{!realm.loginWithEmailAllowed
|
||||
? msg("username")
|
||||
: !realm.registrationEmailAsUsername
|
||||
? msg("usernameOrEmail")
|
||||
: msg("email")}
|
||||
</label>
|
||||
<input
|
||||
tabIndex={2}
|
||||
id="username"
|
||||
className={kcClsx("kcInputClass")}
|
||||
name="username"
|
||||
defaultValue={login.username ?? ""}
|
||||
type="text"
|
||||
autoFocus
|
||||
autoComplete="username"
|
||||
aria-invalid={messagesPerField.existsError("username", "password")}
|
||||
/>
|
||||
{messagesPerField.existsError("username", "password") && (
|
||||
<span
|
||||
id="input-error"
|
||||
className={kcClsx("kcInputErrorMessageClass")}
|
||||
aria-live="polite"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: kcSanitize(messagesPerField.getFirstError("username", "password"))
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={kcClsx("kcFormGroupClass")}>
|
||||
<label htmlFor="password" className={kcClsx("kcLabelClass")}>
|
||||
{msg("password")}
|
||||
</label>
|
||||
<PasswordWrapper kcClsx={kcClsx} i18n={i18n} passwordInputId="password">
|
||||
<input
|
||||
tabIndex={3}
|
||||
id="password"
|
||||
className={kcClsx("kcInputClass")}
|
||||
name="password"
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
aria-invalid={messagesPerField.existsError("username", "password")}
|
||||
/>
|
||||
</PasswordWrapper>
|
||||
{usernameHidden && messagesPerField.existsError("username", "password") && (
|
||||
<span
|
||||
id="input-error"
|
||||
className={kcClsx("kcInputErrorMessageClass")}
|
||||
aria-live="polite"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: kcSanitize(messagesPerField.getFirstError("username", "password"))
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={kcClsx("kcFormGroupClass", "kcFormSettingClass")}>
|
||||
<div id="kc-form-options">
|
||||
{realm.rememberMe && !usernameHidden && (
|
||||
<div className="checkbox">
|
||||
<label>
|
||||
<input
|
||||
tabIndex={5}
|
||||
id="rememberMe"
|
||||
name="rememberMe"
|
||||
type="checkbox"
|
||||
defaultChecked={!!login.rememberMe}
|
||||
/>{" "}
|
||||
{msg("rememberMe")}
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className={kcClsx("kcFormOptionsWrapperClass")}>
|
||||
{realm.resetPasswordAllowed && (
|
||||
<span>
|
||||
<a tabIndex={6} href={url.loginResetCredentialsUrl}>
|
||||
{msg("doForgotPassword")}
|
||||
</a>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="kc-form-buttons" className={kcClsx("kcFormGroupClass")}>
|
||||
<input type="hidden" id="id-hidden-input" name="credentialId" value={auth.selectedCredential} />
|
||||
<input
|
||||
tabIndex={7}
|
||||
disabled={isLoginButtonDisabled}
|
||||
className={kcClsx("kcButtonClass", "kcButtonPrimaryClass", "kcButtonBlockClass", "kcButtonLargeClass")}
|
||||
name="login"
|
||||
id="kc-login"
|
||||
type="submit"
|
||||
value={msgStr("doLogIn")}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Template>
|
||||
);
|
||||
}
|
||||
|
||||
function PasswordWrapper(props: { kcClsx: KcClsx; i18n: I18n; passwordInputId: string; children: JSX.Element }) {
|
||||
const { kcClsx, i18n, passwordInputId, children } = props;
|
||||
|
||||
const { msgStr } = i18n;
|
||||
|
||||
const { isPasswordRevealed, toggleIsPasswordRevealed } = useIsPasswordRevealed({ passwordInputId });
|
||||
|
||||
return (
|
||||
<div className={kcClsx("kcInputGroup")}>
|
||||
{children}
|
||||
<button
|
||||
type="button"
|
||||
className={kcClsx("kcFormPasswordVisibilityButtonClass")}
|
||||
aria-label={msgStr(isPasswordRevealed ? "hidePassword" : "showPassword")}
|
||||
aria-controls={passwordInputId}
|
||||
onClick={toggleIsPasswordRevealed}
|
||||
>
|
||||
<i className={kcClsx(isPasswordRevealed ? "kcFormPasswordVisibilityIconHide" : "kcFormPasswordVisibilityIconShow")} aria-hidden />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user