Compare commits

...

2 Commits

Author SHA1 Message Date
Teffen Ellis
acb323ce33 web: Fix alignment, rendering on high contrast.
web: Apply footer resize.

web: Fix application of global styles in style roots.

web: Fix missing layout attribute.

web: Normalize background alignment.

web: Fix layout issues, color overrides.

web: Fix alignment, colors, jank.

web: Separate method into function.

web: Clean up alignment, reflow.

web: Fix colors, compatibility mode.

web: Add content left/right support.

web: Fix colors, compatibility mode overrides.
2025-10-20 16:53:50 +02:00
Teffen Ellis
a0fd402d2d web: Separate global styles from element roots. 2025-10-20 16:53:49 +02:00
22 changed files with 739 additions and 609 deletions

View File

@@ -18,8 +18,6 @@
{% include "base/theme.html" %}
<link rel="stylesheet" type="text/css" href="{% static 'dist/authentik.css' %}">
<style>{{ brand_css }}</style>
<script src="{% versioned_script 'dist/poly-%v.js' %}" type="module"></script>
<script src="{% versioned_script 'dist/standalone/loading/index-%v.js' %}" type="module"></script>

View File

@@ -1,6 +1,13 @@
{% load static %}
<link rel="stylesheet" type="text/css" href="{% static 'dist/layers/global.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'dist/authentik.css' %}">
{% if ui_theme == "dark" %}
<meta name="color-scheme" content="dark" />
<meta name="theme-color" content="#18191a">
<link rel="stylesheet" type="text/css" href="{% static 'dist/layers/global.dark.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'dist/theme-dark.css' %}">
{% elif ui_theme == "light" %}
<meta name="color-scheme" content="light" />
<meta name="theme-color" content="#ffffff">
@@ -8,4 +15,7 @@
<meta name="color-scheme" content="light dark" />
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#18191a" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" type="text/css" href="{% static 'dist/layers/global.dark.css' %}" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" type="text/css" href="{% static 'dist/theme-dark.css' %}" media="(prefers-color-scheme: dark)">
{% endif %}

View File

@@ -6,20 +6,16 @@
{% block head_before %}
<link rel="prefetch" href="{{ request.brand.branding_default_flow_background_url }}" />
<link rel="stylesheet" type="text/css" href="{% static 'dist/patternfly.min.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'dist/theme-dark.css' %}" media="(prefers-color-scheme: dark)">
{% include "base/header_js.html" %}
{% endblock %}
{% block head %}
<style>
:root {
--ak-flow-background: url("{{ request.brand.branding_default_flow_background_url }}");
--pf-c-background-image--BackgroundImage: var(--ak-flow-background);
--pf-c-background-image--BackgroundImage-2x: var(--ak-flow-background);
--pf-c-background-image--BackgroundImage--sm: var(--ak-flow-background);
--pf-c-background-image--BackgroundImage--sm-2x: var(--ak-flow-background);
--pf-c-background-image--BackgroundImage--lg: var(--ak-flow-background);
}
:root {
--ak-global--background-color: var(--pf-global--BackgroundColor--150);
--ak-global--background-image: url("{{ request.brand.branding_default_flow_background_url }}");
}
/* Form with user */
.form-control-static {
margin-top: var(--pf-global--spacer--sm);
@@ -43,41 +39,37 @@
{% endblock %}
{% block body %}
<div class="pf-c-background-image">
</div>
<ak-skip-to-content></ak-skip-to-content>
<ak-message-container></ak-message-container>
<div class="pf-c-login stacked">
<div class="ak-login-container">
<main class="pf-c-login__main">
<div class="pf-c-login__main-header pf-c-brand ak-brand">
<img src="{{ brand.branding_logo_url }}" alt="authentik Logo" />
</div>
<header class="pf-c-login__main-header">
<h1 class="pf-c-title pf-m-3xl">
{% block card_title %}
{% endblock %}
</h1>
</header>
<div class="pf-c-login__main-body">
{% block card %}
<div class="pf-c-login">
<main class="pf-c-login__main">
<div part="branding" class="pf-c-login__main-header pf-c-brand">
<img part="branding-logo" src="{{ brand.branding_logo_url }}" alt="authentik Logo" />
</div>
<header class="pf-c-login__main-header">
<h1 class="pf-c-title pf-m-3xl">
{% block card_title %}
{% endblock %}
</div>
</main>
<footer class="pf-c-login__footer">
<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>
</div>
</h1>
</header>
<div class="pf-c-login__main-body">
{% block card %}
{% endblock %}
</div>
</main>
<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>
</div>
{% endblock %}

View File

@@ -20,9 +20,10 @@ window.authentik.flow = {
{% block head %}
<script src="{% versioned_script 'dist/flow/FlowInterface-%v.js' %}" type="module"></script>
<style>
:root {
--ak-flow-background: url("{{ flow_background_url }}");
}
:root {
--ak-global--background-color: var(--pf-global--BackgroundColor--150);
--ak-global--background-image: url("{{ flow_background_url }}");
}
</style>
{% endblock %}

View File

@@ -6,54 +6,51 @@
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>{{.Title}}</title>
<link rel="shortcut icon" type="image/png" href="/outpost.goauthentik.io/static/dist/assets/icons/icon.png">
<link rel="stylesheet" type="text/css" href="/outpost.goauthentik.io/static/dist/patternfly.min.css">
<link rel="stylesheet" type="text/css" href="/outpost.goauthentik.io/static/dist/layers/global.css">
<link rel="stylesheet" type="text/css" href="/outpost.goauthentik.io/static/dist/authentik.css">
<link rel="stylesheet" type="text/css" href="/outpost.goauthentik.io/static/layers/global.dark.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" type="text/css" href="/outpost.goauthentik.io/static/theme-dark.css" media="(prefers-color-scheme: dark)">
<link rel="prefetch" href="/outpost.goauthentik.io/static/dist/assets/images/flow_background.jpg" />
<style>
.pf-c-background-image::before {
--ak-flow-background: url("/outpost.goauthentik.io/static/dist/assets/images/flow_background.jpg");
}
:root {
--ak-flow-background: url("/outpost.goauthentik.io/static/dist/assets/images/flow_background.jpg");
--pf-c-background-image--BackgroundImage: var(--ak-flow-background);
--pf-c-background-image--BackgroundImage-2x: var(--ak-flow-background);
--pf-c-background-image--BackgroundImage--sm: var(--ak-flow-background);
--pf-c-background-image--BackgroundImage--sm-2x: var(--ak-flow-background);
--pf-c-background-image--BackgroundImage--lg: var(--ak-flow-background);
--ak-global--background-color: var(--pf-global--BackgroundColor--150);
--ak-global--background-image: url("/outpost.goauthentik.io/static/dist/assets/images/flow_background.jpg");
}
</style>
</head>
<body>
<div class="pf-c-background-image">
</div>
<div class="pf-c-login stacked">
<div class="ak-login-container">
<main class="pf-c-login__main">
<div class="pf-c-login__main-header pf-c-brand ak-brand">
<img src="/outpost.goauthentik.io/static/dist/assets/icons/icon_left_brand.svg" alt="authentik Logo" />
</div>
<header class="pf-c-login__main-header">
<h1 class="pf-c-title pf-m-3xl">
{{ .Title }}
</h1>
</header>
<div class="pf-c-login__main-body">
{{ .Message }}
</div>
<div class="pf-c-login__main-body">
<a href="/" class="pf-c-button pf-m-primary pf-m-block">Go to home</a>
</div>
</main>
<footer class="pf-c-login__footer">
<ul class="pf-c-list pf-m-inline">
<li>
<span>
Powered by authentik
</span>
</li>
</ul>
</footer>
</div>
<div class="pf-c-login">
<main class="pf-c-login__main">
<div part="branding" class="pf-c-login__main-header pf-c-brand">
<img part="branding-logo" src="/outpost.goauthentik.io/static/dist/assets/icons/icon_left_brand.svg" alt="authentik Logo" />
</div>
<header class="pf-c-login__main-header">
<h1 class="pf-c-title pf-m-3xl">
{{ .Title }}
</h1>
</header>
<div class="pf-c-login__main-body">
{{ .Message }}
</div>
<div class="pf-c-login__main-body">
<a href="/" class="pf-c-button pf-m-primary pf-m-block">Go to home</a>
</div>
</main>
<footer class="pf-c-login__footer pf-m-dark">
<ul class="pf-c-list pf-m-inline">
<li>
<span>
Powered by authentik
</span>
</li>
</ul>
</footer>
</div>
</body>
</html>

View File

@@ -99,17 +99,6 @@ export class AdminInterface extends WithCapabilitiesConfig(AuthenticatedInterfac
display: none;
}
.pf-c-page {
background-color: var(--pf-c-page--BackgroundColor) !important;
}
:host([theme="dark"]) {
/* Global page background colour */
.pf-c-page {
--pf-c-page--BackgroundColor: var(--ak-dark-background);
}
}
ak-page-navbar {
grid-area: header;
}

View File

@@ -7,43 +7,6 @@
--__AK_UI_BASE__: 1;
}
/* #region Global */
:root {
--ak-accent: #fd4b2d;
--ak-dark-foreground: #fafafa;
--ak-dark-foreground-darker: #bebebe;
--ak-dark-foreground-link: #5a5cb9;
--ak-dark-background: #18191a;
--ak-dark-background-darker: #000000;
--ak-dark-background-light: #1c1e21;
--ak-dark-background-light-ish: #212427;
--ak-dark-background-lighter: #2b2e33;
--ak-flow-background-color-contrast: var(--pf-global--Color--100);
--ak-flow-footer-color: var(--pf-global--Color--light-100);
/* PatternFly likes to override global variables for some reason */
--ak-global--Color--100: var(--pf-global--Color--100);
/* Minimum width after which the sidebar becomes automatic */
--ak-sidebar--minimum-auto-width: 80rem;
/**
* The height of the navbar and branded sidebar.
* @todo This shouldn't be necessary. The sidebar can instead use a grid layout
* ensuring they share the same height.
*/
--ak-navbar--height: 7rem;
--pf-global--disabled-color--100: GrayText;
--pf-global--disabled-color--200: color-mix(in srgb, GrayText 100%, CanvasText 75%);
--pf-global--disabled-color--300: color-mix(in srgb, GrayText 100%, CanvasText 100%);
}
/* #endregion */
/* #region Scrollbars */
/**
@@ -63,6 +26,16 @@
--ak-scrollbar-thumb-background-color: hsl(0 0% 76%);
}
.pf-m-dark {
--pf-global--Color--100: var(--pf-global--Color--dark-100);
--pf-global--Color--200: var(--pf-global--Color--dark-200);
--pf-global--BorderColor--100: var(--pf-global--BorderColor--dark-100);
--pf-global--primary-color--100: var(--pf-global--primary-color--dark-100);
--pf-global--link--Color: var(--pf-global--link--Color--dark);
--pf-global--link--Color--hover: var(--pf-global--link--Color--dark);
--pf-global--BackgroundColor--100: var(--pf-global--BackgroundColor--light-100);
}
/* Applicable to browsers with a WebKit lineage (Chrome, Edge, Safari) */
::-webkit-scrollbar {
background: var(--ak-scrollbar-background-color);
@@ -156,19 +129,6 @@
color: var(--pf-c-form__label-required--Color);
}
html {
--pf-c-nav__link--PaddingTop: 0.5rem;
--pf-c-nav__link--PaddingRight: 0.5rem;
--pf-c-nav__link--PaddingBottom: 0.5rem;
--pf-c-nav__link--PaddingLeft: 0.5rem;
}
html > form > input {
position: absolute;
top: -2000px;
left: -2000px;
}
/* #endregion */
/* #region Screen readers */
@@ -334,49 +294,222 @@ ak-tabs[vertical] {
/* #endregion */
/* #region Login adjustments */
/* #region Login */
/* compatibility-mode-fix */
.pf-c-login.pf-c-login {
--ak-login--header-min-height: var(--pf-global--spacer--lg);
--ak-login--header-max-height: clamp(0, 45%, 15dvh);
--ak-login--max-width: 35rem;
--ak-login--main-column-width: minmax(min-content, var(--ak-login--max-width));
--pf-c-login__main-footer--PaddingBottom: 0;
--pf-c-login__main-body--PaddingBottom: clamp(
var(--pf-global--spacer--xs),
7dvw,
var(--pf-global--spacer--3xl)
);
/* Ensure card is displayed on small screens */
.pf-c-login__main {
display: block;
position: relative;
width: 100%;
flex: 1 1 auto;
place-content: center;
}
@media (max-width: 1199px) {
.pf-c-login__container {
display: flex;
flex-direction: column;
padding: 0;
display: grid;
justify-content: space-between;
grid-template-rows:
[header] minmax(0, clamp(1rem, 15%, 15dvh))
[main] minmax(auto, min-content)
[footer] auto;
grid-template-columns:
1fr
[main] var(--ak-login--main-column-width)
1fr;
grid-template-areas:
"header header header"
" . main . "
"footer footer footer";
&::before {
display: block;
content: "";
background-color: var(--ak-login--background-color-overlay, transparent);
z-index: -1;
height: 100%;
pointer-events: none;
}
&::before,
[part="login-overlay"] {
grid-row: header / footer;
grid-column: header;
}
&::after {
display: block;
content: "";
grid-area: main;
z-index: -1;
height: 75dvh;
pointer-events: none;
}
}
.ak-login-container {
max-width: 35rem;
width: 100%;
/* #region Login Main */
.pf-c-login__main {
--pf-c-login__container--PaddingLeft: 0 !important;
--pf-c-login__container--PaddingRight: 0 !important;
aspect-ratio: 1.2/ 1;
justify-content: space-between;
grid-area: main;
margin: 0;
--ak-login--padding-max: 8dvw;
--ak-login--padding: clamp(
var(--pf-global--spacer--md),
var(--pf-global--spacer--2xl),
var(--ak-login--padding-max)
);
position: relative;
max-width: var(--ak-login--max-width);
display: flex;
flex-direction: column;
height: calc(100vh - var(--pf-global--spacer--lg) - var(--pf-global--spacer--lg));
flex-flow: column;
.slotted-content {
position: relative;
flex: 1 1 auto;
}
}
.pf-c-login__main-header {
padding-inline: var(--ak-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 {
padding-inline: calc(var(--ak-login--padding) / 4);
padding-block-start: clamp(var(--pf-global--spacer--xs), 7dvw, var(--pf-global--spacer--3xl));
padding-block-end: calc(var(--ak-login--padding) / 2);
display: flex;
justify-content: center;
[part="branding-logo"] {
display: block;
width: clamp(75%, calc(var(--ak-login--max-width) / 2), 90%);
min-height: 4rem;
}
}
.pf-c-login__main-body {
padding-inline: var(--ak-login--padding);
}
.pf-c-login__main-footer {
display: block;
}
.pf-c-login__main-footer-band {
@media (max-width: 35rem) or (max-height: 17.5rem) {
--pf-c-login__main-footer-band--BackgroundColor: transparent !important;
}
}
@media (min-width: 70rem) and (min-height: 17.5rem) {
.pf-c-login[data-layout="content_left"],
.pf-c-login[data-layout="content_right"] {
display: flex;
flex-flow: row wrap;
place-content: space-between;
gap: var(--pf-global--spacer--lg);
.pf-c-login__main,
.pf-c-login__footer {
align-self: center;
}
}
.pf-c-login[data-layout="content_right"] {
flex-direction: row-reverse;
}
.pf-c-login[data-layout="sidebar_left"],
.pf-c-login[data-layout="sidebar_right"] {
--ak-login--max-width: 36rem;
--ak-login--background-color-overlay: var(--pf-c-login__main--BackgroundColor);
.pf-c-login__main {
aspect-ratio: auto;
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"] {
grid-template-columns: [main footer] var(--ak-login--main-column-width) repeat(2, 1fr);
grid-template-areas:
"header . ."
"main . ."
"footer . .";
}
.pf-c-login[data-layout="sidebar_right"] {
grid-template-columns: repeat(2, 1fr) var(--ak-login--main-column-width) [main footer];
grid-template-areas:
". . header"
". . main "
". . footer";
}
.pf-c-login__main-footer-band {
--pf-c-login__main-footer-band--BackgroundColor: transparent !important;
}
}
/* #endregion */
.pf-c-data-list {
padding-inline-start: 0;
}
.pf-c-login__footer {
color: var(--ak-flow-footer-color);
flex: 250 0 auto;
--pf-global--Color--100: var(--pf-global--Color--light-100);
min-height: var(--pf-global--spacer--2xl);
grid-area: footer;
flex: 0 0 auto;
display: flex;
justify-content: end;
flex-direction: column;
}
align-self: end;
padding-inline: var(--pf-global--spacer--sm);
padding-block: var(--pf-global--spacer--md) !important;
@media (max-width: 768px) {
:root {
--ak-flow-footer-color: var(--ak-flow-background-color-contrast);
ul.pf-c-list.pf-m-inline {
justify-content: center;
}
}
.pf-c-login__footer ul.pf-c-list.pf-m-inline {
justify-content: center;
padding: 2rem 0;
@media (max-width: 35rem) {
color: inherit;
}
}
/* #endregion */
@@ -394,12 +527,6 @@ ak-tabs[vertical] {
margin-right: var(--pf-global--spacer--sm);
}
/* ensure background on non-flow pages match */
.pf-c-background-image::before {
background-image: var(--ak-flow-background);
background-position: center;
}
.pf-m-success {
color: var(--pf-global--success-color--100) !important;
}
@@ -448,6 +575,11 @@ fieldset {
&:has(legend.sr-only) {
border-width: 0;
&:not(.pf-c-modal-box__footer) {
--ak-legend-padding-inline-base: 0;
--ak-legend-margin-inline-base: 0;
}
}
&.pf-c-form__group {
@@ -464,6 +596,12 @@ fieldset {
}
}
&.pf-c-login__main-footer-band {
& > *:last-child {
padding-block-end: var(--pf-c-login__main-footer-band-item--PaddingTop);
}
}
&.pf-c-modal-box__footer {
--ak-legend-padding-inline-base: var(--pf-global--spacer--md);
padding-block: calc(var(--ak-legend-padding-inline-base) / 2);
@@ -571,42 +709,6 @@ fieldset {
}
}
/* Flow-card adjustments for static pages */
.pf-c-brand {
padding-top: calc(
var(--pf-c-login__main-footer-links--PaddingTop) +
var(--pf-c-login__main-footer-links--PaddingBottom) +
var(--pf-c-login__main-body--PaddingBottom)
);
max-height: 9rem;
}
.ak-brand {
display: flex;
justify-content: center;
width: 100%;
}
.ak-brand img {
padding: 0 2rem;
max-height: inherit;
}
@media (min-height: 60rem) {
.pf-c-login[data-layout="stacked"] .pf-c-login__main {
margin-top: 13rem;
}
}
.pf-c-login[data-layout="sidebar_left"],
.pf-c-login[data-layout="sidebar_right"] {
--ak-flow-footer-color: var(--ak-flow-background-color-contrast);
}
.pf-c-data-list {
padding-inline-start: 0;
}
/* #region Code blocks */
pre:has(.hljs) {

View File

@@ -0,0 +1,83 @@
/**
* @file authentik global layer.
*/
/* #region Root */
:root {
--ak-accent: #fd4b2d;
--ak-dark-foreground: #fafafa;
--ak-dark-foreground-darker: #bebebe;
--ak-dark-foreground-link: #5a5cb9;
--ak-dark-background: #18191a;
--ak-dark-background-darker: #000000;
--ak-dark-background-light: #1c1e21;
--ak-dark-background-light-ish: #212427;
--ak-dark-background-lighter: #2b2e33;
--ak-global--background-contrast: var(--pf-global--Color--100);
/* PatternFly likes to override global variables for some reason */
--ak-global--Color--100: var(--pf-global--Color--100);
/* Minimum width after which the sidebar becomes automatic */
--ak-sidebar--minimum-auto-width: 80rem;
/**
* The height of the navbar and branded sidebar.
* @todo This shouldn't be necessary. The sidebar can instead use a grid layout
* ensuring they share the same height.
*/
--ak-navbar--height: 7rem;
--pf-global--disabled-color--100: GrayText;
--pf-global--disabled-color--200: color-mix(in srgb, GrayText 100%, CanvasText 75%);
--pf-global--disabled-color--300: color-mix(in srgb, GrayText 100%, CanvasText 100%);
}
/* #endregion */
/* #region Document */
html,
body {
height: auto;
min-height: 100dvh;
}
body {
background-color: var(--ak-global--background-color, var(--pf-global--BackgroundColor--150));
&::before {
content: "";
position: fixed;
inset: 0;
pointer-events: none;
background-size: cover;
background-repeat: no-repeat;
background-position: center;
background-attachment: local;
background-image: none;
@media (min-width: 35rem) and (min-height: 17.5rem) {
background-image: var(--ak-global--background-image, none);
}
}
}
/* #endregion */
html {
--pf-c-nav__link--PaddingTop: 0.5rem;
--pf-c-nav__link--PaddingRight: 0.5rem;
--pf-c-nav__link--PaddingBottom: 0.5rem;
--pf-c-nav__link--PaddingLeft: 0.5rem;
}
html > form > input {
position: absolute;
top: -2000px;
left: -2000px;
}

View File

@@ -0,0 +1,29 @@
/**
* @file authentik global dark layer.
*/
/* #region Global */
:root {
/* TODO: We've seemed to have drifted from PF's dark-100 usage. Revisit this after PF 5. */
--pf-global--BackgroundColor--100: #1c1e21 !important;
--pf-global--BackgroundColor--dark-100: #18191a !important;
--pf-global--BackgroundColor--150: var(--pf-global--BackgroundColor--dark-100) !important;
--pf-global--BackgroundColor--200: var(--pf-global--BackgroundColor--dark-200) !important;
--pf-global--BackgroundColor--300: var(--pf-global--BackgroundColor--dark-300) !important;
--pf-global--Color--100: var(--ak-dark-foreground) !important;
--ak-global--Color--100: var(--ak-dark-foreground) !important;
--pf-global--BorderColor--100: var(--ak-dark-background-lighter) !important;
--pf-global--link--Color: var(--pf-global--link--Color--light) !important;
--pf-global--link--Color--hover: var(--pf-global--link--Color--light--hover) !important;
}
/* #endregion */
/* #region Document */
body {
color-scheme: dark;
}
/* #endregion */

View File

@@ -7,22 +7,6 @@
--__AK_UI_DARK__: 1;
}
/* #region Global */
:root {
--pf-global--Color--100: var(--ak-dark-foreground) !important;
--ak-global--Color--100: var(--ak-dark-foreground) !important;
--pf-c-page__main-section--m-light--BackgroundColor: var(--ak-dark-background-darker);
--pf-global--BorderColor--100: var(--ak-dark-background-lighter) !important;
--pf-global--link--Color: var(--pf-global--link--Color--light);
--pf-global--link--Color--hover: var(--pf-global--link--Color--light--hover);
}
body {
background-color: var(--ak-dark-background) !important;
color-scheme: dark;
}
/* #region Scrollbars */
:root {
@@ -33,6 +17,27 @@ body {
--ak-scrollbar-thumb-background-color: hsl(0 0% 42%);
}
body:has(ak-flow-executor) {
background-color: var(--pf-global--BackgroundColor--150);
}
/**
* Our reversal of Patternfly's light variables requires us to set the colors
* back to their defaults when in dark mode.
*/
.pf-m-dark {
--pf-global--Color--100: var(--pf-global--Color--light-100);
--pf-global--Color--200: var(--pf-global--Color--light-200);
--pf-global--BorderColor--100: var(--pf-global--BorderColor--light-100);
--pf-global--primary-color--100: var(--pf-global--primary-color--light-100);
--pf-global--link--Color: var(--pf-global--link--Color--light);
--pf-global--link--Color--hover: var(--pf-global--link--Color--light);
--pf-global--BackgroundColor--100: var(--pf-global--BackgroundColor--dark-100);
}
.pf-c-login {
--pf-c-login__main--BackgroundColor: var(--pf-global--BackgroundColor--dark-100) !important;
}
/* #endregion */
.pf-c-radio {
@@ -41,15 +46,7 @@ body {
/* Global page background colour */
.pf-c-page {
--pf-c-page--BackgroundColor: var(--ak-dark-background);
}
.pf-c-drawer__content {
--pf-c-drawer__content--BackgroundColor: var(--ak-dark-background);
}
.pf-c-title {
color: var(--ak-dark-foreground);
--pf-c-page--BackgroundColor: var(--pf-global--BackgroundColor--dark-100);
}
.pf-u-mb-xl {
@@ -61,7 +58,7 @@ body {
/* Header sections */
.pf-c-page__main-section {
--pf-c-page__main-section--BackgroundColor: var(--ak-dark-background);
--pf-c-page__main-section--BackgroundColor: var(--pf-global--BackgroundColor--dark-100);
}
.sidebar-trigger,
@@ -69,26 +66,12 @@ body {
background-color: transparent !important;
}
.pf-c-content {
color: var(--ak-dark-foreground);
}
/* #region Card */
.pf-c-card {
--pf-c-card--BackgroundColor: var(--ak-dark-background-light);
color: var(--ak-dark-foreground);
}
.pf-c-card.pf-m-non-selectable-raised {
--pf-c-card--BackgroundColor: var(--ak-dark-background-lighter);
}
.pf-c-card__title,
.pf-c-card__body {
color: var(--ak-dark-foreground);
}
/* #endregion */
/* #region Fields */
@@ -157,8 +140,7 @@ fieldset {
/* #region Page layout */
/**
* Our reversal of the page header on the light theme requires us to set the colors
* back to their PatternFly defaults.
* cf. Color reversal.
*/
.pf-c-page__header {
--pf-global--Color--100: var(--pf-global--Color--light-100);
@@ -369,24 +351,15 @@ select.pf-c-form-control {
/* #region Flows */
.pf-c-login__main {
--pf-c-login__main--BackgroundColor: var(--ak-dark-background);
}
.pf-c-login__main-body,
.pf-c-login__main-header,
.pf-c-login__main-header-desc {
color: var(--ak-dark-foreground);
}
.pf-c-login__main-footer-links-item img,
.pf-c-login__main-footer-links-item .fas {
filter: invert(1);
@media (min-width: 35rem) and (min-height: 50rem) {
.pf-c-login__main-footer-links-item img,
.pf-c-login__main-footer-links-item .fas {
filter: invert(1);
}
}
.pf-c-login__main-footer-band {
--pf-c-login__main-footer-band--BackgroundColor: var(--ak-dark-background-lighter);
color: var(--ak-dark-foreground);
--pf-c-login__main-footer-band--BackgroundColor: var(--pf-global--BackgroundColor--dark-300);
}
.form-control-static {

View File

@@ -286,6 +286,44 @@ export function applyDocumentTheme(hint: CSSColorSchemeValue | UIThemeHint = "au
applyStyleSheets(preferredColorScheme);
}
/**
* A CSS variable representing the global background image.
*/
export const AKBackgroundImageProperty = "--ak-global--background-image";
/**
* Applies the given background image URL to the document body.
*
* This method is very defensive to avoid unnecessary DOM repaints.
*/
export function applyBackgroundImageProperty(value?: string | null): void {
const fallbackOrigin = window.location.origin;
if (!value || !URL.canParse(value, fallbackOrigin)) {
return;
}
const nextBackgroundURL = new URL(value, fallbackOrigin);
const currentBackgroundImage = getComputedStyle(document.body, "::before").backgroundImage;
let currentBackgroundImageURL: URL | null = null;
if (currentBackgroundImage && currentBackgroundImage !== "none") {
// Extract URL from background-image property
const [, urlMatch] = currentBackgroundImage.match(/url\(["']?([^"']*)["']?\)/) || [];
if (URL.canParse(urlMatch)) {
currentBackgroundImageURL = new URL(urlMatch, fallbackOrigin);
}
}
if (currentBackgroundImageURL && currentBackgroundImageURL.href === nextBackgroundURL.href) {
return;
}
document.body.style.setProperty(AKBackgroundImageProperty, `url("${nextBackgroundURL.href}")`);
}
/**
* Returns the root interface element of the page.
*

View File

@@ -8,7 +8,6 @@ import { EVENT_LOCALE_CHANGE, EVENT_LOCALE_REQUEST } from "#common/constants";
import { AKElement } from "#elements/Base";
import { customEvent } from "#elements/utils/customEvents";
import { html } from "lit";
import { customElement, property } from "lit/decorators.js";
/**
@@ -26,6 +25,10 @@ import { customElement, property } from "lit/decorators.js";
*/
@customElement("ak-locale-context")
export class LocaleContext extends WithBrandConfig(AKElement) {
protected createRenderRoot(): HTMLElement | DocumentFragment {
return this;
}
/// @attribute The text representation of the current locale */
@property({ attribute: true, type: String })
locale = DEFAULT_LOCALE;
@@ -90,10 +93,6 @@ export class LocaleContext extends WithBrandConfig(AKElement) {
// works just fine for almost every use case.
this.dispatchEvent(customEvent(EVENT_LOCALE_CHANGE));
}
render() {
return html`<slot></slot>`;
}
}
export default LocaleContext;

View File

@@ -0,0 +1,20 @@
:host {
--pf-c-login__main-body--PaddingBottom: var(--pf-global--spacer--2xl);
position: relative;
}
.pf-c-drawer__body {
display: flex;
flex-flow: column;
}
.pf-c-drawer__content {
--pf-c-drawer__content--BackgroundColor: transparent;
}
.inspector-toggle {
position: absolute;
top: 1rem;
right: 1rem;
z-index: 100;
}

View File

@@ -9,11 +9,14 @@ import "#flow/stages/FlowErrorStage";
import "#flow/stages/FlowFrameStage";
import "#flow/stages/RedirectStage";
import Styles from "./FlowExecutor.css";
import { DEFAULT_CONFIG } from "#common/api/config";
import { EVENT_FLOW_ADVANCE, EVENT_FLOW_INSPECTOR_TOGGLE } from "#common/constants";
import { pluckErrorDetail } from "#common/errors/network";
import { globalAK } from "#common/global";
import { configureSentry } from "#common/sentry/index";
import { applyBackgroundImageProperty } from "#common/theme";
import { WebsocketClient } from "#common/ws";
import { Interface } from "#elements/Interface";
@@ -35,7 +38,7 @@ import {
} from "@goauthentik/api";
import { msg } from "@lit/localize";
import { css, CSSResult, html, nothing, PropertyValues, TemplateResult } from "lit";
import { CSSResult, html, nothing, PropertyValues, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { unsafeHTML } from "lit/directives/unsafe-html.js";
import { until } from "lit/directives/until.js";
@@ -48,20 +51,14 @@ import PFLogin from "@patternfly/patternfly/components/Login/login.css";
import PFTitle from "@patternfly/patternfly/components/Title/title.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
const FlowLayoutClasses = {
[FlowLayoutEnum.ContentLeft]: "pf-c-login__container",
[FlowLayoutEnum.ContentRight]: "pf-c-login__container content-right",
[FlowLayoutEnum.SidebarLeft]: "ak-login-container",
[FlowLayoutEnum.SidebarRight]: "ak-login-container",
[FlowLayoutEnum.Stacked]: "ak-login-container",
[FlowLayoutEnum.UnknownDefaultOpenApi]: "ak-login-container",
} as const satisfies Record<FlowLayoutEnum, string>;
@customElement("ak-flow-executor")
export class FlowExecutor
extends WithCapabilitiesConfig(WithBrandConfig(Interface))
implements StageHost
{
static readonly DefaultLayout: FlowLayoutEnum =
globalAK()?.flow?.layout || FlowLayoutEnum.Stacked;
//#region Styles
static styles: CSSResult[] = [
@@ -72,99 +69,7 @@ export class FlowExecutor
PFTitle,
PFList,
PFBackgroundImage,
css`
:host {
--pf-c-login__main-body--PaddingBottom: var(--pf-global--spacer--2xl);
}
.pf-c-background-image::before {
--pf-c-background-image--BackgroundImage: var(--ak-flow-background);
--pf-c-background-image--BackgroundImage-2x: var(--ak-flow-background);
--pf-c-background-image--BackgroundImage--sm: var(--ak-flow-background);
--pf-c-background-image--BackgroundImage--sm-2x: var(--ak-flow-background);
--pf-c-background-image--BackgroundImage--lg: var(--ak-flow-background);
@media (max-width: 768px) {
background: var(--pf-c-login__main--BackgroundColor) !important;
}
}
.ak-hidden {
display: none;
}
:host {
position: relative;
}
.pf-c-drawer__content {
background-color: transparent;
}
.pf-c-login {
align-items: baseline;
}
/* layouts */
@media (min-height: 60rem) {
.pf-c-login[data-layout="stacked"] .pf-c-login__main {
margin-top: 13rem;
}
}
.pf-c-login__container.content-right {
grid-template-areas:
"header main"
"footer main"
". main";
}
.pf-c-login[data-layout="sidebar_left"] {
justify-content: flex-start;
padding-top: 0;
padding-bottom: 0;
}
.pf-c-login[data-layout="sidebar_left"] .ak-login-container,
.pf-c-login[data-layout="sidebar_right"] .ak-login-container {
height: 100%;
min-height: 100dvh;
background-color: var(--pf-c-login__main--BackgroundColor);
padding-inline: var(--pf-global--spacer--lg);
padding-block-end: var(--pf-global--spacer--xs);
}
.pf-c-login[data-layout="sidebar_left"] .pf-c-list,
.pf-c-login[data-layout="sidebar_right"] .pf-c-list {
color: #000;
}
.pf-c-login[data-layout="sidebar_right"] {
justify-content: flex-end;
padding-top: 0;
padding-bottom: 0;
}
:host([theme="dark"]) .pf-c-login[data-layout="sidebar_left"] .ak-login-container,
:host([theme="dark"]) .pf-c-login[data-layout="sidebar_right"] .ak-login-container {
background-color: var(--ak-dark-background);
}
:host([theme="dark"]) .pf-c-login[data-layout="sidebar_left"] .pf-c-list,
:host([theme="dark"]) .pf-c-login[data-layout="sidebar_right"] .pf-c-list {
color: var(--ak-dark-foreground);
}
.pf-c-brand {
padding-top: calc(
var(--pf-c-login__main-footer-links--PaddingTop) +
var(--pf-c-login__main-footer-links--PaddingBottom) +
var(--pf-c-login__main-body--PaddingBottom)
);
max-height: 9rem;
}
.ak-brand {
display: flex;
justify-content: center;
}
.ak-brand img {
padding: 0 2rem;
max-height: inherit;
}
.inspector-toggle {
position: absolute;
top: 1rem;
right: 1rem;
z-index: 100;
}
`,
Styles,
];
//#endregion
@@ -204,6 +109,9 @@ export class FlowExecutor
@state()
protected inspectorAvailable?: boolean;
@state()
protected layout: FlowLayoutEnum = FlowExecutor.DefaultLayout;
@state()
public flowInfo?: ContextualFlowInfo;
@@ -297,8 +205,14 @@ export class FlowExecutor
// DOM post-processing has to happen after the render.
public updated(changedProperties: PropertyValues<this>) {
super.updated(changedProperties);
if (changedProperties.has("challenge") && this.challenge?.flowInfo) {
this.layout = this.challenge?.flowInfo?.layout || FlowExecutor.DefaultLayout;
}
if (changedProperties.has("flowInfo") && this.flowInfo) {
this.#setShadowStyles(this.flowInfo);
applyBackgroundImageProperty(this.flowInfo.background);
}
}
@@ -358,29 +272,10 @@ export class FlowExecutor
});
};
#setShadowStyles(value: ContextualFlowInfo) {
if (!value) return;
this.shadowRoot
?.querySelectorAll<HTMLDivElement>(".pf-c-background-image")
.forEach((bg) => {
bg.style.setProperty("--ak-flow-background", `url('${value?.background}')`);
});
}
//#region Render
get layout(): FlowLayoutEnum {
return (
this.challenge?.flowInfo?.layout || globalAK()?.flow?.layout || FlowLayoutEnum.Stacked
);
}
async renderChallenge(): Promise<TemplateResult> {
if (!this.challenge) {
return html`<ak-flow-card loading></ak-flow-card>`;
}
switch (this.challenge?.component) {
async renderChallenge(component: ChallengeTypes["component"]): Promise<TemplateResult> {
switch (component) {
case "ak-stage-access-denied":
await import("#flow/stages/access_denied/AccessDeniedStage");
return html`<ak-stage-access-denied
@@ -569,11 +464,17 @@ export class FlowExecutor
);
}
render(): TemplateResult {
protected renderLoading(): TemplateResult {
return html`<div class="slotted-content">
<slot></slot>
</div>`;
}
public override render(): TemplateResult {
const { layout } = this;
const { component } = this.challenge || {};
return html`<ak-locale-context>
<div class="pf-c-background-image" part="background-image"></div>
<div class="pf-c-page__drawer" part="page-drawer">
<div
class="pf-c-drawer ${this.inspectorOpen ? "pf-m-expanded" : "pf-m-collapsed"}"
@@ -582,35 +483,44 @@ export class FlowExecutor
<div class="pf-c-drawer__main" part="drawer-main">
<div class="pf-c-drawer__content" part="drawer-content">
<div class="pf-c-drawer__body" part="drawer-body">
<div class="pf-c-login" data-layout=${layout} part="flow">
<div class=${FlowLayoutClasses[layout]} part="flow-container">
<main
class="pf-c-login__main"
aria-label=${msg("Authentication form")}
part="flow-main"
<div class="pf-c-login" data-layout=${layout} part="login">
${this.loading && this.challenge
? html`<ak-loading-overlay
part="login-overlay"
></ak-loading-overlay>`
: nothing}
<main
class="pf-c-login__main"
aria-label=${msg("Authentication form")}
part="flow-main"
>
<div
class="pf-c-login__main-header pf-c-brand"
part="branding"
>
${this.loading && this.challenge
? html`<ak-loading-overlay></ak-loading-overlay>`
: nothing}
<div
class="pf-c-login__main-header pf-c-brand ak-brand"
>
<img
src="${themeImage(this.brandingLogo)}"
alt="${msg("authentik Logo")}"
role="presentation"
/>
</div>
${until(this.renderChallenge())}
</main>
<ak-brand-links
part="brand-links"
role="contentinfo"
aria-label=${msg("Site footer")}
class="pf-c-login__footer"
.links=${this.brandingFooterLinks}
></ak-brand-links>
</div>
<img
part="branding-logo"
src="${themeImage(this.brandingLogo)}"
alt="${msg("authentik Logo")}"
role="presentation"
/>
</div>
${component
? until(this.renderChallenge(component))
: this.renderLoading()}
</main>
<ak-brand-links
part="brand-links"
role="contentinfo"
aria-label=${msg("Site footer")}
class="pf-c-login__footer ${layout ===
FlowLayoutEnum.Stacked
? "pf-m-dark"
: ""}"
.links=${this.brandingFooterLinks}
></ak-brand-links>
</div>
</div>
</div>

View File

@@ -2,10 +2,6 @@
height: 100dvh;
}
.pf-c-card {
--pf-c-card--BackgroundColor: var(--pf-c-notification-drawer--BackgroundColor);
}
:host {
background-color: var(--pf-c-notification-drawer--BackgroundColor);
}

View File

@@ -1,10 +1,11 @@
import "#elements/EmptyState";
import { AKElement } from "#elements/Base";
import { SlottedTemplateResult } from "#elements/types";
import { ChallengeTypes } from "@goauthentik/api";
import { css, CSSResult, html, nothing } from "lit";
import { CSSResult, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import PFLogin from "@patternfly/patternfly/components/Login/login.css";
@@ -30,32 +31,7 @@ export class FlowCard extends AKElement {
@property({ type: Boolean })
loading = false;
static styles: CSSResult[] = [
PFBase,
PFLogin,
PFTitle,
css`
.pf-c-login__main-footer {
display: block;
}
slot[name="footer-band"] {
text-align: center;
background-color: var(--pf-c-login__main-footer-band--BackgroundColor);
padding: 0;
margin-top: 1em;
}
.pf-c-login__main-body {
--pf-c-login__main-body--md--PaddingLeft: var(--pf-global--spacer--md);
--pf-c-login__main-body--md--PaddingRight: var(--pf-global--spacer--md);
}
.pf-c-login__main-body:last-child {
padding-bottom: calc(var(--pf-c-login__main-header--PaddingTop) * 1.2);
}
`,
];
static styles: CSSResult[] = [PFBase, PFLogin, PFTitle];
render() {
let inner = html`<slot></slot>`;
@@ -63,25 +39,22 @@ export class FlowCard extends AKElement {
inner = html`<ak-empty-state loading default-label></ak-empty-state>`;
}
// No title if the challenge doesn't provide a title and no custom title is set
let title = undefined;
let title: null | SlottedTemplateResult = null;
if (this.hasSlotted("title")) {
title = html`<h1 class="pf-c-title pf-m-3xl"><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>`;
}
return html`${title ? html`<div class="pf-c-login__main-header">${title}</div>` : nothing}
const footer = this.hasSlotted("footer") ? html`<slot name="footer"></slot>` : null;
const footerBand = this.hasSlotted("footer-band")
? html`<slot name="footer-band"></slot>`
: null;
return html`${title ? html`<div class="pf-c-login__main-header">${title}</div>` : null}
<div class="pf-c-login__main-body">${inner}</div>
${this.hasSlotted("footer") || this.hasSlotted("footer-band")
? html`<footer class="pf-c-login__main-footer">
${this.hasSlotted("footer") ? html`<slot name="footer"></slot>` : nothing}
${this.hasSlotted("footer-band")
? html`<slot
name="footer-band"
class="pf-c-login__main-footer-band"
></slot>`
: nothing}
</footer>`
: nothing}`;
${footer || footerBand
? html`<div class="pf-c-login__main-footer">${footer}${footerBand}</div>`
: null}`;
}
}

View File

@@ -0,0 +1,22 @@
.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);
}
&:hover {
background-color: var(--pf-global--BackgroundColor--200);
}
}
i {
font-size: var(--pf-global--icon--FontSize--lg);
}
.content {
text-align: left;
}

View File

@@ -3,6 +3,8 @@ import "#flow/stages/authenticator_validate/AuthenticatorValidateStageCode";
import "#flow/stages/authenticator_validate/AuthenticatorValidateStageDuo";
import "#flow/stages/authenticator_validate/AuthenticatorValidateStageWebAuthn";
import Styles from "./AuthenticatorValidateStage.css";
import { DEFAULT_CONFIG } from "#common/api/config";
import { BaseStage, StageHost, SubmitOptions } from "#flow/stages/base";
@@ -19,8 +21,9 @@ import {
} from "@goauthentik/api";
import { msg } from "@lit/localize";
import { css, CSSResult, html, nothing, PropertyValues, TemplateResult } from "lit";
import { CSSResult, html, nothing, PropertyValues, TemplateResult } from "lit";
import { customElement, state } from "lit/decorators.js";
import { repeat } from "lit/directives/repeat.js";
import PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css";
@@ -29,39 +32,6 @@ import PFLogin from "@patternfly/patternfly/components/Login/login.css";
import PFTitle from "@patternfly/patternfly/components/Title/title.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
const customCSS = css`
.authenticator-button {
/* compatibility-mode-fix */
& {
align-items: center;
width: 100%;
display: grid;
grid-template-columns: auto 1fr;
gap: var(--pf-global--spacer--md);
}
&:hover {
background-color: var(--pf-global--Color--light-200);
}
}
:host([theme="dark"]) .authenticator-button {
color: var(--ak-dark-foreground) !important;
&:hover {
background-color: var(--pf-global--Color--300);
}
}
i {
font-size: 1.5rem;
padding: 1rem 0;
width: 3rem;
}
.content {
text-align: left;
}
`;
interface DevicePickerProps {
icon?: string;
label: string;
@@ -121,7 +91,7 @@ export class AuthenticatorValidateStage
PFFormControl,
PFTitle,
PFButton,
customCSS,
Styles,
];
flowSlug = "";
@@ -227,36 +197,42 @@ export class AuthenticatorValidateStage
return nothing;
}
const deviceChallengeButtons = this.challenge.deviceChallenges.map((challenges, idx) => {
const buttonID = `device-challenge-${idx}`;
const labelID = `${buttonID}-label`;
const descriptionID = `${buttonID}-description`;
const { deviceChallenges } = this.challenge;
const { icon, label, description } = DevicePickerPropMap[challenges.deviceClass];
const deviceChallengeButtons = repeat(
deviceChallenges,
(challenges) => challenges.deviceUid,
(challenges, idx) => {
const buttonID = `device-challenge-${idx}`;
const labelID = `${buttonID}-label`;
const descriptionID = `${buttonID}-description`;
return html`
<button
id=${buttonID}
aria-labelledby=${labelID}
aria-describedby=${descriptionID}
class="pf-c-button authenticator-button"
type="button"
@click=${() => {
this.selectedDeviceChallenge = challenges;
}}
>
<i class="fas ${icon}" aria-hidden="true"></i>
<div class="content">
<p id=${labelID}>${label}</p>
<small id=${descriptionID}>${description}</small>
</div>
</button>
`;
});
const { icon, label, description } = DevicePickerPropMap[challenges.deviceClass];
return html`
<button
id=${buttonID}
aria-labelledby=${labelID}
aria-describedby=${descriptionID}
class="pf-c-button authenticator-button"
type="button"
@click=${() => {
this.selectedDeviceChallenge = challenges;
}}
>
<i class="fas ${icon}" aria-hidden="true"></i>
<div class="content">
<h1 class="pf-c-title pf-m-sm" id=${labelID}>${label}</h1>
<p class="pf-c-form__helper-text" id=${descriptionID}>${description}</p>
</div>
</button>
`;
},
);
return html`<fieldset class="pf-c-form__group pf-m-action" name="device-challenges">
<legend class="pf-c-title">${msg("Select an authentication method")}</legend>
${deviceChallengeButtons.length
${deviceChallenges.length
? deviceChallengeButtons
: msg("No authentication methods available.")}
</fieldset>`;
@@ -267,23 +243,27 @@ export class AuthenticatorValidateStage
return nothing;
}
const stageButtons = this.challenge.configurationStages.map((stage) => {
return html`<button
class="pf-c-button authenticator-button"
type="button"
@click=${() => {
this.submit({
component: this.challenge.component || "",
selectedStage: stage.pk,
});
}}
>
<div class="content">
<p>${stage.name}</p>
<small>${stage.verboseName}</small>
</div>
</button>`;
});
const stageButtons = repeat(
this.challenge.configurationStages,
(stage) => stage.pk,
(stage) => {
return html`<button
class="pf-c-button authenticator-button"
type="button"
@click=${() => {
this.submit({
component: this.challenge.component || "",
selectedStage: stage.pk,
});
}}
>
<div class="content">
<h1 class="pf-c-title pf-m-sm">${stage.name}</h1>
<p class="pf-c-form__helper-text">${stage.verboseName}</p>
</div>
</button>`;
},
);
return html`<fieldset class="pf-c-form__group pf-m-action" name="stages">
<legend class="sr-only">${msg("Select a configuration stage")}</legend>

View File

@@ -24,6 +24,7 @@ import { msg, str } from "@lit/localize";
import { css, CSSResult, html, nothing, PropertyValues, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { createRef, ref } from "lit/directives/ref.js";
import { repeat } from "lit/directives/repeat.js";
import PFAlert from "@patternfly/patternfly/components/Alert/alert.css";
import PFButton from "@patternfly/patternfly/components/Button/button.css";
@@ -286,24 +287,33 @@ export class IdentificationStage extends BaseStage<
}
renderFooter() {
if (!this.challenge?.enrollUrl && !this.challenge?.recoveryUrl) {
const { enrollUrl, recoveryUrl } = this.challenge || {};
const enrollmentItem = enrollUrl
? html`<div class="pf-c-login__main-footer-band-item">
${msg("Need an account?")}
<a id="enroll" href="${enrollUrl}">${msg("Sign up.")}</a>
</div>`
: null;
const recoveryItem = recoveryUrl
? html`<div class="pf-c-login__main-footer-band-item">
<a id="recovery" href="${recoveryUrl}">${msg("Forgot username or password?")}</a>
</div>`
: null;
if (!enrollmentItem && !recoveryItem) {
return nothing;
}
return html`<div slot="footer-band" class="pf-c-login__main-footer-band">
${this.challenge.enrollUrl
? html`<p class="pf-c-login__main-footer-band-item">
${msg("Need an account?")}
<a id="enroll" href="${this.challenge.enrollUrl}">${msg("Sign up.")}</a>
</p>`
: nothing}
${this.challenge.recoveryUrl
? html`<p class="pf-c-login__main-footer-band-item">
<a id="recovery" href="${this.challenge.recoveryUrl}"
>${msg("Forgot username or password?")}</a
>
</p>`
: nothing}
</div>`;
return html`<fieldset
slot="footer-band"
part="additional-actions"
class="pf-c-login__main-footer-band"
>
<legend class="sr-only">${msg("Additional actions")}</legend>
${enrollmentItem} ${recoveryItem}
</fieldset>`;
}
renderInput(): TemplateResult {
@@ -404,7 +414,7 @@ export class IdentificationStage extends BaseStage<
}
render(): TemplateResult {
return html`<ak-flow-card .challenge=${this.challenge}>
return html`<ak-flow-card .challenge=${this.challenge} part="flow-card">
<form class="pf-c-form" @submit=${this.submitForm}>
${this.challenge.applicationPre
? html`<p>
@@ -425,13 +435,15 @@ export class IdentificationStage extends BaseStage<
`
: nothing}
</form>
${(this.challenge.sources || []).length > 0
${this.challenge.sources?.length
? html`<ul slot="footer" class="pf-c-login__main-footer-links">
${(this.challenge.sources || []).map((source) => {
return this.renderSource(source);
})}
${repeat(
this.challenge.sources,
(source) => source.name,
(source) => this.renderSource(source),
)}
</ul> `
: nothing}
: null}
${this.renderFooter()}
</ak-flow-card>`;
}

View File

@@ -10,7 +10,7 @@ import { PasswordManagerPrefill } from "#flow/stages/identification/Identificati
import { PasswordChallenge, PasswordChallengeResponseRequest } from "@goauthentik/api";
import { msg } from "@lit/localize";
import { CSSResult, html, nothing, TemplateResult } from "lit";
import { CSSResult, html, TemplateResult } from "lit";
import { customElement } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
@@ -79,12 +79,17 @@ export class PasswordStage extends BaseStage<PasswordChallenge, PasswordChalleng
</fieldset>
</form>
${this.challenge.recoveryUrl
? html`<div slot="footer-band" class="pf-c-login__main-footer-band">
<p class="pf-c-login__main-footer-band-item">
<a href="${this.challenge.recoveryUrl}"> ${msg("Forgot password?")}</a>
</p>
</div>`
: nothing}
? html`<fieldset
slot="footer-band"
part="additional-actions"
class="pf-c-login__main-footer-band"
>
<legend class="sr-only">${msg("Additional actions")}</legend>
<div class="pf-c-login__main-footer-band-item">
<a href="${this.challenge.recoveryUrl}">${msg("Forgot password?")}</a>
</div>
</fieldset>`
: null}
</ak-flow-card>`;
}
}

View File

@@ -7,8 +7,6 @@ import { msg } from "@lit/localize";
import { css, html, TemplateResult } from "lit";
import { customElement } from "lit/decorators.js";
import PFEmptyState from "@patternfly/patternfly/components/EmptyState/empty-state.css";
import PFPage from "@patternfly/patternfly/components/Page/page.css";
import PFSpinner from "@patternfly/patternfly/components/Spinner/spinner.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
@@ -16,12 +14,23 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css";
export class Loading extends AKElement {
static styles = [
PFBase,
PFPage,
PFSpinner,
PFEmptyState,
css`
:host([theme="dark"]) h1 {
color: var(--ak-dark-foreground);
:host {
position: absolute;
inset: 0;
display: flex;
flex-flow: column;
place-items: center;
justify-content: center;
text-align: center;
gap: var(--pf-global--spacer--md);
}
label {
font-size: var(--pf-global--FontSize--xl);
font-weight: var(--pf-global--FontWeight--normal);
font-family: var(--pf-global--FontFamily--heading--sans-serif);
}
`,
];
@@ -38,24 +47,17 @@ export class Loading extends AKElement {
}
render(): TemplateResult {
return html`<section
class="ak-static-page pf-c-page__main-section pf-m-no-padding-mobile pf-m-xl"
>
<div class="pf-c-empty-state" style="height: 100vh;">
<div class="pf-c-empty-state__content">
<span
class="pf-c-spinner pf-m-xl"
role="progressbar"
aria-valuetext="${msg("Loading...")}"
>
<span class="pf-c-spinner__clipper"></span>
<span class="pf-c-spinner__lead-ball"></span>
<span class="pf-c-spinner__tail-ball"></span>
</span>
<h1 class="pf-c-title pf-m-lg">${msg("Loading...")}</h1>
</div>
</div>
</section>`;
return html`<span class="pf-c-spinner pf-m-xl" aria-hidden="true">
<span class="pf-c-spinner__clipper"></span>
<span class="pf-c-spinner__lead-ball"></span>
<span class="pf-c-spinner__tail-ball"></span>
</span>
<label for="progress" class="pf-c-title pf-m-lg">${msg("Loading")}</label>
<progress
class="sr-only"
id="progress"
aria-valuetext=${msg("Please wait while the content is loading")}
></progress>`;
}
}

View File

@@ -32,7 +32,6 @@
.pf-c-card {
--pf-c-card--BoxShadow: var(--pf-global--BoxShadow--md-bottom);
--pf-c-card--BackgroundColor: var(--pf-global--BackgroundColor--150);
transition: box-shadow 150ms ease-in-out;
border: 0.5px solid var(--pf-global--BorderColor--100);