Compare commits

...

34 Commits

Author SHA1 Message Date
Cory LaViska
ac376afbaa prevent card example from overflowing 2025-03-12 14:44:05 -04:00
Cory LaViska
1283a696a5 fix switch + tooltip behavior (#793) 2025-03-12 18:22:23 +00:00
Cory LaViska
d12b97b0b0 fix wa-pill and wa-input[pill] styles (#791) 2025-03-12 16:19:50 +00:00
Lea Verou
e5c2884880 [Tooltip] Specify inherited CSS properties on host, fixes #773 (#774)
* [Tooltip] Specify inherited CSS properties on host, fixes #773

* Remove unused `--show-delay` and `--hide-delay`
2025-03-10 15:08:27 -04:00
Lea Verou
1d600a77c4 Fix #566 2025-03-10 14:15:06 -04:00
lindsaym-fa
db3c568ba2 Add generated orange to Anodized palette 2025-03-05 22:27:11 -05:00
lindsaym-fa
4bb9805ba6 Add generated orange to Bright palette 2025-03-05 22:27:11 -05:00
lindsaym-fa
bd935fa8d5 Add generated orange to Classic palette 2025-03-05 22:27:11 -05:00
lindsaym-fa
c3e582b47b Add generated orange to Natural palette 2025-03-05 22:27:11 -05:00
lindsaym-fa
4d094a4e19 Add generated orange to Rudimentary palette 2025-03-05 22:27:11 -05:00
lindsaym-fa
782c404bdf Add generated orange to Default palette 2025-03-05 22:27:11 -05:00
lindsaym-fa
f1438981b2 Add generated orange to Elegant palette 2025-03-05 22:27:11 -05:00
lindsaym-fa
18b88c2f5c Add generated orange to Mild palette 2025-03-05 22:27:11 -05:00
lindsaym-fa
a2d85f49a3 Add generated orange to Vogue palette 2025-03-05 22:27:11 -05:00
Lea Verou
be00026cd3 Update postprocess.js 2025-03-05 22:27:11 -05:00
Lea Verou
58ed88bc5a Add orange to list of hues 2025-03-05 22:27:11 -05:00
Lea Verou
1d14e186f3 Generate missing hues from neighboring hues 2025-03-05 22:27:11 -05:00
Lea Verou
5f672aabc2 Refactor: variable rename for consistency 2025-03-05 22:27:11 -05:00
Lea Verou
db08e12a32 Pave the way for being able to have core colors that are not mapped to any tint 2025-03-05 22:27:11 -05:00
Lea Verou
e0fc639226 Only use hex when color is within sRGB 2025-03-05 22:27:11 -05:00
Lea Verou
e6c662b543 tintless.js -> postprocess.js 2025-03-05 22:27:11 -05:00
lindsaym-fa
d1de9a9a73 Fix incorrect sizing tokens in size utilities 2025-02-26 01:01:39 -05:00
lindsaym-fa
4931de8eb4 Fix text color for filled appearance 2025-02-26 01:01:39 -05:00
Lea Verou
71e7227763 Theme remixing fix: Order of params should not matter (#772)
Also renamed the `theme` export to `getThemeCode` since it was being renamed everywhere it was imported.
2025-02-21 14:03:55 -05:00
Lea Verou
dd671e15aa Changelog (#770) 2025-02-21 13:14:19 -05:00
Cory LaViska
2daeea0349 3.0.0-alpha.11 2025-02-21 12:53:05 -05:00
Cory LaViska
3cb6625c1d update changelog 2025-02-21 12:52:51 -05:00
Lea Verou
c4b5446d01 Fix boundingClientRect issue for elements whose host is display: contents 2025-02-21 12:02:20 -05:00
Lindsay M
41affca083 Allow color tweak tags to wrap (#769) 2025-02-21 11:50:13 -05:00
Lea Verou
132dbfabcc Gray tweaks prototype (#761)
Co-authored-by: Lindsay M <126139086+lindsaym-fa@users.noreply.github.com>
2025-02-20 12:10:43 -05:00
lindsaym-fa
4fc6224464 Fix missing listbox border 2025-02-19 14:54:11 -05:00
Lea Verou
4921d1c32e Save palette MVP, fixes #746 (#755) 2025-02-18 16:11:40 -05:00
Lindsay M
54d71d2319 Use tintless and clamped brand colors in themes (#754)
* Use tintless `brand` colors, cutoffs in themes

* Re-add `40-min`, add `70-max`

* Fix mistakes in Mellow theme

* Revert accidental Premium brand color change

* Add changelog
2025-02-18 10:22:32 -05:00
Cory LaViska
c1ecca0169 fix select hint (#760) 2025-02-18 15:09:52 +00:00
58 changed files with 1683 additions and 434 deletions

View File

@@ -1 +1 @@
["red", "yellow", "green", "cyan", "blue", "indigo", "purple", "pink", "gray"]
["red", "orange", "yellow", "green", "cyan", "blue", "indigo", "purple", "pink", "gray"]

View File

@@ -10,6 +10,7 @@
<script type="module" src="/assets/scripts/turbo.js"></script>
<script type="module" src="/assets/scripts/search.js"></script>
<script type="module" src="/assets/scripts/outline.js"></script>
{% if hasSidebar %}<script type="module" src="/assets/scripts/sidebar-tweaks.js"></script>{% endif %}
<script defer data-domain="backers.webawesome.com" src="https://plausible.io/js/script.js"></script>
{# Docs styles #}

View File

@@ -19,12 +19,24 @@
{% for tint_fg in tints | reverse -%}
{% set color_fg = palettes[paletteId][hue][tint_fg] %}
{% if (tint_fg - tint_bg) | abs == difference %}
<td data-tint-bg="{{ tint_bg }}" data-tint-fg="{{ tint_fg }}">
<div class="color swatch" style="--color: var(--wa-color-{{ hue }}-{{ tint_bg }}); color: var(--wa-color-{{ hue }}-{{ tint_fg }})">
{% set contrast_wcag = '' %}
{% if color_fg and color_bg %}
{% set contrast_wcag = color_bg.contrast(color_fg, 'WCAG21') %}
{% endif %}
{% set contrast_wcag = '' %}
{% if color_fg and color_bg -%}
{% set contrast_wcag = color_bg.contrast(color_fg, 'WCAG21') %}
{%- endif %}
<td v-for="contrast of [contrasts.{{ hue }}['{{ tint_bg }}']['{{ tint_fg }}']]"
data-tint-bg="{{ tint_bg }}" data-tint-fg="{{ tint_fg }}" data-original-contrast="{{ contrast_wcag }}">
<div v-content:number="contrast.value"
class="color swatch" :class="{
'value-up': contrast.value - contrast.original > 0.0001,
'value-down': contrast.original - contrast.value > 0.0001,
'contrast-fail': contrast.value < {{ minContrast }}
}"
style="--color: var(--wa-color-{{ hue }}-{{ tint_bg }}); color: var(--wa-color-{{ hue }}-{{ tint_fg }})"
:style="{
'--color': contrast.bgColor,
color: contrast.fgColor,
}"
>
{% if contrast_wcag %}
{{ contrast_wcag | number({maximumSignificantDigits: 2}) }}
{% else %}

View File

@@ -1,22 +1,81 @@
{% set hasSidebar = true %}
{% set hasOutline = true %}
{% set paletteId = page.fileSlug %}
{% set tints = ["95", "90", "80", "70", "60", "50", "40", "30", "20", "10", "05"] %}
{% extends '../_layouts/block.njk' %}
{% extends '../_includes/base.njk' %}
{% block head %}
<style>@import url('/dist/styles/color/{{ paletteId }}.css') layer(palette.{{ paletteId }});</style>
<link href="{{ page.url }}../tweak.css" rel="stylesheet">
<script type="module" src="{{ page.url }}../tweak.js"></script>
{% endblock %}
{% block header %}
<div id="palette-app" data-palette-id="{{ paletteId }}">
<div
:class="{
tweaking: tweaking.chroma,
'tweaking-chroma': tweaking.chroma,
'tweaking-hue': tweaking.chroma,
'tweaking-gray-chroma': tweaking.grayChroma,
'tweaked-chroma': tweaked?.chroma,
'tweaked-hue': tweaked?.hue,
'tweaked-any': tweaked
}"
:style="{
'--chroma-scale': chromaScale,
'--gray-chroma': tweaked?.grayChroma ? grayChroma : '',
}">
{% include 'breadcrumbs.njk' %}
<h1 v-if="saved" class="title">
{% raw %}{{ saved.title }}{% endraw %}
<wa-icon-button name="pencil" label="Rename palette" @click="rename"></wa-icon-button>
<wa-icon-button class="delete" name="trash" label="Delete palette" @click="deleteSaved"></wa-icon-button>
</h1>
<h1 v-if="!saved" class="title">{{ title }}</h1>
<div class="block-info">
<code class="class">.wa-palette-{{ paletteId }}</code>
{% include '../_includes/status.njk' %}
{% if not isPro %}
<wa-badge class="pro" v-if="tweaked">PRO</wa-badge>
{% endif %}
</div>
{% if description %}
<p class="summary">
{{ description | inlineMarkdown | safe }}
</p>
{% endif %}
{% endblock %}
{% block afterContent %}
{% set tints = ["95", "90", "80", "70", "60", "50", "40", "30", "20", "10", "05"] %}
{% set maxChroma = 0 %}
<div id="palette-app">
<div
:class="{ tweaking: tweaking.chroma, 'tweaking-chroma': tweaking.chroma, 'tweaking-hue': tweaking.chroma }"
:style="{ '--chroma-scale': chromaScale }">
<wa-callout size="small" class="tweaked-callout" variant="warning">
<wa-icon name="sliders-simple" slot="icon" variant="regular"></wa-icon>
This palette has been tweaked.
<div class="wa-cluster wa-gap-xs">
<wa-tag v-for="tweakHumanReadable, param in tweaksHumanReadable" removable @wa-remove="reset(param)">{% raw %}{{ tweakHumanReadable }}{% endraw %}</wa-tag>
</div>
<wa-button @click="reset()" appearance="outlined" variant="danger">
<span slot="prefix" class="icon-modifier">
<wa-icon name="circle-xmark" variant="regular"></wa-icon>
</span>
Reset
</wa-button>
<wa-button v-if="!saved" @click="save" variant="success">
<span slot="prefix" class="icon-modifier">
<wa-icon name="sidebar" variant="regular"></wa-icon>
<wa-icon name="circle-plus" class="modifier" style="color: light-dark(var(--wa-color-green-70), var(--wa-color-green-60));"></wa-icon>
</span>
Save
</wa-button>
</wa-callout>
<table class="colors main wa-palette-{{ paletteId }}">
<thead>
@@ -31,25 +90,67 @@
{# Initialize to last hue before gray #}
{%- set hueBefore = hues[hues|length - 2] -%}
{% for hue in hues -%}
{%- set coreColor = palettes[paletteId][hue][palettes[paletteId][hue].maxChromaTint] -%}
{% set coreTint = palettes[paletteId][hue].maxChromaTint %}
{%- set coreColor = palettes[paletteId][hue][coreTint] -%}
{%- set maxChroma = coreColor.c if coreColor.c > maxChroma else maxChroma -%}
<tr data-hue="{{ hue }}" class="color-scale" :class="{tweaking: tweaking.{{ hue }}, tweaked: hueShifts.{{ hue }} }"
{% if hue === 'gray' %}
<tr data-hue="{{ hue }}" class="color-scale"
:class="{tweaking: tweaking.grayChroma, tweaked: tweaked.grayChroma || tweaked.grayColor }">
{% else %}
<tr data-hue="{{ hue }}" class="color-scale"
:class="{tweaking: tweaking.{{ hue }}, tweaked: hueShifts.{{ hue }} }"
:style="{ '--hue-shift': hueShifts.{{ hue }} || '' }">
{% endif %}
<th>
{{ hue | capitalize }}
</th>
<td class="core-column" style="--color: var(--wa-color-{{ hue }})">
{% if hue !== 'gray' %}
{%- set hueAfter = hues[loop.index0 + 1] -%}
{%- set hueAfter = hues[0] if hueAfter == 'gray' else hueAfter -%}
{%- set minShift = hueRanges[hue].min - coreColor.h | round -%}
{%- set maxShift = hueRanges[hue].max - coreColor.h | round -%}
<td class="core-column"
style="--color: var(--wa-color-{{ hue }})"
:style="{
'--color-tweaked': colors.{{ hue }}[{{ coreTint }}],
'--color-gray-undertone': colors[grayColor][{{coreTint}}],
'--color-tweaked-no-gray-chroma': colorsMinusGrayChroma.{{ hue }}[{{ coreTint }}],
}">
<wa-dropdown>
<div slot="trigger" id="core-{{ hue }}-swatch" data-tint="core" class="color swatch" style="background-color: var(--wa-color-{{ hue }}); color: var(--wa-color-{{ hue }}-{{ '05' if palettes[paletteId][hue].maxChromaTint > 60 else '95' }});">
{{ palettes[paletteId][hue].maxChromaTint }}
<wa-icon name="sliders-simple" class="tweak-icon"></wa-icon>
</div>
<div class="popup">
<div slot="trigger" id="core-{{ hue }}-swatch" data-tint="core" class="color swatch"
style="background-color: var(--wa-color-{{ hue }}); color: var(--wa-color-{{ hue }}-{{ '05' if palettes[paletteId][hue].maxChromaTint > 60 else '95' }});"
>
{{ palettes[paletteId][hue].maxChromaTint }}
<wa-icon name="sliders-simple" class="tweak-icon"></wa-icon>
</div>
<div class="popup">
{% if hue === 'gray' %}
<wa-radio-group class="core-color" orientation="horizontal" v-model="grayColor">
{% for h in hues -%}
{%- if h !== 'gray' -%}
<wa-radio-button id="gray-undertone-{{ h }}" value="{{ h }}" label="{{ h | capitalize }}" style="--color: var(--wa-color-{{ h }})"></wa-radio-button>
<wa-tooltip for="gray-undertone-{{ h }}" hoist>
{{ h | capitalize }}
</wa-tooltip>
{%- endif -%}
{%- endfor -%}
<div slot="label">
Gray undertone
</div>
</wa-radio-group>
<div class="decorated-slider gray-chroma-slider" :style="{'--max': maxGrayChroma}">
<wa-slider name="gray-chroma" v-model="grayChroma" ref="grayChromaSlider"
value="0" min="0" :max="maxGrayChroma" step="0.01"
@input="tweaking.grayChroma = true" @change="tweaking.grayChroma = false">
<div slot="label">
Gray colorfulness
<wa-icon-button @click="grayChroma = originalGrayChroma" class="clear-button" name="circle-xmark" library="system" variant="regular" label="Reset"></wa-icon-button>
</div>
</wa-slider>
<div class="label-min">Neutral</div>
<div class="label-max" v-content="moreHue[grayColor]">Warmer/Cooler</div>
</div>
{% else %}
{%- set hueAfter = hues[loop.index0 + 1] -%}
{%- set hueAfter = hues[0] if hueAfter == 'gray' else hueAfter -%}
{%- set minShift = hueRanges[hue].min - coreColor.h | round -%}
{%- set maxShift = hueRanges[hue].max - coreColor.h | round -%}
<div class="decorated-slider hue-shift-slider" style="--min: {{ minShift }}; --max: {{ maxShift }};">
<wa-slider name="{{ hue }}-shift" v-model="hueShifts.{{ hue }}" value="0"
min="{{ minShift }}" max="{{ maxShift }}" step="1"
@@ -63,23 +164,23 @@
<div class="label-min">More {{hueBefore}}</div>
<div class="label-max">More {{hueAfter}}</div>
</div>
{%- set hueBefore = hue -%}
{% endif %}
<div class="wa-gap-s">
<code>--wa-color-{{ hue }}</code>
<wa-copy-button value="--wa-color-{{ hue }}" copy-label="--wa-color-{{ hue }}"></wa-copy-button>
</div>
</div>`
<code>--wa-color-{{ hue }}</code>
<wa-copy-button value="--wa-color-{{ hue }}" copy-label="--wa-color-{{ hue }}"></wa-copy-button>
</div>
</div>`
</wa-dropdown>
{%- set hueBefore = hue -%}
{% else %}
<div id="core-{{ hue }}-swatch" class="color swatch" data-tint="core" style="background-color: var(--wa-color-{{ hue }}); color: var(--wa-color-{{ hue }}-{{ '05' if palettes[paletteId][hue].maxChromaTint > 60 else '95' }});">
{{ palettes[paletteId][hue].maxChromaTint }}
</div>
{% endif %}
</td>
{% for tint in tints -%}
{%- set color = palettes[paletteId][hue][tint] -%}
<td data-tint="{{ tint }}" style="--color: var(--wa-color-{{ hue }}-{{ tint }})">
<div class="color swatch" style="background-color: var(--wa-color-{{ hue }}-{{ tint }})">
<td data-tint="{{ tint }}" style="--color: var(--wa-color-{{ hue }}-{{ tint }})"
:style="{
'--color-tweaked': colors.{{ hue }}[{{ tint }}],
'--color-tweaked-no-gray-chroma': colorsMinusGrayChroma.{{ hue }}[{{ tint }}],
}">
<div class="color swatch" style="--color: var(--wa-color-{{ hue }}-{{ tint }})">
<wa-copy-button value="--wa-color-{{ hue }}-{{ tint }}" copy-label="--wa-color-{{ hue }}-{{ tint }}"></wa-copy-button>
</div>
</td>
@@ -94,7 +195,8 @@
<div class="decorated-slider chroma-scale-slider wa-palette-{{ paletteId }}"
:class="{ tweaked: chromaScale !== 1 }"
style="--min: {{ chromaScaleBounds[0] }}; --max: {{ chromaScaleBounds[1] }};">
<wa-slider name="chroma-scale" v-model="chromaScale" value="1" step="0.01"
<wa-slider name="chroma-scale" ref="chromaScaleSlider"
v-model="chromaScale" value="1" step="0.01"
min="{{ chromaScaleBounds[0] }}" max="{{ chromaScaleBounds[1] }}"
@input="tweaking.chroma = true"
@change="tweaking.chroma = false">
@@ -107,18 +209,6 @@ style="--min: {{ chromaScaleBounds[0] }}; --max: {{ chromaScaleBounds[1] }};">
<div class="label-max">More vibrant</div>
</div>
</div>
</div>
<script>
globalThis.wa_data = {
paletteId: '{{ paletteId }}',
colors: {{ palettes[paletteId] | dump | safe }},
maxChroma: {{ maxChroma }},
}
</script>
<script type="module" src="{{ page.url }}../tweak.js"></script>
<h2>Used By</h2>
<section class="index-grid">
@@ -201,7 +291,28 @@ If you are using a Web Awesome theme that uses this palette, it will already be
To use a different palette than a theme default, or to use it in a custom theme, you can import this palette directly from the Web Awesome CDN.
{% set stylesheet = 'styles/color/' + page.fileSlug + '.css' %}
{% include 'import-stylesheet-code.md.njk' %}
<wa-tab-group class="import-stylesheet-code">
<wa-tab panel="html">In HTML</wa-tab>
<wa-tab panel="css">In CSS</wa-tab>
<wa-tab-panel name="html">
Add the following code to the `<head>` of your page:
```html { v-content:html="code.html.highlighted" }
<link rel="stylesheet" href="{% cdnUrl stylesheet %}" />
```
</wa-tab-panel>
<wa-tab-panel name="css">
Add the following code at the top of your CSS file:
```css { v-content:html="code.css.highlighted" }
@import url('{% cdnUrl stylesheet %}');
```
</wa-tab-panel>
</wa-tab-group>
{% endmarkdown %}
</div></div> {# end palette app #}
{% endblock %}

View File

@@ -0,0 +1,254 @@
const sidebar = (globalThis.sidebar = {});
sidebar.palettes = {
render() {
if (this.saved.length === 0) {
return;
}
for (let palette of this.saved) {
sidebar.palette.render(palette);
}
sidebar.updateCurrent();
},
updateSaved() {
this.saved = localStorage.savedPalettes ? JSON.parse(localStorage.savedPalettes) : [];
},
save(saved = this.saved) {
this.saved = saved ?? [];
if (saved.length > 0) {
localStorage.savedPalettes = JSON.stringify(saved);
} else {
delete localStorage.savedPalettes;
}
},
};
sidebar.palettes.updateSaved();
addEventListener('storage', event => sidebar.palettes.updateSaved());
sidebar.palette = {
getUid() {
let savedPalettes = sidebar.palettes.saved;
let uids = new Set(savedPalettes.map(p => p.uid));
if (savedPalettes.length === 0) {
return 1;
}
// Find first available number
for (let i = 1; i <= savedPalettes.length + 1; i++) {
if (!uids.has(i)) {
return i;
}
}
},
equals(p1, p2) {
if (!p1 || !p2) {
return false;
}
return p1.id === p2.id && p1.uid === p2.uid;
},
delete(palette) {
let savedPalettes = sidebar.palettes.saved;
let count = savedPalettes.length;
if (count === 0) {
return;
}
// TODO improve UX of this
if (!confirm(`Are you sure you want to delete palette “${palette.title}”?`)) {
return;
}
savedPalettes = savedPalettes.filter(p => !sidebar.palette.equals(palette, p));
if (savedPalettes.length === count) {
// Nothing was removed
return;
}
// Update UI
let pathname = `/docs/palettes/${palette.id}/`;
let url = pathname + palette.search;
let uls = new Set();
for (let a of document.querySelectorAll(`#sidebar a[href="${url}"]`)) {
let li = a.closest('li');
let ul = li.closest('ul');
uls.add(ul);
li.remove();
}
// Remove empty lists
for (let ul of uls) {
if (!ul.children.length) {
ul.remove();
}
}
sidebar.updateCurrent();
sidebar.palettes.save(savedPalettes);
if (sidebar.palette.equals(globalThis.paletteApp?.saved, palette)) {
paletteApp.postDelete();
}
},
getSaved(palette, savedPalettes = sidebar.palettes.saved) {
return savedPalettes.find(p => sidebar.palette.equals(p, palette));
},
render(palette) {
// Find existing <a>
let { title, id, search, uid } = palette;
for (let a of document.querySelectorAll(`#sidebar a[href^="/docs/palettes/${id}/"][data-uid="${uid}"]`)) {
// Palette already in sidebar, just update it
a.textContent = palette.title;
a.href = `/docs/palettes/${id}/${search}`;
return;
}
let pathname = `/docs/palettes/${id}/`;
let url = pathname + search;
let parentA = document.querySelector(`a[href="${pathname}"]`);
let parentLi = parentA?.closest('li');
let a;
if (parentLi) {
a = Object.assign(document.createElement('a'), { href: url, textContent: title });
a.dataset.uid = uid;
let badges = [...parentLi.querySelectorAll('wa-badge')].map(badge => badge.cloneNode(true));
let ul = parentLi.querySelector('ul') ?? parentLi.appendChild(document.createElement('ul'));
let li = document.createElement('li');
let deleteButton = Object.assign(document.createElement('wa-icon-button'), {
name: 'trash',
label: 'Delete',
className: 'delete',
});
deleteButton.addEventListener('click', () => {
let palette = { id, uid, title: a.textContent, search: a.search };
sidebar.palette.delete(palette);
});
li.append(a, ' ', ...badges, deleteButton);
ul.appendChild(li);
}
},
save(palette, saved) {
let savedPalettes = sidebar.palettes.saved;
let existing = this.getSaved(saved ?? palette, savedPalettes);
let oldValues;
if (existing) {
// Rename
oldValues = { ...existing };
Object.assign(existing, palette);
} else {
savedPalettes.push(palette);
}
this.render(palette, oldValues);
sidebar.updateCurrent();
sidebar.palettes.save(savedPalettes);
},
};
sidebar.updateCurrent = function () {
// Find the sidebar link with the longest shared prefix with the current URL
let pathParts = location.pathname.split('/').filter(Boolean);
let prefixes = [];
if (pathParts.length === 1) {
// If at /docs/ we just use that, otherwise we want at least two parts (/docs/xxx/)
prefixes.push('/' + pathParts[0] + '/');
} else {
for (let i = 2; i <= pathParts.length; i++) {
prefixes.push('/' + pathParts.slice(0, i).join('/') + '/');
}
}
// Last prefix includes the search too (if any)
if (location.search) {
let params = new URLSearchParams(location.search);
params.sort();
prefixes.push(prefixes.at(-1) + location.search);
}
// We want to start from the longest prefix
prefixes.reverse();
let candidates;
let matchingPrefix;
for (let prefix of prefixes) {
candidates = document.querySelectorAll(`#sidebar a[href^="${prefix}"]`);
if (candidates.length > 0) {
matchingPrefix = prefix;
break;
}
}
if (!matchingPrefix) {
// Abort mission
return;
}
if (matchingPrefix === pathParts.at(-1)) {
// Full path matches, check search
if (location.search) {
candidates = [...candidates];
let searchParams = new URLSearchParams(location.search);
if (searchParams.has('uid')) {
// Only consider candidates with the same uid
candidates = candidates.filter(a => {
let params = new URLSearchParams(a.search);
return params.get('uid') === searchParams.get('uid');
});
} else {
// Sort candidates based on how many params they have in common, in descending order
candidates = candidates.sort((a, b) => {
return countSharedSearchParams(searchParams, b.search) - countSharedSearchParams(searchParams, a.search);
});
}
}
}
if (candidates.length > 0) {
for (let current of document.querySelectorAll('#sidebar a.current')) {
current.classList.remove('current');
}
candidates[0].classList.add('current');
}
};
sidebar.render = function () {
this.palettes.render();
};
sidebar.render();
window.addEventListener('turbo:render', () => sidebar.render());
function countSharedSearchParams(searchParams, search) {
if (!search || search === '?') {
return 0;
}
let params = new URLSearchParams(search);
return [...searchParams.keys()].filter(k => params.get(k) === searchParams.get(k)).length;
}

View File

@@ -1,6 +1,6 @@
/**
* Get import code for remixed themes and tweaked palettes.
*/
export { palette as getPaletteCode, theme as getThemeCode } from './tweak/code.js';
export { getThemeCode } from './tweak/code.js';
export { cdnUrl, hueRanges, hues, selectors, tints, urls } from './tweak/data.js';
export { default as Permalink } from './tweak/permalink.js';

View File

@@ -1,7 +1,7 @@
/**
* Get import code for remixed themes and tweaked palettes.
*/
import { selectors, urls } from './data.js';
import { urls } from './data.js';
export function cssImport(url, options = {}) {
let { language = 'html', cdnUrl = '/dist/', attributes } = options;
@@ -25,82 +25,27 @@ export function cssLiteral(value, options = {}) {
}
}
export function theme(base, params, options) {
// Params in correct order
export const themeParams = ['colors', 'palette', 'brand', 'typography'];
export function getThemeCode(base, params, options) {
let ret = [];
if (base) {
ret.push(urls.theme(base));
}
ret.push(
...Object.entries(params)
.filter(([aspect, id]) => Boolean(id))
.map(([aspect, id]) => urls[aspect](id)),
);
for (let aspect of themeParams) {
let value = params[aspect];
if (value) {
ret.push(urls[aspect](value));
}
}
return ret.map(url => cssImport(url, options)).join('\n');
}
export function palette(paletteId, tweaks, options) {
let imports = [];
if (paletteId) {
imports.push(urls.palette(paletteId));
}
let css = '';
if (tweaks) {
let { hueShifts, chromaScale = 1 } = tweaks;
let declarations = [];
if (chromaScale !== 1) {
declarations.push(`--wa-chroma-scale: ${chromaScale};`);
}
if (hueShifts || chromaScale !== 1) {
let element = document.querySelector(`.wa-palette-${paletteId}`) ?? document.documentElement;
let cs = getComputedStyle(element);
for (let hue in hueShifts) {
let shift = hueShifts[hue];
if ((!shift && chromaScale === 1) || hue === 'orange') {
continue;
}
let shiftCode = shift > 0 ? `+ ${shift}` : `- ${-shift}`;
let c = chromaScale === 1 ? 'c' : `calc(c * var(--wa-chroma-scale))`;
let h = shift ? `calc(h ${shiftCode})` : 'h';
declarations.push(`--wa-color-${hue}-tweak: l ${c} ${h});`);
for (let suffix of ['', '-05', '-10', '-20', '-30', '-40', '-50', '-60', '-70', '-80', '-90', '-95']) {
let baseColor = cs.getPropertyValue(`--wa-color-${hue}${suffix}`);
// Work around https://bugs.webkit.org/show_bug.cgi?id=287637
let colorSpace = ['-95', '-90'].includes(suffix) ? ' lch' : 'oklch';
let value = `${colorSpace}(from ${baseColor.padEnd(7)} var(--wa-color-${hue}-tweak))`;
suffix = (suffix + ':').padEnd(4);
declarations.push(`--wa-color-${hue}${suffix} ${value};`);
}
declarations.push('');
}
}
if (declarations.length > 0) {
css += cssRule(selectors.palette(paletteId), declarations);
}
}
let ret = imports.map(url => cssImport(url, options)).join('\n');
if (css) {
ret += `\n\n${cssLiteral(css, options)}`;
}
return ret;
}
export function cssRule(selector, declarations, { indent = ' ' } = {}) {
selector = Array.isArray(selector) ? selector.flat().join(',\n') : selector;
declarations = Array.isArray(declarations) ? declarations.flat() : declarations;

View File

@@ -29,6 +29,45 @@ export const hueRanges = {
pink: { min: 320, max: 365 }, // 45
};
export const moreHue = {
red: 'Redder',
orange: 'More orange', // https://www.reddit.com/r/grammar/comments/u9n0uo/is_it_oranger_or_more_orange/
yellow: 'Yellower',
green: 'Greener',
cyan: 'More cyan',
blue: 'Bluer',
indigo: 'More indigo',
pink: 'Pinker',
};
/**
* Max gray chroma (% of chroma of undertone) per hue
*/
export const maxGrayChroma = {
red: 0.2,
orange: 0.2,
yellow: 0.25,
green: 0.25,
cyan: 0.3,
blue: 0.35,
indigo: 0.35,
purple: 0.3,
pink: 0.25,
};
export const docsURLs = {
colors: '/docs/themes/',
palette: '/docs/palettes/',
typography: '/docs/themes/',
};
export const icons = {
colors: 'palette',
palette: 'swatchbook',
brand: 'droplet',
typography: 'font-case',
};
export const hues = Object.keys(hueRanges);
export const tints = ['05', '10', '20', '30', '40', '50', '60', '70', '80', '90', '95'];

View File

@@ -61,6 +61,8 @@ export default class Permalink extends URLSearchParams {
this.changed = true;
}
}
this.sort();
}
/**

View File

@@ -0,0 +1,36 @@
export function normalizeAngles(angles) {
// First, normalize
angles = angles.map(h => ((h % 360) + 360) % 360);
// Remove top and bottom 25% and find average
let averageHue =
angles
.toSorted((a, b) => a - b)
.slice(angles.length / 4, -angles.length / 4)
.reduce((a, b) => a + b, 0) / angles.length;
for (let i = 0; i < angles.length; i++) {
let h = angles[i];
let prevHue = angles[i - 1];
let delta = h - prevHue;
if (Math.abs(delta) > 180) {
let equivalent = [h + 360, h - 360];
// Offset hue to minimize difference in the direction that brings it closer to the average
let delta = h - averageHue;
if (Math.abs(equivalent[0] - prevHue) <= Math.abs(equivalent[1] - prevHue)) {
angles[i] = equivalent[0];
} else {
angles[i] = equivalent[1];
}
}
}
return angles;
}
export function subtractAngles(θ1, θ2) {
let [a, b] = normalizeAngles([θ1, θ2]);
return a - b;
}

View File

@@ -156,7 +156,7 @@ wa-page > header {
}
/* Pro badges */
wa-badge.pro::part(base) {
wa-badge.pro {
background-color: var(--wa-brand-orange);
border-color: var(--wa-brand-orange);
}
@@ -188,6 +188,29 @@ wa-badge.pro::part(base) {
}
}
}
wa-icon-button.delete {
vertical-align: -0.2em;
margin-inline-start: var(--wa-space-xs);
&:not(li:hover > *, :focus) {
opacity: 0;
}
}
}
wa-icon-button.delete {
&:hover {
color: var(--wa-color-danger-on-quiet);
}
&::part(base):hover {
background: var(--wa-color-danger-fill-quiet);
}
&:not(:hover, :focus) {
opacity: 0.5;
}
}
#sidebar-close-button {
@@ -232,16 +255,32 @@ wa-page > main {
margin-inline: auto;
}
h1.title wa-badge {
vertical-align: middle;
h1.title {
wa-icon-button {
font-size: var(--wa-font-size-l);
color: var(--wa-color-text-quiet);
&::part(base) {
&:not(:hover, :focus) {
opacity: 0.5;
}
}
wa-badge {
vertical-align: middle;
font-size: 1.5rem;
}
}
.block-info {
display: flex;
gap: var(--wa-space-xs);
flex-wrap: wrap;
align-items: center;
margin-block-end: var(--wa-flow-spacing);
code {
line-height: var(--wa-line-height-condensed);
}
}
/* Current link */
@@ -500,6 +539,27 @@ table.colors {
--icon-color: var(--wa-color-success-fill-quiet);
}
.icon-modifier {
position: relative;
display: inline-flex;
.modifier {
position: absolute;
bottom: -0.1em;
right: -0.3em;
font-size: 60%;
&::part(svg) {
stroke: var(--background-color, var(--wa-color-surface-default));
stroke-width: 100px;
paint-order: stroke;
overflow: visible;
stroke-linecap: round;
stroke-linejoin: round;
}
}
}
/* Layout Examples */
.layout-example-boundary {
border: var(--wa-border-width-s) dashed var(--wa-color-neutral-border-normal);

View File

@@ -19,13 +19,13 @@ icon: card
<div slot="footer">
<wa-button variant="brand" pill>More Info</wa-button>
<wa-rating></wa-rating>
<wa-rating label="Rating"></wa-rating>
</div>
</wa-card>
<style>
.card-overview {
max-width: 300px;
width: 300px;
}
.card-overview small {

View File

@@ -42,6 +42,14 @@ wa-code-demo::part(preview) {
<wa-input label="WA Input (url)" type="url"></wa-input>
```
## Pill shaped text fields
Add the `wa-pill` class to an `<input>` to make it pill-shaped.
```html {.example}
<label>Input <input type="text" placeholder="placeholder" class="wa-pill"></label>
```
## Color Picker
Basic:

View File

@@ -0,0 +1,27 @@
---
layout: null
permalink: "/docs/palettes/data.json"
eleventyExcludeFromCollections: true
---
{
{% for palette in collections.palettes %}
{% set paletteId = palette.fileSlug -%}
{% set colors = palettes[paletteId] -%}
"{{ paletteId }}": {
"title": "{{ palette.data.title }}",
"colors": {
{% for hue, tints in colors -%}
"{{ hue }}": {
{% for tint, value in tints -%}
{% if tint != "05" -%}
{% set value = value.coords or value -%}
{% set key = "05" if tint == "5" else tint -%}
"{{ key }}": {{ value | dump | safe }}{{ ', ' if not loop.last }}
{%- endif %}
{% endfor -%}
}{{ ', ' if not loop.last }}
{% endfor %} {# end of hue #}
}
}{{ ', ' if not loop.last }} {# end of palette #}
{% endfor %}
}

View File

@@ -1,4 +1,5 @@
---
title: Default
description: This is the palette used in the default theme.
order: 0
---

View File

@@ -1,6 +1,7 @@
{
"layout": "palette.njk",
"tags": ["palettes", "palette"],
"wide": true,
"eleventyComputed": {
"snippet": ".wa-palette-{{ page.fileSlug }}",
"icon": "palette",

View File

@@ -25,9 +25,16 @@ wa-dropdown > .color.swatch {
--track-color-inactive: transparent;
--track-color-active: transparent;
--thumb-color: var(--color-tweaked, var(--color));
--thumb-shadow: 0 0 0 var(--thumb-gap) var(--wa-color-surface-default),
var(--wa-shadow-offset-x-m) var(--wa-shadow-offset-y-m) var(--wa-shadow-blur-m)
calc(var(--wa-shadow-offset-x-m) * -1 + var(--thumb-gap)) var(--wa-color-shadow);
&:active {
--thumb-size: 2em;
}
&::part(base) {
background: linear-gradient(to right in oklch, var(--color-1), var(--color-2));
background: linear-gradient(to right in var(--color-interpolation-space, oklab), var(--color-1), var(--color-2));
}
}
@@ -63,13 +70,20 @@ wa-dropdown > .color.swatch {
.hue-shift-slider {
--color-1: oklch(from var(--color) l c calc(h + var(--min, 0)));
--color-2: oklch(from var(--color) l c calc(h + var(--max, 0)));
--color-interpolation-space: oklch;
}
.chroma-scale-slider {
--color: var(--wa-color-brand);
--color-1: oklch(from var(--color) l calc(c * var(--min)) h);
--color-2: oklch(from var(--color) l calc(c * var(--max)) h);
--color-tweaked: oklch(from var(--color) l calc(c * var(--chroma-scale)) h);
}
.gray-chroma-slider {
--color: var(--wa-color-gray);
--color-1: oklch(from var(--wa-color-gray) l 0 none);
--color-2: oklch(from var(--color-gray-undertone) l calc(c * var(--max)) h);
margin-top: var(--wa-space-m);
}
.popup {
@@ -91,13 +105,13 @@ wa-dropdown > .color.swatch {
td:not([data-hue='gray'] *) {
--tweak-c: calc(c * var(--chroma-scale, 1));
--tweak-h: calc(h + var(--hue-shift, 0));
--color-tweaked: oklch(from var(--color) l var(--tweak-c) var(--tweak-h));
--color-tweaked-no-chroma-scale: oklch(from var(--color) l c var(--tweak-h));
--color-tweaked-no-hue-shift: oklch(from var(--color) l var(--tweak-c) h);
&:is([data-tint='90'], [data-tint='95']) {
/* Work around https://bugs.webkit.org/show_bug.cgi?id=287637 */
--color-tweaked: lch(from var(--color) l var(--tweak-c) var(--tweak-h));
--color-tweaked-no-chroma-scale: lch(from var(--color) l c var(--tweak-h));
--color-tweaked-no-hue-shift: lch(from var(--color) l var(--tweak-c) h);
@@ -111,14 +125,18 @@ wa-dropdown > .color.swatch {
&:is(.tweaking *) {
--color-2-height: 70%;
}
&:is(.tweaking-chroma *) {
--color: var(--color-tweaked-no-chroma-scale);
}
&:is(.tweaking-chroma *) {
--color: var(--color-tweaked-no-chroma-scale);
}
&:is(.tweaking-hue *) {
--color: var(--color-tweaked-no-hue-shift);
}
&:is(.tweaking-hue *) {
--color: var(--color-tweaked-no-hue-shift);
}
&:is(.tweaking-gray-chroma *) {
--color: var(--color-tweaked-no-gray-chroma);
}
}
@@ -138,3 +156,50 @@ wa-dropdown > .color.swatch {
--tweak-icon-opacity: 80%;
}
}
.tweaked-callout {
padding: var(--wa-space-xs);
padding-inline-start: var(--wa-space-m);
align-items: center;
&:not(.tweaked-any *) {
visibility: hidden;
}
&::part(message) {
display: flex;
align-items: center;
gap: var(--wa-space-xs);
}
wa-button:first-of-type {
margin-inline-start: auto;
}
}
/* Better UI before Vue initializes */
[v-if='saved'],
[v-if^='tweaked'] {
display: none;
}
.core-color {
wa-radio-button::part(base) {
width: 2em;
height: 2em;
padding: 0;
border-radius: var(--wa-border-radius-circle);
background: var(--color);
background-clip: border-box;
}
wa-radio-button:is([checked], :state(checked))::part(base) {
box-shadow:
inset 0 0 0 var(--indicator-width) var(--indicator-color),
inset 0 0 0 calc(var(--indicator-width) + 1.5px) var(--wa-color-surface-default);
}
&::part(form-control-input) {
gap: var(--wa-space-xs);
}
}

View File

@@ -1,8 +1,10 @@
// TODO move these to local imports
import Color from 'https://colorjs.io/dist/color.js';
import { createApp } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js';
import { cdnUrl, getPaletteCode, hueRanges, hues, Permalink, tints } from '../../assets/scripts/tweak.js';
import { createApp, nextTick } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js';
import { cdnUrl, hueRanges, hues, Permalink, tints } from '../../assets/scripts/tweak.js';
import { cssImport, cssLiteral, cssRule } from '../../assets/scripts/tweak/code.js';
import { maxGrayChroma, moreHue, selectors, urls } from '../../assets/scripts/tweak/data.js';
import { subtractAngles } from '../../assets/scripts/tweak/util.js';
import Prism from '/assets/scripts/prism.js';
await Promise.all(['wa-slider'].map(tag => customElements.whenDefined(tag)));
@@ -17,30 +19,50 @@ await Promise.all(['wa-slider'].map(tag => customElements.whenDefined(tag)));
// return computedColor.endsWith(' 0)');
// })();
let paletteAppSpec = {
data() {
const { paletteId, colors, maxChroma } = wa_data;
let allPalettes = await fetch('/docs/palettes/data.json').then(r => r.json());
globalThis.allPalettes = allPalettes;
// Replace colors with their oklch coords (since they're all opaque and all in the same color space)
for (let hue in colors) {
for (let tint of tints) {
colors[hue][tint] = colors[hue][tint].coords;
for (let palette in allPalettes) {
for (let hue in allPalettes[palette].colors) {
let scale = allPalettes[palette].colors[hue];
for (let tint of tints) {
let color = scale[tint];
if (Array.isArray(color)) {
scale[tint] = new Color('oklch', color);
}
}
}
}
const percentFormatter = value => value.toLocaleString(undefined, { style: 'percent' });
let paletteAppSpec = {
data() {
let appRoot = document.querySelector('#palette-app');
let paletteId = appRoot.dataset.paletteId;
let palette = allPalettes[paletteId];
return {
uid: undefined,
paletteId,
paletteTitle: palette.title,
originalColors: palette.colors,
permalink: new Permalink(),
hueRanges,
hueShifts: Object.fromEntries(hues.map(hue => [hue, 0])),
paletteId,
originalColors: colors,
maxChroma,
chromaScale: 1,
grayChroma: undefined,
grayColor: undefined,
tweaking: {},
saved: null,
};
},
created() {
// Non-reactive variables to expose
Object.assign(this, { moreHue });
// Read URL params and apply them. This facilitates permalinks.
this.permalink.mapObject(this.hueShifts, {
keyTo: key => key.replace(/-shift$/, ''),
@@ -49,102 +71,355 @@ let paletteAppSpec = {
valueTo: value => (!value ? 0 : Number(value)),
});
this.grayChroma = this.originalGrayChroma;
this.grayColor = this.originalGrayColor;
if (location.search) {
// Update from URL
this.permalink.writeTo(this.hueShifts);
if (this.permalink.has('chroma-scale')) {
this.chromaScale = Number(this.permalink.get('chromaScale') || 1);
for (let param of ['chroma-scale', 'gray-color', 'gray-chroma']) {
if (this.permalink.has(param)) {
let value = this.permalink.get(param);
if (!isNaN(value)) {
// Convert numeric values to numbers
value = Number(value);
}
let prop = camelCase(param);
this[prop] = value;
}
}
if (this.permalink.has('uid')) {
this.uid = Number(this.permalink.get('uid'));
}
this.saved = sidebar.palette.getSaved(this.getPalette());
}
},
mounted() {
if (this.isTweaked) {
// Update contrast colors
updateContrastTables(this.colors);
for (let ref in this.$refs) {
this.$refs[ref].tooltipFormatter = percentFormatter;
}
},
computed: {
tweaks() {
return { hueShifts: this.hueShifts, chromaScale: this.chromaScale };
return {
hueShifts: this.hueShifts,
chromaScale: this.chromaScale,
grayColor: this.grayColor,
grayChroma: this.grayChroma,
};
},
isTweaked() {
return Object.values(this.hueShifts).some(Boolean);
},
paletteHTML() {
return getPaletteCode(this.paletteId, this.tweaks, { language: 'html', cdnUrl });
},
code() {
let ret = {};
for (let language of ['html', 'css']) {
let code = getPaletteCode(this.paletteId, this.colors, this.tweaked, { language, cdnUrl });
ret[language] = {
raw: code,
highlighted: Prism.highlight(code, Prism.languages[language], language),
};
}
paletteCSS() {
return getPaletteCode(this.paletteId, this.tweaks, { language: 'css', cdnUrl });
return ret;
},
colors() {
return applyTweaks.call(this, this.originalColors, this.tweaks, this.tweaked);
},
colorsMinusChromaScale() {
let tweaked = { ...this.tweaked, chromaScale: false };
return applyTweaks.call(this, this.originalColors, this.tweaks, tweaked);
},
colorsMinusHueShifts() {
let tweaked = { ...this.tweaked, hue: false };
return applyTweaks.call(this, this.originalColors, this.tweaks, tweaked);
},
colorsMinusGrayChroma() {
let tweaked = { ...this.tweaked, grayChroma: false };
return applyTweaks.call(this, this.originalColors, this.tweaks, tweaked);
},
tweaked() {
let anyHueTweaked = Object.values(this.hueShifts).some(Boolean);
let hue = anyHueTweaked
? Object.fromEntries(Object.entries(this.hueShifts).map(([hue, shift]) => [hue, shift !== 0]))
: false;
let ret = {
chromaScale: this.chromaScale !== 1,
hue,
grayChroma: this.grayChroma !== this.originalGrayChroma,
grayColor: this.grayColor !== this.originalGrayColor,
};
let anyTweaked = Object.values(ret).some(Boolean);
return anyTweaked ? ret : false;
},
tweaksHumanReadable() {
let ret = {};
for (let hue in this.originalColors) {
ret[hue] = {};
if (this.chromaScale !== 1) {
ret.chromaScale = 'More ' + (this.chromaScale > 1 ? 'vibrant' : 'muted');
}
for (let tint of tints) {
ret[hue][tint] = this.originalColors[hue][tint].slice();
for (let hue in this.hueShifts) {
let shift = this.hueShifts[hue];
if (this.hueShifts[hue]) {
ret[hue][tint][2] += this.hueShifts[hue];
if (!shift) {
continue;
}
let relHue = shift < 0 ? arrayPrevious(hues, hue) : arrayNext(hues, hue);
let hueTweak = moreHue[relHue] ?? relHue + 'er';
ret[hue] = capitalize(hueTweak + ' ' + hue + 's');
}
if (this.tweaked.grayChroma || this.tweaked.grayColor) {
if (this.tweaked.grayChroma === 0) {
ret.grayChroma = 'Achromatic grays';
} else {
if (this.tweaked.grayColor) {
ret.grayColor = capitalize(this.grayColor) + ' gray undertone';
}
if (this.chromaScale !== 1) {
ret[hue][tint][1] *= this.chromaScale;
if (this.tweaked.grayChroma) {
let more = this.tweaked.grayChroma > this.originalGrayChroma;
ret.grayChroma = `More ${more ? 'colorful' : 'neutral'} grays`;
}
}
}
return ret;
},
originalContrasts() {
return getContrasts(this.originalColors);
},
contrasts() {
return getContrasts(this.colors, this.originalContrasts);
},
originalCoreColors() {
let ret = {};
for (let hue in this.originalColors) {
let maxChromaTintRaw = this.originalColors[hue].maxChromaTintRaw;
ret[hue] = this.originalColors[hue][maxChromaTintRaw];
}
return ret;
},
coreColors() {
let ret = {};
for (let hue in this.colors) {
let maxChromaTintRaw = this.colors[hue].maxChromaTintRaw;
ret[hue] = this.colors[hue][maxChromaTintRaw];
}
return ret;
},
originalGrayColor() {
let grayHue = this.originalCoreColors.gray.get('h');
let minDistance = Infinity;
let closestHue = null;
for (let name in this.originalCoreColors) {
if (name === 'gray') {
continue;
}
let hue = this.originalCoreColors[name].get('h');
let distance = Math.abs(subtractAngles(hue, grayHue));
if (distance < minDistance) {
minDistance = distance;
closestHue = name;
}
}
return closestHue ?? 'indigo';
},
originalGrayChroma() {
let coreTint = this.originalColors.gray.maxChromaTint;
let grayChroma = this.originalColors.gray[coreTint].get('c');
if (grayChroma === 0 || grayChroma === null) {
return 0;
}
let grayColorChroma = this.originalColors[this.originalGrayColor][coreTint].get('c');
return grayChroma / grayColorChroma;
},
/**
* We want to preserve the original grayChroma selection so that when the user switches to another undertone
* that supports higher chromas, their selection will be there.
* This property is the gray chroma % that is actually applied.
*/
computedGrayChroma() {
return Math.min(this.grayChroma, this.maxGrayChroma);
},
maxGrayChroma() {
return maxGrayChroma[this.grayColor] ?? 0.3;
},
},
watch: {
// Note: These could move to `v-html` directives if we widen the app root
paletteHTML() {
let codeElement = document.querySelector('#usage ~ wa-tab-group.import-stylesheet-code code.language-html');
codeElement.textContent = this.paletteHTML;
let copyButton = codeElement.previousElementSibling;
copyButton.value = this.paletteHTML;
Prism.highlightElement(codeElement);
},
paletteCSS() {
let codeElement = document.querySelector('#usage ~ wa-tab-group.import-stylesheet-code code.language-css');
codeElement.textContent = this.paletteCSS;
let copyButton = codeElement.previousElementSibling;
copyButton.value = this.paletteCSS;
Prism.highlightElement(codeElement);
},
hueShifts: {
deep: true,
handler() {
this.permalink.readFrom(this.hueShifts);
// Update page URL
this.permalink.updateLocation();
// Update contrast colors
updateContrastTables(this.colors);
},
},
chromaScale() {
this.permalink.set('chroma-scale', this.chromaScale, 1);
},
// Update page URL
grayColor() {
this.permalink.set('gray-color', this.grayColor, this.originalGrayColor);
},
grayChroma() {
this.permalink.set('gray-chroma', this.grayChroma, this.originalGrayChroma);
},
tweaks: {
deep: true,
async handler(value, oldValue) {
await nextTick(); // must run after individual watchers
// Update page URL
this.permalink.updateLocation();
if (this.saved) {
this.save({ silent: true });
}
},
},
},
methods: {
getPalette() {
return { id: this.paletteId, uid: this.uid, search: location.search };
},
save({ silent } = {}) {
let title = silent
? (this.saved?.title ?? this.paletteTitle)
: prompt('Palette title:', `${this.paletteTitle} (tweaked)`);
if (!title) {
return;
}
let uid = this.uid;
if (!uid) {
// First time saving
this.uid = uid = sidebar.palette.getUid();
this.permalink.set('uid', uid);
this.permalink.updateLocation();
}
let palette = { ...this.getPalette(), uid, title };
sidebar.palette.save(palette, this.saved);
this.saved = palette;
},
rename() {
if (!this.saved) {
return;
}
let newTitle = prompt('New title:', this.saved.title);
if (!newTitle) {
return;
}
this.saved.title = newTitle;
sidebar.palette.save(this.saved);
},
deleteSaved() {
sidebar.palette.delete(this.saved);
},
postDelete() {
this.saved = null;
this.permalink.delete('uid');
this.uid = undefined;
this.permalink.updateLocation();
},
// Update contrast colors
updateContrastTables(this.colors);
/**
* Remove a specific tweak or all tweaks
* @param {string} [param] - The tweak to remove. If not provided, all tweaks are removed.
*/
reset(param) {
if (!param || param === 'chromaScale') {
this.chromaScale = 1;
}
if (param in this.hueShifts) {
this.hueShifts[param] = 0;
} else if (!param) {
for (let hue in this.hueShifts) {
this.hueShifts[hue] = 0;
}
}
if (!param || param === 'grayColor') {
this.grayColor = this.originalGrayColor;
}
if (!param || param === 'grayChroma') {
this.grayChroma = this.originalGrayChroma;
}
},
},
directives: {
// Like v-text, but doesn't complain if the element has content,
// making it possible to use in a PE fashion, with the contents being the fallback
content(el, { value, arg }) {
if (!el.dataset.fallback) {
// Store the original content as a fallback the first time
el.dataset.fallback = el.textContent;
}
if (value === '') {
value = el.dataset.fallback;
} else {
if (arg === 'number') {
value = Number(value).toLocaleString(undefined, { maximumSignificantDigits: 2 });
}
}
if (arg === 'html') {
el.innerHTML = value;
} else {
el.textContent = value;
}
},
},
@@ -154,41 +429,152 @@ let paletteAppSpec = {
};
function init() {
globalThis.paletteApp = createApp(paletteAppSpec).mount('#palette-app');
}
let paletteAppContainer = document.querySelector('#palette-app');
globalThis.paletteApp?.unmount?.();
function updateContrastTables(colors) {
for (let table of document.querySelectorAll('.contrast-table')) {
let { minContrast } = table.dataset;
for (let tr of table.querySelectorAll('tr[data-hue]')) {
let { hue } = tr.dataset;
for (let td of tr.querySelectorAll('td[data-tint-bg][data-tint-fg]')) {
let swatch = td.querySelector('.color.swatch');
let { tintBg, tintFg, originalContrast } = td.dataset;
let bg = new Color('oklch', colors[hue][tintBg]);
let fg = new Color('oklch', colors[hue][tintFg]);
if (!originalContrast) {
td.dataset.originalContrast = originalContrast = swatch.textContent.trim();
}
let contrast = bg.contrast(fg, 'WCAG21').toLocaleString(undefined, { maximumSignificantDigits: 2 });
swatch.textContent = contrast;
swatch.classList.toggle('value-up', contrast > originalContrast);
swatch.classList.toggle('value-down', contrast < originalContrast);
swatch.classList.toggle('contrast-fail', contrast < minContrast);
swatch.style.setProperty('--color', bg.display());
swatch.style.setProperty('color', fg.display());
}
}
if (!paletteAppContainer) {
return;
}
globalThis.paletteApp = createApp(paletteAppSpec).mount(paletteAppContainer);
}
init();
addEventListener('turbo:render', init);
export function getPaletteCode(paletteId, colors, tweaked, options) {
let imports = [];
if (paletteId) {
imports.push(urls.palette(paletteId));
}
let css = '';
let declarations = [];
if (tweaked) {
for (let hue in colors) {
if (hue === 'orange') {
continue;
} else if (hue === 'gray') {
if (!tweaked.grayChroma && !tweaked.grayColor) {
continue;
}
} else if (!tweaked.chromaScale && !tweaked.hue?.[hue]) {
continue;
}
for (let tint of tints) {
let color = colors[hue][tint];
let stringified = color.toString({ format: color.inGamut('srgb') ? 'hex' : undefined });
declarations.push(`--wa-color-${hue}-${tint}: ${stringified};`);
}
declarations.push('');
}
if (declarations.length > 0) {
css += cssRule(selectors.palette(paletteId), declarations);
}
}
let ret = imports.map(url => cssImport(url, options)).join('\n');
if (css) {
ret += `\n\n${cssLiteral(css, options)}`;
}
return ret;
}
function arrayNext(array, element) {
let index = array.indexOf(element);
return array[(index + 1) % array.length];
}
function arrayPrevious(array, element) {
let index = array.indexOf(element);
return array[(index - 1 + array.length) % array.length];
}
function applyTweaks(originalColors, tweaks, tweaked) {
let ret = {};
let { hueShifts, chromaScale = 1, grayColor, grayChroma } = tweaks;
if (!tweaked) {
return originalColors;
}
if (tweaked.grayChroma) {
grayChroma = this.computedGrayChroma;
}
for (let hue in originalColors) {
let originalScale = originalColors[hue];
let scale = (ret[hue] = {});
let descriptors = Object.getOwnPropertyDescriptors(originalScale);
Object.defineProperties(scale, {
maxChromaTint: { ...descriptors.maxChromaTint, enumerable: false },
maxChromaTintRaw: { ...descriptors.maxChromaTintRaw, enumerable: false },
});
for (let tint of tints) {
let color = originalScale[tint].clone();
if (tweaked.hue && hueShifts[hue]) {
color.set({ h: h => h + hueShifts[hue] });
}
if (tweaked.chromaScale && chromaScale !== 1) {
color.set({ c: c => c * chromaScale });
}
if (hue === 'gray' && (tweaked.grayChroma || tweaked.grayColor)) {
let colorUndertone = originalColors[grayColor][tint].clone();
color = colorUndertone.set({ c: c => c * grayChroma });
}
scale[tint] = color;
}
}
return ret;
}
function camelCase(str) {
return (str + '').replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
}
function capitalize(str) {
return str[0].toUpperCase() + str.slice(1);
}
function getContrasts(colors, originalContrasts) {
let ret = {};
for (let hue in colors) {
ret[hue] = {};
for (let tintBg of tints) {
ret[hue][tintBg] = {};
let bgColor = colors[hue][tintBg];
if (!bgColor || !bgColor.contrast) {
continue;
}
for (let tintFg of tints) {
let fgColor = colors[hue][tintFg];
let value = bgColor.contrast(fgColor, 'WCAG21');
if (originalContrasts) {
let original = originalContrasts[hue][tintBg][tintFg];
ret[hue][tintBg][tintFg] = { value, original, bgColor, fgColor };
} else {
ret[hue][tintBg][tintFg] = value;
}
}
}
}
return ret;
}

View File

@@ -14,11 +14,18 @@ During the alpha period, things might break! We take breaking changes very serio
## Next
- Fixed `wa-pill` class for text fields
- Fixed `pill` style for `<wa-input>` elements
## 3.0.0-alpha.11
### Color Palettes
- Color palette tweaking UI. Tweak hue, grays, overall colorfulness, save or share the results.
- Added a `pink` scale to all color palettes
- Fixed an incorrect CSS value in `<wa-select>`'s expand icon
- Tweaked hues of all color palettes to make them more distinct and make their hues more intentional
- Dropped `violet` and `teal`, instead using `purple` and `cyan` (this is not just a renaming, the colors have been adjusted too).
- Fixed a bug in `<wa-switch>` that caused tooltips to work incorrectly when toggling the switch
### Design Tokens
@@ -27,30 +34,54 @@ You can find them in the first column of each color palette.
### Themes
- You can now override the brand color of any theme with any of the 9 hues supported.
- Improved UI for theme remixing, with previews and generated copyable code snippets
- Improved UI for theme remixing:
- You can now override the brand color of any theme with any of the 9 hues supported.
- Rich previews
- Generated copyable code snippets.
- Permalinks
- Updated Active, Glossy, Playful, and Premium themes so that `--wa-color-brand-fill-loud` uses the core color of the chosen brand color, regardless of tint.
### Components
- Various `<wa-radio>` improvements:
- Dropped the `base` part. It can now be styled by directly applying CSS to the element itself.
- Added `hint` attribute and corresponding slot.
- Various `<wa-select>` improvements:
- Added the `tag` part (and associated exported parts) to `<wa-select>` to allow targeting the tag that shows when more than the max number of visible items have been selected
- Fixed a bug that prevented the placeholder color from being customized with the `--wa-form-control-placeholder-color` token
- Dropped the `base` part from `<wa-option>` for easier styling. CSS can now be applied directly to the element itself.
- Various `<wa-card>` improvements:
- Fixed a bug where child elements did not have correct rounding when headers and footers were absent.
- Re-introduced `--border-color` so that the card itself can have a different border color than its inner borders.
- Fixed a bug that prevented slots from showing automatically without `with-` attributes
#### `<wa-radio>`
- Dropped the `base` part. It can now be styled by directly applying CSS to the element itself.
- Added `hint` attribute and corresponding slot.
#### `<wa-select>`
- Added the `tag` part (and associated exported parts) to `<wa-select>` to allow targeting the tag that shows when more than the max number of visible items have been selected
- Fixed a bug that prevented the placeholder color from being customized with the `--wa-form-control-placeholder-color` token
- Fixed an incorrect CSS value in the expand icon
- Fixed a bug that prevented the description from being read by screen readers
#### `<wa-option>`
- `label` attribute to override the generated label (useful for rich content)
- `defaultLabel` property
- Dropped `getTextLabel()` method (if you need dynamic labels, just set the `label` attribute dynamically)
- Dropped `base` part for easier styling. CSS can now be applied directly to the element itself.
#### `<wa-menu-item>`
- `label` attribute to override the generated label (useful for rich content)
- `defaultLabel` property
- Dropped `getTextLabel()` method (if you need dynamic labels, just set the `label` attribute dynamically)
#### `<wa-card>`
- Fixed a bug where child elements did not have correct rounding when headers and footers were absent.
- Re-introduced `--border-color` so that the card itself can have a different border color than its inner borders.
- Fixed a bug that prevented slots from showing automatically without `with-` attributes
#### `<wa-tab>`
- Fixed a bug that caused `document.createElement('wa-tab')` to fail (which also meant it could not be used in VueJS and other frameworks)
### Docs
- Added an orientation example to the native radio docs
- Fixed a number of broken event listeners throughout the docs
## 3.0.0-alpha.10
- 🚨 BREAKING: updated all components to use native events instead of `wa-` prefixed events. This will allow components to work more like native elements in your code, frameworks, third-party plugins, etc. To update your code, simply remove the prefix from your event listeners for the following events.

View File

@@ -10,15 +10,17 @@ override:tags: []
eleventyComputed:
forceTheme: "{{ theme.fileSlug }}"
---
{% set isPro = theme.data.isPro %}
{% set status = theme.data.status %}
{% set since = theme.data.since %}
<link rel="stylesheet" href="/docs/themes/showcase.css" />
{% set content %}
<header>
{% include 'breadcrumbs.njk' %}
<h1 class="title">{{ theme.data.title }}</h1>
<p id="mix_and_match" hidden class="wa-size-s"></p>
<p>{% include 'status.njk' %}</p>
<p id="mix_and_match" class="wa-size-s"></p>
<p id="theme-status">{% include 'status.njk' %}</p>
<p id="theme-showcase-description">{{ theme.data.description | inlineMarkdown | safe }}</p>
</header>
{% include 'theme-showcase.njk' %}
@@ -34,30 +36,18 @@ eleventyComputed:
</wa-image-comparer>
<script type="module">
import { urls as stylesheetURLs } from "/assets/scripts/tweak/data.js";
import { theme as getThemeCode } from "/assets/scripts/tweak/code.js";
import { urls as stylesheetURLs, docsURLs, icons } from "/assets/scripts/tweak/data.js";
import { getThemeCode } from "/assets/scripts/tweak/code.js";
function updateTheme() {
let params = new URLSearchParams(window.location.search);
params = Object.fromEntries(params.entries());
const docsURLs = {
colors: '/docs/themes/',
palette: '/docs/palettes/',
typography: '/docs/themes/'
};
const icons = {
colors: 'palette',
palette: 'swatchbook',
brand: 'droplet',
typography: 'font-case'
};
for (let link of document.querySelectorAll('link.mix-and-match')) {
link.remove();
}
let msgs = [];
let tweaks = [];
let code = getThemeCode("{{ theme.fileSlug }}", params, {attributes: 'class="mix-and-match"'});
document.head.insertAdjacentHTML('beforeend', code);
@@ -72,18 +62,29 @@ function updateTheme() {
}
let icon = icons[name];
msgs.push(`<wa-icon name="${icon}" variant="regular"></wa-icon> ${ title }`);
tweaks.push(`<wa-icon name="${icon}" variant="regular"></wa-icon> ${ title }`);
}
}
for (let p of mix_and_match) {
p.hidden = msgs.length === 0;
if (msgs.length) {
let icon =
p.innerHTML = `<strong><wa-icon name="arrows-rotate"></wa-icon> Remixed</strong> ` + msgs.map(msg => `<wa-badge appearance=outlined>
${ msg }</wa-badge>`).join(' ');
let isRemixed = tweaks.length > 0;
document.documentElement.classList.toggle('is-remixed', isRemixed);
if (isRemixed) {
for (let p of document.querySelectorAll("#theme-status")) {
let proBadge = p.querySelector(".pro");
if (!proBadge) {
p.insertAdjacentHTML('beforeend', '<wa-badge class="pro">PRO</wa-badge>');
}
}
for (let p of mix_and_match) {
if (tweaks.length) {
p.innerHTML = `<strong><wa-icon name="arrows-rotate"></wa-icon> Remixed</strong> ` + tweaks.map(msg => `<wa-badge appearance=outlined>
${ msg }</wa-badge>`).join(' ');
}
}
}
}
updateTheme();
</script>

View File

@@ -107,6 +107,10 @@ function setDefault(select, value) {
}
function render(changedAspect) {
if (!globalThis.demo) {
return;
}
let url = new URL(demo.src);
if (!changedAspect || changedAspect === 'colors') {
@@ -139,10 +143,8 @@ function render(changedAspect) {
// Update code snippets
for (let language in codeSnippets) {
let codeSnippet = codeSnippets[language];
let copyButton = codeSnippet.previousElementSibling;
let code = getThemeCode(data.baseTheme, data.params, { language, cdnUrl });
codeSnippet.textContent = code;
copyButton.value = code;
Prism.highlightElement(codeSnippet);
}
}

View File

@@ -12,6 +12,11 @@ body,
#mix_and_match {
font-weight: var(--wa-font-weight-semibold);
color: var(--wa-color-text-quiet);
margin-block-end: var(--wa-space-xs);
html:not(.is-remixed) {
display: none;
}
wa-icon {
vertical-align: -0.15em;

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "@shoelace-style/webawesome",
"version": "3.0.0-alpha.10",
"version": "3.0.0-alpha.11",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@shoelace-style/webawesome",
"version": "3.0.0-alpha.10",
"version": "3.0.0-alpha.11",
"license": "MIT",
"dependencies": {
"@ctrl/tinycolor": "^4.1.0",

View File

@@ -1,7 +1,7 @@
{
"name": "@shoelace-style/webawesome",
"description": "A forward-thinking library of web components.",
"version": "3.0.0-alpha.10",
"version": "3.0.0-alpha.11",
"homepage": "https://webawesome.com/",
"author": "Web Awesome",
"license": "MIT",

View File

@@ -52,6 +52,7 @@ import styles from './button.css';
@customElement('wa-button')
export default class WaButton extends WebAwesomeFormAssociatedElement {
static shadowStyle = [variantStyles, appearanceStyles, sizeStyles, nativeStyles, styles];
static rectProxy = 'button';
static get validators() {
return [...super.validators, MirrorValidator()];
@@ -224,17 +225,6 @@ export default class WaButton extends WebAwesomeFormAssociatedElement {
this.button.blur();
}
getBoundingClientRect(): DOMRect {
let rect = super.getBoundingClientRect();
let buttonRect = this.button.getBoundingClientRect();
if (rect.width === 0 && buttonRect.width > 0) {
return buttonRect;
}
return rect;
}
render() {
const isLink = this.isLink();
const tag = isLink ? literal`a` : literal`button`;

View File

@@ -50,6 +50,7 @@ import styles from './radio-button.css';
@customElement('wa-radio-button')
export default class WaRadioButton extends WebAwesomeFormAssociatedElement {
static shadowStyle = [variantStyles, appearanceStyles, sizeStyles, nativeStyles, buttonStyles, styles];
static rectProxy = 'input';
private readonly hasSlotController = new HasSlotController(this, '[default]', 'prefix', 'suffix');

View File

@@ -194,7 +194,7 @@
background: var(--wa-color-surface-raised);
border-color: var(--wa-color-surface-border);
border-radius: var(--wa-border-radius-m);
border-style: var(--border-style);
border-style: var(--wa-border-style);
border-width: var(--border-width);
padding-block: var(--wa-space-xs);
padding-inline: 0;
@@ -208,13 +208,13 @@
&::slotted(wa-divider) {
--spacing: var(--wa-space-xs);
}
&::slotted(small) {
display: block;
font-size: var(--wa-font-size-s);
font-weight: var(--wa-font-weight-semibold);
color: var(--wa-color-text-quiet);
padding-block: var(--wa-space-xs);
padding-inline: var(--wa-space-xl);
}
}
slot:not([name])::slotted(small) {
display: block;
font-size: var(--wa-font-size-s);
font-weight: var(--wa-font-weight-semibold);
color: var(--wa-color-text-quiet);
padding-block: var(--wa-space-xs);
padding-inline: var(--wa-space-xl);
}

View File

@@ -927,6 +927,7 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
</div>
<slot
id="hint"
name="hint"
part="hint"
class=${classMap({

View File

@@ -49,6 +49,7 @@ import styles from './switch.css';
*/
@customElement('wa-switch')
export default class WaSwitch extends WebAwesomeFormAssociatedElement {
static shadowRootOptions = { ...WebAwesomeFormAssociatedElement.shadowRootOptions, delegatesFocus: true };
static shadowStyle = [formControlStyles, sizeStyles, styles];
static get validators() {

View File

@@ -4,10 +4,16 @@
--max-width: 30ch;
--padding: var(--wa-space-2xs) var(--wa-space-xs);
/** These styles are added so we don't interfere in the DOM. */
display: inline-block;
position: absolute;
/** These styles are added so we dont interfere in the DOM. */
/** Defaults for inherited CSS properties */
color: var(--wa-tooltip-content-color);
font-size: var(--wa-tooltip-font-size);
line-height: var(--wa-tooltip-line-height);
text-align: start;
white-space: normal;
}
.tooltip {
@@ -41,12 +47,6 @@
max-width: var(--max-width);
border-radius: var(--border-radius);
background-color: var(--background-color);
font: inherit;
color: var(--wa-tooltip-content-color);
font-size: var(--wa-tooltip-font-size);
line-height: var(--wa-tooltip-line-height);
text-align: start;
white-space: normal;
padding: var(--padding);
user-select: none;
-webkit-user-select: none;

View File

@@ -35,11 +35,8 @@ import styles from './tooltip.css';
*
* @cssproperty --background-color - The tooltip's background color.
* @cssproperty --border-radius - The radius of the tooltip's corners.
* @cssproperty --text-color - The color of the tooltip's content.
* @cssproperty --max-width - The maximum width of the tooltip before its content will wrap.
* @cssproperty --padding - The padding within the tooltip.
* @cssproperty --hide-delay - The amount of time to wait before hiding the tooltip when hovering.
* @cssproperty --show-delay - The amount of time to wait before showing the tooltip when hovering.
*/
@customElement('wa-tooltip')
export default class WaTooltip extends WebAwesomeElement {

View File

@@ -204,6 +204,32 @@ export default class WebAwesomeElement extends LitElement {
);
}
getBoundingClientRect(): DOMRect {
let rect = super.getBoundingClientRect();
if (rect.width === 0 && rect.height === 0) {
let Self = this.constructor as typeof WebAwesomeElement;
if (Self.rectProxy) {
let element = this[Self.rectProxy as keyof this];
if (element instanceof Element) {
let childRect = element.getBoundingClientRect();
if (childRect.width > 0 || childRect.height > 0) {
return childRect;
}
}
}
}
return rect;
}
/**
* If getBoundingClientRect() returns an empty rect,
* should we check another element?
*/
static rectProxy: undefined | string;
static createProperty(name: PropertyKey, options?: PropertyDeclaration): void {
if (options) {
if (options.initial !== undefined && options.default === undefined) {

View File

@@ -58,6 +58,17 @@
var(--wa-color-brand-60)
);
/* Text color: white if key < 70, brand-10 otherwise */
--wa-color-brand-70-max: color-mix(
in oklab,
var(--wa-color-brand) var(--wa-color-brand-if-lt-70),
var(--wa-color-brand-70)
);
--wa-color-brand-70-min: color-mix(
in oklab,
var(--wa-color-brand) var(--wa-color-brand-if-gte-70),
var(--wa-color-brand-70)
);
/* Text color: white if key < 60, brand-10 otherwise */
--wa-color-brand-on: color-mix(in oklab, var(--wa-color-brand-10) var(--wa-color-brand-if-gte-60), white);
}

View File

@@ -16,6 +16,20 @@
--wa-color-red: var(--wa-color-red-70);
--wa-color-red-key: 70;
--wa-color-orange-95: oklch(96.462% 0.02077 52.138);
--wa-color-orange-90: oklch(92.556% 0.04363 51.242);
--wa-color-orange-80: #fdbb96 /* oklch(84.396% 0.09052 50.397) */;
--wa-color-orange-70: #eb9c74 /* oklch(76.151% 0.10953 47.299) */;
--wa-color-orange-60: #cf8162 /* oklch(67.86% 0.10698 42.148) */;
--wa-color-orange-50: #aa6248 /* oklch(57.281% 0.1014 40.415) */;
--wa-color-orange-40: #864834 /* oklch(47.233% 0.08958 39.166) */;
--wa-color-orange-30: #6b3727 /* oklch(39.974% 0.07776 38.975) */;
--wa-color-orange-20: #50271a /* oklch(32.519% 0.0649 38.022) */;
--wa-color-orange-10: #32160e /* oklch(23.873% 0.04713 38.423) */;
--wa-color-orange-05: #210c06 /* oklch(18.614% 0.03797 38.589) */;
--wa-color-orange: var(--wa-color-orange-70);
--wa-color-orange-key: 70;
--wa-color-yellow-95: #faf3e1 /* oklch(96.479% 0.02487 89.211) */;
--wa-color-yellow-90: #f4e5be /* oklch(92.412% 0.0535 89.488) */;
--wa-color-yellow-80: #eac673 /* oklch(84.03% 0.1101 86.581) */;

View File

@@ -16,6 +16,20 @@
--wa-color-red: var(--wa-color-red-60);
--wa-color-red-key: 60;
--wa-color-orange-95: oklch(96.406% 0.04001 53.476);
--wa-color-orange-90: oklch(92.395% 0.07984 53.06);
--wa-color-orange-80: oklch(84.389% 0.12224 47.981);
--wa-color-orange-70: oklch(76.55% 0.16521 42.512);
--wa-color-orange-60: #ea7237 /* oklch(68.444% 0.16501 44.349) */;
--wa-color-orange-50: #c0561a /* oklch(57.844% 0.15254 45.085) */;
--wa-color-orange-40: #963e05 /* oklch(47.639% 0.13153 45.898) */;
--wa-color-orange-30: oklch(40.376% 0.11554 45.517);
--wa-color-orange-20: oklch(32.94% 0.09927 45.913);
--wa-color-orange-10: oklch(24.083% 0.07743 46.027);
--wa-color-orange-05: oklch(18.817% 0.06098 48.455);
--wa-color-orange: var(--wa-color-orange-70);
--wa-color-orange-key: 70;
--wa-color-yellow-95: #fff4c0 /* oklch(96.32% 0.0677 97.497) */;
--wa-color-yellow-90: #ffe579 /* oklch(92.176% 0.13122 96.089) */;
--wa-color-yellow-80: #ffbf18 /* oklch(84.069% 0.16897 83.446) */;

View File

@@ -16,6 +16,20 @@
--wa-color-red: var(--wa-color-red-50);
--wa-color-red-key: 50;
--wa-color-orange-95: oklch(96.245% 0.04153 59.224);
--wa-color-orange-90: oklch(92.468% 0.07656 58.647);
--wa-color-orange-80: oklch(84.375% 0.11283 54.179);
--wa-color-orange-70: #fb945a /* oklch(76.369% 0.14454 48.621) */;
--wa-color-orange-60: #f26b31 /* oklch(68.509% 0.18046 41.503) */;
--wa-color-orange-50: #cf4812 /* oklch(58.288% 0.18026 38.576) */;
--wa-color-orange-40: oklch(48.175% 0.16316 38.526);
--wa-color-orange-30: oklch(40.779% 0.13925 37.899);
--wa-color-orange-20: oklch(33.129% 0.11288 38.58);
--wa-color-orange-10: oklch(24.259% 0.0831 38.502);
--wa-color-orange-05: oklch(18.969% 0.06527 38.137);
--wa-color-orange: var(--wa-color-orange-60);
--wa-color-orange-key: 60;
--wa-color-yellow-95: #fef2bf /* oklch(95.823% 0.06674 96.369) */;
--wa-color-yellow-90: #fde588 /* oklch(92.2% 0.11633 95.327) */;
--wa-color-yellow-80: #f4c34e /* oklch(83.998% 0.14252 85.76) */;

View File

@@ -16,6 +16,20 @@
--wa-color-red: var(--wa-color-red-50);
--wa-color-red-key: 50;
--wa-color-orange-95: oklch(96.494% 0.0335 57.914);
--wa-color-orange-90: oklch(92.556% 0.06963 56.631);
--wa-color-orange-80: oklch(84.494% 0.12276 53.381);
--wa-color-orange-70: oklch(76.375% 0.17194 46.091);
--wa-color-orange-60: #eb713f /* oklch(68.398% 0.16422 41.446) */;
--wa-color-orange-50: #cb4b1d /* oklch(58.153% 0.17174 38.404) */;
--wa-color-orange-40: #a2310c /* oklch(48.028% 0.15488 36.538) */;
--wa-color-orange-30: #7f2810 /* oklch(40.591% 0.12506 35.663) */;
--wa-color-orange-20: #5d1d0e /* oklch(32.908% 0.09683 34.387) */;
--wa-color-orange-10: #3a1005 /* oklch(24.088% 0.06954 35.613) */;
--wa-color-orange-05: #270803 /* oklch(18.801% 0.05509 34.149) */;
--wa-color-orange: var(--wa-color-orange-70);
--wa-color-orange-key: 70;
--wa-color-yellow-95: #fef3cd /* oklch(96.322% 0.05069 93.748) */;
--wa-color-yellow-90: #ffe495 /* oklch(92.377% 0.10246 91.296) */;
--wa-color-yellow-80: #fac22b /* oklch(84.185% 0.16263 85.991) */;

View File

@@ -16,6 +16,20 @@
--wa-color-red: var(--wa-color-red-40);
--wa-color-red-key: 40;
--wa-color-orange-95: oklch(96.238% 0.02664 61.788);
--wa-color-orange-90: #fbe1cc /* oklch(92.512% 0.04019 60.45) */;
--wa-color-orange-80: #edc1a0 /* oklch(84.097% 0.06661 58.236) */;
--wa-color-orange-70: #dda178 /* oklch(75.734% 0.09064 55.123) */;
--wa-color-orange-60: #cd8351 /* oklch(67.646% 0.1134 53.172) */;
--wa-color-orange-50: #b65d22 /* oklch(57.437% 0.13446 49.881) */;
--wa-color-orange-40: oklch(47.576% 0.13426 46.452);
--wa-color-orange-30: oklch(40.382% 0.12087 47.003);
--wa-color-orange-20: oklch(32.846% 0.0965 46.227);
--wa-color-orange-10: oklch(24.06% 0.06873 45.849);
--wa-color-orange-05: #260900 /* oklch(18.727% 0.05359 44.791) */;
--wa-color-orange: var(--wa-color-orange-50);
--wa-color-orange-key: 50;
--wa-color-yellow-95: #f7f4da /* oklch(96.266% 0.03422 101.63) */;
--wa-color-yellow-90: #ede7c3 /* oklch(92.349% 0.04769 99.435) */;
--wa-color-yellow-80: #d8ca96 /* oklch(83.793% 0.06999 94.829) */;

View File

@@ -16,6 +16,20 @@
--wa-color-red: var(--wa-color-red-40);
--wa-color-red-key: 40;
--wa-color-orange-95: oklch(96.126% 0.05417 66.333);
--wa-color-orange-90: oklch(92.413% 0.07898 62.545);
--wa-color-orange-80: #f9bd86 /* oklch(84.088% 0.09891 63.847) */;
--wa-color-orange-70: #e2a05f /* oklch(75.707% 0.11352 64.057) */;
--wa-color-orange-60: #d18228 /* oklch(67.572% 0.13809 64.146) */;
--wa-color-orange-50: oklch(57.202% 0.13583 64.309);
--wa-color-orange-40: oklch(47.462% 0.13789 64.939);
--wa-color-orange-30: oklch(40.049% 0.12025 65.207);
--wa-color-orange-20: oklch(32.552% 0.09777 64.859);
--wa-color-orange-10: oklch(23.884% 0.07141 64.246);
--wa-color-orange-05: oklch(18.698% 0.05597 65.589);
--wa-color-orange: var(--wa-color-orange-60);
--wa-color-orange-key: 60;
--wa-color-yellow-95: #fff4b3 /* oklch(96.064% 0.08319 99.657) */;
--wa-color-yellow-90: #fee58c /* oklch(92.346% 0.11242 94.205) */;
--wa-color-yellow-80: #e3c868 /* oklch(83.63% 0.12003 93.943) */;

View File

@@ -16,6 +16,20 @@
--wa-color-red: var(--wa-color-red-50);
--wa-color-red-key: 50;
--wa-color-orange-95: #f8f0ec /* oklch(96.084% 0.01043 54.557) */;
--wa-color-orange-90: #f2e3d8 /* oklch(92.485% 0.02211 56.694) */;
--wa-color-orange-80: #e4c4ad /* oklch(84.166% 0.04799 57.553) */;
--wa-color-orange-70: #d3a583 /* oklch(75.674% 0.07148 57.481) */;
--wa-color-orange-60: #bc8a65 /* oklch(67.319% 0.08062 57.054) */;
--wa-color-orange-50: #9e6940 /* oklch(56.757% 0.08845 56.746) */;
--wa-color-orange-40: #7e4d27 /* oklch(46.949% 0.08447 57.382) */;
--wa-color-orange-30: #673a17 /* oklch(39.774% 0.0793 55.768) */;
--wa-color-orange-20: #4f2906 /* oklch(32.45% 0.0725 57.629) */;
--wa-color-orange-10: #311702 /* oklch(23.759% 0.05361 57.126) */;
--wa-color-orange-05: #200d01 /* oklch(18.517% 0.04211 57.178) */;
--wa-color-orange: var(--wa-color-orange-50);
--wa-color-orange-key: 50;
--wa-color-yellow-95: #f6f1ea /* oklch(96.009% 0.01076 76.598) */;
--wa-color-yellow-90: #eee5d7 /* oklch(92.521% 0.02111 79.091) */;
--wa-color-yellow-80: #dbc8a8 /* oklch(84.033% 0.04791 80.753) */;

View File

@@ -16,6 +16,20 @@
--wa-color-red: var(--wa-color-red-50);
--wa-color-red-key: 50;
--wa-color-orange-95: oklch(96.488% 0.04965 58.92);
--wa-color-orange-90: oklch(92.244% 0.08759 57.789);
--wa-color-orange-80: oklch(84.32% 0.12702 56.232);
--wa-color-orange-70: oklch(76.31% 0.17967 50.95);
--wa-color-orange-60: #e87431 /* oklch(68.352% 0.16354 47.083) */;
--wa-color-orange-50: oklch(58.23% 0.17947 43.3);
--wa-color-orange-40: oklch(48.089% 0.16071 40.798);
--wa-color-orange-30: oklch(40.708% 0.13699 39.616);
--wa-color-orange-20: oklch(33.124% 0.1121 39.599);
--wa-color-orange-10: oklch(24.257% 0.08204 39.602);
--wa-color-orange-05: oklch(18.966% 0.06414 39.606);
--wa-color-orange: var(--wa-color-orange-70);
--wa-color-orange-key: 70;
--wa-color-yellow-95: #fff5b4 /* oklch(96.281% 0.08306 100.4) */;
--wa-color-yellow-90: #fde572 /* oklch(91.915% 0.13738 97.724) */;
--wa-color-yellow-80: #f4c403 /* oklch(83.862% 0.17104 90.777) */;

View File

@@ -24,15 +24,22 @@ for (let paletteId in palettes) {
palettes[paletteId] = tokens;
for (let hue in tokens) {
let tints = Object.assign({}, tokens[hue]);
tokens[hue] = tints;
let scale = Object.assign({}, tokens[hue]);
tokens[hue] = scale;
let maxChromaTint = DEFAULT_ACCENT;
let maxChroma = tints[DEFAULT_ACCENT].c || 0;
let maxChromaTint = DEFAULT_ACCENT; // TODO handle scale.core
let maxChroma = scale.core?.get('oklch.c') ?? (scale[DEFAULT_ACCENT].c || 0);
for (let tint in tints) {
let color = tints[tint].to('oklch');
tints[tint] = color;
for (let tint in scale) {
let color = scale[tint];
if (!color || color.constructor.name !== 'Color') {
// Not a color
continue;
}
color = color.to('oklch');
scale[tint] = color;
if (tint === '05') {
// The object has both '5' and '05' keys, but '05' is out of order
@@ -47,14 +54,14 @@ for (let paletteId in palettes) {
}
}
tints['05'] = tints['5'];
scale['05'] = scale['5'];
tints.maxChroma = tints.maxChromaRaw = maxChroma;
tints.maxChromaTint = tints.maxChromaTintRaw = maxChromaTint;
scale.maxChroma = scale.maxChromaRaw = maxChroma;
scale.maxChromaTint = scale.maxChromaTintRaw = maxChromaTint;
if (maxChromaTint < MIN_ACCENT || maxChromaTint > MAX_ACCENT) {
tints.maxChromaTint = clamp(MIN_ACCENT, maxChromaTint, MAX_ACCENT);
tints.maxChroma = tints[maxChromaTint].c;
scale.maxChromaTint = clamp(MIN_ACCENT, maxChromaTint, MAX_ACCENT);
scale.maxChroma = scale[maxChromaTint].c;
}
}
}

View File

@@ -10,7 +10,7 @@ import { PALETTE_DIR } from './util.js';
export const paletteFiles = fs.readdirSync(PALETTE_DIR + '/').filter(file => file.endsWith('.css'));
export const declarationRegex =
/^\s*--wa-color-(?<hue>[a-z]+)-(?<level>[0-9]+):\s*(?<color>.+?)\s*(\/\*.+?\*\/)?\s*;$/gm;
/^\s*--wa-color-(?<hue>[a-z]+)(?:-(?<level>[0-9]+|key))?:\s*(?<color>.+?)\s*(\/\*.+?\*\/)?\s*;$/gm;
export const rawCSS = {};
function parse(contents, file) {
@@ -24,8 +24,24 @@ function parse(contents, file) {
const ret = {};
for (let match of matches) {
let { hue, level, color } = match.groups;
let { hue, level = '', color } = match.groups;
ret[hue] ??= {};
let scale = ret[hue];
if (level === 'key') {
scale.maxChromaTint = color;
continue;
}
if (!level) {
if (color.startsWith('var(')) {
// Core color aliased to another color, ignore
continue;
} else {
// Custom core color
level = 'core';
}
}
// Attempt to convert color to Color object, fall back to string if this fails
// This will happen for e.g. colors defined via color-mix()
@@ -38,13 +54,13 @@ function parse(contents, file) {
if (level.startsWith('0')) {
// Leading zeroes throw off sorting, add both properties
// NOTE: Ideally one of the two would be added as non-enumerable, but then we cannot access it via 11ty data
ret[hue][level] = color;
scale[level] = color;
// Drop leading zeroes
level = level.replace(/^0+/, '');
}
ret[hue][level] = color;
scale[level] = color;
}
return ret;

View File

@@ -0,0 +1,162 @@
/**
* Post-process palette CSS files to add core colors, oklch coordinates etc.
* Run via node postprocess.js
* Warning: Will overwrite existing files. Check the diff before committing!
*/
import chalk from 'chalk';
import fs from 'fs';
import path from 'path';
import palettes from './palettes-analyzed.js';
import { PALETTE_DIR, formatComparison, hueToChalk } from './util.js';
// TODO import from global data file instead of duplicating this data
const hues = ['red', 'orange', 'yellow', 'green', 'cyan', 'blue', 'indigo', 'purple', 'pink', 'gray'];
const huesChromatic = hues.slice(0, -1);
/** If a hue is missing, how should it be generated from the neighboring hues? */
const mixPercentage = { orange: 0.6 };
const selector = paletteId =>
[':where(:root)', ':host', ":where([class^='wa-theme-'], [class*=' wa-theme-'])", `.wa-palette-${paletteId}`].join(
',\n',
);
// Used for formatting warnings
const paletteIdMaxChars = Object.keys(palettes).reduce((max, id) => Math.max(max, id.length), 0);
const hueMaxChars = Object.keys(palettes.default).reduce((max, id) => Math.max(max, id.length), 0);
const indent = ' ';
const paletteIssues = { total: 0 };
for (let paletteId in palettes) {
const palette = palettes[paletteId];
let paletteCSS = '';
let hueCSS = Object.fromEntries(hues.map(hue => [hue, '']));
for (let hue in palette) {
let scale = palette[hue];
if (scale.maxChromaTint != scale.maxChromaTintRaw) {
reportPaletteIssue(
`Clamping accent color to ${chalk.bold(scale.maxChromaTint)}, but peak chroma is in ${chalk.bold(scale.maxChromaTintRaw)} (${formatComparison(scale[scale.maxChromaTintRaw].c, scale[scale.maxChromaTint].c)})`,
{ paletteId, hue: hue },
);
}
hueCSS[hue] += scaleCSS(hue, scale);
}
// Generate missing hues
for (let i = 0; i < hues.length; i++) {
let hue = hues[i];
if (hueCSS[hue]) {
continue;
}
// Find previous and next hue to interpolate
// We assume gaps will always be at most 1 hue wide
let prevHue = huesChromatic[i - 1] ?? huesChromatic.at(-1);
let nextHue = huesChromatic[i + 1] ?? huesChromatic[0];
reportPaletteIssue(`Missing hue. Generating from ${prevHue} and ${nextHue}`, { paletteId, hue });
let prevScale = palette[prevHue];
let nextScale = palette[nextHue];
let progress = mixPercentage[hue] ?? 0.5;
let scale = (palette[hue] = {});
scale.maxChromaTint = (1 - progress) * prevScale.maxChromaTint + progress * nextScale.maxChromaTint;
scale.maxChromaTint = Math.round(scale.maxChromaTint / 10) * 10;
for (let tint in prevScale) {
if (tint === '05' || !(tint > 0)) {
continue;
}
let prevColor = palette[prevHue][tint];
let nextColor = palette[nextHue][tint];
let color = prevColor.mix(nextColor, progress, { space: 'oklch' });
scale[tint] = color;
}
// Ensure core color has the max chroma
let coreColor = scale[scale.maxChromaTint];
coreColor.c = Math.max(...Object.values(scale).map(color => color.c || 0)) + 0.0002;
hueCSS[hue] += scaleCSS(hue, scale);
}
hueCSS = Object.values(hueCSS).filter(Boolean).join('\n\n');
// TODO apply Prettier instead of faking it
paletteCSS = `${selector(paletteId)} {\n${hueCSS.trimEnd().replace(/^(?=\S)/gm, indent)}\n}\n`;
fs.writeFileSync(path.join(PALETTE_DIR, paletteId + '.css'), paletteCSS, 'utf8');
}
let issuePaletteCount = Object.keys(paletteIssues).length - 1;
console.info(
`🎨 Wrote ${Object.keys(palettes).length} palette files.` +
(paletteIssues.total > 0
? ` ${chalk.bold(paletteIssues.total)} issues found across ${chalk.bold(issuePaletteCount)} palettes.`
: ''),
);
function reportPaletteIssue(issue, { paletteId, hue }) {
let palettePrefix = `[${paletteId}]`.padEnd(paletteIdMaxChars + 2);
if (!paletteIssues[paletteId]) {
// First time encountering an issue with this palette
paletteIssues[paletteId] = { count: 0 };
} else {
// Don't print palette id multiple times
palettePrefix = ' '.repeat(paletteIdMaxChars + 2);
}
paletteIssues[paletteId].count++;
paletteIssues.total++;
let msg = palettePrefix;
let huePrefix = '';
if (hue) {
huePrefix = hueToChalk(hue)(hue.padEnd(hueMaxChars + 2));
}
console.warn(`${msg}${huePrefix}${issue}`);
}
function declareColor(color, hue, tint) {
tint = tint.padStart(2, '0');
let ret = `--wa-color-${hue}-${tint}: `;
if (color.inGamut('srgb')) {
ret += `${color.toString({ format: 'hex' })} /* ${color.toString()} */;`;
} else {
ret += `${color.toString()};`;
}
return ret;
}
function scaleCSS(hue, scale) {
let ret = [];
for (let tint in scale) {
if (tint === '05' || !(tint > 0)) {
// The object has both '5' and '05' keys, but '05' is out of order
// Also ignore non-tints
continue;
}
let color = scale[tint];
ret.push(declareColor(color, hue, tint));
}
ret.reverse();
ret.push(`--wa-color-${hue}: var(--wa-color-${hue}-${scale.maxChromaTint});`);
ret.push(`--wa-color-${hue}-key: ${scale.maxChromaTint};`);
return ret.join('\n');
}

View File

@@ -1,80 +0,0 @@
/**
* Add tintless variables and OKLCH coords as comments to palette CSS files.
* Run via node tintless.js
* Warning: Will overwrite existing files. Check the diff before committing!
*/
import chalk from 'chalk';
import fs from 'fs';
import path from 'path';
import palettes, { rawPalettes } from './palettes-analyzed.js';
import { PALETTE_DIR, formatComparison, hueToChalk } from './util.js';
const selector = paletteId =>
[':where(:root)', ':host', ":where([class^='wa-theme-'], [class*=' wa-theme-'])", `.wa-palette-${paletteId}`].join(
',\n',
);
// Used for formatting warnings
const paletteIdMaxChars = Object.keys(palettes).reduce((max, id) => Math.max(max, id.length), 0);
const hueMaxChars = Object.keys(palettes.default).reduce((max, id) => Math.max(max, id.length), 0);
let issueCount = 0;
let issuePaletteCount = 0;
for (let paletteId in palettes) {
const tokens = palettes[paletteId];
let css = '';
let prefix = `[${paletteId}]`.padEnd(paletteIdMaxChars + 2);
for (let hue in tokens) {
let tints = tokens[hue];
let tintCSS = '';
for (let tint in tints) {
if (tint === '05' || !(tint > 0)) {
// The object has both '5' and '05' keys, but '05' is out of order
// Also ignore non-tints
continue;
}
let color = tints[tint];
tint = tint.padStart(2, '0');
tintCSS =
`--wa-color-${hue}-${tint}: ${color.toString({ format: 'hex' })} /* ${color.toString()} */;\n` + tintCSS;
}
if (tints.maxChromaTint != tints.maxChromaTintRaw) {
let huePrefix = hueToChalk(hue)(hue.padEnd(hueMaxChars + 2));
console.warn(
`${prefix} ${huePrefix}: Clamping accent color to ${chalk.bold(tints.maxChromaTint)}, but peak chroma is in ${chalk.bold(tints.maxChromaTintRaw)} (${formatComparison(tints[tints.maxChromaTintRaw].c, tints[tints.maxChromaTint].c)})`,
);
issueCount++;
if (prefix.trim()) {
// First time encountering an issue with this palette
issuePaletteCount++;
// Don't print palette id multiple times
prefix = ' '.repeat(paletteIdMaxChars + 2);
}
}
tintCSS += `--wa-color-${hue}: var(--wa-color-${hue}-${tints.maxChromaTint});\n`;
tintCSS += `--wa-color-${hue}-key: ${tints.maxChromaTint};\n`;
css += tintCSS + '\n';
}
let indent = ' ';
css = `${selector(paletteId)} {\n${css.trimEnd().replace(/^(?=\S)/gm, indent)}\n}\n`;
fs.writeFileSync(path.join(PALETTE_DIR, paletteId + '.css'), css, 'utf8');
}
console.info(
`🎨 Wrote ${Object.keys(palettes).length} palette files.` +
(issueCount > 0 ? ` ${chalk.bold(issueCount)} issues found across ${chalk.bold(issuePaletteCount)} palettes.` : ''),
);

View File

@@ -16,6 +16,20 @@
--wa-color-red: var(--wa-color-red-50);
--wa-color-red-key: 50;
--wa-color-orange-95: oklch(96.355% 0.05982 62.448);
--wa-color-orange-90: oklch(92.371% 0.10134 60.314);
--wa-color-orange-80: oklch(84.228% 0.13101 54.157);
--wa-color-orange-70: oklch(76.275% 0.16839 51.143);
--wa-color-orange-60: #e67530 /* oklch(68.22% 0.16179 47.997) */;
--wa-color-orange-50: oklch(58.011% 0.16819 44.953);
--wa-color-orange-40: #9b390d /* oklch(47.739% 0.14004 40.585) */;
--wa-color-orange-30: #7a2b17 /* oklch(40.323% 0.11475 35.602) */;
--wa-color-orange-20: #5c1e0f /* oklch(32.963% 0.09552 34.666) */;
--wa-color-orange-10: #3a0f06 /* oklch(24.042% 0.07066 34.715) */;
--wa-color-orange-05: #270803 /* oklch(18.867% 0.05444 34.696) */;
--wa-color-orange: var(--wa-color-orange-70);
--wa-color-orange-key: 70;
--wa-color-yellow-95: #fef6ab /* oklch(96.234% 0.09455 102.83) */;
--wa-color-yellow-90: #fde761 /* oklch(92.138% 0.15325 99.997) */;
--wa-color-yellow-80: #f5c308 /* oklch(83.774% 0.17019 89.81) */;

View File

@@ -121,7 +121,8 @@ input:where(:not(
font: inherit;
}
.wa-pill,
:host([pill]) {
border-radius: var(--wa-border-radius-pill);
input.wa-pill,
textarea.wa-pill,
:host([pill]) .wa-text-field {
border-radius: var(--wa-border-radius-pill) !important;
}

View File

@@ -55,6 +55,8 @@ input[type='range'] {
0 0 0 var(--thumb-gap) var(--wa-color-surface-default);
-webkit-appearance: none;
margin-top: calc(var(--thumb-size) / -2 + var(--track-height) / 2);
transition: var(--wa-transition-fast);
transition-property: width, height;
}
&:enabled {

View File

@@ -26,13 +26,13 @@
*/
--wa-color-brand-fill-quiet: var(--wa-color-brand-95);
--wa-color-brand-fill-normal: var(--wa-color-brand-90);
--wa-color-brand-fill-loud: var(--wa-color-brand-80);
--wa-color-brand-fill-loud: var(--wa-color-brand);
--wa-color-brand-border-quiet: var(--wa-color-brand-90);
--wa-color-brand-border-normal: var(--wa-color-brand-80);
--wa-color-brand-border-loud: var(--wa-color-brand-70);
--wa-color-brand-on-quiet: var(--wa-color-brand-40);
--wa-color-brand-on-normal: var(--wa-color-brand-30);
--wa-color-brand-on-loud: black;
--wa-color-brand-on-loud: var(--wa-color-brand-on);
--wa-color-success-fill-quiet: var(--wa-color-green-95);
--wa-color-success-fill-normal: var(--wa-color-green-90);
@@ -90,13 +90,13 @@
*/
--wa-color-brand-fill-quiet: var(--wa-color-brand-10);
--wa-color-brand-fill-normal: var(--wa-color-brand-20);
--wa-color-brand-fill-loud: var(--wa-color-brand-80);
--wa-color-brand-fill-loud: var(--wa-color-brand);
--wa-color-brand-border-quiet: var(--wa-color-brand-20);
--wa-color-brand-border-normal: var(--wa-color-brand-30);
--wa-color-brand-border-loud: var(--wa-color-brand-60);
--wa-color-brand-on-quiet: var(--wa-color-brand-80);
--wa-color-brand-on-normal: var(--wa-color-brand-90);
--wa-color-brand-on-loud: black;
--wa-color-brand-on-loud: var(--wa-color-brand-on);
--wa-color-success-fill-quiet: var(--wa-color-green-10);
--wa-color-success-fill-normal: var(--wa-color-green-20);

View File

@@ -17,7 +17,7 @@
*/
--wa-color-brand-fill-quiet: var(--wa-color-brand-95);
--wa-color-brand-fill-normal: var(--wa-color-brand-90);
--wa-color-brand-fill-loud: var(--wa-color-brand-70);
--wa-color-brand-fill-loud: var(--wa-color-brand-70-min);
--wa-color-brand-border-quiet: var(--wa-color-brand-70);
--wa-color-brand-border-normal: var(--wa-color-brand-50);
--wa-color-brand-border-loud: var(--wa-color-brand-30);
@@ -77,7 +77,7 @@
*/
--wa-color-brand-fill-quiet: var(--wa-color-brand-10);
--wa-color-brand-fill-normal: var(--wa-color-brand-20);
--wa-color-brand-fill-loud: var(--wa-color-brand-50);
--wa-color-brand-fill-loud: var(--wa-color-brand-50-max);
--wa-color-brand-border-quiet: var(--wa-color-brand-30);
--wa-color-brand-border-normal: var(--wa-color-brand-50);
--wa-color-brand-border-loud: var(--wa-color-brand-80);

View File

@@ -24,7 +24,7 @@
*/
--wa-color-brand-fill-quiet: var(--wa-color-brand-95);
--wa-color-brand-fill-normal: var(--wa-color-brand-90);
--wa-color-brand-fill-loud: var(--wa-color-brand-50);
--wa-color-brand-fill-loud: var(--wa-color-brand-50-max);
--wa-color-brand-border-quiet: var(--wa-color-brand-80);
--wa-color-brand-border-normal: var(--wa-color-brand-60);
--wa-color-brand-border-loud: var(--wa-color-brand-40);
@@ -96,7 +96,7 @@
*/
--wa-color-brand-fill-quiet: var(--wa-color-brand-20);
--wa-color-brand-fill-normal: var(--wa-color-brand-30);
--wa-color-brand-fill-loud: var(--wa-color-brand-70);
--wa-color-brand-fill-loud: var(--wa-color-brand-70-min);
--wa-color-brand-border-quiet: var(--wa-color-brand-30);
--wa-color-brand-border-normal: var(--wa-color-brand-40);
--wa-color-brand-border-loud: var(--wa-color-brand-80);

View File

@@ -61,7 +61,7 @@
*/
--wa-color-brand-fill-quiet: var(--wa-color-brand-95);
--wa-color-brand-fill-normal: var(--wa-color-brand-90);
--wa-color-brand-fill-loud: var(--wa-color-brand-50);
--wa-color-brand-fill-loud: var(--wa-color-brand-50-max);
--wa-color-brand-border-quiet: var(--wa-color-brand-90);
--wa-color-brand-border-normal: var(--wa-color-brand-80);
--wa-color-brand-border-loud: var(--wa-color-brand-60);
@@ -149,7 +149,7 @@
*/
--wa-color-brand-fill-quiet: var(--wa-color-brand-10);
--wa-color-brand-fill-normal: var(--wa-color-brand-20);
--wa-color-brand-fill-loud: var(--wa-color-brand-50);
--wa-color-brand-fill-loud: var(--wa-color-brand-50-max);
--wa-color-brand-border-quiet: var(--wa-color-brand-20);
--wa-color-brand-border-normal: var(--wa-color-brand-30);
--wa-color-brand-border-loud: var(--wa-color-brand-40);

View File

@@ -19,13 +19,13 @@
*/
--wa-color-brand-fill-quiet: var(--wa-color-brand-95);
--wa-color-brand-fill-normal: var(--wa-color-brand-90);
--wa-color-brand-fill-loud: var(--wa-color-brand-40);
--wa-color-brand-fill-loud: var(--wa-color-brand);
--wa-color-brand-border-quiet: var(--wa-color-brand-90);
--wa-color-brand-border-normal: var(--wa-color-brand-80);
--wa-color-brand-border-loud: var(--wa-color-brand-60);
--wa-color-brand-on-quiet: var(--wa-color-brand-40);
--wa-color-brand-on-normal: var(--wa-color-brand-30);
--wa-color-brand-on-loud: white;
--wa-color-brand-on-loud: var(--wa-color-brand-on);
--wa-color-success-fill-loud: var(--wa-color-green-40);
@@ -51,13 +51,13 @@
*/
--wa-color-brand-fill-quiet: var(--wa-color-brand-10);
--wa-color-brand-fill-normal: var(--wa-color-brand-20);
--wa-color-brand-fill-loud: var(--wa-color-brand-40);
--wa-color-brand-fill-loud: var(--wa-color-brand);
--wa-color-brand-border-quiet: var(--wa-color-brand-20);
--wa-color-brand-border-normal: var(--wa-color-brand-30);
--wa-color-brand-border-loud: var(--wa-color-brand-40);
--wa-color-brand-on-quiet: var(--wa-color-brand-60);
--wa-color-brand-on-normal: var(--wa-color-brand-70);
--wa-color-brand-on-loud: white;
--wa-color-brand-on-loud: var(--wa-color-brand-on);
--wa-color-success-fill-loud: var(--wa-color-green-40);

View File

@@ -29,7 +29,7 @@
*/
--wa-color-brand-fill-quiet: var(--wa-color-brand-90);
--wa-color-brand-fill-normal: var(--wa-color-brand-80);
--wa-color-brand-fill-loud: var(--wa-color-brand-40);
--wa-color-brand-fill-loud: var(--wa-color-brand-50-max);
--wa-color-brand-border-quiet: var(--wa-color-brand-80);
--wa-color-brand-border-normal: var(--wa-color-brand-70);
--wa-color-brand-border-loud: var(--wa-color-brand-60);
@@ -39,7 +39,7 @@
--wa-color-success-fill-quiet: var(--wa-color-green-90);
--wa-color-success-fill-normal: var(--wa-color-green-80);
--wa-color-success-fill-loud: var(--wa-color-green-40);
--wa-color-success-fill-loud: var(--wa-color-green-50);
--wa-color-success-border-quiet: var(--wa-color-green-80);
--wa-color-success-border-normal: var(--wa-color-green-70);
--wa-color-success-border-loud: var(--wa-color-green-60);
@@ -49,7 +49,7 @@
--wa-color-warning-fill-quiet: var(--wa-color-yellow-90);
--wa-color-warning-fill-normal: var(--wa-color-yellow-80);
--wa-color-warning-fill-loud: var(--wa-color-yellow-40);
--wa-color-warning-fill-loud: var(--wa-color-yellow-50);
--wa-color-warning-border-quiet: var(--wa-color-yellow-80);
--wa-color-warning-border-normal: var(--wa-color-yellow-70);
--wa-color-warning-border-loud: var(--wa-color-yellow-60);
@@ -59,7 +59,7 @@
--wa-color-danger-fill-quiet: var(--wa-color-red-90);
--wa-color-danger-fill-normal: var(--wa-color-red-80);
--wa-color-danger-fill-loud: var(--wa-color-red-40);
--wa-color-danger-fill-loud: var(--wa-color-red-50);
--wa-color-danger-border-quiet: var(--wa-color-red-80);
--wa-color-danger-border-normal: var(--wa-color-red-70);
--wa-color-danger-border-loud: var(--wa-color-red-60);
@@ -69,7 +69,7 @@
--wa-color-neutral-fill-quiet: var(--wa-color-gray-90);
--wa-color-neutral-fill-normal: var(--wa-color-gray-80);
--wa-color-neutral-fill-loud: var(--wa-color-gray-40);
--wa-color-neutral-fill-loud: var(--wa-color-gray-50);
--wa-color-neutral-border-quiet: var(--wa-color-gray-80);
--wa-color-neutral-border-normal: var(--wa-color-gray-70);
--wa-color-neutral-border-loud: var(--wa-color-gray-60);

View File

@@ -21,13 +21,13 @@
*/
--wa-color-brand-fill-quiet: var(--wa-color-brand-90);
--wa-color-brand-fill-normal: var(--wa-color-brand-80);
--wa-color-brand-fill-loud: var(--wa-color-brand-50);
--wa-color-brand-fill-loud: var(--wa-color-brand);
--wa-color-brand-border-quiet: var(--wa-color-brand-90);
--wa-color-brand-border-normal: var(--wa-color-brand-70);
--wa-color-brand-border-loud: var(--wa-color-brand-50);
--wa-color-brand-on-quiet: var(--wa-color-brand-30);
--wa-color-brand-on-normal: var(--wa-color-brand-30);
--wa-color-brand-on-loud: white;
--wa-color-brand-on-loud: var(--wa-color-brand-on);
--wa-color-success-fill-quiet: var(--wa-color-green-90);
--wa-color-success-fill-normal: var(--wa-color-green-80);
@@ -94,13 +94,13 @@
*/
--wa-color-brand-fill-quiet: var(--wa-color-brand-20);
--wa-color-brand-fill-normal: var(--wa-color-brand-40);
--wa-color-brand-fill-loud: var(--wa-color-brand-50);
--wa-color-brand-fill-loud: var(--wa-color-brand);
--wa-color-brand-border-quiet: var(--wa-color-brand-30);
--wa-color-brand-border-normal: var(--wa-color-brand-40);
--wa-color-brand-border-loud: var(--wa-color-brand-50);
--wa-color-brand-on-quiet: var(--wa-color-brand-70);
--wa-color-brand-on-normal: var(--wa-color-brand-90);
--wa-color-brand-on-loud: white;
--wa-color-brand-on-loud: var(--wa-color-brand-on);
--wa-color-success-fill-quiet: var(--wa-color-green-20);
--wa-color-success-fill-normal: var(--wa-color-green-40);

View File

@@ -24,13 +24,13 @@
*/
--wa-color-brand-fill-quiet: var(--wa-color-brand-95);
--wa-color-brand-fill-normal: var(--wa-color-brand-90);
--wa-color-brand-fill-loud: var(--wa-color-brand-80);
--wa-color-brand-fill-loud: var(--wa-color-brand);
--wa-color-brand-border-quiet: var(--wa-color-brand-80);
--wa-color-brand-border-normal: var(--wa-color-brand-70);
--wa-color-brand-border-loud: var(--wa-color-brand-50);
--wa-color-brand-on-quiet: var(--wa-color-brand-40);
--wa-color-brand-on-normal: var(--wa-color-brand-30);
--wa-color-brand-on-loud: var(--wa-color-brand-20);
--wa-color-brand-on-loud: var(--wa-color-brand-on);
--wa-color-success-fill-quiet: var(--wa-color-green-95);
--wa-color-success-fill-normal: var(--wa-color-green-90);
@@ -98,13 +98,13 @@
*/
--wa-color-brand-fill-quiet: var(--wa-color-brand-30);
--wa-color-brand-fill-normal: var(--wa-color-brand-40);
--wa-color-brand-fill-loud: var(--wa-color-brand-80);
--wa-color-brand-fill-loud: var(--wa-color-brand);
--wa-color-brand-border-quiet: var(--wa-color-brand-30);
--wa-color-brand-border-normal: var(--wa-color-brand-40);
--wa-color-brand-border-loud: var(--wa-color-brand-70);
--wa-color-brand-on-quiet: var(--wa-color-brand-70);
--wa-color-brand-on-normal: var(--wa-color-brand-90);
--wa-color-brand-on-loud: var(--wa-color-brand-20);
--wa-color-brand-on-loud: var(--wa-color-brand-on);
--wa-color-success-fill-quiet: var(--wa-color-green-30);
--wa-color-success-fill-normal: var(--wa-color-green-40);

View File

@@ -45,7 +45,8 @@
--outlined-border-color: var(--wa-color-border-normal);
--text-color: var(--wa-color-on-normal, var(--wa-color-neutral-on-normal));
--text-color: var(--wa-color-on-quiet, var(--wa-color-neutral-on-quiet));
--text-color-hover: var(--wa-color-on-normal, var(--wa-color-neutral-on-normal));
}
.wa-plain,

View File

@@ -9,10 +9,10 @@
:host {
/* Components are meant to override these */
--size-xs: var(--wa-space-xs);
--size-s: var(--wa-space-s);
--size-m: var(--wa-space-m);
--size-l: var(--wa-space-l);
--size-xs: var(--wa-font-size-xs);
--size-s: var(--wa-font-size-s);
--size-m: var(--wa-font-size-m);
--size-l: var(--wa-font-size-l);
--space-xs: var(--wa-space-xs);
--space-s: var(--wa-space-s);