From 6e7cf567db05c8f14c16a88b316bba7a02cc5cd2 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 19 May 2025 12:44:58 +0100 Subject: [PATCH] Finish theming --- modules/banner/element-web/src/Banner.tsx | 23 +++++------ modules/banner/element-web/src/config.ts | 16 +++++++- modules/banner/element-web/src/index.tsx | 16 +++++--- modules/banner/element-web/src/theme.ts | 36 +++++++++--------- .../banner/element-web/tests/banner.spec.ts | 8 +++- .../univention-nav-hover-linux.png | Bin 2719 -> 2705 bytes 6 files changed, 58 insertions(+), 41 deletions(-) diff --git a/modules/banner/element-web/src/Banner.tsx b/modules/banner/element-web/src/Banner.tsx index 27e9fae5b9..81193fbaa2 100644 --- a/modules/banner/element-web/src/Banner.tsx +++ b/modules/banner/element-web/src/Banner.tsx @@ -6,7 +6,7 @@ Please see LICENSE files in the repository root for full details. */ import { type FC } from "react"; -import styled, { ThemeProvider } from "styled-components"; +import styled from "styled-components"; import { type Api } from "@element-hq/element-web-module-api"; import { Heading } from "@vector-im/compound-web"; @@ -14,7 +14,6 @@ import { type ModuleConfig } from "./config"; import UniventionMenu from "./Univention/Menu"; import Menu from "./Menu"; import Logo from "./Logo.tsx"; -import { theme } from "./theme.ts"; const Root = styled.nav` height: ${({ theme }): string => theme.bannerHeight}; @@ -50,17 +49,15 @@ const Banner: FC = ({ api, logoUrl, href, menu }) => { } return ( - - - {menuJsx} - - - - - {api.config.get("brand")} - - - + + {menuJsx} + + + + + {api.config.get("brand")} + + ); }; diff --git a/modules/banner/element-web/src/config.ts b/modules/banner/element-web/src/config.ts index 8b6db1c122..1066a85bc2 100644 --- a/modules/banner/element-web/src/config.ts +++ b/modules/banner/element-web/src/config.ts @@ -5,7 +5,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE files in the repository root for full details. */ -import { z } from "zod"; +import { z, ZodSchema, ZodTypeDef } from "zod"; + +import { Theme } from "./theme.ts"; const StaticConfig = z.object({ type: z.literal("static"), @@ -85,15 +87,25 @@ export const ModuleConfig = z.object({ */ logo_link_url: z.string().url(), + /** + * Configuration for the menu. + */ menu: z.discriminatedUnion("type", [StaticConfig, UniventionConfig]), + + /** + * Theme variable overrides, optional. + */ + theme: Theme.default({}), }); export type ModuleConfig = z.infer; +export type ConfigSchema = ZodSchema, ZodTypeDef, z.input>; + export const CONFIG_KEY = "io.element.element-web-modules.banner"; declare module "@element-hq/element-web-module-api" { export interface Config { - [CONFIG_KEY]: ModuleConfig; + [CONFIG_KEY]: ConfigSchema["_input"]; } } diff --git a/modules/banner/element-web/src/index.tsx b/modules/banner/element-web/src/index.tsx index 54dc3a0392..f6baa9c896 100644 --- a/modules/banner/element-web/src/index.tsx +++ b/modules/banner/element-web/src/index.tsx @@ -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 { ThemeProvider } from "styled-components"; + import type { Module, Api, ModuleFactory } from "@element-hq/element-web-module-api"; import Translations from "./translations.json"; import { ModuleConfig, CONFIG_KEY } from "./config"; @@ -33,12 +35,14 @@ class BannerModule implements Module { const root = this.api.createRoot(div); root.render( - , + + + , ); } } diff --git a/modules/banner/element-web/src/theme.ts b/modules/banner/element-web/src/theme.ts index 80d2528418..aadc572c3f 100644 --- a/modules/banner/element-web/src/theme.ts +++ b/modules/banner/element-web/src/theme.ts @@ -5,26 +5,26 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE files in the repository root for full details. */ -const theme = { - textColor: "var(--cpd-color-text-primary)", - subheadingColor: "var(--cpd-color-text-secondary)", - bannerBackgroundColor: "var(--cpd-color-bg-canvas-default)", - bannerHeight: "60px", - triggerWidth: "68px", - triggerBackgroundColor: "var(--cpd-color-bg-subtle-secondary)", - triggerBackgroundColorHover: "var(--cpd-color-bg-accent-hovered)", - triggerBackgroundColorPressed: "var(--cpd-color-bg-accent-pressed)", - triggerColor: "var(--cpd-color-icon-primary)", - triggerColorContrast: "var(--cpd-color-icon-on-solid-primary)", - menuWidth: "320px", - menuBackgroundColor: "var(--cpd-color-bg-canvas-default)", - menuButtonBackgroundColorHover: "var(--cpd-color-bg-action-secondary-hovered)", - menuButtonBackgroundColorPressed: "var(--cpd-color-bg-action-secondary-pressed)", -}; +import { z } from "zod"; -type Theme = typeof theme; +export const Theme = z.object({ + textColor: z.string().default("var(--cpd-color-text-primary)"), + subheadingColor: z.string().default("var(--cpd-color-text-secondary)"), + bannerBackgroundColor: z.string().default("var(--cpd-color-bg-canvas-default)"), + bannerHeight: z.string().default("60px"), + triggerWidth: z.string().default("68px"), + triggerBackgroundColor: z.string().default("var(--cpd-color-bg-subtle-secondary)"), + triggerBackgroundColorHover: z.string().default("var(--cpd-color-bg-accent-hovered)"), + triggerBackgroundColorPressed: z.string().default("var(--cpd-color-bg-accent-pressed)"), + triggerColor: z.string().default("var(--cpd-color-icon-primary)"), + triggerColorContrast: z.string().default("var(--cpd-color-icon-on-solid-primary)"), + menuWidth: z.string().default("320px"), + menuBackgroundColor: z.string().default("var(--cpd-color-bg-canvas-default)"), + menuButtonBackgroundColorHover: z.string().default("var(--cpd-color-bg-action-secondary-hovered)"), + menuButtonBackgroundColorPressed: z.string().default("var(--cpd-color-bg-action-secondary-pressed)"), +}); -export { theme }; +export type Theme = z.infer; declare module "styled-components" { // eslint-disable-next-line @typescript-eslint/no-empty-object-type diff --git a/modules/banner/element-web/tests/banner.spec.ts b/modules/banner/element-web/tests/banner.spec.ts index ae9566ec5d..fe60dfc3a4 100644 --- a/modules/banner/element-web/tests/banner.spec.ts +++ b/modules/banner/element-web/tests/banner.spec.ts @@ -6,7 +6,7 @@ Please see LICENSE files in the repository root for full details. */ import { test as base, expect } from "../../../../playwright/element-web-test.ts"; -import { ModuleConfig } from "../src/config.ts"; +import { type ConfigSchema } from "../src/config.ts"; const test = base.extend<{ // Resolver for when to respond to the navigation.json request @@ -39,7 +39,7 @@ test.describe("Banner", () => { // We don't take a screenshot as we don't want to assert Element's styling, only our own }); - const configs: ModuleConfig[] = [ + const configs: ConfigSchema["_input"][] = [ { logo_url: "http://localhost:8080/logo.svg", logo_link_url: "https://example.com/portal", @@ -84,6 +84,10 @@ test.describe("Banner", () => { logo_url: "http://localhost:8080/opendesk/logofull.svg", ics_url: "http://localhost:8080/ics/", }, + theme: { + triggerBackgroundColorHover: "#571EFA", + triggerBackgroundColorPressed: "#4519C2", + }, }, ]; diff --git a/modules/banner/element-web/tests/snapshots/banner.spec.ts/univention-nav-hover-linux.png b/modules/banner/element-web/tests/snapshots/banner.spec.ts/univention-nav-hover-linux.png index 5cbc8775ee71225ed381a7913e7db6097b3e3b70..a72b1cedc1f8b8b4fc59513680e161814d9f0674 100644 GIT binary patch literal 2705 zcmcgu`8(U$7mlU6s8KUiJE%6*s@=D>1)(acXcR3>*SqsEz3j5V2312?Cw4FoPM{MZ8{S#QP6%WIETk2L^-CYlN#S zxO-uJW%VxTEz81ohg__Mp(@8l}m zd&y_Xc_SB_UGS`o_b!aa(3x01O3)h&x3HL6JBTtR?Zg@k7VXBYe(9$~AFzhE7r47Q z=d1#=NU3zIJ89_SfY*c&-y7w^00X%ypGx2Z$m8oEAppNk3&?tBT-E-uO)T+wc?)d9RTJrhef8a5_R<<2EOO;1#d zEM|4dh`u^al{Vbnt56dGH0u8T_tTXlEbR@$4Q%$7*S}7+mTmMG?8cnz1`-5H6X(I}c4pNtX zC)c*1lZ>z3ySYUYq!jzdZKVi72mMlJ6T1Z1wrv#7`UHpgc~nhCRpov9qH{+Y&8}kg zkLN`m3;iKEdTK|cEH>iyOOYjgdchv8CpBpjfFE_@;?Zd{y4gU z{y5RwV(c1UI`gQLyJ~>SH)h-ZYFeWV+%t_+8mZC9e*C|*I<@{TNlCqW$`}6$5OiZT zj=E5&M@roql^~Kxu6_sk!-;jafP|xqvuca$i1k9e_K_edF3FButl~H zLS6MPfkKAYTk9;y@M=LAEapXG`uNpUzcG`x>dV@fMqgG`b;*MVNB7`=@(CQ;P9`h{ z-8ZPMyC|OdN7DZ8T)I)xZ1ZTs(2wfbDnotFOgw{4r6NGwo z;nrgZD~}(pJpSI1nUckZYciN?8ueCtXfd`%!|>}&H+vXNY-|L`LC3>IOhW&WZ`G1| zU2$JRRMmIX=UXm793#J087f2jw`FWF^{AOVbuT>jh?A!udC14D+Zh?sv&{RuZf>z} zrswkv)TYr5VT)1PMnnOoX>RxLW==?Nx;!3RMSt-E z6Mp}p${b63XwP$_93CxY7#JSA7#I^8kPolgd(t07T;=d_O4k<6uAQ(DU2@FHI1f(8ElVVI)kr`xi|_)k!`I?c6Idfv;WB)f(5Ia9uil_fWnHdY|`$qNR^=_RMO*YHjMd^l`d zE3(?cV!7lB*nB}7$VPJZqPD8u)4E?XObf8%mE_rt*0)q{aNe$)93GJ;{D~(5ZO7}} z7GNk!2h`JJCoX~q!~bNDVWPM6xUpbx9Er-kZ=O=B$cTO_VPXvvFW$Zsic4O^vA{-z z;=YUE`t46NjtIs6;I^a~p_jxQ2)JZ=T!y=swfNi0i_K| z^aLQC#Rw=!e3&B{-(WEWf6pXglvfyw{BbXOKuhBhUWg{EjoQz1ip6%Oz zOLXy_J-SE%p{9y7<+l#cU8}RvLAfYN>%h-QozSS6 zQ(2n$p7#pyPN?33H+{TBFP~A*yG!xfF46YYe8(xC>rwo`zU>N_s&Lyheh==7@MvM- zI|7mH;7br;%>LZRu0dx{c=eNft*6PpxscUe$(LTD!Q7Q+SA33)D=ou4iyK6TYbWxx zG6em+;?~*S-guFNdFM;ZpH=jVJdn%`T}&kk$`nmeVZVED5>sptI?!6EAPPjh*}WNY zFFFjde>3x&S~^RA{=1Xwv%6uGINzDz_-`or8=X@u-Q!W;MM}y?L#&BErw?_5!%xup z&oTbs?xYL!0V3$NhKcl)lpIK2E+?}jt;ztn`1+Dlv&uh+#2qXYSs_qh!9hNpc)3p) zzrF1>a0wVx63Y5_C|}V2PC@4;DW7Z~Lw?qSy5$)tNd8VnZs0lJ63V>e?iMDEhXn?l zCU}aD{8`ZsMmYVEpFAosJ@X0;d0UhxXVAA8-2o`3eNFP8*GAMSxwkl<-B&x@0f9dD z-HQE5jL(CB)&e?tA9NBxbwPf00Gm!7YXT7TUkG*#XWqY5@B5BW03w>1Si*=mTp#=k DI2_$L delta 2459 zcmYjTc{JPE7pLoVw1}k`js7exn#4M^_Gz2wl%g3$D?(94Q$lSe_!?8Bw2T@{Z5cvR z31Z&~rA%$9y+SM{$RMpPc7D?H`{Vb{dFR~o-sgVa``q{LJvUL3Ao-^z@ZdK5`n~6w z%dD`w_eNwoR@@P_N_|}?SXoNN6+LhApd{yN&qz;*mgh}v!KD+L1*KVk2i^!se?{_b zYXf1ZDRfT1wgGqn|vz|-6FrjhtwSN@<%vX>-!RNp#Ogw1s8@c77M)mUn z_xWC7!?U}r)-SEZ79k@uRFKd6!55C=#yq&>tizK2`NgleXmAAm{+|8*#A_6V2~K(Y zz|!wd2LfRu0YW1c&|p6EA40%W)c%6F%)nPh!=|h{i6P^EO*ad(7`{I8G})MA2j`Z2 zOzI-mMIQno>e3e$oTueWOwfVjGy7Jwi;RQu7iozwAwE;h7kwVg;_|?!7*NnbVQ6?h zaZJ0%bYnpAkXc<%uZyemAUMwNgsfd1;X?oE3RUU*30oc(_So`(yE6A(~U%`nKF^AE-ybw;hV^3)=li)cXT-si8P)#g2@y7?J<%<8PS_v99g*c;zwK6ck;7V4t26q`a(7Nl0S9*$ z!1HS8hVYZTAzOMtUxH(C9~^$quVM~UFj{$qO4%7Qd77E=$tHyh+eL&&nO}v(X8PJS zG__GB^K^?3mo-_2h-7WYk!c0HWE6x|`$$EWpU6;UFx#2mFsBw$z-;= z#Gx8P2Eyv%qO<+}wk)?q{uKxi>b+_8#br$;Ppzvcmkq$#A)99iYA)GNs?){Ax?}Mw zH)Lgx^fccWJrMp@u;!kc;(a(u0DztMwbx@nQfL23Ts0?$UFU?;X=n5GSjsE$vXujE zOotB`-C+-xE(cWL;Bq*=X%HU@WV zxKS4q${@_#Iv{fpr#e2^`NM=8A%tYnF~lYYu+vrwko59PqPSDjsapCx07?QcaNtA)&p&C#e_9 zV{0I0i#Vl+xVdt@-grc}XB?rYEOuey6fH2I04;%lbGA5o#N+J=HALKc8+WvoFu-VCgE5eh6)O>T_@D8kP` zId5_)W+M}>2&4oE!3q9&K~bS@p$_kap{(HXT%(5IFm!^A$q)jLS@Ep%ZyHhwv42xp zpGr;dq2TewQK5;}^Nj{T4EuY^Moibm*Dgn2I{^`ueiRvgeO#yU#%d|0d941}Jg&l! zzBY)LEffuriwyV|SY1jKsZC8DAx81(!2tMW(YjmmB!m5JOiattIKtSNV{GMRVOd3^QxPz2) zm5fbSVR*%By|zD){P5JO!i+eAUi9oLS-+N9TWMnl`&Wa3ibp0b*%s*c-5q$hake?! ziAu7EDq<$=f3x#82lxHRe-}|ryqtzpx8Wq9>)w6imXMKmTz1uG?Z=~VL!2#b z6g^oOhX=MAbx_X+z6L4fHGZ>P{AO$8#hDCH`C?E~_4hlsdpc)s+KX|DADnL*JM#DC&y5Sl*IRI(rT5R7ULr@S z@6-i7V&R6WodHtequO$-!=X3nhn+$b7wHbQ0M=%>$%Ya@d4OHnf-8lX_8OAi(s0SH zSq!iHK`zs4Fy|5-@li_*glb#A640l4vy|S+=2hU~DC_byJ18OR=k=JN}4reDjj(1Ef8=eUPI=x60qsVF48NQ!}=?fEqTc0$J`3#&E97*gIxFD+>j};b+ zAiCZu^=&N@;e$l=Mtp_V&pGeC;AVaHBIm6(vwxolINk%tJ^f$u1y8kW2NL|j;OcbR zGIF*Cllk3ETJe=!gzL;~yt;_az=VH!D4S|}*lx^coH~WbO!_N^$Zf)o_avQ4C$`}? zEC*T>5)!m_vVHl88f4