Add additional configuration options to Banner module (#34384)
* Add additional configuration options to Banner module * Iterate * Iterate
This commit is contained in:
@@ -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");
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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<Props> = ({ api, config, fallbackLogoUrl }) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
let content: JSX.Element;
|
||||
let logoUrl = fallbackLogoUrl;
|
||||
const logoProps: Omit<ComponentProps<typeof Logo>, "api"> = {
|
||||
src: fallbackLogoUrl,
|
||||
};
|
||||
|
||||
if (config instanceof Error) {
|
||||
content = <CentredContainer>{api.i18n.translate("univention_error")}</CentredContainer>;
|
||||
} else if (config) {
|
||||
@@ -191,8 +194,10 @@ const Menu: FC<Props> = ({ 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 = (
|
||||
<CentredContainer>
|
||||
@@ -231,7 +236,7 @@ const Menu: FC<Props> = ({ api, config, fallbackLogoUrl }) => {
|
||||
>
|
||||
<Dialog.Title>
|
||||
<SidebarHeading>
|
||||
<Logo api={api} src={logoUrl} />
|
||||
<Logo {...logoProps} api={api} />
|
||||
<Dialog.Close asChild>
|
||||
<CloseButton
|
||||
aria-label={api.i18n.translate("close_label")}
|
||||
|
||||
@@ -11,15 +11,28 @@ import { Theme } from "./theme.ts";
|
||||
|
||||
z.config(z.locales.en());
|
||||
|
||||
const StaticConfig = z.object({
|
||||
type: z.literal("static"),
|
||||
|
||||
const MenuConfig = z.object({
|
||||
/**
|
||||
* Alternative logo URL to display in the popover menu.
|
||||
* Will use the main logo url if omitted.
|
||||
*/
|
||||
logo_url: z.optional(z.url()),
|
||||
|
||||
/**
|
||||
* Height in pixels to use when rendering the logo in the menu, defaults to 32px.
|
||||
* The width will be set automatically to maintain the original aspect ratio.
|
||||
*/
|
||||
logo_height: z.optional(z.number()),
|
||||
|
||||
/**
|
||||
* Optional link href for the logo in the menu.
|
||||
*/
|
||||
logo_href: z.optional(z.url()),
|
||||
});
|
||||
|
||||
const StaticConfig = z.extend(MenuConfig, {
|
||||
type: z.literal("static"),
|
||||
|
||||
/**
|
||||
* Categories of links to display in the menu.
|
||||
*/
|
||||
@@ -58,15 +71,9 @@ const StaticConfig = z.object({
|
||||
|
||||
export type StaticConfig = z.infer<typeof StaticConfig>;
|
||||
|
||||
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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
Reference in New Issue
Block a user