mirror of
https://github.com/shoelace-style/webawesome.git
synced 2026-01-13 04:29:14 +00:00
Compare commits
13 Commits
select-css
...
preprocss
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e8a5427e0 | ||
|
|
52a4e5ca54 | ||
|
|
d8673a7d71 | ||
|
|
4fc6224464 | ||
|
|
4921d1c32e | ||
|
|
54d71d2319 | ||
|
|
c1ecca0169 | ||
|
|
d6a91919e0 | ||
|
|
4621094ea1 | ||
|
|
726dc73e2a | ||
|
|
4bfebf3249 | ||
|
|
99ad0abdd3 | ||
|
|
902e2b6367 |
@@ -13,4 +13,4 @@ package-lock.json
|
||||
tsconfig.json
|
||||
cdn
|
||||
_site
|
||||
docs/assets/scripts/prism.js
|
||||
docs/assets/scripts/prism-downloaded.js
|
||||
|
||||
@@ -94,7 +94,7 @@ export default function (eleventyConfig) {
|
||||
eleventyConfig.addPlugin(highlightCodePlugin());
|
||||
|
||||
// Add copy code buttons to code blocks
|
||||
eleventyConfig.addPlugin(copyCodePlugin());
|
||||
eleventyConfig.addPlugin(copyCodePlugin);
|
||||
|
||||
// Various text replacements
|
||||
eleventyConfig.addPlugin(
|
||||
|
||||
1
docs/_data/hueRanges.js
Normal file
1
docs/_data/hueRanges.js
Normal file
@@ -0,0 +1 @@
|
||||
export { hueRanges as default } from '../assets/scripts/tweak/data.js';
|
||||
@@ -5,12 +5,12 @@
|
||||
<meta name="theme-color" content="#f36944">
|
||||
|
||||
<script type="module" src="/assets/scripts/code-examples.js"></script>
|
||||
<script type="module" src="/assets/scripts/copy-code.js"></script>
|
||||
|
||||
<script type="module" src="/assets/scripts/scroll.js"></script>
|
||||
<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 #}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<table class="colors wa-palette-{{ paletteId }}">
|
||||
<table class="colors wa-palette-{{ paletteId }} contrast-table" data-min-contrast="{{ minContrast }}">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
@@ -12,19 +12,31 @@
|
||||
</tr>
|
||||
</thead>
|
||||
{% for hue in hues -%}
|
||||
<tr>
|
||||
<tr data-hue="{{ hue }}">
|
||||
<th>{{ hue | capitalize }}</th>
|
||||
{% for tint_bg in tints -%}
|
||||
{% set color_bg = palettes[paletteId][hue][tint_bg] %}
|
||||
{% for tint_fg in tints | reverse -%}
|
||||
{% set color_fg = palettes[paletteId][hue][tint_fg] %}
|
||||
{% if (tint_fg - tint_bg) | abs == difference %}
|
||||
<td>
|
||||
<div class="color swatch" style="background-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 %}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
{% set paletteId = palette.fileSlug or page.fileSlug %}
|
||||
{% set tints = [80, 60, 40, 20] %}
|
||||
{% set suffixes = ['-80', '', '-20'] %}
|
||||
{% set width = 20 %}
|
||||
{% set height = 13 %}
|
||||
{% set gap_x = 3 %}
|
||||
{% set gap_y = 3 %}
|
||||
{% set height = 12 %}
|
||||
{% set height_core = 20 %}
|
||||
{% set gap_x = 4 %}
|
||||
{% set gap_y = 4 %}
|
||||
|
||||
<svg viewBox="0 0 {{ (width + gap_x) * hues|length }} {{ (height + gap_y) * tints|length }}" fill="none" xmlns="http://www.w3.org/2000/svg" class="wa-palette-{{ paletteId }} palette-icon">
|
||||
{% set total_width = (width + gap_x) * hues|length %}
|
||||
{% set total_height = (height + gap_y) * suffixes|length + (height_core - height) %}
|
||||
<svg viewBox="0 0 {{ total_width }} {{ total_height }}" fill="none" xmlns="http://www.w3.org/2000/svg" class="wa-palette-{{ paletteId }} palette-icon">
|
||||
<style>
|
||||
@import url('/dist/styles/color/{{ paletteId }}.css') layer(palette.{{ paletteId }});
|
||||
.palette-icon {
|
||||
@@ -15,10 +18,14 @@
|
||||
|
||||
{% for hue in hues -%}
|
||||
{% set hueIndex = loop.index0 %}
|
||||
{% for tint in tints -%}
|
||||
<rect x="{{ hueIndex * (width + gap_x) }}" y="{{ loop.index0 * (height + gap_y) }}"
|
||||
width="{{ width }}" height="{{ height }}"
|
||||
fill="var(--wa-color-{{ hue }}-{{ tint }})" rx="4" />
|
||||
{% set y = 0 %}
|
||||
{% for suffix in suffixes -%}
|
||||
{% set swatch_height = height if suffix else height_core %}
|
||||
|
||||
<rect x="{{ hueIndex * (width + gap_x) }}" y="{{ y }}"
|
||||
width="{{ width }}" height="{{ swatch_height }}"
|
||||
fill="var(--wa-color-{{ hue }}{{ suffix }})" rx="2" />
|
||||
{% set y = y + swatch_height + gap_y %}
|
||||
{%- endfor %}
|
||||
{% endfor %}
|
||||
</svg>
|
||||
|
||||
@@ -1,17 +1,74 @@
|
||||
{% set hasSidebar = true %}
|
||||
{% set hasOutline = true %}
|
||||
{# {% set forceTheme = page.fileSlug %} #}
|
||||
|
||||
{% extends '../_layouts/block.njk' %}
|
||||
|
||||
{% set paletteId = page.fileSlug %}
|
||||
|
||||
{% block afterContent %}
|
||||
<style>@import url('/dist/styles/color/{{ paletteId }}.css') layer(palette.{{ paletteId }});</style>
|
||||
|
||||
{% set tints = ["95", "90", "80", "70", "60", "50", "40", "30", "20", "10", "05"] %}
|
||||
|
||||
<table class="colors wa-palette-{{ paletteId }}">
|
||||
{% 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,
|
||||
'tweaked-chroma': tweaked.chroma,
|
||||
'tweaked-hue': tweaked.hue,
|
||||
'tweaked-any': tweaked.chroma || tweaked.hue
|
||||
}"
|
||||
:style="{ '--chroma-scale': chromaScale }">
|
||||
|
||||
{% 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' %}
|
||||
</div>
|
||||
{% if description %}
|
||||
<p class="summary">
|
||||
{{ description | inlineMarkdown | safe }}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block afterContent %}
|
||||
|
||||
{% set maxChroma = 0 %}
|
||||
|
||||
<wa-callout size="small" class="tweaked-callout" variant="brand">
|
||||
<wa-icon name="sliders-simple" slot="icon" variant="regular"></wa-icon>
|
||||
This palette has been tweaked.
|
||||
<wa-tag v-for="tweakHumanReadable, param in tweaksHumanReadable" removable @wa-remove="removeTweak(param)">{% raw %}{{ tweakHumanReadable }}{% endraw %}</wa-tag>
|
||||
|
||||
<wa-button @click="reset" appearance="outlined">
|
||||
<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">
|
||||
<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>
|
||||
<tr>
|
||||
<th></th>
|
||||
@@ -21,17 +78,57 @@
|
||||
{%- endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
{# Initialize to last hue before gray #}
|
||||
{%- set hueBefore = hues[hues|length - 2] -%}
|
||||
{% for hue in hues -%}
|
||||
<tr>
|
||||
<th>{{ hue | capitalize }}</th>
|
||||
<td class="core-column">
|
||||
<div class="color swatch" style="background-color: var(--wa-color-{{ hue }}); color: var(--wa-color-{{ hue }}-{{ '05' if palettes[paletteId][hue].maxChromaTint > 60 else '95' }});">
|
||||
{%- set coreColor = palettes[paletteId][hue][palettes[paletteId][hue].maxChromaTint] -%}
|
||||
{%- set maxChroma = coreColor.c if coreColor.c > maxChroma else maxChroma -%}
|
||||
<tr data-hue="{{ hue }}" class="color-scale" :class="{tweaking: tweaking.{{ hue }}, tweaked: hueShifts.{{ hue }} }"
|
||||
:style="{ '--hue-shift': hueShifts.{{ hue }} || '' }">
|
||||
<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 -%}
|
||||
<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-copy-button value="--wa-color-{{ hue }}" copy-label="--wa-color-{{ hue }}"></wa-copy-button>
|
||||
<wa-icon name="sliders-simple" class="tweak-icon"></wa-icon>
|
||||
</div>
|
||||
<div class="popup">
|
||||
<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"
|
||||
@input="tweaking.hue = tweaking.{{hue}} = true"
|
||||
@change="tweaking.hue = tweaking.{{ hue }} = false">
|
||||
<div slot="label">
|
||||
Tweak {{ hue }} hue
|
||||
<wa-icon-button @click="hueShifts.{{ hue }} = 0" class="clear-button" name="circle-xmark" library="system" variant="regular" label="Reset"></wa-icon-button>
|
||||
</div>
|
||||
</wa-slider>
|
||||
<div class="label-min">More {{hueBefore}}</div>
|
||||
<div class="label-max">More {{hueAfter}}</div>
|
||||
</div>
|
||||
<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>`
|
||||
</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 -%}
|
||||
<td>
|
||||
{%- 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 }})">
|
||||
<wa-copy-button value="--wa-color-{{ hue }}-{{ tint }}" copy-label="--wa-color-{{ hue }}-{{ tint }}"></wa-copy-button>
|
||||
</div>
|
||||
@@ -41,6 +138,25 @@
|
||||
{%- endfor %}
|
||||
</table>
|
||||
|
||||
{% set chromaScaleBounds = [
|
||||
(0.08 / maxChroma) | number({maximumFractionDigits: 2}),
|
||||
(0.3 / maxChroma]) | number({maximumFractionDigits: 2}) -%}
|
||||
<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"
|
||||
min="{{ chromaScaleBounds[0] }}" max="{{ chromaScaleBounds[1] }}"
|
||||
@input="tweaking.chroma = true"
|
||||
@change="tweaking.chroma = false">
|
||||
<div slot="label">
|
||||
Overall colorfulness
|
||||
<wa-icon-button @click="chromaScale = 1" class="clear-button" name="circle-xmark" library="system" variant="regular" label="Reset"></wa-icon-button>
|
||||
</div>
|
||||
</wa-slider>
|
||||
<div class="label-min">More muted</div>
|
||||
<div class="label-max">More vibrant</div>
|
||||
</div>
|
||||
|
||||
<h2>Used By</h2>
|
||||
|
||||
<section class="index-grid">
|
||||
@@ -65,6 +181,7 @@ A difference of `40` ensures a minimum **3:1** contrast ratio, suitable for larg
|
||||
{% endmarkdown %}
|
||||
|
||||
{% set difference = 40 %}
|
||||
{% set minContrast = 3 %}
|
||||
{% include "contrast-table.njk" %}
|
||||
|
||||
{% markdown %}
|
||||
@@ -84,6 +201,7 @@ A difference of `50` ensures a minimum **4.5:1** contrast ratio, suitable for no
|
||||
{% endmarkdown %}
|
||||
|
||||
{% set difference = 50 %}
|
||||
{% set minContrast = 4.5 %}
|
||||
{% include "contrast-table.njk" %}
|
||||
|
||||
{% markdown %}
|
||||
@@ -102,6 +220,7 @@ A difference of `60` ensures a minimum **7:1** contrast ratio, suitable for all
|
||||
{% endmarkdown %}
|
||||
|
||||
{% set difference = 60 %}
|
||||
{% set minContrast = 7 %}
|
||||
{% include "contrast-table.njk" %}
|
||||
|
||||
{% markdown %}
|
||||
@@ -114,13 +233,34 @@ This also goes for a difference of `65`:
|
||||
{% include "contrast-table.njk" %}
|
||||
|
||||
{% markdown %}
|
||||
## How to use this palette
|
||||
## How to use this palette { #usage }
|
||||
|
||||
If you are using a Web Awesome theme that uses this palette, it will already be included.
|
||||
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 %}
|
||||
|
||||
|
||||
|
||||
@@ -3,30 +3,39 @@ import { parse } from 'node-html-parser';
|
||||
/**
|
||||
* Eleventy plugin to add copy buttons to code blocks.
|
||||
*/
|
||||
export function copyCodePlugin(options = {}) {
|
||||
export function copyCodePlugin(eleventyConfig, options = {}) {
|
||||
options = {
|
||||
container: 'body',
|
||||
...options,
|
||||
};
|
||||
|
||||
return function (eleventyConfig) {
|
||||
eleventyConfig.addTransform('copy-code', content => {
|
||||
const doc = parse(content, { blockTextElements: { code: true } });
|
||||
const container = doc.querySelector(options.container);
|
||||
let codeCount = 0;
|
||||
eleventyConfig.addTransform('copy-code', content => {
|
||||
const doc = parse(content, { blockTextElements: { code: true } });
|
||||
const container = doc.querySelector(options.container);
|
||||
|
||||
if (!container) {
|
||||
return content;
|
||||
if (!container) {
|
||||
return content;
|
||||
}
|
||||
|
||||
// Look for code blocks
|
||||
container.querySelectorAll('pre > code').forEach(code => {
|
||||
const pre = code.closest('pre');
|
||||
let preId = pre.getAttribute('id') || `code-block-${++codeCount}`;
|
||||
let codeId = code.getAttribute('id') || `${preId}-inner`;
|
||||
|
||||
if (!code.getAttribute('id')) {
|
||||
code.setAttribute('id', codeId);
|
||||
}
|
||||
if (!pre.getAttribute('id')) {
|
||||
pre.setAttribute('id', preId);
|
||||
}
|
||||
|
||||
// Look for code blocks
|
||||
container.querySelectorAll('pre > code').forEach(code => {
|
||||
const pre = code.closest('pre');
|
||||
|
||||
// Add a copy button (we set the copy data at runtime to reduce page bloat)
|
||||
pre.innerHTML = `<wa-copy-button class="copy-button" hoist></wa-copy-button>` + pre.innerHTML;
|
||||
});
|
||||
|
||||
return doc.toString();
|
||||
// Add a copy button
|
||||
pre.innerHTML += `<wa-icon-button href="#${preId}" class="block-link-icon" name="link"></wa-icon-button>
|
||||
<wa-copy-button from="${codeId}" class="copy-button" hoist></wa-copy-button>`;
|
||||
});
|
||||
};
|
||||
|
||||
return doc.toString();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
function setCopyValue() {
|
||||
document.querySelectorAll('.copy-button').forEach(copyButton => {
|
||||
const pre = copyButton.closest('pre');
|
||||
const code = pre?.querySelector('code');
|
||||
|
||||
if (code) {
|
||||
copyButton.value = code.textContent;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Set data for all copy buttons when the page loads
|
||||
setCopyValue();
|
||||
|
||||
document.addEventListener('turbo:load', setCopyValue);
|
||||
8
docs/assets/scripts/prism-downloaded.js
Normal file
8
docs/assets/scripts/prism-downloaded.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,37 +0,0 @@
|
||||
/**
|
||||
* Get import code for remixed themes.
|
||||
*/
|
||||
export const urls = {
|
||||
colors: id => `styles/themes/${id}/color.css`,
|
||||
palette: id => `styles/color/${id}.css`,
|
||||
brand: id => `styles/brand/${id}.css`,
|
||||
typography: id => `styles/themes/${id}/typography.css`,
|
||||
};
|
||||
|
||||
function getImport(url, options = {}) {
|
||||
let { language = 'html', cdnUrl = '/dist/', attributes } = options;
|
||||
url = cdnUrl + url;
|
||||
|
||||
if (language === 'css') {
|
||||
return `@import url('${url}');`;
|
||||
} else {
|
||||
attributes = attributes ? ` ${attributes}` : '';
|
||||
return `<link rel="stylesheet" href="${url}"${attributes} />`;
|
||||
}
|
||||
}
|
||||
|
||||
export function getCode(base, params, options) {
|
||||
let ret = [];
|
||||
|
||||
if (base) {
|
||||
ret.push(`styles/themes/${base}.css`);
|
||||
}
|
||||
|
||||
ret.push(
|
||||
...Object.entries(params)
|
||||
.filter(([aspect, id]) => Boolean(id))
|
||||
.map(([aspect, id]) => urls[aspect](id)),
|
||||
);
|
||||
|
||||
return ret.map(url => getImport(url, options)).join('\n');
|
||||
}
|
||||
206
docs/assets/scripts/sidebar-tweaks.js
Normal file
206
docs/assets/scripts/sidebar-tweaks.js
Normal file
@@ -0,0 +1,206 @@
|
||||
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();
|
||||
},
|
||||
|
||||
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.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.saved = null;
|
||||
}
|
||||
},
|
||||
|
||||
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();
|
||||
|
||||
for (let prefix of prefixes) {
|
||||
let a = document.querySelector(`#sidebar a[href^="${prefix}"]`);
|
||||
|
||||
if (a) {
|
||||
for (let current of document.querySelectorAll('#sidebar a.current')) {
|
||||
current.classList.remove('current');
|
||||
}
|
||||
a.classList.add('current');
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
sidebar.render = function () {
|
||||
this.palettes.render();
|
||||
};
|
||||
|
||||
sidebar.render();
|
||||
window.addEventListener('turbo:render', () => sidebar.render());
|
||||
6
docs/assets/scripts/tweak.js
Normal file
6
docs/assets/scripts/tweak.js
Normal file
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Get import code for remixed themes and tweaked palettes.
|
||||
*/
|
||||
export { theme as getThemeCode } from './tweak/code.js';
|
||||
export { cdnUrl, hueRanges, hues, selectors, tints, urls } from './tweak/data.js';
|
||||
export { default as Permalink } from './tweak/permalink.js';
|
||||
49
docs/assets/scripts/tweak/code.js
Normal file
49
docs/assets/scripts/tweak/code.js
Normal file
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Get import code for remixed themes and tweaked palettes.
|
||||
*/
|
||||
import { urls } from './data.js';
|
||||
|
||||
export function cssImport(url, options = {}) {
|
||||
let { language = 'html', cdnUrl = '/dist/', attributes } = options;
|
||||
url = cdnUrl + url;
|
||||
|
||||
if (language === 'css') {
|
||||
return `@import url('${url}');`;
|
||||
} else {
|
||||
attributes = attributes ? ` ${attributes}` : '';
|
||||
return `<link rel="stylesheet" href="${url}"${attributes} />`;
|
||||
}
|
||||
}
|
||||
|
||||
export function cssLiteral(value, options = {}) {
|
||||
let { language = 'html' } = options;
|
||||
|
||||
if (language === 'css') {
|
||||
return value;
|
||||
} else {
|
||||
return `<style>\n${value}\n</style>`;
|
||||
}
|
||||
}
|
||||
|
||||
export function theme(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)),
|
||||
);
|
||||
|
||||
return ret.map(url => cssImport(url, options)).join('\n');
|
||||
}
|
||||
|
||||
export function cssRule(selector, declarations, { indent = ' ' } = {}) {
|
||||
selector = Array.isArray(selector) ? selector.flat().join(',\n') : selector;
|
||||
declarations = Array.isArray(declarations) ? declarations.flat() : declarations;
|
||||
declarations = declarations.map(declaration => indent + declaration.trim()).join('\n');
|
||||
return `${selector} {\n${declarations.trimEnd()}\n}`;
|
||||
}
|
||||
34
docs/assets/scripts/tweak/data.js
Normal file
34
docs/assets/scripts/tweak/data.js
Normal file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Data related to theme remixing and palette tweaking
|
||||
* Must work in both browser and Node.js
|
||||
*/
|
||||
export const cdnUrl = globalThis.document ? document.documentElement.dataset.cdnUrl : '/dist/';
|
||||
|
||||
export const urls = {
|
||||
theme: id => `styles/themes/${id}.css`,
|
||||
colors: id => `styles/themes/${id}/color.css`,
|
||||
palette: id => `styles/color/${id}.css`,
|
||||
brand: id => `styles/brand/${id}.css`,
|
||||
typography: id => `styles/themes/${id}/typography.css`,
|
||||
};
|
||||
|
||||
export const selectors = {
|
||||
palette: id =>
|
||||
[':where(:root)', ':host', ":where([class^='wa-theme-'], [class*=' wa-theme-'])", `.wa-palette-${id}`].join(',\n'),
|
||||
};
|
||||
|
||||
export const hueRanges = {
|
||||
red: { min: 5, max: 35 }, // 30
|
||||
orange: { min: 35, max: 60 }, // 25
|
||||
yellow: { min: 60, max: 112 }, // 45
|
||||
green: { min: 112, max: 170 }, // 55
|
||||
cyan: { min: 170, max: 220 }, // 50
|
||||
blue: { min: 220, max: 265 }, // 45
|
||||
indigo: { min: 265, max: 290 }, // 25
|
||||
purple: { min: 290, max: 320 }, // 30
|
||||
pink: { min: 320, max: 365 }, // 45
|
||||
};
|
||||
|
||||
export const hues = Object.keys(hueRanges);
|
||||
|
||||
export const tints = ['05', '10', '20', '30', '40', '50', '60', '70', '80', '90', '95'];
|
||||
81
docs/assets/scripts/tweak/permalink.js
Normal file
81
docs/assets/scripts/tweak/permalink.js
Normal file
@@ -0,0 +1,81 @@
|
||||
const IDENTITY = x => x;
|
||||
|
||||
export default class Permalink extends URLSearchParams {
|
||||
/** Params changed since last URL I/O */
|
||||
changed = false;
|
||||
|
||||
constructor(params) {
|
||||
super(location.search);
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return Object.fromEntries(this.entries());
|
||||
}
|
||||
|
||||
#mappings = new WeakMap();
|
||||
|
||||
mapObject(obj, mapping = {}) {
|
||||
this.#mappings.set(obj, mapping);
|
||||
}
|
||||
|
||||
readFrom(obj) {
|
||||
let mapping = this.#mappings.get(obj) ?? {};
|
||||
let { keyFrom = IDENTITY, valueFrom = IDENTITY } = mapping;
|
||||
|
||||
for (let key in obj) {
|
||||
let value = obj[key];
|
||||
let mappedValue = valueFrom(value);
|
||||
let mappedKey = keyFrom(key);
|
||||
this.set(mappedKey, mappedValue);
|
||||
}
|
||||
}
|
||||
|
||||
writeTo(obj) {
|
||||
let mapping = this.#mappings.get(obj) ?? {};
|
||||
let { keyTo = IDENTITY, valueTo = IDENTITY, canExtend = false } = mapping;
|
||||
|
||||
for (let [key, value] of this) {
|
||||
let mappedKey = keyTo(key);
|
||||
let mappedValue = valueTo(value);
|
||||
|
||||
if (canExtend || mappedKey in obj) {
|
||||
obj[mappedKey] = mappedValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set(key, value, defaultValue) {
|
||||
let oldValue = this.get(key);
|
||||
|
||||
if (!value || value == defaultValue) {
|
||||
super.delete(key);
|
||||
|
||||
if (oldValue) {
|
||||
this.changed = true;
|
||||
}
|
||||
} else {
|
||||
super.set(key, value);
|
||||
|
||||
if (String(value) !== String(oldValue)) {
|
||||
this.changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
this.sort();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update page URL if it has changed since last time
|
||||
*/
|
||||
updateLocation() {
|
||||
if (this.changed) {
|
||||
// If there’s already a search, replace it.
|
||||
// We don’t want to clog the user’s history while they iterate
|
||||
let search = this.toString();
|
||||
let historyAction = location.search && search ? 'replaceState' : 'pushState';
|
||||
history[historyAction](null, '', `?${search}`);
|
||||
this.changed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,3 +27,19 @@ wa-copy-button.copy-button {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.block-link-icon {
|
||||
position: absolute;
|
||||
inset-block-start: 0;
|
||||
inset-inline-end: calc(100% + var(--wa-space-s));
|
||||
|
||||
transition: var(--wa-transition-slow);
|
||||
|
||||
&:not(:hover, :focus) {
|
||||
opacity: 50%;
|
||||
}
|
||||
|
||||
:not(:hover, :focus-within) > & {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 */
|
||||
@@ -400,9 +439,18 @@ wa-page > main:has(> .index-grid) {
|
||||
|
||||
&.color {
|
||||
border-color: transparent;
|
||||
transition: background var(--wa-transition-slow);
|
||||
background: linear-gradient(var(--color-2, transparent) 0% 100%) no-repeat border-box var(--color,);
|
||||
background-position: var(--color-2-position, bottom);
|
||||
background-size: var(--color-2-width, 100%) var(--color-2-height, 50%);
|
||||
|
||||
&.contrast-fail {
|
||||
outline: 1px dashed var(--wa-color-red);
|
||||
outline-offset: calc(-1 * var(--wa-space-2xs));
|
||||
}
|
||||
}
|
||||
|
||||
wa-copy-button {
|
||||
> wa-copy-button {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
@@ -463,6 +511,55 @@ table.colors {
|
||||
}
|
||||
}
|
||||
|
||||
.value-up,
|
||||
.value-down {
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: ' ' var(--icon);
|
||||
position: absolute;
|
||||
margin-inline-start: 3em;
|
||||
scale: 1 0.6;
|
||||
color: color-mix(in oklch, oklch(from var(--icon-color) none c h) 0%, oklch(from currentColor l none none));
|
||||
font-size: 90%;
|
||||
}
|
||||
}
|
||||
|
||||
.value-down {
|
||||
--icon: '▼';
|
||||
--icon-color: var(--wa-color-danger-fill-quiet);
|
||||
|
||||
&::after {
|
||||
margin-block-end: -0.2em;
|
||||
}
|
||||
}
|
||||
|
||||
.value-up {
|
||||
--icon: '▲';
|
||||
--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);
|
||||
|
||||
27
docs/docs/palettes/data.json.njk
Normal file
27
docs/docs/palettes/data.json.njk
Normal 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 %}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
title: Default
|
||||
description: This is the palette used in the default theme.
|
||||
order: 0
|
||||
---
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"layout": "palette.njk",
|
||||
"tags": ["palettes", "palette"],
|
||||
"wide": true,
|
||||
"eleventyComputed": {
|
||||
"snippet": ".wa-palette-{{ page.fileSlug }}",
|
||||
"icon": "palette",
|
||||
|
||||
164
docs/docs/palettes/tweak.css
Normal file
164
docs/docs/palettes/tweak.css
Normal file
@@ -0,0 +1,164 @@
|
||||
:root {
|
||||
--fa-sliders-simple: '\f1de';
|
||||
}
|
||||
|
||||
.core-column {
|
||||
position: relative;
|
||||
|
||||
> wa-dropdown {
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
wa-dropdown > .color.swatch {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.decorated-slider {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr auto;
|
||||
margin-block-end: var(--wa-space-xl);
|
||||
|
||||
wa-slider {
|
||||
grid-column: 1 / -1;
|
||||
--track-height: 1em;
|
||||
--track-color-inactive: transparent;
|
||||
--track-color-active: transparent;
|
||||
--thumb-color: var(--color-tweaked, var(--color));
|
||||
|
||||
&::part(base) {
|
||||
background: linear-gradient(to right in oklch, var(--color-1), var(--color-2));
|
||||
}
|
||||
}
|
||||
|
||||
[slot='label'] {
|
||||
min-height: 1.1lh;
|
||||
}
|
||||
|
||||
.clear-button {
|
||||
vertical-align: middle;
|
||||
font-size: var(--wa-font-size-xs);
|
||||
|
||||
&:not(.tweaked *) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.label-min,
|
||||
.label-max {
|
||||
font-size: var(--wa-font-size-xs);
|
||||
}
|
||||
|
||||
.label-min {
|
||||
grid-column: 1;
|
||||
margin-inline-start: 0.15em;
|
||||
}
|
||||
|
||||
.label-max {
|
||||
grid-column: 3;
|
||||
margin-inline-end: 0.1em;
|
||||
}
|
||||
}
|
||||
|
||||
.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)));
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
|
||||
.popup {
|
||||
background: var(--wa-color-surface-default);
|
||||
border: 1px solid var(--wa-color-surface-border);
|
||||
padding: var(--wa-space-xl);
|
||||
border-radius: var(--wa-border-radius-m);
|
||||
|
||||
code {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.color-scale {
|
||||
th {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
/* outline: 1px dashed red; */
|
||||
}
|
||||
}
|
||||
|
||||
.color.swatch {
|
||||
--color-2: var(--color-tweaked);
|
||||
--color-2-height: 100%;
|
||||
|
||||
&:is(.tweaking *) {
|
||||
--color-2-height: 70%;
|
||||
|
||||
&:is(.tweaking-chroma *) {
|
||||
--color: var(--color-tweaked-no-chroma-scale);
|
||||
}
|
||||
|
||||
&:is(.tweaking-hue *) {
|
||||
--color: var(--color-tweaked-no-hue-shift);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tweak-icon {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
right: var(--wa-space-s);
|
||||
opacity: var(--tweak-icon-opacity, 0%);
|
||||
}
|
||||
|
||||
.core-column:hover {
|
||||
--tweak-icon-opacity: 40%;
|
||||
}
|
||||
|
||||
&.tweaked .core-column {
|
||||
--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;
|
||||
}
|
||||
}
|
||||
|
||||
[v-if='saved'] {
|
||||
display: none;
|
||||
}
|
||||
411
docs/docs/palettes/tweak.js
Normal file
411
docs/docs/palettes/tweak.js
Normal file
@@ -0,0 +1,411 @@
|
||||
// TODO move these to local imports
|
||||
import Color from 'https://colorjs.io/dist/color.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 { selectors, urls } from '../../assets/scripts/tweak/data.js';
|
||||
import Prism from '/assets/scripts/prism.js';
|
||||
|
||||
await Promise.all(['wa-slider'].map(tag => customElements.whenDefined(tag)));
|
||||
|
||||
// // Detect https://bugs.webkit.org/show_bug.cgi?id=287637
|
||||
// const SAFARI_OKLCH_BUG = (() => {
|
||||
// let dummy = document.createElement('div');
|
||||
// document.body.appendChild(dummy);
|
||||
// dummy.style.color = 'oklch(from #d5e0e6 l c h)';
|
||||
// let computedColor = getComputedStyle(dummy).color;
|
||||
// dummy.remove();
|
||||
// return computedColor.endsWith(' 0)');
|
||||
// })();
|
||||
|
||||
let allPalettes = await fetch('/docs/palettes/data.json').then(r => r.json());
|
||||
globalThis.allPalettes = allPalettes;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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])),
|
||||
chromaScale: 1,
|
||||
tweaking: {},
|
||||
saved: null,
|
||||
};
|
||||
},
|
||||
|
||||
created() {
|
||||
// Read URL params and apply them. This facilitates permalinks.
|
||||
this.permalink.mapObject(this.hueShifts, {
|
||||
keyTo: key => key.replace(/-shift$/, ''),
|
||||
keyFrom: key => key + '-shift',
|
||||
valueFrom: value => (!value ? '' : Number(value)),
|
||||
valueTo: value => (!value ? 0 : Number(value)),
|
||||
});
|
||||
|
||||
if (location.search) {
|
||||
// Update from URL
|
||||
this.permalink.writeTo(this.hueShifts);
|
||||
|
||||
if (this.permalink.has('chroma-scale')) {
|
||||
this.chromaScale = Number(this.permalink.get('chroma-scale') || 1);
|
||||
}
|
||||
|
||||
if (this.permalink.has('uid')) {
|
||||
this.uid = Number(this.permalink.get('uid'));
|
||||
}
|
||||
|
||||
let palette = { id: this.paletteId, uid: this.uid, search: location.search };
|
||||
this.saved = sidebar.palette.getSaved(palette);
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
global() {
|
||||
return globalThis;
|
||||
},
|
||||
|
||||
tweaks() {
|
||||
return { hueShifts: this.hueShifts, chromaScale: this.chromaScale };
|
||||
},
|
||||
|
||||
isTweaked() {
|
||||
return Object.values(this.hueShifts).some(Boolean);
|
||||
},
|
||||
|
||||
code() {
|
||||
let ret = {};
|
||||
for (let language of ['html', 'css']) {
|
||||
let code = getPaletteCode(this.paletteId, this.tweaks, { language, cdnUrl });
|
||||
ret[language] = {
|
||||
raw: code,
|
||||
highlighted: Prism.highlight(code, Prism.languages[language], language),
|
||||
};
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
|
||||
colors() {
|
||||
let ret = {};
|
||||
|
||||
for (let hue in this.originalColors) {
|
||||
let originalScale = this.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 oklch = originalScale[tint].coords.slice();
|
||||
|
||||
if (this.hueShifts[hue]) {
|
||||
oklch[2] += this.hueShifts[hue];
|
||||
}
|
||||
|
||||
if (this.chromaScale !== 1) {
|
||||
oklch[1] *= this.chromaScale;
|
||||
}
|
||||
|
||||
scale[tint] = new Color('oklch', oklch);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
|
||||
tweaked() {
|
||||
return {
|
||||
chroma: this.chromaScale !== 1,
|
||||
hue: Object.values(this.hueShifts).some(Boolean),
|
||||
};
|
||||
},
|
||||
|
||||
tweaksHumanReadable() {
|
||||
let ret = {};
|
||||
|
||||
if (this.chromaScale !== 1) {
|
||||
ret.chromaScale = 'more ' + (this.chromaScale > 1 ? 'vibrant' : 'muted');
|
||||
}
|
||||
|
||||
for (let hue in this.hueShifts) {
|
||||
let shift = this.hueShifts[hue];
|
||||
|
||||
if (!shift) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let relHue = shift < 0 ? arrayPrevious(hues, hue) : arrayNext(hues, hue);
|
||||
let hueTweak =
|
||||
{
|
||||
red: 'redder',
|
||||
orange: 'oranger',
|
||||
indigo: 'more indigo',
|
||||
}[relHue] ?? relHue + 'er';
|
||||
|
||||
ret[hue] = hueTweak + ' ' + hue + 's';
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
|
||||
originalContrasts() {
|
||||
let ret = {};
|
||||
|
||||
for (let hue in this.originalColors) {
|
||||
ret[hue] = {};
|
||||
|
||||
for (let tintBg of tints) {
|
||||
ret[hue][tintBg] = {};
|
||||
let bgColor = this.originalColors[hue][tintBg];
|
||||
|
||||
if (!bgColor || !bgColor.contrast) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (let tintFg of tints) {
|
||||
let contrast = bgColor.contrast(this.originalColors[hue][tintFg], 'WCAG21');
|
||||
ret[hue][tintBg][tintFg] = contrast;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
|
||||
contrasts() {
|
||||
let ret = {};
|
||||
|
||||
for (let hue in this.colors) {
|
||||
ret[hue] = {};
|
||||
|
||||
for (let tintBg in this.colors[hue]) {
|
||||
ret[hue][tintBg] = {};
|
||||
let bgColor = this.colors[hue][tintBg];
|
||||
|
||||
for (let tintFg in this.colors[hue]) {
|
||||
let fgColor = this.colors[hue][tintFg];
|
||||
let value = bgColor.contrast(fgColor, 'WCAG21');
|
||||
let original = this.originalContrasts[hue][tintBg][tintFg];
|
||||
ret[hue][tintBg][tintFg] = { value, original, bgColor, fgColor };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
hueShifts: {
|
||||
deep: true,
|
||||
handler() {
|
||||
this.permalink.readFrom(this.hueShifts);
|
||||
},
|
||||
},
|
||||
|
||||
chromaScale() {
|
||||
this.permalink.set('chroma-scale', this.chromaScale, 1);
|
||||
},
|
||||
|
||||
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: {
|
||||
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) {
|
||||
this.uid = uid = sidebar.palette.getUid();
|
||||
|
||||
this.permalink.set('uid', uid);
|
||||
this.permalink.updateLocation();
|
||||
}
|
||||
|
||||
let palette = { title, id: this.paletteId, uid, search: location.search };
|
||||
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);
|
||||
this.saved = null;
|
||||
},
|
||||
|
||||
reset() {
|
||||
for (let hue in this.hueShifts) {
|
||||
this.hueShifts[hue] = 0;
|
||||
}
|
||||
this.chromaScale = 1;
|
||||
},
|
||||
|
||||
removeTweak(param) {
|
||||
if (param === 'chromaScale') {
|
||||
this.chromaScale = 1;
|
||||
} else {
|
||||
this.hueShifts[param] = 0;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
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;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
compilerOptions: {
|
||||
isCustomElement: tag => tag.startsWith('wa-'),
|
||||
},
|
||||
};
|
||||
|
||||
function init() {
|
||||
globalThis.paletteApp?.unmount?.();
|
||||
globalThis.paletteApp = createApp(paletteAppSpec).mount('#palette-app');
|
||||
}
|
||||
|
||||
init();
|
||||
addEventListener('turbo:render', init);
|
||||
|
||||
export function getPaletteCode(paletteId, tweaks, options) {
|
||||
let palette = allPalettes[paletteId].colors;
|
||||
|
||||
let imports = [];
|
||||
|
||||
if (paletteId) {
|
||||
imports.push(urls.palette(paletteId));
|
||||
}
|
||||
|
||||
let css = '';
|
||||
|
||||
if (tweaks) {
|
||||
let { hueShifts, chromaScale = 1 } = tweaks;
|
||||
let declarations = [];
|
||||
|
||||
if (hueShifts || chromaScale !== 1) {
|
||||
for (let hue in hueShifts) {
|
||||
let shift = hueShifts[hue];
|
||||
|
||||
if ((!shift && chromaScale === 1) || hue === 'orange') {
|
||||
continue;
|
||||
}
|
||||
|
||||
let scale = palette[hue];
|
||||
|
||||
for (let tint of ['05', '10', '20', '30', '40', '50', '60', '70', '80', '90', '95']) {
|
||||
let color = scale[tint];
|
||||
|
||||
if (Array.isArray(color)) {
|
||||
color = new Color('oklch', coords);
|
||||
} else {
|
||||
color = color.clone();
|
||||
}
|
||||
color.set({ h: h => h + shift, c: c => c * chromaScale });
|
||||
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];
|
||||
}
|
||||
@@ -16,8 +16,9 @@ During the alpha period, things might break! We take breaking changes very serio
|
||||
|
||||
### Color Palettes
|
||||
|
||||
- Tweaked hues of all color palettes to make them more distinct and make their hues more intentional
|
||||
- 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
|
||||
|
||||
### Design Tokens
|
||||
|
||||
@@ -26,8 +27,9 @@ You can find them in the first column of each color palette.
|
||||
|
||||
### Themes
|
||||
|
||||
- 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.
|
||||
- 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, with previews and generated copyable code snippets.
|
||||
|
||||
### Components
|
||||
|
||||
@@ -42,6 +44,7 @@ You can find them in the first column of each color palette.
|
||||
- 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
|
||||
- Fixed a bug in `<wa-select>` that prevented the description from being read by screen readers
|
||||
|
||||
|
||||
### Docs
|
||||
|
||||
5
docs/docs/themes/demo.njk
vendored
5
docs/docs/themes/demo.njk
vendored
@@ -34,7 +34,8 @@ eleventyComputed:
|
||||
</wa-image-comparer>
|
||||
|
||||
<script type="module">
|
||||
import { getCode, urls as stylesheetURLs } from "/assets/scripts/remix.js";
|
||||
import { urls as stylesheetURLs } from "/assets/scripts/tweak/data.js";
|
||||
import { theme as getThemeCode } from "/assets/scripts/tweak/code.js";
|
||||
|
||||
function updateTheme() {
|
||||
let params = new URLSearchParams(window.location.search);
|
||||
@@ -57,7 +58,7 @@ function updateTheme() {
|
||||
}
|
||||
|
||||
let msgs = [];
|
||||
let code = getCode("{{ theme.fileSlug }}", params, {attributes: 'class="mix-and-match"'});
|
||||
let code = getThemeCode("{{ theme.fileSlug }}", params, {attributes: 'class="mix-and-match"'});
|
||||
document.head.insertAdjacentHTML('beforeend', code);
|
||||
|
||||
for (let name in stylesheetURLs) {
|
||||
|
||||
41
docs/docs/themes/remix.js
vendored
41
docs/docs/themes/remix.js
vendored
@@ -1,12 +1,6 @@
|
||||
import { getCode } from '/assets/scripts/remix.js';
|
||||
|
||||
import Prism from '/assets/scripts/prism.js';
|
||||
import { cdnUrl, getThemeCode, Permalink } from '/assets/scripts/tweak.js';
|
||||
await Promise.all(['wa-select', 'wa-option', 'wa-details'].map(tag => customElements.whenDefined(tag)));
|
||||
globalThis.Prism = globalThis.Prism || {};
|
||||
globalThis.Prism.manual = true;
|
||||
await import('/assets/scripts/prism.js');
|
||||
Prism.plugins.customClass.prefix('code-');
|
||||
|
||||
const cdnUrl = document.documentElement.dataset.cdnUrl;
|
||||
|
||||
const domChange = document.startViewTransition ? document.startViewTransition.bind(document) : fn => fn();
|
||||
|
||||
@@ -57,17 +51,11 @@ function init() {
|
||||
typography: '',
|
||||
},
|
||||
params: { colors: '', palette: '', brand: '', typography: '' },
|
||||
urlParams: new URLSearchParams(location.search),
|
||||
urlParams: new Permalink(),
|
||||
};
|
||||
|
||||
// Read URL params and apply them. This facilitates permalinks.
|
||||
if (location.search) {
|
||||
for (let aspect in data.params) {
|
||||
if (data.urlParams.has(aspect)) {
|
||||
data.params[aspect] = data.urlParams.get(aspect);
|
||||
}
|
||||
}
|
||||
}
|
||||
data.urlParams.mapObject(data.params);
|
||||
data.urlParams.writeTo(data.params);
|
||||
|
||||
if (computed.isRemixed) {
|
||||
// Start with the remixing UI open if the theme has been remixed
|
||||
@@ -133,16 +121,11 @@ function render(changedAspect) {
|
||||
|
||||
for (let aspect in data.params) {
|
||||
let value = data.params[aspect];
|
||||
|
||||
if (value) {
|
||||
data.urlParams.set(aspect, value);
|
||||
} else {
|
||||
data.urlParams.delete(aspect);
|
||||
}
|
||||
|
||||
selects[aspect].value = value;
|
||||
}
|
||||
|
||||
data.urlParams.readFrom(data.params);
|
||||
|
||||
// Update demo URL
|
||||
domChange(() => {
|
||||
url.search = data.urlParams;
|
||||
@@ -150,18 +133,14 @@ function render(changedAspect) {
|
||||
return new Promise(resolve => (demo.onload = resolve));
|
||||
});
|
||||
|
||||
// Update page URL. If there’s already a search, replace it.
|
||||
// We don’t want to clog the user’s history while they iterate
|
||||
let historyAction = location.search ? 'replaceState' : 'pushState';
|
||||
history[historyAction](null, '', `?${data.urlParams}`);
|
||||
// Update page URL
|
||||
data.urlParams.updateLocation();
|
||||
|
||||
// Update code snippets
|
||||
for (let language in codeSnippets) {
|
||||
let codeSnippet = codeSnippets[language];
|
||||
let copyButton = codeSnippet.previousElementSibling;
|
||||
let code = getCode(data.baseTheme, data.params, { language, cdnUrl });
|
||||
let code = getThemeCode(data.baseTheme, data.params, { language, cdnUrl });
|
||||
codeSnippet.textContent = code;
|
||||
copyButton.value = code;
|
||||
Prism.highlightElement(codeSnippet);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import { dirname, join, relative } from 'path';
|
||||
import process from 'process';
|
||||
import copy from 'recursive-copy';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { preprocessStylesheet } from './preprocess-css.js';
|
||||
import { cdnDir, distDir, docsDir, rootDir, runScript, siteDir } from './utils.js';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
@@ -36,6 +37,7 @@ async function buildAll() {
|
||||
await generateManifest();
|
||||
await generateReactWrappers();
|
||||
await generateTypes();
|
||||
await preprocessStyles();
|
||||
await generateStyles();
|
||||
|
||||
// copy everything to unbundled before we generate bundles.
|
||||
@@ -105,6 +107,23 @@ function generateReactWrappers() {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate preprocessed CSS
|
||||
*/
|
||||
async function preprocessStyles() {
|
||||
const preprocessedCSSFiles = await globby(join(rootDir, 'src/styles/**/*.css.njk'));
|
||||
|
||||
if (preprocessedCSSFiles.length > 0) {
|
||||
spinner.start('Preprocessing stylesheets');
|
||||
|
||||
for (let filePath of preprocessedCSSFiles) {
|
||||
await preprocessStylesheet(filePath);
|
||||
}
|
||||
|
||||
spinner.succeed();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies theme stylesheets to the dist.
|
||||
*/
|
||||
@@ -371,6 +390,7 @@ if (isDeveloping) {
|
||||
try {
|
||||
const isTestFile = filename.includes('.test.ts');
|
||||
const isCssStylesheet = filename.includes('.css');
|
||||
const isPreprocessedStylesheet = filename.endsWith('.css.njk');
|
||||
const isComponent =
|
||||
filename.includes('components/') && filename.includes('.ts') && !isCssStylesheet && !isTestFile;
|
||||
|
||||
@@ -381,6 +401,10 @@ if (isDeveloping) {
|
||||
|
||||
await regenerateBundle();
|
||||
|
||||
if (isPreprocessedStylesheet || filename.endsWith('src/styles/data.js')) {
|
||||
await preprocessStyles();
|
||||
}
|
||||
|
||||
// Copy stylesheets when CSS files change
|
||||
if (isCssStylesheet) {
|
||||
await generateStyles();
|
||||
|
||||
61
scripts/preprocess-css.js
Normal file
61
scripts/preprocess-css.js
Normal file
@@ -0,0 +1,61 @@
|
||||
import { readFile, writeFile } from 'fs/promises';
|
||||
import nunjucks from 'nunjucks';
|
||||
import * as prettier from 'prettier';
|
||||
import prettierConfig from '../prettier.config.js';
|
||||
import * as globalData from '../src/styles/data.js';
|
||||
|
||||
const prelude = inputFilename => `/* DO NOT EDIT THIS FILE. It is generated from "${inputFilename}" */`;
|
||||
|
||||
let env = nunjucks.configure({ autoescape: false });
|
||||
let filenameVariables = Object.keys(globalData)
|
||||
.filter(key => key.endsWith('s'))
|
||||
.map(key => key.slice(0, -1));
|
||||
let filenameVariablesRegex = RegExp(`\\{\\{\\s*(${filenameVariables.join('|')})\\s*\\}\\}`, 'g');
|
||||
|
||||
export async function preprocessStylesheet(inputPath) {
|
||||
const content = await readFile(inputPath, 'utf-8');
|
||||
let outputPath = inputPath.replace(/\.njk$/, '');
|
||||
let filename = outputPath.split('/').pop();
|
||||
|
||||
if (filenameVariablesRegex.test(filename)) {
|
||||
// NOTE only supports a single variable right now
|
||||
filenameVariablesRegex.lastIndex = 0;
|
||||
let ret = [];
|
||||
|
||||
for (let match of filename.matchAll(filenameVariablesRegex)) {
|
||||
let variable = match[1];
|
||||
let values = globalData[variable + 's'];
|
||||
|
||||
for (let value of values) {
|
||||
let data = { [variable]: value };
|
||||
let css = await renderCSS(content, { data, inputPath, outputPath });
|
||||
|
||||
// Now use Nunjucks *again*, to render the actual filename
|
||||
let localOutputFilePath = env.renderString(outputPath, data);
|
||||
|
||||
ret.push(writeFile(localOutputFilePath, css, 'utf-8'));
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.all(ret);
|
||||
} else {
|
||||
let css = await renderCSS(content, { inputPath, outputPath });
|
||||
return writeFile(outputPath, css, 'utf-8');
|
||||
}
|
||||
|
||||
// TODO add generated files to .gitignore?
|
||||
}
|
||||
|
||||
export async function renderCSS(content, { data, inputPath, outputPath } = {}) {
|
||||
let inputFilename = inputPath.split('/').pop();
|
||||
data = data ? { ...globalData, ...data } : globalData;
|
||||
let css = env.renderString(content, data);
|
||||
|
||||
if (prelude) {
|
||||
css = prelude(inputFilename) + '\n' + css;
|
||||
}
|
||||
|
||||
css = await prettier.format(css, { ...prettierConfig, filepath: outputPath });
|
||||
|
||||
return css;
|
||||
}
|
||||
@@ -55,12 +55,6 @@ details {
|
||||
padding-block-start: var(--spacing);
|
||||
}
|
||||
|
||||
.show {
|
||||
}
|
||||
|
||||
.hide {
|
||||
}
|
||||
|
||||
@keyframes show {
|
||||
from {
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
--banner-height: 0px;
|
||||
--header-height: 0px;
|
||||
--subheader-height: 0px;
|
||||
--scroll-margin-top: calc(var(--header-height, 0px) + var(--subheader-height, 0px));
|
||||
--scroll-margin-top: calc(var(--header-height, 0px) + var(--subheader-height, 0px) + 0.5em);
|
||||
}
|
||||
|
||||
slot[name]:not([name='skip-to-content'], [name='navigation-toggle'])::slotted(*) {
|
||||
|
||||
@@ -177,7 +177,7 @@
|
||||
align-items: center;
|
||||
color: var(--wa-color-neutral-on-quiet);
|
||||
transition: rotate var(--wa-transition-slow) ease;
|
||||
rotate: 0;
|
||||
rotate: 0deg;
|
||||
margin-inline-start: var(--wa-space-s);
|
||||
|
||||
.open & {
|
||||
@@ -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;
|
||||
|
||||
@@ -927,6 +927,7 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
|
||||
</div>
|
||||
|
||||
<slot
|
||||
id="hint"
|
||||
name="hint"
|
||||
part="hint"
|
||||
class=${classMap({
|
||||
|
||||
@@ -24,7 +24,6 @@ let id = 0;
|
||||
@customElement('wa-tab')
|
||||
export default class WaTab extends WebAwesomeElement {
|
||||
static shadowStyle = styles;
|
||||
public slot = 'nav'; // Auto-slot into nav slot
|
||||
|
||||
private readonly attrId = ++id;
|
||||
private readonly componentId = `wa-tab-${this.attrId}`;
|
||||
@@ -47,6 +46,9 @@ export default class WaTab extends WebAwesomeElement {
|
||||
@property({ type: Number, reflect: true }) tabIndex = 0;
|
||||
|
||||
connectedCallback() {
|
||||
// Auto-slot into nav slot
|
||||
this.slot ||= 'nav';
|
||||
|
||||
super.connectedCallback();
|
||||
this.setAttribute('role', 'tab');
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* DO NOT EDIT THIS FILE. It is generated from "base.css.njk" */
|
||||
:where(:root),
|
||||
:host,
|
||||
:where([class^='wa-theme-'], [class*=' wa-theme-']),
|
||||
@@ -8,16 +9,20 @@
|
||||
* --wa-color-brand-if-lt-N ➡️ 100% if key < N, 0% otherwise
|
||||
* --wa-color-brand-if-gte-N ➡️ 100% if key >= N, 0% otherwise
|
||||
*/
|
||||
--wa-color-brand-if-lt-40: calc(clamp(0, 40 - var(--wa-color-brand-key), 1) * 100%);
|
||||
--wa-color-brand-if-lt-50: calc(clamp(0, 50 - var(--wa-color-brand-key), 1) * 100%);
|
||||
--wa-color-brand-if-lt-60: calc(clamp(0, 60 - var(--wa-color-brand-key), 1) * 100%);
|
||||
--wa-color-brand-if-lt-70: calc(clamp(0, 70 - var(--wa-color-brand-key), 1) * 100%);
|
||||
--wa-color-brand-if-lt-80: calc(clamp(0, 80 - var(--wa-color-brand-key), 1) * 100%);
|
||||
|
||||
--wa-color-brand-if-lt-40: calc(clamp(0, 40 - var(--wa-color-brand-key), 1) * 100%);
|
||||
--wa-color-brand-if-gte-40: calc(100% - var(--wa-color-brand-if-lt-40));
|
||||
|
||||
--wa-color-brand-if-lt-50: calc(clamp(0, 50 - var(--wa-color-brand-key), 1) * 100%);
|
||||
--wa-color-brand-if-gte-50: calc(100% - var(--wa-color-brand-if-lt-50));
|
||||
|
||||
--wa-color-brand-if-lt-60: calc(clamp(0, 60 - var(--wa-color-brand-key), 1) * 100%);
|
||||
--wa-color-brand-if-gte-60: calc(100% - var(--wa-color-brand-if-lt-60));
|
||||
|
||||
--wa-color-brand-if-lt-70: calc(clamp(0, 70 - var(--wa-color-brand-key), 1) * 100%);
|
||||
--wa-color-brand-if-gte-70: calc(100% - var(--wa-color-brand-if-lt-70));
|
||||
|
||||
--wa-color-brand-if-lt-80: calc(clamp(0, 80 - var(--wa-color-brand-key), 1) * 100%);
|
||||
--wa-color-brand-if-gte-80: calc(100% - var(--wa-color-brand-if-lt-80));
|
||||
|
||||
/*
|
||||
@@ -25,6 +30,7 @@
|
||||
* --wa-color-brand-N-max ➡️ var(--color-brand) if key <= N, var(--color-brand-N) otherwise
|
||||
* --wa-color-brand-N-min ➡️ var(--color-brand) if key >= N, var(--color-brand-N) otherwise
|
||||
*/
|
||||
|
||||
--wa-color-brand-40-max: color-mix(
|
||||
in oklab,
|
||||
var(--wa-color-brand) var(--wa-color-brand-if-lt-40),
|
||||
@@ -58,6 +64,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);
|
||||
}
|
||||
|
||||
36
src/styles/brand/base.css.njk
Normal file
36
src/styles/brand/base.css.njk
Normal file
@@ -0,0 +1,36 @@
|
||||
:where(:root),
|
||||
:host,
|
||||
:where([class^='wa-theme-'], [class*=' wa-theme-']),
|
||||
:where([class^='wa-palette-'], [class*=' wa-palette-']),
|
||||
:where([class^='wa-brand-'], [class*=' wa-brand-']) {
|
||||
/**
|
||||
* Conditional tokens for use in color-mix()
|
||||
* --wa-color-brand-if-lt-N ➡️ 100% if key < N, 0% otherwise
|
||||
* --wa-color-brand-if-gte-N ➡️ 100% if key >= N, 0% otherwise
|
||||
*/
|
||||
{% for tint in ['40', '50', '60', '70', '80'] %}
|
||||
--wa-color-brand-if-lt-{{ tint }}: calc(clamp(0, {{ tint }} - var(--wa-color-brand-key), 1) * 100%);
|
||||
--wa-color-brand-if-gte-{{ tint }}: calc(100% - var(--wa-color-brand-if-lt-{{ tint }}));
|
||||
{% endfor %}
|
||||
|
||||
/*
|
||||
* Convenience tokens for common tint cutoffs
|
||||
* --wa-color-brand-N-max ➡️ var(--color-brand) if key <= N, var(--color-brand-N) otherwise
|
||||
* --wa-color-brand-N-min ➡️ var(--color-brand) if key >= N, var(--color-brand-N) otherwise
|
||||
*/
|
||||
{% for tint in ['40', '50', '60', '70'] %}
|
||||
--wa-color-brand-{{ tint }}-max: color-mix(
|
||||
in oklab,
|
||||
var(--wa-color-brand) var(--wa-color-brand-if-lt-{{ tint }}),
|
||||
var(--wa-color-brand-{{ tint }})
|
||||
);
|
||||
--wa-color-brand-{{ tint }}-min: color-mix(
|
||||
in oklab,
|
||||
var(--wa-color-brand) var(--wa-color-brand-if-gte-{{ tint }}),
|
||||
var(--wa-color-brand-{{ tint }})
|
||||
);
|
||||
{% endfor %}
|
||||
|
||||
/* 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);
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
/* DO NOT EDIT THIS FILE. It is generated from "{{ hue }}.css.njk" */
|
||||
@import url('base.css');
|
||||
|
||||
:where(:root),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* DO NOT EDIT THIS FILE. It is generated from "{{ hue }}.css.njk" */
|
||||
@import url('base.css');
|
||||
|
||||
:where(:root),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* DO NOT EDIT THIS FILE. It is generated from "{{ hue }}.css.njk" */
|
||||
@import url('base.css');
|
||||
|
||||
:where(:root),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* DO NOT EDIT THIS FILE. It is generated from "{{ hue }}.css.njk" */
|
||||
@import url('base.css');
|
||||
|
||||
:where(:root),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* DO NOT EDIT THIS FILE. It is generated from "{{ hue }}.css.njk" */
|
||||
@import url('base.css');
|
||||
|
||||
:where(:root),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* DO NOT EDIT THIS FILE. It is generated from "{{ hue }}.css.njk" */
|
||||
@import url('base.css');
|
||||
|
||||
:where(:root),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* DO NOT EDIT THIS FILE. It is generated from "{{ hue }}.css.njk" */
|
||||
@import url('base.css');
|
||||
|
||||
:where(:root),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* DO NOT EDIT THIS FILE. It is generated from "{{ hue }}.css.njk" */
|
||||
@import url('base.css');
|
||||
|
||||
:where(:root),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* DO NOT EDIT THIS FILE. It is generated from "{{ hue }}.css.njk" */
|
||||
@import url('base.css');
|
||||
|
||||
:where(:root),
|
||||
|
||||
13
src/styles/brand/{{ hue }}.css.njk
Normal file
13
src/styles/brand/{{ hue }}.css.njk
Normal file
@@ -0,0 +1,13 @@
|
||||
@import url('base.css');
|
||||
|
||||
:where(:root),
|
||||
:host,
|
||||
:where([class^='wa-theme-'], [class*=' wa-theme-']),
|
||||
:where([class^='wa-palette-'], [class*=' wa-palette-']),
|
||||
.wa-brand-{{ hue }} {
|
||||
{%- for tint in tints | reverse %}
|
||||
--wa-color-brand-{{ tint }}: var(--wa-color-{{ hue }}-{{ tint }});
|
||||
{%- endfor %}
|
||||
--wa-color-brand: var(--wa-color-{{ hue }});
|
||||
--wa-color-brand-key: var(--wa-color-{{ hue }}-key);
|
||||
}
|
||||
@@ -1,98 +1,207 @@
|
||||
// Run via node ranges.js to analyze all palettes
|
||||
// or node ranges.js <paletteId> to analyze a single palette
|
||||
// Add palette ids, hue names, or tints to filter the analysis (e.g. node ranges.js red anodized -gray)
|
||||
import palettes from './palettes-analyzed.js';
|
||||
import { toPrecision } from './util.js';
|
||||
|
||||
let paletteId = process.argv[2];
|
||||
import { aggregates, normalizeAngles, toPrecision } from './util.js';
|
||||
|
||||
/**
|
||||
* Each "test" consists of the following params to analyze:
|
||||
* - component: The color component to analyze (h, c, l)
|
||||
* - label: The label to display in the console
|
||||
* - by: The grouping to analyze by (tint, hue)
|
||||
* - levels: The number of tints from the core color to include in the analysis.
|
||||
* Examples: undefined for all tints, 0 for the core color only, 10 for the core color and ±10 from it.
|
||||
* - component: The color component to analyze (h, c, l). If `getValue()` is provided, this is ignored.
|
||||
* - getValue: A function to extract the value to analyze from a color, for more complex analysis than just getting a component
|
||||
* - by: The grouping to analyze by (1-2 of 'tint', 'hue', 'palette'). If `getKey()` is provided, this is ignored
|
||||
* - getKey: A function to generate a key for each group. If not provided, it is generated based on the 'by' param
|
||||
* - caption: The caption to display in the console. If not provided, a default label is generated from test params.
|
||||
* - filter: Restrict to specific hues/tints/palettes or exclude them
|
||||
* - stats: The stats to calculate for each group (min, max, mid, extent, avg, median, count)
|
||||
*/
|
||||
let tests = [
|
||||
{ component: 'h', label: 'Hue', by: 'hue', levels: paletteId ? undefined : 10 },
|
||||
{ component: 'c', label: 'Chroma', by: 'tint' },
|
||||
{ component: 'l', label: 'L', by: 'tint' },
|
||||
{ component: 'h', by: 'hue', filter: ['core ± 10', '-gray'] },
|
||||
{ component: 'h', by: 'hue', filter: ['core', '-gray'] },
|
||||
{ component: 'h', by: 'palette', filter: ['core', 'gray'] },
|
||||
{ component: 'c', by: 'tint', filter: '-gray' },
|
||||
{ component: 'c', by: 'palette', filter: 'core', stats: ['max', 'avg', 'median', 'count'] },
|
||||
{ component: 'l', by: 'tint' },
|
||||
{
|
||||
caption: 'Hue change between consecutive tints',
|
||||
getValue(color, { palette, tint, hue }) {
|
||||
let nextTint = getNextTint(tint);
|
||||
let nextColor = palettes[palette][hue][nextTint];
|
||||
return color.h - nextColor.h;
|
||||
},
|
||||
getKey({ tint }) {
|
||||
return `${tint} → ${getNextTint(tint)}`;
|
||||
},
|
||||
filter: '-95',
|
||||
},
|
||||
];
|
||||
|
||||
if (!paletteId) {
|
||||
tests.push({ component: 'h', label: 'Core Hue', by: 'hue', levels: 0 });
|
||||
const COMPONENT_NAMES = { l: 'lightness', c: 'chroma', h: 'hue' };
|
||||
const CORE_TINT_MICROSYNTAX = /^core\s*((?<op>[-+±])\s*(?<offset>\d+))?$/;
|
||||
|
||||
const all = {
|
||||
tints: ['95', '90', '80', '70', '60', '50', '40', '30', '20', '10', '05'],
|
||||
hues: ['red', 'yellow', 'green', 'cyan', 'blue', 'indigo', 'purple', 'pink', 'gray'],
|
||||
palettes: Object.keys(palettes),
|
||||
};
|
||||
|
||||
let args = process.argv.slice(2);
|
||||
let used = getSubset(all, args);
|
||||
|
||||
const getNextTint = tint => Number(tint) + (tint == 5 || tint == 90 ? 5 : 10);
|
||||
|
||||
for (let key in used) {
|
||||
if (used[key].length < all[key].length) {
|
||||
// Subset of values
|
||||
console.log(`Analyzing only ${key}:`, used[key].join(', '));
|
||||
}
|
||||
}
|
||||
|
||||
const tints = ['95', '90', '80', '70', '60', '50', '40', '30', '20', '10', '05'];
|
||||
const hues = ['red', 'yellow', 'green', 'cyan', 'blue', 'indigo', 'purple', 'pink', 'gray'];
|
||||
/**
|
||||
* Apply a list of args (hues, tints, palette ids) to add or exclude against the corresponding arrays
|
||||
*/
|
||||
function getSubset(all, args) {
|
||||
args = Array.isArray(args) ? args : [args];
|
||||
|
||||
function analyzePalette(scales, results, { component, levels, by = 'tint' }) {
|
||||
for (let hue in scales) {
|
||||
let colors = scales[hue];
|
||||
let key = colors.maxChromaTint;
|
||||
let resultsByHue = by === 'hue' ? results[hue] : results;
|
||||
let used = {
|
||||
tints: [],
|
||||
hues: [],
|
||||
palettes: [],
|
||||
};
|
||||
|
||||
for (let tint of tints) {
|
||||
let color = colors[tint];
|
||||
let value = color[component];
|
||||
let resultsByTint = by === 'tint' ? resultsByHue[tint] : resultsByHue;
|
||||
if (args.length > 0) {
|
||||
for (let arg of args) {
|
||||
let isNegative = arg.startsWith('-');
|
||||
arg = isNegative ? arg.slice(1) : arg;
|
||||
let key = Object.entries(all).find(([key, values]) => values.includes(arg))?.[0];
|
||||
|
||||
if (levels === undefined || Math.abs(tint - key) <= levels) {
|
||||
if (resultsByTint.min > value) resultsByTint.min = value;
|
||||
if (resultsByTint.max < value) resultsByTint.max = value;
|
||||
if (!key && CORE_TINT_MICROSYNTAX.test(arg)) {
|
||||
key = 'tints';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function analyze(options = {}) {
|
||||
let results = {};
|
||||
let keys = options.by === 'hue' ? hues : tints;
|
||||
|
||||
for (let key of keys) {
|
||||
results[key] = { min: Infinity, max: -Infinity };
|
||||
}
|
||||
|
||||
if (paletteId) {
|
||||
analyzePalette(palettes[paletteId], results, options);
|
||||
} else {
|
||||
for (let paletteId in palettes) {
|
||||
analyzePalette(palettes[paletteId], results, options);
|
||||
}
|
||||
}
|
||||
|
||||
// Add extent & mid, make numbers easier to read
|
||||
for (let key of keys) {
|
||||
let info = results[key];
|
||||
if (options.component === 'h') {
|
||||
// Fixup hues crossing 0
|
||||
if (Math.abs(info.max - info.min) > 180) {
|
||||
info.min += 360;
|
||||
|
||||
if (info.min > info.max) {
|
||||
[info.min, info.max] = [info.max, info.min];
|
||||
if (key) {
|
||||
if (isNegative) {
|
||||
let array = used[key].length === 0 ? all[key] : used[key];
|
||||
used[key] = array.filter(value => value !== arg);
|
||||
} else {
|
||||
used[key].push(arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
info.extent = info.max - info.min;
|
||||
info.mid = (info.min + info.max) / 2;
|
||||
|
||||
for (let prop in info) {
|
||||
info[prop] = toPrecision(info[prop]);
|
||||
// If no filters, use all values
|
||||
for (let key in used) {
|
||||
if (used[key].length === 0) {
|
||||
used[key] = all[key].slice();
|
||||
}
|
||||
}
|
||||
|
||||
let label = `${options.label || options.component} ranges`;
|
||||
console.log(label + (options.levels !== undefined ? ` (±${options.levels} from core tint)` : '') + ':');
|
||||
console.table(results);
|
||||
return used;
|
||||
}
|
||||
|
||||
if (paletteId) {
|
||||
// Analyze a single palette
|
||||
console.log(`Analyzing palette '${paletteId}'`);
|
||||
function runTest(test = {}) {
|
||||
let {
|
||||
component,
|
||||
getValue = color => color[component],
|
||||
by = 'tint',
|
||||
getKey = getDefaultKey(by),
|
||||
filter,
|
||||
caption = getDefaultCaption(test),
|
||||
stats = ['min', 'max', 'median', 'count'],
|
||||
silent,
|
||||
} = test;
|
||||
let results = {};
|
||||
let localUsed = filter ? getSubset(used, filter) : used;
|
||||
|
||||
for (let palette of localUsed.palettes) {
|
||||
for (let hue of localUsed.hues) {
|
||||
let coreTint = palettes[palette][hue].maxChromaTint;
|
||||
// Resolve any core tint microsyntax
|
||||
let tints = localUsed.tints.flatMap(tint => {
|
||||
if (CORE_TINT_MICROSYNTAX.test(tint)) {
|
||||
let { op, offset } = CORE_TINT_MICROSYNTAX.exec(tint).groups;
|
||||
|
||||
if (!offset) {
|
||||
return coreTint;
|
||||
}
|
||||
|
||||
return used.tints.filter(t => {
|
||||
let distance = t - coreTint;
|
||||
return Math.abs(distance) <= offset && !((op === '-' && distance > 0) || (op === '+' && distance < 0));
|
||||
});
|
||||
}
|
||||
|
||||
return tint;
|
||||
});
|
||||
|
||||
for (let tint of tints) {
|
||||
let key = getKey({ hue, tint, palette });
|
||||
let color = palettes[palette][hue][tint];
|
||||
let value = getValue(color, { hue, tint, palette }, localUsed);
|
||||
|
||||
results[key] ??= [];
|
||||
results[key].push(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process results
|
||||
for (let key in results) {
|
||||
let values = results[key];
|
||||
|
||||
if (component === 'h') {
|
||||
values = normalizeAngles(values);
|
||||
}
|
||||
|
||||
results[key] = stats.reduce((acc, stat) => {
|
||||
acc[stat] = toPrecision(aggregates[stat](values, acc));
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
if (!silent) {
|
||||
console.log(caption);
|
||||
console.table(results);
|
||||
console.log('\n');
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
for (let test of tests) {
|
||||
analyze(test);
|
||||
runTest(test);
|
||||
}
|
||||
|
||||
function getDefaultKey(by) {
|
||||
by = Array.isArray(by) ? by : [by];
|
||||
|
||||
return variables =>
|
||||
by
|
||||
.map((variableName, i) => {
|
||||
if (variableName === 'tint' && i === 0) {
|
||||
// Drop leading zeros because they throw off row order
|
||||
return String(variables.tint).replace(/^0+/, '');
|
||||
}
|
||||
return variables[variableName];
|
||||
})
|
||||
.join('-');
|
||||
}
|
||||
|
||||
function getDefaultCaption({ component, by, filter }) {
|
||||
let ret = COMPONENT_NAMES[component];
|
||||
|
||||
by = Array.isArray(by) ? by : [by];
|
||||
ret += ` by ${by.join(' ')}`;
|
||||
|
||||
if (filter) {
|
||||
filter = Array.isArray(filter) ? filter.join(', ') : filter;
|
||||
ret += ` (${filter})`;
|
||||
}
|
||||
|
||||
ret = ret.replace('hue by hue', 'hue by color name');
|
||||
|
||||
return capitalize(ret);
|
||||
}
|
||||
|
||||
function capitalize(str) {
|
||||
return str[0].toUpperCase() + str.slice(1);
|
||||
}
|
||||
|
||||
@@ -57,3 +57,50 @@ export function hueToChalk(hue) {
|
||||
export function toPrecision(value, precision = 2) {
|
||||
return +Number(value).toPrecision(precision);
|
||||
}
|
||||
|
||||
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 const aggregates = {
|
||||
min: values => Math.min(...values),
|
||||
max: values => Math.max(...values),
|
||||
avg: values => values.reduce((a, b) => a + b, 0) / values.length,
|
||||
count: values => values.length,
|
||||
values: values => values,
|
||||
median: values => {
|
||||
let sorted = values.slice().sort((a, b) => a - b);
|
||||
let mid = Math.floor(sorted.length / 2);
|
||||
return sorted.length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
|
||||
},
|
||||
extent: (values, { min, max }) => max - min,
|
||||
mid: (values, { min, max }) => (max + min) / 2,
|
||||
};
|
||||
|
||||
6
src/styles/data.js
Normal file
6
src/styles/data.js
Normal file
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Any data relating to the design system.
|
||||
*/
|
||||
export const hues = ['red', 'yellow', 'green', 'cyan', 'blue', 'indigo', 'purple', 'pink', 'gray'];
|
||||
export const tints = ['05', '10', '20', '30', '40', '50', '60', '70', '80', '90', '95'];
|
||||
export const variants = ['neutral', 'brand', 'success', 'warning', 'danger'];
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
/**
|
||||
* Do not edit this file directly. It is generated via variants.css.js
|
||||
*/
|
||||
|
||||
/* DO NOT EDIT THIS FILE. It is generated from "variants.css.njk" */
|
||||
/**
|
||||
* Register color properties so that the space toggle hack can work.
|
||||
*/
|
||||
@@ -64,7 +61,6 @@
|
||||
* Element defaults.
|
||||
We want these to resolve to inherit when inside a variant, and only be applied when not inside an explicit variant.
|
||||
*/
|
||||
|
||||
:host(wa-button),
|
||||
.wa-button,
|
||||
button,
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
/**
|
||||
* Generate variants.css
|
||||
* To use: node variants.css.js > variants.css
|
||||
*/
|
||||
const variants = ['neutral', 'brand', 'success', 'warning', 'danger'];
|
||||
const roles = ['fill', 'border', 'on'];
|
||||
const noise = ['loud', 'normal', 'quiet'];
|
||||
const defaults = {
|
||||
neutral: [
|
||||
[':host(wa-button)', '.wa-button', 'button', "input[type='button']", "input[type='submit']"],
|
||||
[':host(wa-tag)', '.wa-tag'],
|
||||
],
|
||||
brand: [
|
||||
[':host(wa-callout)', '.wa-callout'],
|
||||
[':host(wa-badge)', '.wa-badge'],
|
||||
],
|
||||
};
|
||||
const types = roles.map(layer => noise.map(priority => layer + '-' + priority)).flat();
|
||||
|
||||
let ret = comment('Do not edit this file directly. It is generated via variants.css.js').trimStart();
|
||||
|
||||
ret += comment('Register color properties so that the space toggle hack can work.');
|
||||
|
||||
for (let type of types) {
|
||||
ret += cssRule(`@property --wa-color-${type}`, [
|
||||
`syntax: '<color>';`,
|
||||
'inherits: true;',
|
||||
'initial-value: transparent;',
|
||||
]);
|
||||
}
|
||||
|
||||
ret += comment(`Element defaults.
|
||||
We want these to resolve to inherit when inside a variant, and only be applied when not inside an explicit variant.`);
|
||||
|
||||
for (let variant in defaults) {
|
||||
let selector = defaults[variant];
|
||||
let declarations = types.map(type => `--wa-color-${type}: var(--wa-no-variant, var(--wa-color-${variant}-${type}));`);
|
||||
|
||||
ret += cssRule(selector, declarations);
|
||||
}
|
||||
|
||||
ret += comment('Variants');
|
||||
|
||||
for (let variant of variants) {
|
||||
let selector = [`.wa-${variant}`, `:host([variant='${variant}'])`];
|
||||
|
||||
if (variant === 'neutral') {
|
||||
selector.unshift(':root');
|
||||
}
|
||||
|
||||
let declarations = types.map(type => `--wa-color-${type}: var(--wa-color-${variant}-${type});`);
|
||||
ret += cssRule(selector, declarations);
|
||||
}
|
||||
|
||||
ret += cssRule([variants.map(variant => `.wa-${variant}`), ':host([variant])'], '--wa-no-variant: /* space toggle */;');
|
||||
|
||||
console.log(ret.trimEnd());
|
||||
|
||||
function cssRule(selector, declarations) {
|
||||
selector = Array.isArray(selector) ? selector.flat().join(',\n') : selector;
|
||||
declarations = Array.isArray(declarations) ? declarations.flat().join('\n') : declarations;
|
||||
declarations = declarations.replace(/^/gm, ' ');
|
||||
return `\n${selector} {\n${declarations}\n}\n`;
|
||||
}
|
||||
|
||||
function comment(text) {
|
||||
return ['\n/**', ` * ${text}`, ' */\n'].join('\n');
|
||||
}
|
||||
54
src/styles/utilities/variants.css.njk
Normal file
54
src/styles/utilities/variants.css.njk
Normal file
@@ -0,0 +1,54 @@
|
||||
{% set suffixes = [
|
||||
'fill-loud', 'fill-normal', 'fill-quiet',
|
||||
'border-loud', 'border-normal', 'border-quiet',
|
||||
'on-loud', 'on-normal', 'on-quiet'
|
||||
] -%}
|
||||
|
||||
/**
|
||||
* Register color properties so that the space toggle hack can work.
|
||||
*/
|
||||
{% for suffix in suffixes %}
|
||||
@property --wa-color-{{ suffix }} {
|
||||
syntax: '<color>';
|
||||
inherits: true;
|
||||
initial-value: transparent;
|
||||
}
|
||||
{% endfor %}
|
||||
|
||||
/**
|
||||
* Element defaults.
|
||||
We want these to resolve to inherit when inside a variant, and only be applied when not inside an explicit variant.
|
||||
*/
|
||||
:host(wa-button), .wa-button, button, input[type='button'], input[type='submit'],
|
||||
:host(wa-tag), .wa-tag {
|
||||
{%- for suffix in suffixes %}
|
||||
--wa-color-{{ suffix }}: var(--wa-no-variant, var(--wa-color-neutral-{{ suffix }}));
|
||||
{%- endfor %}
|
||||
}
|
||||
|
||||
:host(wa-callout), .wa-callout,
|
||||
:host(wa-badge), .wa-badge {
|
||||
{%- for suffix in suffixes %}
|
||||
--wa-color-{{ suffix }}: var(--wa-no-variant, var(--wa-color-brand-{{ suffix }}));
|
||||
{%- endfor %}
|
||||
}
|
||||
|
||||
/**
|
||||
* Variants
|
||||
*/
|
||||
{% for variant in variants %}
|
||||
{{ ':root,' if variant === 'neutral' }}
|
||||
.wa-{{ variant }},
|
||||
:host([variant='{{ variant }}']) {
|
||||
{%- for suffix in suffixes %}
|
||||
--wa-color-{{ suffix }}: var(--wa-color-{{ variant }}-{{ suffix }});
|
||||
{%- endfor %}
|
||||
}
|
||||
{%- endfor %}
|
||||
|
||||
{% for variant in variants %}
|
||||
.wa-{{ variant }},
|
||||
{%- endfor %}
|
||||
:host([variant]) {
|
||||
--wa-no-variant: /* space toggle */;
|
||||
}
|
||||
Reference in New Issue
Block a user