Enable custom themes to theme Compound (#12240)

* Enable custom themes to theme Compound

* Remove the now redundant username color variables

They are replaced by the Compound theming options (specifically, username colors can be themed by changing the color of Compound's decorative color tokens).
pull/28217/head
Robin 2024-02-13 09:07:58 -05:00 committed by GitHub
parent 203c15f205
commit 8bbad9f653
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 77 additions and 49 deletions

View File

@ -17,7 +17,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
@import url("@vector-im/compound-design-tokens/assets/web/css/compound-design-tokens.css");
@import url("@vector-im/compound-design-tokens/assets/web/css/compound-design-tokens.css") layer(compound);
@import url("@vector-im/compound-web/dist/style.css");
@import "./_font-sizes.pcss";
@import "./_animations.pcss";

View File

@ -14,8 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
@charset "utf-8";
.mx_JumpToBottomButton {
z-index: 1000;
position: absolute;

View File

@ -14,8 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
@charset "utf-8";
.mx_TopUnreadMessagesBar {
z-index: 1000;
position: absolute;

View File

@ -32,9 +32,6 @@ $background: var(--background, $background);
$panels: var(--panels, var(--cpd-color-gray-600));
$panel-actions: var(--panels-actions, var(--cpd-color-gray-300));
/* --accent-color */
$username-variant3-color: var(--accent-color);
/* --timeline-background-color */
$button-secondary-bg-color: var(--timeline-background-color);
$lightbox-border-color: var(--timeline-background-color);
@ -110,14 +107,6 @@ $accent-alt: var(--primary-color);
/* --warning-color */
$button-danger-disabled-bg-color: var(--warning-color-50pct); /* still needs alpha at 0.5 */
/* --username colors (which use a 0-based index) */
$username-variant1-color: var(--username-colors_0, $username-variant1-color);
$username-variant2-color: var(--username-colors_1, $username-variant2-color);
$username-variant3-color: var(--username-colors_2, $username-variant3-color);
$username-variant4-color: var(--username-colors_3, $username-variant4-color);
$username-variant5-color: var(--username-colors_4, $username-variant5-color);
$username-variant6-color: var(--username-colors_5, $username-variant6-color);
/* --timeline-highlights-color */
$event-selected-color: var(--timeline-highlights-color);
$event-highlight-bg-color: var(--timeline-highlights-color);

View File

@ -1,5 +1,3 @@
@import url("@vector-im/compound-design-tokens/assets/web/css/compound-design-tokens.css");
@import "../../../../res/css/_font-sizes.pcss";
@import "_paths.pcss";
@import "_fonts.pcss";

View File

@ -35,17 +35,22 @@ interface IFontFaces extends Omit<Record<(typeof allowedFontFaceProps)[number],
}[];
}
interface CompoundTheme {
[token: string]: string;
}
export type CustomTheme = {
name: string;
colors: {
is_dark?: boolean; // eslint-disable-line camelcase
colors?: {
[key: string]: string;
};
fonts: {
fonts?: {
faces: IFontFaces[];
general: string;
monospace: string;
};
is_dark?: boolean; // eslint-disable-line camelcase
compound?: CompoundTheme;
};
/**
@ -120,10 +125,10 @@ function clearCustomTheme(): void {
document.body.style.removeProperty(prop);
}
}
const customFontFaceStyle = document.querySelector("head > style[title='custom-theme-font-faces']");
if (customFontFaceStyle) {
customFontFaceStyle.remove();
}
// remove the custom style sheets
document.querySelector("head > style[title='custom-theme-font-faces']")?.remove();
document.querySelector("head > style[title='custom-theme-compound']")?.remove();
}
const allowedFontFaceProps = [
@ -177,6 +182,22 @@ function generateCustomFontFaceCSS(faces: IFontFaces[]): string {
.join("\n");
}
const COMPOUND_TOKEN = /^--cpd-[a-z0-9-]+$/;
/**
* Generates a style sheet to override Compound design tokens as specified in
* the given theme.
*/
function generateCustomCompoundCSS(theme: CompoundTheme): string {
const properties: string[] = [];
for (const [token, value] of Object.entries(theme))
if (COMPOUND_TOKEN.test(token)) properties.push(`${token}: ${value};`);
else logger.warn(`'${token}' is not a valid Compound token`);
// Insert the design token overrides into the 'custom' cascade layer as
// documented at https://compound.element.io/?path=/docs/develop-theming--docs
return `@layer compound.custom { :root, [class*="cpd-theme-"] { ${properties.join(" ")} } }`;
}
function setCustomThemeVars(customTheme: CustomTheme): void {
const { style } = document.body;
@ -218,6 +239,14 @@ function setCustomThemeVars(customTheme: CustomTheme): void {
style.setProperty("--font-family-monospace", fonts.monospace);
}
}
if (customTheme.compound) {
const css = generateCustomCompoundCSS(customTheme.compound);
const style = document.createElement("style");
style.setAttribute("title", "custom-theme-compound");
style.setAttribute("type", "text/css");
style.appendChild(document.createTextNode(css));
document.head.appendChild(style);
}
}
export function getCustomTheme(themeName: string): CustomTheme {
@ -284,9 +313,6 @@ export async function setTheme(theme?: string): Promise<void> {
* Adds the Compound theme class to the top-most element in the document
* This will automatically refresh the colour scales based on the OS or user
* preferences
*
* Note: Theming through Compound is not yet established. Brand theming should
* be done in a similar manner as it used to be done.
*/
document.body.classList.remove("cpd-theme-light", "cpd-theme-dark", "cpd-theme-light-hc", "cpd-theme-dark-hc");

View File

@ -0,0 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`theme setTheme applies a custom Compound theme 1`] = `"@layer compound.custom { :root, [class*="cpd-theme-"] { --cpd-color-icon-accent-tertiary: var(--cpd-color-blue-800); --cpd-color-text-action-accent: var(--cpd-color-blue-900); } }"`;

View File

@ -21,31 +21,26 @@ describe("theme", () => {
describe("setTheme", () => {
let lightTheme: HTMLStyleElement;
let darkTheme: HTMLStyleElement;
let lightCustomTheme: HTMLStyleElement;
let spyQuerySelectorAll: jest.MockInstance<NodeListOf<Element>, [selectors: string]>;
let spyClassList: jest.SpyInstance<void, string[], any>;
beforeEach(() => {
const styles = [
{
const styles = ["light", "dark", "light-custom", "dark-custom"].map(
(theme) =>
({
dataset: {
mxTheme: "light",
mxTheme: theme,
},
disabled: true,
href: "urlLight",
href: "fake URL",
onload: (): void => void 0,
} as unknown as HTMLStyleElement,
{
dataset: {
mxTheme: "dark",
},
disabled: true,
href: "urlDark",
onload: (): void => void 0,
} as unknown as HTMLStyleElement,
];
}) as unknown as HTMLStyleElement,
);
lightTheme = styles[0];
darkTheme = styles[1];
lightCustomTheme = styles[2];
jest.spyOn(document.body, "style", "get").mockReturnValue([] as any);
spyQuerySelectorAll = jest.spyOn(document, "querySelectorAll").mockReturnValue(styles as any);
@ -124,6 +119,27 @@ describe("theme", () => {
jest.advanceTimersByTime(200 * 10);
});
});
it("applies a custom Compound theme", async () => {
jest.spyOn(SettingsStore, "getValue").mockReturnValue([
{
name: "blue",
compound: {
"--cpd-color-icon-accent-tertiary": "var(--cpd-color-blue-800)",
"--cpd-color-text-action-accent": "var(--cpd-color-blue-900)",
},
},
]);
const spy = jest.spyOn(document.head, "appendChild").mockImplementation();
await new Promise((resolve) => {
setTheme("custom-blue").then(resolve);
lightCustomTheme.onload!({} as Event);
});
expect(spy).toHaveBeenCalled();
expect(spy.mock.calls[0][0].textContent).toMatchSnapshot();
spy.mockRestore();
});
});
describe("enumerateThemes", () => {

View File

@ -3123,9 +3123,9 @@
integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==
"@vector-im/compound-design-tokens@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@vector-im/compound-design-tokens/-/compound-design-tokens-1.0.0.tgz#4fe7744bbe0bd093b064d42ca8bb475862bb2ce7"
integrity sha512-/hKAxE/WsmnNZamlSmLoFeAhNDhRpFdJYuY8NrPLaS/dKS/QRnty6UYzs9yWOVNFeiBfkNsrb7wYIFMrYWSRJw==
version "1.1.0"
resolved "https://registry.yarnpkg.com/@vector-im/compound-design-tokens/-/compound-design-tokens-1.1.0.tgz#9b1a91317c404a1cd0d76d2fd5a7f2df5f1bf0a6"
integrity sha512-1HcCm6YsOda98rGXO4fg0WjEdrMnx/0tdtFmYIlnYkDYTbnfpFg+ffIDY7jgammWbOYwUZpZhM5q9ofb7/EgkA==
dependencies:
svg2vectordrawable "^2.9.1"