web: Flow Executor layout fixes (#20134)

* Fix footer alignment.

* Fix loading position in compatibility mode.

* Apply min height only when placeholder content is present.

* Fix alignment in compatibility mode.

* Add compatibility mode host selectors.

* Fix nullish challenge height. Clarify selector behavior.

* Add type defintion

* Fix padding.

* Fix misapplication of pf-* class to container.

* Fix huge base64 encoded attribute.

* Clean up layering issues, order of styles.

* Disable dev override.

* Document parts.
This commit is contained in:
Teffen Ellis
2026-02-16 20:29:32 +01:00
committed by GitHub
parent ff611c845f
commit 61a75e6a0a
18 changed files with 460 additions and 229 deletions
+18 -13
View File
@@ -44,19 +44,24 @@
{% endblock %}
</div>
</main>
<footer aria-label="Site footer" class="pf-c-login__footer pf-m-dark">
<ul class="pf-c-list pf-m-inline">
{% for link in footer_links %}
<li>
<a href="{{ link.href }}">{{ link.name }}</a>
</li>
{% endfor %}
<li>
<span>
{% trans 'Powered by authentik' %}
</span>
</li>
</ul>
<footer
name="site-footer"
aria-label="{% trans 'Site footer' %}"
class="pf-c-login__footer pf-m-dark">
<div name="flow-links" aria-label="{% trans 'Flow links' %}">
<ul class="pf-c-list pf-m-inline" part="list">
{% for link in footer_links %}
<li part="list-item">
<a part="list-item-link" href="{{ link.href }}">{{ link.name }}</a>
</li>
{% endfor %}
<li part="list-item">
<span>
{% trans 'Powered by authentik' %}
</span>
</li>
</ul>
</div>
</footer>
</div>
</div>
+11 -8
View File
@@ -9,7 +9,15 @@
{{ block.super }}
<link rel="prefetch" href="{{ flow_background_url }}" />
{% if flow.compatibility_mode and not inspector %}
<script data-id="shady-dom">ShadyDOM = { force: true };</script>
{% comment %}
@see {@link web/types/webcomponents.d.ts} for type definitions.
{% endcomment %}
<script data-id="shady-dom">
"use strict";
window.ShadyDOM = window.ShadyDOM || {}
window.ShadyDOM.force = true
</script>
{% endif %}
{% include "base/header_js.html" %}
<script data-id="flow-config">
@@ -45,16 +53,11 @@
slug="{{ flow.slug }}"
class="pf-c-login"
data-layout="{{ flow.layout|default:'stacked' }}"
loading
>
{% include "base/placeholder.html" %}
<ak-brand-links
slot="footer"
exportparts="list:brand-links-list, list-item:brand-links-list-item"
role="contentinfo"
aria-label="{% trans 'Site footer' %}"
class="pf-c-login__footer {% if flow.layout == 'stacked' %}pf-m-dark{% endif %}"
></ak-brand-links>
<ak-brand-links name="flow-links" slot="footer"></ak-brand-links>
</ak-flow-executor>
</div>
</div>
+4 -2
View File
@@ -1,4 +1,5 @@
:host {
:host,
ak-loading-overlay.style-scope {
position: absolute;
inset: 0;
z-index: 1;
@@ -11,6 +12,7 @@
);
}
:host([topmost]) {
:host([topmost]),
ak-loading-overlay[topmost].style-scope {
z-index: var(--pf-global--ZIndex--2xl);
}
+6 -5
View File
@@ -1,6 +1,7 @@
@import "../styles/authentik/components/Login/login.css";
:host {
:host,
ak-flow-executor.style-scope {
display: flex;
min-height: 100dvh;
flex-flow: column nowrap;
@@ -49,14 +50,14 @@
}
}
filter: var(--ak-global--background-contrast-Filter);
filter: var(--ak-global--BackgroundContrastFilter);
grid-area: header;
/* At least a third of the card cut-off is available. */
@media (width <= 61.25rem) and (height <= 61.25rem) {
--ak-global--background-contrast-Filter: none;
--ak-c-flow-executor__locale-select--Color: var(--ak-global--background-contrast);
--ak-global--BackgroundContrastFilter: none;
--ak-c-flow-executor__locale-select--Color: var(--ak-c-login__main--Color);
grid-area: main;
}
@@ -79,7 +80,7 @@
@media (min-width: 70rem) and (min-height: 17.5rem) {
:host([data-layout^="sidebar"]),
[data-layout^="sidebar"] /* Compatibility mode */ {
--ak-global--background-contrast-Filter: none !important;
--ak-global--BackgroundContrastFilter: none !important;
[part="locale-select"] {
--ak-c-flow-executor__locale-select--Color: inherit !important;
+30 -3
View File
@@ -68,6 +68,18 @@ import PFTitle from "@patternfly/patternfly/components/Title/title.css";
*
* @attr {string} slug - The slug of the flow to execute.
* @prop {ChallengeTypes | null} challenge - The current challenge to render.
*
* @part main - The main container for the flow content.
* @part content - The container for the stage content.
* @part content-iframe - The iframe element when using a frame background layout.
* @part footer - The footer container.
* @part locale-select - The locale select component.
* @part branding - The branding element, used for the background image in some layouts.
* @part loading-overlay - The loading overlay element.
* @part challenge-additional-actions - Container in stages which have additional actions.
* @part challenge-footer-band - Container for the stage footer, used for additional actions in some stages.
* @part locale-select-label - The label of the locale select component.
* @part locale-select-select - The select element of the locale select component.
*/
@customElement("ak-flow-executor")
export class FlowExecutor
@@ -538,7 +550,7 @@ export class FlowExecutor
//#region Render
protected renderLoading(): SlottedTemplateResult {
return html`<slot class="slotted-content" name="placeholder"></slot>`;
return html`<slot name="placeholder"></slot>`;
}
protected renderFrameBackground(): SlottedTemplateResult {
@@ -567,6 +579,21 @@ export class FlowExecutor
});
}
protected renderFooter(): SlottedTemplateResult {
return guard([this.layout], () => {
return html`<footer
aria-label=${msg("Site footer")}
name="site-footer"
part="footer"
class="pf-c-login__footer ${this.layout === FlowLayoutEnum.Stacked
? "pf-m-dark"
: ""}"
>
<slot name="footer"></slot>
</footer>`;
});
}
protected override render(): SlottedTemplateResult {
const { component } = this.challenge || {};
@@ -593,11 +620,11 @@ export class FlowExecutor
})}
</div>
${this.loading && this.challenge
? html`<ak-loading-overlay></ak-loading-overlay>`
? html`<ak-loading-overlay part="loading-overlay"></ak-loading-overlay>`
: nothing}
${component ? until(this.renderChallenge(component)) : this.renderLoading()}
</main>
<slot name="footer"></slot>`;
${this.renderFooter()}`;
}
//#endregion
+2 -1
View File
@@ -3,7 +3,8 @@
width: 100%;
}
:host {
:host,
ak-flow-inspector.style-scope {
background-color: var(--pf-c-notification-drawer--BackgroundColor);
--pf-c-drawer__panel--BackgroundColor: var(--pf-global--BackgroundColor--150) !important;
}
+41
View File
@@ -0,0 +1,41 @@
:host,
ak-form-static.style-scope {
margin-block-start: var(--pf-global--spacer--sm);
display: flex;
align-items: center;
justify-content: space-between;
flex-flow: wrap;
gap: var(--pf-global--spacer--sm);
}
.pf-c-avatar {
flex: 0 0 auto;
}
.primary-content {
display: flex;
align-items: center;
flex: 1 1 auto;
gap: var(--pf-global--spacer--md);
}
.username {
flex: 1 1 auto;
text-align: left;
max-width: 20rem;
text-overflow: ellipsis;
overflow-wrap: break-word;
display: box;
display: -webkit-box;
line-clamp: 3;
-webkit-line-clamp: 3;
box-orient: vertical;
-webkit-box-orient: vertical;
overflow: hidden;
}
.links {
flex: 0 0 auto;
text-align: right;
}
+8 -53
View File
@@ -3,6 +3,8 @@ import { LitFC } from "#elements/types";
import { ifPresent } from "#elements/utils/attributes";
import { isDefaultAvatar } from "#elements/utils/images";
import Styles from "#flow/FormStatic.css";
import {
AccessDeniedChallenge,
AuthenticatorDuoChallenge,
@@ -18,7 +20,7 @@ import {
} from "@goauthentik/api";
import { msg, str } from "@lit/localize";
import { css, CSSResult, html, nothing } from "lit";
import { CSSResult, html, nothing } from "lit";
import { customElement, property } from "lit/decorators.js";
import { guard } from "lit/directives/guard.js";
@@ -26,6 +28,8 @@ import PFAvatar from "@patternfly/patternfly/components/Avatar/avatar.css";
@customElement("ak-form-static")
export class AKFormStatic extends AKElement {
static styles: CSSResult[] = [PFAvatar, Styles];
public override role = "banner";
public override ariaLabel = msg("User information");
@@ -35,59 +39,12 @@ export class AKFormStatic extends AKElement {
@property({ type: String })
public username: string = "";
static styles: CSSResult[] = [
PFAvatar,
css`
:host {
margin-block-start: var(--pf-global--spacer--sm);
display: flex;
align-items: center;
justify-content: space-between;
flex-flow: wrap;
gap: var(--pf-global--spacer--sm);
}
.pf-c-avatar {
flex: 0 0 auto;
}
.primary-content {
display: flex;
align-items: center;
flex: 1 1 auto;
gap: var(--pf-global--spacer--md);
}
.username {
flex: 1 1 auto;
text-align: left;
max-width: 20rem;
text-overflow: ellipsis;
overflow-wrap: break-word;
display: box;
display: -webkit-box;
line-clamp: 3;
-webkit-line-clamp: 3;
box-orient: vertical;
-webkit-box-orient: vertical;
overflow: hidden;
}
.links {
flex: 0 0 auto;
text-align: right;
}
`,
];
protected override render() {
if (!this.username) {
return nothing;
}
return html`
<div class="primary-content">
return html`<div class="primary-content">
${this.avatar && !isDefaultAvatar(this.avatar)
? html`<img
class="pf-c-avatar"
@@ -105,8 +62,7 @@ export class AKFormStatic extends AKElement {
</div>
<div class="links">
<slot name="link"></slot>
</div>
`;
</div>`;
}
}
@@ -138,8 +94,7 @@ export const FlowUserDetails: LitFC<FlowUserDetailsProps> = ({ challenge }) => {
[pendingUserAvatar, pendingUser, flowInfo],
() =>
html`<ak-form-static
class="pf-c-form__group"
avatar=${ifPresent(pendingUserAvatar)}
.avatar=${ifPresent(pendingUserAvatar)}
username=${ifPresent(pendingUser)}
>
${flowInfo?.cancelUrl
+20 -17
View File
@@ -6,27 +6,28 @@ import { AKElement } from "#elements/Base";
import { FooterLink } from "@goauthentik/api";
import { msg } from "@lit/localize";
import { css, html } from "lit";
import { html } from "lit";
import { customElement, property } from "lit/decorators.js";
import { map } from "lit/directives/map.js";
import PFList from "@patternfly/patternfly/components/List/list.css";
const styles = css`
.pf-c-list a {
color: unset;
}
ul.pf-c-list.pf-m-inline {
justify-content: center;
padding: 0;
column-gap: var(--pf-global--spacer--xl);
row-gap: var(--pf-global--spacer--md);
}
`;
/**
* @part list - The list element containing the links
* @part list-item - Each item in the list, including the "Powered by authentik" item
* @part list-item-link - The link element for each item, if applicable
*/
@customElement("ak-brand-links")
export class BrandLinks extends AKElement {
static styles = [PFList, styles];
/**
* Rendering in the light DOM ensures consistent styling across some of the
* more complex flow environments, such as...
*
* - When JavaScript is not available, such as on error pages.
* - During the initial loading of the page, before the web components are fully initialized.
* - After the flow executor has initialized, to avoid repaint issues.
*/
protected createRenderRoot(): HTMLElement | DocumentFragment {
return this;
}
@property({ type: Array, attribute: false })
public links: FooterLink[] = globalAK().brand.uiFooterLinks || [];
@@ -37,7 +38,9 @@ export class BrandLinks extends AKElement {
const children = sanitizeHTML(BrandedHTMLPolicy, link.name);
if (link.href) {
return html`<li><a href="${link.href}">${children}</a></li>`;
return html`<li part="list-item">
<a part="list-item-link" href=${link.href}>${children}</a>
</li>`;
}
return html`<li part="list-item">
+6 -2
View File
@@ -42,9 +42,13 @@ export class FlowCard extends AKElement {
// No title if the challenge doesn't provide a title and no custom title is set
let title: null | SlottedTemplateResult = null;
if (this.hasSlotted("title")) {
title = html`<h1 class="pf-c-title pf-m-3xl"><slot name="title"></slot></h1>`;
title = html`<h1 class="pf-c-title pf-m-3xl ak-m-clamped">
<slot name="title"></slot>
</h1>`;
} else if (this.challenge?.flowInfo?.title) {
title = html`<h1 class="pf-c-title pf-m-3xl">${this.challenge.flowInfo.title}</h1>`;
title = html`<h1 class="pf-c-title pf-m-3xl ak-m-clamped">
${this.challenge.flowInfo.title}
</h1>`;
}
const footer = this.hasSlotted("footer") ? html`<slot name="footer"></slot>` : null;
const footerBand = this.hasSlotted("footer-band")
@@ -1,22 +1,20 @@
.authenticator-button {
/* compatibility-mode-fix */
& {
align-items: center;
width: 100%;
display: grid;
grid-template-columns: minmax(auto, 2rem) minmax(33%, max-content);
gap: var(--pf-global--spacer--lg);
}
.authenticator-button,
ak-stage-authenticator-validate.style-scope .authenticator-button {
align-items: center;
width: 100%;
display: grid;
grid-template-columns: minmax(auto, 2rem) minmax(33%, max-content);
gap: var(--pf-global--spacer--lg);
&:hover {
background-color: var(--pf-global--BackgroundColor--200);
}
}
i {
font-size: var(--pf-global--icon--FontSize--lg);
}
i {
font-size: var(--pf-global--icon--FontSize--lg);
}
.content {
text-align: left;
.content {
text-align: left;
}
}
+4 -2
View File
@@ -1,9 +1,11 @@
:host {
:host,
ak-stage-captcha.style-scope {
--captcha-background-to: var(--pf-global--BackgroundColor--light-100);
--captcha-background-from: var(--pf-global--BackgroundColor--light-300);
}
:host([theme="dark"]) {
:host([theme="dark"]),
ak-stage-captcha[theme="dark"].style-scope {
--captcha-background-to: var(--ak-dark-background-light);
--captcha-background-from: var(--pf-global--BackgroundColor--300);
}
+16 -16
View File
@@ -1,18 +1,15 @@
fieldset[name="login-sources"] {
fieldset[name="login-sources"],
ak-stage-identification.style-scope fieldset[name="login-sources"] {
--ak-c-login-sources-padding-inline: var(--pf-global--spacer--xl);
/* compatibility-mode-fix */
flex: 1 1 auto;
display: flex;
flex-flow: row wrap;
justify-content: center;
gap: var(--pf-global--spacer--sm);
& {
flex: 1 1 auto;
display: flex;
flex-flow: row wrap;
justify-content: center;
gap: var(--pf-global--spacer--sm);
padding-inline: var(--ak-c-login-sources-padding-inline) !important;
padding-block-start: var(--pf-global--spacer--md) !important;
}
padding-inline: var(--ak-c-login-sources-padding-inline) !important;
padding-block-start: var(--pf-global--spacer--md) !important;
.source-button {
display: flex;
@@ -65,9 +62,12 @@ fieldset[name="login-sources"] {
}
}
:host([theme="dark"]) fieldset[name="login-sources"] .pf-c-button__icon {
img,
.pf-c-button__icon .fas {
filter: invert(1);
:host([theme="dark"]),
ak-stage-identification {
fieldset[name="login-sources"] .pf-c-button__icon {
img,
.pf-c-button__icon .fas {
filter: invert(1);
}
}
}
+6
View File
@@ -33,6 +33,12 @@
--pf-global--BackgroundColor--100: var(--pf-global--BackgroundColor--light-100);
}
.pf-m-title {
.pf-m-3xl.ak-m-clamped {
--pf-c-title--m-3xl--FontSize: clamp(1rem, var(--pf-global--FontSize--3xl), 7dvw);
}
}
.pf-m-monospace {
font-family: var(--pf-global--FontFamily--monospace);
+8 -2
View File
@@ -24,6 +24,7 @@
/* #region authentik extensions */
/* #region Root */
:root {
--ak-accent: #fd4b2d;
@@ -32,8 +33,9 @@
--ak-dark-background-light: #1c1e21;
--ak-dark-background-lighter: #2b2e33;
--ak-global--background-contrast: var(--pf-global--Color--100);
--ak-global--background-contrast-Filter: drop-shadow(
--ak-global--BackgroundColorContrast--100: var(--pf-global--Color--light-100);
--ak-global--BackgroundContrastFilter: drop-shadow(
0 0 2px
var(--ak-locale-select--ShadowBlendColor, var(--pf-global--BackgroundColor--dark-200))
);
@@ -42,4 +44,8 @@
--ak-sidebar--minimum-auto-width: 80rem;
}
html[data-theme="dark"] {
--ak-global--BackgroundColorContrast--100: var(--pf-global--palette--black-150);
}
/* #endregion */
@@ -1,41 +1,39 @@
/**
* @file Patternfly Login component overrides and customizations.
*
* These styles are not as simple as they may seem at first glance.
* The overlap between the concept of the login page, the flow executor,
* and Django-provided templates means that these styles need to be flexible.
*
* - The initial render of the login page is server-side rendered.
* - The use of ShadyDOM in the flow executor means that styles need to be compatible with both Shadow DOM and Light DOM contexts.
* - The layout must adapt for mobile, tablet, and desktop.
* - And dark and light themes must work while allowing for user provided style overrides.
* - These styles have a unique relationship with the styles in `FlowExecutor.css` and `static.global.css`.
*
* All that said, we generally follow Patternfly's structure, save for the mobile layout which is unique to our implementation.
*/
/* #region Login Component */
/* compatibility-mode-fix */
.pf-c-login.pf-c-login {
--ak-c-login--PaddingMax: 8dvw;
--ak-c-login--padding: clamp(
var(--pf-global--spacer--md),
var(--pf-global--spacer--2xl),
var(--ak-c-login--PaddingMax)
);
--ak-c-login__main--brand-PaddingMin: var(--pf-global--spacer--xs);
--ak-c-login__main--brand-PaddingIdeal: 5rem;
--ak-c-login__main--brand-PaddingMax: 15dvh;
--ak-c-login__footer--PaddingBlock: var(--pf-global--spacer--md);
--ak-c-login--MaxWidth: 35rem;
--ak-c-login__main-ColumnWidth: minmax(
min(100%, var(--ak-c-login--MaxWidth)),
var(--ak-c-login--MaxWidth)
);
--pf-c-login__main-body--PaddingBottom: 0;
--ak-c-login__main--footer-PaddingMin: var(--pf-global--spacer--xs);
--ak-c-login__main--footer-PaddingIdeal: 3rem;
--ak-c-login__main--footer-PaddingMax: 9dvh;
--pf-c-login__main-footer--PaddingBottom: clamp(
var(--ak-c-login__main--footer-PaddingMin),
var(--ak-c-login__main--footer-PaddingIdeal),
var(--ak-c-login__main--footer-PaddingMax)
);
--pf-c-login__main-footer-band--BackgroundColor: transparent;
/**
* Take note, we avoid applying Patternfly styles to custom elements directly:
*
* ```html
* <ak-button class="pf-c-button pf-m-primary">Click me</ak-button>
* ```
*
* However, the flow executor requires that the `.pf-c-login` class be applied to the host element.
* This allows for some careful enhancements to the login page without depending on the Shadow DOM,
* with some caveats:
*
* - Custom variables should be defined in static.global.css and used here to allow the user to override them as needed.
* - The data-layout attribute is applied to this element and the .pf-c-login__main element,
* allowing for some layout-specific styles to be applied.
* - The pf-c-login__footer is slotted into the flow executor, and requires a
* delicate balance of inheriting styles from the login page while ensuring sufficient contrast against the background.
*/
.pf-c-login {
flex: 1 1 auto;
padding: 0;
@@ -63,7 +61,7 @@
&::before {
display: block;
content: "";
background-color: var(--ak-c-login--BackgroundColorOverlay, transparent);
background-color: var(--ak-c-login--BackgroundColorOverlay, transparent) !important;
z-index: -1;
height: 100%;
pointer-events: none;
@@ -81,7 +79,7 @@
}
@media (max-width: 35rem) or (max-height: 17.5rem) {
--ak-c-login--BackgroundColorOverlay: var(--pf-c-login__main--BackgroundColor);
--ak-c-login--BackgroundColorOverlay: var(--ak-c-login__main--BackgroundColor);
}
}
@@ -89,16 +87,11 @@
grid-area: main;
}
[data-theme="dark"] .pf-c-login,
:host([theme="dark"]) .pf-c-login {
--pf-c-login__main--BackgroundColor: var(--pf-global--BackgroundColor--100);
}
/* #region Page Header */
.pf-c-login__header {
grid-area: header;
padding-inline: calc(var(--ak-c-login--padding) / 2);
padding-inline: calc(var(--ak-c-login--spacer) / 2);
align-self: start;
display: grid;
@@ -107,7 +100,7 @@
/* #endregion */
/* #region Page Footer */
/* #region Main Footer */
/* compatibility-mode-fix */
.pf-c-login__main-footer .pf-c-button__icon {
@@ -125,10 +118,6 @@
/* #region Card Main */
.pf-c-login__main {
--pf-c-login__container--PaddingLeft: 0 !important;
--pf-c-login__container--PaddingRight: 0 !important;
--ak-c-login__main--BoxShadow: var(--pf-global--BoxShadow--md);
box-shadow: var(--ak-c-login__main--BoxShadow) !important;
grid-area: main;
@@ -136,17 +125,36 @@
position: relative;
max-width: var(--ak-c-login--MaxWidth);
min-height: calc(var(--ak-c-login--MaxWidth) * 0.8);
min-height: var(--ak-c-login__main--MinHeight, unset);
display: flex;
flex-flow: column;
justify-content: space-between;
.slotted-content {
slot[name="placeholder"] {
position: relative;
flex: 1 1 auto;
}
/**
* Note that the use of `slot` as attribute selector is intentional.
* We're checking for the presence of an element attempting to slot itself as the placeholder.
*
* This approach allows us to handle some of the lesser-intuitive combinations
* of whether we're in a shadow DOM and how to gracefully transition to a
* post-JavaScript state without any awkward repaints.
* We're also interested in whether the slot itself is within the main element,
* as it indicates that the placeholder content is slotted and a shadow DOM is present.
*
* This ensures the height remains consistent from the initial render,
* preventing layout shifts when the placeholder is replaced with the actual content.
*/
&:has([slot="placeholder"]),
&:has(slot[name="placeholder"]) {
--ak-c-login__main--MinHeight: calc(var(--ak-c-login--MaxWidth) * 0.8);
}
@media (max-width: 35rem) or (max-height: 17.5rem) {
--ak-c-login__main--BoxShadow: none;
}
@@ -156,15 +164,6 @@
/* #region Main Header */
.pf-c-login__main-header {
padding-inline: var(--ak-c-login--padding);
padding-block: clamp(var(--pf-global--spacer--xs), 6dvw, var(--pf-global--spacer--lg));
.pf-c-title {
font-size: clamp(1rem, var(--pf-c-title--m-3xl--FontSize), 7dvw);
}
}
.pf-c-login__main-header.pf-c-brand {
--ak-c-login__main-padding-block-start: clamp(
var(--ak-c-login__main--brand-PaddingMin),
@@ -172,13 +171,13 @@
var(--ak-c-login__main--brand-PaddingMax)
);
padding-inline: calc(var(--ak-c-login--padding) / 4);
padding-inline: calc(var(--ak-c-login--spacer) / 4);
padding-block-start: calc(
var(--ak-c-login__main-padding-block-start) - var(--ak-c-login__footer--PaddingBlock)
);
padding-bottom: var(--pf-global--spacer--xs);
padding-block-end: calc(var(--ak-c-login--padding) / 2);
padding-block-end: calc(var(--ak-c-login--spacer) / 2);
display: flex;
justify-content: center;
@@ -203,7 +202,6 @@
.pf-c-login__main-body {
flex: 1 1 auto;
padding-inline: var(--ak-c-login--padding);
}
/* #endregion */
@@ -242,7 +240,7 @@
/* #region Layout variations */
.pf-c-login[data-layout$="frame_background"] {
--ak-c-login--BackgroundColorOverlay: var(--pf-c-login__main--BackgroundColor);
--ak-c-login--BackgroundColorOverlay: var(--ak-c-login__main--BackgroundColor);
}
.pf-c-login[data-layout^="sidebar_left"] {
@@ -292,19 +290,13 @@
.pf-c-login[data-layout^="sidebar"] {
--ak-c-login--MaxWidth: 36rem;
--ak-c-login--BackgroundColorOverlay: var(--pf-c-login__main--BackgroundColor);
--ak-c-login--BackgroundColorOverlay: var(--ak-c-login__main--BackgroundColor);
--ak-c-login__footer--Color: var(--ak-c-login__main--Color);
.pf-c-login__main {
height: 100%;
justify-content: normal;
}
.pf-c-login__footer {
color: inherit;
flex: 1 1 auto;
justify-content: end;
width: 100%;
}
}
.pf-c-login[data-layout^="sidebar_left"] {
@@ -328,28 +320,51 @@
/* #endregion */
/* #region Page Footer */
/**
* The footer must respect a few constraints to ensure it remains legible::after
*
* - The mobile layout should have the same background color as the login main content.
* - Aside from CSS variables footer styles should not be applied in the static.global.css file,
* This may seem unnecessary, but PatternFly's own base styles for `pf-c-*` elements
*. will override styles in an uphill battle against user overrides.
*/
.pf-c-login__footer {
--pf-global--Color--100: var(--pf-global--Color--light-100) !important;
grid-area: footer;
flex: 0 0 auto;
padding-block: var(--ak-c-login__footer--PaddingBlock);
display: flex;
flex-direction: column;
align-self: end;
justify-content: center;
padding-inline: var(--pf-global--spacer--xl) !important;
padding-block: var(--ak-c-login__footer--PaddingBlock) !important;
align-self: end;
flex: 0 0 auto;
min-height: calc((var(--ak-c-login__footer--PaddingBlock) * 2) + 1rem);
line-height: var(--pf-global--LineHeight--md);
min-height: calc(
(var(--ak-c-login__footer--PaddingBlock) * 2) + (var(--pf-global--LineHeight--md) * 1rem)
);
color: var(--ak-c-login__footer--Color);
@media (max-width: 35rem) {
color: var(--pf-global--Color--200);
--ak-c-login__footer--Color: var(--ak-c-login__main--Color);
}
@media (min-width: 35rem) and (min-height: 17.5rem) {
filter: var(--ak-global--background-contrast-Filter);
filter: var(--ak-global--BackgroundContrastFilter);
}
}
/**
* The dark modifier is used in stacked layout to ensure sufficient contrast against the darker background.
* This may appear unnecessary, but PF4's own login footer styles are not designed
* with our mobile layout in mind. this ensures that the footer remains legible
* even when the card is reduced in size and the background contrast is removed.
*/
.pf-c-login__footer {
@media (max-width: 35rem) {
--pf-global--Color--100: var(--pf-global--Color--dark-100) !important;
--pf-global--Color--200: var(--pf-global--Color--dark-200) !important;
}
}
+106 -9
View File
@@ -15,18 +15,93 @@
@import "#elements/locale/ak-locale-select.css";
@import "#flow/FlowExecutor.css";
.pf-c-login__main-body {
display: flex;
flex-flow: column;
/**
* @file Static global styles for authentik.
*
* Similar the base/globals.css file, this file is only injected in server templates
* that may not have the full web component support.
* If you're deciding on where to put a style, prefer a more specific file
* to avoid unnecessarily increasing the global scope of the style.
*/
.pf-c-form {
display: flex;
flex-flow: column;
flex: 1 1 auto;
justify-content: end;
}
/* #region Custom login variables */
:root {
--ak-c-login--PaddingMax: 8dvw;
--ak-c-login--spacer: clamp(
var(--pf-global--spacer--md),
var(--pf-global--spacer--2xl),
var(--ak-c-login--PaddingMax)
);
--ak-c-login--MaxWidth: 35rem;
--ak-c-login__main--BackgroundColor: var(--pf-global--BackgroundColor--light-100);
--ak-c-login__main--Color: var(--pf-global--Color--dark-100);
--ak-c-login__main--brand-PaddingMin: var(--pf-global--spacer--xs);
--ak-c-login__main--brand-PaddingIdeal: 5rem;
--ak-c-login__main--brand-PaddingMax: 15dvh;
--ak-c-login__main-ColumnWidth: minmax(
min(100%, var(--ak-c-login--MaxWidth)),
var(--ak-c-login--MaxWidth)
);
--ak-c-login__main-header-PaddingBlock: clamp(
var(--pf-global--spacer--xs),
6dvw,
var(--pf-global--spacer--lg)
);
--ak-c-login__main-header-PaddingInline: var(--ak-c-login--spacer);
--ak-c-login__main--footer-PaddingMin: var(--pf-global--spacer--xs);
--ak-c-login__main--footer-PaddingIdeal: 3rem;
--ak-c-login__main--footer-PaddingMax: 9dvh;
--ak-c-login__main--BoxShadow: var(--pf-global--BoxShadow--md);
--ak-c-login__footer--PaddingBlock: var(--pf-global--spacer--md);
--ak-c-login__footer--Color: var(--ak-global--BackgroundColorContrast--100);
}
[data-theme="dark"] .pf-c-login {
--ak-c-login__main--BackgroundColor: var(--pf-global--BackgroundColor--dark-100);
}
/* #endregion */
/* #region PF4 Login */
.pf-c-login {
--pf-c-login__main-header--PaddingTop: var(--ak-c-login__main-header-PaddingBlock);
--pf-c-login__main-header--PaddingBottom: var(--ak-c-login__main-header-PaddingBlock);
--pf-c-login__main-header--PaddingLeft: var(--ak-c-login__main-header-PaddingInline);
--pf-c-login__main-header--PaddingRight: var(--ak-c-login__main-header-PaddingInline);
--pf-c-login__main--BackgroundColor: var(--ak-c-login__main--BackgroundColor);
--pf-c-login__main-body--PaddingLeft: var(--ak-c-login--spacer);
--pf-c-login__main-body--PaddingRight: var(--ak-c-login--spacer);
--pf-c-login__main-body--PaddingBottom: 0;
--pf-c-login__main-footer--PaddingBottom: clamp(
var(--ak-c-login__main--footer-PaddingMin),
var(--ak-c-login__main--footer-PaddingIdeal),
var(--ak-c-login__main--footer-PaddingMax)
);
--pf-c-login__main-footer-band--BackgroundColor: transparent;
--pf-c-login__footer--c-list--xl--PaddingTop: 0;
--pf-c-login__container--PaddingLeft: 0 !important;
--pf-c-login__container--PaddingRight: 0 !important;
}
/* #endregion */
/* #region Form */
/* Fallback form controls with minimal runtime expectations. */
.form-control-static {
margin-top: var(--pf-global--spacer--sm);
display: flex;
@@ -48,3 +123,25 @@
line-height: var(--pf-global--spacer--xl);
}
}
/* #endregion */
/* #region Flow Links */
[name="flow-links"] {
[part="list"],
&::part(list) {
--pf-c-list--m-inline--li--MarginRight: 0;
justify-content: center;
column-gap: var(--pf-global--spacer--2xl);
row-gap: var(--pf-global--spacer--md);
}
[part="list-item-link"],
&::part(list-item-link) {
color: unset;
}
}
/* #endregion */
+65
View File
@@ -0,0 +1,65 @@
/**
* @file Web component globals applied to the Window object.
*
* @see https://www.npmjs.com/package/@webcomponents/webcomponentsjs
*/
export {};
declare global {
type Booleanish = "true" | "false";
type WebComponentFlags = Record<string, Booleanish | boolean | Record<string, boolean>>;
interface WebComponents {
/**
* Flags that can be set on the `WebComponents` global to control the behavior of web components in the application.
* Typically, this is limited to the `webcomponents-loader`.
*/
flags?: WebComponentFlags;
}
interface ShadyDOM {
/**
* Forces the use of the Shady DOM polyfill, even in browsers that support native Shadow DOM.
* This can be useful for testing or to work around specific issues with native Shadow DOM in certain browsers.
*/
force?: boolean | Booleanish;
/**
* Prevents the patching of native Shadow DOM APIs when the Shady DOM polyfill is in use.
* This can be useful for debugging or to avoid conflicts with other libraries that also patch these APIs.
*/
noPatch?: boolean | Booleanish;
}
interface CustomElementRegistry {
/**
* An indication of whether the polyfill for web components is in use.
*/
readonly forcePolyfill?: Booleanish | boolean;
}
interface Window {
/**
* An object representing the state of web component support and configuration in the application.
*/
WebComponents?: Readonly<WebComponents>;
/**
* An object representing the configuration for the Shady DOM polyfill,
* which provides support for Shadow DOM in browsers that do not natively support it.
*/
ShadyDOM?: Readonly<ShadyDOM>;
/**
* A root path for loading web component polyfills. This is only applicable
*
* @remarks
* If you're using the loader on a page that enforces the `trusted-types`
* Content Security Policy, you'll need to allow the `webcomponents-loader`
* policy name so that the loader can dynamically create and insert a `<script>`
* for the polyfill bundle it selects based on feature detection. I
* f you set `WebComponents.root` (which is rare), it should be set to a {@linkcode TrustedScriptURL}
* for Trusted Types compatibility.
*/
root?: string | TrustedScriptURL;
}
}