@devoinc/genesys-brand-devo
Version:
Devo brand tokens
1,133 lines (1,033 loc) • 39 kB
HTML
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Design Tokens</title>
<link href='https://fonts.googleapis.com/css?family=Poppins:400,500,600' rel='stylesheet'>
<style>
:root {
--box-shadow-size: 6px;
--color-heading: #1f282e;
--color-body: #5b6870;
--color-strong: #3c4952;
--color-weak: #81919c;
--fs-base: 1.4rem;
--fs-sm: 1.3rem;
--lh-base: 2.4rem;
--lh-sm: 2rem;
--preview-width: 6rem;
--preview-height: 2rem;
}
/* GLOBAL ----------------------------------------------------------------*/
html {
font-size: 0.625rem;
}
h1 {
color: var(--color-heading);
margin: 3.6rem 0 1.2rem 0;
}
body {
margin: 0;
padding: 0 4.8rem;
font-family: 'Poppins', sans-serif;
font-size: var(--fs-base);
color: var(--color-body);
}
p {
padding: 0;
border: 0;
margin: 0;
max-width: 99.2rem;
-webkit-font-smoothing: antialiased;
}
label {
color: var(--color-strong);
}
mark {
background-color: #c6dbf5;
}
.hidden {
display: none ;
}
.column {
display: flex;
flex-direction: column;
gap: 2.4rem;
}
.count {
text-align: right;
margin: 2.4rem 0;
font-size: var(--fs-sm);
}
.wrapper {
display: flex;
gap: 4.8rem;
margin-top: 3.6rem;
font-size: 1.4rem;
line-height: var(--lh-base);
}
.wrapper__aside {
flex: 0 0 30rem;
position: sticky;
top: 0;
}
.wrapper__main {
flex: 1 1 auto;
}
.no-margin-top {
margin-top: 0;
}
/* FILTER ----------------------------------------------------------------*/
.filter {
padding-top: 1rem;
position: sticky;
top: 0;
display: flex;
flex-direction: column;
}
.filter__heading {
margin: 2.4rem 0 0.8rem 0;
color: var(--color-strong);
}
/* TABLE -----------------------------------------------------------------*/
table {
box-sizing: inherit;
border: 0;
font: inherit;
vertical-align: baseline;
border-spacing: 0;
table-layout: fixed;
width: 100%;
padding: 0;
border-collapse: collapse;
}
table tbody {
font-size: var(--fs-sm);
line-height: 1.5;
}
table tr th {
padding: 1rem 1.2rem;
white-space: nowrap;
text-align: left;
color: var(--color-heading);
font-weight: bold;
}
table tr td {
padding: 0.8rem 1.2rem;
border-bottom: 0.1rem solid rgba(0, 0, 0, 0.08);
overflow-wrap: break-word;
}
table tr th,
table tr td {
width: 25%;
}
table tr th:first-child,
table tr td:first-child {
width: 50%;
}
table tr td:first-child {
color: var(--color-heading);
}
table tr th:last-child,
table tr td:last-child {
width: 20%;
}
.table__sticky {
position: sticky;
top: 0;
box-shadow: 0 4px var(--box-shadow-size) 0 rgba(12, 41, 56, 0.08), 0 2px 2px 1px rgba(12, 41, 56, 0.04);
background-color: #fff;
}
.table__sticky::after,
.table__sticky::before,
.table__header::after {
content: "";
position: absolute;
z-index: 1;
background-color: #fff;
}
.table__header::after {
top: calc(var(--box-shadow-size) * -1);
z-index: 1;
height: var(--box-shadow-size);
width: 100%;
background-color: #fff;
}
.table__sticky::before,
.table__sticky::after {
content: "";
position: absolute;
top: 0;
z-index: 1;
width: var(--box-shadow-size);
height: calc(100% + (var(--box-shadow-size) * 2));
background-color: #fff;
}
.table__sticky::after {
right: calc(var(--box-shadow-size) * -1);
}
.table__sticky::before {
left: calc(var(--box-shadow-size) * -1);
}
/* FIELDS ----------------------------------------------------------------*/
.field {
display: flex;
flex-direction: column;
flex: 1 1 auto;
gap: 0.4rem;
}
.field+.field {
margin-top: 1.2rem
}
input,
select {
width: 100%;
height: 3.2rem;
padding: 0 1.2rem;
border-width: 0.1rem;
border-style: solid;
border-radius: 0.4rem;
box-sizing: border-box;
border-color: #c3d3de;
}
.button__container {
display: flex;
gap: 0.5rem;
cursor: pointer;
}
.button__container button {
width: 4rem;
border-width: 0rem;
border-style: solid;
border-radius: 4px;
box-sizing: border-box;
background-color: rgba(0, 0, 0, 0.08);
color: rgba(0, 0, 0, 0.64);
cursor: pointer;
}
.button__container button:hover {
background-color: #cfdce4;
}
.button__container button.active {
background-color: #C6DBF5;
text-shadow: 0 0 0.5px;
}
#regexErrorMsg {
color: #d62433;
font-size: var(--fs-sm);
}
/* PREVIEW ---------------------------------------------------------------*/
.preview--border,
.preview--border-size,
.preview--border-radius,
.preview--border-color,
.preview--box-shadow,
.preview--color {
width: var(--preview-width);
height: var(--preview-height);
}
.preview--color,
.preview--border,
.preview--border-size,
.preview--box-shadow,
.preview--border-color {
border-radius: 0.4rem;
}
.preview--color {
border: 0.2rem solid #fff;
outline: 0.1rem solid rgba(0, 0, 0, 0.1);
}
.preview--border-radius {
background-color: rgb(249, 234, 252);
}
.preview--border-color {
border: solid 0.2rem;
}
.preview--border-size {
border-style: solid;
border-color: rgba(0, 0, 0, 0.12);
}
.preview--text-color {
font-weight: 600;
}
.preview--size {
max-height: 6.4rem;
max-width: 6.4rem;
background-color: rgb(225, 250, 242);
display: flex;
justify-content: center;
align-items: center;
font-weight: 700;
}
.preview--line-height {
background-color: rgb(225, 250, 242);
}
</style>
</head>
<body>
<div id="heading">
<h1>
<!-- To be filled dinamically -->
</h1>
<p>The different token formats available in this preview
are generated on the fly from the internal JSON output.
This preview should not be used to visually validate anything other than
tokens in JSON format.
</p>
</div>
<div class="wrapper">
<div id="aside" class="column wrapper__aside">
<div class="filter">
<div class="field">
<label for="searchSelector">Free search</label>
<div class="button__container">
<input type="text" id="searchSelector" name="search query" val="" placeholder="Search by token name" />
<button id="regexSelector" title="Use Regular Expression">(.*)</button>
</div>
<label for="regexSelector" id="regexErrorMsg"
class="error__message hidden"><!-- To be filled dinamically --></label>
</div>
<div class="field">
<label for="formatSelector">Format</label>
<select name="format" id="formatSelector">
<option value="js">JavaScript</option>
<option value="scss">SCSS</option>
<option value="css">CSS</option>
<option value="figma">FIGMA</option>
<option value="json">JSON (internal)</option>
</select>
</div>
<div class="field">
<label for="schemaSelector">Schema</label>
<select name="schema" id="schemaSelector">
<!-- To be filled dinamically -->
</select>
</div>
<div class="filter__heading">Advanced filters </div>
<div class="field">
<label for="tierSelector">Tier</label>
<select name="tier" id="tierSelector">
<!-- To be filled dinamically -->
</select>
</div>
<div class="field hidden" id="componentSelectorContainer">
<label for="componentSelector">Component</label>
<select name="component" id="componentSelector" disabled>
<!-- To be filled dinamically -->
</select>
</div>
<div class="field">
<label for="categorySelector">Category</label>
<select name="category" id="categorySelector" disabled>
<!-- To be filled dinamically -->
</select>
</div>
<div class="field">
<label for="propertySelector">Property</label>
<select name="property" id="propertySelector" disabled>
<!-- To be filled dinamically -->
</select>
</div>
<p class="count">
<!-- To be filled dinamically -->
</p>
</div>
</div>
<div class="wrapper__main">
<div class="table">
<table>
<thead class="table__sticky">
<tr>
<th>Token</th>
<th>Raw Value</th>
<th>Preview</th>
</tr>
</thead>
<tbody>
<!-- To be filled dinamically -->
</tbody>
</table>
</div>
</div>
</div>
</body>
<script>
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// -- Variables -------------------------------------------------------------
window.defaultAllOption = "all";
window.advancedAttrs = ["tier", "component", "category", "property"];
window.datasets = [];
window.filteredTokens = [];
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// -- Fill categories in selects --------------------------------------------
const setTitle = () => {
document.getElementsByTagName("h1")[0].innerText =
window.config.title || "Design Tokens";
};
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// -- Fill categories in selects --------------------------------------------
const buildFilterOps = (datasets) => {
const p = datasets[window.config.scheme.options[0]].reduce(
(acc, { attributes, path }) => {
const buildObj = (attrPath, obj) => {
const defaultUnknown = `__${attrPath[0]}__`;
const attrVal = attributes[attrPath[0]] || defaultUnknown;
if (
attrPath.length === 1 &&
Array.isArray(obj) &&
!obj.includes(attrVal)
) {
obj.push(attrVal);
} else {
const options = window.config?.advancedAttrs?.[attrPath[0]]?.options;
if (
obj &&
!obj[attrVal] &&
(!options || options?.includes(attrVal))
) {
obj[attrVal] = attrPath.length > 2 ? {} : [];
}
}
if (obj && attrPath.length >= 2) {
buildObj(attrPath.slice(1), obj[attributes[attrPath[0]] || defaultUnknown]);
}
};
buildObj(window.advancedAttrs, acc);
return acc;
},
{}
);
return p;
};
const appendOptions = (elId, value, attrs) => {
// Do not append if internal.
if (value.startsWith('__')) return;
const el = document.querySelector(elId);
const optEl = document.createElement("option");
optEl.value = value;
optEl.innerHTML = value;
Object.entries(attrs || []).forEach(([attr, value]) =>
optEl.setAttribute(attr, value)
);
el.appendChild(optEl);
};
const fillAdvancedSelectsOptions = () => {
const filterOptions = buildFilterOps(window.datasets);
// Append defaultAllOption option
window.advancedAttrs.forEach((key) =>
appendOptions(`#${key}Selector`, window.defaultAllOption)
);
Object.keys(filterOptions).forEach((tier) => {
Object.keys(filterOptions[tier]).forEach((component) => {
Object.keys(filterOptions[tier][component]).forEach((category) => {
filterOptions[tier][component][category].forEach((property) => {
appendOptions("#propertySelector", property, {
"data-tier": tier,
"data-component": component,
"data-category": category,
});
});
appendOptions("#categorySelector", category, { "data-tier": tier, "data-component": component });
});
appendOptions("#componentSelector", component, { "data-tier": tier });
});
appendOptions("#tierSelector", tier);
});
};
fillSchemaSelector = () => {
window.config.scheme.options.forEach((schema) =>
appendOptions("#schemaSelector", schema)
);
};
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// -- Fetch config ----------------------------------------------------------
const fetchConfig = () => {
return new Promise((resolve, reject) => {
const xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = () => {
if (xmlhttp.readyState == XMLHttpRequest.DONE) {
if (xmlhttp.status == 200) {
const config = JSON.parse(xmlhttp.responseText);
resolve(config);
} else if (xmlhttp.status == 404) {
reject("Invalid path to config file");
} else {
reject("Ups, something unexpected happened");
}
}
};
const path = `./preview-config.json`;
xmlhttp.open("GET", path, true);
xmlhttp.send();
});
};
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// -- Fetch datasets --------------------------------------------------------
const fetchDatasets = () => {
const datasetsPromises = window.config.scheme.options.map(
(schema) =>
new Promise((resolve, reject) => {
const xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = () => {
if (xmlhttp.readyState == XMLHttpRequest.DONE) {
if (xmlhttp.status == 200) {
const ds = JSON.parse(xmlhttp.responseText);
resolve({
schema,
tokens: ds.sort((a, b) => a.name.localeCompare(b.name)),
});
} else if (xmlhttp.status == 404) {
reject("Invalid path to tokens");
} else {
reject("Ups, something unexpected happened");
}
}
};
const path = `${schema}/json/tokens.json.all.json`;
xmlhttp.open("GET", path, true);
xmlhttp.send();
})
);
return Promise.all(datasetsPromises);
};
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// -- Render content --------------------------------------------------------
const camelToKebabCase = (str) =>
str.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
const name = (el) => "<td>" + el + "</td>";
const rawValue = (el) => "<td>" + el + "</td>";
const colorPreview = (color) =>
'<div class="preview preview--color" style="background-color:' + color + ';"></div>';
const borderColorPreview = (color) =>
'<div class="preview preview--border-color" style="border-color:' + color + ';"></div>';
const boxShadowPreview = (shadow) =>
'<div class="preview preview--box-shadow" style="box-shadow:' + shadow + ';"></div>';
const borderRadiusPreview = (radius) =>
'<div class="preview preview--border-radius" style="border-radius:' +
radius +
';"></div>';
const borderPreview = (border) =>
'<div class="preview preview--border" style="border:' + border + ';"></div>';
const borderSizePreview = (size) =>
'<div class="preview preview--border-size" style="border-width:' + size + ';"></div>';
const textColorPreview = (color) =>
'<div class="preview preview--text-color" style="color:' + color + ';">TOKEN</div>';
const fontSizePreview = (size) =>
'<div style="font-size:' + size + ';">TOKEN</div>';
const fontWeightPreview = (weight) =>
'<div style="font-weight:' + weight + ';">TOKEN</div>';
const lineSizePreview = (size) =>
'<div class="preview preview--line-height" style="height:' + size + ';"></div>';
const sizePreview = (size) => {
const sizeNumber = parseFloat(size?.substring(0, size.length - 3));
const result = sizeNumber < 6 ? size : "6rem"; return ('<div class="preview preview--size" style="height:' + result + "; width:"
+ result + ';">' + (sizeNumber < 6 ? "" : "bigger") + "</div>");
};
const preview = (token) => {
const { path, name, value } = token;
const container = (content = "") => "<td>" + content + "</td>";
if (path.includes("color")) {
if (path.includes("text")) {
return container(textColorPreview(value));
} else if (path.includes("border")) {
return container(borderColorPreview(value))
} else {
return container(colorPreview(value));
}
}
if (path.includes("boxShadow") || path.includes("textShadow")) {
return container(boxShadowPreview(value));
}
if (path.includes("size")) {
return container(sizePreview(value));
}
if (path.includes("fontSize")) {
return container(fontSizePreview(value));
}
if (path.includes("fontWeight")) {
return container(fontWeightPreview(value));
}
if (path.includes("lineHeight")) {
return container(lineSizePreview(value));
}
if (path.includes("shape")) {
if (path.includes("border")) {
return container(borderPreview(value));
}
if (path.includes("borderSize")) {
return container(borderSizePreview(value));
}
if (path.includes("borderRadius")) {
return container(borderRadiusPreview(value));
}
return container("n/a");
}
return container("n/a");
};
const createRow = (el) => {
return `<tr> ${name(el._formatted)} ${rawValue(el.highlighted || el.value)} ${preview(
el
)} </tr>`;
};
const renderContent = () => {
const list = document.querySelector("table tbody");
const count = document.querySelector(".count");
const query = document.querySelector("#searchSelector").value;
const rows = window.filteredTokens.map((el) => createRow(el));
list.innerHTML = rows.join("");
count.innerHTML =
"Showing " +
rows.length +
"/" +
window.datasets[window.config.scheme.options[0]].length;
};
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// -- Form elements handlers ------------------------------------------------
const handleTierSelectorChange = (value) => {
// Enable/disabled next select
if (value !== window.defaultAllOption) {
if (value === 'cmp') {
document.getElementById("componentSelector").disabled = false;
} else {
document.getElementById("categorySelector").disabled = false;
}
} else {
document.getElementById("componentSelector").disabled = true;
document.getElementById("categorySelector").disabled = true;
document.getElementById("propertySelector").disabled = true;
}
// Show or hide Component select
if (value === 'cmp') {
document.getElementById("componentSelectorContainer").classList.remove('hidden');
} else {
document.getElementById("componentSelectorContainer").classList.add('hidden');
}
// Update self
updateUrl("tier", value);
// Update component selector
document.getElementById("componentSelector").value = window.defaultAllOption;
updateAdvancedSearchOptions("tier", "component", value, `option${dataTag('tier', value)}`);
updateUrl("component", window.defaultAllOption);
// Update category selector
document.getElementById("categorySelector").value = window.defaultAllOption;
updateAdvancedSearchOptions("tier", "category", value, `option${dataTag('tier', value)}`);
updateUrl("category", window.defaultAllOption);
// Update property selector
document.getElementById("propertySelector").value = window.defaultAllOption;
updateAdvancedSearchOptions("tier", "property", value, `option${dataTag('tier', value)}`);
updateUrl("property", window.defaultAllOption);
// Update preview
applyFilters();
renderContent();
}
const handleComponentSelectorChange = (value) => {
// Enable/disabled next select
if (value !== window.defaultAllOption) {
document.getElementById("categorySelector").disabled = false;
} else {
document.getElementById("categorySelector").disabled = true;
document.getElementById("propertySelector").disabled = true;
}
// Update self
updateUrl("component", value);
const tierVal = document.querySelector("#tierSelector").value;
const query = `option${dataTag('tier', tierVal)}${dataTag('component', value)}`;
// Update category selector
document.getElementById("categorySelector").value = window.defaultAllOption;
updateAdvancedSearchOptions("component", "category", value, query);
updateUrl("category", window.defaultAllOption);
// Update property selector
document.getElementById("propertySelector").value = window.defaultAllOption;
updateAdvancedSearchOptions("component", "property", value, query);
updateUrl("property", window.defaultAllOption);
// Update preview
applyFilters();
renderContent();
}
const handleCategorySelectorChange = (value) => {
// Enable/disabled next select
if (value !== window.defaultAllOption) {
document.getElementById("propertySelector").disabled = false;
} else {
document.getElementById("propertySelector").disabled = true;
}
// Update self
updateUrl("category", value);
// Update property selector
const tierVal = document.querySelector("#tierSelector").value;
const componentVal = document.querySelector("#componentSelector").value;
document.getElementById("propertySelector").value = window.defaultAllOption;
if (tierVal === 'cmp') {
updateAdvancedSearchOptions("category", "property", value, `option${dataTag('tier', tierVal)}${dataTag('component', componentVal)}${dataTag('category', value)}`);
} else {
updateAdvancedSearchOptions("category", "property", value, `option${dataTag('tier', tierVal)}${dataTag('category', value)}`);
}
updateUrl("property", window.defaultAllOption);
// Update preview
applyFilters();
renderContent();
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// -- Reactive Form elements ------------------------------------------------
const debounce = (cb, ms) => {
let timerId;
return (...args) => {
clearTimeout(timerId);
timerId = setTimeout(() => {
cb(...args);
}, ms);
};
}
const updateUrl = (param, value) => {
const url = new URL(window.location.href);
const searchParams = url.searchParams;
if (!value || value === "" || value === window.defaultAllOption) {
searchParams.delete(param);
} else {
searchParams.set(param, value);
}
window.history.pushState({}, null, url.toString());
};
const handleBaseParamChange = (param, value) => {
updateUrl(param, value);
applyFilters();
renderContent();
};
const updateAdvancedSearchOptions = (parent, targetId, value, selector) => {
const parentEl = document.getElementById(`${targetId}Selector`);
parentEl
.querySelectorAll(`option`)
.forEach((el) => el.classList.add("hidden"));
if (value !== window.defaultAllOption) {
parentEl.querySelector(`option[value=${window.defaultAllOption}]`).classList.remove("hidden");
parentEl
.querySelectorAll(selector)
.forEach((el) => {
el.classList.remove("hidden");
});
} else {
parentEl
.querySelectorAll(`option`)
.forEach((el) => el.classList.remove("hidden"));
}
};
const toggleRegexMode = (element) => {
element.classList.toggle('active');
updateUrl('regex', element.classList.contains("active"));
applyFilters();
renderContent();
}
// Utils
const dataTag = (key, val) => val !== window.defaultAllOption ? `[data-${key}=${val}]` : '';
// Search
const searchSelector = document.querySelector("#searchSelector");
searchSelector.addEventListener("input",
debounce((ev) => handleBaseParamChange("query", ev.target.value), 200)
);
// Regex
const regexSelector = document.querySelector("#regexSelector");
regexSelector.addEventListener("click", () => toggleRegexMode(regexSelector)
);
// Format
const formatSelector = document.querySelector("#formatSelector");
formatSelector.addEventListener("change", (ev) =>
handleBaseParamChange("format", ev.target.value)
);
// Schema
const schemaSelector = document.querySelector("#schemaSelector");
schemaSelector.addEventListener("change", (ev) =>
handleBaseParamChange("schema", ev.target.value)
);
// Tier
const tierSelector = document.querySelector("#tierSelector");
tierSelector.addEventListener("change", (ev) => handleTierSelectorChange(ev.target.value));
// Component
const componentSelector = document.querySelector("#componentSelector");
componentSelector.addEventListener("change", (ev) => handleComponentSelectorChange(ev.target.value));
// Category
const categorySelector = document.querySelector("#categorySelector");
categorySelector.addEventListener("change", (ev) => handleCategorySelectorChange(ev.target.value));
// Property
const propertySelector = document.querySelector("#propertySelector");
propertySelector.addEventListener("change", (ev) =>
handleBaseParamChange("property", ev.target.value)
);
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// -- Initialize form elements ----------------------------------------------
const initializeFormElements = () => {
// Fill form elements with custom options
fillAdvancedSelectsOptions();
const search = window.location.search;
const urlParams = new URLSearchParams(search);
// Selectors
const searchSelector = document.querySelector("#searchSelector");
const regexSelector = document.querySelector("#regexSelector");
const formatSelector = document.querySelector("#formatSelector");
const schemaSelector = document.querySelector("#schemaSelector");
const tierSelector = document.querySelector("#tierSelector");
const componentSelector = document.querySelector("#componentSelector");
const categorySelector = document.querySelector("#categorySelector");
const propertySelector = document.querySelector("#propertySelector");
const fullscreenSelector = urlParams.get("fullscreen") || 'false';
// Set value of all form elements based on url params.
searchSelector.value =
urlParams.get("query") || "";
regexSelector.classList.toggle('active', !!urlParams.get("regex"));
formatSelector.value =
urlParams.get("format") || "js";
schemaSelector.value =
urlParams.get("schema") || window.config.scheme.options[0];
tierSelector.value =
urlParams.get("tier") || window.defaultAllOption;
componentSelector.value =
urlParams.get("component") || window.defaultAllOption;
categorySelector.value =
urlParams.get("category") || window.defaultAllOption;
propertySelector.value =
urlParams.get("property") || window.defaultAllOption;
// Enable disable dependant selects
if (tierSelector.value !== window.defaultAllOption) {
if (tierSelector.value === 'cmp') {
componentSelector.disabled = false;
} else {
categorySelector.disabled = false;
}
}
if (componentSelector.value !== window.defaultAllOption) {
categorySelector.disabled = false;
}
if (categorySelector.value !== window.defaultAllOption) {
propertySelector.disabled = false;
}
// Show conditional selects
if (tierSelector.value === 'cmp') {
document.getElementById("componentSelectorContainer").classList.remove('hidden');
}
// Fullscreen - show/hide aside and heading
if (fullscreenSelector === 'true') {
document.getElementById("aside").classList.add('hidden');
document.getElementById("heading").classList.add('hidden');
// remove margin top from wrapper
document.querySelector(".wrapper").classList.add('no-margin-top');
}
// Update options in selects
// component
updateAdvancedSearchOptions("component", "category", componentSelector.value, `option${dataTag('tier', tierSelector.value)}${dataTag('component', componentSelector.value)}`);
updateAdvancedSearchOptions("component", "property", componentSelector.value, `option${dataTag('tier', tierSelector.value)}${dataTag('component', componentSelector.value)}`);
// category
if (tierSelector.value === 'cmp') {
updateAdvancedSearchOptions("category", "property", categorySelector.value, `option${dataTag('tier', tierSelector.value)}${dataTag('component', componentSelector.value)}${dataTag('category', categorySelector.value)}`);
} else {
updateAdvancedSearchOptions("category", "property", categorySelector.value, `option${dataTag('tier', tierSelector.value)}${dataTag('category', categorySelector.value)}`);
}
//property
};
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// -- Filtering tasks -------------------------------------------------------
const filtersFns = new Map(
// Order matters;
Object.entries({
schema: (schema) => (datasets) => {
// If no schema, return default value.
if (!schema) return datasets[window.config.scheme.options[0]];
// else return selection.
return datasets[schema];
},
tier: (tier) => (tokens) => {
// Options to be visible (if any). Else all are shown.
const tierOpts = window.config?.advancedAttrs?.tier?.options;
// If there is no filter selected or 'all' is selected and there
// is no internal filtering from config.
if ((!tier || tier === window.defaultAllOption) && !tierOpts) {
return tokens;
}
// If there is no filter selected or 'all' is selected and there
// is an internal filtering from config required.
if ((!tier || tier == window.defaultAllOption) && tierOpts) {
const filteredTokens = tokens.filter((token) =>
tierOpts.includes(token.attributes.tier)
);
return filteredTokens;
}
// If filter is active.
return tokens.filter((token) => token.attributes.tier === tier);
},
component: (component) => (tokens) => {
// Options to be visible (if any). Else all are shown.
const componentOpts = window.config?.advancedAttrs?.component?.options;
// If there is no filter selected or 'all' is selected and there
// is no internal filtering from config.
if ((!component || component === window.defaultAllOption) && !componentOpts) {
return tokens;
}
// If there is no filter selected or 'all' is selected and there
// is an internal filtering from config required.
if ((!component || component == window.defaultAllOption) && componentOpts) {
const filteredTokens = tokens.filter((token) =>
componentOpts.includes(token.attributes.component)
);
return filteredTokens;
}
// If filter is active.
return tokens.filter((token) => token.attributes.component === component);
},
category: (category) => (tokens) => {
// Options to be visible (if any). Else all are shown.
const categoryOpts = window.config?.advancedAttrs?.category?.options;
// If there is no filter selected or 'all' is selected and there
// is no internal filtering from config.
if ((!category || category === window.defaultAllOption) && !categoryOpts) {
return tokens;
}
// If there is no filter selected or 'all' is selected and there
// is an internal filtering from config required.
if (category == window.defaultAllOption && categoryOpts) {
return tokens.filter((token) =>
categoryOpts.includes(token.attributes.category)
);
}
// If filter is active.
return tokens.filter((token) => token.attributes.category === category);
},
property: (property) => (tokens) => {
// Options to be visible (if any). Else all are shown.
const propertyOpts = window.config?.advancedAttrs?.property?.options;
// If there is no filter selected or 'all' is selected and there
// is no internal filtering from config.
if ((!property || property === window.defaultAllOption) && !propertyOpts) {
return tokens;
}
// If there is no filter selected or 'all' is selected and there
// is an internal filtering from config required.
if (property == window.defaultAllOption && propertyOpts) {
return tokens.filter((token) =>
propertyOpts.includes(token.attributes.property)
);
}
// If filter is active.
return tokens.filter((token) => token.attributes.property === property);
},
format: (format) => (tokens) => {
return tokens.map((token) => {
if (!format || format === "js")
return { ...token, _formatted: token.name.replaceAll("-", ".") };
if (format === "scss")
return { ...token, _formatted: `$${camelToKebabCase(token.name)}` };
if (format === "css")
return { ...token, _formatted: `--${camelToKebabCase(token.name)}` };
if (format === "figma")
return { ...token, _formatted: token.name.replaceAll("-", "/") };
if (format === "json") return { ...token, _formatted: token.name };
});
},
query: (query, { regexMode }) => (tokens) => {
const errMsgElement = document.querySelector('#regexErrorMsg');
errMsgElement.classList.toggle('hidden', true);
if (!query) return { query, tokens };
if (regexMode) {
try {
return {
query: '',
tokens: tokens.filter((token) =>
token._formatted.match(query) ||
token.value.toString().match(query)
)
};
} catch (err) {
errMsgElement.classList.toggle('hidden');
errMsgElement.innerHTML = err.message;
return {
query: '',
tokens: []
};
}
} else {
const cleanQuery = query.trim().toLowerCase();
return {
query: cleanQuery,
tokens: tokens.filter((token) =>
token._formatted.toLowerCase().includes(cleanQuery) ||
token.value.toString().toLowerCase().includes(cleanQuery)
)
};
}
},
highlight: (_query, { regexMode, fullScreen }) => ({ query, tokens }) => {
if (!query) return tokens;
if (regexMode || fullScreen) return tokens;
return tokens.map(token => {
let name = token._formatted;
let value = token.value;
const nameMatchIdx = token._formatted.toLowerCase().indexOf(query);
const valueMatchIdx = token.value.toString().toLowerCase().indexOf(query);
// Display match in token name
if (nameMatchIdx !== -1) {
const pre = token._formatted.substring(0, nameMatchIdx);
const match = token._formatted.substring(nameMatchIdx, nameMatchIdx + query.length);
const pos = token._formatted.substring(nameMatchIdx + query.length);
name = `${pre}<mark>${match}</mark>${pos}`;
}
// Display match in raw value name
if (valueMatchIdx !== -1) {
const pre = token.value.toString().substring(0, valueMatchIdx);
const match = token.value.toString().substring(valueMatchIdx, valueMatchIdx + query.length);
const pos = token.value.toString().substring(valueMatchIdx + query.length);
value = `${pre}<mark>${match}</mark>${pos}`;
}
return {
...token,
highlighted: value,
_formatted: name,
}
});
},
})
);
const applyFilters = () => {
const search = window.location.search;
const urlParams = new URLSearchParams(search);
const regexMode = urlParams.get('regex') === 'true';
const fullScreen = urlParams.get('fullscreen') === 'true';
// Apply all filters from url
window.filteredTokens = [...filtersFns.entries()].reduce(
(acc, [filterId, filterFn]) =>
filterFn(urlParams.get(filterId), { regexMode, fullScreen })(acc),
window.datasets
);
};
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// -- entrypoint ------------------------------------------------------------
// HIDDEN FEATURES
// - Fullscreen mode: add fullscreen=true to the url
// - Preview global tokens: add tier=global to the url
(async () => {
window.config = await fetchConfig();
const rawDatasets = await fetchDatasets();
window.datasets = rawDatasets.reduce(
(acc, { schema, tokens }) => ({ ...acc, [schema]: tokens }),
{}
);
// Set H1 title
setTitle();
// Set initial values to form elements
initializeFormElements();
// Fill schema selector options
fillSchemaSelector();
// Apply data filters
applyFilters();
// Render tokens list
renderContent();
})();
</script>
</html>