/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import {
html,
LitElement,
classMap,
} from "chrome://global/content/vendor/lit.all.mjs";
import { storybookTables, variableLookupTable } from "./tokens-storybook.mjs";
import styles from "./tokens-table.css";
export default {
title: "Docs/Tokens Table",
parameters: {
options: {
showPanel: false,
},
},
};
const HCM_MAP = {
ActiveText: "#8080FF",
ButtonBorder: "#000000",
ButtonFace: "#000000",
ButtonText: "#FFEE32",
Canvas: "#000000",
CanvasText: "#ffffff",
Field: "#000000",
FieldText: "#ffffff",
GrayText: "#A6A6A6",
Highlight: "#D6B4FD",
HighlightText: "#2B2B2B",
LinkText: "#8080FF",
Mark: "#000000",
MarkText: "#000000",
SelectedItem: "#D6B4FD",
SelectedItemText: "#2B2B2B",
AccentColor: "8080FF",
AccentColorText: "#2B2B2B",
VisitedText: "#8080FF",
};
const THEMED_TABLES = [
"attention-dot",
"background-color",
"border",
"border-color",
"opacity",
"text-color",
"color",
"outline",
"icon-color",
"link",
];
/**
*
*/
class TablesPage extends LitElement {
#query = "";
static properties = {
surface: { type: String, state: true },
tokensData: { type: Object, state: true },
filteredTokens: { type: Object, state: true },
};
constructor() {
super();
this.surface = "brand";
this.tokensData = storybookTables;
}
handleSurfaceChange(e) {
this.surface = e.target.value;
}
handleInput(e) {
this.#query = e.originalTarget.value.trim().toLowerCase();
e.preventDefault();
this.handleSearch();
}
debounce(fn, delayMs = 300) {
let timeout;
return function () {
clearTimeout(timeout);
timeout = setTimeout(() => {
fn.apply(this, arguments);
}, delayMs);
};
}
handleSearch() {
// Clear filteredTokens to show all data.
if (!this.#query) {
this.filteredTokens = null;
}
let filteredTokens = Object.entries(this.tokensData).reduce(
(acc, [key, tokens]) => {
if (key.includes(this.#query)) {
return { ...acc, [key]: tokens };
}
let filteredItems = tokens.filter(({ name: tokenName, value }) =>
this.filterTokens(this.#query, tokenName, value)
);
if (filteredItems.length) {
return { ...acc, [key]: filteredItems };
}
return acc;
},
{}
);
this.filteredTokens = filteredTokens;
}
filterTokens(searchTerm, tokenName, tokenVal) {
if (
tokenName.includes(searchTerm) ||
(typeof tokenVal == "string" && tokenVal.includes(searchTerm))
) {
return true;
}
if (typeof tokenVal == "object") {
return Object.entries(tokenVal).some(([key, val]) =>
this.filterTokens(searchTerm, key, val)
);
}
return false;
}
handleClearSearch(e) {
this.#query = "";
e.preventDefault();
this.handleSearch();
}
render() {
if (!this.tokensData) {
return "";
}
return html`
${Object.entries(this.filteredTokens ?? this.tokensData).map(
([tableName, tableEntries]) => {
return html`
`;
}
)}
`;
}
}
customElements.define("tables-page", TablesPage);
/**
*
*/
class TokensTable extends LitElement {
TEMPLATES = {
"font-size": this.fontTemplate,
"font-weight": this.fontTemplate,
"icon-color": this.iconTemplate,
"icon-size": this.iconTemplate,
link: this.linkTemplate,
margin: this.spaceAndSizeTemplate,
"min-height": this.spaceAndSizeTemplate,
outline: this.outlineTemplate,
padding: this.paddingTemplate,
size: this.spaceAndSizeTemplate,
space: this.spaceAndSizeTemplate,
"text-color": this.fontTemplate,
};
static properties = {
name: { type: String },
tokens: { type: Array },
surface: { type: String },
};
createRenderRoot() {
return this;
}
getDisplayValues(theme, token) {
let value = this.getResolvedValue(theme, token);
let raw = this.getRawValue(theme, value);
return { value, ...(raw !== value && { raw }) };
}
// Return the value with variable references.
// e.g. var(--color-white)
getResolvedValue(theme, token) {
if (typeof token == "string" || typeof token == "number") {
return token;
}
if (theme == "hcm") {
return (
token.forcedColors ||
token.prefersContrast ||
token[this.surface]?.default ||
token.default
);
}
if (token[this.surface]) {
return this.getResolvedValue(theme, token[this.surface]);
}
if (token[theme]) {
return token[theme];
}
return token.default;
}
// Return the value with any variables replaced by what they represent.
// e.g. #ffffff
getRawValue(theme, token) {
let cssRegex = /(?var\(--(?[a-z-0-9,\s]+)\))/;
let matches = cssRegex.exec(token);
if (matches) {
let variableValue = variableLookupTable[matches.groups?.lookupKey];
if (typeof variableValue == "undefined") {
return token;
}
if (typeof variableValue == "object") {
variableValue = this.getResolvedValue(theme, variableValue);
}
let rawValue = token.replace(matches.groups?.var, variableValue);
if (rawValue.match(cssRegex)) {
return this.getRawValue(theme, rawValue);
}
return rawValue;
}
return token;
}
getTemplate(value, tokenName, category = this.name) {
// 0 is a valid value
if (value == undefined) {
return "N/A";
}
let templateFn = this.TEMPLATES[category]?.bind(this);
if (templateFn) {
return templateFn(category, value, tokenName);
}
return html`
`;
}
outlineTemplate(_, value, tokenName) {
let property = tokenName.replaceAll(/--|focus-|-error/g, "");
if (property == "outline-inset") {
property = "outline-offset";
}
return html`
`;
}
fontTemplate(category, value) {
return html`
The quick brown fox jumps over the lazy dog
`;
}
iconTemplate(_, value, tokenName) {
let property = tokenName.includes("color") ? "background-color" : "height";
return html`
`;
}
linkTemplate(_, value, tokenName) {
let hasOutline = tokenName.includes("outline");
return html`
Click me!
`;
}
spaceAndSizeTemplate(_, value, tokenName) {
let isSize = tokenName.includes("size") || tokenName.includes("height");
let items = isSize
? html`
`
: html`
`;
return html`
${items}
`;
}
paddingTemplate(_, value) {
return html`
`;
}
getDisplayProperty(category) {
switch (category) {
case "attention-dot":
case "color":
return "background-color";
case "text-color":
return "color";
default:
return category.replace("button", "");
}
}
cellTemplate(type, tokenValue, tokenName) {
let { value, raw } = this.getDisplayValues("default", tokenValue);
return html`
${type == "preview"
? html`${this.getTemplate(raw ?? value, tokenName)}`
: html` ${value}
${raw ? html` ${raw} ` : ""}`}
|
`;
}
themeCellTemplate(theme, tokenValue, tokenName) {
let { value, raw } = this.getDisplayValues(theme, tokenValue);
return html`
${this.getTemplate(raw ?? value, tokenName)}
${value}
${raw ? html` ${raw} ` : ""}
|
`;
}
render() {
let themedTable = THEMED_TABLES.includes(this.name);
return html`
${this.name}
Name |
${themedTable
? html`
Light |
Dark |
High contrast |
`
: html`
Value |
Preview |
`}
${this.tokens.map(({ name: tokenName, value }) => {
return html`
${tokenName} |
${themedTable
? html`
${this.themeCellTemplate("light", value, tokenName)}
${this.themeCellTemplate("dark", value, tokenName)}
${this.themeCellTemplate("hcm", value, tokenName)}
`
: html`
${this.cellTemplate("value", value, tokenName)}
${this.cellTemplate("preview", value, tokenName)}
`}
`;
})}
`;
}
}
customElements.define("tokens-table", TokensTable);
export const Default = () => {
return html``;
};