diff --git a/web/src/admin/AdminInterface/index.entrypoint.ts b/web/src/admin/AdminInterface/index.entrypoint.ts index 6f09c4926431..1cfe1e850580 100644 --- a/web/src/admin/AdminInterface/index.entrypoint.ts +++ b/web/src/admin/AdminInterface/index.entrypoint.ts @@ -30,16 +30,15 @@ import { renderNotificationDrawerPanel, } from "#elements/notifications/utils"; -import { PageNavMenuToggle } from "#components/ak-page-navbar"; - import type { AboutModal } from "#admin/AdminInterface/AboutModal"; import Styles from "#admin/AdminInterface/index.entrypoint.css"; import { ROUTES } from "#admin/Routes"; import { CapabilitiesEnum } from "@goauthentik/api"; +import { msg } from "@lit/localize"; import { CSSResult, html, nothing, PropertyValues, TemplateResult } from "lit"; -import { customElement, property, query, state } from "lit/decorators.js"; +import { customElement, eventOptions, property, query, state } from "lit/decorators.js"; import { classMap } from "lit/directives/class-map.js"; import PFButton from "@patternfly/patternfly/components/Button/button.css"; @@ -73,19 +72,30 @@ export class AdminInterface extends WithCapabilitiesConfig( @query("ak-about-modal") public aboutModal?: AboutModal; - @property({ type: Boolean, reflect: true }) + @property({ type: Boolean, reflect: true, attribute: "sidebar" }) public sidebarOpen = false; - #onPageNavMenuEvent = (event: PageNavMenuToggle) => { - this.sidebarOpen = event.open; + //#endregion + + //#region Public Methods + + public toggleSidebar = () => { + this.sidebarOpen = !this.sidebarOpen; }; + //#endregion + + //#region Lifecycle + #sidebarMatcher: MediaQueryList; #sidebarMediaQueryListener = (event: MediaQueryListEvent) => { this.sidebarOpen = event.matches; }; - //#endregion + @eventOptions({ passive: true }) + protected routeChangeListener() { + this.sidebarOpen = this.#sidebarMatcher.matches; + } @state() protected drawer: DrawerState = readDrawerParams(); @@ -96,8 +106,6 @@ export class AdminInterface extends WithCapabilitiesConfig( persistDrawerParams(event.drawer); }; - //#region Lifecycle - constructor() { configureSentry(); @@ -105,12 +113,8 @@ export class AdminInterface extends WithCapabilitiesConfig( WebsocketClient.connect(); - this.#sidebarMatcher = window.matchMedia("(min-width: 1200px)"); + this.#sidebarMatcher = window.matchMedia("(width >= 1200px)"); this.sidebarOpen = this.#sidebarMatcher.matches; - - this.addEventListener(PageNavMenuToggle.eventName, this.#onPageNavMenuEvent, { - passive: true, - }); } public connectedCallback() { @@ -139,7 +143,11 @@ export class AdminInterface extends WithCapabilitiesConfig( } } - render(): TemplateResult { + //#endregion + + //#region Rendering + + protected override render(): TemplateResult { if (!isAPIResultReady(this.session) || !canAccessAdmin(this.session.user)) { return html``; } @@ -158,6 +166,19 @@ export class AdminInterface extends WithCapabilitiesConfig( return html`
+ + @@ -181,6 +202,7 @@ export class AdminInterface extends WithCapabilitiesConfig( id="main-content" defaultUrl="/administration/overview" .routes=${ROUTES} + @ak-route-change=${this.routeChangeListener} >
@@ -189,9 +211,19 @@ export class AdminInterface extends WithCapabilitiesConfig( + +
`; } + + //#endregion } declare global { diff --git a/web/src/components/ak-page-navbar.css b/web/src/components/ak-page-navbar.css new file mode 100644 index 000000000000..8d90e69ba075 --- /dev/null +++ b/web/src/components/ak-page-navbar.css @@ -0,0 +1,190 @@ +:host { + --pf-c-page__header-tools--MarginRight: 0; + --ak-c-page-navbar__brand-logo--Height: var(--pf-global--FontSize--4xl, 2.25rem); + --ak-c-page-navbar__brand---BackgroundColor: var(--pf-c-page__sidebar--BackgroundColor); + --ak-c-page-navbar--Height: 7.5rem; +} + +:host([theme="dark"]) { + --ak-c-page-navbar__brand---BackgroundColor: var(--pf-c-page__sidebar--BackgroundColor); +} + +.main-content { + border-bottom-width: 0.5px; + border-bottom-style: solid; + border-bottom-color: var(--pf-global--BorderColor--100); + background-color: var(--pf-c-page__main-nav--BackgroundColor); + display: flex; + height: var(--ak-c-page-navbar--Height); + + flex-direction: row; + box-shadow: var(--pf-global--BoxShadow--sm-bottom); + + display: grid; + column-gap: var(--pf-global--spacer--md); + + grid-template-columns: [brand] auto [toggle] auto [primary] 1fr [secondary] auto; + grid-template-rows: auto auto; + grid-template-areas: + "brand toggle primary secondary" + "brand toggle description secondary"; + + @media (width <= 425px) { + /* Fix for mobile flickering between elements with box shadows. */ + border-bottom: none; + } + @media (width <= 767px) { + --ak-c-page-navbar--Height: auto; + } + + @media (width <= 767px) { + row-gap: var(--pf-global--spacer--xs); + + align-items: center; + grid-template-areas: + "toggle primary secondary" + "toggle description description"; + justify-content: space-between; + width: 100%; + } + + @media (width < 1200px) { + column-gap: calc(var(--pf-global--spacer--md) / 2); + } +} + +.items { + display: block; + + &.primary { + grid-column: primary; + grid-row: primary / description; + + align-self: center; + padding-block: var(--pf-global--spacer--md); + + @media (width <= 768px) { + padding-block: var(--pf-global--spacer--sm); + } + + &.block-sibling { + } + + @media (width >= 425px) { + &.block-sibling { + align-self: end; + padding-block-end: 0; + grid-row: primary; + } + } + + .accent-icon { + height: 1.2em; + width: 1em; + + @media (width <= 768px) { + display: none; + } + } + } + + &.page-description { + padding-top: 0.3em; + grid-area: description; + margin-block-end: var(--pf-global--spacer--md); + + display: box; + display: -webkit-box; + line-clamp: 2; + -webkit-line-clamp: 2; + box-orient: vertical; + -webkit-box-orient: vertical; + overflow: hidden; + + @media (width <= 425px) { + display: none; + } + + @media (width >= 769px) { + text-wrap: balance; + } + } + + &.secondary { + grid-area: secondary; + flex: 0 0 auto; + justify-self: end; + padding-block: var(--pf-global--spacer--sm); + padding-inline-end: var(--pf-global--spacer--sm); + + @media (width >= 769px) { + align-content: center; + padding-block: var(--pf-global--spacer--md); + padding-inline-end: var(--pf-global--spacer--xl); + } + } +} + +.brand { + grid-area: brand; + background-color: var(--ak-c-page-navbar__brand---BackgroundColor); + height: 100%; + width: var(--pf-c-page__sidebar--Width); + align-items: center; + padding-inline: var(--pf-global--spacer--sm); + + display: flex; + justify-content: center; + + &.pf-m-collapsed { + display: none; + } + + @media (width < 1200px) { + display: none; + } +} + +.logo { + flex: 0 0 auto; + height: var(--ak-c-page-navbar__brand-logo--Height); + + & img { + height: 100%; + } + + & i { + font-size: var(--ak-c-page-navbar__brand-logo--Height); + height: var(--ak-c-page-navbar__brand-logo--Height); + line-height: var(--ak-c-page-navbar__brand-logo--Height); + } +} + +::slotted([slot="toggle"]) { + --pf-c-button--FontSize: var(--pf-global--FontSize--2xl) !important; + height: 100%; + + grid-area: toggle; +} + +@media (width >= 1200px) { + slot[name="toggle"] { + display: none; + } +} + +.pf-c-content .page-title { + display: box; + display: -webkit-box; + line-clamp: 2; + -webkit-line-clamp: 2; + box-orient: vertical; + -webkit-box-orient: vertical; + overflow: hidden; +} + +h1 { + display: flex; + flex-direction: row; + align-items: center !important; +} diff --git a/web/src/components/ak-page-navbar.ts b/web/src/components/ak-page-navbar.ts index 2791a83b9c9a..0202281b6b8f 100644 --- a/web/src/components/ak-page-navbar.ts +++ b/web/src/components/ak-page-navbar.ts @@ -9,9 +9,12 @@ import { WithSession } from "#elements/mixins/session"; import { isAdminRoute } from "#elements/router/utils"; import { ThemedImage } from "#elements/utils/images"; +import Styles from "#components/ak-page-navbar.css"; + import { msg } from "@lit/localize"; -import { css, CSSResult, html, nothing, TemplateResult } from "lit"; -import { customElement, property, state } from "lit/decorators.js"; +import { CSSResult, html, nothing, TemplateResult } from "lit"; +import { customElement, state } from "lit/decorators.js"; +import { guard } from "lit/directives/guard.js"; import PFAvatar from "@patternfly/patternfly/components/Avatar/avatar.css"; import PFButton from "@patternfly/patternfly/components/Button/button.css"; @@ -20,7 +23,6 @@ import PFDrawer from "@patternfly/patternfly/components/Drawer/drawer.css"; import PFDropdown from "@patternfly/patternfly/components/Dropdown/dropdown.css"; import PFNotificationBadge from "@patternfly/patternfly/components/NotificationBadge/notification-badge.css"; import PFPage from "@patternfly/patternfly/components/Page/page.css"; -import PFBase from "@patternfly/patternfly/patternfly-base.css"; export class PageDetailsUpdate extends Event { static readonly eventName = "ak-page-details-update"; @@ -32,16 +34,6 @@ export class PageDetailsUpdate extends Event { } } -export class PageNavMenuToggle extends Event { - static readonly eventName = "ak-page-nav-menu-toggle"; - open: boolean; - - constructor(open?: boolean) { - super(PageNavMenuToggle.eventName, { bubbles: true, composed: true }); - this.open = !!open; - } -} - export function setPageDetails(header: PageHeaderInit) { window.dispatchEvent(new PageDetailsUpdate(header)); } @@ -71,205 +63,14 @@ export class AKPageNavbar //#region Static Properties static styles: CSSResult[] = [ - PFBase, PFButton, PFPage, PFDrawer, - PFNotificationBadge, PFContent, PFAvatar, PFDropdown, - css` - :host { - position: sticky; - top: 0; - z-index: var(--pf-c-page__header--ZIndex); - --pf-c-page__header-tools--MarginRight: 0; - --ak-brand-logo-height: var(--pf-global--FontSize--4xl, 2.25rem); - --ak-brand-background-color: var(--pf-c-page__sidebar--BackgroundColor); - --host-navbar-height: var(--ak-c-page-header--height, 7.5rem); - } - - :host([theme="dark"]) { - --ak-brand-background-color: var(--pf-c-page__sidebar--BackgroundColor); - - .sidebar-trigger, - .notification-trigger { - background-color: transparent !important; - } - } - - .main-content { - border-bottom-width: 0.5px; - border-bottom-style: solid; - border-bottom-color: var(--pf-global--BorderColor--100); - background-color: var(--pf-c-page__main-nav--BackgroundColor); - display: flex; - flex-direction: row; - box-shadow: var(--pf-global--BoxShadow--sm-bottom); - - display: grid; - column-gap: var(--pf-global--spacer--sm); - grid-template-columns: [brand] auto [toggle] auto [primary] 1fr [secondary] auto; - grid-template-rows: auto auto; - grid-template-areas: - "brand toggle primary secondary" - "brand toggle description secondary"; - - @media (min-width: 769px) { - height: var(--host-navbar-height); - } - - @media (max-width: 768px) { - row-gap: var(--pf-global--spacer--xs); - - align-items: center; - grid-template-areas: - "toggle primary secondary" - "toggle description description"; - justify-content: space-between; - width: 100%; - } - } - - .items { - display: block; - - &.primary { - grid-column: primary; - grid-row: primary / description; - - align-self: center; - padding-block: var(--pf-global--spacer--md); - - @media (max-width: 768px) { - padding-block: var(--pf-global--spacer--sm); - } - - &.block-sibling { - align-self: end; - } - - @media (min-width: 426px) { - &.block-sibling { - padding-block-end: 0; - grid-row: primary; - } - } - - .accent-icon { - height: 1.2em; - width: 1em; - - @media (max-width: 768px) { - display: none; - } - } - } - - &.page-description { - padding-top: 0.3em; - grid-area: description; - margin-block-end: var(--pf-global--spacer--md); - - display: box; - display: -webkit-box; - line-clamp: 2; - -webkit-line-clamp: 2; - box-orient: vertical; - -webkit-box-orient: vertical; - overflow: hidden; - - @media (max-width: 425px) { - display: none; - } - - @media (min-width: 769px) { - text-wrap: balance; - } - } - - &.secondary { - grid-area: secondary; - flex: 0 0 auto; - justify-self: end; - padding-block: var(--pf-global--spacer--sm); - padding-inline-end: var(--pf-global--spacer--sm); - - @media (min-width: 769px) { - align-content: center; - padding-block: var(--pf-global--spacer--md); - padding-inline-end: var(--pf-global--spacer--xl); - } - } - } - - .brand { - grid-area: brand; - background-color: var(--ak-brand-background-color); - height: 100%; - width: var(--pf-c-page__sidebar--Width); - align-items: center; - padding-inline: var(--pf-global--spacer--sm); - - display: flex; - justify-content: center; - - &.pf-m-collapsed { - display: none; - } - - @media (max-width: 1199px) { - display: none; - } - } - - .sidebar-trigger { - grid-area: toggle; - height: 100%; - } - - .logo { - flex: 0 0 auto; - height: var(--ak-brand-logo-height); - - & img { - height: 100%; - } - - & i { - font-size: var(--ak-brand-logo-height); - height: var(--ak-brand-logo-height); - line-height: var(--ak-brand-logo-height); - } - } - - .sidebar-trigger, - .notification-trigger { - font-size: 1.5rem; - } - - .notification-trigger.has-notifications { - color: var(--pf-global--active-color--100); - } - - .pf-c-content .page-title { - display: box; - display: -webkit-box; - line-clamp: 2; - -webkit-line-clamp: 2; - box-orient: vertical; - -webkit-box-orient: vertical; - overflow: hidden; - } - - h1 { - display: flex; - flex-direction: row; - align-items: center !important; - } - `, + Styles, ]; //#endregion @@ -291,9 +92,6 @@ export class AKPageNavbar @state() hasIcon = true; - @property({ type: Boolean, reflect: true }) - public open?: boolean; - //#endregion //#region Private Methods @@ -311,11 +109,6 @@ export class AKPageNavbar document.title = title; } - #toggleSidebar() { - this.open = !this.open; - this.dispatchEvent(new PageNavMenuToggle(!!this.open)); - } - //#endregion //#region Event Handlers @@ -353,28 +146,32 @@ export class AKPageNavbar //#region Render - renderIcon() { - if (this.icon) { - if (this.iconImage && !this.icon.startsWith("fa://")) { - return html``; - } + protected renderIcon() { + return guard([this.icon, this.iconImage], () => { + if (this.icon) { + if (this.iconImage && !this.icon.startsWith("fa://")) { + return html``; + } - const icon = this.icon.replaceAll("fa://", "fa "); + const icon = this.icon.replaceAll("fa://", "fa "); - return html``; - } - return nothing; + return html``; + } + + return nothing; + }); } - render(): TemplateResult { - return html` -