diff --git a/apps/web/res/css/_index.pcss b/apps/web/res/css/_index.pcss index 842bb0c6b8..716430b3fc 100644 --- a/apps/web/res/css/_index.pcss +++ b/apps/web/res/css/_index.pcss @@ -10,4 +10,5 @@ Please see LICENSE files in the repository root for full details. @import url("maplibre-gl/dist/maplibre-gl.css") layer(app-web); @import url("@vector-im/compound-design-tokens/assets/web/css/compound-design-tokens.css") layer(compound-tokens); @import url("@vector-im/compound-web/dist/style.css") layer(compound-web); -@import url("@element-hq/web-shared-components/dist/element-web-shared-components.css") layer(shared-components); +/** Shared components is layer-aware and will fit itself into the correct layer */ +@import url("@element-hq/web-shared-components/dist/element-web-shared-components.css"); diff --git a/modules/banner/README.md b/modules/banner/README.md index 21955c4e63..6b85fad17a 100644 --- a/modules/banner/README.md +++ b/modules/banner/README.md @@ -23,11 +23,10 @@ The `Menu` type is fulfilled by the following discriminated union: ### Static menu -| Key | Type | Description | -| ---------- | ---------------- | ------------------------------------------------------------------------- | -| type | "static" | The type for this menu config | -| logo_url | string, optional | URL to the logo to render in the menu, defaults to banner logo if omitted | -| categories | `[]Category` | Categories to render in the menu | +| Key | Type | Description | +| ---------- | ------------ | -------------------------------- | +| type | "static" | The type for this menu config | +| categories | `[]Category` | Categories to render in the menu | The `Category` type is fulfilled by the following interface: @@ -45,6 +44,14 @@ The `Link` type is fulfilled by the following interface: | link_url | string | The URL to link to | | target | string, optional | The `target` to use for this link | +### All menus additionally support the following optional keys: + +| Key | Type | Description | +| ----------- | ---------------- | ------------------------------------------------------------------------- | +| logo_url | string, optional | URL to the logo to render in the menu, defaults to banner logo if omitted | +| logo_href | string, optional | URL to send the user to when clicking the logo in the menu | +| logo_height | number, optional | Height of the logo in pixels, defaults to 32 if omitted | + ## Copyright & License Copyright (c) 2025 New Vector Ltd diff --git a/modules/banner/e2e/banner.spec.ts b/modules/banner/e2e/banner.spec.ts index 93db62482c..ecb8c583e8 100644 --- a/modules/banner/e2e/banner.spec.ts +++ b/modules/banner/e2e/banner.spec.ts @@ -234,4 +234,46 @@ test.describe("Banner", () => { await expect(axe).toHaveNoViolations(); }); }); + + test.describe("static config", () => { + test.use({ + config: { + "io.element.element-web-modules.banner": { + logo_url: "https://domain/logo1.png", + logo_link_url: "https://domain", + title: "Title", + menu: { + type: "static", + logo_url: "https://domain/logo2.png", + logo_href: "https://domain/menu-link", + categories: [ + { + name: "Category 1", + links: [ + { + icon_uri: "https://domain/app1/logo.png", + name: "App 1", + link_url: "https://domain/app1", + }, + ], + }, + ], + }, + }, + }, + }); + + test("should show a href on the menu logo if one is specified", async ({ page }) => { + await expect(page.getByLabel("Show portal")).toHaveAttribute("href", "https://domain"); + + const trigger = page.getByLabel("Show menu"); + await trigger.click(); + + const sidebar = page.getByRole("dialog"); + await expect(sidebar.getByRole("link", { name: "Show portal" })).toHaveAttribute( + "href", + "https://domain/menu-link", + ); + }); + }); }); diff --git a/modules/banner/src/Menu.tsx b/modules/banner/src/Menu.tsx index 00cfd3178f..7afb0ca9bd 100644 --- a/modules/banner/src/Menu.tsx +++ b/modules/banner/src/Menu.tsx @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE files in the repository root for full details. */ -import { type FC, type JSX, useState } from "react"; +import { type ComponentProps, type FC, type JSX, useState } from "react"; import { AnimatePresence, motion } from "motion/react"; import * as Dialog from "@radix-ui/react-dialog"; import styled, { useTheme } from "styled-components"; @@ -179,7 +179,10 @@ const Menu: FC = ({ api, config, fallbackLogoUrl }) => { const [open, setOpen] = useState(false); let content: JSX.Element; - let logoUrl = fallbackLogoUrl; + const logoProps: Omit, "api"> = { + src: fallbackLogoUrl, + }; + if (config instanceof Error) { content = {api.i18n.translate("univention_error")}; } else if (config) { @@ -191,8 +194,10 @@ const Menu: FC = ({ api, config, fallbackLogoUrl }) => { ); if (config.logo_url) { - logoUrl = config.logo_url; + logoProps.src = config.logo_url; } + logoProps.height = config.logo_height !== undefined ? `${config.logo_height}px` : undefined; + logoProps.href = config.logo_href; } else { content = ( @@ -231,7 +236,7 @@ const Menu: FC = ({ api, config, fallbackLogoUrl }) => { > - + ; -const UniventionConfig = z.object({ +const UniventionConfig = z.extend(MenuConfig, { type: z.literal("univention"), - /** - * Alternative logo URL to display in the popover menu. - * Will use the main logo url if omitted. - */ - logo_url: z.optional(z.url()), - /** * Base URL to an Intercom Service * https://docs.software-univention.de/intercom-service/latest/architecture.html#endpoints diff --git a/modules/banner/src/index.tsx b/modules/banner/src/index.tsx index ab46be4fc8..4b65373f8c 100644 --- a/modules/banner/src/index.tsx +++ b/modules/banner/src/index.tsx @@ -6,7 +6,6 @@ Please see LICENSE files in the repository root for full details. */ import { ThemeProvider } from "styled-components"; -import compound from "@vector-im/compound-web/dist/style.css" with { type: "css" }; import type { Module, Api, ModuleFactory } from "@element-hq/element-web-module-api"; import Translations from "./translations.json"; @@ -29,7 +28,6 @@ class BannerModule implements Module { return; } - document.adoptedStyleSheets.push(compound); document.adoptedStyleSheets.push(style); this.api.i18n.register(Translations); diff --git a/modules/banner/src/style.css b/modules/banner/src/style.css index 6e573485f5..29a08db118 100644 --- a/modules/banner/src/style.css +++ b/modules/banner/src/style.css @@ -5,6 +5,8 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE files in the repository root for full details. */ +@import "@vector-im/compound-web/dist/style.css" layer(compound-layer); + /* Styles to ensure the banner does not push the app out of the viewport */ body { display: flex; diff --git a/modules/restricted-guests/src/index.tsx b/modules/restricted-guests/src/index.tsx index e165a2d448..09b051ba69 100644 --- a/modules/restricted-guests/src/index.tsx +++ b/modules/restricted-guests/src/index.tsx @@ -5,14 +5,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE files in the repository root for full details. */ -import compound from "@vector-im/compound-web/dist/style.css" with { type: "css" }; - import type { Module, Api, ModuleFactory } from "@element-hq/element-web-module-api"; import Translations from "./translations.json"; import { ModuleConfig, CONFIG_KEY } from "./config"; import { name as ModuleName } from "../package.json"; import RoomPreviewBar from "./RoomPreviewBar.tsx"; import AuthFooter from "./AuthFooter.tsx"; +import style from "./style.css" with { type: "css" }; const GUEST_INVISIBLE_COMPONENTS = [ "UIComponent.sendInvites", @@ -37,7 +36,7 @@ class RestrictedGuestsModule implements Module { return; } - document.adoptedStyleSheets.push(compound); + document.adoptedStyleSheets.push(style); this.api.i18n.register(Translations); diff --git a/modules/restricted-guests/src/style.css b/modules/restricted-guests/src/style.css new file mode 100644 index 0000000000..6d53d1c890 --- /dev/null +++ b/modules/restricted-guests/src/style.css @@ -0,0 +1,8 @@ +/* +Copyright 2026 Element Creations Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE files in the repository root for full details. +*/ + +@import "@vector-im/compound-web/dist/style.css" layer(compound-layer);