Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ requests. GitHub Pages publishes it from `main` through `.github/workflows/pages
| `index.html` | the page |
| `assets/css/site.css` | the styles: colour tokens for the light and dark themes, Fluent surfaces, the layout |
| `assets/js/config.js` | the version the page shows and the release flag (below) |
| `assets/js/site.js` | versions in the snippets, code highlighting and copy buttons, the theme switch, tabs, the crate filter, Reveal |
| `assets/js/site.js` | versions in the snippets, code highlighting and copy buttons, the theme switch, tabs, the `cargo generate` commands of the templates, the crate filter, Reveal |
| `assets/img/` | the logo and the screenshots (WebP, each with a 480 or 640 pixel variant) |
| `scripts/update-crates.py` | writes the crate table from the README of FerroUI |
| `scripts/check-site.py` | the checks of CI: local references, versions, names |
Expand Down
70 changes: 69 additions & 1 deletion assets/css/site.css
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ p { margin: 0; }
box-shadow: 0 0 0 1px var(--focus-inner);
border-radius: var(--r-control);
}
.btn:focus-visible, .chip:focus-visible, .tab:focus-visible, .icon-btn:focus-visible, .copy:focus-visible, .field input:focus-visible {
.btn:focus-visible, .chip:focus-visible, .tab:focus-visible, .tpl-tab:focus-visible, .icon-btn:focus-visible, .copy:focus-visible, .field input:focus-visible, .opt input:focus-visible, .opt select:focus-visible {
outline: 2px solid var(--rust);
outline-offset: 2px;
box-shadow: 0 0 0 1px var(--focus-inner);
Expand Down Expand Up @@ -818,6 +818,72 @@ table.data td:first-child { color: var(--text); }
.template .section-head { margin-bottom: 20px; }
.template h3 { font-size: 22px; font-weight: 600; margin-bottom: 8px; }

/* Templates */

.tpl-about { display: flex; flex-direction: column; gap: 12px; color: var(--text-2); font-size: 15px; }
.tpl-about .notice { margin-bottom: 0; }
.tpl { display: grid; gap: 16px; grid-template-columns: 1fr; margin-top: 14px; }
@media (min-width: 960px) { .tpl { grid-template-columns: 280px minmax(0, 1fr); align-items: start; } }
.tpl-list { display: grid; gap: 2px; padding: 6px; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
@media (min-width: 960px) { .tpl-list { grid-template-columns: 1fr; position: sticky; top: calc(var(--header-h) + 16px); } }
.tpl-tab {
position: relative;
display: grid; gap: 1px;
padding: 8px 12px 8px 16px;
border-radius: var(--r-control);
border: 1px solid transparent;
background: transparent;
color: var(--text);
font: inherit; text-align: left;
cursor: pointer;
transition: background var(--dur) var(--ease);
}
.tpl-tab:hover { background: var(--layer-alt); }
.tpl-tab[aria-selected="true"] { background: var(--card-solid); border-color: var(--stroke-card); box-shadow: var(--shadow-2); }
.tpl-tab[aria-selected="true"]::before {
content: ""; position: absolute; left: 4px; top: 50%;
width: 3px; height: 16px; margin-top: -8px; border-radius: 2px;
background: var(--rust-fill);
}
.tpl-name { font-family: var(--font-mono); font-size: 13.5px; font-weight: 600; overflow-wrap: anywhere; }
.tpl-kind { font-size: 12.5px; color: var(--text-2); }
@media (max-width: 959px) { .tpl-kind { display: none; } }
.tpl-panels { min-width: 0; }
.tpl-panel { padding: 20px; }
.tpl-panel:hover { background: var(--card); box-shadow: var(--shadow-2); }
.tpl-panel h3 { font-family: var(--font-mono); font-size: 18px; font-weight: 600; margin-bottom: 8px; display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
.tpl-panel h3 .badge { font-family: var(--font-text); }
.tpl-files { display: flex; flex-wrap: wrap; gap: 6px; list-style: none; margin: 0 0 16px; padding: 0; }
.tpl-files li {
font-family: var(--font-mono); font-size: 12px;
padding: 3px 8px;
border-radius: var(--r-control);
border: 1px solid var(--stroke);
background: var(--layer-alt);
color: var(--text-2);
}
.tpl-options { display: flex; flex-wrap: wrap; gap: 12px 16px; align-items: flex-end; margin: 0 0 16px; }
.no-js .tpl-options { display: none; }
.opt { display: grid; gap: 4px; margin: 0; padding: 0; border: 0; font-size: 12px; font-weight: 600; color: var(--text-2); min-width: 0; }
.opt input[type="text"], .opt select {
height: 34px; min-width: 170px; max-width: 100%;
padding: 0 10px;
border-radius: var(--r-control);
border: 1px solid var(--stroke);
border-bottom: 1px solid var(--text-3);
background: var(--card);
color: var(--text);
font: inherit; font-size: 14px; font-weight: 400;
}
.opt select option { background: var(--card-solid); color: var(--text); }
.opt input[type="text"]:focus, .opt select:focus { border-bottom: 2px solid var(--rust-fill); background: var(--card-solid); }
.opt input::placeholder { color: var(--text-3); }
.opt-hosts { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 14px; min-height: 34px; padding-top: 18px; position: relative; }
.opt-hosts legend { position: absolute; top: 0; left: 0; padding: 0; }
.opt-hosts label { display: inline-flex; align-items: center; gap: 6px; font-size: 14px; font-weight: 400; color: var(--text); cursor: pointer; }
.opt-hosts input { width: 16px; height: 16px; margin: 0; accent-color: var(--rust-fill); }
.tpl-panel .code { margin-bottom: 0; }

/* Crates */

.crate-tools { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; margin-bottom: 16px; }
Expand Down Expand Up @@ -941,6 +1007,8 @@ table.crates tr[hidden] { display: none; }
@media (forced-colors: active) {
.reveal::before, .reveal::after { display: none; }
.card, .btn, .chip, .tab, .code { border: 1px solid CanvasText; }
.tpl-tab[aria-selected="true"] { border-color: Highlight; }
.tpl-tab[aria-selected="true"]::before { background: Highlight; }
}

@media print {
Expand Down
66 changes: 62 additions & 4 deletions assets/js/site.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// FerroUI website: versions, code highlighting and copy, theme, navigation, tabs, crate filter,
// Reveal lighting and entrance motion. Plain JavaScript, no dependencies.
// FerroUI website: versions, code highlighting and copy, theme, navigation, tabs, template
// commands, crate filter, Reveal lighting and entrance motion. Plain JavaScript, no dependencies.
(function () {
"use strict";

Expand Down Expand Up @@ -224,8 +224,9 @@
tab.addEventListener("click", function () { select(tab, false); });
tab.addEventListener("keydown", function (e) {
var next = null;
if (e.key === "ArrowRight") next = tabs[(i + 1) % tabs.length];
else if (e.key === "ArrowLeft") next = tabs[(i - 1 + tabs.length) % tabs.length];
var vertical = list.getAttribute("aria-orientation") === "vertical";
if (e.key === "ArrowRight" || (vertical && e.key === "ArrowDown")) next = tabs[(i + 1) % tabs.length];
else if (e.key === "ArrowLeft" || (vertical && e.key === "ArrowUp")) next = tabs[(i - 1 + tabs.length) % tabs.length];
else if (e.key === "Home") next = tabs[0];
else if (e.key === "End") next = tabs[tabs.length - 1];
if (next) { e.preventDefault(); select(next, true); }
Expand All @@ -234,6 +235,62 @@
});
}

// The templates: each panel writes the cargo generate command of its template from its options.
// data-before and data-next are the lines before and after it, separated by "|"; a line that
// starts with "option?" (or "!option?") is written only when that checkbox is on (or off).
var TEMPLATES_REPO = "https://github.com/FerroUI/templates";
function setupTemplates() {
document.querySelectorAll("[data-template]").forEach(function (panel) {
var code = panel.querySelector("code[data-command]");
if (!code) return;
var template = panel.getAttribute("data-template");
var nameInput = panel.querySelector("[data-name]");
var controls = Array.prototype.slice.call(panel.querySelectorAll("[data-define]"));
function clean(text) { return text.replace(/[^A-Za-z0-9_.-]/g, ""); }
function valueOf(control) {
return control.type === "checkbox" ? (control.checked ? "true" : "false") : clean(control.value.trim());
}
function lines(attribute, name) {
return (panel.getAttribute(attribute) || "").split("|").filter(Boolean).map(function (line) {
var condition = line.match(/^(!?)([a-z_]+)\?(.*)$/);
if (condition) {
var box = panel.querySelector("[data-define='" + condition[2] + "']");
var on = box ? box.checked : false;
if (on === (condition[1] === "!")) return null;
line = condition[3];
}
return line.split("{name}").join(name);
}).filter(function (line) { return line !== null; });
}
// cargo generate names the directory of the project in kebab case (MyApp is my-app).
function kebab(text) {
return text.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/_/g, "-").toLowerCase();
}
function render() {
var name = nameInput ? clean(nameInput.value.trim()) || nameInput.defaultValue : "my-app";
var defines = controls.filter(function (control) {
if (control.hasAttribute("data-always")) return valueOf(control) !== "";
return valueOf(control) !== (control.getAttribute("data-default") || "");
}).map(function (control) { return "--define " + control.getAttribute("data-define") + "=" + valueOf(control); });
var command = "cargo generate --git " + TEMPLATES_REPO + " " + template + " --name " + name;
var parts = [command];
// Two defines per continuation line keep the command readable on a phone.
for (var i = 0; i < defines.length; i += 2) parts.push(defines.slice(i, i + 2).join(" "));
var text = lines("data-before", name).concat([parts.join(" \\\n ")], lines("data-next", kebab(name))).join("\n");
if (code.textContent !== text) {
code.textContent = text;
highlight(code);
}
}
if (nameInput) nameInput.addEventListener("input", render);
controls.forEach(function (control) {
control.addEventListener("input", render);
control.addEventListener("change", render);
});
render();
});
}

// The crate table: a search field and one filter per group.
function setupCrates() {
var table = document.querySelector("table.crates");
Expand Down Expand Up @@ -313,6 +370,7 @@
setupMenu();
setupScrollSpy();
setupTabs();
setupTemplates();
setupCrates();
setupReveal();
setupRise();
Expand Down
Loading
Loading