pf5 login (#27222)
* better alignment for the login Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * added register pf5 to login Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> --------- Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>
This commit is contained in:
parent
4a357223b3
commit
60126d5d2e
5 changed files with 336 additions and 5 deletions
|
@ -77,6 +77,7 @@
|
||||||
x-on:focusin.window="! $refs.panel?.contains($event.target) && close()"
|
x-on:focusin.window="! $refs.panel?.contains($event.target) && close()"
|
||||||
x-id="['language-select']"
|
x-id="['language-select']"
|
||||||
>
|
>
|
||||||
|
<div class="pf-v5-c-login__container">
|
||||||
<main class="pf-v5-c-login__main">
|
<main class="pf-v5-c-login__main">
|
||||||
<header class="pf-v5-c-login__main-header">
|
<header class="pf-v5-c-login__main-header">
|
||||||
<h1 class="pf-v5-c-title pf-m-3xl"><#nested "header"></h1>
|
<h1 class="pf-v5-c-title pf-m-3xl"><#nested "header"></h1>
|
||||||
|
@ -213,6 +214,7 @@
|
||||||
<#nested "socialProviders">
|
<#nested "socialProviders">
|
||||||
</footer>
|
</footer>
|
||||||
</main>
|
</main>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
<#macro termsAcceptance>
|
||||||
|
<#if termsAcceptanceRequired??>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="${properties.kcInputWrapperClass!}">
|
||||||
|
${msg("termsTitle")}
|
||||||
|
<div id="kc-registration-terms-text">
|
||||||
|
${kcSanitize(msg("termsText"))?no_esc}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="${properties.kcLabelWrapperClass!}">
|
||||||
|
<input type="checkbox" id="termsAccepted" name="termsAccepted" class="${properties.kcCheckboxInputClass!}"
|
||||||
|
aria-invalid="<#if messagesPerField.existsError('termsAccepted')>true</#if>"
|
||||||
|
/>
|
||||||
|
<label for="termsAccepted" class="${properties.kcLabelClass!}">${msg("acceptTerms")}</label>
|
||||||
|
</div>
|
||||||
|
<#if messagesPerField.existsError('termsAccepted')>
|
||||||
|
<div class="${properties.kcLabelWrapperClass!}">
|
||||||
|
<span id="input-error-terms-accepted" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
||||||
|
${kcSanitize(messagesPerField.get('termsAccepted'))?no_esc}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</#if>
|
||||||
|
</div>
|
||||||
|
</#if>
|
||||||
|
</#macro>
|
101
themes/src/main/resources/theme/keycloak.v2/login/register.ftl
Executable file
101
themes/src/main/resources/theme/keycloak.v2/login/register.ftl
Executable file
|
@ -0,0 +1,101 @@
|
||||||
|
<#import "pf-5-template.ftl" as layout>
|
||||||
|
<#import "user-profile-commons.ftl" as userProfileCommons>
|
||||||
|
<#import "register-commons.ftl" as registerCommons>
|
||||||
|
<@layout.registrationLayout displayMessage=messagesPerField.exists('global') displayRequiredFields=true; section>
|
||||||
|
<#if section = "header">
|
||||||
|
${msg("registerTitle")}
|
||||||
|
<#elseif section = "form">
|
||||||
|
<form id="kc-register-form" class="${properties.kcFormClass!}" action="${url.registrationAction}" method="post">
|
||||||
|
|
||||||
|
<@userProfileCommons.userProfileFormFields; callback, attribute>
|
||||||
|
<#if callback = "afterField">
|
||||||
|
<#-- render password fields just under the username or email (if used as username) -->
|
||||||
|
<#if passwordRequired?? && (attribute.name == 'username' || (attribute.name == 'email' && realm.registrationEmailAsUsername))>
|
||||||
|
<div class="${properties.kcFormGroupClass!}">
|
||||||
|
<label for="password" class="${properties.kcLabelClass!}">
|
||||||
|
<span class="pf-v5-c-form__label-text">
|
||||||
|
${msg("password")}
|
||||||
|
<span class="pf-v5-c-form__label-required" aria-hidden="true">*</span>
|
||||||
|
<span>
|
||||||
|
</label>
|
||||||
|
<span class="${properties.kcInputGroup!}">
|
||||||
|
<span class="${properties.kcInputClass!}">
|
||||||
|
<input type="password" id="password" name="password"
|
||||||
|
autocomplete="new-password"
|
||||||
|
aria-invalid="<#if messagesPerField.existsError('password','password-confirm')>true</#if>"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<button class="${properties.kcFormPasswordVisibilityButtonClass!}" type="button" aria-label="${msg('showPassword')}"
|
||||||
|
aria-controls="password" data-password-toggle
|
||||||
|
data-icon-show="${properties.kcFormPasswordVisibilityIconShow!}" data-icon-hide="${properties.kcFormPasswordVisibilityIconHide!}"
|
||||||
|
data-label-show="${msg('showPassword')}" data-label-hide="${msg('hidePassword')}">
|
||||||
|
<i class="${properties.kcFormPasswordVisibilityIconShow!}" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<#if messagesPerField.existsError('password')>
|
||||||
|
<span id="input-error-password" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
||||||
|
${kcSanitize(messagesPerField.get('password'))?no_esc}
|
||||||
|
</span>
|
||||||
|
</#if>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="${properties.kcFormGroupClass!}">
|
||||||
|
<div class="${properties.kcLabelWrapperClass!}">
|
||||||
|
<label for="password-confirm" class="${properties.kcLabelClass!}">
|
||||||
|
<span class="pf-v5-c-form__label-text">
|
||||||
|
${msg("passwordConfirm")}
|
||||||
|
<span class="pf-v5-c-form__label-required" aria-hidden="true">*</span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="${properties.kcInputGroup!}">
|
||||||
|
<span class="${properties.kcInputClass!}">
|
||||||
|
<input type="password" id="password-confirm"
|
||||||
|
name="password-confirm"
|
||||||
|
aria-invalid="<#if messagesPerField.existsError('password-confirm')>true</#if>"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<button class="${properties.kcFormPasswordVisibilityButtonClass!}" type="button" aria-label="${msg('showPassword')}"
|
||||||
|
aria-controls="password-confirm" data-password-toggle
|
||||||
|
data-icon-show="${properties.kcFormPasswordVisibilityIconShow!}" data-icon-hide="${properties.kcFormPasswordVisibilityIconHide!}"
|
||||||
|
data-label-show="${msg('showPassword')}" data-label-hide="${msg('hidePassword')}">
|
||||||
|
<i class="${properties.kcFormPasswordVisibilityIconShow!}" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<#if messagesPerField.existsError('password-confirm')>
|
||||||
|
<span id="input-error-password-confirm" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
||||||
|
${kcSanitize(messagesPerField.get('password-confirm'))?no_esc}
|
||||||
|
</span>
|
||||||
|
</#if>
|
||||||
|
</div>
|
||||||
|
</#if>
|
||||||
|
</#if>
|
||||||
|
</@userProfileCommons.userProfileFormFields>
|
||||||
|
|
||||||
|
<@registerCommons.termsAcceptance/>
|
||||||
|
|
||||||
|
<#if recaptchaRequired??>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="${properties.kcInputWrapperClass!}">
|
||||||
|
<div class="g-recaptcha" data-size="compact" data-sitekey="${recaptchaSiteKey}"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<div class="${properties.kcFormGroupClass!}">
|
||||||
|
<div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
|
||||||
|
<div class="${properties.kcFormOptionsWrapperClass!}">
|
||||||
|
<span><a href="${url.loginUrl}">${kcSanitize(msg("backToLogin"))?no_esc}</a></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
|
||||||
|
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}" type="submit" value="${msg("doRegister")}"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<script type="module" src="${url.resourcesPath}/js/passwordVisibility.js"></script>
|
||||||
|
</#if>
|
||||||
|
</@layout.registrationLayout>
|
|
@ -1,8 +1,13 @@
|
||||||
/* Patternfly CSS places a "bg-login.jpg" as the background on this ".login-pf" class.
|
.pf-v5-c-login__container {
|
||||||
This clashes with the "keycloak-bg.png' background defined on the body below.
|
grid-template-areas: "main";
|
||||||
Therefore the Patternfly background must be set to none. */
|
grid-template-columns: 34rem;
|
||||||
.login-pf {
|
}
|
||||||
background: none;
|
|
||||||
|
#kc-header-wrapper {
|
||||||
|
padding: 62px 10px 20px;
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-pf body {
|
.login-pf body {
|
||||||
|
|
|
@ -0,0 +1,196 @@
|
||||||
|
<#macro userProfileFormFields>
|
||||||
|
<#assign currentGroup="">
|
||||||
|
|
||||||
|
<#list profile.attributes as attribute>
|
||||||
|
|
||||||
|
<#assign groupName = attribute.group!"">
|
||||||
|
<#if groupName != currentGroup>
|
||||||
|
<#assign currentGroup=groupName>
|
||||||
|
<#if currentGroup != "" >
|
||||||
|
<div class="${properties.kcFormGroupClass!}">
|
||||||
|
|
||||||
|
<#assign groupDisplayHeader=attribute.groupDisplayHeader!"">
|
||||||
|
<#if groupDisplayHeader != "">
|
||||||
|
<#assign groupHeaderText=advancedMsg(attribute.groupDisplayHeader)!groupName>
|
||||||
|
<#else>
|
||||||
|
<#assign groupHeaderText=groupName>
|
||||||
|
</#if>
|
||||||
|
<div class="${properties.kcContentWrapperClass!}">
|
||||||
|
<label id="header-${groupName}" class="${kcFormGroupHeader!}">${groupHeaderText}</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<#assign groupDisplayDescription=attribute.groupDisplayDescription!"">
|
||||||
|
<#if groupDisplayDescription != "">
|
||||||
|
<#assign groupDescriptionText=advancedMsg(attribute.groupDisplayDescription)!"">
|
||||||
|
<label id="description-${groupName}" class="${properties.kcLabelClass!}">${groupDescriptionText}</label>
|
||||||
|
</#if>
|
||||||
|
</div>
|
||||||
|
</#if>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#nested "beforeField" attribute>
|
||||||
|
<div class="${properties.kcFormGroupClass!}">
|
||||||
|
<label for="${attribute.name}" class="${properties.kcLabelClass!}">
|
||||||
|
<span class="pf-v5-c-form__label-text">
|
||||||
|
${advancedMsg(attribute.displayName!'')}
|
||||||
|
<#if attribute.required>
|
||||||
|
<span class="pf-v5-c-form__label-required" aria-hidden="true">*</span>
|
||||||
|
</#if>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<span class="${properties.kcInputClass!}">
|
||||||
|
<#if attribute.annotations.inputHelperTextBefore??>
|
||||||
|
<div class="${properties.kcInputHelperTextBeforeClass!}" id="form-help-text-before-${attribute.name}" aria-live="polite">${kcSanitize(advancedMsg(attribute.annotations.inputHelperTextBefore))?no_esc}</div>
|
||||||
|
</#if>
|
||||||
|
<@inputFieldByType attribute=attribute/>
|
||||||
|
<#if messagesPerField.existsError('${attribute.name}')>
|
||||||
|
<span id="input-error-${attribute.name}" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
||||||
|
${kcSanitize(messagesPerField.get('${attribute.name}'))?no_esc}
|
||||||
|
</span>
|
||||||
|
</#if>
|
||||||
|
<#if attribute.annotations.inputHelperTextAfter??>
|
||||||
|
<div class="${properties.kcInputHelperTextAfterClass!}" id="form-help-text-after-${attribute.name}" aria-live="polite">${kcSanitize(advancedMsg(attribute.annotations.inputHelperTextAfter))?no_esc}</div>
|
||||||
|
</#if>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
<#nested "afterField" attribute>
|
||||||
|
</#list>
|
||||||
|
|
||||||
|
<#list profile.html5DataAnnotations?keys as key>
|
||||||
|
<script type="module" src="${url.resourcesPath}/js/${key}.js"></script>
|
||||||
|
</#list>
|
||||||
|
</#macro>
|
||||||
|
|
||||||
|
<#macro inputFieldByType attribute>
|
||||||
|
<#switch attribute.annotations.inputType!''>
|
||||||
|
<#case 'textarea'>
|
||||||
|
<@textareaTag attribute=attribute/>
|
||||||
|
<#break>
|
||||||
|
<#case 'select'>
|
||||||
|
<#case 'multiselect'>
|
||||||
|
<@selectTag attribute=attribute/>
|
||||||
|
<#break>
|
||||||
|
<#case 'select-radiobuttons'>
|
||||||
|
<#case 'multiselect-checkboxes'>
|
||||||
|
<@inputTagSelects attribute=attribute/>
|
||||||
|
<#break>
|
||||||
|
<#default>
|
||||||
|
<@inputTag attribute=attribute/>
|
||||||
|
</#switch>
|
||||||
|
</#macro>
|
||||||
|
|
||||||
|
<#macro inputTag attribute>
|
||||||
|
<input type="<@inputTagType attribute=attribute/>" id="${attribute.name}" name="${attribute.name}" value="${(attribute.value!'')}" class="${properties.kcInputClass!}"
|
||||||
|
aria-invalid="<#if messagesPerField.existsError('${attribute.name}')>true</#if>"
|
||||||
|
<#if attribute.readOnly>disabled</#if>
|
||||||
|
<#if attribute.autocomplete??>autocomplete="${attribute.autocomplete}"</#if>
|
||||||
|
<#if attribute.annotations.inputTypePlaceholder??>placeholder="${attribute.annotations.inputTypePlaceholder}"</#if>
|
||||||
|
<#if attribute.annotations.inputTypePattern??>pattern="${attribute.annotations.inputTypePattern}"</#if>
|
||||||
|
<#if attribute.annotations.inputTypeSize??>size="${attribute.annotations.inputTypeSize}"</#if>
|
||||||
|
<#if attribute.annotations.inputTypeMaxlength??>maxlength="${attribute.annotations.inputTypeMaxlength}"</#if>
|
||||||
|
<#if attribute.annotations.inputTypeMinlength??>minlength="${attribute.annotations.inputTypeMinlength}"</#if>
|
||||||
|
<#if attribute.annotations.inputTypeMax??>max="${attribute.annotations.inputTypeMax}"</#if>
|
||||||
|
<#if attribute.annotations.inputTypeMin??>min="${attribute.annotations.inputTypeMin}"</#if>
|
||||||
|
<#if attribute.annotations.inputTypeStep??>step="${attribute.annotations.inputTypeStep}"</#if>
|
||||||
|
<#if attribute.annotations.inputTypeStep??>step="${attribute.annotations.inputTypeStep}"</#if>
|
||||||
|
<#list attribute.html5DataAnnotations as key, value>
|
||||||
|
data-${key}="${value}"
|
||||||
|
</#list>
|
||||||
|
/>
|
||||||
|
</#macro>
|
||||||
|
|
||||||
|
<#macro inputTagType attribute>
|
||||||
|
<#compress>
|
||||||
|
<#if attribute.annotations.inputType??>
|
||||||
|
<#if attribute.annotations.inputType?starts_with("html5-")>
|
||||||
|
${attribute.annotations.inputType[6..]}
|
||||||
|
<#else>
|
||||||
|
${attribute.annotations.inputType}
|
||||||
|
</#if>
|
||||||
|
<#else>
|
||||||
|
text
|
||||||
|
</#if>
|
||||||
|
</#compress>
|
||||||
|
</#macro>
|
||||||
|
|
||||||
|
<#macro textareaTag attribute>
|
||||||
|
<textarea id="${attribute.name}" name="${attribute.name}" class="${properties.kcInputClass!}"
|
||||||
|
aria-invalid="<#if messagesPerField.existsError('${attribute.name}')>true</#if>"
|
||||||
|
<#if attribute.readOnly>disabled</#if>
|
||||||
|
<#if attribute.annotations.inputTypeCols??>cols="${attribute.annotations.inputTypeCols}"</#if>
|
||||||
|
<#if attribute.annotations.inputTypeRows??>rows="${attribute.annotations.inputTypeRows}"</#if>
|
||||||
|
<#if attribute.annotations.inputTypeMaxlength??>maxlength="${attribute.annotations.inputTypeMaxlength}"</#if>
|
||||||
|
>${(attribute.value!'')}</textarea>
|
||||||
|
</#macro>
|
||||||
|
|
||||||
|
<#macro selectTag attribute>
|
||||||
|
<select id="${attribute.name}" name="${attribute.name}" class="${properties.kcInputClass!}"
|
||||||
|
aria-invalid="<#if messagesPerField.existsError('${attribute.name}')>true</#if>"
|
||||||
|
<#if attribute.readOnly>disabled</#if>
|
||||||
|
<#if attribute.annotations.inputType=='multiselect'>multiple</#if>
|
||||||
|
<#if attribute.annotations.inputTypeSize??>size="${attribute.annotations.inputTypeSize}"</#if>
|
||||||
|
>
|
||||||
|
<#if attribute.annotations.inputType=='select'>
|
||||||
|
<option value=""></option>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#if attribute.annotations.inputOptionsFromValidation?? && attribute.validators[attribute.annotations.inputOptionsFromValidation]?? && attribute.validators[attribute.annotations.inputOptionsFromValidation].options??>
|
||||||
|
<#assign options=attribute.validators[attribute.annotations.inputOptionsFromValidation].options>
|
||||||
|
<#elseif attribute.validators.options?? && attribute.validators.options.options??>
|
||||||
|
<#assign options=attribute.validators.options.options>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#if options??>
|
||||||
|
<#list options as option>
|
||||||
|
<option value="${option}" <#if attribute.values?seq_contains(option)>selected</#if>><@selectOptionLabelText attribute=attribute option=option/></option>
|
||||||
|
</#list>
|
||||||
|
</#if>
|
||||||
|
</select>
|
||||||
|
</#macro>
|
||||||
|
|
||||||
|
<#macro inputTagSelects attribute>
|
||||||
|
<#if attribute.annotations.inputType=='select-radiobuttons'>
|
||||||
|
<#assign inputType='radio'>
|
||||||
|
<#assign classDiv=properties.kcInputClassRadio!>
|
||||||
|
<#assign classInput=properties.kcInputClassRadioInput!>
|
||||||
|
<#assign classLabel=properties.kcInputClassRadioLabel!>
|
||||||
|
<#else>
|
||||||
|
<#assign inputType='checkbox'>
|
||||||
|
<#assign classDiv=properties.kcInputClassCheckbox!>
|
||||||
|
<#assign classInput=properties.kcInputClassCheckboxInput!>
|
||||||
|
<#assign classLabel=properties.kcInputClassCheckboxLabel!>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#if attribute.annotations.inputOptionsFromValidation?? && attribute.validators[attribute.annotations.inputOptionsFromValidation]?? && attribute.validators[attribute.annotations.inputOptionsFromValidation].options??>
|
||||||
|
<#assign options=attribute.validators[attribute.annotations.inputOptionsFromValidation].options>
|
||||||
|
<#elseif attribute.validators.options?? && attribute.validators.options.options??>
|
||||||
|
<#assign options=attribute.validators.options.options>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#if options??>
|
||||||
|
<#list options as option>
|
||||||
|
<div class="${classDiv}">
|
||||||
|
<input type="${inputType}" id="${attribute.name}-${option}" name="${attribute.name}" value="${option}" class="${classInput}"
|
||||||
|
aria-invalid="<#if messagesPerField.existsError('${attribute.name}')>true</#if>"
|
||||||
|
<#if attribute.readOnly>disabled</#if>
|
||||||
|
<#if attribute.values?seq_contains(option)>checked</#if>
|
||||||
|
/>
|
||||||
|
<label for="${attribute.name}-${option}" class="${classLabel}<#if attribute.readOnly> ${properties.kcInputClassRadioCheckboxLabelDisabled!}</#if>"><@selectOptionLabelText attribute=attribute option=option/></label>
|
||||||
|
</div>
|
||||||
|
</#list>
|
||||||
|
</#if>
|
||||||
|
</#macro>
|
||||||
|
|
||||||
|
<#macro selectOptionLabelText attribute option>
|
||||||
|
<#compress>
|
||||||
|
<#if attribute.annotations.inputOptionLabels??>
|
||||||
|
${advancedMsg(attribute.annotations.inputOptionLabels[option]!option)}
|
||||||
|
<#else>
|
||||||
|
<#if attribute.annotations.inputOptionLabelsI18nPrefix??>
|
||||||
|
${msg(attribute.annotations.inputOptionLabelsI18nPrefix + '.' + option)}
|
||||||
|
<#else>
|
||||||
|
${option}
|
||||||
|
</#if>
|
||||||
|
</#if>
|
||||||
|
</#compress>
|
||||||
|
</#macro>
|
Loading…
Reference in a new issue