Compare commits

..

8 Commits

Author SHA1 Message Date
Lea Verou
c77acdbcd5 Delete clamped-colors.njk
No longer necessary
2025-02-20 14:35:49 -05:00
Lea Verou
d574aef103 Docs 2025-02-20 14:35:30 -05:00
Lea Verou
0fd77ca80a Prettier 2025-02-20 13:19:12 -05:00
Lea Verou
aaffb4d221 Clamped tokens for all hues, semantic scales
Also renamed:
- `-if-[gte|lt]-*` tokens to just `-[gte|lt]-*`
- `-{tint}-{min|max}` -> `-{min|max}-{tint}`

Actually import base.css

Add semantic scales
2025-02-20 13:18:04 -05:00
Lea Verou
79c9708a14 Remove superfluous import 2025-02-20 13:18:04 -05:00
Lea Verou
2090b05e8a Move CSS preprocessing code to separate file 2025-02-20 13:17:44 -05:00
Lea Verou
3cdd8e49a1 Preprocess (some) stylesheets with Nunjucks 2025-02-20 13:17:02 -05:00
Lea Verou
d8673a7d71 Preprocess (some) stylesheets with Nunjucks
- Run Prettier on preprocessed CSS
- Add comment at the start of every generated CSS file
- Generate multiple files via interpolation in filenames!
- Preprocess brand/*.css, variants.css
2025-02-20 13:14:18 -05:00
165 changed files with 2129 additions and 4326 deletions

View File

@@ -1,12 +1,11 @@
# This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
# # This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node
# # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
name: SSR Tests
on:
# push:
# branches: [next]
workflow_dispatch:
push:
branches: [next]
jobs:
ssr_test:

View File

@@ -1,4 +1,3 @@
import * as path from 'node:path';
import { anchorHeadingsPlugin } from './_utils/anchor-headings.js';
import { codeExamplesPlugin } from './_utils/code-examples.js';
import { copyCodePlugin } from './_utils/copy-code.js';
@@ -7,10 +6,9 @@ import { highlightCodePlugin } from './_utils/highlight-code.js';
import { markdown } from './_utils/markdown.js';
import { removeDataAlphaElements } from './_utils/remove-data-alpha-elements.js';
// import { formatCodePlugin } from './_utils/format-code.js';
// import litPlugin from '@lit-labs/eleventy-plugin-lit';
import litPlugin from '@lit-labs/eleventy-plugin-lit';
import { readFile } from 'fs/promises';
import nunjucks from 'nunjucks';
// import componentList from './_data/componentList.js';
import componentList from './_data/componentList.js';
import * as filters from './_utils/filters.js';
import { outlinePlugin } from './_utils/outline.js';
import { replaceTextPlugin } from './_utils/replace-text.js';
@@ -18,10 +16,7 @@ import { searchPlugin } from './_utils/search.js';
import process from 'process';
import * as url from 'url';
const __dirname = url.fileURLToPath(new URL('.', import.meta.url));
const packageData = JSON.parse(await readFile(path.join(__dirname, '..', 'package.json'), 'utf-8'));
const packageData = JSON.parse(await readFile('./package.json', 'utf-8'));
const isAlpha = process.argv.includes('--alpha');
const isDev = process.argv.includes('--develop');
@@ -29,22 +24,12 @@ const globalData = {
package: packageData,
isAlpha,
layout: 'page.njk',
server: {
head: '',
loginOrAvatar: '',
flashes: '',
},
};
const passThroughExtensions = ['js', 'css', 'png', 'svg', 'jpg', 'mp4'];
const passThrough = [...passThroughExtensions.map(ext => 'docs/**/*.' + ext)];
export default function (eleventyConfig) {
/**
* This is the guard we use for now to make sure our final built files dont need a 2nd pass by the server. This keeps us able to still deploy the bare HTML files on Vercel until the app is ready.
*/
const serverBuild = process.env.WEBAWESOME_SERVER === 'true';
// NOTE - alpha setting removes certain pages
if (isAlpha) {
eleventyConfig.ignores.add('**/experimental/**');
@@ -70,38 +55,7 @@ export default function (eleventyConfig) {
// Shortcodes - {% shortCode arg1, arg2 %}
eleventyConfig.addShortcode('cdnUrl', location => {
return `https://early.webawesome.com/webawesome@${packageData.version}/dist/` + (location || '').replace(/^\//, '');
});
// Turns `{% server "foo" %} into `{{ server.foo | safe }}` when the WEBAWESOME_SERVER variable is set to "true"
eleventyConfig.addShortcode('server', function (property) {
if (serverBuild) {
return `{{ server.${property} | safe }}`;
}
return '';
});
eleventyConfig.addTransform('second-nunjucks-transform', function NunjucksTransform(content) {
// For a server build, we expect a server to run the second transform.
if (serverBuild) {
return content;
}
// Only run the transform on files nunjucks would transform.
if (!this.page.inputPath.match(/.(md|html|njk)$/)) {
return content;
}
/** This largely mimics what an app would do and just stubs out what we don't care about. */
return nunjucks.renderString(content, {
// Stub the server EJS shortcodes.
server: {
head: '',
loginOrAvatar: '',
flashes: '',
},
});
return `https://early.webawesome.com/webawesome@${packageData.version}/dist/` + location.replace(/^\//, '');
});
// Paired shortcodes - {% shortCode %}content{% endShortCode %}
@@ -163,6 +117,29 @@ export default function (eleventyConfig) {
]),
);
// SSR plugin
if (!isDev) {
//
// Problematic components in SSR land:
// - animation (breaks on navigation + ssr with Turbo)
// - mutation-observer (why SSR this?)
// - resize-observer (why SSR this?)
// - tooltip (why SSR this?)
//
const omittedModules = [];
const componentModules = componentList
.filter(component => !omittedModules.includes(component.tagName.split(/wa-/)[1]))
.map(component => {
const name = component.tagName.split(/wa-/)[1];
return `./dist/components/${name}/${name}.js`;
});
eleventyConfig.addPlugin(litPlugin, {
mode: 'worker',
componentModules,
});
}
// Build the search index
eleventyConfig.addPlugin(
searchPlugin({
@@ -189,31 +166,6 @@ export default function (eleventyConfig) {
eleventyConfig.addPassthroughCopy(glob);
}
// // SSR plugin
// // Make sure this is the last thing, we don't want to run the risk of accidentally transforming shadow roots with the nunjucks 2nd transform.
// if (!isDev) {
// //
// // Problematic components in SSR land:
// // - animation (breaks on navigation + ssr with Turbo)
// // - mutation-observer (why SSR this?)
// // - resize-observer (why SSR this?)
// // - tooltip (why SSR this?)
// //
// const omittedModules = [];
// const componentModules = componentList
// .filter(component => !omittedModules.includes(component.tagName.split(/wa-/)[1]))
// .map(component => {
// const name = component.tagName.split(/wa-/)[1];
// const componentDirectory = process.env.UNBUNDLED_DIST_DIRECTORY || path.join('.', 'dist');
// return path.join(componentDirectory, 'components', name, `${name}.js`);
// });
//
// eleventyConfig.addPlugin(litPlugin, {
// mode: 'worker',
// componentModules,
// });
// }
return {
markdownTemplateEngine: 'njk',
dir: {

View File

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

View File

@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en" data-fa-kit-code="b10bfbde90" data-cdn-url="{% cdnUrl %}" class="wa-cloak">
<html lang="en" data-fa-kit-code="b10bfbde90" data-cdn-url="{% cdnUrl %}">
<head>
{% include 'head.njk' %}
<meta name="theme-color" content="#f36944">
@@ -50,9 +50,6 @@
Search
<kbd slot="suffix" class="only-desktop">/</kbd>
</wa-button>
{# Login #}
{% server "loginOrAvatar" %}
</div>
</header>
@@ -79,19 +76,14 @@
</aside>
{% endif %}
{# Main #}
<main id="content">
{# Expandable outline #}
{% if hasOutline %}
<nav id="outline-expandable">
<details class="outline-links">
<summary>On this page</summary>
</details>
</nav>
{% endif %}
<div id="flashes">{% server "flashes" %}</div>
{% block header %}
{% include 'breadcrumbs.njk' %}

View File

@@ -1,11 +1,8 @@
{% set ancestors = page.url | ancestors %}
{% if ancestors.length > 0 %}
{% set breadcrumbs = page.url | breadcrumbs %}
{% if breadcrumbs.length > 0 %}
<wa-breadcrumb id="docs-breadcrumbs">
{% for ancestor in ancestors %}
{% if ancestor.page.url != "/" %}
<wa-breadcrumb-item href="{{ ancestor.page.url }}">{{ ancestor.data.title }}</wa-breadcrumb-item>
{% endif %}
{% for crumb in breadcrumbs %}
<wa-breadcrumb-item href="{{ crumb.url }}">{{ crumb.title }}</wa-breadcrumb-item>
{% endfor %}
<wa-breadcrumb-item>{# Current page #}</wa-breadcrumb-item>
</wa-breadcrumb>

View File

@@ -1,18 +1,12 @@
{# Cards for pages listed by category #}
<section id="grid" class="index-grid">
{% set groupedPages = allPages | groupPages(categories, page) %}
{% for category, pages in groupedPages -%}
{% if groupedPages.meta.groupCount > 1 and pages.length > 0 %}
<h2 class="index-category" id="{{ category | slugify }}">
{% if pages.meta.url %}<a href="{{ pages.meta.url }}">{{ pages.meta.title }}</a>
{% else %}
{{ pages.meta.title }}
{% endif %}
</h2>
{% endif %}
{%- for page in pages -%}
{% include "page-card.njk" %}
{%- endfor -%}
{% for category, pages in allPages | groupByTags(categories) -%}
<h2 class="index-category">{{ category | getCategoryTitle(categories) }}</h2>
{%- for page in pages -%}
{%- if not page.data.parent or listChildren -%}
{% include "page-card.njk" %}
{%- endif -%}
{%- endfor -%}
{%- endfor -%}
</section>

View File

@@ -23,9 +23,10 @@
<script src="/assets/scripts/hydration-errors.js"></script>
<link rel="stylesheet" href="/assets/styles/hydration-errors.css">
<link rel="preconnect" href="https://cdn.jsdelivr.net">
<script type="module" src="https://cdn.jsdelivr.net/npm/@hotwired/turbo@8.0.10/+esm"></script>
{# Web Awesome #}
<script type="module" src="/dist/webawesome.loader.js"></script>
<script type="module" src="/dist/webawesome.ssr-loader.js"></script>
<script type="module" src="/assets/scripts/theme-picker.js"></script>
{# Preset Theme #}
@@ -46,7 +47,3 @@
<link rel="stylesheet" href="/dist/styles/webawesome.css" />
<link id="color-stylesheet" rel="stylesheet" href="/dist/styles/utilities.css" />
<link rel="stylesheet" href="/dist/styles/forms.css" />
{# Used by Web Awesome App to inject other assets into the head. #}
{% server "head" %}

View File

@@ -2,7 +2,7 @@
<a href="{{ page.url }}"{{ page.data.keywords | attr('data-keywords') }}>
<wa-card with-header>
<div slot="header">
{% include "svgs/" + (page.data.icon or "thumbnail-placeholder") + ".njk" ignore missing %}
{% include "svgs/" + (page.data.icon or "thumbnail-placeholder") + ".njk" %}
</div>
<span class="page-name">{{ page.data.title }}</span>
{% if pageSubtitle -%}

View File

@@ -1,12 +1,9 @@
{# Some collections (like "patterns") will not have any items in the alpha build for example. So this checks to make sure the collection exists. #}
{% if collections[tag] -%}
{% set groupUrl %}/docs/{{ tag }}/{% endset %}
{% set groupItem = groupUrl | getCollectionItemFromUrl %}
{% set children = groupItem.data.children if groupItem.data.children.length > 0 else (collections[tag] | sort) %}
<wa-details {{ ((tag in (tags or [])) or (groupUrl in page.url)) | attr('open') }}>
<h2 slot="summary">
{% if groupItem %}
{% if groupUrl | getCollectionItemFromUrl %}
<a href="{{ groupUrl }}" title="Overview">{{ title or (tag | capitalize) }}
<wa-icon name="grid-2"></wa-icon>
</a>
@@ -15,8 +12,10 @@
{% endif %}
</h2>
<ul>
{% for page in children %}
{% for page in collections[tag] | sort %}
{% if not page.data.parent -%}
{% include 'sidebar-link.njk' %}
{%- endif %}
{% endfor %}
</ul>
</wa-details>

View File

@@ -1,4 +1,4 @@
{% if page | show -%}
{% if not (isAlpha and page.data.noAlpha) and page.fileSlug != tag and not page.data.unlisted -%}
<li>
<a href="{{ page.url }}">{{ page.data.title }}</a>
{% if page.data.status == 'experimental' %}<wa-icon name="flask"></wa-icon>{% endif %}

View File

@@ -1,5 +1,6 @@
---
layout: page-outline
tags: ["overview"]
---
{% set forTag = forTag or (page.url | split('/') | last) %}
{% if description %}
@@ -12,10 +13,8 @@ layout: page-outline
</wa-input>
</div>
{% set allPages = allPages or collections[forTag] %}
{% if allPages and allPages.length > 0 %}
{% set allPages = collections[forTag] %}
{% include "grouped-pages.njk" %}
{% endif %}
<link href="/assets/styles/filter.css" rel="stylesheet">
<script type="module" src="/assets/scripts/filter.js"></script>

View File

@@ -1,9 +1,4 @@
{% if hasSidebar == undefined %}
{% set hasSidebar = true %}
{% endif %}
{% if hasOutline == undefined %}
{% set hasOutline = false %}
{% endif %}
{% set hasSidebar = true %}
{% set hasOutline = false %}
{% extends "../_includes/base.njk" %}

View File

@@ -18,15 +18,11 @@
tweaking: tweaking.chroma,
'tweaking-chroma': tweaking.chroma,
'tweaking-hue': tweaking.chroma,
'tweaking-gray-chroma': tweaking.grayChroma,
'tweaked-chroma': tweaked?.chroma,
'tweaked-hue': tweaked?.hue,
'tweaked-any': tweaked
'tweaked-chroma': tweaked.chroma,
'tweaked-hue': tweaked.hue,
'tweaked-any': tweaked.chroma || tweaked.hue
}"
:style="{
'--chroma-scale': chromaScale,
'--gray-chroma': tweaked?.grayChroma ? grayChroma : '',
}">
:style="{ '--chroma-scale': chromaScale }">
{% include 'breadcrumbs.njk' %}
@@ -40,9 +36,6 @@
<div class="block-info">
<code class="class">.wa-palette-{{ paletteId }}</code>
{% include '../_includes/status.njk' %}
{% if not isPro %}
<wa-badge class="pro" v-if="tweaked">PRO</wa-badge>
{% endif %}
</div>
{% if description %}
<p class="summary">
@@ -55,20 +48,18 @@
{% set maxChroma = 0 %}
<wa-callout size="small" class="tweaked-callout" variant="warning">
<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.
<div class="wa-cluster wa-gap-xs">
<wa-tag v-for="tweakHumanReadable, param in tweaksHumanReadable" removable @wa-remove="reset(param)">{% raw %}{{ tweakHumanReadable }}{% endraw %}</wa-tag>
</div>
<wa-tag v-for="tweakHumanReadable, param in tweaksHumanReadable" removable @wa-remove="removeTweak(param)">{% raw %}{{ tweakHumanReadable }}{% endraw %}</wa-tag>
<wa-button @click="reset()" appearance="outlined" variant="danger">
<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" variant="success">
<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>
@@ -90,67 +81,25 @@
{# Initialize to last hue before gray #}
{%- set hueBefore = hues[hues|length - 2] -%}
{% for hue in hues -%}
{% set coreTint = palettes[paletteId][hue].maxChromaTint %}
{%- set coreColor = palettes[paletteId][hue][coreTint] -%}
{%- set coreColor = palettes[paletteId][hue][palettes[paletteId][hue].maxChromaTint] -%}
{%- set maxChroma = coreColor.c if coreColor.c > maxChroma else maxChroma -%}
{% if hue === 'gray' %}
<tr data-hue="{{ hue }}" class="color-scale"
:class="{tweaking: tweaking.grayChroma, tweaked: tweaked.grayChroma || tweaked.grayColor }">
{% else %}
<tr data-hue="{{ hue }}" class="color-scale"
:class="{tweaking: tweaking.{{ hue }}, tweaked: hueShifts.{{ hue }} }"
<tr data-hue="{{ hue }}" class="color-scale" :class="{tweaking: tweaking.{{ hue }}, tweaked: hueShifts.{{ hue }} }"
:style="{ '--hue-shift': hueShifts.{{ hue }} || '' }">
{% endif %}
<th>
{{ hue | capitalize }}
</th>
<td class="core-column"
style="--color: var(--wa-color-{{ hue }})"
:style="{
'--color-tweaked': colors.{{ hue }}[{{ coreTint }}],
'--color-gray-undertone': colors[grayColor][{{coreTint}}],
'--color-tweaked-no-gray-chroma': colorsMinusGrayChroma.{{ hue }}[{{ coreTint }}],
}">
<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-icon name="sliders-simple" class="tweak-icon"></wa-icon>
</div>
<div class="popup">
{% if hue === 'gray' %}
<wa-radio-group class="core-color" orientation="horizontal" v-model="grayColor">
{% for h in hues -%}
{%- if h !== 'gray' -%}
<wa-radio-button id="gray-undertone-{{ h }}" value="{{ h }}" label="{{ h | capitalize }}" style="--color: var(--wa-color-{{ h }})"></wa-radio-button>
<wa-tooltip for="gray-undertone-{{ h }}" hoist>
{{ h | capitalize }}
</wa-tooltip>
{%- endif -%}
{%- endfor -%}
<div slot="label">
Gray undertone
</div>
</wa-radio-group>
<div class="decorated-slider gray-chroma-slider" :style="{'--max': maxGrayChroma}">
<wa-slider name="gray-chroma" v-model="grayChroma" ref="grayChromaSlider"
value="0" min="0" :max="maxGrayChroma" step="0.01"
@input="tweaking.grayChroma = true" @change="tweaking.grayChroma = false">
<div slot="label">
Gray colorfulness
<wa-icon-button @click="grayChroma = originalGrayChroma" class="clear-button" name="circle-xmark" library="system" variant="regular" label="Reset"></wa-icon-button>
</div>
</wa-slider>
<div class="label-min">Neutral</div>
<div class="label-max" v-content="moreHue[grayColor]">Warmer/Cooler</div>
</div>
{% else %}
{%- set hueAfter = hues[loop.index0 + 1] -%}
{%- set hueAfter = hues[0] if hueAfter == 'gray' else hueAfter -%}
{%- set minShift = hueRanges[hue].min - coreColor.h | round -%}
{%- set maxShift = hueRanges[hue].max - coreColor.h | round -%}
<div slot="trigger" id="core-{{ hue }}-swatch" data-tint="core" class="color swatch" style="background-color: var(--wa-color-{{ hue }}); color: var(--wa-color-{{ hue }}-{{ '05' if palettes[paletteId][hue].maxChromaTint > 60 else '95' }});">
{{ palettes[paletteId][hue].maxChromaTint }}
<wa-icon name="sliders-simple" class="tweak-icon"></wa-icon>
</div>
<div class="popup">
<div 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"
@@ -164,23 +113,23 @@
<div class="label-min">More {{hueBefore}}</div>
<div class="label-max">More {{hueAfter}}</div>
</div>
{%- set hueBefore = hue -%}
{% endif %}
<div class="wa-gap-s">
<code>--wa-color-{{ hue }}</code>
<wa-copy-button value="--wa-color-{{ hue }}" copy-label="--wa-color-{{ hue }}"></wa-copy-button>
</div>
</div>`
<code>--wa-color-{{ hue }}</code>
<wa-copy-button value="--wa-color-{{ hue }}" copy-label="--wa-color-{{ hue }}"></wa-copy-button>
</div>
</div>`
</wa-dropdown>
{%- set hueBefore = hue -%}
{% else %}
<div id="core-{{ hue }}-swatch" class="color swatch" data-tint="core" style="background-color: var(--wa-color-{{ hue }}); color: var(--wa-color-{{ hue }}-{{ '05' if palettes[paletteId][hue].maxChromaTint > 60 else '95' }});">
{{ palettes[paletteId][hue].maxChromaTint }}
</div>
{% endif %}
</td>
{% for tint in tints -%}
{%- set color = palettes[paletteId][hue][tint] -%}
<td data-tint="{{ tint }}" style="--color: var(--wa-color-{{ hue }}-{{ tint }})"
:style="{
'--color-tweaked': colors.{{ hue }}[{{ tint }}],
'--color-tweaked-no-gray-chroma': colorsMinusGrayChroma.{{ hue }}[{{ tint }}],
}">
<div class="color swatch" style="--color: var(--wa-color-{{ hue }}-{{ tint }})">
<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>
</td>
@@ -195,8 +144,7 @@
<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" ref="chromaScaleSlider"
v-model="chromaScale" value="1" step="0.01"
<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">

View File

@@ -68,7 +68,7 @@ wa_data.palettes = {
<wa-option label="{{ palette.data.title }}" value="{{ palette.fileSlug if not currentPalette }}" {{ (palette.fileSlug if currentPalette) | attr('data-id') }}>
<wa-card with-header>
<div slot="header">
{% include "svgs/" + (palette.data.icon or "thumbnail-placeholder") + ".njk" ignore missing %}
{% include "svgs/" + (palette.data.icon or "thumbnail-placeholder") + ".njk" %}
</div>
<span class="page-name">
{{ palette.data.title }}

View File

@@ -29,9 +29,6 @@ function getCollection(name) {
}
export function getCollectionItemFromUrl(url, collection) {
if (!url) {
return null;
}
collection ??= getCollection.call(this, 'all') || [];
return collection.find(item => item.url === url);
}
@@ -45,33 +42,35 @@ export function split(text, separator) {
return (text + '').split(separator).filter(Boolean);
}
export function ancestors(url, { withCurrent = false, withRoot = false } = {}) {
let ret = [];
let currentUrl = url;
let currentItem = getCollectionItemFromUrl.call(this, url);
export function breadcrumbs(url, { withCurrent = false } = {}) {
const parts = split(url, '/');
const ret = [];
if (!currentItem) {
// Might have eleventyExcludeFromCollections, jump to parent
let parentUrl = this.ctx.parentUrl;
if (parentUrl) {
url = parentUrl;
while (parts.length) {
let partialUrl = '/' + parts.join('/') + '/';
let item = getCollectionItemFromUrl.call(this, partialUrl);
if (item && (partialUrl !== url || withCurrent)) {
let title = item.data.title;
if (title) {
ret.unshift({ url: partialUrl, title });
}
}
parts.pop();
if (item?.data.parent) {
let parentURL = item.data.parent;
if (!item.data.parent.startsWith('/')) {
// Parent is in the same directory
parts.push(item.data.parent);
parentURL = '/' + parts.join('/') + '/';
}
let parentBreadcrumbs = breadcrumbs.call(this, parentURL, { withCurrent: true });
return [...parentBreadcrumbs, ...ret];
}
}
for (let item; (item = getCollectionItemFromUrl.call(this, url)); url = item.data.parentUrl) {
ret.unshift(item);
}
if (!withRoot && ret[0]?.page.url === '/') {
// Remove root
ret.shift();
}
if (!withCurrent && ret.at(-1)?.page.url === currentUrl) {
// Remove current page
ret.pop();
}
return ret;
}
@@ -178,196 +177,72 @@ export function sort(arr, by = { 'data.order': 1, 'data.title': '' }) {
});
}
export function show(page) {
return !(page.data.noAlpha && page.data.isAlpha) && !page.data.unlisted;
}
/**
* Group an 11ty collection (or any array of objects with a `data.tags` property) by certain tags.
* @param {object[]} collection
* @param { Object<string, string> | string[]} [options] Options object or array of tags to group by.
* @param {string[] | true} [options.tags] Tags to group by. If true, groups by all tags.
* If not provided/empty, defaults to grouping by page hierarchy, with any pages with more than 1 children becoming groups.
* @param {string[]} [options.groups] The groups to use if only a subset or a specific order is desired. Defaults to `options.tags`.
* @param {string[]} [options.titles] Any title overrides for groups.
* @param {string | false} [options.other="Other"] The title to use for the "Other" group. If `false`, the "Other" group is removed..
* @returns { Object.<string, object[]> } An object of group ids to arrays of page objects.
* @param { Object<string, string> | (string | Object<string, string>)[]} [tags] The tags to group by. If not provided/empty, defaults to grouping by all tags.
* @returns { Object.<string, object[]> } An object with keys for each tag, and an array of items for each tag.
*/
export function groupPages(collection, options = {}, page) {
export function groupByTags(collection, tags) {
if (!collection) {
console.error(`Empty collection passed to groupPages() to group by ${JSON.stringify(options)}`);
console.error(`Empty collection passed to groupByTags() to group by ${JSON.stringify(tags)}`);
}
if (!tags) {
// Default to grouping by union of all tags
tags = Array.from(new Set(collection.flatMap(item => item.data.tags)));
} else if (Array.isArray(tags)) {
// May contain objects of one-off tag -> label mappings
tags = tags.map(tag => (typeof tag === 'object' ? Object.keys(tag)[0] : tag));
} else if (typeof tags === 'object') {
// tags is an object of tags to labels, so we just want the keys
tags = Object.keys(tags);
}
if (Array.isArray(options)) {
options = { tags: options };
}
let { tags, groups, titles = {}, other = 'Other', filter = show } = options;
if (groups === undefined && Array.isArray(tags)) {
groups = tags;
}
let grouping;
if (tags) {
grouping = {
isGroup: item => undefined,
getCandidateGroups: item => item.data.tags,
getGroupMeta: group => ({}),
};
} else {
grouping = {
isGroup: item => (item.data.children.length >= 2 ? item.page.url : undefined),
getCandidateGroups: item => {
let parentUrl = item.data.parentUrl;
if (page?.url === parentUrl) {
return [];
}
return [parentUrl];
},
getGroupMeta: group => {
let item = byUrl[group] || getCollectionItemFromUrl.call(this, group);
return {
title: item?.data.title,
url: group,
item,
};
},
sortGroups: groups => sort(groups.map(url => byUrl[url]).filter(Boolean)).map(item => item.page.url),
};
}
let byUrl = {};
let byParentUrl = {};
if (filter) {
collection = collection.filter(filter);
}
let ret = Object.fromEntries(tags.map(tag => [tag, []]));
ret.other = [];
for (let item of collection) {
let url = item.page.url;
let parentUrl = item.data.parentUrl;
let categorized = false;
byUrl[url] = item;
if (parentUrl) {
byParentUrl[parentUrl] ??= [];
byParentUrl[parentUrl].push(item);
}
}
let urlToGroups = {};
for (let item of collection) {
let url = item.page.url;
let parentUrl = item.data.parentUrl;
if (grouping.isGroup(item)) {
continue;
}
let parentItem = byUrl[parentUrl];
if (parentItem && !grouping.isGroup(parentItem)) {
// Their parent is also here and is not a group
continue;
}
let candidateGroups = grouping.getCandidateGroups(item);
if (groups) {
candidateGroups = candidateGroups.filter(group => groups.includes(group));
}
urlToGroups[url] ??= [];
for (let group of candidateGroups) {
urlToGroups[url].push(group);
}
}
let ret = {};
for (let url in urlToGroups) {
let groups = urlToGroups[url];
let item = byUrl[url];
if (groups.length === 0) {
// Not filtered out but also not categorized
groups = ['other'];
}
for (let group of groups) {
ret[group] ??= [];
ret[group].push(item);
if (!ret[group].meta) {
if (group === 'other') {
ret[group].meta = { title: other };
} else {
ret[group].meta = grouping.getGroupMeta(group);
ret[group].meta.title = titles[group] ?? ret[group].meta.title ?? capitalize(group);
}
for (let tag of tags) {
if (item.data.tags.includes(tag)) {
ret[tag].push(item);
categorized = true;
}
}
}
if (other === false) {
delete ret.other;
}
// Sort
let sortedGroups = groups ?? grouping.sortGroups?.(Object.keys(ret));
if (sortedGroups) {
ret = sortObject(ret, sortedGroups);
} else {
// At least make sure other is last
if (ret.other) {
let otherGroup = ret.other;
delete ret.other;
ret.other = otherGroup;
if (!categorized) {
ret.other.push(item);
}
}
Object.defineProperty(ret, 'meta', {
value: {
groupCount: Object.keys(ret).length,
},
enumerable: false,
});
return ret;
}
/**
* Sort an object by its keys
* @param {*} obj
* @param {function | string[]} order
*/
function sortObject(obj, order) {
let ret = {};
let sortedKeys = Array.isArray(order) ? order : Object.keys(obj).sort(order);
for (let key of sortedKeys) {
if (key in obj) {
ret[key] = obj[key];
}
}
// Add any keys that weren't in the order
for (let key in obj) {
if (!(key in ret)) {
ret[key] = obj[key];
// Remove empty categories
for (let category in ret) {
if (ret[category].length === 0) {
delete ret[category];
}
}
return ret;
}
function capitalize(str) {
str += '';
return str.charAt(0).toUpperCase() + str.slice(1);
export function getCategoryTitle(category, categories) {
let title;
if (Array.isArray(categories)) {
// Find relevant entry
// [{id: "Title"}, id2, ...]
title = categories.find(entry => typeof entry === 'object' && entry?.[category])?.[category];
} else if (typeof categories === 'object') {
// {id: "Title", id2: "Title 2", ...}
title = categories[category];
}
if (title) {
return title;
}
// Capitalized
return category.charAt(0).toUpperCase() + category.slice(1);
}
const IDENTITY = x => x;

View File

@@ -39,7 +39,7 @@ export function outlinePlugin(options = {}) {
}
// Create a clone of the heading so we can remove links and [data-no-outline] elements from the text content
clone.querySelectorAll('.wa-visually-hidden, [hidden], [aria-hidden="true"]').forEach(el => el.remove());
clone.querySelectorAll('a').forEach(a => a.remove());
clone.querySelectorAll('[data-no-outline]').forEach(el => el.remove());
// Generate the link

View File

@@ -1,11 +1,3 @@
function debounce(func, wait) {
let timeout;
return function (...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
function updateResults(input) {
const filter = input.value.toLowerCase().trim();
let filtered = Boolean(filter);
@@ -26,10 +18,8 @@ function updateResults(input) {
}
}
const debouncedUpdateResults = debounce(updateResults, 300);
document.documentElement.addEventListener('input', e => {
if (e.target?.matches('#block-filter wa-input')) {
debouncedUpdateResults(e.target);
updateResults(e.target);
}
});

View File

@@ -13,9 +13,7 @@ sidebar.palettes = {
sidebar.updateCurrent();
},
updateSaved() {
this.saved = localStorage.savedPalettes ? JSON.parse(localStorage.savedPalettes) : [];
},
saved: localStorage.savedPalettes ? JSON.parse(localStorage.savedPalettes) : [],
save(saved = this.saved) {
this.saved = saved ?? [];
@@ -28,9 +26,6 @@ sidebar.palettes = {
},
};
sidebar.palettes.updateSaved();
addEventListener('storage', event => sidebar.palettes.updateSaved());
sidebar.palette = {
getUid() {
let savedPalettes = sidebar.palettes.saved;
@@ -41,7 +36,7 @@ sidebar.palette = {
}
// Find first available number
for (let i = 1; i <= savedPalettes.length + 1; i++) {
for (let i = 1; i < savedPalettes.length + 1; i++) {
if (!uids.has(i)) {
return i;
}
@@ -99,7 +94,7 @@ sidebar.palette = {
sidebar.palettes.save(savedPalettes);
if (sidebar.palette.equals(globalThis.paletteApp?.saved, palette)) {
paletteApp.postDelete();
paletteApp.saved = null;
}
},
@@ -189,52 +184,18 @@ sidebar.updateCurrent = function () {
// We want to start from the longest prefix
prefixes.reverse();
let candidates;
let matchingPrefix;
for (let prefix of prefixes) {
candidates = document.querySelectorAll(`#sidebar a[href^="${prefix}"]`);
let a = document.querySelector(`#sidebar a[href^="${prefix}"]`);
if (candidates.length > 0) {
matchingPrefix = prefix;
if (a) {
for (let current of document.querySelectorAll('#sidebar a.current')) {
current.classList.remove('current');
}
a.classList.add('current');
break;
}
}
if (!matchingPrefix) {
// Abort mission
return;
}
if (matchingPrefix === pathParts.at(-1)) {
// Full path matches, check search
if (location.search) {
candidates = [...candidates];
let searchParams = new URLSearchParams(location.search);
if (searchParams.has('uid')) {
// Only consider candidates with the same uid
candidates = candidates.filter(a => {
let params = new URLSearchParams(a.search);
return params.get('uid') === searchParams.get('uid');
});
} else {
// Sort candidates based on how many params they have in common, in descending order
candidates = candidates.sort((a, b) => {
return countSharedSearchParams(searchParams, b.search) - countSharedSearchParams(searchParams, a.search);
});
}
}
}
if (candidates.length > 0) {
for (let current of document.querySelectorAll('#sidebar a.current')) {
current.classList.remove('current');
}
candidates[0].classList.add('current');
}
};
sidebar.render = function () {
@@ -243,12 +204,3 @@ sidebar.render = function () {
sidebar.render();
window.addEventListener('turbo:render', () => sidebar.render());
function countSharedSearchParams(searchParams, search) {
if (!search || search === '?') {
return 0;
}
let params = new URLSearchParams(search);
return [...searchParams.keys()].filter(k => params.get(k) === searchParams.get(k)).length;
}

View File

@@ -1,30 +1,12 @@
let initialPageLoadComplete = false;
window.addEventListener('load', () => {
initialPageLoadComplete = true;
});
// Helper for view transitions
export function domChange(fn, { behavior = 'smooth', ignoreInitialLoad = true } = {}) {
export function domChange(fn, { behavior = 'smooth' } = {}) {
const canUseViewTransitions =
document.startViewTransition && !window.matchMedia('(prefers-reduced-motion: reduce)').matches;
// Skip transitions on initial page load
if (!initialPageLoadComplete && ignoreInitialLoad) {
fn(false);
return null;
}
if (canUseViewTransitions && behavior === 'smooth') {
const transition = document.startViewTransition(() => {
fn(true);
// Wait a brief delay before finishing the transition to prevent jumpiness
return new Promise(resolve => setTimeout(resolve, 200));
});
return transition;
document.startViewTransition(fn);
} else {
fn(false);
return null;
fn(true);
}
}

View File

@@ -1,6 +1,3 @@
import 'https://cdn.jsdelivr.net/npm/@hotwired/turbo@8.0.10/+esm';
import { preventTurboFouce } from '/dist/webawesome.js';
if (!window.___turboScrollPositions___) {
window.___turboScrollPositions___ = {};
}
@@ -73,4 +70,3 @@ function fixDSD(e) {
window.addEventListener('turbo:before-cache', saveScrollPosition);
window.addEventListener('turbo:before-render', restoreScrollPosition);
window.addEventListener('turbo:render', restoreScrollPosition);
preventTurboFouce();

View File

@@ -1,6 +1,6 @@
/**
* Get import code for remixed themes and tweaked palettes.
*/
export { getThemeCode } from './tweak/code.js';
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';

View File

@@ -25,23 +25,18 @@ export function cssLiteral(value, options = {}) {
}
}
// Params in correct order
export const themeParams = ['colors', 'palette', 'brand', 'typography'];
export function getThemeCode(base, params, options) {
export function theme(base, params, options) {
let ret = [];
if (base) {
ret.push(urls.theme(base));
}
for (let aspect of themeParams) {
let value = params[aspect];
if (value) {
ret.push(urls[aspect](value));
}
}
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');
}

View File

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

View File

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

View File

@@ -370,22 +370,10 @@ wa-page > main:has(> .index-grid) {
.index-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-columns: repeat(auto-fit, minmax(min(22ch, 100%), 1fr));
gap: var(--wa-space-2xl);
margin-block-end: var(--wa-space-3xl);
@media screen and (max-width: 1470px) {
grid-template-columns: repeat(3, 1fr);
}
@media screen and (max-width: 960px) {
grid-template-columns: repeat(2, 1fr);
}
@media screen and (max-width: 500px) {
grid-template-columns: repeat(1, 1fr);
}
a {
border-radius: var(--wa-border-radius-l);
text-decoration: none;

View File

@@ -15,17 +15,27 @@ icon: card
<strong>Mittens</strong><br />
This kitten is as cute as he is playful. Bring him home today!<br />
<small class="wa-caption-m">6 weeks old</small>
<small>6 weeks old</small>
<div slot="footer" class="wa-split">
<div slot="footer">
<wa-button variant="brand" pill>More Info</wa-button>
<wa-rating label="Rating"></wa-rating>
<wa-rating></wa-rating>
</div>
</wa-card>
<style>
.card-overview {
width: 300px;
max-width: 300px;
}
.card-overview small {
color: var(--wa-color-text-quiet);
}
.card-overview [slot='footer'] {
display: flex;
justify-content: space-between;
align-items: center;
}
</style>
```
@@ -55,9 +65,9 @@ If using SSR, you need to also use the `with-header` attribute to add a header t
```html {.example}
<wa-card with-header class="card-header">
<div slot="header" class="wa-split">
<div slot="header">
Header Title
<wa-icon-button name="gear" variant="solid" label="Settings" class="wa-size-m"></wa-icon-button>
<wa-icon-button name="gear" variant="solid" label="Settings"></wa-icon-button>
</div>
This card has a header. You can put all sorts of things in it!
@@ -68,9 +78,19 @@ If using SSR, you need to also use the `with-header` attribute to add a header t
max-width: 300px;
}
.card-header [slot='header'] {
display: flex;
align-items: center;
justify-content: space-between;
}
.card-header h3 {
margin: 0;
}
.card-header wa-icon-button {
font-size: var(--wa-font-size-m);
}
</style>
```
@@ -83,7 +103,7 @@ If using SSR, you need to also use the `with-footer` attribute to add a footer t
<wa-card with-footer class="card-footer">
This card has a footer. You can put all sorts of things in it!
<div slot="footer" class="wa-split">
<div slot="footer">
<wa-rating></wa-rating>
<wa-button variant="brand">Preview</wa-button>
</div>
@@ -93,6 +113,12 @@ If using SSR, you need to also use the `with-footer` attribute to add a footer t
.card-footer {
max-width: 300px;
}
.card-footer [slot='footer'] {
display: flex;
justify-content: space-between;
align-items: center;
}
</style>
```
@@ -127,7 +153,7 @@ Use the `size` attribute to change a card's size.
<wa-card with-footer size="small">
This is a small card.
<footer slot="footer" class="wa-split">
<footer slot="footer" class="wa-flank">
<wa-button variant="brand" pill>More Info</wa-button>
<wa-rating></wa-rating>
</footer>
@@ -136,7 +162,7 @@ Use the `size` attribute to change a card's size.
<wa-card with-footer size="medium">
This is a medium card (default).
<footer slot="footer" class="wa-split">
<footer slot="footer" class="wa-flank">
<wa-button variant="brand" pill>More Info</wa-button>
<wa-rating></wa-rating>
</footer>
@@ -145,39 +171,14 @@ Use the `size` attribute to change a card's size.
<wa-card with-footer size="large">
This is a large card.
<footer slot="footer" class="wa-split">
<footer slot="footer" class="wa-flank">
<wa-button variant="brand" pill>More Info</wa-button>
<wa-rating></wa-rating>
</footer>
</wa-card>
</div>
```
### Appearance
Use the `appearance` attribute to change the card's visual appearance.
```html {.example}
<div class="wa-grid">
<wa-card>
<img
slot="image"
src="https://images.unsplash.com/photo-1559209172-0ff8f6d49ff7?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=80"
alt="A kitten sits patiently between a terracotta pot and decorative grasses."
/>
<div slot="header">Outlined (default)</div>
Card content.
</wa-card>
{% for appearance in ['outlined filled', 'outlined accent', 'plain', 'filled', 'accent'] -%}
<wa-card appearance="{{ appearance }}">
<img
slot="image"
src="https://images.unsplash.com/photo-1559209172-0ff8f6d49ff7?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=80"
alt="A kitten sits patiently between a terracotta pot and decorative grasses."
/>
<div slot="header">{{ appearance | capitalize }}</div>
Card content.
</wa-card>
{%- endfor %}
</div>
```
<style>
</style>

View File

@@ -77,31 +77,6 @@ The details component automatically adapts to right-to-left languages:
</wa-details>
```
### Appearance
Use the `appearance` attribute to change the elements visual appearance.
```html {.example}
<div class="wa-stack">
<wa-details summary="Outlined (default)">
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
</wa-details>
<wa-details summary="Filled" appearance="filled">
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
</wa-details>
<wa-details summary="Filled + Outlined" appearance="filled outlined">
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
</wa-details>
<wa-details summary="Plain" appearance="plain">
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
</wa-details>
</div>
```
### Grouping Details
Details are designed to function independently, but you can simulate a group or "accordion" where only one is shown at a time by listening for the `wa-show` event.

View File

@@ -2,10 +2,13 @@
title: Components
description: Components are the essential building blocks to create intuitive, cohesive experiences. Browse the library of customizable, framework-friendly web components included in Web Awesome.
layout: overview
override:tags: []
categories:
tags: [actions, feedback, imagery, inputs, navigation, organization, helpers]
titles:
feedback: 'Feedback & Status'
helpers: 'Utilities'
- actions
- feedback: 'Feedback & Status'
- imagery
- inputs
- navigation
- organization
- helpers: 'Utilities'
override:tags: []
---

View File

@@ -1,80 +1,10 @@
/**
* Global data for all pages
*/
import { sort } from '../_utils/filters.js';
export default {
eleventyComputed: {
// Default parent. Can be overridden by explicitly setting parent in the data.
// parent can refer to either an ancestor page in the URL or another page in the same directory
parent(data) {
let { parent, page } = data;
if (parent) {
return parent;
}
return page.url.split('/').filter(Boolean).at(-2);
},
parentUrl(data) {
let { parent, page } = data;
return getParentUrl(page.url, parent);
},
parentItem(data) {
let { parentUrl } = data;
return data.collections.all.find(item => item.url === parentUrl);
},
children(data) {
let { collections, page, parentOf } = data;
let mainTag = data.tags?.[0];
let collection = data.collections[mainTag] ?? [];
if (parentOf) {
return collections[parentOf];
}
let collection = collections.all ?? [];
let url = page.url;
let ret = collection.filter(item => {
return item.data.parentUrl === url;
});
sort(ret);
return ret;
return collection.filter(item => item.data.parent === data.page.fileSlug);
},
},
};
function getParentUrl(url, parent) {
let parts = url.split('/').filter(Boolean);
let ancestorIndex = parts.findLastIndex(part => part === parent);
let retParts = parts.slice();
if (ancestorIndex > -1) {
// parent is an ancestor
retParts.splice(ancestorIndex + 1);
} else {
// parent is a sibling in the same directory
retParts.splice(-1, 1, parent);
}
let ret = retParts.join('/');
if (url.startsWith('/')) {
ret = '/' + ret;
}
if (!retParts.at(-1).includes('.') && !ret.endsWith('/')) {
// If no extension, make sure to end with a slash
ret += '/';
}
if (ret === '/docs/') {
ret = '/';
}
return ret;
}

View File

@@ -1,44 +0,0 @@
---
title: Clamped Color Tokens
layout: block
---
{% set tints = ['max-50', 'max-60', 'max-70', 'min-50', 'min-60', 'min-70'] %}
{% set hues = ['red', 'orange', 'yellow', 'green', 'cyan', 'blue', 'indigo', 'purple', 'pink', 'gray'] %}
<table class="colors">
<thead>
<tr>
<th></th>
<th class="core-column">Core tint</th>
{% for tint in tints -%}
<th>{{ tint }}</th>
{%- endfor %}
</tr>
</thead>
{% for hue in hues -%}
<tr class="wa-color-{{ hue }}">
<th>{{ hue | capitalize }}</th>
<td class="core-column">
<div class="color swatch" style="background-color: var(--wa-color-{{ hue }}); color: var(--wa-color-{{ hue }}-on); --key: var(--wa-color-{{ hue }}-key);">
{{ palettes[paletteId][hue].maxChromaTint }}
<wa-copy-button value="--wa-color-{{ hue }}" copy-label="--wa-color-{{ hue }}"></wa-copy-button>
</div>
</td>
{% for tint in tints -%}
<td>
<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>
</td>
{%- endfor -%}
</tr>
{%- endfor %}
</table>
<style>
.core-column .color.swatch::before {
counter-reset: key var(--key);
content: counter(key);
}
</style>

View File

@@ -37,6 +37,10 @@ This snippet includes three parts:
Now you can [start using Web Awesome!](/docs/usage)
:::info
While convenient, autoloading may lead to a [Flash of Undefined Custom Elements](https://www.abeautifulsite.net/posts/flash-of-undefined-custom-elements/). The linked article describes some ways to alleviate it.
:::
---
## Using Font Awesome Kit Codes

View File

@@ -2,7 +2,6 @@
title: Layout
description: Layout components and utility classes help you organize content that can adapt to any device or screen size. See the [installation instructions](#installation) to use Web Awesome's layout tools in your project.
layout: overview
parentOf: layout
categories: ["components", "utilities"]
override:tags: []
---
@@ -23,4 +22,4 @@ Or, you can choose to import _only_ the utilities:
```html
<link rel="stylesheet" href="{% cdnUrl 'styles/utilities.css' %}" />
```
{% endmarkdown %}
{% endmarkdown %}

View File

@@ -33,7 +33,7 @@ Use the [variant utility classes](../utilities/color.md) to set the button's sem
### Appearance
Use the [appearance utility classes](/docs/utilities/appearance) to change the button's visual appearance:
Use the [appearance utility classes](../utilities/appearance.md) to change the button's visual appearance:
```html {.example}
<div style="margin-block-end: 1rem;">

View File

@@ -57,7 +57,7 @@ Use the [variant utility classes](../utilities/color.md) to set the callout's co
### Appearance
Use the [appearance utility classes](/docs/utilities/appearance) to change the callout's visual appearance (the default is `outlined filled`).
Use the [appearance utility classes](../utilities/appearance.md) to change the callout's visual appearance (the default is `outlined filled`).
```html {.example}
<article class="wa-callout wa-brand wa-outlined wa-accent">

View File

@@ -19,35 +19,6 @@ file: styles/native/details.css
## Examples
### Appearance
Use the [appearance utility classes](/docs/utilities/appearance) to change the element's visual appearance:
```html {.example}
<div class="wa-stack">
<details>
<summary>Outlined (default)</summary>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
</details>
<details class="wa-filled">
<summary>Filled</summary>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
</details>
<details class="wa-filled wa-outlined">
<summary>Filled + Outlined</summary>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
</details>
<details class="wa-plain">
<summary>Plain</summary>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
</details>
</div>
```
### Right-to-Left Languages
The details styling automatically adapts to right-to-left languages:

View File

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

View File

@@ -5,6 +5,6 @@ layout: overview
override:tags: []
forTag: palette
categories:
tags: [other, pro]
other: Free
pro: Pro
---

View File

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

View File

@@ -3,8 +3,7 @@ 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 { maxGrayChroma, moreHue, selectors, urls } from '../../assets/scripts/tweak/data.js';
import { subtractAngles } from '../../assets/scripts/tweak/util.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)));
@@ -35,8 +34,6 @@ for (let palette in allPalettes) {
}
}
const percentFormatter = value => value.toLocaleString(undefined, { style: 'percent' });
let paletteAppSpec = {
data() {
let appRoot = document.querySelector('#palette-app');
@@ -52,17 +49,12 @@ let paletteAppSpec = {
hueRanges,
hueShifts: Object.fromEntries(hues.map(hue => [hue, 0])),
chromaScale: 1,
grayChroma: undefined,
grayColor: undefined,
tweaking: {},
saved: null,
};
},
created() {
// Non-reactive variables to expose
Object.assign(this, { moreHue });
// Read URL params and apply them. This facilitates permalinks.
this.permalink.mapObject(this.hueShifts, {
keyTo: key => key.replace(/-shift$/, ''),
@@ -71,49 +63,30 @@ let paletteAppSpec = {
valueTo: value => (!value ? 0 : Number(value)),
});
this.grayChroma = this.originalGrayChroma;
this.grayColor = this.originalGrayColor;
if (location.search) {
// Update from URL
this.permalink.writeTo(this.hueShifts);
for (let param of ['chroma-scale', 'gray-color', 'gray-chroma']) {
if (this.permalink.has(param)) {
let value = this.permalink.get(param);
if (!isNaN(value)) {
// Convert numeric values to numbers
value = Number(value);
}
let prop = camelCase(param);
this[prop] = value;
}
if (this.permalink.has('chroma-scale')) {
this.chromaScale = Number(this.permalink.get('chroma-scale') || 1);
}
if (this.permalink.has('uid')) {
this.uid = Number(this.permalink.get('uid'));
}
this.saved = sidebar.palette.getSaved(this.getPalette());
}
},
mounted() {
for (let ref in this.$refs) {
this.$refs[ref].tooltipFormatter = percentFormatter;
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,
grayColor: this.grayColor,
grayChroma: this.grayChroma,
};
return { hueShifts: this.hueShifts, chromaScale: this.chromaScale };
},
isTweaked() {
@@ -123,7 +96,7 @@ let paletteAppSpec = {
code() {
let ret = {};
for (let language of ['html', 'css']) {
let code = getPaletteCode(this.paletteId, this.colors, this.tweaked, { language, cdnUrl });
let code = getPaletteCode(this.paletteId, this.tweaks, { language, cdnUrl });
ret[language] = {
raw: code,
highlighted: Prism.highlight(code, Prism.languages[language], language),
@@ -134,46 +107,47 @@ let paletteAppSpec = {
},
colors() {
return applyTweaks.call(this, this.originalColors, this.tweaks, this.tweaked);
},
let ret = {};
colorsMinusChromaScale() {
let tweaked = { ...this.tweaked, chromaScale: false };
return applyTweaks.call(this, this.originalColors, this.tweaks, tweaked);
},
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 },
});
colorsMinusHueShifts() {
let tweaked = { ...this.tweaked, hue: false };
return applyTweaks.call(this, this.originalColors, this.tweaks, tweaked);
},
for (let tint of tints) {
let oklch = originalScale[tint].coords.slice();
colorsMinusGrayChroma() {
let tweaked = { ...this.tweaked, grayChroma: false };
return applyTweaks.call(this, this.originalColors, this.tweaks, tweaked);
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() {
let anyHueTweaked = Object.values(this.hueShifts).some(Boolean);
let hue = anyHueTweaked
? Object.fromEntries(Object.entries(this.hueShifts).map(([hue, shift]) => [hue, shift !== 0]))
: false;
let ret = {
chromaScale: this.chromaScale !== 1,
hue,
grayChroma: this.grayChroma !== this.originalGrayChroma,
grayColor: this.grayColor !== this.originalGrayColor,
return {
chroma: this.chromaScale !== 1,
hue: Object.values(this.hueShifts).some(Boolean),
};
let anyTweaked = Object.values(ret).some(Boolean);
return anyTweaked ? ret : false;
},
tweaksHumanReadable() {
let ret = {};
if (this.chromaScale !== 1) {
ret.chromaScale = 'More ' + (this.chromaScale > 1 ? 'vibrant' : 'muted');
ret.chromaScale = 'more ' + (this.chromaScale > 1 ? 'vibrant' : 'muted');
}
for (let hue in this.hueShifts) {
@@ -184,100 +158,64 @@ let paletteAppSpec = {
}
let relHue = shift < 0 ? arrayPrevious(hues, hue) : arrayNext(hues, hue);
let hueTweak = moreHue[relHue] ?? relHue + 'er';
let hueTweak =
{
red: 'redder',
orange: 'oranger',
indigo: 'more indigo',
}[relHue] ?? relHue + 'er';
ret[hue] = capitalize(hueTweak + ' ' + hue + 's');
}
if (this.tweaked.grayChroma || this.tweaked.grayColor) {
if (this.tweaked.grayChroma === 0) {
ret.grayChroma = 'Achromatic grays';
} else {
if (this.tweaked.grayColor) {
ret.grayColor = capitalize(this.grayColor) + ' gray undertone';
}
if (this.tweaked.grayChroma) {
let more = this.tweaked.grayChroma > this.originalGrayChroma;
ret.grayChroma = `More ${more ? 'colorful' : 'neutral'} grays`;
}
}
ret[hue] = hueTweak + ' ' + hue + 's';
}
return ret;
},
originalContrasts() {
return getContrasts(this.originalColors);
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() {
return getContrasts(this.colors, this.originalContrasts);
},
originalCoreColors() {
let ret = {};
for (let hue in this.originalColors) {
let maxChromaTintRaw = this.originalColors[hue].maxChromaTintRaw;
ret[hue] = this.originalColors[hue][maxChromaTintRaw];
}
return ret;
},
coreColors() {
let ret = {};
for (let hue in this.colors) {
let maxChromaTintRaw = this.colors[hue].maxChromaTintRaw;
ret[hue] = this.colors[hue][maxChromaTintRaw];
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;
},
originalGrayColor() {
let grayHue = this.originalCoreColors.gray.get('h');
let minDistance = Infinity;
let closestHue = null;
for (let name in this.originalCoreColors) {
if (name === 'gray') {
continue;
}
let hue = this.originalCoreColors[name].get('h');
let distance = Math.abs(subtractAngles(hue, grayHue));
if (distance < minDistance) {
minDistance = distance;
closestHue = name;
}
}
return closestHue ?? 'indigo';
},
originalGrayChroma() {
let coreTint = this.originalColors.gray.maxChromaTint;
let grayChroma = this.originalColors.gray[coreTint].get('c');
if (grayChroma === 0 || grayChroma === null) {
return 0;
}
let grayColorChroma = this.originalColors[this.originalGrayColor][coreTint].get('c');
return grayChroma / grayColorChroma;
},
/**
* We want to preserve the original grayChroma selection so that when the user switches to another undertone
* that supports higher chromas, their selection will be there.
* This property is the gray chroma % that is actually applied.
*/
computedGrayChroma() {
return Math.min(this.grayChroma, this.maxGrayChroma);
},
maxGrayChroma() {
return maxGrayChroma[this.grayColor] ?? 0.3;
},
},
watch: {
@@ -292,14 +230,6 @@ let paletteAppSpec = {
this.permalink.set('chroma-scale', this.chromaScale, 1);
},
grayColor() {
this.permalink.set('gray-color', this.grayColor, this.originalGrayColor);
},
grayChroma() {
this.permalink.set('gray-chroma', this.grayChroma, this.originalGrayChroma);
},
tweaks: {
deep: true,
async handler(value, oldValue) {
@@ -316,10 +246,6 @@ let paletteAppSpec = {
},
methods: {
getPalette() {
return { id: this.paletteId, uid: this.uid, search: location.search };
},
save({ silent } = {}) {
let title = silent
? (this.saved?.title ?? this.paletteTitle)
@@ -332,15 +258,13 @@ let paletteAppSpec = {
let uid = this.uid;
if (!uid) {
// First time saving
this.uid = uid = sidebar.palette.getUid();
this.permalink.set('uid', uid);
this.permalink.updateLocation();
}
let palette = { ...this.getPalette(), uid, title };
let palette = { title, id: this.paletteId, uid, search: location.search };
sidebar.palette.save(palette, this.saved);
this.saved = palette;
},
@@ -362,38 +286,21 @@ let paletteAppSpec = {
deleteSaved() {
sidebar.palette.delete(this.saved);
},
postDelete() {
this.saved = null;
this.permalink.delete('uid');
this.uid = undefined;
this.permalink.updateLocation();
},
/**
* Remove a specific tweak or all tweaks
* @param {string} [param] - The tweak to remove. If not provided, all tweaks are removed.
*/
reset(param) {
if (!param || param === 'chromaScale') {
reset() {
for (let hue in this.hueShifts) {
this.hueShifts[hue] = 0;
}
this.chromaScale = 1;
},
removeTweak(param) {
if (param === 'chromaScale') {
this.chromaScale = 1;
}
if (param in this.hueShifts) {
} else {
this.hueShifts[param] = 0;
} else if (!param) {
for (let hue in this.hueShifts) {
this.hueShifts[hue] = 0;
}
}
if (!param || param === 'grayColor') {
this.grayColor = this.originalGrayColor;
}
if (!param || param === 'grayChroma') {
this.grayChroma = this.originalGrayChroma;
}
},
},
@@ -429,20 +336,16 @@ let paletteAppSpec = {
};
function init() {
let paletteAppContainer = document.querySelector('#palette-app');
globalThis.paletteApp?.unmount?.();
if (!paletteAppContainer) {
return;
}
globalThis.paletteApp = createApp(paletteAppSpec).mount(paletteAppContainer);
globalThis.paletteApp = createApp(paletteAppSpec).mount('#palette-app');
}
init();
addEventListener('turbo:render', init);
export function getPaletteCode(paletteId, colors, tweaked, options) {
export function getPaletteCode(paletteId, tweaks, options) {
let palette = allPalettes[paletteId].colors;
let imports = [];
if (paletteId) {
@@ -450,27 +353,37 @@ export function getPaletteCode(paletteId, colors, tweaked, options) {
}
let css = '';
let declarations = [];
if (tweaked) {
for (let hue in colors) {
if (hue === 'orange') {
continue;
} else if (hue === 'gray') {
if (!tweaked.grayChroma && !tweaked.grayColor) {
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;
}
} else if (!tweaked.chromaScale && !tweaked.hue?.[hue]) {
continue;
}
for (let tint of tints) {
let color = colors[hue][tint];
let stringified = color.toString({ format: color.inGamut('srgb') ? 'hex' : undefined });
declarations.push(`--wa-color-${hue}-${tint}: ${stringified};`);
}
let scale = palette[hue];
declarations.push('');
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) {
@@ -496,85 +409,3 @@ function arrayPrevious(array, element) {
let index = array.indexOf(element);
return array[(index - 1 + array.length) % array.length];
}
function applyTweaks(originalColors, tweaks, tweaked) {
let ret = {};
let { hueShifts, chromaScale = 1, grayColor, grayChroma } = tweaks;
if (!tweaked) {
return originalColors;
}
if (tweaked.grayChroma) {
grayChroma = this.computedGrayChroma;
}
for (let hue in originalColors) {
let originalScale = originalColors[hue];
let scale = (ret[hue] = {});
let descriptors = Object.getOwnPropertyDescriptors(originalScale);
Object.defineProperties(scale, {
maxChromaTint: { ...descriptors.maxChromaTint, enumerable: false },
maxChromaTintRaw: { ...descriptors.maxChromaTintRaw, enumerable: false },
});
for (let tint of tints) {
let color = originalScale[tint].clone();
if (tweaked.hue && hueShifts[hue]) {
color.set({ h: h => h + hueShifts[hue] });
}
if (tweaked.chromaScale && chromaScale !== 1) {
color.set({ c: c => c * chromaScale });
}
if (hue === 'gray' && (tweaked.grayChroma || tweaked.grayColor)) {
let colorUndertone = originalColors[grayColor][tint].clone();
color = colorUndertone.set({ c: c => c * grayChroma });
}
scale[tint] = color;
}
}
return ret;
}
function camelCase(str) {
return (str + '').replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
}
function capitalize(str) {
return str[0].toUpperCase() + str.slice(1);
}
function getContrasts(colors, originalContrasts) {
let ret = {};
for (let hue in colors) {
ret[hue] = {};
for (let tintBg of tints) {
ret[hue][tintBg] = {};
let bgColor = colors[hue][tintBg];
if (!bgColor || !bgColor.contrast) {
continue;
}
for (let tintFg of tints) {
let fgColor = colors[hue][tintFg];
let value = bgColor.contrast(fgColor, 'WCAG21');
if (originalContrasts) {
let original = originalContrasts[hue][tintBg][tintFg];
ret[hue][tintBg][tintFg] = { value, original, bgColor, fgColor };
} else {
ret[hue][tintBg][tintFg] = value;
}
}
}
}
return ret;
}

View File

@@ -2,5 +2,7 @@
title: Patterns
description: Patterns are reusable solutions to common design problems.
layout: overview
categories: ["e-commerce"]
listChildren: true
override:tags: []
---

View File

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

View File

@@ -31,7 +31,8 @@ If you're customizing the default dark styles, scope your styles to the followin
```css
.wa-dark,
.wa-invert {
.wa-invert,
:is(:host-context(.wa-dark)) {
/* your custom styles here */
}
```

View File

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

View File

@@ -1,13 +1,13 @@
---
title: Themes
description: Themes are collections of design tokens that thread through every Web Awesome component and pattern.
description: Themes are collections of design tokens that thread through every Web Awesome component and pattern.
Themes play a crucial role in [customizing Web Awesome](/docs/customizing).
layout: overview
override:tags: []
forTag: theme
categories:
tags: [other, pro]
other: Free
pro: Pro
---
<div class="max-line-length">
@@ -30,7 +30,7 @@ In pre-made themes, we use a light color scheme by default.
Additionally, styles may be scoped to the `:root` selector to be activated automatically.
For pre-made themes, *all* custom properties are scoped to `:root`, the theme class, and `wa-light`.
Finally, we scope themes to `:host` to ensure the styles are applied to the shadow roots of custom elements.
Finally, we scope themes to `:host` and `:host-context()` to ensure the styles are applied to the shadow roots of custom elements.
For example, the default theme is set up like this:
@@ -44,7 +44,8 @@ For example, the default theme is set up like this:
}
.wa-dark,
.wa-invert {
.wa-invert,
:host-context(.wa-dark) {
/* subset of CSS custom properties for a dark color scheme */
}
```

View File

@@ -107,10 +107,6 @@ function setDefault(select, value) {
}
function render(changedAspect) {
if (!globalThis.demo) {
return;
}
let url = new URL(demo.src);
if (!changedAspect || changedAspect === 'colors') {

View File

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

View File

@@ -1,6 +1,7 @@
---
title: Color
description: Ensure consistent use of color and readable contrast with Web Awesome's color properties.
wide: true
---
<style>
@@ -43,6 +44,18 @@ description: Ensure consistent use of color and readable contrast with Web Aweso
color: var(--wa-color-brand-on-loud);
text-align: center;
}
.colors.bounded,
.core-column {
.color.swatch {
--key: var(--core-key);
--lt-60: calc(clamp(0, 60 - var(--key), 1) * 100%);
&::before {
counter-reset: key var(--key);
content: counter(key);
}
}
}
</style>
Web Awesome's color system is made up of CSS custom properties to help with consistent color use throughout your project.
@@ -64,21 +77,125 @@ You can use these numbers to ensure accessible color contrast per [WCAG 2.1 succ
- A difference of 50 ensures a minimum 4.5:1 contrast ratio, suitable for normal text (AA) and large text (AAA)
- A difference of 60 ensures a minimum 7:1 contrast ratio, suitable for all text (AAA)
Each Web Awesome palette defines seven literal colors each with 11 lightness values using the format `--wa-color-{hue}-{tint}`.
Each Web Awesome palette defines eight literal colors each with 11 lightness values using the format `--wa-color-{hue}-{tint}`.
Additionally, the "core" (most vibrant) color of each scale is mapped to `--wa-color-{hue}`.
{% set tints = ["95", "90", "80", "70", "60", "50", "40", "30", "20", "10", "05"] -%}
<table class="colors">
<thead>
<tr>
<th></th>
<th class="core-column">Core tint</th>
{% for tint in tints -%}
<th>{{ tint }}</th>
{%- endfor %}
</tr>
</thead>
{% for hue in hues -%}
<div class="color-name">{{ hue | capitalize }}</div>
<ul class="color-group">
{% for tint in ["95", "90", "80", "70", "60", "50", "40", "30", "20", "10", "05"] -%}
<li class="color-preview">
<tr style="--core-key: var(--wa-color-{{ hue }}-key)">
<th>{{ hue | capitalize }}</th>
<td class="core-column" style="--color: var(--wa-color-{{ hue }})">
<div class="color swatch" style="background-color: var(--wa-color-{{ hue }}); color: var(--wa-color-{{ hue }}-on);">
<wa-copy-button value="--wa-color-{{ hue }}" copy-label="--wa-color-{{ hue }}"></wa-copy-button>
</div>
</td>
{% for tint in tints -%}
<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>
<small>{{ tint }}</small>
</li>
{%- endfor %}
</ul>
</td>
{%- endfor -%}
</tr>
{%- endfor %}
</table>
## Bounded Core Colors
In addition to the literal color scales above and their core colors,
we also define variables for "bounded" core colors, which resolve to the core color of the hue's scale,
but clamped to a specific tint.
This is useful for using core colors while ensuring a minimum contrast ratio with other colors in your theme.
### Upper Bound Core Colors
These variables resolve to the core color as long as it is equal or below the specified tint,
and to the specified tint otherwise.
{% set tints_clamped = ['40', '50', '60', '70'] %}
<table class="colors bounded">
<thead>
<tr>
<th></th>
<th class="core-column">Core tint</th>
{% for tint in tints_clamped -%}
<th>max-{{ tint }}</th>
{%- endfor %}
</tr>
</thead>
{% for hue in hues -%}
<tr style="--core-key: var(--wa-color-{{ hue }}-key)">
<th>{{ hue | capitalize }}</th>
<td class="core-column">
<div class="color swatch" style="background-color: var(--wa-color-{{ hue }}); color: var(--wa-color-{{ hue }}-on);">
{{ palettes[paletteId][hue].maxChromaTint -}}
<wa-copy-button value="--wa-color-{{ hue }}" copy-label="--wa-color-{{ hue }}"></wa-copy-button>
</div>
</td>
{% for tint in tints_clamped -%}
<td>
<div class="color swatch" style="
--key: min(var(--core-key), {{ tint }});
color: color-mix(in oklab, var(--wa-color-{{ hue }}-10), white var(--lt-60));
background-color: var(--wa-color-{{ hue }}-max-{{ tint }});">
<wa-copy-button value="--wa-color-{{ hue }}-max-{{ tint }}" copy-label="--wa-color-{{ hue }}-max-{{ tint }}"></wa-copy-button>
</div>
</td>
{%- endfor -%}
</tr>
{%- endfor %}
</table>
### Lower Bound Core Colors
These variables resolve to the core color as long as it is at least at the specified tint,
and to the specified tint otherwise.
<table class="colors bounded">
<thead>
<tr>
<th></th>
<th class="core-column">Core tint</th>
{% for tint in tints_clamped -%}
<th>min-{{ tint }}</th>
{%- endfor %}
</tr>
</thead>
{% for hue in hues -%}
<tr style="--core-key: var(--wa-color-{{ hue }}-key)">
<th>{{ hue | capitalize }}</th>
<td class="core-column">
<div class="color swatch" style="background-color: var(--wa-color-{{ hue }}); color: var(--wa-color-{{ hue }}-on);">
{{ palettes[paletteId][hue].maxChromaTint -}}
<wa-copy-button value="--wa-color-{{ hue }}" copy-label="--wa-color-{{ hue }}"></wa-copy-button>
</div>
</td>
{% for tint in tints_clamped -%}
<td>
<div class="color swatch" style="
--key: max(var(--core-key), {{ tint }});
color: color-mix(in oklab, var(--wa-color-{{ hue }}-10), white var(--lt-60));
background-color: var(--wa-color-{{ hue }}-min-{{ tint }})">
<wa-copy-button value="--wa-color-{{ hue }}-min-{{ tint }}" copy-label="--wa-color-{{ hue }}-min-{{ tint }}"></wa-copy-button>
</div>
</td>
{%- endfor -%}
</tr>
{%- endfor %}
</table>
## Foundational Colors

View File

@@ -3,5 +3,4 @@ title: Design Tokens
description: A theme is a collection of predefined CSS custom properties that control global styles from color to shadows. These custom properties thread through all Web Awesome components for a consistent look and feel.
layout: overview
override:tags: []
categories: {tags: true}
---

View File

@@ -8,30 +8,6 @@ Web Awesome components are just regular HTML elements, or [custom elements](http
If you're new to custom elements, often referred to as "web components," this section will familiarize you with how to use them.
## Awaiting Registration
Unlike traditional frameworks, custom elements don't have a centralized initialization phase. This means you need to verify that a custom element has been properly registered before attempting to interact with its properties or methods.
You can use the [`customElements.whenDefined()`](https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/whenDefined) method to ensure a specific component is ready:
```ts
await customElements.whenDefined('wa-button');
// <wa-button> is ready to use!
const button = document.querySelector('wa-button');
```
When working with multiple components, checking each one individually can become tedious. For convenience, Web Awesome provides the `allDefined()` function which automatically detects and waits for all Web Awesome components in the DOM to be initialized before resolving.
```ts
import { allDefined } from '/dist/webawesome.js';
// Waits for all Web Awesome components in the DOM to be registered
await allDefined();
// All Web Awesome components on the page are ready!
```
## Attributes & Properties
Many components have properties that can be set using attributes. For example, buttons accept a `size` attribute that maps to the `size` property which dictates the button's size.

View File

@@ -1,32 +0,0 @@
---
title: Reduce FOUCE
description: Utility to improve the loading experience by hiding non-prerendered custom elements until they are registered.
file: styles/utilities/fouce.css
icon: spinner
---
While convenient, autoloading can lead to a [Flash of Undefined Custom Elements](https://www.abeautifulsite.net/posts/flash-of-undefined-custom-elements/).
The [FOUCE style utility](/docs/utilities/fouce/#opting-in) (which is automatically applied if you use the [Web Awesome utilities](/docs/utilities/)) takes care of hiding custom elements until they and their contents have been registered, up to a maximum of two seconds.
In many cases, this is not enough, and you may wish to hide a broader wrapper element or even the entire page until all WA elements within it have loaded. To do that, you can add the `wa-reduce-fouce` class to any element on the page or even apply it to the whole page by placing the class on the `<html>` element.
```html
<html class="wa-cloak">
...
</html>
```
As soon as all elements are registered _or_ after two seconds have elapsed, the autoloader will show the page. The two-second timeout prevents blank screens from persisting on slow networks and pages that have errors.
:::details Are you using Turbo in your app?
If you're using [Turbo](https://turbo.hotwired.dev/) to serve a multi-page application (MPA) as a single page application (SPA), you might notice FOUCE when navigating from page to page. This is because Turbo renders the new page's content before the autoloader has a change to register new components.
The following function acts as a middleware to ensure components are registered _before_ the page shows, eliminating FOUCE for page-to-page navigation with Turbo.
```js
import { preventTurboFouce } from '/dist/webawesome.js';
preventTurboFouce();
```
:::

View File

@@ -0,0 +1,131 @@
---
title: Reduce FOUCE
description: Utility to improve the loading experience by hiding non-prerendered custom elements until they are registered.
file: styles/utilities/fouce.css
icon: spinner
---
{% markdown %}
No class is needed to use this utility, it will be applied automatically as long as it its CSS is included.
Here is a comparison of the loading experience with and without this utility,
with a simulated slow loading time:
{% endmarkdown %}
<div class="wa-split wa-align-items-end">
<strong>Normal loading</strong>
<wa-button onclick="document.querySelectorAll('iframe').forEach(iframe => iframe.srcdoc = iframe.srcdoc)">
<wa-icon name="refresh"></wa-icon>
Refresh
</wa-button>
<strong>With FOUCE reduction</strong>
</div>
{% set sample_card %}
<link id="theme-stylesheet" rel="stylesheet" href="/dist/styles/themes/default.css" render="blocking" fetchpriority="high">
<link rel="stylesheet" href="/dist/styles/webawesome.css">
<link rel="stylesheet" href="/dist/styles/forms.css">
<script type=module>
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
const loadScript = src => new Promise((resolve, reject) => {
const script = document.createElement("script");
script.src = src;
script.type = "module";
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
await delay(500);
await loadScript("/dist/components/button/button.js");
await delay(500);
await loadScript("/dist/components/card/card.js");
await delay(500);
await loadScript("/dist/components/rating/rating.js");
</script>
<wa-card with-footer with-image class="card-overview">
<img
slot="image"
src="https://images.unsplash.com/photo-1559209172-0ff8f6d49ff7?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=80"
alt="A kitten sits patiently between a terracotta pot and decorative grasses."
/>
<strong>Mittens</strong><br />
This kitten is as cute as he is playful. Bring him home today!<br />
<small>6 weeks old</small>
<div slot="footer">
<wa-button variant="brand" pill>More Info</wa-button>
<wa-rating></wa-rating>
</div>
</wa-card>
<style>
.card-overview small {
color: var(--wa-color-text-quiet);
}
.card-overview [slot=footer] {
display: flex;
justify-content: space-between;
align-items: center;
}
</style>
{% endset %}
<div class="iframes">
<iframe srcdoc='<body class="wa-fouce-off">{{ sample_card }}</body>'></iframe>
<iframe srcdoc='{{ sample_card }}'></iframe>
</div>
<style>
.iframes {
display: flex;
gap: var(--wa-space-m);
margin-top: var(--wa-space-l);
iframe {
flex: 1;
height: 60ch;
}
}
</style>
{% markdown %}
## How does it work?
The utility consists of a timeout (`2s` by default) and a fade duration (`200ms` by default).
- If the element is _ready_ before the timeout, it will appear immediately.
- If it takes longer than _timeout_ + _fade_, it will fade in over the fade duration.
- If it takes somewhere between _timeout_ and _timeout_ + _fade_, you will get an interrupted fade.
An element is considered ready when both of these are true:
1. Either It has been registered or has a `did-ssr` attribute (indicating it was pre-rendered)
2. If its a Web Awesome component, its contents are also ready
## Customization
You can use the following CSS variables to customize the behavior:
| Variable | Description | Default |
| --- | --- | --- |
| `--wa-fouce-fade` | The transition duration for the fade effect. | `200ms` |
| `--wa-fouce-timeout` | The timeout after which elements will appear even if not registered | `2s` |
The fade duration cannot be longer than the timeout.
This means that you can disable FOUCE reduction on an element by setting `--wa-fouce-timeout: 0s`.
For example, if instead of `did-ssr` you used an `ssr` attribute to mark elements that were pre-rendered, you can do this to get them to appear immediately:
```css
[ssr] {
--wa-fouce-timeout: 0s;
}
```
You can also opt-out from FOUCE reduction for an element and its contents by adding the `.wa-fouce-off` class to it.
Applying this class to the root element will disable the utility for the entire page.
{% endmarkdown %}

View File

@@ -4,6 +4,8 @@ description: Build better with Web Awesome, the open source library of web compo
layout: page
---
<style>
.title,
.anchor-heading a,
@@ -385,4 +387,4 @@ layout: page
&copy; Fonticons, Inc.
</div>
</footer>
</div>
</div>

4
package-lock.json generated
View File

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

View File

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

View File

@@ -4,7 +4,6 @@ import { execSync } from 'child_process';
import { deleteAsync } from 'del';
import esbuild from 'esbuild';
import { replace } from 'esbuild-plugin-replace';
import { mkdir, readFile } from 'fs/promises';
import getPort, { portNumbers } from 'get-port';
import { globby } from 'globby';
@@ -13,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));
@@ -37,6 +37,7 @@ async function buildAll() {
await generateManifest();
await generateReactWrappers();
await generateTypes();
await preprocessStyles();
await generateStyles();
// copy everything to unbundled before we generate bundles.
@@ -106,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.
*/
@@ -267,13 +285,6 @@ async function regenerateBundle() {
* Generates the documentation site.
*/
async function generateDocs() {
/**
* Used by the webawesome-app to skip doc generation since it will do its own.
*/
if (process.env.SKIP_ELEVENTY === 'true') {
return;
}
spinner.start('Writing the docs');
const args = [];
@@ -331,6 +342,7 @@ if (isDeveloping) {
bs.init(
{
startPath: '/',
open: false,
port,
logLevel: 'silent',
logPrefix: '[webawesome]',
@@ -379,6 +391,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;
@@ -389,6 +402,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
View 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;
}

View File

@@ -52,7 +52,6 @@ import styles from './button.css';
@customElement('wa-button')
export default class WaButton extends WebAwesomeFormAssociatedElement {
static shadowStyle = [variantStyles, appearanceStyles, sizeStyles, nativeStyles, styles];
static rectProxy = 'button';
static get validators() {
return [...super.validators, MirrorValidator()];
@@ -109,7 +108,7 @@ export default class WaButton extends WebAwesomeFormAssociatedElement {
@property({ reflect: true }) value: string | null = null;
/** When set, the underlying button will be rendered as an `<a>` with this `href` instead of a `<button>`. */
@property({ reflect: true }) href = null;
@property() href = '';
/** Tells the browser where to open the link. Only used when `href` is present. */
@property() target: '_blank' | '_parent' | '_self' | '_top';
@@ -225,6 +224,17 @@ export default class WaButton extends WebAwesomeFormAssociatedElement {
this.button.blur();
}
getBoundingClientRect(): DOMRect {
let rect = super.getBoundingClientRect();
let buttonRect = this.button.getBoundingClientRect();
if (rect.width === 0 && buttonRect.width > 0) {
return buttonRect;
}
return rect;
}
render() {
const isLink = this.isLink();
const tag = isLink ? literal`a` : literal`button`;

View File

@@ -5,16 +5,15 @@
--spacing: var(--wa-space);
--border-width: var(--wa-panel-border-width);
--outlined-background-color: var(--wa-color-surface-default);
--outlined-border-color: var(--wa-color-surface-border);
--border-color: var(--wa-color-surface-border);
--border-radius: var(--wa-panel-border-radius);
--inner-border-radius: calc(var(--border-radius) - var(--border-width));
--inner-border-color: var(--outlined-border-color);
display: flex;
flex-direction: column;
background-color: var(--background-color, var(--wa-color-surface-default));
border-color: var(--border-color, var(--wa-color-surface-border));
background-color: var(--wa-color-surface-default);
border-color: var(--border-color);
border-radius: var(--border-radius);
border-style: var(--wa-panel-border-style);
box-shadow: var(--wa-shadow-s);
@@ -22,20 +21,6 @@
color: var(--wa-color-text-normal);
}
:host(:is([appearance~='accent'], .wa-accent)) {
color: var(--text-color, var(--wa-color-text-normal));
}
:host([appearance~='filled']),
:host(.wa-filled) {
--inner-border-color: oklab(from var(--outlined-border-color) l a b / 65%);
}
:host([appearance='plain']) {
--inner-border-color: transparent;
box-shadow: none;
}
/* Take care of top and bottom radii */
.image,
:host(:not([with-image])) .header,
@@ -61,19 +46,10 @@
}
}
/* Round all corners for plain appearance */
:host([appearance='plain']) .image {
border-radius: var(--inner-border-radius);
&::slotted(img) {
border-radius: inherit !important;
}
}
.header {
display: block;
border-block-end-style: inherit;
border-block-end-color: var(--inner-border-color);
border-block-end-color: var(--border-color);
border-block-end-width: var(--border-width);
padding: calc(var(--spacing) / 2) var(--spacing);
}
@@ -86,7 +62,7 @@
.footer {
display: block;
border-block-start-style: inherit;
border-block-start-color: var(--inner-border-color);
border-block-start-color: var(--border-color);
border-block-start-width: var(--border-width);
padding: var(--spacing);
}

View File

@@ -2,7 +2,6 @@ import { html } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { HasSlotController } from '../../internal/slot.js';
import WebAwesomeElement from '../../internal/webawesome-element.js';
import appearanceStyles from '../../styles/utilities/appearance.css';
import sizeStyles from '../../styles/utilities/size.css';
import styles from './card.css';
@@ -23,24 +22,19 @@ import styles from './card.css';
* @csspart footer - The container that wraps the card's footer.
*
* @cssproperty [--border-radius=var(--wa-panel-border-radius)] - The radius for the card's corners. Expects a single value.
* @cssproperty [--border-color=var(--wa-color-surface-border)] - The color of the card's borders. Expects a single value.
* @cssproperty [--inner-border-color=var(--wa-color-surface-border)] - The color of the card's inner borders, e.g. those separating headers and footers from the main content. Expects a single value.
* @cssproperty [--border-color=var(--wa-color-surface-border)] - The color of the card's borders, including inner borders. Expects a single value.
* @cssproperty [--border-width=var(--wa-panel-border-width)] - The width of the card's borders. Expects a single value.
* @cssproperty [--spacing=var(--wa-space)] - The amount of space around and between sections of the card. Expects a single value.
*/
@customElement('wa-card')
export default class WaCard extends WebAwesomeElement {
static shadowStyle = [sizeStyles, appearanceStyles, styles];
static shadowStyle = [sizeStyles, styles];
private readonly hasSlotController = new HasSlotController(this, 'footer', 'header', 'image');
/** The component's size. Will be inherited by any descendants with a `size` attribute. */
@property({ reflect: true, initial: 'medium' }) size: 'small' | 'medium' | 'large' | 'inherit' = 'inherit';
/** The card's visual appearance. */
@property({ reflect: true })
appearance: 'accent' | 'filled' | 'outlined' | 'plain' = 'outlined';
/** Renders the card with a header. Only needed for SSR, otherwise is automatically added. */
@property({ attribute: 'with-header', type: Boolean, reflect: true }) withHeader = false;

View File

@@ -278,11 +278,11 @@
}
/*
* Color dropdown
*/
* Color dropdown
*/
.color-dropdown {
display: contents;
display: flex;
}
.color-dropdown::part(panel) {

View File

@@ -9,7 +9,6 @@ import { getTargetElement, waitForEvent } from '../../internal/event.js';
import { watch } from '../../internal/watch.js';
import WebAwesomeElement from '../../internal/webawesome-element.js';
import nativeStyles from '../../styles/native/details.css';
import appearanceStyles from '../../styles/utilities/appearance.css';
import { LocalizeController } from '../../utilities/localize.js';
import '../icon/icon.js';
import styles from './details.css';
@@ -46,7 +45,7 @@ import styles from './details.css';
*/
@customElement('wa-details')
export default class WaDetails extends WebAwesomeElement {
static shadowStyle = [appearanceStyles, nativeStyles, styles];
static shadowStyle = [nativeStyles, styles];
private detailsObserver: MutationObserver;
private readonly localize = new LocalizeController(this);
@@ -68,9 +67,6 @@ export default class WaDetails extends WebAwesomeElement {
/** Disables the details so it can't be toggled. */
@property({ type: Boolean, reflect: true }) disabled = false;
/** The element's visual appearance. */
@property({ reflect: true }) appearance: 'filled' | 'outlined' | 'plain' = 'outlined';
firstUpdated() {
this.body.style.height = this.open ? 'auto' : '0';
if (this.open) {

View File

@@ -1,8 +1,5 @@
:host {
--background-color-hover: var(--wa-color-neutral-fill-quiet);
--text-color-hover: color-mix(in oklab, currentColor, var(--wa-color-mix-hover));
--background-color-active: transparent;
--text-color-active: color-mix(in oklab, currentColor, var(--wa-color-mix-active));
display: inline-block;
color: var(--wa-color-text-quiet);
@@ -25,13 +22,12 @@
:host(:not([disabled])) .icon-button:hover,
:host(:not([disabled])) .icon-button:focus-visible {
background-color: var(--background-color-hover);
color: var(--text-color-hover);
background-color: var(--wa-color-neutral-fill-quiet);
color: color-mix(in oklab, currentColor, var(--wa-color-mix-hover));
}
:host(:not([disabled])) .icon-button:active {
background-color: var(--background-color-active);
color: var(--text-color-active);
color: color-mix(in oklab, currentColor, var(--wa-color-mix-active));
}
.icon-button:focus {

View File

@@ -17,10 +17,7 @@ import styles from './icon-button.css';
* @event blur - Emitted when the icon button loses focus.
* @event focus - Emitted when the icon button gains focus.
*
* @cssproperty [--background-color-hover=var(--wa-color-neutral-fill-quiet)] - The color of the button's background on hover.
* @cssproperty [--background-color-active=var(--wa-color-neutral-fill-quiet)] - The color of the button's background on `:active`.
* @cssproperty --text-color-hover - The color of the button's background on hover.
* @cssproperty --text-color-active - The color of the button's background on `:active`.
* @cssproperty --background-color-hover - The color of the button's background on hover.
*
* @csspart base - The component's base wrapper.
*/

View File

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

View File

@@ -208,13 +208,13 @@
&::slotted(wa-divider) {
--spacing: var(--wa-space-xs);
}
}
slot:not([name])::slotted(small) {
display: block;
font-size: var(--wa-font-size-s);
font-weight: var(--wa-font-weight-semibold);
color: var(--wa-color-text-quiet);
padding-block: var(--wa-space-xs);
padding-inline: var(--wa-space-xl);
&::slotted(small) {
display: block;
font-size: var(--wa-font-size-s);
font-weight: var(--wa-font-weight-semibold);
color: var(--wa-color-text-quiet);
padding-block: var(--wa-space-xs);
padding-inline: var(--wa-space-xl);
}
}

View File

@@ -647,7 +647,6 @@ describe('<wa-select>', () => {
);
const el = form.querySelector<WaSelect>('wa-select')!;
expect(el.defaultValue).to.equal('option-1');
expect(el.value).to.equal('');
expect(new FormData(form).get('select')).equal('');
@@ -658,7 +657,6 @@ describe('<wa-select>', () => {
await aTimeout(10);
await el.updateComplete;
expect(el.optionValues ? [...el.optionValues] : []).to.have.members(['option-1']);
expect(el.value).to.equal('option-1');
expect(new FormData(form).get('select')).equal('option-1');
});
@@ -747,8 +745,6 @@ describe('<wa-select>', () => {
);
const el = form.querySelector<WaSelect>('wa-select')!;
expect(el.optionValues ? [...el.optionValues] : []).to.have.members(['bar', 'baz']);
expect(el.optionValues?.size).to.equal(2);
expect(el.value).to.have.members(['bar', 'baz']);
expect(el.value!.length).to.equal(2);
expect(new FormData(form).getAll('select')).to.have.members(['bar', 'baz']);
@@ -764,36 +760,6 @@ describe('<wa-select>', () => {
expect(new FormData(form).getAll('select')).to.have.members(['foo', 'bar', 'baz']);
});
});
describe('With setting the value via JS', () => {
it('Should preserve value even if not returned', async () => {
const form = await fixture<HTMLFormElement>(
html` <form>
<wa-select name="select">
<wa-option value="bar">Bar</wa-option>
<wa-option value="baz">Baz</wa-option>
</wa-select>
</form>`,
);
const el = form.querySelector<WaSelect>('wa-select')!;
expect(el.value).to.equal('');
el.value = 'foo';
await aTimeout(10);
await el.updateComplete;
expect(el.value).to.equal('');
const option = document.createElement('wa-option');
option.value = 'foo';
option.innerText = 'Foo';
el.append(option);
await aTimeout(10);
await el.updateComplete;
expect(el.value).to.equal('foo');
});
});
});
});
}

View File

@@ -118,7 +118,6 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
@state() displayLabel = '';
@state() currentOption: WaOption;
@state() selectedOptions: WaOption[] = [];
@state() optionValues: Set<string> | undefined;
/** The name of the select, submitted as a name/value pair with form data. */
@property() name = '';
@@ -159,47 +158,7 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
return val;
}
private _value: string[] | undefined;
@property({ attribute: false })
set value(val: string | string[]) {
let oldValue = this.value;
if (!Array.isArray(val)) {
val = val.split(' ');
}
if (!this._value || this._value.join(' ') !== val.join(' ')) {
this._value = val;
let newValue = this.value;
if (newValue != oldValue) {
this.requestUpdate('value', oldValue);
}
}
}
get value() {
let value = this._value ?? this.defaultValue;
value = Array.isArray(value) ? value : [value];
let optionsChanged = !this.optionValues;
if (optionsChanged) {
this.optionValues = new Set(
this.getAllOptions()
.filter(option => !option.disabled)
.map(option => option.value),
);
}
// Drop values not in the DOM
let ret: string | string[] = value.filter(v => this.optionValues!.has(v));
ret = this.multiple ? ret : (ret[0] ?? '');
if (optionsChanged) {
this.requestUpdate('value');
}
return ret;
}
@property({ attribute: false }) value: string | string[] | null = null;
/** The select's size. */
@property({ reflect: true, initial: 'medium' }) size: 'small' | 'medium' | 'large' | 'inherit' = 'inherit';
@@ -291,6 +250,7 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
?pill=${this.pill}
size=${this.size}
removable
@wa-remove=${(event: WaRemoveEvent) => this.handleTagRemove(event, option)}
>
${option.label}
</wa-tag>
@@ -578,41 +538,21 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
}
const allOptions = this.getAllOptions();
this.optionValues = undefined; // dirty the value so it gets recalculated
const val = this.valueHasChanged ? this.value : this.defaultValue;
const value = Array.isArray(val) ? val : [val];
const values: string[] = [];
const value = this.value;
// Check for duplicate values in menu items
allOptions.forEach(option => values.push(option.value));
// Select only the options that match the new value
this.setSelectedOptions(allOptions.filter(el => value.includes(el.value)));
}
private handleTagRemove(event: WaRemoveEvent, directOption?: WaOption) {
private handleTagRemove(event: WaRemoveEvent, option: WaOption) {
event.stopPropagation();
if (this.disabled) return;
// Use the directly provided option if available (from getTag method)
let option = directOption;
// If no direct option was provided, find the option from the event path
if (!option) {
const tagElement = (event.target as Element).closest('wa-tag[part~=tag]');
if (tagElement) {
// Find the index of this tag among all tags
const tagsContainer = this.shadowRoot?.querySelector('[part="tags"]');
if (tagsContainer) {
const allTags = Array.from(tagsContainer.children);
const index = allTags.indexOf(tagElement as HTMLElement);
if (index >= 0 && index < this.selectedOptions.length) {
option = this.selectedOptions[index];
}
}
}
}
if (option) {
if (!this.disabled) {
this.toggleOptionSelection(option, false);
// Emit after updating
@@ -625,9 +565,6 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
// Gets an array of all `<wa-option>` elements
private getAllOptions() {
if (!this?.querySelectorAll) {
return [];
}
return [...this.querySelectorAll<WaOption>('wa-option')];
}
@@ -691,24 +628,11 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
// Update selected options cache
this.selectedOptions = options.filter(el => el.selected);
let selectedValues = new Set(this.selectedOptions.map(el => el.value));
// Toggle values present in the DOM from this.value, while preserving options NOT present in the DOM (for lazy loading)
// Note that options NOT present in the DOM will be moved to the end after this
if (selectedValues.size > 0 || this._value) {
if (!this._value) {
// First time it's set
let value = this.defaultValue ?? [];
this._value = Array.isArray(value) ? value : [value];
}
// Filter out values that are in the DOM
this._value = this._value.filter(value => !this.optionValues?.has(value));
this._value.unshift(...selectedValues);
}
// Update the value and display label
if (this.multiple) {
this.value = this.selectedOptions.map(el => el.value);
if (this.placeholder && this.value.length === 0) {
// When no items are selected, keep the value empty so the placeholder shows
this.displayLabel = '';
@@ -717,6 +641,7 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
}
} else {
const selectedOption = this.selectedOptions[0];
this.value = selectedOption?.value ?? '';
this.displayLabel = selectedOption?.label ?? '';
}
@@ -729,8 +654,10 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
return this.selectedOptions.map((option, index) => {
if (index < this.maxOptionsVisible || this.maxOptionsVisible <= 0) {
const tag = this.getTag(option, index);
if (!tag) return null;
return typeof tag === 'string' ? unsafeHTML(tag) : tag;
// Wrap so we can handle the remove
return html`<div @wa-remove=${(e: WaRemoveEvent) => this.handleTagRemove(e, option)}>
${typeof tag === 'string' ? unsafeHTML(tag) : tag}
</div>`;
} else if (index === this.maxOptionsVisible) {
// Hit tag limit
return html`
@@ -746,7 +673,7 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
>
`;
}
return null;
return html``;
});
}
@@ -946,9 +873,7 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
/>
<!-- Tags need to wait for first hydration before populating otherwise it will create a hydration mismatch. -->
${this.multiple && this.hasUpdated
? html`<div part="tags" class="tags" @wa-remove=${this.handleTagRemove}>${this.tags}</div>`
: ''}
${this.multiple && this.hasUpdated ? html`<div part="tags" class="tags">${this.tags}</div>` : ''}
<input
class="value-input"

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,3 +1,4 @@
/* DO NOT EDIT THIS FILE. It is generated from "{{ hue }}.css.njk" */
:where(:root),
:host,
:where([class^='wa-theme-'], [class*=' wa-theme-']),
@@ -15,6 +16,10 @@
--wa-color-brand-10: var(--wa-color-blue-10);
--wa-color-brand-05: var(--wa-color-blue-05);
--wa-color-brand: var(--wa-color-blue);
--wa-color-brand-key: var(--wa-color-blue-key);
--wa-color-brand-lt-40: var(--wa-color-blue-lt-40);
--wa-color-brand-gte-40: var(--wa-color-blue-gte-40);
--wa-color-brand-lt-50: var(--wa-color-blue-lt-50);
--wa-color-brand-gte-50: var(--wa-color-blue-gte-50);
@@ -25,6 +30,11 @@
--wa-color-brand-lt-70: var(--wa-color-blue-lt-70);
--wa-color-brand-gte-70: var(--wa-color-blue-gte-70);
--wa-color-brand-lt-80: var(--wa-color-blue-lt-80);
--wa-color-brand-gte-80: var(--wa-color-blue-gte-80);
--wa-color-brand-max-40: var(--wa-color-blue-max-40);
--wa-color-brand-min-40: var(--wa-color-blue-min-40);
--wa-color-brand-max-50: var(--wa-color-blue-max-50);
--wa-color-brand-min-50: var(--wa-color-blue-min-50);
--wa-color-brand-max-60: var(--wa-color-blue-max-60);

View File

@@ -1,3 +1,4 @@
/* DO NOT EDIT THIS FILE. It is generated from "{{ hue }}.css.njk" */
:where(:root),
:host,
:where([class^='wa-theme-'], [class*=' wa-theme-']),
@@ -15,6 +16,10 @@
--wa-color-brand-10: var(--wa-color-cyan-10);
--wa-color-brand-05: var(--wa-color-cyan-05);
--wa-color-brand: var(--wa-color-cyan);
--wa-color-brand-key: var(--wa-color-cyan-key);
--wa-color-brand-lt-40: var(--wa-color-cyan-lt-40);
--wa-color-brand-gte-40: var(--wa-color-cyan-gte-40);
--wa-color-brand-lt-50: var(--wa-color-cyan-lt-50);
--wa-color-brand-gte-50: var(--wa-color-cyan-gte-50);
@@ -25,6 +30,11 @@
--wa-color-brand-lt-70: var(--wa-color-cyan-lt-70);
--wa-color-brand-gte-70: var(--wa-color-cyan-gte-70);
--wa-color-brand-lt-80: var(--wa-color-cyan-lt-80);
--wa-color-brand-gte-80: var(--wa-color-cyan-gte-80);
--wa-color-brand-max-40: var(--wa-color-cyan-max-40);
--wa-color-brand-min-40: var(--wa-color-cyan-min-40);
--wa-color-brand-max-50: var(--wa-color-cyan-max-50);
--wa-color-brand-min-50: var(--wa-color-cyan-min-50);
--wa-color-brand-max-60: var(--wa-color-cyan-max-60);

View File

@@ -1,3 +1,4 @@
/* DO NOT EDIT THIS FILE. It is generated from "{{ hue }}.css.njk" */
:where(:root),
:host,
:where([class^='wa-theme-'], [class*=' wa-theme-']),
@@ -15,6 +16,10 @@
--wa-color-brand-10: var(--wa-color-gray-10);
--wa-color-brand-05: var(--wa-color-gray-05);
--wa-color-brand: var(--wa-color-gray);
--wa-color-brand-key: var(--wa-color-gray-key);
--wa-color-brand-lt-40: var(--wa-color-gray-lt-40);
--wa-color-brand-gte-40: var(--wa-color-gray-gte-40);
--wa-color-brand-lt-50: var(--wa-color-gray-lt-50);
--wa-color-brand-gte-50: var(--wa-color-gray-gte-50);
@@ -25,6 +30,11 @@
--wa-color-brand-lt-70: var(--wa-color-gray-lt-70);
--wa-color-brand-gte-70: var(--wa-color-gray-gte-70);
--wa-color-brand-lt-80: var(--wa-color-gray-lt-80);
--wa-color-brand-gte-80: var(--wa-color-gray-gte-80);
--wa-color-brand-max-40: var(--wa-color-gray-max-40);
--wa-color-brand-min-40: var(--wa-color-gray-min-40);
--wa-color-brand-max-50: var(--wa-color-gray-max-50);
--wa-color-brand-min-50: var(--wa-color-gray-min-50);
--wa-color-brand-max-60: var(--wa-color-gray-max-60);

View File

@@ -1,3 +1,4 @@
/* DO NOT EDIT THIS FILE. It is generated from "{{ hue }}.css.njk" */
:where(:root),
:host,
:where([class^='wa-theme-'], [class*=' wa-theme-']),
@@ -15,6 +16,10 @@
--wa-color-brand-10: var(--wa-color-green-10);
--wa-color-brand-05: var(--wa-color-green-05);
--wa-color-brand: var(--wa-color-green);
--wa-color-brand-key: var(--wa-color-green-key);
--wa-color-brand-lt-40: var(--wa-color-green-lt-40);
--wa-color-brand-gte-40: var(--wa-color-green-gte-40);
--wa-color-brand-lt-50: var(--wa-color-green-lt-50);
--wa-color-brand-gte-50: var(--wa-color-green-gte-50);
@@ -25,6 +30,11 @@
--wa-color-brand-lt-70: var(--wa-color-green-lt-70);
--wa-color-brand-gte-70: var(--wa-color-green-gte-70);
--wa-color-brand-lt-80: var(--wa-color-green-lt-80);
--wa-color-brand-gte-80: var(--wa-color-green-gte-80);
--wa-color-brand-max-40: var(--wa-color-green-max-40);
--wa-color-brand-min-40: var(--wa-color-green-min-40);
--wa-color-brand-max-50: var(--wa-color-green-max-50);
--wa-color-brand-min-50: var(--wa-color-green-min-50);
--wa-color-brand-max-60: var(--wa-color-green-max-60);

View File

@@ -1,3 +1,4 @@
/* DO NOT EDIT THIS FILE. It is generated from "{{ hue }}.css.njk" */
:where(:root),
:host,
:where([class^='wa-theme-'], [class*=' wa-theme-']),
@@ -15,6 +16,10 @@
--wa-color-brand-10: var(--wa-color-indigo-10);
--wa-color-brand-05: var(--wa-color-indigo-05);
--wa-color-brand: var(--wa-color-indigo);
--wa-color-brand-key: var(--wa-color-indigo-key);
--wa-color-brand-lt-40: var(--wa-color-indigo-lt-40);
--wa-color-brand-gte-40: var(--wa-color-indigo-gte-40);
--wa-color-brand-lt-50: var(--wa-color-indigo-lt-50);
--wa-color-brand-gte-50: var(--wa-color-indigo-gte-50);
@@ -25,6 +30,11 @@
--wa-color-brand-lt-70: var(--wa-color-indigo-lt-70);
--wa-color-brand-gte-70: var(--wa-color-indigo-gte-70);
--wa-color-brand-lt-80: var(--wa-color-indigo-lt-80);
--wa-color-brand-gte-80: var(--wa-color-indigo-gte-80);
--wa-color-brand-max-40: var(--wa-color-indigo-max-40);
--wa-color-brand-min-40: var(--wa-color-indigo-min-40);
--wa-color-brand-max-50: var(--wa-color-indigo-max-50);
--wa-color-brand-min-50: var(--wa-color-indigo-min-50);
--wa-color-brand-max-60: var(--wa-color-indigo-max-60);

View File

@@ -1,36 +0,0 @@
:where(:root),
:host,
:where([class^='wa-theme-'], [class*=' wa-theme-']),
:where([class^='wa-palette-'], [class*=' wa-palette-']),
.wa-brand-orange {
--wa-color-brand-95: var(--wa-color-orange-95);
--wa-color-brand-90: var(--wa-color-orange-90);
--wa-color-brand-80: var(--wa-color-orange-80);
--wa-color-brand-70: var(--wa-color-orange-70);
--wa-color-brand-60: var(--wa-color-orange-60);
--wa-color-brand-50: var(--wa-color-orange-50);
--wa-color-brand-40: var(--wa-color-orange-40);
--wa-color-brand-30: var(--wa-color-orange-30);
--wa-color-brand-20: var(--wa-color-orange-20);
--wa-color-brand-10: var(--wa-color-orange-10);
--wa-color-brand-05: var(--wa-color-orange-05);
--wa-color-brand: var(--wa-color-orange);
--wa-color-brand-lt-50: var(--wa-color-orange-lt-50);
--wa-color-brand-gte-50: var(--wa-color-orange-gte-50);
--wa-color-brand-lt-60: var(--wa-color-orange-lt-60);
--wa-color-brand-gte-60: var(--wa-color-orange-gte-60);
--wa-color-brand-lt-70: var(--wa-color-orange-lt-70);
--wa-color-brand-gte-70: var(--wa-color-orange-gte-70);
--wa-color-brand-max-50: var(--wa-color-orange-max-50);
--wa-color-brand-min-50: var(--wa-color-orange-min-50);
--wa-color-brand-max-60: var(--wa-color-orange-max-60);
--wa-color-brand-min-60: var(--wa-color-orange-min-60);
--wa-color-brand-max-70: var(--wa-color-orange-max-70);
--wa-color-brand-min-70: var(--wa-color-orange-min-70);
--wa-color-brand-on: var(--wa-color-orange-on);
}

View File

@@ -1,3 +1,4 @@
/* DO NOT EDIT THIS FILE. It is generated from "{{ hue }}.css.njk" */
:where(:root),
:host,
:where([class^='wa-theme-'], [class*=' wa-theme-']),
@@ -15,6 +16,10 @@
--wa-color-brand-10: var(--wa-color-pink-10);
--wa-color-brand-05: var(--wa-color-pink-05);
--wa-color-brand: var(--wa-color-pink);
--wa-color-brand-key: var(--wa-color-pink-key);
--wa-color-brand-lt-40: var(--wa-color-pink-lt-40);
--wa-color-brand-gte-40: var(--wa-color-pink-gte-40);
--wa-color-brand-lt-50: var(--wa-color-pink-lt-50);
--wa-color-brand-gte-50: var(--wa-color-pink-gte-50);
@@ -25,6 +30,11 @@
--wa-color-brand-lt-70: var(--wa-color-pink-lt-70);
--wa-color-brand-gte-70: var(--wa-color-pink-gte-70);
--wa-color-brand-lt-80: var(--wa-color-pink-lt-80);
--wa-color-brand-gte-80: var(--wa-color-pink-gte-80);
--wa-color-brand-max-40: var(--wa-color-pink-max-40);
--wa-color-brand-min-40: var(--wa-color-pink-min-40);
--wa-color-brand-max-50: var(--wa-color-pink-max-50);
--wa-color-brand-min-50: var(--wa-color-pink-min-50);
--wa-color-brand-max-60: var(--wa-color-pink-max-60);

View File

@@ -1,3 +1,4 @@
/* DO NOT EDIT THIS FILE. It is generated from "{{ hue }}.css.njk" */
:where(:root),
:host,
:where([class^='wa-theme-'], [class*=' wa-theme-']),
@@ -15,6 +16,10 @@
--wa-color-brand-10: var(--wa-color-purple-10);
--wa-color-brand-05: var(--wa-color-purple-05);
--wa-color-brand: var(--wa-color-purple);
--wa-color-brand-key: var(--wa-color-purple-key);
--wa-color-brand-lt-40: var(--wa-color-purple-lt-40);
--wa-color-brand-gte-40: var(--wa-color-purple-gte-40);
--wa-color-brand-lt-50: var(--wa-color-purple-lt-50);
--wa-color-brand-gte-50: var(--wa-color-purple-gte-50);
@@ -25,6 +30,11 @@
--wa-color-brand-lt-70: var(--wa-color-purple-lt-70);
--wa-color-brand-gte-70: var(--wa-color-purple-gte-70);
--wa-color-brand-lt-80: var(--wa-color-purple-lt-80);
--wa-color-brand-gte-80: var(--wa-color-purple-gte-80);
--wa-color-brand-max-40: var(--wa-color-purple-max-40);
--wa-color-brand-min-40: var(--wa-color-purple-min-40);
--wa-color-brand-max-50: var(--wa-color-purple-max-50);
--wa-color-brand-min-50: var(--wa-color-purple-min-50);
--wa-color-brand-max-60: var(--wa-color-purple-max-60);

View File

@@ -1,3 +1,4 @@
/* DO NOT EDIT THIS FILE. It is generated from "{{ hue }}.css.njk" */
:where(:root),
:host,
:where([class^='wa-theme-'], [class*=' wa-theme-']),
@@ -15,6 +16,10 @@
--wa-color-brand-10: var(--wa-color-red-10);
--wa-color-brand-05: var(--wa-color-red-05);
--wa-color-brand: var(--wa-color-red);
--wa-color-brand-key: var(--wa-color-red-key);
--wa-color-brand-lt-40: var(--wa-color-red-lt-40);
--wa-color-brand-gte-40: var(--wa-color-red-gte-40);
--wa-color-brand-lt-50: var(--wa-color-red-lt-50);
--wa-color-brand-gte-50: var(--wa-color-red-gte-50);
@@ -25,6 +30,11 @@
--wa-color-brand-lt-70: var(--wa-color-red-lt-70);
--wa-color-brand-gte-70: var(--wa-color-red-gte-70);
--wa-color-brand-lt-80: var(--wa-color-red-lt-80);
--wa-color-brand-gte-80: var(--wa-color-red-gte-80);
--wa-color-brand-max-40: var(--wa-color-red-max-40);
--wa-color-brand-min-40: var(--wa-color-red-min-40);
--wa-color-brand-max-50: var(--wa-color-red-max-50);
--wa-color-brand-min-50: var(--wa-color-red-min-50);
--wa-color-brand-max-60: var(--wa-color-red-max-60);

View File

@@ -1,3 +1,4 @@
/* DO NOT EDIT THIS FILE. It is generated from "{{ hue }}.css.njk" */
:where(:root),
:host,
:where([class^='wa-theme-'], [class*=' wa-theme-']),
@@ -15,6 +16,10 @@
--wa-color-brand-10: var(--wa-color-yellow-10);
--wa-color-brand-05: var(--wa-color-yellow-05);
--wa-color-brand: var(--wa-color-yellow);
--wa-color-brand-key: var(--wa-color-yellow-key);
--wa-color-brand-lt-40: var(--wa-color-yellow-lt-40);
--wa-color-brand-gte-40: var(--wa-color-yellow-gte-40);
--wa-color-brand-lt-50: var(--wa-color-yellow-lt-50);
--wa-color-brand-gte-50: var(--wa-color-yellow-gte-50);
@@ -25,6 +30,11 @@
--wa-color-brand-lt-70: var(--wa-color-yellow-lt-70);
--wa-color-brand-gte-70: var(--wa-color-yellow-gte-70);
--wa-color-brand-lt-80: var(--wa-color-yellow-lt-80);
--wa-color-brand-gte-80: var(--wa-color-yellow-gte-80);
--wa-color-brand-max-40: var(--wa-color-yellow-max-40);
--wa-color-brand-min-40: var(--wa-color-yellow-min-40);
--wa-color-brand-max-50: var(--wa-color-yellow-max-50);
--wa-color-brand-min-50: var(--wa-color-yellow-min-50);
--wa-color-brand-max-60: var(--wa-color-yellow-max-60);

View File

@@ -0,0 +1,22 @@
: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);
{% for tint in ['40', '50', '60', '70', '80'] %}
--wa-color-brand-lt-{{ tint }}: var(--wa-color-{{ hue }}-lt-{{ tint }});
--wa-color-brand-gte-{{ tint }}: var(--wa-color-{{ hue }}-gte-{{ tint }});
{% endfor %}
{% for tint in ['40', '50', '60', '70'] %}
--wa-color-brand-max-{{ tint }}: var(--wa-color-{{ hue }}-max-{{ tint }});
--wa-color-brand-min-{{ tint }}: var(--wa-color-{{ hue }}-min-{{ tint }});
{%- endfor %}
--wa-color-brand-on: var(--wa-color-{{ hue }}-on);
}

View File

@@ -18,20 +18,6 @@
--wa-color-red: var(--wa-color-red-70);
--wa-color-red-key: 70;
--wa-color-orange-95: #ffefe8 /* oklch(96.287% 0.01978 45.262) */;
--wa-color-orange-90: #ffe0d2 /* oklch(92.854% 0.03927 45.816) */;
--wa-color-orange-80: #fbbc9e /* oklch(84.557% 0.08373 46.569) */;
--wa-color-orange-70: #f3976f /* oklch(76.236% 0.12363 43.995) */;
--wa-color-orange-60: #d67e59 /* oklch(68.046% 0.12088 43.174) */;
--wa-color-orange-50: #ae6140 /* oklch(57.433% 0.11041 43.333) */;
--wa-color-orange-40: #89472c /* oklch(47.39% 0.09837 42.278) */;
--wa-color-orange-30: #6f351e /* oklch(40.084% 0.08944 41.51) */;
--wa-color-orange-20: #542513 /* oklch(32.696% 0.07578 40.822) */;
--wa-color-orange-10: #341408 /* oklch(23.762% 0.05577 40.619) */;
--wa-color-orange-05: #230b04 /* oklch(18.679% 0.04417 39.815) */;
--wa-color-orange: var(--wa-color-orange-70);
--wa-color-orange-key: 70;
--wa-color-yellow-95: #faf3e1 /* oklch(96.479% 0.02487 89.211) */;
--wa-color-yellow-90: #f4e5be /* oklch(92.412% 0.0535 89.488) */;
--wa-color-yellow-80: #eac673 /* oklch(84.03% 0.1101 86.581) */;

View File

@@ -1,3 +1,5 @@
/* DO NOT EDIT THIS FILE. It is generated from "base.css.njk" */
:where([class^='wa-theme-'], [class*=' wa-theme-']),
:where([class^='wa-palette-'], [class*=' wa-palette-']),
:where([class^='wa-brand-'], [class*=' wa-brand-']),
@@ -9,75 +11,104 @@
* --wa-color-{hue}-gte-N ➡️ 100% if key >= N, 0% otherwise
*/
--wa-color-red-lt-40: calc(clamp(0, 40 - var(--wa-color-red-key), 1) * 100%);
--wa-color-red-gte-40: calc(100% - var(--wa-color-red-lt-40));
--wa-color-red-lt-50: calc(clamp(0, 50 - var(--wa-color-red-key), 1) * 100%);
--wa-color-red-gte-50: calc(100% - var(--wa-color-red-lt-50));
--wa-color-red-lt-60: calc(clamp(0, 60 - var(--wa-color-red-key), 1) * 100%);
--wa-color-red-gte-60: calc(100% - var(--wa-color-red-lt-60));
--wa-color-red-lt-70: calc(clamp(0, 70 - var(--wa-color-red-key), 1) * 100%);
--wa-color-red-gte-70: calc(100% - var(--wa-color-red-lt-70));
--wa-color-red-lt-80: calc(clamp(0, 80 - var(--wa-color-red-key), 1) * 100%);
--wa-color-red-gte-80: calc(100% - var(--wa-color-red-lt-80));
--wa-color-orange-lt-50: calc(clamp(0, 50 - var(--wa-color-orange-key), 1) * 100%);
--wa-color-orange-gte-50: calc(100% - var(--wa-color-orange-lt-50));
--wa-color-orange-lt-60: calc(clamp(0, 60 - var(--wa-color-orange-key), 1) * 100%);
--wa-color-orange-gte-60: calc(100% - var(--wa-color-orange-lt-60));
--wa-color-orange-lt-70: calc(clamp(0, 70 - var(--wa-color-orange-key), 1) * 100%);
--wa-color-orange-gte-70: calc(100% - var(--wa-color-orange-lt-70));
--wa-color-yellow-lt-40: calc(clamp(0, 40 - var(--wa-color-yellow-key), 1) * 100%);
--wa-color-yellow-gte-40: calc(100% - var(--wa-color-yellow-lt-40));
--wa-color-yellow-lt-50: calc(clamp(0, 50 - var(--wa-color-yellow-key), 1) * 100%);
--wa-color-yellow-gte-50: calc(100% - var(--wa-color-yellow-lt-50));
--wa-color-yellow-lt-60: calc(clamp(0, 60 - var(--wa-color-yellow-key), 1) * 100%);
--wa-color-yellow-gte-60: calc(100% - var(--wa-color-yellow-lt-60));
--wa-color-yellow-lt-70: calc(clamp(0, 70 - var(--wa-color-yellow-key), 1) * 100%);
--wa-color-yellow-gte-70: calc(100% - var(--wa-color-yellow-lt-70));
--wa-color-yellow-lt-80: calc(clamp(0, 80 - var(--wa-color-yellow-key), 1) * 100%);
--wa-color-yellow-gte-80: calc(100% - var(--wa-color-yellow-lt-80));
--wa-color-green-lt-40: calc(clamp(0, 40 - var(--wa-color-green-key), 1) * 100%);
--wa-color-green-gte-40: calc(100% - var(--wa-color-green-lt-40));
--wa-color-green-lt-50: calc(clamp(0, 50 - var(--wa-color-green-key), 1) * 100%);
--wa-color-green-gte-50: calc(100% - var(--wa-color-green-lt-50));
--wa-color-green-lt-60: calc(clamp(0, 60 - var(--wa-color-green-key), 1) * 100%);
--wa-color-green-gte-60: calc(100% - var(--wa-color-green-lt-60));
--wa-color-green-lt-70: calc(clamp(0, 70 - var(--wa-color-green-key), 1) * 100%);
--wa-color-green-gte-70: calc(100% - var(--wa-color-green-lt-70));
--wa-color-green-lt-80: calc(clamp(0, 80 - var(--wa-color-green-key), 1) * 100%);
--wa-color-green-gte-80: calc(100% - var(--wa-color-green-lt-80));
--wa-color-cyan-lt-40: calc(clamp(0, 40 - var(--wa-color-cyan-key), 1) * 100%);
--wa-color-cyan-gte-40: calc(100% - var(--wa-color-cyan-lt-40));
--wa-color-cyan-lt-50: calc(clamp(0, 50 - var(--wa-color-cyan-key), 1) * 100%);
--wa-color-cyan-gte-50: calc(100% - var(--wa-color-cyan-lt-50));
--wa-color-cyan-lt-60: calc(clamp(0, 60 - var(--wa-color-cyan-key), 1) * 100%);
--wa-color-cyan-gte-60: calc(100% - var(--wa-color-cyan-lt-60));
--wa-color-cyan-lt-70: calc(clamp(0, 70 - var(--wa-color-cyan-key), 1) * 100%);
--wa-color-cyan-gte-70: calc(100% - var(--wa-color-cyan-lt-70));
--wa-color-cyan-lt-80: calc(clamp(0, 80 - var(--wa-color-cyan-key), 1) * 100%);
--wa-color-cyan-gte-80: calc(100% - var(--wa-color-cyan-lt-80));
--wa-color-blue-lt-40: calc(clamp(0, 40 - var(--wa-color-blue-key), 1) * 100%);
--wa-color-blue-gte-40: calc(100% - var(--wa-color-blue-lt-40));
--wa-color-blue-lt-50: calc(clamp(0, 50 - var(--wa-color-blue-key), 1) * 100%);
--wa-color-blue-gte-50: calc(100% - var(--wa-color-blue-lt-50));
--wa-color-blue-lt-60: calc(clamp(0, 60 - var(--wa-color-blue-key), 1) * 100%);
--wa-color-blue-gte-60: calc(100% - var(--wa-color-blue-lt-60));
--wa-color-blue-lt-70: calc(clamp(0, 70 - var(--wa-color-blue-key), 1) * 100%);
--wa-color-blue-gte-70: calc(100% - var(--wa-color-blue-lt-70));
--wa-color-blue-lt-80: calc(clamp(0, 80 - var(--wa-color-blue-key), 1) * 100%);
--wa-color-blue-gte-80: calc(100% - var(--wa-color-blue-lt-80));
--wa-color-indigo-lt-40: calc(clamp(0, 40 - var(--wa-color-indigo-key), 1) * 100%);
--wa-color-indigo-gte-40: calc(100% - var(--wa-color-indigo-lt-40));
--wa-color-indigo-lt-50: calc(clamp(0, 50 - var(--wa-color-indigo-key), 1) * 100%);
--wa-color-indigo-gte-50: calc(100% - var(--wa-color-indigo-lt-50));
--wa-color-indigo-lt-60: calc(clamp(0, 60 - var(--wa-color-indigo-key), 1) * 100%);
--wa-color-indigo-gte-60: calc(100% - var(--wa-color-indigo-lt-60));
--wa-color-indigo-lt-70: calc(clamp(0, 70 - var(--wa-color-indigo-key), 1) * 100%);
--wa-color-indigo-gte-70: calc(100% - var(--wa-color-indigo-lt-70));
--wa-color-indigo-lt-80: calc(clamp(0, 80 - var(--wa-color-indigo-key), 1) * 100%);
--wa-color-indigo-gte-80: calc(100% - var(--wa-color-indigo-lt-80));
--wa-color-purple-lt-40: calc(clamp(0, 40 - var(--wa-color-purple-key), 1) * 100%);
--wa-color-purple-gte-40: calc(100% - var(--wa-color-purple-lt-40));
--wa-color-purple-lt-50: calc(clamp(0, 50 - var(--wa-color-purple-key), 1) * 100%);
--wa-color-purple-gte-50: calc(100% - var(--wa-color-purple-lt-50));
--wa-color-purple-lt-60: calc(clamp(0, 60 - var(--wa-color-purple-key), 1) * 100%);
--wa-color-purple-gte-60: calc(100% - var(--wa-color-purple-lt-60));
--wa-color-purple-lt-70: calc(clamp(0, 70 - var(--wa-color-purple-key), 1) * 100%);
--wa-color-purple-gte-70: calc(100% - var(--wa-color-purple-lt-70));
--wa-color-purple-lt-80: calc(clamp(0, 80 - var(--wa-color-purple-key), 1) * 100%);
--wa-color-purple-gte-80: calc(100% - var(--wa-color-purple-lt-80));
--wa-color-pink-lt-40: calc(clamp(0, 40 - var(--wa-color-pink-key), 1) * 100%);
--wa-color-pink-gte-40: calc(100% - var(--wa-color-pink-lt-40));
--wa-color-pink-lt-50: calc(clamp(0, 50 - var(--wa-color-pink-key), 1) * 100%);
--wa-color-pink-gte-50: calc(100% - var(--wa-color-pink-lt-50));
--wa-color-pink-lt-60: calc(clamp(0, 60 - var(--wa-color-pink-key), 1) * 100%);
--wa-color-pink-gte-60: calc(100% - var(--wa-color-pink-lt-60));
--wa-color-pink-lt-70: calc(clamp(0, 70 - var(--wa-color-pink-key), 1) * 100%);
--wa-color-pink-gte-70: calc(100% - var(--wa-color-pink-lt-70));
--wa-color-pink-lt-80: calc(clamp(0, 80 - var(--wa-color-pink-key), 1) * 100%);
--wa-color-pink-gte-80: calc(100% - var(--wa-color-pink-lt-80));
--wa-color-gray-lt-40: calc(clamp(0, 40 - var(--wa-color-gray-key), 1) * 100%);
--wa-color-gray-gte-40: calc(100% - var(--wa-color-gray-lt-40));
--wa-color-gray-lt-50: calc(clamp(0, 50 - var(--wa-color-gray-key), 1) * 100%);
--wa-color-gray-gte-50: calc(100% - var(--wa-color-gray-lt-50));
--wa-color-gray-lt-60: calc(clamp(0, 60 - var(--wa-color-gray-key), 1) * 100%);
--wa-color-gray-gte-60: calc(100% - var(--wa-color-gray-lt-60));
--wa-color-gray-lt-70: calc(clamp(0, 70 - var(--wa-color-gray-key), 1) * 100%);
--wa-color-gray-gte-70: calc(100% - var(--wa-color-gray-lt-70));
--wa-color-gray-lt-80: calc(clamp(0, 80 - var(--wa-color-gray-key), 1) * 100%);
--wa-color-gray-gte-80: calc(100% - var(--wa-color-gray-lt-80));
/*
* Convenience tokens for common tint cutoffs
@@ -85,6 +116,8 @@
* --wa-color-{hue}-N-min ➡️ var(--color-{hue}) if key >= N, var(--color-{hue}-N) otherwise
*/
--wa-color-red-max-40: color-mix(in oklab, var(--wa-color-red) var(--wa-color-red-lt-40), var(--wa-color-red-40));
--wa-color-red-min-40: color-mix(in oklab, var(--wa-color-red) var(--wa-color-red-gte-40), var(--wa-color-red-40));
--wa-color-red-max-50: color-mix(in oklab, var(--wa-color-red) var(--wa-color-red-lt-50), var(--wa-color-red-50));
--wa-color-red-min-50: color-mix(in oklab, var(--wa-color-red) var(--wa-color-red-gte-50), var(--wa-color-red-50));
--wa-color-red-max-60: color-mix(in oklab, var(--wa-color-red) var(--wa-color-red-lt-60), var(--wa-color-red-60));
@@ -92,37 +125,16 @@
--wa-color-red-max-70: color-mix(in oklab, var(--wa-color-red) var(--wa-color-red-lt-70), var(--wa-color-red-70));
--wa-color-red-min-70: color-mix(in oklab, var(--wa-color-red) var(--wa-color-red-gte-70), var(--wa-color-red-70));
--wa-color-orange-max-50: color-mix(
--wa-color-yellow-max-40: color-mix(
in oklab,
var(--wa-color-orange) var(--wa-color-orange-lt-50),
var(--wa-color-orange-50)
var(--wa-color-yellow) var(--wa-color-yellow-lt-40),
var(--wa-color-yellow-40)
);
--wa-color-orange-min-50: color-mix(
--wa-color-yellow-min-40: color-mix(
in oklab,
var(--wa-color-orange) var(--wa-color-orange-gte-50),
var(--wa-color-orange-50)
var(--wa-color-yellow) var(--wa-color-yellow-gte-40),
var(--wa-color-yellow-40)
);
--wa-color-orange-max-60: color-mix(
in oklab,
var(--wa-color-orange) var(--wa-color-orange-lt-60),
var(--wa-color-orange-60)
);
--wa-color-orange-min-60: color-mix(
in oklab,
var(--wa-color-orange) var(--wa-color-orange-gte-60),
var(--wa-color-orange-60)
);
--wa-color-orange-max-70: color-mix(
in oklab,
var(--wa-color-orange) var(--wa-color-orange-lt-70),
var(--wa-color-orange-70)
);
--wa-color-orange-min-70: color-mix(
in oklab,
var(--wa-color-orange) var(--wa-color-orange-gte-70),
var(--wa-color-orange-70)
);
--wa-color-yellow-max-50: color-mix(
in oklab,
var(--wa-color-yellow) var(--wa-color-yellow-lt-50),
@@ -154,6 +166,16 @@
var(--wa-color-yellow-70)
);
--wa-color-green-max-40: color-mix(
in oklab,
var(--wa-color-green) var(--wa-color-green-lt-40),
var(--wa-color-green-40)
);
--wa-color-green-min-40: color-mix(
in oklab,
var(--wa-color-green) var(--wa-color-green-gte-40),
var(--wa-color-green-40)
);
--wa-color-green-max-50: color-mix(
in oklab,
var(--wa-color-green) var(--wa-color-green-lt-50),
@@ -185,6 +207,12 @@
var(--wa-color-green-70)
);
--wa-color-cyan-max-40: color-mix(in oklab, var(--wa-color-cyan) var(--wa-color-cyan-lt-40), var(--wa-color-cyan-40));
--wa-color-cyan-min-40: color-mix(
in oklab,
var(--wa-color-cyan) var(--wa-color-cyan-gte-40),
var(--wa-color-cyan-40)
);
--wa-color-cyan-max-50: color-mix(in oklab, var(--wa-color-cyan) var(--wa-color-cyan-lt-50), var(--wa-color-cyan-50));
--wa-color-cyan-min-50: color-mix(
in oklab,
@@ -204,6 +232,12 @@
var(--wa-color-cyan-70)
);
--wa-color-blue-max-40: color-mix(in oklab, var(--wa-color-blue) var(--wa-color-blue-lt-40), var(--wa-color-blue-40));
--wa-color-blue-min-40: color-mix(
in oklab,
var(--wa-color-blue) var(--wa-color-blue-gte-40),
var(--wa-color-blue-40)
);
--wa-color-blue-max-50: color-mix(in oklab, var(--wa-color-blue) var(--wa-color-blue-lt-50), var(--wa-color-blue-50));
--wa-color-blue-min-50: color-mix(
in oklab,
@@ -223,6 +257,16 @@
var(--wa-color-blue-70)
);
--wa-color-indigo-max-40: color-mix(
in oklab,
var(--wa-color-indigo) var(--wa-color-indigo-lt-40),
var(--wa-color-indigo-40)
);
--wa-color-indigo-min-40: color-mix(
in oklab,
var(--wa-color-indigo) var(--wa-color-indigo-gte-40),
var(--wa-color-indigo-40)
);
--wa-color-indigo-max-50: color-mix(
in oklab,
var(--wa-color-indigo) var(--wa-color-indigo-lt-50),
@@ -254,6 +298,16 @@
var(--wa-color-indigo-70)
);
--wa-color-purple-max-40: color-mix(
in oklab,
var(--wa-color-purple) var(--wa-color-purple-lt-40),
var(--wa-color-purple-40)
);
--wa-color-purple-min-40: color-mix(
in oklab,
var(--wa-color-purple) var(--wa-color-purple-gte-40),
var(--wa-color-purple-40)
);
--wa-color-purple-max-50: color-mix(
in oklab,
var(--wa-color-purple) var(--wa-color-purple-lt-50),
@@ -285,6 +339,12 @@
var(--wa-color-purple-70)
);
--wa-color-pink-max-40: color-mix(in oklab, var(--wa-color-pink) var(--wa-color-pink-lt-40), var(--wa-color-pink-40));
--wa-color-pink-min-40: color-mix(
in oklab,
var(--wa-color-pink) var(--wa-color-pink-gte-40),
var(--wa-color-pink-40)
);
--wa-color-pink-max-50: color-mix(in oklab, var(--wa-color-pink) var(--wa-color-pink-lt-50), var(--wa-color-pink-50));
--wa-color-pink-min-50: color-mix(
in oklab,
@@ -304,6 +364,12 @@
var(--wa-color-pink-70)
);
--wa-color-gray-max-40: color-mix(in oklab, var(--wa-color-gray) var(--wa-color-gray-lt-40), var(--wa-color-gray-40));
--wa-color-gray-min-40: color-mix(
in oklab,
var(--wa-color-gray) var(--wa-color-gray-gte-40),
var(--wa-color-gray-40)
);
--wa-color-gray-max-50: color-mix(in oklab, var(--wa-color-gray) var(--wa-color-gray-lt-50), var(--wa-color-gray-50));
--wa-color-gray-min-50: color-mix(
in oklab,
@@ -326,7 +392,6 @@
/* Text color: white if key < 60, {hue}-10 otherwise */
--wa-color-red-on: color-mix(in oklab, var(--wa-color-red-10) var(--wa-color-red-gte-60), white);
--wa-color-orange-on: color-mix(in oklab, var(--wa-color-orange-10) var(--wa-color-orange-gte-60), white);
--wa-color-yellow-on: color-mix(in oklab, var(--wa-color-yellow-10) var(--wa-color-yellow-gte-60), white);
--wa-color-green-on: color-mix(in oklab, var(--wa-color-green-10) var(--wa-color-green-gte-60), white);
--wa-color-cyan-on: color-mix(in oklab, var(--wa-color-cyan-10) var(--wa-color-cyan-gte-60), white);

View File

@@ -0,0 +1,34 @@
{% for class in ['theme', 'palette', 'brand'] %}
:where([class^='wa-{{ class }}-'], [class*=' wa-{{ class }}-']),
{%- endfor %}
:where(:root),
:host {
/**
* Conditional tokens for use in color-mix()
* --wa-color-{hue}-lt-N ➡️ 100% if key < N, 0% otherwise
* --wa-color-{hue}-gte-N ➡️ 100% if key >= N, 0% otherwise
*/
{% for hue in hues %}
{% for tint in ['40', '50', '60', '70', '80'] %}
--wa-color-{{ hue }}-lt-{{ tint }}: calc(clamp(0, {{ tint }} - var(--wa-color-{{ hue }}-key), 1) * 100%);
--wa-color-{{ hue }}-gte-{{ tint }}: calc(100% - var(--wa-color-{{ hue }}-lt-{{ tint }}));
{%- endfor %}
{% endfor %}
/*
* Convenience tokens for common tint cutoffs
* --wa-color-{hue}-N-max ➡️ var(--color-{hue}) if key <= N, var(--color-{hue}-N) otherwise
* --wa-color-{hue}-N-min ➡️ var(--color-{hue}) if key >= N, var(--color-{hue}-N) otherwise
*/
{% for hue in hues %}
{% for tint in ['40', '50', '60', '70'] %}
--wa-color-{{ hue }}-max-{{ tint }}: color-mix(in oklab, var(--wa-color-{{ hue }}) var(--wa-color-{{ hue }}-lt-{{ tint }}), var(--wa-color-{{ hue }}-{{ tint }}));
--wa-color-{{ hue }}-min-{{ tint }}: color-mix(in oklab, var(--wa-color-{{ hue }}) var(--wa-color-{{ hue }}-gte-{{ tint }}), var(--wa-color-{{ hue }}-{{ tint }}));
{%- endfor %}
{%- endfor %}
/* Text color: white if key < 60, {hue}-10 otherwise */
{% for hue in hues %}
--wa-color-{{ hue }}-on: color-mix(in oklab, var(--wa-color-{{ hue }}-10) var(--wa-color-{{ hue }}-gte-60), white);
{%- endfor %}
}

View File

@@ -18,20 +18,6 @@
--wa-color-red: var(--wa-color-red-60);
--wa-color-red-key: 60;
--wa-color-orange-95: oklch(96.406% 0.04001 53.476);
--wa-color-orange-90: oklch(92.395% 0.07984 53.06);
--wa-color-orange-80: oklch(84.389% 0.12224 47.981);
--wa-color-orange-70: oklch(76.55% 0.16521 42.512);
--wa-color-orange-60: #e97331 /* oklch(68.357% 0.16507 46.504) */;
--wa-color-orange-50: #bf5712 /* oklch(57.834% 0.15197 47.326) */;
--wa-color-orange-40: oklch(47.62% 0.132 48.51);
--wa-color-orange-30: oklch(40.38% 0.11554 50);
--wa-color-orange-20: oklch(32.94% 0.09927 52);
--wa-color-orange-10: oklch(24.083% 0.07743 54);
--wa-color-orange-05: oklch(18.817% 0.06098 55);
--wa-color-orange: var(--wa-color-orange-70);
--wa-color-orange-key: 70;
--wa-color-yellow-95: #fff4c0 /* oklch(96.32% 0.0677 97.497) */;
--wa-color-yellow-90: #ffe579 /* oklch(92.176% 0.13122 96.089) */;
--wa-color-yellow-80: #ffbf18 /* oklch(84.069% 0.16897 83.446) */;

View File

@@ -6,45 +6,31 @@
.wa-palette-classic {
--wa-color-red-95: #fff0ef /* oklch(96.667% 0.01632 22.08) */;
--wa-color-red-90: #ffdedc /* oklch(92.735% 0.03679 21.966) */;
--wa-color-red-80: #ffb8b5 /* oklch(84.778% 0.083 21.686) */;
--wa-color-red-70: #fe8f8d /* oklch(76.859% 0.13466 21.762) */;
--wa-color-red-60: #f56667 /* oklch(68.982% 0.17631 22.472) */;
--wa-color-red-50: #e02c2b /* oklch(58.861% 0.21461 27.156) */;
--wa-color-red-40: #b5051a /* oklch(48.833% 0.19611 25.68) */;
--wa-color-red-30: #900015 /* oklch(41.172% 0.16676 24.609) */;
--wa-color-red-20: #6c000d /* oklch(33.479% 0.1356 24.617) */;
--wa-color-red-10: #450005 /* oklch(24.598% 0.09968 24.835) */;
--wa-color-red-05: #2f0002 /* oklch(19.218% 0.07801 25.517) */;
--wa-color-red-80: #ffb8b4 /* oklch(84.753% 0.08313 22.598) */;
--wa-color-red-70: #fd908e /* oklch(76.913% 0.13219 21.705) */;
--wa-color-red-60: #f46766 /* oklch(68.945% 0.17423 23.179) */;
--wa-color-red-50: #df2f2e /* oklch(58.922% 0.21141 26.911) */;
--wa-color-red-40: #b60000 /* oklch(48.747% 0.20003 29.234) */;
--wa-color-red-30: #910000 /* oklch(41.235% 0.16921 29.234) */;
--wa-color-red-20: #6d0000 /* oklch(33.581% 0.1378 29.234) */;
--wa-color-red-10: #450000 /* oklch(24.517% 0.1006 29.234) */;
--wa-color-red-05: #2f0000 /* oklch(19.165% 0.07864 29.234) */;
--wa-color-red: var(--wa-color-red-50);
--wa-color-red-key: 50;
--wa-color-orange-95: #fff0e4 /* oklch(96.374% 0.0228 61.238) */;
--wa-color-orange-90: #ffe0c8 /* oklch(92.611% 0.04689 59.917) */;
--wa-color-orange-80: #ffbb89 /* oklch(84.386% 0.10217 57.161) */;
--wa-color-orange-70: #ff9342 /* oklch(76.486% 0.15964 54.102) */;
--wa-color-orange-60: #f36d00 /* oklch(68.715% 0.18774 47.79) */;
--wa-color-orange-50: #c94e00 /* oklch(58.068% 0.17131 43.217) */;
--wa-color-orange-40: #9d3800 /* oklch(47.924% 0.14534 41.739) */;
--wa-color-orange-30: #7e2900 /* oklch(40.53% 0.1259 40.51) */;
--wa-color-orange-20: #5e1c00 /* oklch(32.874% 0.1027 40.228) */;
--wa-color-orange-10: #3b0f00 /* oklch(24.125% 0.07446 40.837) */;
--wa-color-orange-05: #280700 /* oklch(18.837% 0.05933 39.827) */;
--wa-color-orange: var(--wa-color-orange-60);
--wa-color-orange-key: 60;
--wa-color-yellow-95: #fef2bf /* oklch(95.823% 0.06674 96.369) */;
--wa-color-yellow-90: #fde588 /* oklch(92.2% 0.11633 95.327) */;
--wa-color-yellow-80: #f5c24b /* oklch(83.879% 0.14445 85.083) */;
--wa-color-yellow-80: #f4c34e /* oklch(83.998% 0.14252 85.76) */;
--wa-color-yellow-70: #e9a010 /* oklch(75.825% 0.15689 75.537) */;
--wa-color-yellow-60: #d78000 /* oklch(67.839% 0.15287 64.455) */;
--wa-color-yellow-50: #b26000 /* oklch(57.324% 0.13672 58.338) */;
--wa-color-yellow-40: #8a4700 /* oklch(47.183% 0.11461 56.655) */;
--wa-color-yellow-30: #6e3700 /* oklch(40.03% 0.0976 56.323) */;
--wa-color-yellow-20: #522700 /* oklch(32.54% 0.07981 55.802) */;
--wa-color-yellow-10: #331600 /* oklch(23.846% 0.05834 56.02) */;
--wa-color-yellow-05: #210d00 /* oklch(18.619% 0.04431 58.553) */;
--wa-color-yellow: var(--wa-color-yellow-70);
--wa-color-yellow-key: 70;
--wa-color-yellow-60: #de7c00 /* oklch(68.073% 0.15991 59.827) */;
--wa-color-yellow-50: #bc5908 /* oklch(57.654% 0.1491 50.241) */;
--wa-color-yellow-40: #934107 /* oklch(47.603% 0.12628 47.819) */;
--wa-color-yellow-30: #763104 /* oklch(40.324% 0.1093 46.564) */;
--wa-color-yellow-20: #572301 /* oklch(32.677% 0.08796 47.926) */;
--wa-color-yellow-10: #371300 /* oklch(24.001% 0.06559 47.77) */;
--wa-color-yellow-05: #250a00 /* oklch(18.773% 0.0519 47.039) */;
--wa-color-yellow: var(--wa-color-yellow-60);
--wa-color-yellow-key: 60;
--wa-color-green-95: #d4fce1 /* oklch(95.554% 0.05477 155.71) */;
--wa-color-green-90: #a4f8c2 /* oklch(91.11% 0.1107 155.35) */;

View File

@@ -18,20 +18,6 @@
--wa-color-red: var(--wa-color-red-50);
--wa-color-red-key: 50;
--wa-color-orange-95: #fff0e6 /* oklch(96.426% 0.02105 56.133) */;
--wa-color-orange-90: #ffdfca /* oklch(92.468% 0.04529 55.325) */;
--wa-color-orange-80: #ffbb94 /* oklch(84.588% 0.09454 50.876) */;
--wa-color-orange-70: #ff9266 /* oklch(76.744% 0.14429 42.309) */;
--wa-color-orange-60: #f46a45 /* oklch(68.848% 0.17805 35.951) */;
--wa-color-orange-50: #cd491c /* oklch(58.195% 0.17597 37.577) */;
--wa-color-orange-40: #9f3501 /* oklch(47.889% 0.14981 39.957) */;
--wa-color-orange-30: #802700 /* oklch(40.637% 0.1298 39.149) */;
--wa-color-orange-20: #601b00 /* oklch(33.123% 0.10587 39.117) */;
--wa-color-orange-10: #3c0d00 /* oklch(24.043% 0.07768 38.607) */;
--wa-color-orange-05: #280600 /* oklch(18.644% 0.0607 38.252) */;
--wa-color-orange: var(--wa-color-orange-60);
--wa-color-orange-key: 60;
--wa-color-yellow-95: #fef3cd /* oklch(96.322% 0.05069 93.748) */;
--wa-color-yellow-90: #ffe495 /* oklch(92.377% 0.10246 91.296) */;
--wa-color-yellow-80: #fac22b /* oklch(84.185% 0.16263 85.991) */;

View File

@@ -18,20 +18,6 @@
--wa-color-red: var(--wa-color-red-40);
--wa-color-red-key: 40;
--wa-color-orange-95: #ffefe1 /* oklch(96.105% 0.02545 63.746) */;
--wa-color-orange-90: #fbe0cb /* oklch(92.334% 0.04096 60.142) */;
--wa-color-orange-80: #efc19f /* oklch(84.313% 0.06973 58.09) */;
--wa-color-orange-70: #e1a173 /* oklch(76.013% 0.09772 56.368) */;
--wa-color-orange-60: #d1824d /* oklch(67.937% 0.11938 53.195) */;
--wa-color-orange-50: #b65d22 /* oklch(57.543% 0.13444 49.914) */;
--wa-color-orange-40: #934107 /* oklch(47.603% 0.12628 47.819) */;
--wa-color-orange-30: #773001 /* oklch(40.317% 0.11198 46.326) */;
--wa-color-orange-20: #592100 /* oklch(32.692% 0.09239 45.689) */;
--wa-color-orange-10: #381200 /* oklch(24.015% 0.06777 45.771) */;
--wa-color-orange-05: #260900 /* oklch(18.789% 0.05427 44.405) */;
--wa-color-orange: var(--wa-color-orange-50);
--wa-color-orange-key: 50;
--wa-color-yellow-95: #f7f4da /* oklch(96.266% 0.03422 101.63) */;
--wa-color-yellow-90: #ede7c3 /* oklch(92.349% 0.04769 99.435) */;
--wa-color-yellow-80: #d8ca96 /* oklch(83.793% 0.06999 94.829) */;

View File

@@ -18,20 +18,6 @@
--wa-color-red: var(--wa-color-red-40);
--wa-color-red-key: 40;
--wa-color-orange-95: #fff0e8 /* oklch(96.479% 0.01949 50.157) */;
--wa-color-orange-90: #ffdfc8 /* oklch(92.42% 0.04692 57.822) */;
--wa-color-orange-80: #ffbb84 /* oklch(84.3% 0.10585 59.641) */;
--wa-color-orange-70: #e79f64 /* oklch(76.075% 0.1143 59.207) */;
--wa-color-orange-60: #ca854c /* oklch(67.612% 0.11209 58.789) */;
--wa-color-orange-50: #a56732 /* oklch(57.133% 0.10488 58.75) */;
--wa-color-orange-40: #824c1a /* oklch(47.091% 0.09621 59.168) */;
--wa-color-orange-30: #6a3907 /* oklch(39.832% 0.09012 58.688) */;
--wa-color-orange-20: #512800 /* oklch(32.57% 0.07814 57.787) */;
--wa-color-orange-10: #331600 /* oklch(23.846% 0.05834 56.02) */;
--wa-color-orange-05: #220c00 /* oklch(18.585% 0.04625 54.588) */;
--wa-color-orange: var(--wa-color-orange-70);
--wa-color-orange-key: 70;
--wa-color-yellow-95: #fff4b3 /* oklch(96.064% 0.08319 99.657) */;
--wa-color-yellow-90: #fee58c /* oklch(92.346% 0.11242 94.205) */;
--wa-color-yellow-80: #e3c868 /* oklch(83.63% 0.12003 93.943) */;

View File

@@ -18,20 +18,6 @@
--wa-color-red: var(--wa-color-red-50);
--wa-color-red-key: 50;
--wa-color-orange-95: #f9f2ed /* oklch(96.52% 0.01008 58.217) */;
--wa-color-orange-90: #f4e3d6 /* oklch(92.595% 0.02556 59.903) */;
--wa-color-orange-80: #e6c3aa /* oklch(84.087% 0.05242 57.474) */;
--wa-color-orange-70: #d3a685 /* oklch(75.824% 0.06976 57.703) */;
--wa-color-orange-60: #bf8962 /* oklch(67.428% 0.08515 56.553) */;
--wa-color-orange-50: #a26839 /* oklch(57.025% 0.09733 58.071) */;
--wa-color-orange-40: #824b20 /* oklch(46.959% 0.09366 55.634) */;
--wa-color-orange-30: #683a17 /* oklch(39.855% 0.08077 54.814) */;
--wa-color-orange-20: #4d2a0e /* oklch(32.469% 0.06563 55.984) */;
--wa-color-orange-10: #301705 /* oklch(23.621% 0.04968 54.306) */;
--wa-color-orange-05: #1f0e03 /* oklch(18.572% 0.03671 56.067) */;
--wa-color-orange: var(--wa-color-orange-50);
--wa-color-orange-key: 50;
--wa-color-yellow-95: #f6f1ea /* oklch(96.009% 0.01076 76.598) */;
--wa-color-yellow-90: #eee5d7 /* oklch(92.521% 0.02111 79.091) */;
--wa-color-yellow-80: #dbc8a8 /* oklch(84.033% 0.04791 80.753) */;

View File

@@ -18,20 +18,6 @@
--wa-color-red: var(--wa-color-red-50);
--wa-color-red-key: 50;
--wa-color-orange-95: #fff1e4 /* oklch(96.567% 0.02297 65.466) */;
--wa-color-orange-90: #ffdfc4 /* oklch(92.327% 0.0504 62.301) */;
--wa-color-orange-80: #ffbc80 /* oklch(84.418% 0.10871 62.363) */;
--wa-color-orange-70: #ff9427 /* oklch(76.467% 0.1689 59.353) */;
--wa-color-orange-60: #f16e00 /* oklch(68.563% 0.18516 48.551) */;
--wa-color-orange-50: #c75003 /* oklch(58.025% 0.16741 44.08) */;
--wa-color-orange-40: #9e3702 /* oklch(47.974% 0.14693 40.809) */;
--wa-color-orange-30: #802700 /* oklch(40.637% 0.1298 39.149) */;
--wa-color-orange-20: #601a00 /* oklch(32.994% 0.10679 38.524) */;
--wa-color-orange-10: #3d0d00 /* oklch(24.253% 0.07888 38.298) */;
--wa-color-orange-05: #290600 /* oklch(18.868% 0.06197 37.848) */;
--wa-color-orange: var(--wa-color-orange-60);
--wa-color-orange-key: 60;
--wa-color-yellow-95: #fff5b4 /* oklch(96.281% 0.08306 100.4) */;
--wa-color-yellow-90: #fde572 /* oklch(91.915% 0.13738 97.724) */;
--wa-color-yellow-80: #f4c403 /* oklch(83.862% 0.17104 90.777) */;

View File

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

View File

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

View File

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

View File

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

View File

@@ -18,20 +18,6 @@
--wa-color-red: var(--wa-color-red-50);
--wa-color-red-key: 50;
--wa-color-orange-95: #fff1de /* oklch(96.415% 0.02927 75.692) */;
--wa-color-orange-90: #fee0bc /* oklch(92.25% 0.05764 72.084) */;
--wa-color-orange-80: #fdbe7a /* oklch(84.485% 0.11179 67.463) */;
--wa-color-orange-70: #fb9641 /* oklch(76.472% 0.15504 57.097) */;
--wa-color-orange-60: #f56b11 /* oklch(68.77% 0.18951 45.673) */;
--wa-color-orange-50: #cf480e /* oklch(58.335% 0.18077 39.019) */;
--wa-color-orange-40: #a13308 /* oklch(48.025% 0.15268 37.837) */;
--wa-color-orange-30: #802604 /* oklch(40.538% 0.12998 37.701) */;
--wa-color-orange-20: #601a02 /* oklch(33.012% 0.10627 37.613) */;
--wa-color-orange-10: #3d0d01 /* oklch(24.269% 0.07841 37.157) */;
--wa-color-orange-05: #290600 /* oklch(18.868% 0.06197 37.848) */;
--wa-color-orange: var(--wa-color-orange-60);
--wa-color-orange-key: 60;
--wa-color-yellow-95: #fef6ab /* oklch(96.234% 0.09455 102.83) */;
--wa-color-yellow-90: #fde761 /* oklch(92.138% 0.15325 99.997) */;
--wa-color-yellow-80: #f5c308 /* oklch(83.774% 0.17019 89.81) */;

View File

@@ -1,36 +0,0 @@
:where(:root),
:host,
:where([class^='wa-theme-'], [class*=' wa-theme-']),
:where([class^='wa-palette-'], [class*=' wa-palette-']),
.wa-danger-blue {
--wa-color-danger-95: var(--wa-color-blue-95);
--wa-color-danger-90: var(--wa-color-blue-90);
--wa-color-danger-80: var(--wa-color-blue-80);
--wa-color-danger-70: var(--wa-color-blue-70);
--wa-color-danger-60: var(--wa-color-blue-60);
--wa-color-danger-50: var(--wa-color-blue-50);
--wa-color-danger-40: var(--wa-color-blue-40);
--wa-color-danger-30: var(--wa-color-blue-30);
--wa-color-danger-20: var(--wa-color-blue-20);
--wa-color-danger-10: var(--wa-color-blue-10);
--wa-color-danger-05: var(--wa-color-blue-05);
--wa-color-danger: var(--wa-color-blue);
--wa-color-danger-lt-50: var(--wa-color-blue-lt-50);
--wa-color-danger-gte-50: var(--wa-color-blue-gte-50);
--wa-color-danger-lt-60: var(--wa-color-blue-lt-60);
--wa-color-danger-gte-60: var(--wa-color-blue-gte-60);
--wa-color-danger-lt-70: var(--wa-color-blue-lt-70);
--wa-color-danger-gte-70: var(--wa-color-blue-gte-70);
--wa-color-danger-max-50: var(--wa-color-blue-max-50);
--wa-color-danger-min-50: var(--wa-color-blue-min-50);
--wa-color-danger-max-60: var(--wa-color-blue-max-60);
--wa-color-danger-min-60: var(--wa-color-blue-min-60);
--wa-color-danger-max-70: var(--wa-color-blue-max-70);
--wa-color-danger-min-70: var(--wa-color-blue-min-70);
--wa-color-danger-on: var(--wa-color-blue-on);
}

View File

@@ -1,36 +0,0 @@
:where(:root),
:host,
:where([class^='wa-theme-'], [class*=' wa-theme-']),
:where([class^='wa-palette-'], [class*=' wa-palette-']),
.wa-danger-cyan {
--wa-color-danger-95: var(--wa-color-cyan-95);
--wa-color-danger-90: var(--wa-color-cyan-90);
--wa-color-danger-80: var(--wa-color-cyan-80);
--wa-color-danger-70: var(--wa-color-cyan-70);
--wa-color-danger-60: var(--wa-color-cyan-60);
--wa-color-danger-50: var(--wa-color-cyan-50);
--wa-color-danger-40: var(--wa-color-cyan-40);
--wa-color-danger-30: var(--wa-color-cyan-30);
--wa-color-danger-20: var(--wa-color-cyan-20);
--wa-color-danger-10: var(--wa-color-cyan-10);
--wa-color-danger-05: var(--wa-color-cyan-05);
--wa-color-danger: var(--wa-color-cyan);
--wa-color-danger-lt-50: var(--wa-color-cyan-lt-50);
--wa-color-danger-gte-50: var(--wa-color-cyan-gte-50);
--wa-color-danger-lt-60: var(--wa-color-cyan-lt-60);
--wa-color-danger-gte-60: var(--wa-color-cyan-gte-60);
--wa-color-danger-lt-70: var(--wa-color-cyan-lt-70);
--wa-color-danger-gte-70: var(--wa-color-cyan-gte-70);
--wa-color-danger-max-50: var(--wa-color-cyan-max-50);
--wa-color-danger-min-50: var(--wa-color-cyan-min-50);
--wa-color-danger-max-60: var(--wa-color-cyan-max-60);
--wa-color-danger-min-60: var(--wa-color-cyan-min-60);
--wa-color-danger-max-70: var(--wa-color-cyan-max-70);
--wa-color-danger-min-70: var(--wa-color-cyan-min-70);
--wa-color-danger-on: var(--wa-color-cyan-on);
}

View File

@@ -1,36 +0,0 @@
:where(:root),
:host,
:where([class^='wa-theme-'], [class*=' wa-theme-']),
:where([class^='wa-palette-'], [class*=' wa-palette-']),
.wa-danger-gray {
--wa-color-danger-95: var(--wa-color-gray-95);
--wa-color-danger-90: var(--wa-color-gray-90);
--wa-color-danger-80: var(--wa-color-gray-80);
--wa-color-danger-70: var(--wa-color-gray-70);
--wa-color-danger-60: var(--wa-color-gray-60);
--wa-color-danger-50: var(--wa-color-gray-50);
--wa-color-danger-40: var(--wa-color-gray-40);
--wa-color-danger-30: var(--wa-color-gray-30);
--wa-color-danger-20: var(--wa-color-gray-20);
--wa-color-danger-10: var(--wa-color-gray-10);
--wa-color-danger-05: var(--wa-color-gray-05);
--wa-color-danger: var(--wa-color-gray);
--wa-color-danger-lt-50: var(--wa-color-gray-lt-50);
--wa-color-danger-gte-50: var(--wa-color-gray-gte-50);
--wa-color-danger-lt-60: var(--wa-color-gray-lt-60);
--wa-color-danger-gte-60: var(--wa-color-gray-gte-60);
--wa-color-danger-lt-70: var(--wa-color-gray-lt-70);
--wa-color-danger-gte-70: var(--wa-color-gray-gte-70);
--wa-color-danger-max-50: var(--wa-color-gray-max-50);
--wa-color-danger-min-50: var(--wa-color-gray-min-50);
--wa-color-danger-max-60: var(--wa-color-gray-max-60);
--wa-color-danger-min-60: var(--wa-color-gray-min-60);
--wa-color-danger-max-70: var(--wa-color-gray-max-70);
--wa-color-danger-min-70: var(--wa-color-gray-min-70);
--wa-color-danger-on: var(--wa-color-gray-on);
}

Some files were not shown because too many files have changed in this diff Show More