From 4316c2670a7e45d50a3c1364174f5c8f3d2cebd1 Mon Sep 17 00:00:00 2001 From: Lea Verou Date: Fri, 24 Jan 2025 14:03:19 -0500 Subject: [PATCH] Improve theme remixing UI --- docs/_includes/import-stylesheet-code.md.njk | 10 +- docs/_layouts/theme.njk | 83 ++--------- docs/assets/styles/docs.css | 19 --- docs/docs/themes/demo.njk | 17 ++- docs/docs/themes/remixed/index.js | 138 +++++++++++++++++++ docs/docs/themes/remixed/index.njk | 84 +++++++++++ docs/docs/themes/remixed/style.css | 18 +++ docs/docs/themes/showcase.css | 12 ++ 8 files changed, 282 insertions(+), 99 deletions(-) create mode 100644 docs/docs/themes/remixed/index.js create mode 100644 docs/docs/themes/remixed/index.njk create mode 100644 docs/docs/themes/remixed/style.css diff --git a/docs/_includes/import-stylesheet-code.md.njk b/docs/_includes/import-stylesheet-code.md.njk index c70314b46..69adea334 100644 --- a/docs/_includes/import-stylesheet-code.md.njk +++ b/docs/_includes/import-stylesheet-code.md.njk @@ -1,3 +1,5 @@ +{%- if not stylesheets %}{% set stylesheets = [stylesheet] %}{% endif -%} + In HTML In CSS @@ -5,14 +7,18 @@ Simply add the following code to the `` of your page: ```html - +{% for stylesheet in stylesheets -%} +{% if not loop.last %} +{% endif %}{% endfor %} ``` Simply add the following code at the top of your CSS file: ```css -@import url('{% cdnUrl stylesheet %}'); +{% for stylesheet in stylesheets -%} +@import url('{% cdnUrl stylesheet %}');{% if not loop.last %} +{% endif %}{% endfor %} ``` diff --git a/docs/_layouts/theme.njk b/docs/_layouts/theme.njk index dfa90a01c..8858dd0a3 100644 --- a/docs/_layouts/theme.njk +++ b/docs/_layouts/theme.njk @@ -3,64 +3,15 @@ {# {% set forceTheme = page.fileSlug %} #} {% extends '../_includes/base.njk' %} +{% set themeId = page.fileSlug %} +{% if themeId == 'remixed' -%} + {% set themeId = 'default' %} +{% endif -%} {% block header %} - - -

- - - Remix - - - - - (Theme default) - - {% for theme in collections.theme | sort %} - {% if theme.fileSlug !== page.fileSlug %} - {{ theme.data.title }} - {% endif %} - {% endfor %} - - - - - (Theme default) - - {% for p in collections.palette | sort %} - {% if p.fileSlug !== palette %} - {{ p.data.title }} - {% endif %} - {% endfor %} - - - - - (Theme default) - - {% for theme in collections.theme | sort %} - {% if theme.fileSlug !== page.fileSlug %} - {{ theme.data.title }} - {% endif %} - {% endfor %} - - - -

- + +{% if palette -%}

Default Color Palette

{% set paletteURL = '/docs/palettes/' + palette + '/' %} {% set themePage = page %} @@ -69,34 +20,20 @@ {% include 'page-card.njk' %} {% set page = themePage %} +{% endif %} {% endblock %} {% block afterContent %} {% markdown %} +{%- if page.fileSlug != 'remixed' %} ## How to use this theme You can import this theme from the Web Awesome CDN. -{% set stylesheet = 'styles/themes/' + page.fileSlug + '.css' %} +{% set stylesheet = 'styles/themes/' + themeId + '.css' %} {% include 'import-stylesheet-code.md.njk' %} - -### Remixing { #remixing } - -If you want to combine the **colors** from this theme with another theme, you can import this CSS file *after* the other theme’s CSS file: - -{% set stylesheet = 'styles/themes/' + page.fileSlug + '/color.css' %} -{% include 'import-stylesheet-code.md.njk' %} - -To use the **typography** from this theme with another theme, you can import this CSS file *after* the other theme’s CSS file: - -{% set stylesheet = 'styles/themes/' + page.fileSlug + '/typography.css' %} -{% include 'import-stylesheet-code.md.njk' %} - - - - Please note that not all combinations will look good — once you’re mixing and matching, you’re on your own! - +{% endif %} ## Dark mode diff --git a/docs/assets/styles/docs.css b/docs/assets/styles/docs.css index b98b5bc8f..56a0e72a5 100644 --- a/docs/assets/styles/docs.css +++ b/docs/assets/styles/docs.css @@ -538,23 +538,4 @@ table.colors { height: 65vh; max-height: 21lh; } - - #mix_and_match { - strong { - display: flex; - align-items: center; - gap: var(--wa-space-2xs); - margin-top: 1.2em; - } - - wa-select::part(label) { - margin-block-end: 0; - } - - wa-select[value='']::part(display-input), - wa-option[value=''] { - font-style: italic; - color: var(--wa-color-text-quiet); - } - } } diff --git a/docs/docs/themes/demo.njk b/docs/docs/themes/demo.njk index c511f639b..ec7885ed0 100644 --- a/docs/docs/themes/demo.njk +++ b/docs/docs/themes/demo.njk @@ -19,6 +19,10 @@ eleventyComputed: {% include 'breadcrumbs.njk' %}

{{ theme.data.title }}

+ + + Remix +

{% include 'status.njk' %}

{{ theme.data.description | inlineMarkdown | safe }}

@@ -39,12 +43,12 @@ function updateTheme() { let params = new URLSearchParams(window.location.search); let script = document.currentScript; const stylesheetURLs = { - colors: id => `/dist/styles/themes/${ id }/color.css`, + color: id => `/dist/styles/themes/${ id }/color.css`, palette: id => `/dist/styles/color/${ id }.css`, typography: id => `/dist/styles/themes/${ id }/typography.css` }; const icons = { - colors: 'palette', + color: 'palette', palette: 'swatchbook', typography: 'font-case' } @@ -66,11 +70,14 @@ function updateTheme() { } } + let isRemixed = msgs.length > 0; + document.documentElement.classList.toggle("remixed", isRemixed); + for (let p of mix_and_match) { - p.hidden = msgs.length === 0; - if (msgs.length) { + p.hidden = !isRemixed; + if (isRemixed) { let icon = - p.innerHTML = ` Remixed ` + msgs.map(msg => ` + p.innerHTML = ` Remixed
` + msgs.map(msg => ` ${ msg }`).join(' '); } } diff --git a/docs/docs/themes/remixed/index.js b/docs/docs/themes/remixed/index.js new file mode 100644 index 000000000..23fb84e78 --- /dev/null +++ b/docs/docs/themes/remixed/index.js @@ -0,0 +1,138 @@ +let params = { base: 'default', palette: '', color: '', typography: '' }; + +// Find usage code snippet and prepare it +let snippets = document.querySelectorAll('#remixed-usage ~ wa-tab-group pre > code'); +let copyButtons = document.querySelectorAll('#remixed-usage ~ wa-tab-group pre > wa-copy-button'); +let codeExamples = []; + +for (let snippet of snippets) { + let tokens = [...snippet.children]; + let base = tokens.shift(); + let [palette, color, typography] = tokens; + + codeExamples.push({ snippet, base, palette, color, typography }); + + // Remove non-base tokens + for (let token of tokens) { + let whitespace = token.previousSibling; + + if (whitespace.nodeType === Node.TEXT_NODE) { + // Move whitespace to beginning of node + token.prepend(token.previousSibling); + } + } +} + +// Read URL params and apply them. This facilitates permalinks. +if (location.search) { + let urlParams = new URLSearchParams(location.search); + + for (let aspect in params) { + if (urlParams.has(aspect)) { + params[aspect] = urlParams.get(aspect); + } + } +} + +const selects = Object.fromEntries( + [...document.querySelectorAll('#mix_and_match wa-select')].map(select => [select.getAttribute('name'), select]), +); + +document.querySelector('#mix_and_match').addEventListener( + 'change', + function (event) { + for (let name in selects) { + params[name] = selects[name].value; + } + + render(); + }, + { capture: true }, +); + +function hasOverride(name) { + if (!params[name]) { + return false; + } + + if (name === 'palette') { + return params[name] !== defaultPalettes[params.base]; + } + + if (name !== 'base') { + return params[name] !== params.base; + } + + return true; +} + +function render() { + let demoUrl = new URL(`/docs/themes/${params.base}/demo.html`, location); + let pageParams = new URLSearchParams(params); + + for (let aspect in params) { + if (aspect !== 'base' && params[aspect]) { + demoUrl.searchParams.set(aspect, params[aspect]); + } + + if (!params[aspect] || (aspect === 'base' && params[aspect] === 'default') || !hasOverride(aspect)) { + pageParams.delete(aspect); + } + + // Output code snippet + if (aspect !== 'base') { + for (let codeExample of codeExamples) { + let token = codeExample[aspect]; + let value = params[aspect]; + + if (hasOverride(aspect)) { + // Update code example + let valueToken = [...token.querySelectorAll('.code-attr-value, .code-url')].pop(); + valueToken.textContent = replaceStyleSheetURL(valueToken.textContent, aspect, value); + + // Add code example to
+          codeExample.snippet.append(token);
+        } else {
+          token.remove();
+        }
+      }
+    }
+
+    // Update selects
+    if (selects[aspect].value === undefined) {
+      selects[aspect].setAttribute('value', params[aspect]);
+    } else {
+      selects[aspect].value = params[aspect];
+    }
+  }
+
+  // Update code snippet copy buttons
+  for (let copyButton of copyButtons) {
+    copyButton.value = copyButton.nextElementSibling.textContent;
+  }
+
+  // Update demo URL
+  demo.src = demoUrl;
+
+  // Update page URL. If there’s already a search, replace it.
+  // We don’t want to clog the user’s history while they iterate
+  let historyAction = location.search ? 'replaceState' : 'pushState';
+  history[historyAction](null, '', `?${pageParams}`);
+}
+
+const regexes = {
+  base: /\/themes\/([a-z-]+)\.css/,
+  palette: /\/color\/([a-z-]+)\.css/,
+  color: /\/themes\/([a-z-]+)\/color\.css/,
+  typography: /\/themes\/([a-z-]+)\/typography\.css/,
+};
+
+function replaceStyleSheetURL(url, name, value) {
+  let regex = regexes[name];
+  return url.replace(regex, (match, oldValue) => {
+    return match.replace(oldValue, value);
+  });
+}
+
+globalThis.params = params;
+render();
diff --git a/docs/docs/themes/remixed/index.njk b/docs/docs/themes/remixed/index.njk
new file mode 100644
index 000000000..ac790c6f3
--- /dev/null
+++ b/docs/docs/themes/remixed/index.njk
@@ -0,0 +1,84 @@
+---
+title: Remixed
+description: TODO
+isPro: true
+tags: pro
+---
+
+{% block header %}
+
+
+
+
+
+  
+  Please note that not all combinations will look good — once you’re mixing and matching, you’re on your own!
+
+{% endblock %}
+
+{% block afterContent %}
+{% markdown %}
+
+## How to use your remixed theme { #remixed-usage }
+
+You can import your remixed theme by importing the individual theme files from the Web Awesome CDN.
+{# Note: If you change the order here, you need to update index.js too #}
+{% set stylesheets = [
+  'styles/themes/default.css',
+  'styles/color/default.css',
+  'styles/themes/default/color.css',
+  'styles/themes/default/typography.css'
+] %}
+{% set stylesheet = 'styles/themes/default/color.css' %}
+{% include 'import-stylesheet-code.md.njk' %}
+
+
+{% endmarkdown %}
+{% endblock %}
diff --git a/docs/docs/themes/remixed/style.css b/docs/docs/themes/remixed/style.css
new file mode 100644
index 000000000..1584aa9ce
--- /dev/null
+++ b/docs/docs/themes/remixed/style.css
@@ -0,0 +1,18 @@
+#mix_and_match {
+  strong {
+    display: flex;
+    align-items: center;
+    gap: var(--wa-space-2xs);
+    margin-top: 1.2em;
+  }
+
+  wa-select::part(label) {
+    margin-block-end: 0;
+  }
+
+  wa-select[value='']::part(display-input),
+  wa-option[value=''] {
+    font-style: italic;
+    color: var(--wa-color-text-quiet);
+  }
+}
diff --git a/docs/docs/themes/showcase.css b/docs/docs/themes/showcase.css
index d073cf0f8..437fdcabd 100644
--- a/docs/docs/themes/showcase.css
+++ b/docs/docs/themes/showcase.css
@@ -9,6 +9,18 @@ body,
   overflow: hidden;
 }
 
+html.remixed {
+  .remix-link {
+    display: none;
+  }
+}
+
+html:not(.remixed) {
+  #mix_and_match {
+    display: none;
+  }
+}
+
 #mix_and_match {
   font-weight: var(--wa-font-weight-semibold);
   color: var(--wa-color-text-quiet);