Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Commit

Permalink
Enable custom themes to theme Compound (#12240)
Browse files Browse the repository at this point in the history
* 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).
  • Loading branch information
robintown authored Feb 13, 2024
1 parent 203c15f commit 8bbad9f
Show file tree
Hide file tree
Showing 9 changed files with 77 additions and 49 deletions.
2 changes: 1 addition & 1 deletion res/css/_common.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
2 changes: 0 additions & 2 deletions res/css/views/rooms/_JumpToBottomButton.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 0 additions & 2 deletions res/css/views/rooms/_TopUnreadMessagesBar.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
11 changes: 0 additions & 11 deletions res/themes/light-custom/css/_custom.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 0 additions & 2 deletions res/themes/light/css/light.pcss
Original file line number Diff line number Diff line change
@@ -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";
Expand Down
46 changes: 36 additions & 10 deletions src/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};

/**
Expand Down Expand Up @@ -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 = [
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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");

Expand Down
3 changes: 3 additions & 0 deletions test/__snapshots__/theme-test.ts.snap
Original file line number Diff line number Diff line change
@@ -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); } }"`;
52 changes: 34 additions & 18 deletions test/theme-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
{
dataset: {
mxTheme: "light",
},
disabled: true,
href: "urlLight",
onload: (): void => void 0,
} as unknown as HTMLStyleElement,
{
dataset: {
mxTheme: "dark",
},
disabled: true,
href: "urlDark",
onload: (): void => void 0,
} as unknown as HTMLStyleElement,
];
const styles = ["light", "dark", "light-custom", "dark-custom"].map(
(theme) =>
({
dataset: {
mxTheme: theme,
},
disabled: true,
href: "fake URL",
onload: (): void => void 0,
}) 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);
Expand Down Expand Up @@ -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", () => {
Expand Down
6 changes: 3 additions & 3 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down

0 comments on commit 8bbad9f

Please sign in to comment.