diff --git a/docs/_includes/base.njk b/docs/_includes/base.njk
index 3742a94db..d0d8cafd4 100644
--- a/docs/_includes/base.njk
+++ b/docs/_includes/base.njk
@@ -15,6 +15,8 @@
{# Docs styles #}
+
+ {% block head %}{% endblock %}
diff --git a/docs/_includes/page-card.njk b/docs/_includes/page-card.njk
index 6afb3fb16..f63d813ed 100644
--- a/docs/_includes/page-card.njk
+++ b/docs/_includes/page-card.njk
@@ -5,6 +5,9 @@
{% include "svgs/" + (page.data.icon or "thumbnail-placeholder") + ".njk" %}
{{ page.data.title }}
+ {% if pageSubtitle -%}
+ {{ pageSubtitle }}
+ {%- endif %}
{% endif %}
diff --git a/docs/_includes/svgs/palette.njk b/docs/_includes/svgs/palette.njk
index dbe9f4208..1faa7f837 100644
--- a/docs/_includes/svgs/palette.njk
+++ b/docs/_includes/svgs/palette.njk
@@ -1,4 +1,4 @@
-{% set paletteId = page.fileSlug %}
+{% set paletteId = palette.fileSlug or page.fileSlug %}
{% set tints = [80, 60, 40, 20] %}
{% set width = 20 %}
{% set height = 13 %}
diff --git a/docs/_includes/svgs/theme-color.njk b/docs/_includes/svgs/theme-color.njk
new file mode 100644
index 000000000..0c41f7f5d
--- /dev/null
+++ b/docs/_includes/svgs/theme-color.njk
@@ -0,0 +1,24 @@
+{% set themeId = theme.fileSlug %}
+
+
+
+
+
+
+
+
+
+
A
+
A
+
A
+
A
+ {#
#}
+
+
A
+
A
+
A
+
A
+ {#
#}
+
+
+
diff --git a/docs/_includes/svgs/theme-typography.njk b/docs/_includes/svgs/theme-typography.njk
new file mode 100644
index 000000000..06ba0212a
--- /dev/null
+++ b/docs/_includes/svgs/theme-typography.njk
@@ -0,0 +1,16 @@
+{% set themeId = theme.fileSlug %}
+
+
diff --git a/docs/_layouts/theme.njk b/docs/_layouts/theme.njk
index 03be52f24..6dbfd782e 100644
--- a/docs/_layouts/theme.njk
+++ b/docs/_layouts/theme.njk
@@ -4,72 +4,131 @@
{% extends '../_includes/base.njk' %}
+{% block head %}
+
+
+
+{% endblock %}
+
{% block header %}
+
-
-
-
+
+
+
Remix
-
-
-
+
+ Customize this theme by changing its colors and/or remixing it with design elements from other themes!
+
+
+
- (Theme default)
-
{% for theme in collections.theme | sort %}
- {% if theme.fileSlug !== page.fileSlug %}
- {{ theme.data.title }}
- {% endif %}
+ {% set currentTheme = theme.fileSlug == page.fileSlug %}
+
+
+
+ {% include "svgs/theme-color.njk" %}
+
+
+ {{ theme.data.title }}
+ {% if theme.data.isPro %}PRO{% endif %}
+ {% if currentTheme %}This theme{% endif %}
+
+
+
{% endfor %}
-
+
- (Theme default)
-
- {% for p in collections.palette | sort %}
- {% if p.fileSlug !== palette %}
- {{ p.data.title }}
- {% endif %}
+ {% set defaultPalette = palette %}
+ {% for palette in collections.palette | sort %}
+ {% set currentPalette = palette.fileSlug == defaultPalette %}
+
+
+
+ {% include "svgs/" + (palette.data.icon or "thumbnail-placeholder") + ".njk" %}
+
+
+ {{ palette.data.title }}
+ {% if palette.data.isPro %}PRO{% endif %}
+ {% if currentPalette %}Theme default{% endif %}
+
+
+
+ {% endfor %}
+ {% set palette = defaultPalette %}
+
+
+
+
+ {% for hue in hues %}
+ {% set currentBrand = hue == brand %}
+
+ {{ hue | capitalize }}
+ {% if currentBrand %}Theme default{% endif %}
+
{% endfor %}
-
+
- (Theme default)
-
{% for theme in collections.theme | sort %}
- {% if theme.fileSlug !== page.fileSlug %}
- {{ theme.data.title }}
- {% endif %}
+ {% set currentTheme = theme.fileSlug == page.fileSlug %}
+
+
+
+ {% include "svgs/theme-typography.njk" %}
+
+
+ {{ theme.data.title }}
+ {% if theme.data.isPro %}PRO{% endif %}
+ {% if currentTheme %}This theme{% endif %}
+
+
+
{% endfor %}
+
-
-
-
-
-Default Color Palette
+Color
{% set paletteURL = '/docs/palettes/' + palette + '/' %}
-{% set themePage = page %}
-{% set page = paletteURL | getCollectionItemFromUrl %}
-
-{% include 'page-card.njk' %}
-
-{% set page = themePage %}
+
+
+ {% set themePage = page %}
+ {% set page = paletteURL | getCollectionItemFromUrl %}
+ {% set pageSubtitle = "Default color palette" %}
+ {% include 'page-card.njk' %}
+ {% set page = themePage %}
+
+
+
+ {{ brand | capitalize }}
+ Default brand color
+
+
{% endblock %}
{% block afterContent %}
diff --git a/docs/assets/styles/docs.css b/docs/assets/styles/docs.css
index 37ddd9fa3..7491fc306 100644
--- a/docs/assets/styles/docs.css
+++ b/docs/assets/styles/docs.css
@@ -360,7 +360,7 @@ wa-page > main:has(> .index-grid) {
}
&::part(header) {
- background-color: var(--wa-color-neutral-fill-quiet);
+ background-color: var(--header-background, var(--wa-color-neutral-fill-quiet));
border-bottom: none;
display: flex;
align-items: center;
@@ -543,23 +543,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/assets/styles/theme-icons.css b/docs/assets/styles/theme-icons.css
new file mode 100644
index 000000000..573e2aee1
--- /dev/null
+++ b/docs/assets/styles/theme-icons.css
@@ -0,0 +1,34 @@
+.theme-color-icon {
+ display: grid;
+ gap: var(--wa-space-xs);
+ grid-template-columns: repeat(4, auto);
+
+ div {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ box-sizing: border-box;
+ border-radius: var(--wa-border-radius-m);
+ background-color: var(--background-color);
+ border: var(--wa-border-width-s) var(--wa-border-style) var(--border-color);
+ padding: var(--wa-space-2xs) var(--wa-space-xs);
+ color: var(--text-color);
+ font-weight: var(--wa-font-weight-semibold);
+
+ &.plain {
+ font-weight: var(--wa-font-weight-bold);
+ }
+ }
+}
+
+.theme-typography-icon {
+ display: flex;
+ flex-direction: column;
+ gap: var(--wa-space-xs);
+
+ h3,
+ p {
+ margin-block: 0;
+ padding: 0;
+ }
+}
diff --git a/docs/docs/palettes/palettes.json b/docs/docs/palettes/palettes.json
index 1e08935c4..3c8d82240 100644
--- a/docs/docs/palettes/palettes.json
+++ b/docs/docs/palettes/palettes.json
@@ -3,6 +3,7 @@
"tags": ["palettes", "palette"],
"eleventyComputed": {
"snippet": ".wa-palette-{{ page.fileSlug }}",
- "icon": "palette"
+ "icon": "palette",
+ "file": "styles/color/{{ page.fileSlug }}.css"
}
}
diff --git a/docs/docs/themes/active.md b/docs/docs/themes/active.md
index 8b2f44b54..85dd20e01 100644
--- a/docs/docs/themes/active.md
+++ b/docs/docs/themes/active.md
@@ -4,4 +4,5 @@ description: Energetic and tactile, always in motion.
isPro: true
tags: pro
palette: rudimentary
+brand: green
---
diff --git a/docs/docs/themes/awesome.md b/docs/docs/themes/awesome.md
index 5a9e63e92..3954e27a1 100644
--- a/docs/docs/themes/awesome.md
+++ b/docs/docs/themes/awesome.md
@@ -3,4 +3,5 @@ title: Awesome
description: Punchy and vibrant, the rockstar of themes.
order: 0.2
palette: bright
+brand: blue
---
diff --git a/docs/docs/themes/brutalist.md b/docs/docs/themes/brutalist.md
index 497c0a6ff..35cda07dd 100644
--- a/docs/docs/themes/brutalist.md
+++ b/docs/docs/themes/brutalist.md
@@ -4,4 +4,5 @@ description: Sharp, square, and unapologetically bold.
isPro: true
tags: pro
palette: default
+brand: blue
---
diff --git a/docs/docs/themes/default.md b/docs/docs/themes/default.md
index 7aa4f6802..e2da0baaf 100644
--- a/docs/docs/themes/default.md
+++ b/docs/docs/themes/default.md
@@ -3,4 +3,5 @@ title: Default
description: Your trusty companion, like a perfectly broken-in pair of jeans.
order: 0
palette: default
+brand: blue
---
diff --git a/docs/docs/themes/demo.njk b/docs/docs/themes/demo.njk
index c511f639b..634fb20ad 100644
--- a/docs/docs/themes/demo.njk
+++ b/docs/docs/themes/demo.njk
@@ -41,11 +41,13 @@ function updateTheme() {
const stylesheetURLs = {
colors: id => `/dist/styles/themes/${ id }/color.css`,
palette: id => `/dist/styles/color/${ id }.css`,
+ brand: id => `/dist/styles/brand/${ id }.css`,
typography: id => `/dist/styles/themes/${ id }/typography.css`
};
const icons = {
colors: 'palette',
palette: 'swatchbook',
+ brand: 'droplet',
typography: 'font-case'
}
@@ -58,7 +60,7 @@ function updateTheme() {
for (let name in stylesheetURLs) {
if (params.get(name)) {
let url = stylesheetURLs[name](params.get(name));
- script.insertAdjacentHTML('afterend', ``);
+ script.insertAdjacentHTML('beforebegin', ``);
let docsURL = (name === 'palette' ? '/docs/palettes/' : '/docs/themes/') + params.get(name) + '/';
let title = params.get(name).replace(/^[a-z]/g, c => c.toUpperCase());
let icon = icons[name];
@@ -70,7 +72,7 @@ function updateTheme() {
p.hidden = msgs.length === 0;
if (msgs.length) {
let icon =
- p.innerHTML = ` Remixed ` + msgs.map(msg => `
+ p.innerHTML = ` Remixed ` + msgs.map(msg => `
${ msg }`).join(' ');
}
}
diff --git a/docs/docs/themes/glossy.md b/docs/docs/themes/glossy.md
index da84f5a86..60f8f8d66 100644
--- a/docs/docs/themes/glossy.md
+++ b/docs/docs/themes/glossy.md
@@ -4,4 +4,5 @@ description: Bustling with plenty of luster and shine.
isPro: true
tags: pro
palette: elegant
+brand: indigo
---
diff --git a/docs/docs/themes/matter.md b/docs/docs/themes/matter.md
index 5b45c14dc..5dfb418c5 100644
--- a/docs/docs/themes/matter.md
+++ b/docs/docs/themes/matter.md
@@ -4,6 +4,7 @@ description: Digital design inspired by the real world.
isPro: true
tags: pro
palette: mild
+brand: indigo
---
Set the page theme to "{{ title }}" from the top right to preview the following examples.
diff --git a/docs/docs/themes/playful.md b/docs/docs/themes/playful.md
index 8cc08e1ec..514343eb1 100644
--- a/docs/docs/themes/playful.md
+++ b/docs/docs/themes/playful.md
@@ -4,4 +4,5 @@ description: Cheerful and engaging, like a playground on screen.
isPro: true
tags: pro
palette: rudimentary
+brand: purple
---
diff --git a/docs/docs/themes/premium.md b/docs/docs/themes/premium.md
index 40f38b00e..919d8847a 100644
--- a/docs/docs/themes/premium.md
+++ b/docs/docs/themes/premium.md
@@ -4,4 +4,5 @@ description: The ultimate in sophistication and style.
isPro: true
tags: pro
palette: anodized
+brand: cyan
---
diff --git a/docs/docs/themes/remix.css b/docs/docs/themes/remix.css
new file mode 100644
index 000000000..e9472e600
--- /dev/null
+++ b/docs/docs/themes/remix.css
@@ -0,0 +1,127 @@
+#mix_and_match {
+ margin-block-end: var(--wa-space-2xl);
+
+ &::part(content) {
+ display: flex;
+ gap: var(--wa-space-xl);
+ padding-block-start: var(--wa-space-m);
+ }
+
+ > [slot='summary'] {
+ margin: 0;
+
+ wa-icon:first-of-type {
+ vertical-align: -0.15em;
+ color: var(--wa-color-text-quiet);
+ }
+
+ wa-icon#what-is-remixing {
+ vertical-align: -0.1em;
+ font-size: var(--wa-font-size-s);
+ color: var(--wa-color-text-quiet);
+ }
+ }
+
+ wa-select {
+ &::part(label) {
+ margin-block-end: 0;
+ }
+ }
+
+ wa-select:has(> wa-option > wa-card) {
+ &::part(listbox) {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
+ width: min(90vw, 800px);
+ }
+
+ &:state(blank)::part(display-input) {
+ font-style: italic;
+ color: var(--wa-color-text-quiet);
+ }
+ }
+
+ wa-option:has(> wa-card) {
+ position: relative;
+ padding: var(--wa-space-smaller);
+
+ &::part(checked-icon) {
+ position: absolute;
+ inset-block-start: calc(var(--wa-space-smaller) - 0.5em);
+ inset-inline-end: calc(var(--wa-space-smaller) - 0.5em);
+ width: 1em;
+ height: 1em;
+ line-height: 1em;
+ padding: 0.4em;
+ border-radius: var(--wa-border-radius-circle);
+ text-align: center;
+ background: var(--wa-color-brand-fill-loud);
+ color: var(--wa-color-brand-on-loud);
+ font-size: var(--wa-font-size-xs);
+ }
+
+ wa-card {
+ --spacing: var(--wa-space-smaller);
+
+ &::part(body) {
+ background: var(--wa-color-neutral-fill-quiet);
+ padding-block: var(--wa-space-s);
+ }
+ }
+
+ &:state(current),
+ &:state(hover),
+ &:state(selected),
+ &:hover {
+ background: transparent;
+ }
+
+ &:hover {
+ wa-card {
+ border-color: var(--wa-color-brand-border-loud);
+ box-shadow: 0 0 0 var(--wa-border-width-m) var(--wa-color-brand-border-normal);
+ }
+ }
+
+ &[aria-selected='true'] {
+ wa-card {
+ --border-color: var(--wa-color-brand-border-loud);
+ box-shadow: 0 0 0 var(--wa-border-width-m) var(--wa-color-brand-border-loud);
+
+ &::part(body) {
+ background: var(--wa-color-brand-fill-quiet);
+ }
+ }
+ }
+ }
+
+ .selected-swatch,
+ wa-select[name='brand'] wa-option::before {
+ content: '';
+ display: inline-block;
+ width: 1.2em;
+ aspect-ratio: 1;
+ flex: none;
+ border-radius: var(--wa-border-radius-m);
+ background: var(--color);
+ border: 1px solid var(--wa-color-surface-default);
+ }
+
+ wa-select[name='brand'] wa-option {
+ white-space: nowrap;
+
+ &::before {
+ width: 1em;
+ margin-inline: var(--wa-space-xs);
+ }
+
+ &::part(checked-icon) {
+ order: 2;
+ }
+ }
+}
+
+#test_select wa-option:state(selected) {
+ outline: 2px solid red;
+ background: yellow;
+}
diff --git a/docs/docs/themes/remix.js b/docs/docs/themes/remix.js
new file mode 100644
index 000000000..e5e3add7e
--- /dev/null
+++ b/docs/docs/themes/remix.js
@@ -0,0 +1,146 @@
+await Promise.all(['wa-select', 'wa-option', 'wa-details'].map(tag => customElements.whenDefined(tag)));
+const domChange = document.startViewTransition ? document.startViewTransition.bind(document) : fn => fn();
+
+let selects, data;
+
+let computed = {
+ get isRemixed() {
+ return Object.values(data.params).filter(Boolean).length > 0;
+ },
+ get palette() {
+ return data.params.palette || data.defaultParams.palette;
+ },
+ get brand() {
+ return data.params.brand || data.defaultParams.brand;
+ },
+};
+
+function selectsChanged(event) {
+ data.params[event.target.name] = event.target.value;
+ render(event.target.name);
+}
+
+function init() {
+ selects = Object.fromEntries(
+ [...document.querySelectorAll('#mix_and_match wa-select')].map(select => [select.getAttribute('name'), select]),
+ );
+
+ data = {
+ baseTheme: wa_data.baseTheme,
+ themes: wa_data.themes,
+ palettes: wa_data.palettes,
+ defaultParams: {
+ colors: '',
+ get palette() {
+ let colors = data.params.colors || data.baseTheme;
+ return data.themes[colors].palette;
+ },
+ get brand() {
+ let colors = data.params.colors || data.baseTheme;
+ return data.themes[colors].brand;
+ },
+ typography: '',
+ },
+ params: { colors: '', palette: '', brand: '', typography: '' },
+ urlParams: new URLSearchParams(location.search),
+ };
+
+ // Read URL params and apply them. This facilitates permalinks.
+ if (location.search) {
+ for (let aspect in data.params) {
+ if (data.urlParams.has(aspect)) {
+ data.params[aspect] = data.urlParams.get(aspect);
+ }
+ }
+ }
+
+ if (computed.isRemixed) {
+ // Start with the remixing UI open if the theme has been remixed
+ mix_and_match.setAttribute('open', '');
+ mix_and_match.open = true;
+ }
+
+ for (let name in selects) {
+ selects[name].addEventListener('change', selectsChanged);
+ }
+
+ Promise.all(Object.values(selects).map(select => select.updateComplete)).then(() => render());
+
+ return { selects, data, computed, render };
+}
+
+globalThis.remixApp = init();
+
+// Async load CSS for other themes *before* current theme stylesheet
+let themeStylesheet = document.querySelector('#theme-stylesheet');
+
+for (const theme in data.themes) {
+ themeStylesheet.insertAdjacentHTML(
+ 'beforebegin',
+ `
+ `,
+ );
+}
+
+for (const palette in data.palettes) {
+ themeStylesheet.insertAdjacentHTML(
+ 'beforebegin',
+ ``,
+ );
+}
+
+function setDefault(select, value) {
+ let oldDefaultOption = select.querySelector(`wa-option[value=""]:not([data-id="${value}"])`);
+ let newDefaultOption = select.querySelector(`wa-option[value="${value}"]`);
+
+ if (oldDefaultOption) {
+ oldDefaultOption.value = oldDefaultOption.dataset.id;
+ }
+
+ if (newDefaultOption) {
+ newDefaultOption.dataset.id ??= newDefaultOption.value;
+ newDefaultOption.value = '';
+ }
+}
+
+function render(changedAspect) {
+ let url = new URL(demo.src);
+
+ if (!changedAspect || changedAspect === 'colors') {
+ // Update the default palette when the theme colors change to the default palette of that theme
+ setDefault(selects.palette, data.defaultParams.palette);
+ setDefault(selects.brand, data.defaultParams.brand);
+ }
+
+ let brand = data.params.brand || data.defaultParams.brand;
+ selects.brand.style.setProperty('--color', `var(--wa-color-${brand})`);
+ selects.brand.className = `wa-palette-${computed.palette}`;
+
+ for (let aspect in data.params) {
+ let value = data.params[aspect];
+
+ if (value) {
+ data.urlParams.set(aspect, value);
+ } else {
+ data.urlParams.delete(aspect);
+ }
+
+ selects[aspect].value = value;
+ }
+
+ // Update demo URL
+ domChange(() => {
+ url.search = data.urlParams;
+ demo.src = url;
+ return new Promise(resolve => (demo.onload = resolve));
+ });
+
+ // 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, '', `?${data.urlParams}`);
+}
+
+addEventListener('turbo:render', event => {
+ remixApp = init();
+});
diff --git a/docs/docs/themes/showcase.css b/docs/docs/themes/showcase.css
index d073cf0f8..b4693e0fe 100644
--- a/docs/docs/themes/showcase.css
+++ b/docs/docs/themes/showcase.css
@@ -14,7 +14,18 @@ body,
color: var(--wa-color-text-quiet);
wa-icon {
- vertical-align: middle;
+ vertical-align: -0.15em;
+ }
+
+ > strong {
+ margin-inline-end: var(--wa-space-2xs);
+ }
+
+ wa-badge {
+ > a {
+ text-decoration: none;
+ color: inherit;
+ }
}
}
diff --git a/docs/docs/themes/tailspin.md b/docs/docs/themes/tailspin.md
index 0623424ce..212376fe7 100644
--- a/docs/docs/themes/tailspin.md
+++ b/docs/docs/themes/tailspin.md
@@ -4,4 +4,5 @@ description: Like a bird in flight, guiding you from there to here.
isPro: true
tags: pro
palette: vogue
+brand: indigo
---
diff --git a/docs/docs/themes/themes.json b/docs/docs/themes/themes.json
index 11dfb3eb0..3af2e88e3 100644
--- a/docs/docs/themes/themes.json
+++ b/docs/docs/themes/themes.json
@@ -1,5 +1,9 @@
{
"layout": "theme.njk",
"wide": true,
- "tags": ["themes", "theme"]
+ "tags": ["themes", "theme"],
+ "brand": "blue",
+ "eleventyComputed": {
+ "file": "styles/themes/{{ page.fileSlug }}.css"
+ }
}
diff --git a/src/components/option/option.ts b/src/components/option/option.ts
index cc67a0831..d2b3e78c2 100644
--- a/src/components/option/option.ts
+++ b/src/components/option/option.ts
@@ -155,6 +155,8 @@ export default class WaOption extends WebAwesomeElement {
console.error(`Option values cannot include a space. All spaces have been replaced with underscores.`, this);
this.value = this.value.replace(/ /g, '_');
}
+
+ this.handleDefaultSlotChange();
}
if (changedProperties.has('current')) {