mirror of
https://github.com/goauthentik/authentik.git
synced 2026-06-17 19:09:11 +03:00
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:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 */
|
||||
|
||||
Vendored
+65
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user